diff --git a/.clang-format b/.clang-format index 686111529a9..c3a9fa31eb5 100644 --- a/.clang-format +++ b/.clang-format @@ -3,7 +3,7 @@ AllowShortFunctionsOnASingleLine: Inline AlwaysBreakTemplateDeclarations: Yes BasedOnStyle: LLVM BreakConstructorInitializers: BeforeComma -ColumnLimit: 100 +ColumnLimit: 0 ConstructorInitializerAllOnOneLineOrOnePerLine: true IndentWidth: 4 IndentAccessModifiers: false diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000000..15f67b986f1 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,73 @@ +FROM mcr.microsoft.com/devcontainers/base:ubuntu-22.04 + +ENV DEBIAN_FRONTEND=noninteractive + +# Install some basics +RUN apt-get update \ + && apt-get install -y \ + wget \ + curl \ + git \ + vim \ + unzip \ + xz-utils \ + software-properties-common \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Add latest cmake +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc | apt-key add - \ + && apt-add-repository "deb https://apt.kitware.com/ubuntu/ $(lsb_release -sc) main" + +# Install required packages for dev +RUN apt-get update \ + && apt-get install -y \ + build-essential \ + libtool autoconf pkg-config \ + ninja-build \ + ruby-full \ + clang-14 \ + llvm-14 \ + libc++-dev libc++abi-dev \ + cmake \ + libboost-all-dev \ + ccache \ + # Swift dependencies + binutils \ + git \ + gnupg2 \ + libc6-dev \ + libcurl4-openssl-dev \ + libedit2 \ + libgcc-9-dev \ + libpython3.8 \ + libsqlite3-0 \ + libstdc++-9-dev \ + libxml2-dev \ + libz3-dev \ + pkg-config \ + tzdata \ + unzip \ + zlib1g-dev \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +ENV CC=/usr/bin/clang-14 +ENV CXX=/usr/bin/clang++-14 + +# Instal Swift +RUN curl -sSL \ + https://download.swift.org/swift-5.8-release/ubuntu2204/swift-5.8-RELEASE/swift-5.8-RELEASE-ubuntu22.04.tar.gz \ + -o swift-5.8.tar.gz && \ + mkdir -p swift-5.8 && \ + tar -xzf swift-5.8.tar.gz -C swift-5.8 --strip-components=1 && \ + mv swift-5.8 /usr/share/swift + +ENV PATH="/usr/share/swift/usr/bin:${PATH}" + +USER vscode + +# Install rust +RUN curl https://sh.rustup.rs -sSf | sh -s -- --no-modify-path -y +ENV PATH="/home/vscode/.cargo/bin:${PATH}" +RUN cargo install --force cbindgen \ + && rustup target add wasm32-unknown-emscripten diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000000..6bf6d7c88ac --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,31 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/debian +{ + "name": "Debian", + // Use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "build": { + "dockerfile": "Dockerfile" + }, + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": [ + "rust-lang.rust-analyzer", + "sswg.swift-lang", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "rebornix.Ruby" + ] + } + }, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + "remoteUser": "vscode" +} diff --git a/.dockerignore b/.dockerignore new file mode 120000 index 00000000000..3e4e48b0b5f --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.gitignore \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 33536958aad..1724b9a1b7e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,4 +3,5 @@ # @global-owner1 and @global-owner2 will be requested for # review when someone opens a pull request. -* @hewigovens @catenocrypt +* @milerius @satoshiotomakan @ar-g +kotlin/ @ar-g @JaimeToca @rkokhatskyi diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 11fc491ef1d..16161265dae 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -16,5 +16,23 @@ A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. +**Checklist** + + + + +- [ ] task number 1 + - [ ] subtask number 1 + - [ ] subtask number 2 +- [ ] task number 2 +- [ ] task number 3 + +**Resources** + + + +Resources link + **Additional context** + Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index 56badb9d825..b8d6bb34c8a 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -2,43 +2,82 @@ name: Android CI on: push: - branches: [ master ] + branches: [ dev, master ] pull_request: - branches: [ master ] + branches: [ dev, master ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: build: - - runs-on: macos-11 - + runs-on: macos-latest-large + if: github.event.pull_request.draft == false steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 with: - java-version: 11 + java-version: '17' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + - name: Install system dependencies - run: brew install boost ninja - - name: Install Android Dependencies run: | - tools/install-android-dependencies + tools/install-sys-dependencies-mac + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + + - name: Install Rust dependencies + run: | + tools/install-rust-dependencies + + - name: Install Android Dependencies + run: tools/install-android-dependencies + - name: Cache internal dependencies id: internal_cache - uses: actions/cache@v1.1.2 + uses: actions/cache@v3 with: path: build/local - key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + key: ${{ runner.os }}-${{ runner.arch }}-internal-${{ hashFiles('tools/install-dependencies') }} + - name: Install internal dependencies - run: | - tools/install-dependencies + run: tools/install-dependencies if: steps.internal_cache.outputs.cache-hit != 'true' - - name: Run test + + - name: Generate files + run: tools/generate-files android + + - name: Build Kotlin doc + run: tools/kotlin-doc + + - name: Build tests run: | - tools/generate-files - tools/android-test + pushd android + ./gradlew assembleAndroidTest + popd + + - name: Run tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 30 + target: google_apis + arch: x86 + ndk: 23.1.7779620 + cmake: 3.18.1 + script: cd android; ./gradlew connectedAndroidTest + - name: Build sample app - run: | - tools/samples-build android + run: tools/samples-build android env: GITHUB_USER: ${{ github.actor }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/codegen-v2.yml b/.github/workflows/codegen-v2.yml new file mode 100644 index 00000000000..50446778240 --- /dev/null +++ b/.github/workflows/codegen-v2.yml @@ -0,0 +1,47 @@ +name: Codegen-v2 Tests + +on: + push: + branches: [ dev, master ] + pull_request: + branches: [ dev, master ] + +env: + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: "sccache" + +jobs: + test: + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false + steps: + - uses: actions/checkout@v3 + - name: Install system dependencies + run: | + tools/install-sys-dependencies-linux + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.3 + + - name: Install Rust dependencies + run: | + tools/install-rust-dependencies + + - name: Run codegen-v2 tests + run: | + cargo test --all + working-directory: codegen-v2 + + # Generate files for a blockchain. + # Please note the blockchain should not be implemented in Rust at the moment of running this step, + # otherwise consider either generating files for another blockchain or removing this step at all. + - name: Test codegen-v2 new-blockchain-rust + run: | + cargo run -- new-blockchain-rust iotex + working-directory: codegen-v2 + + # Check if `new-blockchain-rust` command has generated files that do not break project compilation. + - name: Check Rust compiles + run: | + cargo check --tests + working-directory: rust diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 369efae9cbd..db3513b2a17 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -2,13 +2,10 @@ name: Docker CI on: push: - branches: [ master ] - paths: - - Dockerfile - - tools/install-dependencies + branches: [ dev, master ] pull_request: - branches: [ master ] - paths: + branches: [ dev, master ] + paths: - Dockerfile - tools/install-dependencies @@ -16,15 +13,14 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Lint Dockerfile - run: | - curl -L https://github.com/hadolint/hadolint/releases/download/v1.17.6/hadolint-Linux-x86_64 -o hadolint && chmod +x hadolint - ./hadolint Dockerfile - - - name: Build Dockerfile - uses: docker/build-push-action@v1.1.0 - with: - repository: trustwallet/wallet-core - tags: latest - push: false + - uses: actions/checkout@v3 + - name: Lint Dockerfile + run: | + curl -L https://github.com/hadolint/hadolint/releases/download/v1.17.6/hadolint-Linux-x86_64 -o hadolint && chmod +x hadolint + ./hadolint Dockerfile + - name: Build Dockerfile + uses: docker/build-push-action@v1.1.0 + with: + repository: trustwallet/wallet-core + tags: latest + push: false diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 21d4ca246a7..649b15e84a2 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -2,34 +2,55 @@ name: iOS CI on: push: - branches: [ master ] + branches: [ dev, master ] pull_request: - branches: [ master ] + branches: [ dev, master ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: build: - runs-on: macos-11 + runs-on: macos-latest-xlarge + if: github.event.pull_request.draft == false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - name: Install system dependencies run: | - brew install boost ninja xcodegen xcbeautify + tools/install-sys-dependencies-mac + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + + - name: Install Rust dependencies + run: | + tools/install-rust-dependencies + - name: Cache internal dependencies id: internal_cache - uses: actions/cache@v1.1.2 + uses: actions/cache@v3 with: path: build/local key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + - name: Install internal dependencies run: | tools/install-dependencies if: steps.internal_cache.outputs.cache-hit != 'true' + - name: Run codegen tests run: tools/codegen-test + - name: Run iOS tests run: | - tools/generate-files + tools/generate-files ios tools/ios-test + - name: Build sample app run: | tools/samples-build ios diff --git a/.github/workflows/kotlin-ci.yml b/.github/workflows/kotlin-ci.yml new file mode 100644 index 00000000000..1198c20ae3c --- /dev/null +++ b/.github/workflows/kotlin-ci.yml @@ -0,0 +1,83 @@ +name: Kotlin CI + +on: + push: + branches: [ dev, master ] + pull_request: + branches: [ dev, master ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: macos-latest-xlarge + if: github.event.pull_request.draft == false + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + with: + gradle-version: 8.1.1 + + - name: Install system dependencies + run: | + tools/install-sys-dependencies-mac + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + + - name: Install Rust dependencies + run: | + tools/install-rust-dependencies + + - name: Install emsdk + run: tools/install-wasm-dependencies + + - name: Install Kotlin Dependencies + run: tools/install-kotlin-dependencies + + - name: Cache internal dependencies + id: internal_cache + uses: actions/cache@v3 + with: + path: build/local + key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + + - name: Install internal dependencies + run: tools/install-dependencies + if: steps.internal_cache.outputs.cache-hit != 'true' + + - name: Generate files + run: | + source emsdk/emsdk_env.sh + tools/generate-files + + - name: CMake (Java, Kotlin) + run: | + cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DTW_UNITY_BUILD=ON -DTW_COMPILE_JAVA=ON -DTW_COMPILE_KOTLIN=ON -GNinja + + - name: Build JNI + run: | + ninja -Cbuild + mv build/libTrustWalletCore.dylib kotlin/wallet-core-kotlin/src/jvmMain/resources/jni/macos-arm64/ + + - name: Build Kotlin Multiplatform + run: tools/kotlin-build + + - name: Run Kotlin Multiplatform tests + run: tools/kotlin-test diff --git a/.github/workflows/kotlin-sample-ci.yml b/.github/workflows/kotlin-sample-ci.yml new file mode 100644 index 00000000000..f96b2a4b280 --- /dev/null +++ b/.github/workflows/kotlin-sample-ci.yml @@ -0,0 +1,39 @@ +name: Kotlin Multiplatform CI + +on: + push: + branches: [ dev, master ] + pull_request: + branches: [ dev, master ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: macos-latest-xlarge + if: github.event.pull_request.draft == false + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Install Kotlin Dependencies + run: tools/install-kotlin-dependencies + + - name: Build KMP Sample + run: | + ./gradlew --version + ./gradlew assemble + working-directory: samples/kmp + env: + GITHUB_USER: ${{ github.actor }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/linux-ci-rust.yml b/.github/workflows/linux-ci-rust.yml new file mode 100644 index 00000000000..36d3e57931a --- /dev/null +++ b/.github/workflows/linux-ci-rust.yml @@ -0,0 +1,161 @@ +name: Linux CI Rust + +on: + push: + branches: [ dev, master ] + pull_request: + branches: [ dev, master ] + +env: + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: "sccache" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # Check formatting, clippy warnings, run tests and check code coverage. + build-and-test: + permissions: + contents: read + checks: write + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false + steps: + - uses: actions/checkout@v3 + - name: Install system dependencies + run: | + tools/install-sys-dependencies-linux + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.3 + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + + - name: Install Rust dependencies + run: | + tools/install-rust-dependencies dev + + - name: Check code formatting + run: | + cargo fmt --check + working-directory: rust + + - name: Check Clippy warnings + run: | + cargo clippy -- -D warnings + working-directory: rust + + - name: Run tests + run: | + tools/rust-coverage + + - name: Gather and check Rust code coverage + run: | + tools/check-coverage rust/coverage.stats rust/coverage.info + + # Run Rust tests in WASM. + test-wasm: + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false + steps: + - uses: actions/checkout@v3 + - name: Install system dependencies + run: | + tools/install-sys-dependencies-linux + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.3 + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + + - name: Install Rust dependencies + run: | + tools/install-rust-dependencies + + - name: Install emsdk + run: tools/install-wasm-dependencies + + - name: Run tests in WASM + run: tools/rust-test wasm + + check-binary-sizes: + permissions: + contents: read + pull-requests: write + runs-on: macos-latest-xlarge + if: github.event.pull_request.draft == false + steps: + - uses: actions/checkout@v3 + - name: Install system dependencies + run: | + tools/install-sys-dependencies-mac + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.3 + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + + - name: Install Rust dependencies + run: tools/install-rust-dependencies + + - name: Install emsdk + run: tools/install-wasm-dependencies + + - name: Compile release binaries + run: | + mkdir -p build/local/lib + source emsdk/emsdk_env.sh + tools/rust-bindgen + + - name: Generate release report + run: | + ./tools/release-size measure-rust > release-report.json + + - name: Upload release report + uses: actions/upload-artifact@v4 + with: + name: release_report + path: release-report.json + + # Download previous release report, compare the release binary sizes, and post/update a comment at the Pull Request. + - name: Download previous release report + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false + uses: dawidd6/action-download-artifact@v6 + with: + commit: ${{github.event.pull_request.base.sha}} + path: previous + if_no_artifact_found: warn + # Same artifact name as at the "Upload release report" step. + name: release_report + # Ignore status or conclusion in the search. + workflow_conclusion: "" + + - name: Craft Comment Body + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false + run: | + # Please note `previous/release-report.json` may not exist if the previous report was not found. + ./tools/release-size compare --before previous/release-report.json --current release-report.json > report-diff.md + + - name: Create or Update Comment + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false + uses: edumserrano/find-create-or-update-comment@v2 + with: + issue-number: ${{ github.event.pull_request.number }} + body-includes: "Binary size comparison" + comment-author: 'github-actions[bot]' + edit-mode: replace + body-path: 'report-diff.md' diff --git a/.github/workflows/linux-ci-sonarcloud.yml b/.github/workflows/linux-ci-sonarcloud.yml new file mode 100644 index 00000000000..775486d741a --- /dev/null +++ b/.github/workflows/linux-ci-sonarcloud.yml @@ -0,0 +1,66 @@ +name: Linux CI SonarCloud + +on: + push: + branches: [ dev, master ] + pull_request: + branches: [ dev, master ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.fork == false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Install system dependencies + run: | + tools/install-sys-dependencies-linux + tools/install-rust-dependencies + + - name: Cache internal dependencies + id: internal_cache + uses: actions/cache@v3 + with: + path: build/local + key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + + - name: Install internal dependencies + run: | + tools/install-dependencies + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + if: steps.internal_cache.outputs.cache-hit != 'true' + + - name: Code generation + run: | + tools/generate-files native + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + + - name: CMake (coverage/clang-tidy/clang-asan) + run: | + cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DTW_CODE_COVERAGE=ON -DTW_ENABLE_CLANG_TIDY=ON -DTW_CLANG_ASAN=ON -GNinja + cat build/compile_commands.json + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + + - name: SonarCloud Scan + run: | + ./tools/sonarcloud-analysis + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 7071ebed20c..653fbf0c135 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -2,25 +2,30 @@ name: Linux CI on: push: - branches: [ master ] + branches: [ dev, master ] pull_request: - branches: [ master ] + branches: [ dev, master ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install system dependencies run: | - # build-essential clang-11 libc++-dev libc++abi-dev ruby-full cmake - sudo apt-get update && sudo apt-get install ninja-build lcov llvm-11 clang-tidy-11 libboost-all-dev --fix-missing + tools/install-sys-dependencies-linux ci + tools/install-rust-dependencies - name: Cache internal dependencies id: internal_cache - uses: actions/cache@v1.1.2 + uses: actions/cache@v3 with: path: build/local - key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-sys-dependencies-linux') }}-${{ hashFiles('tools/install-dependencies') }} - name: Install internal dependencies run: | tools/install-dependencies @@ -28,31 +33,30 @@ jobs: CC: /usr/bin/clang CXX: /usr/bin/clang++ if: steps.internal_cache.outputs.cache-hit != 'true' + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + rust + - name: Code generation run: | - tools/generate-files + tools/generate-files native env: CC: /usr/bin/clang CXX: /usr/bin/clang++ - name: CMake (coverage/clang-tidy/clang-asan) - if: github.ref == 'refs/heads/master' - run: | - cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DCODE_COVERAGE=ON -DCLANG_TIDY=ON -DCLANG_ASAN=ON - env: - CC: /usr/bin/clang - CXX: /usr/bin/clang++ - - name: CMake (coverage) - if: github.ref != 'refs/heads/master' run: | - cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DCODE_COVERAGE=ON + cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DTW_UNITY_BUILD=ON -DTW_CODE_COVERAGE=ON -DTW_ENABLE_CLANG_TIDY=ON -DTW_CLANG_ASAN=ON -GNinja env: CC: /usr/bin/clang CXX: /usr/bin/clang++ - name: Build and test run: | - make -Cbuild -j12 tests TrezorCryptoTests + ninja -Cbuild tests TrezorCryptoTests build/trezor-crypto/crypto/tests/TrezorCryptoTests - build/tests/tests tests --gtest_output=xml + build/tests/tests --gtest_output=xml env: CC: /usr/bin/clang CXX: /usr/bin/clang++ diff --git a/.github/workflows/linux-sampleapp-ci.yml b/.github/workflows/linux-sampleapp-ci.yml index 882d8b33da8..da1aa11656b 100644 --- a/.github/workflows/linux-sampleapp-ci.yml +++ b/.github/workflows/linux-sampleapp-ci.yml @@ -1,25 +1,31 @@ -name: Linux SampleApp CI +name: Linux SampleApps CI on: push: - branches: [ master ] + branches: [ dev, master ] pull_request: - branches: [ master ] + branches: [ dev, master ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install system dependencies run: | - sudo apt-get update && sudo apt-get install ninja-build llvm-11 libboost-all-dev clang-11 --fix-missing + tools/install-sys-dependencies-linux ci + tools/install-rust-dependencies - name: Cache internal dependencies id: internal_cache - uses: actions/cache@v1.1.2 + uses: actions/cache@v3 with: path: build/local - key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-sys-dependencies-linux') }}-${{ hashFiles('tools/install-dependencies') }} - name: Install internal dependencies run: | tools/install-dependencies @@ -29,13 +35,13 @@ jobs: if: steps.internal_cache.outputs.cache-hit != 'true' - name: Code generation run: | - tools/generate-files + tools/generate-files native env: CC: /usr/bin/clang CXX: /usr/bin/clang++ - name: CMake run: | - cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug + cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DTW_UNITY_BUILD=ON env: CC: /usr/bin/clang CXX: /usr/bin/clang++ @@ -55,9 +61,16 @@ jobs: env: CC: /usr/bin/clang CXX: /usr/bin/clang++ + - name: Build, run, and test devconsole.ts + run: | + cd samples/typescript/devconsole.ts + npm install + npm run build + echo -e "help() \n .exit" | npm run start + npm run test - name: Install Go env: - GO_VERSION: 1.16.12 + GO_VERSION: 1.19 GO_ARCH: amd64 run: | curl -O -L "https://golang.org/dl/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" @@ -73,3 +86,17 @@ jobs: env: CC: /usr/bin/clang CXX: /usr/bin/clang++ + - name: Build Golang dev console + run: | + cd samples/go/dev-console + ./prepare.sh + cd cmd && go build -o ../tw_dev_console && cd - + env: + CC: /usr/bin/clang + CXX: /usr/bin/clang++ + - name: Build and run Rust sample app + run: | + cd samples/rust + rustc --version + cargo build + cargo run diff --git a/.github/workflows/wasm-ci.yml b/.github/workflows/wasm-ci.yml index 140f5adc850..da063d565be 100644 --- a/.github/workflows/wasm-ci.yml +++ b/.github/workflows/wasm-ci.yml @@ -2,27 +2,31 @@ name: Wasm CI on: push: - branches: [ master ] + branches: [ dev, master ] pull_request: - branches: [ master ] + branches: [ dev, master ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install system dependencies run: | - # build-essential clang-11 libc++-dev libc++abi-dev ruby-full cmake python3 - sudo apt-get update && sudo apt-get install libboost-all-dev --fix-missing - + tools/install-sys-dependencies-linux + tools/install-rust-dependencies - name: Install emsdk run: tools/install-wasm-dependencies - name: Cache internal dependencies id: internal_cache - uses: actions/cache@v1.1.2 + uses: actions/cache@v3 with: path: build/local key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} @@ -35,7 +39,9 @@ jobs: if: steps.internal_cache.outputs.cache-hit != 'true' - name: Code generation - run: tools/generate-files + run: | + source emsdk/emsdk_env.sh + tools/generate-files wasm env: CC: /usr/bin/clang CXX: /usr/bin/clang++ @@ -50,6 +56,5 @@ jobs: - name: Test run: | - npm install && npm run copy:wasm - npm run build && npm run test + npm install && npm run build-and-test working-directory: wasm diff --git a/.gitignore b/.gitignore index 5686032fd60..42e6942a72d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ cmake-build-debug/ .cquery_cache/ .cxx/ .cache/ +build_pvs_studio/ # Dependencies node_modules @@ -22,14 +23,19 @@ lib/protobuf .vscode/ .project .history/ +*.xcuserdatad/ # Generated files +jni/android/generated jni/cpp/generated jni/java/wallet/core/jni -jni/java/wallet/core/proto +jni/proto/wallet +jni/dokka-out swift/Sources/Generated swift/wallet-core/ -src/Generated +codegen-v2/bindings/ + +src/Generated/*.cpp include/TrustWalletCore/TWHRP.h include/TrustWalletCore/TW*Proto.h include/TrustWalletCore/TWDerivation.h @@ -61,5 +67,5 @@ samples/cpp/*.cmake # built binary samples/cpp/sample -*.xcuserdatad/ - +# Rust target build +**/target/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 6fe591894f7..6fec5513326 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,57 +1,43 @@ -# Copyright © 2017-2022 Trust Wallet. +# SPDX-License-Identifier: Apache-2.0 # -# This file is part of Trust. The full Trust copyright notice, including -# terms governing use, modification, and redistribution, is contained in the -# file LICENSE at the root of the source code distribution tree. +# Copyright © 2017 Trust Wallet. -cmake_minimum_required(VERSION 3.8 FATAL_ERROR) +cmake_minimum_required(VERSION 3.18 FATAL_ERROR) project(TrustWalletCore) -include(GNUInstallDirs) - -# Configure warnings -set(TW_CXX_WARNINGS "-Wshorten-64-to-32") -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TW_CXX_WARNINGS}") -set(CMAKE_EXPORT_COMPILE_COMMANDS 1) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) -set(CMAKE_CXX_VISIBILITY_PRESET hidden) - -set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version" FORCE) - if (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) message(FATAL_ERROR "You should use clang compiler") -endif() +endif () if ("$ENV{PREFIX}" STREQUAL "") set(PREFIX "${CMAKE_SOURCE_DIR}/build/local") -else() +else () set(PREFIX "$ENV{PREFIX}") -endif() - -# Configure CCache if available -find_program(CCACHE_FOUND ccache) -if(CCACHE_FOUND) - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) -endif(CCACHE_FOUND) +endif () -include_directories(${PREFIX}/include) -link_directories(${PREFIX}/lib) +include(GNUInstallDirs) +include(cmake/StandardSettings.cmake) +include(cmake/CompilerWarnings.cmake) +include(cmake/StaticAnalyzers.cmake) +include(cmake/FindHostPackage.cmake) + +set(WALLET_CORE_RS_TARGET_DIR ${CMAKE_SOURCE_DIR}/rust/target) +add_library(${PROJECT_NAME}_INTERFACE INTERFACE) +target_include_directories(${PROJECT_NAME}_INTERFACE INTERFACE ${PREFIX}/include) +target_link_directories(${PROJECT_NAME}_INTERFACE INTERFACE ${PREFIX}/lib) +target_link_directories(${PROJECT_NAME}_INTERFACE INTERFACE ${WALLET_CORE_RS_TARGET_DIR}/release) +set_project_warnings(${PROJECT_NAME}_INTERFACE) add_subdirectory(trezor-crypto) +set(WALLET_CORE_RS_LIB libwallet_core_rs.a) -macro(find_host_package) - set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) - set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER) - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) - find_package(${ARGN}) - set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -endmacro(find_host_package) +set(WALLET_CORE_BINDGEN ${WALLET_CORE_RS_TARGET_DIR}/release/${WALLET_CORE_RS_LIB}) +if (TW_COMPILE_WASM) + message(STATUS "Wasm build enabled") + set(WALLET_CORE_BINDGEN ${WALLET_CORE_RS_TARGET_DIR}/wasm32-unknown-emscripten/release/${WALLET_CORE_RS_LIB}) + add_subdirectory(wasm) +endif () find_host_package(Boost REQUIRED) @@ -60,84 +46,116 @@ include(ExternalProject) # Dependencies include(cmake/Protobuf.cmake) -option(CODE_COVERAGE "Enable coverage reporting" OFF) -if(CODE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fprofile-arcs -ftest-coverage") - set(CMAKE_EXE_LINKER_FLAGS "-fprofile-arcs -ftest-coverage") -endif() - -option(CLANG_TIDY "Enable static code analysis with (clang-tidy)" OFF) -if(CLANG_TIDY) - find_program(CLANG_TIDY_BIN NAMES clang-tidy-11) - if(CLANG_TIDY_BIN) - set(CMAKE_CXX_CLANG_TIDY clang-tidy-11;) - message("clang-tidy ${CMAKE_CXX_CLANG_TIDY} ${CLANG_TIDY_BIN}") - else() - message(FATAL_ERROR "Could not find clang-tidy") - endif() -endif() - -option(CLANG_ASAN "Enable ASAN dynamic address sanitizer" OFF) -if(CLANG_ASAN) - # https://clang.llvm.org/docs/AddressSanitizer.html - # https://github.com/trustwallet/wallet-core/issues/1170 - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fno-omit-frame-pointer") - set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fsanitize=address -fno-omit-frame-pointer") - message("CLANG_ASAN on, ${CMAKE_CXX_FLAGS_DEBUG}") -endif() - # Source files -if(${ANDROID}) - message("Configuring for JNI") - file(GLOB_RECURSE sources src/*.c src/*.cc src/*.cpp src/*.h jni/cpp/*.c jni/cpp/*.cpp jni/cpp/*.h jni/cpp/*.c) +if (${ANDROID}) + message("Configuring for Android JNI") + file(GLOB_RECURSE core_sources src/*.c src/*.cc src/*.cpp src/*.h jni/cpp/*.cpp jni/cpp/*.h) + if (${KOTLIN}) + file(GLOB_RECURSE specific_sources + jni/kotlin/*.h + jni/kotlin/*.c + kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/cpp/generated/*.h + kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/cpp/generated/*.c + ) + else () + file(GLOB_RECURSE specific_sources jni/android/*.h jni/android/*.c) + endif () + set(sources ${core_sources} ${specific_sources}) add_library(TrustWalletCore SHARED ${sources} ${PROTO_SRCS} ${PROTO_HDRS}) - find_library(log-lib log) - target_link_libraries(TrustWalletCore PRIVATE TrezorCrypto protobuf ${log-lib} Boost::boost) -else() + if (${CMAKE_ANDROID_ARCH_ABI} STREQUAL "arm64-v8a") + set(WALLET_CORE_BINDGEN ${WALLET_CORE_RS_TARGET_DIR}/aarch64-linux-android/release/${WALLET_CORE_RS_LIB}) + elseif (${CMAKE_ANDROID_ARCH_ABI} STREQUAL "x86") + set(WALLET_CORE_BINDGEN ${WALLET_CORE_RS_TARGET_DIR}/i686-linux-android/release/${WALLET_CORE_RS_LIB}) + elseif (${CMAKE_ANDROID_ARCH_ABI} STREQUAL "armeabi-v7a") + set(WALLET_CORE_BINDGEN ${WALLET_CORE_RS_TARGET_DIR}/armv7-linux-androideabi/release/${WALLET_CORE_RS_LIB}) + elseif (${CMAKE_ANDROID_ARCH_ABI} STREQUAL "x86_64") + set(WALLET_CORE_BINDGEN ${WALLET_CORE_RS_TARGET_DIR}/x86_64-linux-android/release/${WALLET_CORE_RS_LIB}) + endif () + target_link_libraries(TrustWalletCore PUBLIC ${WALLET_CORE_BINDGEN} ${PROJECT_NAME}_INTERFACE PRIVATE TrezorCrypto protobuf ${log-lib} Boost::boost) +elseif (${TW_COMPILE_JAVA}) + message("Configuring for JNI") + file(GLOB_RECURSE core_sources src/*.c src/*.cc src/*.cpp src/*.h jni/cpp/*.cpp jni/cpp/*.h) + if (${TW_COMPILE_KOTLIN}) + file(GLOB_RECURSE specific_sources + jni/kotlin/*.h + jni/kotlin/*.c + kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/cpp/generated/*.h + kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/cpp/generated/*.c + ) + else () + file(GLOB_RECURSE specific_sources jni/android/*.h jni/android/*.c) + endif () + set(sources ${core_sources} ${specific_sources}) + add_library(TrustWalletCore SHARED ${sources} ${PROTO_SRCS} ${PROTO_HDRS}) + find_package(JNI REQUIRED) + target_include_directories(TrustWalletCore PRIVATE ${JNI_INCLUDE_DIRS}) + target_link_libraries(TrustWalletCore PUBLIC ${WALLET_CORE_BINDGEN} ${PROJECT_NAME}_INTERFACE PRIVATE TrezorCrypto protobuf Boost::boost) +else () message("Configuring standalone") file(GLOB_RECURSE sources src/*.c src/*.cc src/*.cpp src/*.h) - add_library(TrustWalletCore ${sources} ${PROTO_SRCS} ${PROTO_HDRS}) + add_library(TrustWalletCore STATIC ${sources} ${PROTO_SRCS} ${PROTO_HDRS}) + target_link_libraries(TrustWalletCore PUBLIC ${WALLET_CORE_BINDGEN} ${PROJECT_NAME}_INTERFACE PRIVATE TrezorCrypto protobuf Boost::boost) +endif () - target_link_libraries(TrustWalletCore PRIVATE TrezorCrypto protobuf Boost::boost) -endif() -target_compile_options(TrustWalletCore PRIVATE "-Wall") +if (TW_CODE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_enable_coverage(TrustWalletCore) +endif () -set_target_properties(TrustWalletCore - PROPERTIES - CXX_STANDARD 17 - CXX_STANDARD_REQUIRED ON -) +if (TW_CLANG_ASAN) + target_enable_asan(TrustWalletCore) +endif () # Define headers for this library. PUBLIC headers are used for compiling the # library, and will be added to consumers' build paths. target_include_directories(TrustWalletCore - PUBLIC + PUBLIC $ $ - PRIVATE + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/jni/cpp ${CMAKE_CURRENT_SOURCE_DIR}/build/local/include -) + ) -if(NOT ANDROID AND NOT IOS_PLATFORM) +if (TW_UNIT_TESTS) add_subdirectory(tests) +endif () + +if (TW_BUILD_EXAMPLES) add_subdirectory(walletconsole/lib) add_subdirectory(walletconsole) -endif() +endif () + +if (TW_ENABLE_PVS_STUDIO) + tw_add_pvs_studio_target(TrustWalletCore) +endif () + +if (TW_ENABLE_CLANG_TIDY) + tw_add_clang_tidy_target(TrustWalletCore) +endif () + +if (TW_UNITY_BUILD) + set_target_properties(TrustWalletCore PROPERTIES UNITY_BUILD ON) + + file(GLOB_RECURSE PROTOBUF_SOURCE_FILES CONFIGURE_DEPENDS src/Cosmos/Protobuf/*.pb.cc src/Hedera/Protobuf/*.pb.cc src/proto/*.pb.cc) + foreach (file ${PROTOBUF_SOURCE_FILES}) + set_property(SOURCE ${file} PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) + endforeach () + message(STATUS "Unity build activated") +endif () configure_file(${CMAKE_CURRENT_SOURCE_DIR}/swift/cpp.xcconfig.in ${CMAKE_CURRENT_SOURCE_DIR}/swift/cpp.xcconfig @ONLY) install(TARGETS TrustWalletCore - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) install( - DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/ - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/WalletCore - FILES_MATCHING PATTERN "*.h" + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/WalletCore + FILES_MATCHING PATTERN "*.h" ) install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/Dockerfile b/Dockerfile index ce77a076d68..082d5fd1969 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive @@ -14,11 +14,7 @@ RUN apt-get update \ software-properties-common \ && apt-get clean && rm -rf /var/lib/apt/lists/* -# Add latest cmake/boost SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc | apt-key add - \ - && apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main' \ - && apt-add-repository -y ppa:mhier/libboost-latest # Install required packages for dev RUN apt-get update \ @@ -27,45 +23,45 @@ RUN apt-get update \ libtool autoconf pkg-config \ ninja-build \ ruby-full \ - clang-10 \ - llvm-10 \ + clang-14 \ + llvm-14 \ libc++-dev libc++abi-dev \ - cmake \ - libboost1.74-dev \ + cmake \ + libboost-all-dev \ ccache \ && apt-get clean && rm -rf /var/lib/apt/lists/* -ENV CC=/usr/bin/clang-10 -ENV CXX=/usr/bin/clang++-10 +ENV CC=/usr/bin/clang-14 +ENV CXX=/usr/bin/clang++-14 + +# Install rust +RUN wget "https://sh.rustup.rs" -O rustup.sh \ + && sh rustup.sh -y +ENV PATH="/root/.cargo/bin:${PATH}" +RUN rustup default nightly-2024-06-13 +RUN cargo install --force cbindgen \ + && rustup target add wasm32-unknown-emscripten # ↑ Setup build environment # ↓ Build and compile wallet core -RUN git clone https://github.com/trustwallet/wallet-core.git +COPY . /wallet-core WORKDIR /wallet-core # Install dependencies RUN tools/install-dependencies -# Build: generate, cmake, and make lib -RUN tools/generate-files \ - && cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug \ +# Build: generate files and rust lib +RUN tools/generate-files native + +# Build: cmake + make wallet core +RUN cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug \ && make -Cbuild -j12 TrustWalletCore # Build unit tester RUN make -Cbuild -j12 tests -# Download and Install Go -ENV GO_VERSION=1.16.12 -ENV GO_ARCH=amd64 -RUN wget "https://golang.org/dl/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" \ - && tar -xf "go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" \ - && chown -R root:root ./go \ - && mv -v ./go /usr/local \ - && ls /usr/local/go \ - && /usr/local/go/bin/go version \ - && rm "go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" - -# Building GoLang sample app: cd samples/go && /usr/local/go/bin/go build -o main && ./main +# Download and Install Go: apt install golang-go +# Build Go sample app: cd samples/go && /usr/local/go/bin/go build -o main && ./main CMD ["/bin/bash"] diff --git a/LICENSE b/LICENSE index d7b2f475420..591e7191a60 100644 --- a/LICENSE +++ b/LICENSE @@ -1,19 +1,190 @@ -Copyright (c) 2017-2020 Trust Wallet - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2017 Trust Wallet + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-3RD-PARTY.txt b/LICENSE-3RD-PARTY.txt new file mode 100644 index 00000000000..93b7fb0a22a --- /dev/null +++ b/LICENSE-3RD-PARTY.txt @@ -0,0 +1,850 @@ +3RD PARTY LICENSES + +Note that not all files in the wallet-core repository and in the released +software packages belong to the wallet-core project. For 3rd party files, +the individual licenses apply. + + +############################################################################# +LICENSE TEXTS +############################################################################# + +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +############################################################################# + + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + +############################################################################# + +Copyright 2008 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Code generated by the Protocol Buffer compiler is owned by the owner +of the input file used when generating it. This code is not +standalone and requires a support library to be linked with it. This +support library is itself covered by the above license. + +############################################################################# + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +## Runtime Library Exception to the Apache 2.0 License: ## + + + As an exception, if you use this Software to compile your source code and + portions of this Software are embedded into the binary product as a result, + you may redistribute such product without providing attribution as would + otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. + +############################################################################# + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +############################################################################# + +The MIT License (MIT) + +Copyright (c) 2013 Tomas Dzetkulic +Copyright (c) 2013 Pavol Rusnak + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Package.swift b/Package.swift index 5e1d49459d7..38e74510f2f 100644 --- a/Package.swift +++ b/Package.swift @@ -12,13 +12,13 @@ let package = Package( targets: [ .binaryTarget( name: "WalletCore", - url: "https://github.com/trustwallet/wallet-core/releases/download/2.7.8/WalletCore.xcframework.zip", - checksum: "6f37b01f92fe76b0c97e31652c1756678ff84eb42009fd39b19f534eb9dc562e" + url: "https://github.com/trustwallet/wallet-core/releases/download/4.0.33/WalletCore.xcframework.zip", + checksum: "2fb8b833047b9697bba6ade66a9bdeede622b2fe0fb7a9b90cb9edb4651ec866" ), .binaryTarget( name: "SwiftProtobuf", - url: "https://github.com/trustwallet/wallet-core/releases/download/2.7.8/SwiftProtobuf.xcframework.zip", - checksum: "a72a9e8191f1654114fbd2d9f6af3e777c5e3dffa06b2954f047bea1ee6cd529" + url: "https://github.com/trustwallet/wallet-core/releases/download/4.0.33/SwiftProtobuf.xcframework.zip", + checksum: "05557735dd607c5a369dc378eb3f299504b880614ef13f136a028ecd320b0e4d" ) ] ) diff --git a/README.md b/README.md index ce0409425a2..7456046acbd 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,19 @@ -Trust Wallet Core is an open source, cross-platform, mobile-focused library +Trust Wallet Core is an open-source, cross-platform, mobile-focused library implementing low-level cryptographic wallet functionality for a high number of blockchains. It is a core part of the popular [Trust Wallet](https://trustwallet.com), and some other projects. Most of the code is C++ with a set of strict C interfaces, and idiomatic interfaces for supported languages: Swift for iOS and Java (Kotlin) for Android. -![iOS CI](https://github.com/trustwallet/wallet-core/workflows/iOS%20CI/badge.svg) -![Android CI](https://github.com/trustwallet/wallet-core/workflows/Android%20CI/badge.svg) -![Linux CI](https://github.com/trustwallet/wallet-core/workflows/Linux%20CI/badge.svg) -![Wasm CI](https://github.com/trustwallet/wallet-core/workflows/Wasm%20CI/badge.svg) -![Docker CI](https://github.com/trustwallet/wallet-core/workflows/Docker%20CI/badge.svg) +[![iOS CI](https://github.com/trustwallet/wallet-core/actions/workflows/ios-ci.yml/badge.svg)](https://github.com/trustwallet/wallet-core/actions/workflows/ios-ci.yml) +[![Android CI](https://github.com/trustwallet/wallet-core/actions/workflows/android-ci.yml/badge.svg)](https://github.com/trustwallet/wallet-core/actions/workflows/android-ci.yml) +[![Linux CI](https://github.com/trustwallet/wallet-core/actions/workflows/linux-ci.yml/badge.svg)](https://github.com/trustwallet/wallet-core/actions/workflows/linux-ci.yml) +[![Rust CI](https://github.com/trustwallet/wallet-core/actions/workflows/linux-ci-rust.yml/badge.svg)](https://github.com/trustwallet/wallet-core/actions/workflows/linux-ci-rust.yml) +[![Wasm CI](https://github.com/trustwallet/wallet-core/actions/workflows/wasm-ci.yml/badge.svg)](https://github.com/trustwallet/wallet-core/actions/workflows/wasm-ci.yml) +[![Kotlin CI](https://github.com/trustwallet/wallet-core/actions/workflows/kotlin-ci.yml/badge.svg)](https://github.com/trustwallet/wallet-core/actions/workflows/kotlin-ci.yml) +[![Docker CI](https://github.com/trustwallet/wallet-core/actions/workflows/docker.yml/badge.svg)](https://github.com/trustwallet/wallet-core/actions/workflows/docker.yml) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=TrustWallet_wallet-core&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=TrustWallet_wallet-core) [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/trustwallet/wallet-core) ![GitHub](https://img.shields.io/github/license/TrustWallet/wallet-core.svg) @@ -22,9 +25,13 @@ Swift for iOS and Java (Kotlin) for Android. For comprehensive documentation, see [developer.trustwallet.com](https://developer.trustwallet.com/wallet-core). +# Audit Reports + +Security Audit reports can be found in the [audit](audit) directory. + # Supported Blockchains -Wallet Core supports more than **60** blockchains: Bitcoin, Ethereum, BNB, Cosmos, Solana, and most major blockchain platforms. +Wallet Core supports more than **130** blockchains: Bitcoin, Ethereum, BNB, Cosmos, Solana, and most major blockchain platforms. The full list is [here](docs/registry.md). # Building @@ -38,20 +45,9 @@ If you want to use wallet core in your project follow these instructions. ## Android -Android releases are hosted on [GitHub packages](https://github.com/trustwallet/wallet-core/packages/700258), please checkout [this installation guide](https://docs.github.com/en/packages/guides/configuring-gradle-for-use-with-github-packages#installing-a-package), you need to add GitHub access token to install it. - -Add this dependency to build.gradle and run `gradle install` +Android releases are hosted on [GitHub packages](https://github.com/trustwallet/wallet-core/packages/700258), you need to add GitHub access token to install it. Please check out [this installation guide](https://developer.trustwallet.com/wallet-core/integration-guide/android-guide#adding-library-dependency) or `build.gradle` from our [android sample](https://github.com/trustwallet/wallet-core/blob/master/samples/android/build.gradle) -```groovy -plugins { - id 'maven' -} - -dependencies { - implementation 'com.trustwallet:wallet-core:x.y.z' -} -``` -Replace x.y.z with latest version: ![GitHub release (latest by date)](https://img.shields.io/github/v/release/trustwallet/wallet-core) +Don't forget replacing the version in the code with latest: ![GitHub release (latest by date)](https://img.shields.io/github/v/release/trustwallet/wallet-core) ## iOS @@ -73,7 +69,7 @@ Or add remote url + `master` branch, it points to recent (not always latest) bin .package(name: "WalletCore", url: "https://github.com/trustwallet/wallet-core", .branchItem("master")), ``` -Then add libraries to target's `dependencies`: +Then add libraries to target's `dependencies`: ```swift .product(name: "WalletCore", package: "WalletCore"), @@ -98,19 +94,24 @@ npm install @trustwallet/wallet-core Please check out the [Go integration sample](https://github.com/trustwallet/wallet-core/tree/master/samples/go). +## Kotlin Multipleplatform (beta) + +Please check out the [Kotlin Multiplatform sample](https://github.com/trustwallet/wallet-core/tree/master/samples/kmp) # Projects Projects using Trust Wallet Core. Add yours too! -[Trust Wallet](https://trustwallet.com) +[Trust Wallet](https://trustwallet.com) [Coinpaprika](https://coinpaprika.com/) -| [IFWallet](https://www.ifwallet.com/) | [crypto.com](https://crypto.com) -| [Alice](https://www.alicedapp.com/) | [Frontier](https://frontier.xyz/) | [Tokenary](https://tokenary.io/) +| [MemesWallet](https://planetmemes.com/) +| [xPortal](https://xportal.com/) +| [Slingshot](https://slingshot.finance/) +| [ECOIN Wallet](https://play.google.com/store/apps/details?id=org.ecoinwallet&pcampaignid=web_share) # Community @@ -122,13 +123,20 @@ There are a few community-maintained projects that extend Wallet Core to some ad # Contributing -The best way to submit feedback and report bugs is to [open a GitHub issue](https://github.com/trustwallet/wallet-core/issues/new). +The best way to submit feedback and report bugs related to WalletCore is to [open a GitHub issue](https://github.com/trustwallet/wallet-core/issues/new). +If the bug is not related to WalletCore but to the TrustWallet app, please [create a Customer Support ticket](https://support.trustwallet.com/en/support/tickets/new). If you want to contribute code please see [Contributing](https://developer.trustwallet.com/wallet-core/contributing). If you want to add support for a new blockchain also see [Adding Support for a New Blockchain](https://developer.trustwallet.com/wallet-core/newblockchain), make sure you have read the [requirements](https://developer.trustwallet.com/wallet-core/newblockchain#requirements) section. Thanks to all the people who contribute. +# Disclaimer + +The Wallet Core project is led and managed by Trust Wallet with a large contributor community and actively used in several projects. Our goal at Wallet Core is to give other wallets an easy way to add chain support. + +Trust Wallet products leverage wallet core, however, they may or may not leverage all the capabilities, features, and assets available in wallet core due to their own product requirements. + # License -Trust Wallet Core is available under the MIT license. See the [LICENSE](LICENSE) file for more info. +Trust Wallet Core is available under the Apache 2.0 license. See the [LICENSE](LICENSE) file for more info. diff --git a/SECURITY.MD b/SECURITY.MD new file mode 100644 index 00000000000..720baa249ff --- /dev/null +++ b/SECURITY.MD @@ -0,0 +1,29 @@ +# Security Policy + +The security of our users' assets is of the utmost importance to us. We take a number of steps to ensure that our crypto wallet is as secure as possible. + +## Reporting a Security Vulnerability + +If you believe you have found a security vulnerability in our wallet, please contact us immediately at [Bug Bounty Binance](https://bugcrowd.com/binance). We will investigate all reports and do our best to quickly fix any vulnerabilities. + +## Responsible Disclosure + +IMPORTANT: Do not file public issues on GitHub for security vulnerabilities. Do not publicly disclose the vulnerability until we have had a chance to patch it. This gives us time to fix the problem and protect our users’ assets. + +## Encryption + +All private keys are encrypted and stored on the user's device. The encryption uses industry-standard algorithms and is designed to protect against brute-force attacks. + +## Regular Audits + +We regularly conduct security audits of our code to ensure that it is free of vulnerabilities. We also stay up-to-date with the latest security best practices and technologies. + +## Bug Bounty Program + +As a part of Binance security program, TrustWallet also participate in their bug bounty program. For more information on eligible scope, rewards, and how to submit a report, please visit [https://bugcrowd.com/binance](https://bugcrowd.com/binance) + +## Disclaimer + +As with any software, there are always potential security risks. We do our best to minimize these risks and keep our users' assets safe, but we cannot guarantee that our wallet will be completely immune to all security threats. + + diff --git a/android/app/build.gradle b/android/app/build.gradle index 9a198ee9732..1e0093c6494 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -2,6 +2,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { + namespace 'com.trustwallet.core.app' compileSdkVersion 32 ndkVersion '23.1.7779620' defaultConfig { @@ -27,7 +28,7 @@ android { } dependencies { - implementation project(':trustwalletcore') + implementation project(':wallet-core') implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2-native-mt' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2-native-mt' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" @@ -35,15 +36,10 @@ dependencies { // Tests androidTestImplementation('androidx.test.espresso:espresso-core:3.3.0', { - testImplementation 'junit:junit:4.13.1' + testImplementation 'junit:junit:4.13.1' exclude group: "com.android.support", module: "support-annotations" }) androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0' androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'android.arch.core:core-testing:1.1.1' - - implementation 'io.grpc:grpc-protobuf:1.43.2' -} -repositories { - mavenCentral() } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt index 41cf2644572..9064a873e7c 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt @@ -34,15 +34,22 @@ class CoinAddressDerivationTests { private fun runDerivationChecks(coin: CoinType, address: String?) = when (coin) { BINANCE -> assertEquals("bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw", address) + TBINANCE -> assertEquals("tbnb12vtaxl9952zm6rwf7v8jerq74pvaf77fkw9xhl", address) BITCOIN -> assertEquals("bc1quvuarfksewfeuevuc6tn0kfyptgjvwsvrprk9d", address) + BITCOINDIAMOND -> assertEquals("1KaRW9xPPtCTZ9FdaTHduCPck4YvSeEWNn", address) BITCOINCASH -> assertEquals("bitcoincash:qpzl3jxkzgvfd9flnd26leud5duv795fnv7vuaha70", address) BITCOINGOLD -> assertEquals("btg1qwz9sed0k4neu6ycrudzkca6cnqe3zweq35hvtg", address) CALLISTO -> assertEquals("0x3E6FFC80745E6669135a76F4A7ce6BCF02436e04", address) DASH -> assertEquals("XqHiz8EXYbTAtBEYs4pWTHh7ipEDQcNQeT", address) DIGIBYTE -> assertEquals("dgb1qtjgmerfqwdffyf8ghcrkgy52cghsqptynmyswu", address) - ETHEREUM, SMARTCHAIN, POLYGON, OPTIMISM, ARBITRUM, ECOCHAIN, AVALANCHECCHAIN, XDAI, + + ETHEREUM, SMARTCHAIN, POLYGON, OPTIMISM, ZKSYNC, ARBITRUM, ARBITRUMNOVA, ECOCHAIN, AVALANCHECCHAIN, XDAI, FANTOM, CELO, CRONOSCHAIN, SMARTBITCOINCASH, KUCOINCOMMUNITYCHAIN, BOBA, METIS, - AURORA, EVMOS, MOONRIVER, MOONBEAM, KLAYTN -> assertEquals("0x8f348F300873Fd5DA36950B2aC75a26584584feE", address) + AURORA, EVMOS, MOONRIVER, MOONBEAM, KAVAEVM, KAIA, METER, OKXCHAIN, POLYGONZKEVM, SCROLL, + CONFLUXESPACE, ACALAEVM, OPBNB, NEON, BASE, LINEA, GREENFIELD, MANTLE, ZENEON, MANTAPACIFIC, + ZETAEVM, MERLIN, LIGHTLINK, BLAST, BOUNCEBIT, ZKLINKNOVA, + -> assertEquals("0x8f348F300873Fd5DA36950B2aC75a26584584feE", address) + RONIN -> assertEquals("ronin:8f348F300873Fd5DA36950B2aC75a26584584feE", address) ETHEREUMCLASSIC -> assertEquals("0x078bA3228F3E6C08bEEac9A005de0b7e7089aD1c", address) GOCHAIN -> assertEquals("0x5940ce4A14210d4Ccd0ac206CE92F21828016aC2", address) @@ -53,12 +60,14 @@ class CoinAddressDerivationTests { POANETWORK -> assertEquals("0xe8a3e8bE17E172B6926130eAfB521e9D2849aca9", address) XRP -> assertEquals("rPwE3gChNKtZ1mhH3Ko8YFGqKmGRWLWXV3", address) TEZOS -> assertEquals("tz1acnY9VbMagps26Kj3RfoGRWD9nYG5qaRX", address) - THUNDERTOKEN -> assertEquals("0x4b92b3ED6d8b24575Bf5ce4C6a86ED261DA0C8d7", address) - TOMOCHAIN -> assertEquals("0xC74b6D8897cBa9A4b659d43fEF73C9cA852cE424", address) + THUNDERCORE -> assertEquals("0x4b92b3ED6d8b24575Bf5ce4C6a86ED261DA0C8d7", address) + VICTION -> assertEquals("0xC74b6D8897cBa9A4b659d43fEF73C9cA852cE424", address) TRON -> assertEquals("TQ5NMqJjhpQGK7YJbESKtNCo86PJ89ujio", address) VECHAIN -> assertEquals("0x1a553275dF34195eAf23942CB7328AcF9d48c160", address) WANCHAIN -> assertEquals("0xD5ca90b928279FE5D06144136a25DeD90127aC15", address) + KOMODO -> assertEquals("RCWJLXE5CSXydxdSnwcghzPgkFswERegyb", address) ZCASH -> assertEquals("t1YYnByMzdGhQv3W3rnjHMrJs6HH4Y231gy", address) + ZEN -> assertEquals("znUmzvod1f4P9LYsBhNxjqCDQvNSStAmYEX", address) FIRO -> assertEquals("aEd5XFChyXobvEics2ppAqgK3Bgusjxtik", address) NIMIQ -> assertEquals("NQ76 7AVR EHDA N05U X7SY XB14 XJU7 8ERV GM6H", address) STELLAR -> assertEquals("GA3H6I4C5XUBYGVB66KXR27JV5KS3APSTKRUWOIXZ5MVWZKVTLXWKZ2P", address) @@ -66,42 +75,84 @@ class CoinAddressDerivationTests { NANO -> assertEquals("nano_39gsbcishxn3n7wd17ono4otq5wazwzusqgqigztx73wbrh5jwbdbshfnumc", address) NEBULAS -> assertEquals("n1ZVgEidtdseYv9ogmGz69Cz4mbqmHYSNqJ", address) NEAR -> assertEquals("0c91f6106ff835c0195d5388565a2d69e25038a7e23d26198f85caf6594117ec", address) - THETA -> assertEquals("0x0d1fa20c218Fec2f2C55d52aB267940485fa5DA4", address) + THETA, THETAFUEL -> assertEquals("0x0d1fa20c218Fec2f2C55d52aB267940485fa5DA4", address) COSMOS -> assertEquals("cosmos142j9u5eaduzd7faumygud6ruhdwme98qsy2ekn", address) DECRED -> assertEquals("DsidJiDGceqHTyqiejABy1ZQ3FX4SiWZkYG", address) DOGECOIN -> assertEquals("DJRFZNg8jkUtjcpo2zJd92FUAzwRjitw6f", address) KIN -> assertEquals("GBL3MT2ICHHM5OJ2QJ44CAHGDK6MLPINVXBKOKLHGBWQDVRWTWQ7U2EA", address) VIACOIN -> assertEquals("via1qnmsgjd6cvfprnszdgmyg9kewtjfgqflz67wwhc", address) + VERGE -> assertEquals("DPb3Xz4vjB6QGLKDmrbprrtv4XzNqkADc2", address) QTUM -> assertEquals("QhceuaTdeCZtcxmVc6yyEDEJ7Riu5gWFoF", address) NULS -> assertEquals("NULSd6HgU8MoRnNjBgvJpa9tqvGxYdv5ne4en", address) EOS -> assertEquals("EOS6hs8sRvGSzuQtq223zwJipMzqTJpXUVjyvHPvPwBSZWWrJTJkg", address) + WAX -> assertEquals("EOS6hs8sRvGSzuQtq223zwJipMzqTJpXUVjyvHPvPwBSZWWrJTJkg", address) IOTEX -> assertEquals("io1qw9cccecw09q7p5kzyqtuhfhvah2mhfrc84jfk", address) + IOTEXEVM -> assertEquals("0x038B8C633873Ca0f06961100BE5d37676EADDD23", address) ZILLIQA -> assertEquals("zil1mk6pqphhkmaguhalq6n3cq0h38ltcehg0rfmv6", address) ZELCASH -> assertEquals("t1UKbRPzL4WN8Rs8aZ8RNiWoD2ftCMHKGUf", address) RAVENCOIN -> assertEquals("RHoCwPc2FCQqwToYnSiAb3SrCET4zEHsbS", address) WAVES -> assertEquals("3P63vkaHhyE9pPv9EfsjwGKqmZYcCRHys4n", address) AETERNITY -> assertEquals("ak_QDHJSfvHG9sDHBobaWt2TAGhuhipYjEqZEH34bWugpJfJc3GN", address) - TERRA -> assertEquals("terra1rh402g98t7sly8trzqw5cyracntlep6qe3smug", address) + TERRA, TERRAV2 -> assertEquals("terra1rh402g98t7sly8trzqw5cyracntlep6qe3smug", address) MONACOIN -> assertEquals("M9xFZzZdZhCDxpx42cM8bQHnLwaeX1aNja", address) FIO -> assertEquals("FIO7MN1LuSfFgrbVHmrt9cVa2FYAs857Ppr9dzvEXoD1miKSxm3n3", address) HARMONY -> assertEquals("one12fk20wmvgypdkn59n4hq8e3aa5899xfx4vsu09", address) SOLANA -> assertEquals("2bUBiBNZyD29gP1oV6de7nxowMLoDBtopMMTGgMvjG5m", address) ALGORAND -> assertEquals("JTJWO524JXIHVPGBDWFLJE7XUIA32ECOZOBLF2QP3V5TQBT3NKZSCG67BQ", address) + ACALA -> assertEquals("25GGezx3LWFQj6HZpYzoWoVzLsHojGtybef3vthC9nd19ms3", address) KUSAMA -> assertEquals("G9xV2EatmrjRC1FLPexc3ddqNRRzCsAdURU8RFiAAJX6ppY", address) POLKADOT -> assertEquals("13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk", address) + PIVX -> assertEquals("D81AqC8zKma3Cht4TbVuh4jyVVyLkZULCm", address) KAVA -> assertEquals("kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87", address) CARDANO -> assertEquals("addr1qyr8jjfnypp95eq74aqzn7ss687ehxclgj7mu6gratmg3mul2040vt35dypp042awzsjk5xm3zr3zm5qh7454uwdv08s84ray2", address) NEO -> assertEquals("AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf", address) FILECOIN -> assertEquals("f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori", address) - ELROND -> assertEquals("erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8", address) + MULTIVERSX -> assertEquals("erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8", address) BANDCHAIN -> assertEquals("band1624hqgend0s3d94z68fyka2y5jak6vd7u0l50r", address) SMARTCHAINLEGACY -> assertEquals("0x49784f90176D8D9d4A3feCDE7C1373dAAb5b13b8", address) OASIS -> assertEquals("oasis1qzcpavvmuw280dk0kd4lxjhtpf0u3ll27yf7sqps", address) THORCHAIN -> assertEquals("thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65", address) + IOST -> assertEquals("4av8w81EyzUgHonsVWqfs15WM4Vrpgox4BYYQWhNQDVu", address) + SYSCOIN -> assertEquals("sys1qkl640se3mwpt666e3lyywnwh09e9jquvx9x8qj", address) + STRATIS -> assertEquals("strax1q0caanaw4nkf6fzwnzq2p7yum680e57pdg05zkm", address) BLUZELLE -> assertEquals("bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund", address) CRYPTOORG -> assertEquals("cro16fdf785ejm00jf9a24d23pzqzjh2h05klxjwu8", address) OSMOSIS -> assertEquals("osmo142j9u5eaduzd7faumygud6ruhdwme98qclefqp", address) ECASH -> assertEquals("ecash:qpelrdn7a0hcucjlf9ascz3lkxv7r3rffgzn6x5377", address) NATIVEEVMOS -> assertEquals("evmos13u6g7vqgw074mgmf2ze2cadzvkz9snlwstd20d", address) + NERVOS -> assertEquals("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3", address) + EVERSCALE -> assertEquals("0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04", address) + TON -> assertEquals("UQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUaT4", address) + APTOS -> assertEquals("0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", address) + NEBL -> assertEquals("NgDVaXAwNgBwb88xLiFKomfBmPkEh9F2d7", address) + SUI -> assertEquals("0xada112cfb90b44ba889cc5d39ac2bf46281e4a91f7919c693bcd9b8323e81ed2", address) + HEDERA -> assertEquals("0.0.302a300506032b657003210049eba62f64d0d941045595d9433e65d84ecc46bcdb1421de55e05fcf2d8357d5", address) + SECRET -> assertEquals("secret1f69sk5033zcdr2p2yf3xjehn7xvgdeq09d2llh", address) + NATIVEINJECTIVE -> assertEquals("inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a", address) + AGORIC -> assertEquals("agoric18zvvgk6j3eq5wd7mqxccgt20gz2w94cy88aek5", address) + STARGAZE -> assertEquals("stars142j9u5eaduzd7faumygud6ruhdwme98qycayaz", address) + JUNO -> assertEquals("juno142j9u5eaduzd7faumygud6ruhdwme98qxkfz30", address) + STRIDE -> assertEquals("stride142j9u5eaduzd7faumygud6ruhdwme98qn029zl", address) + AXELAR -> assertEquals("axelar142j9u5eaduzd7faumygud6ruhdwme98q52u3aj", address) + CRESCENT -> assertEquals("cre142j9u5eaduzd7faumygud6ruhdwme98q5veur7", address) + KUJIRA -> assertEquals("kujira142j9u5eaduzd7faumygud6ruhdwme98qpvgpme", address) + NATIVECANTO -> assertEquals("canto13u6g7vqgw074mgmf2ze2cadzvkz9snlwqua5pd", address) + COMDEX -> assertEquals("comdex142j9u5eaduzd7faumygud6ruhdwme98qhtgm0y", address) + NEUTRON -> assertEquals("neutron142j9u5eaduzd7faumygud6ruhdwme98q5mrmv5", address) + SOMMELIER -> assertEquals("somm142j9u5eaduzd7faumygud6ruhdwme98quc948e", address) + FETCHAI -> assertEquals("fetch142j9u5eaduzd7faumygud6ruhdwme98qrera5y", address) + MARS -> assertEquals("mars142j9u5eaduzd7faumygud6ruhdwme98qdenqrg", address) + UMEE -> assertEquals("umee142j9u5eaduzd7faumygud6ruhdwme98qzjhxjp", address) + COREUM -> assertEquals("core1rawf376jz2lnchgc4wzf4h9c77neg3zldc7xa8", address) + QUASAR -> assertEquals("quasar142j9u5eaduzd7faumygud6ruhdwme98q78symk", address) + PERSISTENCE -> assertEquals("persistence142j9u5eaduzd7faumygud6ruhdwme98q7gv2ch", address) + AKASH -> assertEquals("akash142j9u5eaduzd7faumygud6ruhdwme98qal870f", address) + NOBLE -> assertEquals("noble142j9u5eaduzd7faumygud6ruhdwme98qc8l3wa", address) + ROOTSTOCK -> assertEquals("0xA2D7065F94F838a3aB9C04D67B312056846424Df", address) + SEI -> assertEquals("sei142j9u5eaduzd7faumygud6ruhdwme98qagm0sj", address) + INTERNETCOMPUTER -> assertEquals("6f8e568160a3c8362789848dc0fa52891964473c045cc25208a305fb35b7c4ab", address) + TIA -> assertEquals("celestia142j9u5eaduzd7faumygud6ruhdwme98qpwmfv7", address) + NATIVEZETACHAIN -> assertEquals("zeta13u6g7vqgw074mgmf2ze2cadzvkz9snlwywj304", address) + DYDX -> assertEquals("dydx142j9u5eaduzd7faumygud6ruhdwme98qeayaky", address) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt index 7d4fe6bda0a..d565a1a08be 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/TestCoinType.kt @@ -1,16 +1,20 @@ package com.trustwallet.core.app.blockchains +import com.trustwallet.core.app.utils.toHexByteArray import wallet.core.jni.CoinType import wallet.core.jni.Curve +import wallet.core.jni.PublicKey import wallet.core.jni.Purpose +import wallet.core.jni.Derivation + import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse import org.junit.Test +import wallet.core.jni.PublicKeyType class TestCoinType { init { - System.loadLibrary("TrustWalletCore"); + System.loadLibrary("TrustWalletCore") } @Test @@ -19,7 +23,7 @@ class TestCoinType { assertEquals(CoinType.LITECOIN.value(), 2) assertEquals(CoinType.TRON.value(), 195) assertEquals(CoinType.ETHEREUM.value(), 60) - assertEquals(CoinType.THUNDERTOKEN.value(), 1001) + assertEquals(CoinType.THUNDERCORE.value(), 1001) assertEquals(CoinType.WANCHAIN.value(), 5718350) assertEquals(CoinType.CALLISTO.value(), 820) assertEquals(CoinType.ETHEREUMCLASSIC.value(), 61) @@ -28,7 +32,7 @@ class TestCoinType { assertEquals(CoinType.POANETWORK.value(), 178) assertEquals(CoinType.VECHAIN.value(), 818) assertEquals(CoinType.ICON.value(), 74) - assertEquals(CoinType.TOMOCHAIN.value(), 889) + assertEquals(CoinType.VICTION.value(), 889) assertEquals(CoinType.TEZOS.value(), 1729) assertEquals(CoinType.QTUM.value(), 2301) assertEquals(CoinType.NEBULAS.value(), 2718) @@ -43,4 +47,22 @@ class TestCoinType { fun testCoinCurve() { assertEquals(Curve.SECP256K1, CoinType.BITCOIN.curve()) } + + @Test + fun testDerivationPath() { + var res = CoinType.createFromValue(CoinType.BITCOIN.value()).derivationPath().toString() + assertEquals(res, "m/84'/0'/0'/0/0") + res = CoinType.createFromValue(CoinType.BITCOIN.value()).derivationPathWithDerivation(Derivation.BITCOINLEGACY).toString() + assertEquals(res, "m/44'/0'/0'/0/0") + res = CoinType.createFromValue(CoinType.SOLANA.value()).derivationPathWithDerivation(Derivation.SOLANASOLANA).toString() + assertEquals(res, "m/44'/501'/0'/0'") + } + + @Test + fun testDeriveAddressFromPublicKeyAndDerivation() { + val publicKey = PublicKey("0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798".toHexByteArray(), PublicKeyType.SECP256K1) + + val address = CoinType.BITCOIN.deriveAddressFromPublicKeyAndDerivation(publicKey, Derivation.BITCOINSEGWIT) + assertEquals(address, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4") + } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acala/TestAcalaAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acala/TestAcalaAddress.kt new file mode 100644 index 00000000000..5e50f515ab4 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acala/TestAcalaAddress.kt @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.acala + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestAcalaAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("0x9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0".toHexByteArray()) + val pubkey = key.publicKeyEd25519 + val address = AnyAddress(pubkey, CoinType.ACALA) + assertEquals(address.description(), "269ZCS3WLGydTN8ynhyhZfzJrXkePUcdhwgLQs6TWFs5wVL5") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acala/TestAcalaSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acala/TestAcalaSigner.kt new file mode 100644 index 00000000000..ad99985b4ec --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acala/TestAcalaSigner.kt @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.acala + +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType +import wallet.core.jni.proto.Polkadot + +class TestAcalaSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun AcalaTransactionSigning() { + val transferCallIndices = Polkadot.CallIndices.newBuilder().apply { + custom = Polkadot.CustomCallIndices.newBuilder().apply { + moduleIndex = 0x0a + methodIndex = 0x00 + }.build() + } + + val call = Polkadot.Balance.Transfer.newBuilder().apply { + value = "0xe8d4a51000".toHexBytesInByteString() // 1 ACA + toAddress = "25Qqz3ARAvnZbahGZUzV3xpP1bB3eRrupEprK7f2FNbHbvsz" + callIndices = transferCallIndices.build() + } + + val acalaGenesisHashStr = "0xfc41b9bd8ef8fe53d58c7ea67c794c7ec9a73daf05e6d54b14ff6342c99ba64c".toHexBytesInByteString() + + val input = Polkadot.SigningInput.newBuilder().apply { + genesisHash = acalaGenesisHashStr + blockHash = "0x707ffa05b7dc6cdb6356bd8bd51ff20b2757c3214a76277516080a10f1bc7537".toHexBytesInByteString() + nonce = 0 + specVersion = 2170 + network = CoinType.ACALA.ss58Prefix() + transactionVersion = 2 + privateKey = "9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0".toHexBytesInByteString() + era = Polkadot.Era.newBuilder().apply { + blockNumber = 3893613 + period = 64 + }.build() + balanceCall = Polkadot.Balance.newBuilder().apply { + transfer = call.build() + }.build() + multiAddress = true + } + + val output = AnySigner.sign(input.build(), CoinType.ACALA, Polkadot.SigningOutput.parser()) + val encoded = Numeric.toHexString(output.encoded.toByteArray()) + + // https://acala.subscan.io/extrinsic/3893620-3 + val expected = "0x41028400e9590e4d99264a14a85e21e69537e4a64f66a875d38cb8f76b305f41fabe24a900dd54466dffd1e3c80b76013e9459fbdcd17805bd5fdbca0961a643bad1cbd2b7fe005c62c51c18b67f31eb9e61b187a911952fee172ef18402d07c703eec3100d50200000a0000c8c602ded977c56076ae38d98026fa669ca10d6a2b5a0bfc4086ae7668ed1c60070010a5d4e8" + assertEquals(encoded, expected) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acalaevm/TestAcalaEVMAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acalaevm/TestAcalaEVMAddress.kt new file mode 100644 index 00000000000..6845bea3e89 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acalaevm/TestAcalaEVMAddress.kt @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.acalaevm + +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestAcalaEVMAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(false) + val address = AnyAddress(pubkey, CoinType.ACALAEVM) + val expected = AnyAddress("0xe32DC46bfBF78D1eada7b0a68C96903e01418D64", CoinType.ACALAEVM) + + assertEquals(address.description(), expected.description()) + } +} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/agoric/TestAgoricAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/agoric/TestAgoricAddress.kt new file mode 100644 index 00000000000..1164db16e14 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/agoric/TestAgoricAddress.kt @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.agoric + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestAgoricAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + + val key = PrivateKey("037048190544fa57651452f477c096de4f3073e7835cf3845b04602563a73f73".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(true) + val address = AnyAddress(pubkey, CoinType.AGORIC) + val expected = AnyAddress("agoric18zvvgk6j3eq5wd7mqxccgt20gz2w94cy88aek5", CoinType.AGORIC) + + assertEquals(pubkey.data().toHex(), "0x03df9a5e4089f89d45913fb2b856de984c7e8bf1344cc6444cc9705899a48c939d") + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/agoric/TestAgoricSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/agoric/TestAgoricSigner.kt new file mode 100644 index 00000000000..ecdf4372f3a --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/agoric/TestAgoricSigner.kt @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.agoric + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.AnyAddress +import wallet.core.jni.CoinType +import wallet.core.jni.PrivateKey +import wallet.core.jni.proto.Cosmos + +class TestAgoricSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun AgoricTransactionSigning() { + val key = PrivateKey("037048190544fa57651452f477c096de4f3073e7835cf3845b04602563a73f73".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, CoinType.AGORIC).description() + + val transferAmount = Cosmos.Amount.newBuilder().apply { + amount = "1" + denom = "ubld" + }.build() + + val message = Cosmos.Message.newBuilder().apply { + sendCoinsMessage = Cosmos.Message.Send.newBuilder().apply { + fromAddress = from + toAddress = "agoric1cqvwa8jr6pmt45jndanc8lqmdsxjkhw0yertc0" + addAllAmounts(listOf(transferAmount)) + }.build() + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "2000" + denom = "ubld" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 100000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = Cosmos.SigningMode.Protobuf + accountNumber = 62972 + chainId = "agoric-3" + sequence = 1 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, CoinType.AGORIC, Cosmos.SigningOutput.parser()) + + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWFnb3JpYzE4enZ2Z2s2ajNlcTV3ZDdtcXhjY2d0MjBnejJ3OTRjeTg4YWVrNRItYWdvcmljMWNxdndhOGpyNnBtdDQ1am5kYW5jOGxxbWRzeGpraHcweWVydGMwGgkKBHVibGQSATESZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA9+aXkCJ+J1FkT+yuFbemEx+i/E0TMZETMlwWJmkjJOdEgQKAggBGAESEgoMCgR1YmxkEgQyMDAwEKCNBhpAenbGO4UBK610dwSY6l5pl58qwHW1OujQ/9vF9unQdrA1SE0b/2mZxnevy5y3u6pJfBffWUfCx68PcVEu7D3EYQ==\"}") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/algorand/TestAlgorandSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/algorand/TestAlgorandSigner.kt index 5047a62880e..e466740212b 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/algorand/TestAlgorandSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/algorand/TestAlgorandSigner.kt @@ -7,6 +7,7 @@ import com.trustwallet.core.app.utils.toHexBytesInByteString import org.junit.Assert.* import org.junit.Test import wallet.core.java.AnySigner +import wallet.core.jni.Base64 import wallet.core.jni.CoinType.ALGORAND import wallet.core.jni.proto.Algorand import wallet.core.jni.proto.Algorand.SigningOutput @@ -17,6 +18,33 @@ class TestAlgorandSigner { System.loadLibrary("TrustWalletCore") } + @Test + fun algorandTransactionSigningNFTTransfer() { + // Successfully broadcasted: https://algoexplorer.io/tx/FFLUH4QKZHG744RIQ2AZNWZUSIIH262KZ4MEWSY4RXMWN5NMOOJA + val transaction = Algorand.AssetTransfer.newBuilder() + .setToAddress("362T7CSXNLIOBX6J3H2SCPS4LPYFNV6DDWE6G64ZEUJ6SY5OJIR6SB5CVE") + .setAmount(1) + .setAssetId(989643841) + .build() + val signingInput = Algorand.SigningInput.newBuilder() + .setGenesisId("mainnet-v1.0") + .setGenesisHash(ByteString.copyFrom(Base64.decode("wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="))) + .setNote(ByteString.copyFrom(Base64.decode("VFdUIFRPIFRIRSBNT09O"))) + .setPrivateKey("dc6051ffc7b3ec601bde432f6dea34d40fe3855e4181afa0f0524c42194a6da7".toHexBytesInByteString()) + .setFirstRound(27963950) + .setLastRound(27964950) + .setFee(1000) + .setAssetTransfer(transaction) + .build() + + val output = AnySigner.sign(signingInput, ALGORAND, SigningOutput.parser()) + + assertEquals( + output.signature, + "nXQsDH1ilG3DIo2VQm5tdYKXe9o599ygdqikmROpZiNXAvQeK3avJqgjM5o+iByCdq6uOxlbveDyVmL9nZxxBg==" + ) + } + @Test fun AlgorandTransactionSigning() { val transaction = Algorand.Transfer.newBuilder() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosAddress.kt new file mode 100644 index 00000000000..c8bed69c5d8 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosAddress.kt @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.aptos + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestAptosAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val any = AnyAddress("0x6af7d07b8a541913dfa87a9f99628faa255c70241ef9ebd9b82a7e715ee13108", CoinType.APTOS) + assertEquals(any.coin(), CoinType.APTOS) + assertEquals(any.description(), "0x6af7d07b8a541913dfa87a9f99628faa255c70241ef9ebd9b82a7e715ee13108") + + assertFalse(AnyAddress.isValid("0xMQqpqMQgCBuiPkoXfgZZsJvuzCeI1zc00z6vHJj4", CoinType.APTOS)) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosSigner.kt new file mode 100644 index 00000000000..e536821658b --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosSigner.kt @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.aptos + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType +import wallet.core.jni.proto.Aptos + +class TestAptosSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun AptosTransactionBlindSigning() { + // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x7efd69e7f9462774b932ce500ab51c0d0dcc004cf272e09f8ffd5804c2a84e33?network=mainnet + val key = + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec".toHexBytesInByteString() + + val payloadJson = """ + { + "function": "0x16fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c::AnimeSwapPoolV1::swap_exact_coins_for_coins_3_pair_entry", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin", + "0x881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f4::coin::MOJO", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC" + ], + "arguments": [ + "1000000", + "49329" + ], + "type": "entry_function_payload" + } + """.trimIndent() + val signingInput = Aptos.SigningInput.newBuilder() + .setChainId(1) + .setExpirationTimestampSecs(3664390082) + .setGasUnitPrice(100) + .setMaxGasAmount(100011) + .setSender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30") + .setSequenceNumber(42) + .setAnyEncoded(payloadJson) + .setPrivateKey(key) + .build() + + val result = AnySigner.sign(signingInput, CoinType.APTOS, Aptos.SigningOutput.parser()) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.rawTxn.toByteArray())), + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada0000000001" + ) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.authenticator.signature.toByteArray())), + "42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b" + ) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.encoded.toByteArray())), + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c4042cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b" + ) + } + + @Test + fun AptosTransactionSigning() { + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467?network=devnet + val key = + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec".toHexBytesInByteString() + + val transfer = Aptos.TransferMessage.newBuilder().setAmount(1000) + .setTo("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30").build() + val signingInput = Aptos.SigningInput.newBuilder().setChainId(33) + .setSender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30") + .setSequenceNumber(99) + .setGasUnitPrice(100) + .setMaxGasAmount(3296766) + .setExpirationTimestampSecs(3664390082) + .setTransfer(transfer) + .setPrivateKey(key) + .build() + + val result = AnySigner.sign(signingInput, CoinType.APTOS, Aptos.SigningOutput.parser()) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.rawTxn.toByteArray())), + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021" + ) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.authenticator.signature.toByteArray())), + "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01" + ) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.encoded.toByteArray())), + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01" + ) + } + + @Test + fun AptosTransferTokensCoins() { + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0x197d40ea12e2bfc65a0a913b9f4ca3b0b0208fe0c1514d3d55cef3d5bcf25211?network=mainnet + val key = + "e7f56c77189e03699a75d8ec5c090e41f3d9d4783bc49c33df8a93d915e10de8".toHexBytesInByteString() + + val function = Aptos.StructTag.newBuilder() + .setAccountAddress("0xe9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9") + .setModule("mee_coin") + .setName("MeeCoin") + .build() + + val transfer = Aptos.TokenTransferCoinsMessage.newBuilder() + .setAmount(10000) + .setTo("0xb7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c") + .setFunction(function) + .build() + val signingInput = Aptos.SigningInput.newBuilder() + .setChainId(1) + .setSender("0x1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf25") + .setSequenceNumber(2) + .setGasUnitPrice(100) + .setMaxGasAmount(2000) + .setExpirationTimestampSecs(3664390082) + .setTokenTransferCoins(transfer) + .setPrivateKey(key) + .build() + + val result = AnySigner.sign(signingInput, CoinType.APTOS, Aptos.SigningOutput.parser()) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.rawTxn.toByteArray())), + "1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf2502000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e7472616e736665725f636f696e730107e9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9086d65655f636f696e074d6565436f696e000220b7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c081027000000000000d0070000000000006400000000000000c2276ada0000000001" + ) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.authenticator.signature.toByteArray())), + "30ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d" + ) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.encoded.toByteArray())), + "1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf2502000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e7472616e736665725f636f696e730107e9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9086d65655f636f696e074d6565436f696e000220b7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c081027000000000000d0070000000000006400000000000000c2276ada0000000001002062e7a6a486553b56a53e89dfae3f780693e537e5b0a7ed33290780e581ca83694030ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d" + ) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainAddress.kt index d988eed5bb1..ec361735357 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainAddress.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.bandchain diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt index 662773bba9c..001e9d90631 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.bandchain diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binance/TestBinanceAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binance/TestBinanceAddress.kt index 13b91a5e527..f2c62428f64 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binance/TestBinanceAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binance/TestBinanceAddress.kt @@ -2,6 +2,8 @@ package com.trustwallet.core.app.blockchains.binance import com.trustwallet.core.app.utils.toHexBytes import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Test import wallet.core.jni.* import com.trustwallet.core.app.utils.toHex @@ -22,6 +24,23 @@ class TestBinanceAddress { assertEquals("bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", address.description()) } + @Test + fun testIsValid() { + assertTrue(AnyAddress.isValid("bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw", CoinType.BINANCE)); + + assertFalse(AnyAddress.isValid("bad1devga6q804tx9fqrnx0vtu5r36kxgp9tqx8h9k", CoinType.BINANCE)); + assertFalse(AnyAddress.isValid("tbnb1devga6q804tx9fqrnx0vtu5r36kxgp9t4ruzk2", CoinType.BINANCE)); + } + + @Test + fun testIsValidBech32() { + assertTrue(AnyAddress.isValidBech32("bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw", CoinType.BINANCE, "bnb")); + assertTrue(AnyAddress.isValidBech32("tbnb1devga6q804tx9fqrnx0vtu5r36kxgp9t4ruzk2", CoinType.BINANCE, "tbnb")); + + assertFalse(AnyAddress.isValidBech32("bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw", CoinType.BINANCE, "tbnb")); + assertFalse(AnyAddress.isValidBech32("tbnb1devga6q804tx9fqrnx0vtu5r36kxgp9t4ruzk2", CoinType.BINANCE, "bnb")); + } + @Test fun testBinanceMainnet() { val wallet = HDWallet("rabbit tilt arm protect banner ill produce vendor april bike much identify pond upset front easily glass gallery address hair priority focus forest angle", "") @@ -31,4 +50,15 @@ class TestBinanceAddress { assertEquals("0x727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06", key.data().toHex()) assertEquals("bnb1devga6q804tx9fqrnx0vtu5r36kxgp9tmk4xkm", address) } + + @Test + fun testBinanceTestnet() { + val wallet = HDWallet("rabbit tilt arm protect banner ill produce vendor april bike much identify pond upset front easily glass gallery address hair priority focus forest angle", "") + val privateKey = wallet.getKeyForCoin(CoinType.BINANCE) + val publicKey = privateKey.getPublicKeySecp256k1(true) + val address = AnyAddress(publicKey, CoinType.BINANCE, "tbnb") + + assertEquals("0x727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06", privateKey.data().toHex()) + assertEquals("tbnb1devga6q804tx9fqrnx0vtu5r36kxgp9t4ruzk2", address.description()) + } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binance/TestBinanceWalletConnectSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binance/TestBinanceWalletConnectSigning.kt new file mode 100644 index 00000000000..a66c2dc9776 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binance/TestBinanceWalletConnectSigning.kt @@ -0,0 +1,46 @@ +package com.trustwallet.core.app.blockchains.binance + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexBytes +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.proto.Binance.SigningOutput +import wallet.core.jni.proto.WalletConnect +import wallet.core.jni.* +import wallet.core.jni.CoinType.BINANCE +import wallet.core.java.AnySigner +import wallet.core.jni.proto.Common + +class TestBinanceWalletConnectSigning { + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSignBinanceTransactionFromWalletConnectRequest() { + // Step 1: Parse a signing request received through WalletConnect. + + val parsingInput = WalletConnect.ParseRequestInput.newBuilder().apply { + method = WalletConnect.Method.CosmosSignAmino + payload = "{\"signerAddress\":\"bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2\",\"signDoc\":{\"account_number\":\"19\",\"chain_id\":\"chain-bnb\",\"memo\":\"\",\"data\":null,\"msgs\":[{\"inputs\":[{\"address\":\"bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2\",\"coins\":[{\"amount\":1001000000,\"denom\":\"BNB\"}]}],\"outputs\":[{\"address\":\"bnb13zeh6hs97d5eu2s5qerguhv8ewwue6u4ywa6yf\",\"coins\":[{\"amount\":1001000000,\"denom\":\"BNB\"}]}]}],\"sequence\":\"23\",\"source\":\"1\"}}" + }.build() + + val parsingOutputBytes = WalletConnectRequest.parse(BINANCE, parsingInput.toByteArray()) + val parsingOutput = WalletConnect.ParseRequestOutput.parseFrom(parsingOutputBytes) + + assertEquals(parsingOutput.error, Common.SigningError.OK) + + // Step 2: Set missing fields. + + val signingInput = parsingOutput.binance.toBuilder().apply { + privateKey = ByteString.copyFrom("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832".toHexBytes()) + }.build() + + // Step 3: Sign the transaction. + + val output = AnySigner.sign(signingInput, BINANCE, SigningOutput.parser()) + + assertEquals(output.error, Common.SigningError.OK) + assertEquals(output.signatureJson, "{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"Amo1kgCI2Yw4iMpoxT38k/RWRgJgbLuH8P5e5TPbOOUC\"},\"signature\":\"PCTHhMa7+Z1U/6uxU+3LbTxKd0k231xypdMolyVvjgYvMg+0dTMC+wqW8IxHWXTSDt/Ronu+7ac1h/WN3JWJdQ==\"}") + } +} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinPsbt.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinPsbt.kt new file mode 100644 index 00000000000..3f8a083b7de --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinPsbt.kt @@ -0,0 +1,101 @@ +package com.trustwallet.core.app.blockchains.bitcoin + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.BitcoinPsbt +import wallet.core.jni.BitcoinScript +import wallet.core.jni.BitcoinSigHashType +import wallet.core.jni.CoinType +import wallet.core.jni.CoinType.BITCOIN +import wallet.core.jni.Hash +import wallet.core.jni.PrivateKey +import wallet.core.jni.PublicKey +import wallet.core.jni.PublicKeyType +import wallet.core.jni.proto.Bitcoin +import wallet.core.jni.proto.Bitcoin.SigningOutput +import wallet.core.jni.proto.BitcoinV2 +import wallet.core.jni.proto.Common.SigningError + +class TestBitcoinPsbt { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSignThorSwap() { + // Successfully broadcasted tx: https://mempool.space/tx/634a416e82ac710166725f6a4090ac7b5db69687e86b2d2e38dcb3d91c956c32 + + val privateKey = "f00ffbe44c5c2838c13d2778854ac66b75e04eb6054f0241989e223223ad5e55".toHexBytesInByteString() + val psbt = "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000".toHexBytesInByteString() + + val input = BitcoinV2.PsbtSigningInput.newBuilder() + .setPsbt(psbt) + .addPrivateKeys(privateKey) + .build() + + val outputData = BitcoinPsbt.sign(input.toByteArray(), BITCOIN) + val output = BitcoinV2.PsbtSigningOutput.parseFrom(outputData) + + assertEquals(output.error, SigningError.OK) + assertEquals( + output.psbt.toByteArray().toHex(), + "0x70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d01086c02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000" + ) + assertEquals( + output.encoded.toByteArray().toHex(), + "0x02000000000101147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000" + ) + assertEquals( + output.txid.toByteArray().toHex(), + "0x634a416e82ac710166725f6a4090ac7b5db69687e86b2d2e38dcb3d91c956c32" + ) + } + + @Test + fun testPlanThorSwap() { + // Successfully broadcasted tx: https://mempool.space/tx/634a416e82ac710166725f6a4090ac7b5db69687e86b2d2e38dcb3d91c956c32 + + val privateKey = PrivateKey("f00ffbe44c5c2838c13d2778854ac66b75e04eb6054f0241989e223223ad5e55".toHexBytes()) + val publicKey = privateKey.getPublicKeySecp256k1(true) + val psbt = "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000".toHexBytesInByteString() + + val input = BitcoinV2.PsbtSigningInput.newBuilder() + .setPsbt(psbt) + .addPublicKeys(ByteString.copyFrom(publicKey.data())) + .build() + + val outputData = BitcoinPsbt.plan(input.toByteArray(), BITCOIN) + val output = BitcoinV2.TransactionPlan.parseFrom(outputData) + + assertEquals(output.error, SigningError.OK) + + assertEquals(output.getInputs(0).receiverAddress, "bc1qkyu3n8k8jmekl3pwvdl59k5w8enjp25akz2r3z") + assertEquals(output.getInputs(0).value, 66_406) + + // Vault transfer + assertEquals(output.getOutputs(0).toAddress, "bc1q7g48qdshqd000aysws74pun2uzxrp598gcfum0") + assertEquals(output.getOutputs(0).value, 60_000) + + // OP_RETURN + assertEquals( + output.getOutputs(1).customScriptPubkey.toByteArray().toHex(), + "0x6a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a3530" + ) + assertEquals(output.getOutputs(1).value, 0) + + // Change output + assertEquals(output.getOutputs(2).toAddress, "bc1qkyu3n8k8jmekl3pwvdl59k5w8enjp25akz2r3z") + assertEquals(output.getOutputs(2).value, 4_670) + + assertEquals(output.feeEstimate, 1736) + // Please note that `change` in PSBT planning is always 0. + // That's because we aren't able to determine which output is an actual change from PSBT. + assertEquals(output.change, 0) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt index 45d627d8b46..9d6bcdab802 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt @@ -10,8 +10,13 @@ import wallet.core.jni.BitcoinScript import wallet.core.jni.BitcoinSigHashType import wallet.core.jni.CoinType import wallet.core.jni.CoinType.BITCOIN +import wallet.core.jni.Hash +import wallet.core.jni.PrivateKey +import wallet.core.jni.PublicKey +import wallet.core.jni.PublicKeyType import wallet.core.jni.proto.Bitcoin import wallet.core.jni.proto.Bitcoin.SigningOutput +import wallet.core.jni.proto.BitcoinV2 import wallet.core.jni.proto.Common.SigningError class TestBitcoinSigning { @@ -155,4 +160,197 @@ class TestBitcoinSigning { assertEquals("0x01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000494830450221008d49c4d7cc5ab93c01a67ce3f4ed2c45c59d4da6c76c891a9b56e67eda2e8cb4022078849134c697b1c70c1a19b900d94d8cab00ad7bcc8afe7ad1f6b184c13effa601ffffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02d8d60000000000001976a914a6d85a488bb777a540f24bf777d30d1486036f6188acee430000000000001976a9147d77e6cfb05a9cfc123824279f6caf8b66ac267688ac0002473044022074573d7f7828ae193fbea6d72c0fe2df6cee5c02bf455ea3d9312e16d6a9576502203861c5a3b3a83d4fe372034073f60201a8a944fb4536be0ea7544ab177b967600121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635700000000", Numeric.toHexString(encoded.toByteArray())); } + + @Test + fun testSignBrc20Commit() { + // Successfully broadcasted: https://www.blockchain.com/explorer/transactions/btc/797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1 + val privateKeyData = (Numeric.hexStringToByteArray("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129")) + val dustSatoshis = 546.toLong() + val txId = Numeric.hexStringToByteArray("8ec895b4d30adb01e38471ca1019bfc8c3e5fbd1f28d9e7b5653260d89989008").reversedArray() + + val privateKey = PrivateKey(privateKeyData) + val publicKey = ByteString.copyFrom(privateKey.getPublicKeySecp256k1(true).data()) + + val utxo0 = BitcoinV2.Input.newBuilder() + .setOutPoint(BitcoinV2.OutPoint.newBuilder().apply { + hash = ByteString.copyFrom(txId) + vout = 1 + }) + .setValue(26_400) + .setSighashType(BitcoinSigHashType.ALL.value()) + .setScriptBuilder(BitcoinV2.Input.InputBuilder.newBuilder().apply { + p2Wpkh = BitcoinV2.PublicKeyOrHash.newBuilder().setPubkey(publicKey).build() + }) + + val out0 = BitcoinV2.Output.newBuilder() + .setValue(7_000) + .setBuilder(BitcoinV2.Output.OutputBuilder.newBuilder().apply { + brc20Inscribe = BitcoinV2.Output.OutputBrc20Inscription.newBuilder().apply { + inscribeTo = publicKey + ticker = "oadf" + transferAmount = "20" + }.build() + }) + + val changeOutput = BitcoinV2.Output.newBuilder() + .setValue(16_400) + .setBuilder(BitcoinV2.Output.OutputBuilder.newBuilder().apply { + p2Wpkh = BitcoinV2.PublicKeyOrHash.newBuilder().setPubkey(publicKey).build() + }) + + val signingInput = BitcoinV2.SigningInput.newBuilder() + .setVersion(BitcoinV2.TransactionVersion.V2) + .addPrivateKeys(ByteString.copyFrom(privateKeyData)) + .addInputs(utxo0) + .addOutputs(out0) + .addOutputs(changeOutput) + .setInputSelector(BitcoinV2.InputSelector.UseAll) + .setChainInfo(BitcoinV2.ChainInfo.newBuilder().apply { + p2PkhPrefix = 0 + p2ShPrefix = 5 + }) + .setDangerousUseFixedSchnorrRng(true) + .setFixedDustThreshold(dustSatoshis) + .build() + + val legacySigningInput = Bitcoin.SigningInput.newBuilder().apply { + signingV2 = signingInput + } + + val output = AnySigner.sign(legacySigningInput.build(), BITCOIN, SigningOutput.parser()) + + assertEquals(output.error, SigningError.OK) + assertEquals(output.signingResultV2.error, SigningError.OK) + assertEquals(Numeric.toHexString(output.signingResultV2.encoded.toByteArray()), "0x02000000000101089098890d2653567b9e8df2d1fbe5c3c8bf1910ca7184e301db0ad3b495c88e0100000000ffffffff02581b000000000000225120e8b706a97732e705e22ae7710703e7f589ed13c636324461afa443016134cc051040000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d02483045022100a44aa28446a9a886b378a4a65e32ad9a3108870bd725dc6105160bed4f317097022069e9de36422e4ce2e42b39884aa5f626f8f94194d1013007d5a1ea9220a06dce0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000") + assertEquals(Numeric.toHexString(output.signingResultV2.txid.toByteArray()), "0x797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1") + } + + @Test + fun testSignBrc20Reveal() { + // Successfully broadcasted: https://www.blockchain.com/explorer/transactions/btc/7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca + val privateKeyData = (Numeric.hexStringToByteArray("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129")) + val dustSatoshis = 546.toLong() + val txIdCommit = Numeric.hexStringToByteArray("797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1").reversedArray() + + val privateKey = PrivateKey(privateKeyData) + val publicKey = ByteString.copyFrom(privateKey.getPublicKeySecp256k1(true).data()) + + val utxo0 = BitcoinV2.Input.newBuilder() + .setOutPoint(BitcoinV2.OutPoint.newBuilder().apply { + hash = ByteString.copyFrom(txIdCommit) + vout = 0 + }) + .setValue(7_000) + .setSighashType(BitcoinSigHashType.ALL.value()) + .setScriptBuilder(BitcoinV2.Input.InputBuilder.newBuilder().apply { + brc20Inscribe = BitcoinV2.Input.InputBrc20Inscription.newBuilder().apply { + inscribeTo = publicKey + ticker = "oadf" + transferAmount = "20" + }.build() + }) + + val out0 = BitcoinV2.Output.newBuilder() + .setValue(dustSatoshis) + .setBuilder(BitcoinV2.Output.OutputBuilder.newBuilder().apply { + p2Wpkh = BitcoinV2.PublicKeyOrHash.newBuilder().setPubkey(publicKey).build() + }) + + val signingInput = BitcoinV2.SigningInput.newBuilder() + .setVersion(BitcoinV2.TransactionVersion.V2) + .addPrivateKeys(ByteString.copyFrom(privateKeyData)) + .addInputs(utxo0) + .addOutputs(out0) + .setInputSelector(BitcoinV2.InputSelector.UseAll) + .setChainInfo(BitcoinV2.ChainInfo.newBuilder().apply { + p2PkhPrefix = 0 + p2ShPrefix = 5 + }) + .setDangerousUseFixedSchnorrRng(true) + .setFixedDustThreshold(dustSatoshis) + .build() + + val legacySigningInput = Bitcoin.SigningInput.newBuilder().apply { + signingV2 = signingInput + } + + val output = AnySigner.sign(legacySigningInput.build(), BITCOIN, SigningOutput.parser()) + + assertEquals(output.error, SigningError.OK) + assertEquals(output.signingResultV2.error, SigningError.OK) + assertEquals(Numeric.toHexString(output.signingResultV2.encoded.toByteArray()), "0x02000000000101b11f1782607a1fe5f033ccf9dc17404db020a0dedff94183596ee67ad4177d790000000000ffffffff012202000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d03406a35548b8fa4620028e021a944c1d3dc6e947243a7bfc901bf63fefae0d2460efa149a6440cab51966aa4f09faef2d1e5efcba23ab4ca6e669da598022dbcfe35b0063036f7264010118746578742f706c61696e3b636861727365743d7574662d3800377b2270223a226272632d3230222c226f70223a227472616e73666572222c227469636b223a226f616466222c22616d74223a223230227d6821c00f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000") + assertEquals(Numeric.toHexString(output.signingResultV2.txid.toByteArray()), "0x7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca") + } + + @Test + fun testSignBrc20Transfer() { + // Successfully broadcasted: https://www.blockchain.com/explorer/transactions/btc/3e3576eb02667fac284a5ecfcb25768969680cc4c597784602d0a33ba7c654b7 + val privateKeyData = (Numeric.hexStringToByteArray("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129")) + val dustSatoshis = 546.toLong() + val txIdInscription = Numeric.hexStringToByteArray("7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca").reversedArray() + val txIdForFees = Numeric.hexStringToByteArray("797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1").reversedArray() + + val privateKey = PrivateKey(privateKeyData) + val publicKey = ByteString.copyFrom(privateKey.getPublicKeySecp256k1(true).data()) + val bobAddress = "bc1qazgc2zhu2kmy42py0vs8d7yff67l3zgpwfzlpk" + + val utxo0 = BitcoinV2.Input.newBuilder() + .setOutPoint(BitcoinV2.OutPoint.newBuilder().apply { + hash = ByteString.copyFrom(txIdInscription) + vout = 0 + }) + .setValue(dustSatoshis) + .setSighashType(BitcoinSigHashType.ALL.value()) + .setScriptBuilder(BitcoinV2.Input.InputBuilder.newBuilder().apply { + p2Wpkh = BitcoinV2.PublicKeyOrHash.newBuilder().setPubkey(publicKey).build() + }) + + val utxo1 = BitcoinV2.Input.newBuilder() + .setOutPoint(BitcoinV2.OutPoint.newBuilder().apply { + hash = ByteString.copyFrom(txIdForFees) + vout = 1 + }) + .setValue(16_400) + .setSighashType(BitcoinSigHashType.ALL.value()) + .setScriptBuilder(BitcoinV2.Input.InputBuilder.newBuilder().apply { + p2Wpkh = BitcoinV2.PublicKeyOrHash.newBuilder().setPubkey(publicKey).build() + }) + + val out0 = BitcoinV2.Output.newBuilder() + .setValue(dustSatoshis) + .setToAddress(bobAddress) + + val changeOutput = BitcoinV2.Output.newBuilder() + .setValue(13_400) + .setBuilder(BitcoinV2.Output.OutputBuilder.newBuilder().apply { + p2Wpkh = BitcoinV2.PublicKeyOrHash.newBuilder().setPubkey(publicKey).build() + }) + + val signingInput = BitcoinV2.SigningInput.newBuilder() + .setVersion(BitcoinV2.TransactionVersion.V2) + .addPrivateKeys(ByteString.copyFrom(privateKeyData)) + .addInputs(utxo0) + .addInputs(utxo1) + .addOutputs(out0) + .addOutputs(changeOutput) + .setInputSelector(BitcoinV2.InputSelector.UseAll) + .setChainInfo(BitcoinV2.ChainInfo.newBuilder().apply { + p2PkhPrefix = 0 + p2ShPrefix = 5 + }) + .setDangerousUseFixedSchnorrRng(true) + .setFixedDustThreshold(dustSatoshis) + .build() + + val legacySigningInput = Bitcoin.SigningInput.newBuilder().apply { + signingV2 = signingInput + } + + val output = AnySigner.sign(legacySigningInput.build(), BITCOIN, SigningOutput.parser()) + + assertEquals(output.error, SigningError.OK) + assertEquals(output.signingResultV2.error, SigningError.OK) + assertEquals(Numeric.toHexString(output.signingResultV2.encoded.toByteArray()), "0x02000000000102ca3edda74a46877efa5364ab85947e148508713910ada23e147ea28926dc46700000000000ffffffffb11f1782607a1fe5f033ccf9dc17404db020a0dedff94183596ee67ad4177d790100000000ffffffff022202000000000000160014e891850afc55b64aa8247b2076f8894ebdf889015834000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d024830450221008798393eb0b7390217591a8c33abe18dd2f7ea7009766e0d833edeaec63f2ec302200cf876ff52e68dbaf108a3f6da250713a9b04949a8f1dcd1fb867b24052236950121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb0248304502210096bbb9d1f0596d69875646689e46f29485e8ceccacde9d0025db87fd96d3066902206d6de2dd69d965d28df3441b94c76e812384ab9297e69afe3480ee4031e1b2060121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000") + assertEquals(Numeric.toHexString(output.signingResultV2.txid.toByteArray()), "0x3e3576eb02667fac284a5ecfcb25768969680cc4c597784602d0a33ba7c654b7") + } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoindiamond/TestBitcoinDiamondAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoindiamond/TestBitcoinDiamondAddress.kt new file mode 100644 index 00000000000..497b2d6dedc --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoindiamond/TestBitcoinDiamondAddress.kt @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.bitcoindiamond + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestBitcoinDiamondAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(true); + val address = AnyAddress(pubkey, CoinType.BITCOINDIAMOND) + val expected = AnyAddress("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8", CoinType.BITCOINDIAMOND) + + assertEquals(pubkey.data().toHex(), "0x02485a209514cc896f8ed736e205bc4c35bd5299ef3f9e84054475336b964c02a3") + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoindiamond/TestBitcoinDiamondSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoindiamond/TestBitcoinDiamondSigner.kt new file mode 100644 index 00000000000..3bbbd9a77fd --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoindiamond/TestBitcoinDiamondSigner.kt @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.bitcoindiamond + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test + +import wallet.core.java.AnySigner +import wallet.core.jni.BitcoinScript +import wallet.core.jni.BitcoinSigHashType +import wallet.core.jni.CoinType +import wallet.core.jni.proto.Bitcoin +import wallet.core.jni.proto.Common.SigningError + +class TestBitcoinDiamondSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun BitcoinDiamondTransactionSigning() { + val toScript = BitcoinScript.lockScriptForAddress("1HevQVTSEc8cEpDm65UpfCdj5erd4xxBhx", CoinType.BITCOINDIAMOND); + assertEquals(Numeric.toHexString(toScript.data()), "0x76a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac"); + + // prepare SigningInput + val input = Bitcoin.SigningInput.newBuilder() + .setHashType(BitcoinScript.hashTypeForCoin(CoinType.BITCOINDIAMOND)) + .setAmount(17615) + .setByteFee(1) + .setToAddress("1HevQVTSEc8cEpDm65UpfCdj5erd4xxBhx") + .setChangeAddress("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8") + .setCoinType(CoinType.BITCOINDIAMOND.value()) + + val utxoKey0 = + (Numeric.hexStringToByteArray("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")) + input.addPrivateKey(ByteString.copyFrom(utxoKey0)) + + // build utxo + val txHash0 = (Numeric.hexStringToByteArray("034f4667301711e8a69236a93476ed798f9c11aaae472da5b315191a0453461d")) + val outpoint0 = Bitcoin.OutPoint.newBuilder() + .setHash(ByteString.copyFrom(txHash0)) + .setIndex(0) + .setSequence(Long.MAX_VALUE.toInt()) + .build() + + val utxo0 = Bitcoin.UnspentTransaction.newBuilder() + .setAmount(27615) + .setOutPoint(outpoint0) + .setScript(ByteString.copyFrom("76a914a48da46386ce52cccad178de900c71f06130c31088ac".toHexBytes())) + .build() + + input.addUtxo(utxo0) + + val plan = AnySigner.plan(input.build(), CoinType.BITCOINDIAMOND, Bitcoin.TransactionPlan.parser()) + + input.plan = Bitcoin.TransactionPlan.newBuilder() + .mergeFrom(plan) + .setAmount(17615) + .setFee(10000) + .setChange(0) + .setPreblockhash(ByteString.copyFrom("99668daf7c77c6462f3ba8fab61b4f62abaabd62911e7e59729ee9e1929dfa4b".toHexBytes())) + .build() + + + val output = AnySigner.sign(input.build(), CoinType.BITCOINDIAMOND, Bitcoin.SigningOutput.parser()) + + assertEquals(output.error, SigningError.OK) + val signedTransaction = output.transaction + assert(signedTransaction.isInitialized) + assertEquals(12, signedTransaction.version) + assertEquals(1, signedTransaction.inputsCount) + assertEquals(1, signedTransaction.outputsCount) + + val encoded = output.encoded + assertEquals("0x0c00000099668daf7c77c6462f3ba8fab61b4f62abaabd62911e7e59729ee9e1929dfa4b01034f4667301711e8a69236a93476ed798f9c11aaae472da5b315191a0453461d000000006a473044022078e0d3a9e1eb270ab02c15f8fcf1d3bfc95a324839690b7de4f011a4266132ff02204679e8103c4d3f0bb5192a5f53cc273732fd0e8392ab3b00dc708fd24d0160b3012102485a209514cc896f8ed736e205bc4c35bd5299ef3f9e84054475336b964c02a3ffffffff01cf440000000000001976a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac00000000", + Numeric.toHexString(encoded.toByteArray())); + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleAddress.kt index 99baa611f71..d8eb3424893 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleAddress.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.bluzelle diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleSigner.kt index ba4d7e99238..bf40f0dcc67 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleSigner.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.bluzelle diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoAddress.kt index 8a6a65cafbd..a505e201176 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoAddress.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.cardano @@ -21,7 +19,7 @@ class TestCardanoAddress { @Test fun testAddress() { val key = PrivateKey("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a".toHexByteArray()) - val pubkey = key.publicKeyEd25519Extended + val pubkey = key.publicKeyEd25519Cardano val address = AnyAddress(pubkey, CoinType.CARDANO) val expected = AnyAddress("addr1qx4z6twzknkkux0hhp0kq6hvdfutczp56g56y5em8r8mgvxalp7nkkk25vuspleke2zltaetmlwrfxv7t049cq9jmwjswmfw6t", CoinType.CARDANO) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt index 3fb60658ea2..d9450e9881c 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.cardano @@ -15,10 +13,11 @@ import org.junit.Assert.assertEquals import org.junit.Test import wallet.core.java.AnySigner import wallet.core.jni.* +import wallet.core.jni.Cardano.getByronAddress +import wallet.core.jni.Cardano.getStakingAddress +import wallet.core.jni.Cardano.outputMinAdaAmount import wallet.core.jni.CoinType.CARDANO import wallet.core.jni.proto.Cardano -import wallet.core.jni.proto.Cardano.SigningInput -import wallet.core.jni.proto.Cardano.SigningOutput import wallet.core.jni.proto.Common.SigningError @@ -39,7 +38,7 @@ class TestCardanoSigning { .setTransferMessage(message) .setTtl(53333333) - val privKey = (Numeric.hexStringToByteArray("089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e")) + val privKey = Numeric.hexStringToByteArray("089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e") input.addPrivateKey(ByteString.copyFrom(privKey)) val outpoint1 = Cardano.OutPoint.newBuilder() @@ -64,7 +63,7 @@ class TestCardanoSigning { .build() input.addUtxos(utxo2) - val output = AnySigner.sign(input.build(), CARDANO, SigningOutput.parser()) + val output = AnySigner.sign(input.build(), CARDANO, Cardano.SigningOutput.parser()) assertEquals(output.error, SigningError.OK) val encoded = output.encoded @@ -75,6 +74,60 @@ class TestCardanoSigning { assertEquals(Numeric.toHexString(txid.toByteArray()), "0x9b5b15e133cd73ccaa85307d2986aebc846505118a2eb4e6111e6b4b67d1f389"); } + /// Successfully broadcasted: + /// https://cardanoscan.io/transaction/0203ce2c91f59f169a26e9ef91254639d2b7911afac9c7c0ae64539f88ba46a5 + @Test + fun testSignTransferFromLegacy() { + val privateKey = PrivateKey("98f266d1aac660179bc2f456033941238ee6b2beb8ed0f9f34c9902816781f5a9903d1d395d6ab887b65ea5e344ef09b449507c21a75f0ce8c59d0ed1c6764eba7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f4e890ca4eb6bec44bf751b5a843174534af64d6ad1f44e0613db78a7018781f5aa151d2997f52059466b715d8eefab30a78b874ae6ef4931fa58bb21ef8ce2423d46f19d0fbf75afb0b9a24e31d533f4fd74cee3b56e162568e8defe37123afc4".toHexByteArray()) + var publicKey = privateKey.publicKeyEd25519Cardano + var byronAddress = wallet.core.jni.Cardano.getByronAddress(publicKey) + + assertEquals(byronAddress, "Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8") + + val message = Cardano.Transfer.newBuilder() + .setToAddress("addr1q90uh2eawrdc9vaemftgd50l28yrh9lqxtjjh4z6dnn0u7ggasexxdyyk9f05atygnjlccsjsggtc87hhqjna32fpv5qeq96ls") + .setChangeAddress("addr1qx55ymlqemndq8gluv40v58pu76a2tp4mzjnyx8n6zrp2vtzrs43a0057y0edkn8lh9su8vh5lnhs4npv6l9tuvncv8swc7t08") + .setAmount(3_000_000) + .build() + val input = Cardano.SigningInput.newBuilder() + .setTransferMessage(message) + .setTtl(190000000) + + input.addPrivateKey(ByteString.copyFrom(privateKey.data())) + + val outpoint1 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("8316e5007d61fb90652cabb41141972a38b5bc60954d602cf843476aa3f67f63"))) + .setOutputIndex(0) + .build() + val utxo1 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint1) + .setAddress("Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8") + .setAmount(2_500_000) + .build() + input.addUtxos(utxo1) + + val outpoint2 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("e29392c59c903fefb905730587d22cae8bda30bd8d9aeec3eca082ae77675946"))) + .setOutputIndex(0) + .build() + val utxo2 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint2) + .setAddress("Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8") + .setAmount(1_700_000) + .build() + input.addUtxos(utxo2) + + val output = AnySigner.sign(input.build(), CARDANO, Cardano.SigningOutput.parser()) + assertEquals(output.error, SigningError.OK) + + val encoded = output.encoded + assertEquals(Numeric.toHexString(encoded.toByteArray()), + "0x83a400828258208316e5007d61fb90652cabb41141972a38b5bc60954d602cf843476aa3f67f6300825820e29392c59c903fefb905730587d22cae8bda30bd8d9aeec3eca082ae77675946000182825839015fcbab3d70db82b3b9da5686d1ff51c83b97e032e52bd45a6ce6fe7908ec32633484b152fa756444e5fc62128210bc1fd7b8253ec5490b281a002dc6c082583901a9426fe0cee6d01d1fe32af650e1e7b5d52c35d8a53218f3d0861531621c2b1ebdf4f11f96da67fdcb0e1d97a7e778566166be55f193c30f1a000f9ec1021a0002b0bf031a0b532b80a20081825820d163c8c4f0be7c22cd3a1152abb013c855ea614b92201497a568c5d93ceeb41e58406a23ab9267867fbf021c1cb2232bc83d2cdd663d651d22d59b6cddbca5cb106d4db99da50672f69a2309ca8a329a3f9576438afe4538b013de4591a6dfcd4d090281845820d163c8c4f0be7c22cd3a1152abb013c855ea614b92201497a568c5d93ceeb41e58406a23ab9267867fbf021c1cb2232bc83d2cdd663d651d22d59b6cddbca5cb106d4db99da50672f69a2309ca8a329a3f9576438afe4538b013de4591a6dfcd4d095820a7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f441a0f6"); + + val txid = output.txId + assertEquals(Numeric.toHexString(txid.toByteArray()), "0x0203ce2c91f59f169a26e9ef91254639d2b7911afac9c7c0ae64539f88ba46a5"); + } + @Test fun testSignTransferToken1() { val toToken = Cardano.TokenAmount.newBuilder() @@ -101,7 +154,7 @@ class TestCardanoSigning { .setTransferMessage(message) .setTtl(53333333) - val privKey = (Numeric.hexStringToByteArray("089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e")) + val privKey = Numeric.hexStringToByteArray("089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e") input.addPrivateKey(ByteString.copyFrom(privKey)) val outpoint1 = Cardano.OutPoint.newBuilder() @@ -114,7 +167,7 @@ class TestCardanoSigning { .setAmount(8_051_373) val token3 = Cardano.TokenAmount.newBuilder() .setPolicyId("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77") - .setAssetName("CUBY") + .setAssetNameHex("43554259") .setAmount(ByteString.copyFrom(Numeric.hexStringToByteArray("2dc6c0"))) // 3000000 .build() utxo1.addTokenAmount(token3) @@ -130,7 +183,7 @@ class TestCardanoSigning { .setAmount(2_000_000) val token1 = Cardano.TokenAmount.newBuilder() .setPolicyId("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77") - .setAssetName("SUNDAE") + .setAssetNameHex("53554e444145") .setAmount(ByteString.copyFrom(Numeric.hexStringToByteArray("04d3e8d9"))) // 80996569 .build() utxo2.addTokenAmount(token1) @@ -142,14 +195,204 @@ class TestCardanoSigning { utxo2.addTokenAmount(token2) input.addUtxos(utxo2.build()) - val output = AnySigner.sign(input.build(), CARDANO, SigningOutput.parser()) + val output = AnySigner.sign(input.build(), CARDANO, Cardano.SigningOutput.parser()) + assertEquals(output.error, SigningError.OK) + + val encoded = output.encoded + assertEquals(Numeric.toHexString(encoded.toByteArray()), + "0x83a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76702018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5821a00160a5ba1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a01312d00825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468821a0080aac9a1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a244435542591a004c4b404653554e4441451a03a2bbd9021a0002aa09031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840d90dcfbd190cbe59c42094e59eeb49b3de9d80a85b786cc311f932c5c9302d1c8c6c577b22aa70ff7955c139c700ea918f8cb425c3ba43a27980e1d238e4e908f6"); + + val txid = output.txId + assertEquals(Numeric.toHexString(txid.toByteArray()), "0x201c537693b005b64a0f0528e366ec67a84be0119ed4363b547f141f2a7770c2"); + } + + @Test + fun testSignStakingRegisterAndDelegate() { + val ownAddress = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + val stakingAddress = getStakingAddress(ownAddress) + val poolIdNufi = Numeric.hexStringToByteArray("7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6") + + val message = Cardano.Transfer.newBuilder() + .setToAddress(ownAddress) + .setChangeAddress(ownAddress) + .setAmount(4_000_000) // not relevant if we use MaxAmount + .setUseMaxAmount(true) + .build() + // Register staking key, 2 ADA desposit + val register = Cardano.RegisterStakingKey.newBuilder() + .setStakingAddress(stakingAddress) + .setDepositAmount(2_000_000) + // Delegate + val delegate = Cardano.Delegate.newBuilder() + .setStakingAddress(stakingAddress) + .setPoolId(ByteString.copyFrom(poolIdNufi)) + .setDepositAmount(0) + val input = Cardano.SigningInput.newBuilder() + .setTransferMessage(message) + .setRegisterStakingKey(register) + .setDelegate(delegate) + .setTtl(69885081) + + val privKey = Numeric.hexStringToByteArray("089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e") + input.addPrivateKey(ByteString.copyFrom(privKey)) + + val outpoint1 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e"))) + .setOutputIndex(0) + .build() + val utxo1 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint1) + .setAddress(ownAddress) + .setAmount(4_000_000) + .build() + input.addUtxos(utxo1) + + val outpoint2 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e"))) + .setOutputIndex(1) + .build() + val utxo2 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint2) + .setAddress(ownAddress) + .setAmount(26651312) + .build() + input.addUtxos(utxo2) + + val output = AnySigner.sign(input.build(), CARDANO, Cardano.SigningOutput.parser()) + assertEquals(output.error, SigningError.OK) + + val encoded = output.encoded + assertEquals(Numeric.toHexString(encoded.toByteArray()), + "0x83a500828258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e008258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e01018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a01b27ef5021a0002b03b031a042a5c99048282008200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b83028200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b581c7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6a100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840677c901704be027d9a1734e8aa06f0700009476fa252baaae0de280331746a320a61456d842d948ea5c0e204fc36f3bd04c88ca7ee3d657d5a38014243c37c07825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e0693258401fa21bdc62b85ca217bf08cbacdeba2fadaf33dc09ee3af9cc25b40f24822a1a42cfbc03585cc31a370ef75aaec4d25db6edcf329e40a4e725ec8718c94f220af6"); + + val txid = output.txId + assertEquals(Numeric.toHexString(txid.toByteArray()), "0x96a781fd6481b6a7fd3926da110265e8c44b53947b81daa84da5b148825d02aa"); + } + + @Test + fun testSignStakingWithdraw() { + val ownAddress = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + val stakingAddress = getStakingAddress(ownAddress) + + val message = Cardano.Transfer.newBuilder() + .setToAddress(ownAddress) + .setChangeAddress(ownAddress) + .setAmount(6_000_000) // not relevant if we use MaxAmount + .setUseMaxAmount(true) + .build() + // Withdraw available amount + val withdraw = Cardano.Withdraw.newBuilder() + .setStakingAddress(stakingAddress) + .setWithdrawAmount(3468) + val input = Cardano.SigningInput.newBuilder() + .setTransferMessage(message) + .setWithdraw(withdraw) + .setTtl(71678326) + + val privKey = Numeric.hexStringToByteArray("089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e") + input.addPrivateKey(ByteString.copyFrom(privKey)) + + val outpoint1 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("7dfd2c579794314b1f84efc9db932a098e440ccefb874945591f1d4e85a9152a"))) + .setOutputIndex(0) + .build() + val utxo1 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint1) + .setAddress(ownAddress) + .setAmount(6_305_913) + .build() + input.addUtxos(utxo1) + + val output = AnySigner.sign(input.build(), CARDANO, Cardano.SigningOutput.parser()) + assertEquals(output.error, SigningError.OK) + + val encoded = output.encoded + assertEquals(Numeric.toHexString(encoded.toByteArray()), + "0x83a500818258207dfd2c579794314b1f84efc9db932a098e440ccefb874945591f1d4e85a9152a00018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a005da6ff021a00029f06031a0445b97605a1581de1df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b190d8ca100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058401ebaca2876fd17122404912a2558a98109cdf0f990a938d2917fa2c3b8c4e55e18a2cbabfa82fff03fa0d7ab8b88ca01ed18e42af3bfc4cda7f423a3aa30c00b825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e069325840777f04fa8f083fe562aecf78898aaaaac36e2cc6ca962f6ffb01e84a421cae1860496db79b2c5fb2879524c3d5121060b9ea1e693336230c6e5338e14c4c3303f6"); + + val txid = output.txId + assertEquals(Numeric.toHexString(txid.toByteArray()), "0x6dcf3956232953fc25b8355fb1ded1e912b5802090fd21434d789087d6329683"); + } + + // Successfully broadcasted: + // https://cardanoscan.io/transaction/87ca43a36b09c0b140f0ef2b71fbdcfcf1fdc88f7aa378b861e8eed3e8974628 + @Test + fun testSignNftTransfer() { + val fromAddress = "addr1qy5eme9r6frr0m6q2qpncg282jtrhq5lg09uxy2j0545hj8rv7v2ntdxuv6p4s3eq4lqzg39lewgvt6fk5kmpa0zppesufzjud" + val toAddress = "addr1qy9wjfn6nd8kak6dd8z53u7t5wt9f4lx0umll40px5hnq05avwcsq5r3ytdp36wttzv4558jaq8lvhgqhe3y8nuf5xrquju7z4" + val coinsPerUtxoByte = "4310" + + val tokenAmount = Cardano.TokenAmount.newBuilder() + .setPolicyId("219820e6cb04316f41a337fea356480f412e7acc147d28f175f21b5e") + .setAssetName("coolcatssociety4567") + .setAmount(ByteString.copyFrom(Numeric.hexStringToByteArray("01"))) // 1 + .build() + + // UTXOs + + val outpoint1 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("aba499ec2f23529e70bb256ceaffcc6274a882cf02f29e5670c75ee980d7c2b8"))) + .setOutputIndex(0) + .build() + val utxo1 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint1) + .setAddress(fromAddress) + .setAmount(1_202_490) + .addTokenAmount(tokenAmount) + + val outpoint2 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("ee414d635b3bc67831907354d274a31174664777c57c21ae923b9459e5644840"))) + .setOutputIndex(0) + .build() + val utxo2 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint2) + .setAddress(fromAddress) + .setAmount(1_000_000) + + val outpoint3 = Cardano.OutPoint.newBuilder() + .setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("6a7221dcc28353ed69b733391ffeb984a34c1e72293af111d59f9ddfa8639167"))) + .setOutputIndex(0) + .build() + val utxo3 = Cardano.TxInput.newBuilder() + .setOutPoint(outpoint3) + .setAddress(fromAddress) + .setAmount(2_000_000) + + // Transfer TX + + val privKey = Numeric.hexStringToByteArray("d09831a668db6b36ffb747600cb1cd3e3d34f36e1e6feefc11b5f988719b7557a7029ab80d3e6fe4180ad07a59ddf742ea9730f3c4145df6365fa4ae2ee49c3392e19444caf461567727b7fefec40a3763bdb6ce5e0e8c05f5e340355a8fef4528dfe7502cfbda49e38f5a0021962d52dc3dee82834a23abb6750981799b75577d1ed9af9853707f0ef74264274e71b2f12e86e3c91314b6efa75ef750d9711b84cedd742ab873ef2f9566ad20b3fc702232c6d2f5d83ff425019234037d1e58") + val toTokenBundle = Cardano.TokenBundle.newBuilder() + .addToken(tokenAmount) + .build() + + // Check min ADA amount, set it + val minAmount = outputMinAdaAmount(toAddress, toTokenBundle.toByteArray(), coinsPerUtxoByte).toLong() + assertEquals(minAmount, 1_202_490) + + val transfer = Cardano.Transfer.newBuilder() + .setToAddress(toAddress) + .setChangeAddress(fromAddress) + .setAmount(minAmount) + .setTokenAmount(toTokenBundle) + .build() + val signInput = Cardano.SigningInput.newBuilder() + .setTransferMessage(transfer) + .setTtl(89130965) + .addUtxos(utxo1) + .addUtxos(utxo2) + .addUtxos(utxo3) + .addPrivateKey(ByteString.copyFrom(privKey)) + + // Sign and check + + val output = AnySigner.sign(signInput.build(), CARDANO, Cardano.SigningOutput.parser()) assertEquals(output.error, SigningError.OK) val encoded = output.encoded assertEquals(Numeric.toHexString(encoded.toByteArray()), - "0x83a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76702018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5821a00160a5ba1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a01312d00825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468821a0080a574a2581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a144435542591a004c4b40581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a03a2bbd9021a0002af5e031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290584000feb412442f8851faa59742eb2c37f3994b0d143a424367143490cf828246991e504fa8eac61c403bfa7634bd1f0adc44f3f54f6a474856701e2cbb15fb5b04f6"); + "0x83a400838258206a7221dcc28353ed69b733391ffeb984a34c1e72293af111d59f9ddfa863916700825820aba499ec2f23529e70bb256ceaffcc6274a882cf02f29e5670c75ee980d7c2b800825820ee414d635b3bc67831907354d274a31174664777c57c21ae923b9459e5644840000182825839010ae9267a9b4f6edb4d69c548f3cba39654d7e67f37ffd5e1352f303e9d63b100507122da18e9cb58995a50f2e80ff65d00be6243cf89a186821a0012593aa1581c219820e6cb04316f41a337fea356480f412e7acc147d28f175f21b5ea153636f6f6c63617473736f6369657479343536370182583901299de4a3d24637ef4050033c214754963b829f43cbc311527d2b4bc8e36798a9ada6e3341ac239057e012225fe5c862f49b52db0f5e208731a002b1525021a0002b19b031a055007d5a1008182582088bd26e8656fa7dead846c3373588f0192da5bfb90bf5d3fb877decfb3b3fd085840da8656aca0dacc57d4c2d957fc7dff03908f6dcf60c48f1e40b3006e2fd0cfacfa4c24fa02e35a310572526586d4ce0d30bf660ba274c8efd507848cbe177d09f6"); val txid = output.txId - assertEquals(Numeric.toHexString(txid.toByteArray()), "0xdacb3a0c5b3b7fa36b49f25a0a59b941ab8a21f0db5770e9e6982ff120122649"); + assertEquals(Numeric.toHexString(txid.toByteArray()), "0x87ca43a36b09c0b140f0ef2b71fbdcfcf1fdc88f7aa378b861e8eed3e8974628"); } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/confluxespace/TestConfluxeSpaceAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/confluxespace/TestConfluxeSpaceAddress.kt new file mode 100644 index 00000000000..25d5ae1c81e --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/confluxespace/TestConfluxeSpaceAddress.kt @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.confluxespace + +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestConfluxeSpaceAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(false) + val address = AnyAddress(pubkey, CoinType.CONFLUXESPACE) + val expected = AnyAddress("0xe32DC46bfBF78D1eada7b0a68C96903e01418D64", CoinType.CONFLUXESPACE) + + assertEquals(address.description(), expected.description()) + } +} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt index a81bf62e008..4de24f9b60f 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt @@ -19,6 +19,104 @@ class TestCosmosTransactions { System.loadLibrary("TrustWalletCore") } + @Test + fun testAuthStakingTransaction() { + val key = + PrivateKey("c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc".toHexByteArray()) + + val stakeAuth = Cosmos.Message.StakeAuthorization.newBuilder().apply { + allowList = Cosmos.Message.StakeAuthorization.Validators.newBuilder().apply { + addAddress("cosmosvaloper1gjtvly9lel6zskvwtvlg5vhwpu9c9waw7sxzwx") + }.build() + authorizationType = Cosmos.Message.AuthorizationType.DELEGATE + }.build() + + val authStakingMsg = Cosmos.Message.AuthGrant.newBuilder().apply { + grantee = "cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf" + granter = "cosmos13k0q0l7lg2kr32kvt7ly236ppldy8v9dzwh3gd" + grantStake = stakeAuth + expiration = 1692309600 + }.build() + + val message = Cosmos.Message.newBuilder().apply { + authGrant = authStakingMsg + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "2418" + denom = "uatom" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 96681 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + accountNumber = 1290826 + chainId = "cosmoshub-4" + memo = "" + sequence = 5 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, COSMOS, SigningOutput.parser()) + + assertEquals( + output.serialized, + "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjogARI2CjRjb3Ntb3N2YWxvcGVyMWdqdHZseTlsZWw2enNrdnd0dmxnNXZod3B1OWM5d2F3N3N4end4EgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQEAN1nIfDawlHnep2bNEm14w+g7tYybJJT3htcGVS6s9D7va3ed1OUEIk9LZoc3G//VenJ+KLw26SRVBaRukgVI=\"}" + ) + assertEquals(output.errorMessage, "") + } + + @Test + fun testRemoveAuthStakingTransaction() { + val key = + PrivateKey("c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc".toHexByteArray()) + + val removeAuthStakingMsg = Cosmos.Message.AuthRevoke.newBuilder().apply { + grantee = "cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf" + granter = "cosmos13k0q0l7lg2kr32kvt7ly236ppldy8v9dzwh3gd" + msgTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate" + }.build() + + val message = Cosmos.Message.newBuilder().apply { + authRevoke = removeAuthStakingMsg + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "2194" + denom = "uatom" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 87735 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + accountNumber = 1290826 + chainId = "cosmoshub-4" + memo = "" + sequence = 4 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, COSMOS, SigningOutput.parser()) + + assertEquals( + output.serialized, + "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CqoBCqcBCh8vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnUmV2b2tlEoMBCi1jb3Ntb3MxM2swcTBsN2xnMmtyMzJrdnQ3bHkyMzZwcGxkeTh2OWR6d2gzZ2QSLWNvc21vczFmczdsdTI4aHg1bTlha203cnAwYzI0MjJjbjhyMmY3Z3VydWpoZhojL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuTXNnRGVsZWdhdGUSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAQSEwoNCgV1YXRvbRIEMjE5NBC3rQUaQI7K+W7MMBoD6FbFZxRBqs9VTjErztjWTy57+fvrLaTCIZ+eBs7CuaKqfUZdSN8otjubSHVTQID3k9DpPAX0yDo=\"}" + ) + assertEquals(output.errorMessage, "") + } + @Test fun testSigningTransaction() { val key = @@ -64,8 +162,11 @@ class TestCosmosTransactions { val output = AnySigner.sign(signingInput, COSMOS, SigningOutput.parser()) - assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\"}") - assertEquals(output.error, "") + assertEquals( + output.serialized, + "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\"}" + ) + assertEquals(output.errorMessage, "") } @Test @@ -94,8 +195,12 @@ class TestCosmosTransactions { }] } """ - val key = "c9b0a273831931aa4a5f8d1a570d5021dda91d3319bd3819becdaabfb7b44e3b".toHexByteArray() + val key = + "c9b0a273831931aa4a5f8d1a570d5021dda91d3319bd3819becdaabfb7b44e3b".toHexByteArray() val result = AnySigner.signJSON(json, key, COSMOS.value()) - assertEquals(result, """{"mode":"block","tx":{"fee":{"amount":[{"amount":"5000","denom":"uatom"}],"gas":"200000"},"memo":"Testing","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"995000","denom":"uatom"}],"from_address":"cosmos1ufwv9ymhqaal6xz47n0jhzm2wf4empfqvjy575","to_address":"cosmos135qla4294zxarqhhgxsx0sw56yssa3z0f78pm0"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A6EsukEXB53GhohQVeDpxtkeH8KQIayd/Co/ApYRYkTm"},"signature":"ULEpUqNzoAnYEx2x22F3ANAiPXquAU9+mqLWoAA/ZOUGTMsdb6vryzsW6AKX2Kqj1pGNdrTcQ58Z09JPyjpgEA=="}]}}""") + assertEquals( + result, + """{"mode":"block","tx":{"fee":{"amount":[{"amount":"5000","denom":"uatom"}],"gas":"200000"},"memo":"Testing","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"995000","denom":"uatom"}],"from_address":"cosmos1ufwv9ymhqaal6xz47n0jhzm2wf4empfqvjy575","to_address":"cosmos135qla4294zxarqhhgxsx0sw56yssa3z0f78pm0"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A6EsukEXB53GhohQVeDpxtkeH8KQIayd/Co/ApYRYkTm"},"signature":"ULEpUqNzoAnYEx2x22F3ANAiPXquAU9+mqLWoAA/ZOUGTMsdb6vryzsW6AKX2Kqj1pGNdrTcQ58Z09JPyjpgEA=="}]}}""" + ) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgAddress.kt index 52e06a041d8..214c391624d 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgAddress.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.cryptoorg diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt index 98c568d9bef..74090e73a29 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.cryptoorg @@ -73,6 +71,6 @@ class TestCryptoorgSigner { // https://crypto.org/explorer/tx/BCB213B0A121F0CF11BECCF52475F1C8328D6070F3CFDA9E14C42E6DB30E847E assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpABCo0BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm0KKmNybzFjdHd0Y3dwZ2tza3k5ODhkaHRoNmpzbHh2ZXVtZ3UwZDQ1emdmMBIqY3JvMXhwYWh5NmM3d2xkeGFjdjZsZDk5aDQzNW1odmZuc3VwMjR2Y3VzGhMKB2Jhc2Vjcm8SCDUwMDAwMDAwEmkKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQOIMbBhYj5+i+WdiIxxCEpFyNCJMHy/XsVdxiwde9Vr4xIECgIIARgCEhUKDwoHYmFzZWNybxIENTAwMBDAmgwaQAcxK9xk6r69gmz+1UWaCnYxNuXPXZdp59YcqKPJE5d6fp+IICTBOwd2rs8MiApcf8kNSrbZ6oECxcGQAdxF0SI=\"}") - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/dydx/TestDydxAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/dydx/TestDydxAddress.kt new file mode 100644 index 00000000000..408f8fb4ae3 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/dydx/TestDydxAddress.kt @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.dydx + +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestDydxAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433".toHexByteArray()) + val pubKey = key.getPublicKeySecp256k1(true) + val address = AnyAddress(pubKey, CoinType.DYDX) + val expected = AnyAddress("dydx1mry47pkga5tdswtluy0m8teslpalkdq0hc72uz", CoinType.DYDX) + + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondAddress.kt deleted file mode 100644 index e8304e3deeb..00000000000 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondAddress.kt +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -package com.trustwallet.core.app.blockchains.elrond - -import com.trustwallet.core.app.utils.toHex -import com.trustwallet.core.app.utils.toHexByteArray -import org.junit.Assert.assertEquals -import org.junit.Test -import wallet.core.jni.* - -class TestElrondAddress { - - init { - System.loadLibrary("TrustWalletCore") - } - - private var aliceBech32 = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" - private var alicePubKeyHex = "0x0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1" - private var aliceSeedHex = "0x413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" - - @Test - fun testAddressFromPrivateKey() { - val key = PrivateKey(aliceSeedHex.toHexByteArray()) - val pubKey = key.publicKeyEd25519 - val address = AnyAddress(pubKey, CoinType.ELROND) - - assertEquals(alicePubKeyHex, pubKey.data().toHex()) - assertEquals(aliceBech32, address.description()) - } - - @Test - fun testAddressFromPublicKey() { - val pubKey = PublicKey(alicePubKeyHex.toHexByteArray(), PublicKeyType.ED25519) - val address = AnyAddress(pubKey, CoinType.ELROND) - - assertEquals(aliceBech32, address.description()) - } - - @Test - fun testAddressFromString() { - val address = AnyAddress(aliceBech32, CoinType.ELROND) - - assertEquals(aliceBech32, address.description()) - } -} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt deleted file mode 100644 index a99a949bdb4..00000000000 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/elrond/TestElrondSigner.kt +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -package com.trustwallet.core.app.blockchains.elrond - -import com.google.protobuf.ByteString -import com.trustwallet.core.app.utils.toHexByteArray -import org.junit.Assert.assertEquals -import org.junit.Test -import wallet.core.java.AnySigner -import wallet.core.jni.CoinType -import wallet.core.jni.PrivateKey -import wallet.core.jni.proto.Elrond - -class TestElrondSigner { - - init { - System.loadLibrary("TrustWalletCore") - } - - private var aliceBech32 = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" - private var aliceSeedHex = "0x413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" - private var bobBech32 = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx" - - @Test - fun signGenericAction() { - val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) - - val accounts = Elrond.Accounts.newBuilder() - .setSenderNonce(7) - .setSender(aliceBech32) - .setReceiver(bobBech32) - .build() - - val genericAction = Elrond.GenericAction.newBuilder() - .setAccounts(accounts) - .setValue("0") - .setData("foo") - .setVersion(1) - .build() - - val signingInput = Elrond.SigningInput.newBuilder() - .setGenericAction(genericAction) - .setGasPrice(1000000000) - .setGasLimit(50000) - .setChainId("1") - .setPrivateKey(privateKey) - .build() - - val output = AnySigner.sign(signingInput, CoinType.ELROND, Elrond.SigningOutput.parser()) - val expectedSignature = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700" - - assertEquals(expectedSignature, output.signature) - assertEquals("""{"nonce":7,"value":"0","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"$expectedSignature"}""", output.encoded) - } - - @Test - fun signEGLDTransfer() { - val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) - - val accounts = Elrond.Accounts.newBuilder() - .setSenderNonce(7) - .setSender(aliceBech32) - .setReceiver(bobBech32) - .build() - - val transfer = Elrond.EGLDTransfer.newBuilder() - .setAccounts(accounts) - .setAmount("1000000000000000000") - .build() - - val signingInput = Elrond.SigningInput.newBuilder() - .setEgldTransfer(transfer) - .setChainId("1") - .setPrivateKey(privateKey) - .build() - - val output = AnySigner.sign(signingInput, CoinType.ELROND, Elrond.SigningOutput.parser()) - val expectedSignature = "7e1c4c63b88ea72dcf7855a54463b1a424eb357ac3feb4345221e512ce07c7a50afb6d7aec6f480b554e32cf2037082f3bc17263d1394af1f3ef240be53c930b" - - assertEquals(expectedSignature, output.signature) - assertEquals("""{"nonce":7,"value":"1000000000000000000","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1,"signature":"$expectedSignature"}""", output.encoded) - } - - @Test - fun signESDTTransfer() { - val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) - - val accounts = Elrond.Accounts.newBuilder() - .setSenderNonce(7) - .setSender(aliceBech32) - .setReceiver(bobBech32) - .build() - - val transfer = Elrond.ESDTTransfer.newBuilder() - .setAccounts(accounts) - .setTokenIdentifier("MYTOKEN-1234") - .setAmount("10000000000000") - .build() - - val signingInput = Elrond.SigningInput.newBuilder() - .setEsdtTransfer(transfer) - .setChainId("1") - .setPrivateKey(privateKey) - .build() - - val output = AnySigner.sign(signingInput, CoinType.ELROND, Elrond.SigningOutput.parser()) - val expectedSignature = "9add6d9ac3f1a1fddb07b934e8a73cad3b8c232bdf29d723c1b38ad619905f03e864299d06eb3fe3bbb48a9f1d9b7f14e21dc5eaffe0c87f5718ad0c4198bb0c" - val expectedData = "RVNEVFRyYW5zZmVyQDRkNTk1NDRmNGI0NTRlMmQzMTMyMzMzNEAwOTE4NGU3MmEwMDA=" - - assertEquals(expectedSignature, output.signature) - assertEquals("""{"nonce":7,"value":"0","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":425000,"data":"$expectedData","chainID":"1","version":1,"signature":"$expectedSignature"}""", output.encoded) - } - - @Test - fun signESDTNFTTransfer() { - val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) - - val accounts = Elrond.Accounts.newBuilder() - .setSenderNonce(7) - .setSender(aliceBech32) - .setReceiver(bobBech32) - .build() - - val transfer = Elrond.ESDTNFTTransfer.newBuilder() - .setAccounts(accounts) - .setTokenCollection("LKMEX-aab910") - .setTokenNonce(4) - .setAmount("184300000000000000") - .build() - - val signingInput = Elrond.SigningInput.newBuilder() - .setEsdtnftTransfer(transfer) - .setChainId("1") - .setPrivateKey(privateKey) - .build() - - val output = AnySigner.sign(signingInput, CoinType.ELROND, Elrond.SigningOutput.parser()) - val expectedSignature = "cc935685d5b31525e059a16a832cba98dee751983a5a93de4198f6553a2c55f5f1e0b4300fe9077376fa754546da0b0f6697e66462101a209aafd0fc775ab60a" - val expectedData = "RVNEVE5GVFRyYW5zZmVyQDRjNGI0ZDQ1NTgyZDYxNjE2MjM5MzEzMEAwNEAwMjhlYzNkZmEwMWFjMDAwQDgwNDlkNjM5ZTVhNjk4MGQxY2QyMzkyYWJjY2U0MTAyOWNkYTc0YTE1NjM1MjNhMjAyZjA5NjQxY2MyNjE4Zjg=" - - assertEquals(expectedSignature, output.signature) - assertEquals("""{"nonce":7,"value":"0","receiver":"$aliceBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":937500,"data":"$expectedData","chainID":"1","version":1,"signature":"$expectedSignature"}""", output.encoded) - } -} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/eos/TestEOSSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/eos/TestEOSSigning.kt index 27fb4ce0b11..d5d073b1840 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/eos/TestEOSSigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/eos/TestEOSSigning.kt @@ -82,7 +82,7 @@ class TestEOSSigning { val signatureValue: String = signatures.get(0) as String; assertNotNull("Error parsing JSON result", signatureValue) assertEquals( - "SIG_K1_KfCdjsrTnx5cBpbA5cUdHZAsRYsnC9uKzuS1shFeqfMCfdZwX4PBm9pfHwGRT6ffz3eavhtkyNci5GoFozQAx8P8PBnDmj", + "SIG_K1_K9RdLC7DEDWjTfR64GU8BtDHcAjzR1ntcT651JMcfHNTpdsvDrUwfyzF1FkvL9fxEi2UCtGJZ9zYoNbJoMF1fbU64cRiJ7", signatureValue ) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt new file mode 100644 index 00000000000..235cd442b73 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt @@ -0,0 +1,229 @@ +package com.trustwallet.core.app.blockchains.ethereum + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType +import wallet.core.jni.CoinType.ETHEREUM +import wallet.core.jni.proto.Ethereum +import wallet.core.jni.EthereumAbi +import wallet.core.jni.EthereumAbiFunction +import wallet.core.jni.proto.Ethereum.SigningOutput +import wallet.core.jni.proto.Ethereum.TransactionMode +import wallet.core.jni.PrivateKey +import wallet.core.jni.PublicKey +import wallet.core.jni.PublicKeyType +import com.trustwallet.core.app.utils.Numeric +import org.junit.Assert.assertArrayEquals +import wallet.core.jni.proto.Barz +import wallet.core.jni.Barz as WCBarz + +class TestBarz { + + init { + System.loadLibrary("TrustWalletCore") + } + + // https://testnet.bscscan.com/tx/0x6c6e1fe81c722c0abce1856b9b4e078ab2cad06d51f2d1b04945e5ba2286d1b4 + @Test + fun testInitCode() { + val factoryAddress = "0x3fC708630d85A3B5ec217E53100eC2b735d4f800" + val publicKeyData = Numeric.hexStringToByteArray("04e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b02") + val publicKey = PublicKey(publicKeyData, PublicKeyType.NIST256P1EXTENDED) + val verificationFacet = "0x6BF22ff186CC97D88ECfbA47d1473a234CEBEFDf" + val result = WCBarz.getInitCode(factoryAddress, publicKey, verificationFacet, 0) + assertEquals(Numeric.toHexString(result), "0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000") + } + + @Test + fun testInitCodeNonZeroSalt() { + val factoryAddress = "0x3fC708630d85A3B5ec217E53100eC2b735d4f800" + val publicKeyData = Numeric.hexStringToByteArray("04e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b02") + val publicKey = PublicKey(publicKeyData, PublicKeyType.NIST256P1EXTENDED) + val verificationFacet = "0x6BF22ff186CC97D88ECfbA47d1473a234CEBEFDf" + val salt = 1 + val result = WCBarz.getInitCode(factoryAddress, publicKey, verificationFacet, salt) + assertEquals(Numeric.toHexString(result), "0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000") + } + + @Test + fun testCounterfactualAddress() { + val input = Barz.ContractAddressInput.newBuilder() + input.apply { + factory = "0x2c97f4a366Dd5D91178ec9E36c5C1fcA393A538C" + accountFacet = "0x3322C04EAe11B9b14c6c289f2668b6f07071b591" + verificationFacet = "0x90A6fE0A938B0d4188e9013C99A0d7D9ca6bFB63" + entryPoint = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" + facetRegistry = "0xFd1A8170c12747060324D9079a386BD4290e6f93" + defaultFallback = "0x22eB0720d9Fc4bC90BB812B309e939880B71c20d" + bytecode = "0x608060405260405162003cc638038062003cc68339818101604052810190620000299190620019ad565b60008673ffffffffffffffffffffffffffffffffffffffff16633253960f6040518163ffffffff1660e01b81526004016020604051808303816000875af115801562000079573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200009f919062001aec565b9050600060e01b817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191603620000fe576040517f5a5b4d3900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600267ffffffffffffffff8111156200011e576200011d6200196b565b5b6040519080825280602002602001820160405280156200015b57816020015b62000147620018ff565b8152602001906001900390816200013d5790505b5090506000600167ffffffffffffffff8111156200017e576200017d6200196b565b5b604051908082528060200260200182016040528015620001ad5781602001602082028036833780820191505090505b5090506000600167ffffffffffffffff811115620001d057620001cf6200196b565b5b604051908082528060200260200182016040528015620001ff5781602001602082028036833780820191505090505b509050631f931c1c60e01b8260008151811062000221576200022062001b21565b5b60200260200101907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152505060405180606001604052808d73ffffffffffffffffffffffffffffffffffffffff16815260200160006002811115620002ab57620002aa62001b37565b5b81526020018381525083600081518110620002cb57620002ca62001b21565b5b60200260200101819052508381600081518110620002ee57620002ed62001b21565b5b60200260200101907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152505060405180606001604052808b73ffffffffffffffffffffffffffffffffffffffff1681526020016000600281111562000378576200037762001b37565b5b8152602001828152508360018151811062000398576200039762001b21565b5b6020026020010181905250620003b9846200047e60201b620001671760201c565b6200046c838c8b8e8e8d8d8d8d604051602401620003de979695949392919062001b8c565b6040516020818303038152906040527f95a21aec000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050620004b060201b620001911760201c565b5050505050505050505050506200211f565b806200048f6200073460201b60201c565b60010160016101000a81548163ffffffff021916908360e01c021790555050565b60005b8351811015620006df576000848281518110620004d557620004d462001b21565b5b602002602001015160200151905060006002811115620004fa57620004f962001b37565b5b81600281111562000510576200050f62001b37565b5b0362000570576200056a85838151811062000530576200052f62001b21565b5b60200260200101516000015186848151811062000552576200055162001b21565b5b6020026020010151604001516200073960201b60201c565b620006c8565b6001600281111562000587576200058662001b37565b5b8160028111156200059d576200059c62001b37565b5b03620005fd57620005f7858381518110620005bd57620005bc62001b21565b5b602002602001015160000151868481518110620005df57620005de62001b21565b5b602002602001015160400151620009db60201b60201c565b620006c7565b60028081111562000613576200061262001b37565b5b81600281111562000629576200062862001b37565b5b0362000689576200068385838151811062000649576200064862001b21565b5b6020026020010151600001518684815181106200066b576200066a62001b21565b5b60200260200101516040015162000c8f60201b60201c565b620006c6565b6040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620006bd9062001be7565b60405180910390fd5b5b5b508080620006d69062001c61565b915050620004b3565b507f8faa70878671ccd212d20771b795c50af8fd3ff6cf27f4bde57e5d4de0aeb673838383604051620007159392919062001c82565b60405180910390a16200072f828262000e3760201b60201c565b505050565b600090565b600081511162000780576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620007779062001d97565b60405180910390fd5b60006200079262000f6b60201b60201c565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160362000806576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620007fd9062001dfb565b60405180910390fd5b60008160010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054905090506000816bffffffffffffffffffffffff16036200087c576200087b828562000f9860201b60201c565b5b60005b8351811015620009d4576000848281518110620008a157620008a062001b21565b5b602002602001015190506000846000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161462000998576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200098f9062001e5f565b60405180910390fd5b620009ac8583868a6200107c60201b60201c565b8380620009b99062001ec3565b94505050508080620009cb9062001c61565b9150506200087f565b5050505050565b600081511162000a22576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000a199062001d97565b60405180910390fd5b600062000a3462000f6b60201b60201c565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160362000aa8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000a9f9062001dfb565b60405180910390fd5b60008160010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054905090506000816bffffffffffffffffffffffff160362000b1e5762000b1d828562000f9860201b60201c565b5b60005b835181101562000c8857600084828151811062000b435762000b4262001b21565b5b602002602001015190506000846000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508673ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160362000c39576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000c309062001eef565b60405180910390fd5b62000c4c8582846200122960201b60201c565b62000c608583868a6200107c60201b60201c565b838062000c6d9062001ec3565b9450505050808062000c7f9062001c61565b91505062000b21565b5050505050565b600081511162000cd6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000ccd9062001d97565b60405180910390fd5b600062000ce862000f6b60201b60201c565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161462000d5c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000d539062001f53565b60405180910390fd5b60005b825181101562000e3157600083828151811062000d815762000d8062001b21565b5b602002602001015190506000836000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905062000e198482846200122960201b60201c565b5050808062000e289062001c61565b91505062000d5f565b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16031562000f675762000e988260405180606001604052806028815260200162003c7a60289139620018aa60201b60201c565b6000808373ffffffffffffffffffffffffffffffffffffffff168360405162000ec2919062001fb7565b600060405180830381855af49150503d806000811462000eff576040519150601f19603f3d011682016040523d82523d6000602084013e62000f04565b606091505b50915091508162000f645760008151111562000f235780518082602001fd5b83836040517f192105d700000000000000000000000000000000000000000000000000000000815260040162000f5b92919062001fd7565b60405180910390fd5b50505b5050565b6000807f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f90508091505090565b62000fc38160405180606001604052806024815260200162003ca260249139620018aa60201b60201c565b81600201805490508260010160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001018190555081600201819080600181540180825580915050600190039060005260206000200160009091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b81846000016000857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508360010160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018390806001815401808255809150506001900390600052602060002090600891828204019190066004029091909190916101000a81548163ffffffff021916908360e01c021790555080846000016000857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036200129b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620012929062002003565b60405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036200130c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620013039062002067565b60405180910390fd5b6000836000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160149054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff169050600060018560010160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000180549050620013e59190620020cb565b9050808214620015805760008560010160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000182815481106200144a576200144962001b21565b5b90600052602060002090600891828204019190066004029054906101000a900460e01b9050808660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018481548110620014c957620014c862001b21565b5b90600052602060002090600891828204019190066004026101000a81548163ffffffff021916908360e01c021790555082866000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550505b8460010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600001805480620015d757620015d6620020ec565b5b60019003818190600052602060002090600891828204019190066004026101000a81549063ffffffff02191690559055846000016000847bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001908152602001600020600080820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556000820160146101000a8154906bffffffffffffffffffffffff0219169055505060008103620018a357600060018660020180549050620016c49190620020cb565b905060008660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001015490508181146200180c57600087600201838154811062001732576200173162001b21565b5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508088600201838154811062001779576200177862001b21565b5b9060005260206000200160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550818860010160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010181905550505b86600201805480620018235762001822620020ec565b5b6001900381819060005260206000200160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905590558660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001016000905550505b5050505050565b6000823b9050600081118290620018f9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620018f0919062002102565b60405180910390fd5b50505050565b6040518060600160405280600073ffffffffffffffffffffffffffffffffffffffff168152602001600060028111156200193e576200193d62001b37565b5b8152602001606081525090565b60008151905060018060a01b03811681146200196657600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b83811015620019a157808201518184015260208101905062001984565b50600083830152505050565b600080600080600080600080610100898b031215620019cb57600080fd5b620019d6896200194b565b9750620019e660208a016200194b565b9650620019f660408a016200194b565b955062001a0660608a016200194b565b945062001a1660808a016200194b565b935062001a2660a08a016200194b565b925062001a3660c08a016200194b565b915060e089015160018060401b038082111562001a5257600080fd5b818b0191508b601f83011262001a6757600080fd5b81518181111562001a7d5762001a7c6200196b565b5b601f1960405181603f83601f860116011681019150808210848311171562001aaa5762001aa96200196b565b5b816040528281528e602084870101111562001ac457600080fd5b62001ad783602083016020880162001981565b80955050505050509295985092959890939650565b60006020828403121562001aff57600080fd5b815163ffffffff60e01b8116811462001b1757600080fd5b8091505092915050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052602160045260246000fd5b60018060a01b03811682525050565b6000815180845262001b7681602086016020860162001981565b6020601f19601f83011685010191505092915050565b600060018060a01b03808a168352808916602084015280881660408401528087166060840152808616608084015280851660a08401525060e060c083015262001bd960e083018462001b5c565b905098975050505050505050565b60208152602760208201527f4c69624469616d6f6e644375743a20496e636f7272656374204661636574437560408201527f74416374696f6e0000000000000000000000000000000000000000000000000060608201526000608082019050919050565b634e487b7160e01b600052601160045260246000fd5b60008019820362001c775762001c7662001c4b565b5b600182019050919050565b60006060808301818452808751808352608092508286019150828160051b8701016020808b0160005b8481101562001d6357607f198a8503018652815188850160018060a01b038251168652848201516003811062001cf157634e487b7160e01b600052602160045260246000fd5b80868801525060408083015192508a81880152508082518083528a880191508684019350600092505b8083101562001d465763ffffffff60e01b84511682528682019150868401935060018301925062001d1a565b508096505050508282019150828601955060018101905062001cab565b505062001d738189018b62001b4d565b50868103604088015262001d88818962001b5c565b95505050505050949350505050565b60208152602b60208201527f4c69624469616d6f6e644375743a204e6f2073656c6563746f727320696e206660408201527f6163657420746f2063757400000000000000000000000000000000000000000060608201526000608082019050919050565b60208152602c60208201527f4c69624469616d6f6e644375743a204164642066616365742063616e2774206260408201527f652061646472657373283029000000000000000000000000000000000000000060608201526000608082019050919050565b60208152603560208201527f4c69624469616d6f6e644375743a2043616e2774206164642066756e6374696f60408201527f6e207468617420616c726561647920657869737473000000000000000000000060608201526000608082019050919050565b600060018060601b0380831681810362001ee25762001ee162001c4b565b5b6001810192505050919050565b60208152603860208201527f4c69624469616d6f6e644375743a2043616e2774207265706c6163652066756e60408201527f6374696f6e20776974682073616d652066756e6374696f6e000000000000000060608201526000608082019050919050565b60208152603660208201527f4c69624469616d6f6e644375743a2052656d6f7665206661636574206164647260408201527f657373206d75737420626520616464726573732830290000000000000000000060608201526000608082019050919050565b6000825162001fcb81846020870162001981565b80830191505092915050565b60018060a01b038316815260406020820152600062001ffa604083018462001b5c565b90509392505050565b60208152603760208201527f4c69624469616d6f6e644375743a2043616e27742072656d6f76652066756e6360408201527f74696f6e207468617420646f65736e277420657869737400000000000000000060608201526000608082019050919050565b60208152602e60208201527f4c69624469616d6f6e644375743a2043616e27742072656d6f766520696d6d7560408201527f7461626c652066756e6374696f6e00000000000000000000000000000000000060608201526000608082019050919050565b6000828203905081811115620020e657620020e562001c4b565b5b92915050565b634e487b7160e01b600052603160045260246000fd5b60208152600062002117602083018462001b5c565b905092915050565b611b4b806200212f6000396000f3fe60806040523661000b57005b6000807f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f9050809150600082600001600080357fffffffff00000000000000000000000000000000000000000000000000000000167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610141576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610138906114d3565b60405180910390fd5b3660008037600080366000845af43d6000803e8060008114610162573d6000f35b3d6000fd5b806101706103c0565b60010160016101000a81548163ffffffff021916908360e01c021790555050565b60005b83518110156103755760008482815181106101b2576101b1611510565b5b6020026020010151602001519050600060028111156101d4576101d3611526565b5b8160028111156101e7576101e6611526565b5b036102375761023285838151811061020257610201611510565b5b60200260200101516000015186848151811061022157610220611510565b5b6020026020010151604001516103c5565b610361565b6001600281111561024b5761024a611526565b5b81600281111561025e5761025d611526565b5b036102ae576102a985838151811061027957610278611510565b5b60200260200101516000015186848151811061029857610297611510565b5b60200260200101516040015161063c565b610360565b6002808111156102c1576102c0611526565b5b8160028111156102d4576102d3611526565b5b036103245761031f8583815181106102ef576102ee611510565b5b60200260200101516000015186848151811061030e5761030d611510565b5b6020026020010151604001516108bd565b61035f565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103569061153c565b60405180910390fd5b5b5b50808061036d906115b6565b915050610194565b507f8faa70878671ccd212d20771b795c50af8fd3ff6cf27f4bde57e5d4de0aeb6738383836040516103a99392919061163b565b60405180910390a16103bb8282610a48565b505050565b600090565b6000815111610409576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161040090611747565b60405180910390fd5b6000610413610b6a565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610484576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161047b906117ab565b60405180910390fd5b60008160010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054905090506000816bffffffffffffffffffffffff16036104f1576104f08285610b97565b5b60005b835181101561063557600084828151811061051257610511611510565b5b602002602001015190506000846000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610606576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105fd9061180f565b60405180910390fd5b6106128583868a610c72565b838061061d90611873565b9450505050808061062d906115b6565b9150506104f4565b5050505050565b6000815111610680576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067790611747565b60405180910390fd5b600061068a610b6a565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036106fb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106f2906117ab565b60405180910390fd5b60008160010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054905090506000816bffffffffffffffffffffffff1603610768576107678285610b97565b5b60005b83518110156108b657600084828151811061078957610788611510565b5b602002602001015190506000846000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508673ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361087c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610873906118a2565b60405180910390fd5b610887858284610e1f565b6108938583868a610c72565b838061089e90611873565b945050505080806108ae906115b6565b91505061076b565b5050505050565b6000815111610901576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108f890611747565b60405180910390fd5b600061090b610b6a565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161461097c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161097390611906565b60405180910390fd5b60005b8251811015610a4257600083828151811061099d5761099c611510565b5b602002602001015190506000836000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050610a2d848284610e1f565b50508080610a3a906115b6565b91505061097f565b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160315610b6657610a9f82604051806060016040528060288152602001611aca60289139611481565b6000808373ffffffffffffffffffffffffffffffffffffffff1683604051610ac7919061196a565b600060405180830381855af49150503d8060008114610b02576040519150601f19603f3d011682016040523d82523d6000602084013e610b07565b606091505b509150915081610b6357600081511115610b245780518082602001fd5b83836040517f192105d7000000000000000000000000000000000000000000000000000000008152600401610b5a929190611988565b60405180910390fd5b50505b5050565b6000807f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f90508091505090565b610bb981604051806060016040528060248152602001611af260249139611481565b81600201805490508260010160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001018190555081600201819080600181540180825580915050600190039060005260206000200160009091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b81846000016000857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508360010160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018390806001815401808255809150506001900390600052602060002090600891828204019190066004029091909190916101000a81548163ffffffff021916908360e01c021790555080846000016000857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610e8e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e85906119b2565b60405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610efc576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ef390611a16565b60405180910390fd5b6000836000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160149054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff169050600060018560010160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000180549050610fd39190611a7a565b90508082146111675760008560010160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600001828154811061103457611033611510565b5b90600052602060002090600891828204019190066004029054906101000a900460e01b9050808660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000184815481106110b0576110af611510565b5b90600052602060002090600891828204019190066004026101000a81548163ffffffff021916908360e01c021790555082866000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550505b8460010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054806111bb576111ba611a98565b5b60019003818190600052602060002090600891828204019190066004026101000a81549063ffffffff02191690559055846000016000847bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001908152602001600020600080820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556000820160146101000a8154906bffffffffffffffffffffffff021916905550506000810361147a576000600186600201805490506112a59190611a7a565b905060008660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001015490508181146113e657600087600201838154811061130f5761130e611510565b5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508088600201838154811061135357611352611510565b5b9060005260206000200160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550818860010160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010181905550505b866002018054806113fa576113f9611a98565b5b6001900381819060005260206000200160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905590558660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001016000905550505b5050505050565b6000823b90506000811182906114cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114c49190611aae565b60405180910390fd5b50505050565b602081526020808201527f4469616d6f6e643a2046756e6374696f6e20646f6573206e6f7420657869737460408201526000606082019050919050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052602160045260246000fd5b60208152602760208201527f4c69624469616d6f6e644375743a20496e636f7272656374204661636574437560408201527f74416374696f6e0000000000000000000000000000000000000000000000000060608201526000608082019050919050565b634e487b7160e01b600052601160045260246000fd5b6000801982036115c9576115c86115a0565b5b600182019050919050565b60018060a01b03811682525050565b60005b838110156116015780820151818401526020810190506115e6565b50600083830152505050565b600081518084526116258160208601602086016115e3565b6020601f19601f83011685010191505092915050565b60006060808301818452808751808352608092508286019150828160051b8701016020808b0160005b8481101561171757607f198a8503018652815188850160018060a01b03825116865284820151600381106116a857634e487b7160e01b600052602160045260246000fd5b80868801525060408083015192508a81880152508082518083528a880191508684019350600092505b808310156116fb5763ffffffff60e01b8451168252868201915086840193506001830192506116d1565b5080965050505082820191508286019550600181019050611664565b50506117258189018b6115d4565b508681036040880152611738818961160d565b95505050505050949350505050565b60208152602b60208201527f4c69624469616d6f6e644375743a204e6f2073656c6563746f727320696e206660408201527f6163657420746f2063757400000000000000000000000000000000000000000060608201526000608082019050919050565b60208152602c60208201527f4c69624469616d6f6e644375743a204164642066616365742063616e2774206260408201527f652061646472657373283029000000000000000000000000000000000000000060608201526000608082019050919050565b60208152603560208201527f4c69624469616d6f6e644375743a2043616e2774206164642066756e6374696f60408201527f6e207468617420616c726561647920657869737473000000000000000000000060608201526000608082019050919050565b60006bffffffffffffffffffffffff808316818103611895576118946115a0565b5b6001810192505050919050565b60208152603860208201527f4c69624469616d6f6e644375743a2043616e2774207265706c6163652066756e60408201527f6374696f6e20776974682073616d652066756e6374696f6e000000000000000060608201526000608082019050919050565b60208152603660208201527f4c69624469616d6f6e644375743a2052656d6f7665206661636574206164647260408201527f657373206d75737420626520616464726573732830290000000000000000000060608201526000608082019050919050565b6000825161197c8184602087016115e3565b80830191505092915050565b60018060a01b03831681526040602082015260006119a9604083018461160d565b90509392505050565b60208152603760208201527f4c69624469616d6f6e644375743a2043616e27742072656d6f76652066756e6360408201527f74696f6e207468617420646f65736e277420657869737400000000000000000060608201526000608082019050919050565b60208152602e60208201527f4c69624469616d6f6e644375743a2043616e27742072656d6f766520696d6d7560408201527f7461626c652066756e6374696f6e00000000000000000000000000000000000060608201526000608082019050919050565b6000828203905081811115611a9257611a916115a0565b5b92915050565b634e487b7160e01b600052603160045260246000fd5b602081526000611ac1602083018461160d565b90509291505056fe4c69624469616d6f6e644375743a205f696e6974206164647265737320686173206e6f20636f64654c69624469616d6f6e644375743a204e657720666163657420686173206e6f20636f6465a264697066735822122045b771fb2128a1a34c5b052e9a86464933844b34929cf0d65bbea6a4e76e3b2764736f6c634300081200334c69624469616d6f6e644375743a205f696e6974206164647265737320686173206e6f20636f64654c69624469616d6f6e644375743a204e657720666163657420686173206e6f20636f6465" + publicKey = "0xB5547FBdC56DCE45e1B8ef75569916D438e09c46" + } + val result = WCBarz.getCounterfactualAddress(input.build().toByteArray()) + assertEquals(result, "0x77F62bb3E43190253D4E198199356CD2b25063cA") + } + + @Test + fun testCounterfactualAddressNonZeroSalt() { + val input = Barz.ContractAddressInput.newBuilder() + input.apply { + factory = "0x96C489979E39F877BDb8637b75A25C1a5B2DE14C" + accountFacet = "0xF6F5e5fC74905e65e3FF53c6BacEba8535dd14d1" + verificationFacet = "0xaB84813cbf26Fd951CB3d7E33Dccb8995027e490" + entryPoint = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" + facetRegistry = "0x9a95d201BB8F559771784D12c01F8084278c65E5" + defaultFallback = "0x522cDc7558b5f798dF5D61AB09B6D95Ebd342EF9" + bytecode = "0x60806040526040516104c83803806104c883398101604081905261002291610163565b6000858585858560405160240161003d959493929190610264565b60408051601f198184030181529181526020820180516001600160e01b0316634a93641760e01b1790525190915060009081906001600160a01b038a16906100869085906102c3565b600060405180830381855af49150503d80600081146100c1576040519150601f19603f3d011682016040523d82523d6000602084013e6100c6565b606091505b50915091508115806100e157506100dc816102df565b600114155b156100ff57604051636ff35f8960e01b815260040160405180910390fd5b505050505050505050610306565b80516001600160a01b038116811461012457600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561015a578181015183820152602001610142565b50506000910152565b60008060008060008060c0878903121561017c57600080fd5b6101858761010d565b95506101936020880161010d565b94506101a16040880161010d565b93506101af6060880161010d565b92506101bd6080880161010d565b60a08801519092506001600160401b03808211156101da57600080fd5b818901915089601f8301126101ee57600080fd5b81518181111561020057610200610129565b604051601f8201601f19908116603f0116810190838211818310171561022857610228610129565b816040528281528c602084870101111561024157600080fd5b61025283602083016020880161013f565b80955050505050509295509295509295565b600060018060a01b0380881683528087166020840152808616604084015280851660608401525060a0608083015282518060a08401526102ab8160c085016020870161013f565b601f01601f19169190910160c0019695505050505050565b600082516102d581846020870161013f565b9190910192915050565b80516020808301519190811015610300576000198160200360031b1b821691505b50919050565b6101b3806103156000396000f3fe60806040523661000b57005b600080356001600160e01b03191681527f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f6020819052604090912054819060601c806100cf576004838101546040516366ffd66360e11b81526000356001600160e01b031916928101929092526001600160a01b03169063cdffacc690602401602060405180830381865afa1580156100a8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100cc919061014d565b90505b6001600160a01b0381166101295760405162461bcd60e51b815260206004820152601d60248201527f4261727a3a2046756e6374696f6e20646f6573206e6f74206578697374000000604482015260640160405180910390fd5b3660008037600080366000845af43d6000803e808015610148573d6000f35b3d6000fd5b60006020828403121561015f57600080fd5b81516001600160a01b038116811461017657600080fd5b939250505056fea2646970667358221220d35db061bb6ecdb7688c3674af669ce44d527cae4ded59214d06722d73da62be64736f6c63430008120033" + publicKey = "0xB5547FBdC56DCE45e1B8ef75569916D438e09c46" + salt = 123456 + } + val result = WCBarz.getCounterfactualAddress(input.build().toByteArray()) + assertEquals(result, "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1") + } + + @Test + fun testGetFormattedSignature() { + val signature = Numeric.hexStringToByteArray("0x3044022012d89e3b41e253dc9e90bd34dc1750d059b76d0b1d16af2059aa26e90b8960bf0220256d8a05572c654906ce422464693e280e243e6d9dbc5f96a681dba846bca276") + val challenge = Numeric.hexStringToByteArray("0xcf267a78c5adaf96f341a696eb576824284c572f3e61be619694d539db1925f9") + val authenticatorData = Numeric.hexStringToByteArray("0x1a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d00000000") + val clientDataJSON = "{\"type\":\"webauthn.get\",\"challenge\":\"zyZ6eMWtr5bzQaaW61doJChMVy8-Yb5hlpTVOdsZJfk\",\"origin\":\"https://trustwallet.com\"}" + val result = WCBarz.getFormattedSignature(signature, challenge, authenticatorData, clientDataJSON) + assertEquals(Numeric.toHexString(result), "0x12d89e3b41e253dc9e90bd34dc1750d059b76d0b1d16af2059aa26e90b8960bf256d8a05572c654906ce422464693e280e243e6d9dbc5f96a681dba846bca27600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000251a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025222c226f726967696e223a2268747470733a2f2f747275737477616c6c65742e636f6d227d000000000000000000000000000000000000000000000000000000") + } + + // https://testnet.bscscan.com/tx/0x43fc13dfdf06bbb09da8ce070953753764f1e43782d0c8b621946d8b45749419 + @Test + fun testSignK1TransferAccountDeployed() { + val signingInput = Ethereum.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray()).data()) + chainId = ByteString.copyFrom("0x61".toHexByteArray()) + nonce = ByteString.copyFrom("0x2".toHexByteArray()) + toAddress = "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9" + txMode = TransactionMode.UserOp + + gasLimit = ByteString.copyFrom("0x0186A0".toHexByteArray()) + maxFeePerGas = ByteString.copyFrom("0x01a339c9e9".toHexByteArray()) + maxInclusionFeePerGas = ByteString.copyFrom("0x01a339c9e9".toHexByteArray()) + + userOperation = Ethereum.UserOperation.newBuilder().apply { + entryPoint = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" + sender = "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f" + preVerificationGas = ByteString.copyFrom("0xb708".toHexByteArray()) + verificationGasLimit = ByteString.copyFrom("0x0186a0".toHexByteArray()) + }.build() + + transaction = Ethereum.Transaction.newBuilder().apply { + transfer = Ethereum.Transaction.Transfer.newBuilder().apply { + amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) + }.build() + }.build() + } + + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x2d37191a8688f69090451ed90a0a9ba69d652c2062ee9d023b3ebe964a3ed2ae"); + + assertEquals(output.encoded.toStringUtf8(), "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"100000\",\"initCode\":\"0x\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"2\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0xb16Db98B365B1f89191996942612B14F1Da4Bd5f\",\"signature\":\"0x80e84992ebf8d5f71180231163ed150a7557ed0aa4b4bcee23d463a09847e4642d0fbf112df2e5fa067adf4b2fa17fc4a8ac172134ba5b78e3ec9c044e7f28d71c\",\"verificationGasLimit\":\"100000\"}"); + } + + // https://testnet.bscscan.com/tx/0xea1f5cddc0653e116327cbcb3bc770360a642891176eff2ec69c227e46791c31 + @Test + fun testSignR1TransferAccountNotDeployed() { + val factoryAddress = "0x3fC708630d85A3B5ec217E53100eC2b735d4f800" + val attestationObject = "0xa363666d74646e6f6e656761747453746d74a068617574684461746158981a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e075d00000000000000000000000000000000000000000014c14f8a2dfd8f451581fad6e4e1c11821abcaacd6a5010203262001215820b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2225820116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f" + val verificationFacet = "0x5034534Efe9902779eD6eA6983F435c00f3bc510" + val publicKey = WebAuthn.getPublicKey(attestationObject.toHexByteArray()) + + val signingInput = Ethereum.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray()).data()) + chainId = ByteString.copyFrom("0x61".toHexByteArray()) + nonce = ByteString.copyFrom("0x00".toHexByteArray()) + toAddress = "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9" + txMode = TransactionMode.UserOp + + gasLimit = ByteString.copyFrom("0x2625A0".toHexByteArray()) + maxFeePerGas = ByteString.copyFrom("0x01a339c9e9".toHexByteArray()) + maxInclusionFeePerGas = ByteString.copyFrom("0x01a339c9e9".toHexByteArray()) + + userOperation = Ethereum.UserOperation.newBuilder().apply { + entryPoint = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" + sender = "0x1392Ae041BfBdBAA0cFF9234a0C8F64df97B7218" + preVerificationGas = ByteString.copyFrom("0xb708".toHexByteArray()) + verificationGasLimit = ByteString.copyFrom("0x2DC6C0".toHexByteArray()) + initCode = ByteString.copyFrom(WCBarz.getInitCode(factoryAddress, publicKey, verificationFacet, 0)) + }.build() + + transaction = Ethereum.Transaction.newBuilder().apply { + transfer = Ethereum.Transaction.Transfer.newBuilder().apply { + amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) + }.build() + }.build() + } + + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x548c13a0bb87981d04a3a24a78ad5e4ba8d0afbf3cfe9311250e07b54cd38937"); + + assertEquals(output.encoded.toStringUtf8(), "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"2500000\",\"initCode\":\"0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000005034534efe9902779ed6ea6983f435c00f3bc51000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f00000000000000000000000000000000000000000000000000000000000000\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"0\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0x1392Ae041BfBdBAA0cFF9234a0C8F64df97B7218\",\"signature\":\"0xbf1b68323974e71ad9bd6dfdac07dc062599d150615419bb7876740d2bcf3c8909aa7e627bb0e08a2eab930e2e7313247c9b683c884236dd6ea0b6834fb2cb0a1b\",\"verificationGasLimit\":\"3000000\"}") + } + + // https://testnet.bscscan.com/tx/0x872f709815a9f79623a349f2f16d93b52c4d5136967bab53a586f045edbe9203 + @Test + fun testSignR1BatchedTransferAccountDeployed() { + val approveFunc = EthereumAbiFunction("approve") + approveFunc.addParamAddress("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789".toHexByteArray(), false) + approveFunc.addParamUInt256("0x8AC7230489E80000".toHexByteArray(), false) + val approveCall = EthereumAbi.encode(approveFunc) + + val transferFunc = EthereumAbiFunction("transfer") + transferFunc.addParamAddress("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789".toHexByteArray(), false) + transferFunc.addParamUInt256("0x8AC7230489E80000".toHexByteArray(), false) + val transferCall = EthereumAbi.encode(transferFunc) + + val signingInput = Ethereum.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray()).data()) + chainId = ByteString.copyFrom("0x61".toHexByteArray()) + nonce = ByteString.copyFrom("0x03".toHexByteArray()) + txMode = TransactionMode.UserOp + + gasLimit = ByteString.copyFrom("0x015A61".toHexByteArray()) + maxFeePerGas = ByteString.copyFrom("0x02540BE400".toHexByteArray()) + maxInclusionFeePerGas = ByteString.copyFrom("0x02540BE400".toHexByteArray()) + + userOperation = Ethereum.UserOperation.newBuilder().apply { + entryPoint = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" + sender = "0x1e6c542ebc7c960c6a155a9094db838cef842cf5" + preVerificationGas = ByteString.copyFrom("0xDAFC".toHexByteArray()) + verificationGasLimit = ByteString.copyFrom("0x07F7C4".toHexByteArray()) + }.build() + + transaction = Ethereum.Transaction.newBuilder().apply { + batch = Ethereum.Transaction.Batch.newBuilder().apply { + addAllCalls(listOf( + Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply { + address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266" + amount = ByteString.copyFrom("0x00".toHexByteArray()) + payload = ByteString.copyFrom(approveCall) + }.build(), + Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply { + address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266" + amount = ByteString.copyFrom("0x00".toHexByteArray()) + payload = ByteString.copyFrom(transferCall) + }.build() + )) + }.build() + }.build() + } + + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x84d0464f5a2b191e06295443970ecdcd2d18f565d0d52b5a79443192153770ab"); + assertEquals(output.encoded.toStringUtf8(), "{\"callData\":\"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"88673\",\"initCode\":\"0x\",\"maxFeePerGas\":\"10000000000\",\"maxPriorityFeePerGas\":\"10000000000\",\"nonce\":\"3\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"56060\",\"sender\":\"0x1E6c542ebC7c960c6A155A9094DB838cEf842cf5\",\"signature\":\"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c\",\"verificationGasLimit\":\"522180\"}") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt index 1edb4127f62..b6ad3654115 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt @@ -1,10 +1,12 @@ package com.trustwallet.core.app.blockchains.ethereum +import com.google.protobuf.ByteString import com.trustwallet.core.app.utils.toHexByteArray import org.junit.Assert.assertEquals import org.junit.Test import com.trustwallet.core.app.utils.Numeric -import wallet.core.jni.EthereumAbi +import wallet.core.jni.CoinType +import wallet.core.jni.proto.EthereumAbi class TestEthereumAbiDecoder { @@ -35,15 +37,15 @@ class TestEthereumAbiDecoder { } """.trimIndent() - val decoded = EthereumAbi.decodeCall(call, abi) - val expected = """{"function":"approve(address,uint256)","inputs":[{"name":"_spender","type":"address","value":"0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"},{"name":"_value","type":"uint256","value":"1"}]}""" + val decoded = wallet.core.jni.EthereumAbi.decodeCall(call, abi) + val expected = """{"function":"approve(address,uint256)","inputs":[{"name":"_spender","type":"address","value":"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"},{"name":"_value","type":"uint256","value":"1"}]}""" assertEquals(decoded, expected) } @Test fun testEthereumAbiEncodeTyped() { - val hash = EthereumAbi.encodeTyped( + val hash = wallet.core.jni.EthereumAbi.encodeTyped( """ { "types": { @@ -94,4 +96,117 @@ class TestEthereumAbiDecoder { """) assertEquals(Numeric.toHexString(hash), "0xa85c2e2b118698e88db68a8105b794a8cc7cec074e89ef991cb4f5f533819cc2") } + + @Test + fun testEthereumAbiEncodeFunction() { + val amountIn = EthereumAbi.NumberNParam.newBuilder().apply { + bits = 256 + value = ByteString.copyFrom(Numeric.hexStringToByteArray("0xde0b6b3a7640000")) // 1000000000000000000 + } + val amountOutMin = EthereumAbi.NumberNParam.newBuilder().apply { + bits = 256 + value = ByteString.copyFrom(Numeric.hexStringToByteArray("0x229f7e501ad62bdb")) // 2494851601099271131 + } + val deadline = EthereumAbi.NumberNParam.newBuilder().apply { + bits = 256 + value = ByteString.copyFrom(Numeric.hexStringToByteArray("0x5f0ed070")) // 1594806384 + } + val addressType = EthereumAbi.AddressType.newBuilder().build() + + val encodingInput = EthereumAbi.FunctionEncodingInput.newBuilder() + .setFunctionName("swapExactTokensForTokens") + .addTokens(EthereumAbi.Token.newBuilder().setNumberUint(amountIn)) + .addTokens(EthereumAbi.Token.newBuilder().setNumberUint(amountOutMin)) + .addTokens(EthereumAbi.Token.newBuilder().apply { + array = EthereumAbi.ArrayParam.newBuilder() + .setElementType(EthereumAbi.ParamType.newBuilder().setAddress(addressType)) + .addElements(EthereumAbi.Token.newBuilder().setAddress("0x6B175474E89094C44Da98b954EedeAC495271d0F")) + .addElements(EthereumAbi.Token.newBuilder().setAddress("0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2")) + .addElements(EthereumAbi.Token.newBuilder().setAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")) + .addElements(EthereumAbi.Token.newBuilder().setAddress("0xE41d2489571d322189246DaFA5ebDe1F4699F498")) + .build() + }) + .addTokens(EthereumAbi.Token.newBuilder().setAddress("0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1")) + .addTokens(EthereumAbi.Token.newBuilder().setNumberUint(deadline)) + .build() + + val encodingOutputData = wallet.core.jni.EthereumAbi.encodeFunction(CoinType.ETHEREUM, encodingInput.toByteArray()) + val encodingOutput = EthereumAbi.FunctionEncodingOutput.parseFrom(encodingOutputData) + + assertEquals(encodingOutput.error, EthereumAbi.AbiError.OK) + assertEquals(encodingOutput.functionType, "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)") + + val expectedEncoded = ByteString.copyFrom(Numeric.hexStringToByteArray("0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000229f7e501ad62bdb00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007d8bf18c7ce84b3e175b339c4ca93aed1dd166f1000000000000000000000000000000000000000000000000000000005f0ed07000000000000000000000000000000000000000000000000000000000000000040000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498")) + assertEquals(encodingOutput.encoded, expectedEncoded) + } + + @Test + fun testEthereumAbiDecodeParams() { + val encoded = ByteString.copyFrom(Numeric.hexStringToByteArray("00000000000000000000000088341d1a8f672d2780c8dc725902aae72f143b0c0000000000000000000000000000000000000000000000000000000000000001")) + + val addressType = EthereumAbi.AddressType.newBuilder().build() + val boolType = EthereumAbi.BoolType.newBuilder().build() + + val params = EthereumAbi.AbiParams.newBuilder() + .addParams(EthereumAbi.Param.newBuilder().apply { + name = "to" + param = EthereumAbi.ParamType.newBuilder().setAddress(addressType).build() + }) + .addParams(EthereumAbi.Param.newBuilder().apply { + name = "approved" + param = EthereumAbi.ParamType.newBuilder().setBoolean(boolType).build() + }) + val decodingInput = EthereumAbi.ParamsDecodingInput.newBuilder() + .setEncoded(encoded) + .setAbiParams(params) + .build() + + val decodingOutputData = wallet.core.jni.EthereumAbi.decodeParams(CoinType.ETHEREUM, decodingInput.toByteArray()) + val decodingOutput = EthereumAbi.ParamsDecodingOutput.parseFrom(decodingOutputData) + + assertEquals(decodingOutput.error, EthereumAbi.AbiError.OK) + + assertEquals(decodingOutput.getTokens(0).name, "to") + assertEquals(decodingOutput.getTokens(0).address, "0x88341d1a8F672D2780C8dC725902AAe72F143B0c") + + assertEquals(decodingOutput.getTokens(1).name, "approved") + assertEquals(decodingOutput.getTokens(1).boolean, true) + } + + @Test + fun testEthereumAbiDecodeValue() { + val encoded = ByteString.copyFrom(Numeric.hexStringToByteArray("000000000000000000000000000000000000000000000000000000000000002c48656c6c6f20576f726c64212020202048656c6c6f20576f726c64212020202048656c6c6f20576f726c64210000000000000000000000000000000000000000")) + + val decodingInput = EthereumAbi.ValueDecodingInput.newBuilder() + .setEncoded(encoded) + .setParamType("string") + .build() + + val decodingOutputData = wallet.core.jni.EthereumAbi.decodeValue(CoinType.ETHEREUM, decodingInput.toByteArray()) + val decodingOutput = EthereumAbi.ValueDecodingOutput.parseFrom(decodingOutputData) + + assertEquals(decodingOutput.error, EthereumAbi.AbiError.OK) + assertEquals(decodingOutput.token.stringValue, "Hello World! Hello World! Hello World!") + } + + @Test + fun testEthereumAbiDecodeContractCall() { + val encoded = ByteString.copyFrom(Numeric.hexStringToByteArray("c47f0027000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000086465616462656566000000000000000000000000000000000000000000000000")) + val abi = """{"c47f0027":{"constant":false,"inputs":[{"name":"name","type":"string"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}}""" + val decodingInput = EthereumAbi.ContractCallDecodingInput.newBuilder() + .setEncoded(encoded) + .setSmartContractAbiJson(abi) + .build() + + val decodingOutputData = wallet.core.jni.EthereumAbi.decodeContractCall(CoinType.ETHEREUM, decodingInput.toByteArray()) + val decodingOutput = EthereumAbi.ContractCallDecodingOutput.parseFrom(decodingOutputData) + + assertEquals(decodingOutput.error, EthereumAbi.AbiError.OK) + + val expectedJson = """{"function":"setName(string)","inputs":[{"name":"name","type":"string","value":"deadbeef"}]}""" + assertEquals(decodingOutput.decodedJson, expectedJson) + + assertEquals(decodingOutput.getTokens(0).name, "name") + assertEquals(decodingOutput.getTokens(0).stringValue, "deadbeef") + } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiValue.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiValue.kt index 02a2680b3e5..5afd77932f9 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiValue.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbiValue.kt @@ -87,7 +87,7 @@ class TestEthereumAbiValue { fun testValueDecoderValue() { assertEquals("42", EthereumAbiValue.decodeValue(Numeric.hexStringToByteArray("000000000000000000000000000000000000000000000000000000000000002a"), "uint")) assertEquals("24", EthereumAbiValue.decodeValue(Numeric.hexStringToByteArray("0000000000000000000000000000000000000000000000000000000000000018"), "uint8")) - assertEquals("0xf784682c82526e245f50975190ef0fff4e4fc077", EthereumAbiValue.decodeValue(Numeric.hexStringToByteArray("000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"), "address")) + assertEquals("0xF784682C82526e245F50975190EF0fff4E4fC077", EthereumAbiValue.decodeValue(Numeric.hexStringToByteArray("000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"), "address")) assertEquals("Hello World! Hello World! Hello World!", EthereumAbiValue.decodeValue( Numeric.hexStringToByteArray("000000000000000000000000000000000000000000000000000000000000002c48656c6c6f20576f726c64212020202048656c6c6f20576f726c64212020202048656c6c6f20576f726c64210000000000000000000000000000000000000000"), "string")) @@ -103,7 +103,7 @@ class TestEthereumAbiValue { @Test fun testValueDecoderArray_address() { val input = Numeric.hexStringToByteArray("0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc0770000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3") - assertEquals("[\"0xf784682c82526e245f50975190ef0fff4e4fc077\",\"0x2e00cd222cb42b616d86d037cc494e8ab7f5c9a3\"]", EthereumAbiValue.decodeArray(input, "address[]")) + assertEquals("[\"0xF784682C82526e245F50975190EF0fff4E4fC077\",\"0x2e00CD222Cb42B616D86D037Cc494e8ab7F5c9a3\"]", EthereumAbiValue.decodeArray(input, "address[]")) } @Test diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAddress.kt index f4b29c5db5d..86f725fdd2e 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAddress.kt @@ -1,10 +1,13 @@ package com.trustwallet.core.app.blockchains.ethereum +import com.trustwallet.core.app.utils.Numeric import org.junit.Assert.assertEquals import org.junit.Test import wallet.core.jni.AnyAddress import wallet.core.jni.CoinType import org.junit.Assert.assertFalse +import wallet.core.jni.Ethereum +import wallet.core.jni.Hash class TestEthereumAddress { diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumMessageSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumMessageSigner.kt new file mode 100644 index 00000000000..6245751d124 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumMessageSigner.kt @@ -0,0 +1,134 @@ +package com.trustwallet.core.app.blockchains.ethereum + +import com.trustwallet.core.app.utils.Numeric +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import wallet.core.jni.CoinType +import wallet.core.jni.EthereumMessageSigner +import wallet.core.jni.PrivateKey + +class TestEthereumMessageSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testEthereumSignAndVerifyMessageImmutableX() { + val data = Numeric.hexStringToByteArray("3b0a61f46fdae924007146eacb6db6642de7a5603ad843ec58e10331d89d4b84") + val privateKey = PrivateKey(data) + val publicKey = privateKey.getPublicKey(CoinType.ETHEREUM) + val msg = "Only sign this request if you’ve initiated an action with Immutable X.\n\nFor internal use:\nbd717ba31dca6e0f3f136f7c4197babce5f09a9f25176044c0b3112b1b6017a3" + val signature = EthereumMessageSigner.signMessageImmutableX(privateKey, msg) + assertEquals(signature, "32cd5a58f3419fc5db672e3d57f76199b853eda0856d491b38f557b629b0a0814ace689412bf354a1af81126d2749207dffae8ae8845160f33948a6b787e17ee01"); + assertTrue(EthereumMessageSigner.verifyMessage(publicKey, msg, signature)) + } + + @Test + fun testEthereumSignAndVerifyMessageLegacy() { + val data = Numeric.hexStringToByteArray("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d") + val privateKey = PrivateKey(data) + val publicKey = privateKey.getPublicKey(CoinType.ETHEREUM) + val msg = "Foo" + val signature = EthereumMessageSigner.signMessage(privateKey, msg) + assertEquals(signature, "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be71101b"); + assertTrue(EthereumMessageSigner.verifyMessage(publicKey, msg, signature)) + } + + @Test + fun testEthereumSignAndVerifyMessageLegacyHex() { + val data = Numeric.hexStringToByteArray("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0") + val privateKey = PrivateKey(data) + val publicKey = privateKey.getPublicKey(CoinType.ETHEREUM) + val msg = "0xc0a96273d5c3fbe4d4000491f08daef9c17f88df846c1d6f57eb5f33c1fbd035" + val signature = EthereumMessageSigner.signMessage(privateKey, msg) + assertEquals(signature, "b18a666ad08bf9bfcd39920b26b5a5d1486b67b45119810b3c7bda22e41e5c4c1bfbe0c932f6c14df4947a18ba310831a37b7307d724a3ac2a4935b99d7075141b"); + assertTrue(EthereumMessageSigner.verifyMessage(publicKey, msg, signature)) + } + + @Test + fun testEthereumSignAndVerifyMessage712Legacy() { + val data = Numeric.hexStringToByteArray("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d") + val privateKey = PrivateKey(data) + val publicKey = privateKey.getPublicKey(CoinType.ETHEREUM) + val msg = """ + { + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"} + ], + "Person": [ + {"name": "name", "type": "string"}, + {"name": "wallet", "type": "address"} + ] + }, + "primaryType": "Person", + "domain": { + "name": "Ether Person", + "version": "1", + "chainId": 0, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "name": "Cow", + "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + } + } + """.trimIndent() + val signature = EthereumMessageSigner.signTypedMessage(privateKey, msg) + assertEquals(signature, "446434e4c34d6b7456e5f07a1b994b88bf85c057234c68d1e10c936b1c85706c4e19147c0ac3a983bc2d56ebfd7146f8b62bcea6114900fe8e7d7351f44bf3761c"); + assertTrue(EthereumMessageSigner.verifyMessage(publicKey, msg, signature)) + } + + @Test + fun testEthereumSignAndVerifyMessage712Eip155() { + val data = Numeric.hexStringToByteArray("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d") + val privateKey = PrivateKey(data) + val publicKey = privateKey.getPublicKey(CoinType.ETHEREUM) + val msg = """ + { + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"} + ], + "Person": [ + {"name": "name", "type": "string"}, + {"name": "wallet", "type": "address"} + ] + }, + "primaryType": "Person", + "domain": { + "name": "Ether Person", + "version": "1", + "chainId": 0, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "name": "Cow", + "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + } + } + """.trimIndent() + val signature = EthereumMessageSigner.signTypedMessageEip155(privateKey, msg, 0) + assertEquals(signature, "446434e4c34d6b7456e5f07a1b994b88bf85c057234c68d1e10c936b1c85706c4e19147c0ac3a983bc2d56ebfd7146f8b62bcea6114900fe8e7d7351f44bf37624"); + assertTrue(EthereumMessageSigner.verifyMessage(publicKey, msg, signature)) + } + + @Test + fun testEthereumSignAndVerifyMessageEip155() { + val data = Numeric.hexStringToByteArray("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d") + val privateKey = PrivateKey(data) + val publicKey = privateKey.getPublicKey(CoinType.ETHEREUM) + val msg = "Foo" + val signature = EthereumMessageSigner.signMessageEip155(privateKey, msg, 0) + assertEquals(signature, "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be711023"); + assertTrue(EthereumMessageSigner.verifyMessage(publicKey, msg, signature)) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumRlp.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumRlp.kt new file mode 100644 index 00000000000..80abb39ff7f --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumRlp.kt @@ -0,0 +1,57 @@ +package com.trustwallet.core.app.blockchains.ethereum + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import com.trustwallet.core.app.utils.Numeric +import wallet.core.jni.CoinType +import wallet.core.jni.proto.Common +import wallet.core.jni.proto.EthereumRlp + +class TestEthereumRlp { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testEthereumRlpEncodeEip1559() { + val chainId = ByteString.copyFrom("0x0a".toHexByteArray()) + val nonce = ByteString.copyFrom("0x06".toHexByteArray()) + val maxInclusionFeePerGas = ByteString.copyFrom("0x77359400".toHexByteArray()) // 2000000000 + val maxFeePerGas = ByteString.copyFrom("0xb2d05e00".toHexByteArray()) // 3000000000 + val gasLimit = ByteString.copyFrom("0x526c".toHexByteArray()) // 21100 + val to = "0x6b175474e89094c44da98b954eedeac495271d0f" + val amount = 0.toLong() + val payload = ByteString.copyFrom("a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1".toHexByteArray()) + // An empty `accessList`. + val accessList = EthereumRlp.RlpList.newBuilder().build() + + val rlpList = EthereumRlp.RlpList.newBuilder() + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU256(chainId)) + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU256(nonce)) + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU256(maxInclusionFeePerGas)) + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU256(maxFeePerGas)) + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU256(gasLimit)) + .addItems(EthereumRlp.RlpItem.newBuilder().setAddress(to)) + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU64(amount)) + .addItems(EthereumRlp.RlpItem.newBuilder().setData(payload)) + .addItems(EthereumRlp.RlpItem.newBuilder().setList(accessList)) + .build() + + val encodingInput = EthereumRlp.EncodingInput.newBuilder().apply { + item = EthereumRlp.RlpItem.newBuilder().setList(rlpList).build() + }.build() + + val outputData = wallet.core.jni.EthereumRlp.encode(CoinType.ETHEREUM, encodingInput.toByteArray()) + val output = EthereumRlp.EncodingOutput.parseFrom(outputData) + + assertEquals(output.error, Common.SigningError.OK) + assert(output.errorMessage.isEmpty()) + assertEquals( + Numeric.toHexString(output.encoded.toByteArray()), + "0xf86c0a06847735940084b2d05e0082526c946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1c0" + ) + } +} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt index ab3b38bf229..61a6b9a42df 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt @@ -9,6 +9,8 @@ import wallet.core.java.AnySigner import wallet.core.jni.CoinType import wallet.core.jni.CoinType.ETHEREUM import wallet.core.jni.proto.Ethereum +import wallet.core.jni.EthereumAbi +import wallet.core.jni.EthereumAbiFunction import wallet.core.jni.proto.Ethereum.SigningOutput import wallet.core.jni.proto.Ethereum.TransactionMode import com.trustwallet.core.app.utils.Numeric @@ -147,6 +149,66 @@ class TestEthereumTransactionSigner { assertEquals(Numeric.toHexString(output.data.toByteArray()), "0xf242432a000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee50000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000") } + @Test + fun testEthereumStakeRocketPool() { + val function = EthereumAbiFunction("deposit") + val signingInput = Ethereum.SigningInput.newBuilder() + signingInput.apply { + chainId = ByteString.copyFrom("01".toHexByteArray()) + nonce = ByteString.copyFrom("01".toHexByteArray()) + txMode = TransactionMode.Enveloped + gasPrice = ByteString.copyFrom("77541880".toHexByteArray()) // 2002000000 + gasLimit = ByteString.copyFrom("0320c8".toHexByteArray()) // 205000 + maxFeePerGas = ByteString.copyFrom("067ef83700".toHexByteArray()) // 27900000000 + maxInclusionFeePerGas = ByteString.copyFrom("3b9aca00".toHexByteArray()) // 1000000000 + toAddress = "0x2cac916b2a963bf162f076c0a8a4a8200bcfbfb4" // contract + privateKey = ByteString.copyFrom(PrivateKey("9f56448d33de406db1561aae15fce64bdf0e9706ff15c45d4409e8fcbfd1a498".toHexByteArray()).data()) + transaction = Ethereum.Transaction.newBuilder().apply { + transfer = Ethereum.Transaction.Transfer.newBuilder().apply { + amount = ByteString.copyFrom("2386f26fc10000".toHexByteArray()) // 0.01 ETH + data = ByteString.copyFrom(EthereumAbi.encode(function)) + }.build() + }.build() + } + + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + + // https://etherscan.io/tx/0xfeba0c579f3e964fbc4eafa500e86891b9f4113735b1364edd4433d765506f1e + assertEquals(Numeric.toHexString(output.r.toByteArray()), "0xfb39e5079d7a0598ec45785d73a06b91fe1db707b9c6a150c87ffce2492c66d6") + assertEquals(Numeric.toHexString(output.s.toByteArray()), "0x7fbd43a6f4733b2b4f98ad1bc4678ea2615f5edf56ad91408337adec2f07c0ac") + } + + @Test + fun testEthereumUnstakeRocketPool() { + val function = EthereumAbiFunction("burn") + function.addParamUInt256("0x21faa32ab2502b".toHexByteArray(), false) + + val signingInput = Ethereum.SigningInput.newBuilder() + signingInput.apply { + chainId = ByteString.copyFrom("01".toHexByteArray()) + nonce = ByteString.copyFrom("03".toHexByteArray()) + txMode = TransactionMode.Enveloped + gasPrice = ByteString.copyFrom("77541880".toHexByteArray()) // 2002000000 + gasLimit = ByteString.copyFrom("055730".toHexByteArray()) // 350000 + maxFeePerGas = ByteString.copyFrom("067ef83700".toHexByteArray()) // 27900000000 + maxInclusionFeePerGas = ByteString.copyFrom("3b9aca00".toHexByteArray()) // 1000000000 + toAddress = "0xae78736Cd615f374D3085123A210448E74Fc6393" // contract + privateKey = ByteString.copyFrom(PrivateKey("9f56448d33de406db1561aae15fce64bdf0e9706ff15c45d4409e8fcbfd1a498".toHexByteArray()).data()) + transaction = Ethereum.Transaction.newBuilder().apply { + contractGeneric = Ethereum.Transaction.ContractGeneric.newBuilder().apply { + amount = ByteString.copyFrom("00".toHexByteArray()) + data = ByteString.copyFrom(EthereumAbi.encode(function)) + }.build() + }.build() + } + + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + + // https://etherscan.io/tx/0x7fd3c0e9b8b309b4258baa7677c60f5e00e8db7b647fbe3a52adda25058a4b37 + assertEquals(Numeric.toHexString(output.r.toByteArray()), "0x1fc6e94908107584357799e952b4e3fb87f088aeb66d7930a7015643f19c9e7f") + assertEquals(Numeric.toHexString(output.s.toByteArray()), "0x2c56a0b70ff2e52bf374a3dcd404bc42317d5ca15d319f5e33665352eb48f06f") + } + @Test fun testSignJSON() { val json = """ diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleAddress.kt new file mode 100644 index 00000000000..5922fa9f191 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleAddress.kt @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.everscale + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestEverscaleAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("5b59e0372d19b6355c73fa8cc708fa3301ae2ec21bb6277e8b79d386ccb7846f".toHexByteArray()) + val pubkey = key.publicKeyEd25519 + val address = AnyAddress(pubkey, CoinType.EVERSCALE) + val expected = AnyAddress("0:269fee242eb410786abe1777a14785c8bbeb1e34100c7570e17698b36ad66fb0", CoinType.EVERSCALE) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleSigner.kt new file mode 100644 index 00000000000..a07ba7c46b4 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleSigner.kt @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.everscale + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType.EVERSCALE +import wallet.core.jni.proto.Everscale +import wallet.core.jni.proto.Everscale.SigningOutput + +class TestEverscaleSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSign() { + val transferMessage = Everscale.Transfer.newBuilder().apply { + bounce = false + behavior = Everscale.MessageBehavior.SimpleTransfer + amount = 100000000 + expiredAt = 1680770631 + to = "0:db18a67f4626f15ac0537a18445937f685f9b30184f0d7b28be4bdeb92d2fd90" + encodedContractData = "te6ccgEBAQEAKgAAUAAAAAFLqS2KOWKN+7Y5OSiKhKisiw6t/h2ovvR3WbapyAtrdctwupw=" + }.build() + val signingInput = Everscale.SigningInput.newBuilder().apply { + transfer = transferMessage + privateKey = ByteString.copyFrom("542bd4288352f1c6b270046f153d406aec054a0a06000ab9b36b5c6dd3050ad4".toHexByteArray()) + }.build() + + val output = AnySigner.sign(signingInput, EVERSCALE, SigningOutput.parser()) + + // Link to the external message: https://everscan.io/messages/73807b0a3ca2d8564c023dccd5b9da222a270f68338c6fc2c064dda376a2c59d + val expectedString = "te6ccgICAAIAAQAAAKoAAAHfiAG+Ilaz1wTyTEauoymMGl6o+NGqhszIlHS8BXAmXniYrAImASIQKH2jIwoA65IGC6aua4gAA4fFo/Nuxgb3sIRELhZnSXIS7IsE2E4D+8hk3EWGVZX+ICqlN/ka9DvXduhaXUlsUyF0MjgAAAAIHAABAGhCAG2MUz+jE3itYCm9DCIsm/tC/NmAwnhr2UXyXvXJaX7IIC+vCAAAAAAAAAAAAAAAAAAA" + assertEquals(output.encoded, expectedString) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/filecoin/TestFilecoin.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/filecoin/TestFilecoin.kt index a2d739e65c2..82f292da61d 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/filecoin/TestFilecoin.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/filecoin/TestFilecoin.kt @@ -6,10 +6,7 @@ import com.trustwallet.core.app.utils.toHexBytesInByteString import org.junit.Assert.assertEquals import org.junit.Test import wallet.core.java.AnySigner -import wallet.core.jni.AnyAddress -import wallet.core.jni.CoinType -import wallet.core.jni.CoinType.FILECOIN -import wallet.core.jni.PrivateKey +import wallet.core.jni.* import wallet.core.jni.proto.Filecoin import wallet.core.jni.proto.Filecoin.SigningOutput @@ -20,13 +17,32 @@ class TestFilecoin { } @Test - fun testAddress() { + fun testCreateAddress() { val privateKey = PrivateKey("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe".toHexByteArray()) val publicKey = privateKey.getPublicKeySecp256k1(false) val address = AnyAddress(publicKey, CoinType.FILECOIN) assertEquals("f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq", address.description()) } + @Test + fun testCreateDelegatedAddress() { + val privateKey = PrivateKey("825d2bb32965764a98338139412c7591ed54c951dd65504cd8ddaeaa0fea7b2a".toHexByteArray()) + val publicKey = privateKey.getPublicKeySecp256k1(false) + val address = AnyAddress(publicKey, FilecoinAddressType.DELEGATED) + assertEquals("f410fvak24cyg3saddajborn6idt7rrtfj2ptauk5pbq", address.description()) + } + + @Test + fun testAddressConverter() { + val ethereumAddress = FilecoinAddressConverter.convertToEthereum("f410frw6wy7w6sbsguyn3yzeygg34fgf72n5ao5sxyky") + assertEquals(ethereumAddress, "0x8dbD6c7Ede90646a61Bbc649831b7c298BFd37A0") + assert(AnyAddress.isValid(ethereumAddress, CoinType.ETHEREUM)) + + val filecoinAddress = FilecoinAddressConverter.convertFromEthereum("0x8dbD6c7Ede90646a61Bbc649831b7c298BFd37A0") + assertEquals(filecoinAddress, "f410frw6wy7w6sbsguyn3yzeygg34fgf72n5ao5sxyky") + assert(AnyAddress.isValid(filecoinAddress, CoinType.FILECOIN)) + } + @Test fun testSigner() { val input = Filecoin.SigningInput.newBuilder() @@ -41,7 +57,7 @@ class TestFilecoin { .build() val output = AnySigner.sign(input, CoinType.FILECOIN, SigningOutput.parser()) - val expted = """{"Message":{"From":"f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq","GasFeeCap":"700000000000000000000","GasLimit":1000,"GasPremium":"800000000000000000000","Nonce":2,"To":"f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq","Value":"600000000000000000000"},"Signature":{"Data":"jMRu+OZ/lfppgmqSfGsntFrRLWZnUg3ZYmJTTRLsVt4V1310vR3VKGJpaE6S4sNvDOE6sEgmN9YmfTkPVK2qMgE=","Type":1}}""" - assertEquals(expted, output.json) + val expected = """{"Message":{"From":"f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq","GasFeeCap":"700000000000000000000","GasLimit":1000,"GasPremium":"800000000000000000000","Method":0,"Nonce":2,"To":"f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq","Value":"600000000000000000000"},"Signature":{"Data":"jMRu+OZ/lfppgmqSfGsntFrRLWZnUg3ZYmJTTRLsVt4V1310vR3VKGJpaE6S4sNvDOE6sEgmN9YmfTkPVK2qMgE=","Type":1}}""" + assertEquals(expected, output.json) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOAddress.kt index 6b6461e78d1..4a9bef5fedb 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOAddress.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.fio diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOSigner.kt index 7037706254b..6992aa0b9f8 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOSigner.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.fio diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/greenfield/TestGreenfieldSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/greenfield/TestGreenfieldSigner.kt new file mode 100644 index 00000000000..cd7caf5e2ac --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/greenfield/TestGreenfieldSigner.kt @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.greenfield + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType +import wallet.core.jni.PrivateKey +import wallet.core.jni.proto.Greenfield + +class TestGreenfieldSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun GreenfieldTransactionSigningSend() { + // Successfully broadcasted: https://greenfieldscan.com/tx/ED8508F3C174C4430B8EE718A6D6F0B02A8C516357BE72B1336CF74356529D19 + + val key = + PrivateKey("825d2bb32965764a98338139412c7591ed54c951dd65504cd8ddaeaa0fea7b2a".toHexByteArray()) + + val msgSend = Greenfield.Message.Send.newBuilder().apply { + fromAddress = "0xA815ae0b06dC80318121745BE40e7F8c6654e9f3" + toAddress = "0x8dbD6c7Ede90646a61Bbc649831b7c298BFd37A0" + addAmounts(Greenfield.Amount.newBuilder().apply { + amount = "1234500000000000" + denom = "BNB" + }) + }.build() + + val greenfieldFee = Greenfield.Fee.newBuilder().apply { + gas = 1200 + addAmounts(Greenfield.Amount.newBuilder().apply { + amount = "6000000000000" + denom = "BNB" + }) + }.build() + + val signingInput = Greenfield.SigningInput.newBuilder().apply { + signingMode = Greenfield.SigningMode.Eip712 + encodingMode = Greenfield.EncodingMode.Protobuf + accountNumber = 15952 + ethChainId = "5600" + cosmosChainId = "greenfield_5600-1" + memo = "Trust Wallet test memo" + sequence = 0 + fee = greenfieldFee + mode = Greenfield.BroadcastMode.SYNC + privateKey = ByteString.copyFrom(key.data()) + addMessages(Greenfield.Message.newBuilder().apply { + sendCoinsMessage = msgSend + }) + }.build() + + val output = AnySigner.sign(signingInput, CoinType.GREENFIELD, Greenfield.SigningOutput.parser()) + + assertEquals( + output.serialized, + "{\"mode\":\"BROADCAST_MODE_SYNC\",\"tx_bytes\":\"CqwBCpEBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnEKKjB4QTgxNWFlMGIwNmRDODAzMTgxMjE3NDVCRTQwZTdGOGM2NjU0ZTlmMxIqMHg4ZGJENmM3RWRlOTA2NDZhNjFCYmM2NDk4MzFiN2MyOThCRmQzN0EwGhcKA0JOQhIQMTIzNDUwMDAwMDAwMDAwMBIWVHJ1c3QgV2FsbGV0IHRlc3QgbWVtbxJzClYKTQomL2Nvc21vcy5jcnlwdG8uZXRoLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohAhm/mQgs8vzaqBLW66HrqQNv86PYTBgXyElU1OiuKD/sEgUKAwjIBRIZChQKA0JOQhINNjAwMDAwMDAwMDAwMBCwCRpBwbRb1qEAWwaqVfmp1Mn7iMi7wwV/oPi2J2eW9NBIdNoky+ZL+uegS/kY+funCOrqVZ+Kbol9/djAV+bQaNUB0xw=\"}" + ) + assertEquals(output.errorMessage, "") + } + + @Test + fun GreenfieldTransactionSigningTransferOut() { + // Successfully broadcasted Greenfield: https://greenfieldscan.com/tx/38C29C530A74946CFD22EE07DA642F5EF9399BC9AEA59EC56A9B5BE16DE16CE7 + // BSC (parent transaction): https://testnet.bscscan.com/tx/0x7f73c8a362e14e58cb5e0ec17616afc50eff7aa398db472383a6d017c8a5861a + + val key = + PrivateKey("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0".toHexByteArray()) + + val msgTransferOut = Greenfield.Message.BridgeTransferOut.newBuilder().apply { + fromAddress = "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1" + toAddress = "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1" + amount = Greenfield.Amount.newBuilder().apply { + amount = "5670000000000000" + denom = "BNB" + }.build() + }.build() + + val greenfieldFee = Greenfield.Fee.newBuilder().apply { + gas = 1200 + addAmounts(Greenfield.Amount.newBuilder().apply { + amount = "6000000000000" + denom = "BNB" + }) + }.build() + + val signingInput = Greenfield.SigningInput.newBuilder().apply { + signingMode = Greenfield.SigningMode.Eip712 + encodingMode = Greenfield.EncodingMode.Protobuf + accountNumber = 15560 + ethChainId = "5600" + cosmosChainId = "greenfield_5600-1" + sequence = 7 + fee = greenfieldFee + mode = Greenfield.BroadcastMode.SYNC + privateKey = ByteString.copyFrom(key.data()) + addMessages(Greenfield.Message.newBuilder().apply { + bridgeTransferOut = msgTransferOut + }) + }.build() + + val output = AnySigner.sign(signingInput, CoinType.GREENFIELD, Greenfield.SigningOutput.parser()) + + assertEquals( + output.serialized, + "{\"mode\":\"BROADCAST_MODE_SYNC\",\"tx_bytes\":\"CpkBCpYBCiEvZ3JlZW5maWVsZC5icmlkZ2UuTXNnVHJhbnNmZXJPdXQScQoqMHg5ZDFkOTdhREZjZDMyNEJiZDYwM0QzODcyQkQ3OGUwNDA5ODUxMGIxEioweDlkMWQ5N2FERmNkMzI0QmJkNjAzRDM4NzJCRDc4ZTA0MDk4NTEwYjEaFwoDQk5CEhA1NjcwMDAwMDAwMDAwMDAwEnUKWApNCiYvY29zbW9zLmNyeXB0by5ldGguZXRoc2VjcDI1NmsxLlB1YktleRIjCiECee80Bk2hDbBGPHBIBha6AgcD7DpFAm3ve+vSCC9db8gSBQoDCMgFGAcSGQoUCgNCTkISDTYwMDAwMDAwMDAwMDAQsAkaQc4DDByhu80Uy/M3sQePvAmhmbFWZeGq359rugtskEiXKfCzSB86XmBi+l+Q5ETDS2folMxbufHSE8gM4WBCHzgc\"}" + ) + assertEquals(output.errorMessage, "") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/hedera/TestHederaAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/hedera/TestHederaAddress.kt new file mode 100644 index 00000000000..925c36c7851 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/hedera/TestHederaAddress.kt @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.hedera + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestHederaAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val any = AnyAddress("0.0.1377988", CoinType.HEDERA) + assertEquals(any.coin(), CoinType.HEDERA) + assertEquals(any.description(), "0.0.1377988") + + Assert.assertFalse( + AnyAddress.isValid( + "0.0.a", + CoinType.HEDERA + ) + ) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/hedera/TestHederaSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/hedera/TestHederaSigner.kt new file mode 100644 index 00000000000..c01f813e2d0 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/hedera/TestHederaSigner.kt @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.hedera + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.* +import wallet.core.jni.proto.Hedera + +class TestHederaSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun HederaTransactionSimpleTransferSigning() { + // Successfully broadcasted: https://hashscan.io/testnet/transaction/0.0.48694347-1667222879-749068449?t=1667222891.440398729&p=1 + val key = + "e87a5584c0173263e138db689fdb2a7389025aaae7cb1a18a1017d76012130e8".toHexBytesInByteString() + + val transfer = Hedera.TransferMessage + .newBuilder() + .setAmount(100000000) + .setFrom("0.0.48694347") + .setTo("0.0.48462050") + .build() + + val timestamp = Hedera.Timestamp + .newBuilder() + .setSeconds(1667222879) + .setNanos(749068449) + .build() + + val transactionID = Hedera.TransactionID + .newBuilder() + .setTransactionValidStart(timestamp) + .setAccountID("0.0.48694347") + .build() + + val body = Hedera.TransactionBody + .newBuilder() + .setMemo("") + .setTransfer(transfer) + .setTransactionID(transactionID) + .setNodeAccountID("0.0.9") + .setTransactionFee(100000000) + .setTransactionValidDuration(120) + .build() + + val signingInput = Hedera.SigningInput + .newBuilder() + .setPrivateKey(key) + .setBody(body).build() + + val result = AnySigner.sign(signingInput, CoinType.HEDERA, Hedera.SigningOutput.parser()) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.encoded.toByteArray())), + "0a440a150a0c08df9aff9a0610a1c197e502120518cb889c17120218091880c2d72f22020878721e0a1c0a0c0a0518e2f18d17108084af5f0a0c0a0518cb889c1710ff83af5f12660a640a205d3a70d08b2beafb72c7a68986b3ff819a306078b8c359d739e4966e82e6d40e1a40612589c3b15f1e3ed6084b5a3a5b1b81751578cac8d6c922f31731b3982a5bac80a22558b2197276f5bae49b62503a4d39448ceddbc5ef3ba9bee4c0f302f70c" + ) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerAddress.kt new file mode 100644 index 00000000000..2861aea3a4a --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerAddress.kt @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.internetcomputer + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestInternetComputerAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("ee42eaada903e20ef6e5069f0428d552475c1ea7ed940842da6448f6ef9d48e7".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(false); + val address = AnyAddress(pubkey, CoinType.INTERNETCOMPUTER) + val expected = AnyAddress("2f25874478d06cf68b9833524a6390d0ba69c566b02f46626979a3d6a4153211", CoinType.INTERNETCOMPUTER) + + assertEquals(pubkey.data().toHex(), "0x048542e6fb4b17d6dfcac3948fe412c00d626728815ee7cc70509603f1bc92128a6e7548f3432d6248bc49ff44a1e50f6389238468d17f7d7024de5be9b181dbc8") + assertEquals(address.description(), expected.description()) + } +} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerSigner.kt new file mode 100644 index 00000000000..d3e97de972e --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerSigner.kt @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.internetcomputer + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.* +import wallet.core.jni.proto.InternetComputer +import wallet.core.jni.proto.InternetComputer.SigningOutput + +class TestInternetComputerSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun InternetComputerTransactionSigning() { + val key = PrivateKey("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be".toHexByteArray()) + + val input = InternetComputer.SigningInput.newBuilder() + .setTransaction(InternetComputer.Transaction.newBuilder().apply { + transfer = InternetComputer.Transaction.Transfer.newBuilder().apply { + toAccountIdentifier = "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a" + amount = 100000000 + memo = 0 + currentTimestampNanos = 1691709940000000000 + }.build() + }.build()) + .setPrivateKey(ByteString.copyFrom(key.data())) + val output = AnySigner.sign(input.build(), CoinType.INTERNETCOMPUTER, SigningOutput.parser()) + assertEquals(output.signedTransaction.toByteArray().toHex(), "0x81826b5452414e53414354494f4e81a266757064617465a367636f6e74656e74a66c726571756573745f747970656463616c6c6e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d026b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f706263617267583b0a0012070a050880c2d72f2a220a20943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a3a0a088090caa5a3a78abd176d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f736967984013186f18b9181c189818b318a8186518b2186118d418971618b1187d18eb185818e01826182f1873183b185018cb185d18ef18d81839186418b3183218da1824182f184e18a01880182718c0189018c918a018fd18c418d9189e189818b318ef1874183b185118e118a51864185918e718ed18c71889186c1822182318ca6a726561645f7374617465a367636f6e74656e74a46c726571756573745f747970656a726561645f73746174656e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d0265706174687381824e726571756573745f7374617475735820e8fbc2d5b0bf837b3a369249143e50d4476faafb2dd620e4e982586a51ebcf1e6d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f7369679840182d182718201888188618ce187f0c182a187a18d718e818df18fb18d318d41118a5186a184b18341842185318d718e618e8187a1828186c186a183618461418f3183318bd18a618a718bc18d918c818b7189d186e1865188418ff18fd18e418e9187f181b18d705184818b21872187818d6181c161833184318a2") + } + + @Test + fun InternetComputerTransactionSigningWithInvalidToAccountIdentifier() { + val key = PrivateKey("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be".toHexByteArray()) + + val input = InternetComputer.SigningInput.newBuilder() + .setTransaction(InternetComputer.Transaction.newBuilder().apply { + transfer = InternetComputer.Transaction.Transfer.newBuilder().apply { + toAccountIdentifier = "643d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826b" + amount = 100000000 + memo = 0 + currentTimestampNanos = 1691709940000000000 + }.build() + }.build()) + .setPrivateKey(ByteString.copyFrom(key.data())) + val output = AnySigner.sign(input.build(), CoinType.INTERNETCOMPUTER, SigningOutput.parser()) + assertEquals(output.error.number, 16) + } + + @Test + fun InternetComputerTransactionSigningWithInvalidAmount() { + val key = PrivateKey("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be".toHexByteArray()) + + val input = InternetComputer.SigningInput.newBuilder() + .setTransaction(InternetComputer.Transaction.newBuilder().apply { + transfer = InternetComputer.Transaction.Transfer.newBuilder().apply { + toAccountIdentifier = "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a" + amount = 0 + memo = 0 + currentTimestampNanos = 1691709940000000000 + }.build() + }.build()) + .setPrivateKey(ByteString.copyFrom(key.data())) + val output = AnySigner.sign(input.build(), CoinType.INTERNETCOMPUTER, SigningOutput.parser()) + assertEquals(output.error.number, 23) + } +} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/juno/TestJunoAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/juno/TestJunoAddress.kt new file mode 100644 index 00000000000..39c7022b743 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/juno/TestJunoAddress.kt @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.juno + +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert +import org.junit.Test +import wallet.core.jni.* + +class TestJunoAddress { + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAnyAddressValidation() { + val addr = "juno1gckvjxau7k56f8wg8c8xj80khyp83y8x8eqc94" + val anyAddr = AnyAddress(addr, CoinType.COSMOS, "juno") + assert(AnyAddress.isValidBech32(anyAddr.description(), CoinType.COSMOS, "juno")) + assert(AnyAddress.isValid(anyAddr.description(), CoinType.JUNO)) + assert(!AnyAddress.isValidBech32(anyAddr.description(), CoinType.BITCOIN, "juno")) + assert(!AnyAddress.isValid(anyAddr.description(), CoinType.BITCOIN)) + assert(!AnyAddress.isValid(anyAddr.description(), CoinType.COSMOS)) + } + + @Test + fun testAnyAddressFromPubkey() { + val pubKey = PublicKey("02753f5c275e1847ba4d2fd3df36ad00af2e165650b35fe3991e9c9c46f68b12bc".toHexByteArray(), PublicKeyType.SECP256K1) + val anyAddr = AnyAddress(pubKey, CoinType.COSMOS, "juno") + Assert.assertEquals(anyAddr.description(), "juno1cj2vfjec3c3luf9fx9vddnglhh9gawmncn4k5n"); + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kcc/TestKuCoinCommunityChainAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kcc/TestKuCoinCommunityChainAddress.kt index 0d2fe4a7446..9a2ecfc3811 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kcc/TestKuCoinCommunityChainAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kcc/TestKuCoinCommunityChainAddress.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.kcc diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kusama/TestKusamaAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kusama/TestKusamaAddress.kt index 23f6b258315..39fc302d0ff 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kusama/TestKusamaAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kusama/TestKusamaAddress.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.kusama diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kusama/TestKusamaSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kusama/TestKusamaSigner.kt index 738ff2a4c1f..7bec37b4f0e 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kusama/TestKusamaSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kusama/TestKusamaSigner.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.polkadot @@ -36,7 +34,7 @@ class TestKusamaSigner { blockHash = hash nonce = 1 specVersion = 2019 - network = Polkadot.Network.KUSAMA + network = KUSAMA.ss58Prefix() transactionVersion = 2 privateKey = key balanceCall = Polkadot.Balance.newBuilder().apply { diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXAddress.kt new file mode 100644 index 00000000000..ab3b1ec2483 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXAddress.kt @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.multiversx + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestMultiversXAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + private var aliceBech32 = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + private var alicePubKeyHex = "0x0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1" + private var aliceSeedHex = "0x413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" + + @Test + fun testAddressFromPrivateKey() { + val key = PrivateKey(aliceSeedHex.toHexByteArray()) + val pubKey = key.publicKeyEd25519 + val address = AnyAddress(pubKey, CoinType.MULTIVERSX) + + assertEquals(alicePubKeyHex, pubKey.data().toHex()) + assertEquals(aliceBech32, address.description()) + } + + @Test + fun testAddressFromPublicKey() { + val pubKey = PublicKey(alicePubKeyHex.toHexByteArray(), PublicKeyType.ED25519) + val address = AnyAddress(pubKey, CoinType.MULTIVERSX) + + assertEquals(aliceBech32, address.description()) + } + + @Test + fun testAddressFromString() { + val address = AnyAddress(aliceBech32, CoinType.MULTIVERSX) + + assertEquals(aliceBech32, address.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXSigner.kt new file mode 100644 index 00000000000..811c8a6b76f --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXSigner.kt @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.multiversx + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType +import wallet.core.jni.PrivateKey +import wallet.core.jni.proto.MultiversX + +class TestMultiversXSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + private var aliceBech32 = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + private var aliceSeedHex = "0x413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" + private var bobBech32 = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx" + private var carolBech32 = "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8" + + @Test + fun signGenericAction() { + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + + val accounts = MultiversX.Accounts.newBuilder() + .setSenderNonce(7) + .setSender(aliceBech32) + .setReceiver(bobBech32) + .build() + + val genericAction = MultiversX.GenericAction.newBuilder() + .setAccounts(accounts) + .setValue("0") + .setData("foo") + .setVersion(1) + .build() + + val signingInput = MultiversX.SigningInput.newBuilder() + .setGenericAction(genericAction) + .setGasPrice(1000000000) + .setGasLimit(50000) + .setChainId("1") + .setPrivateKey(privateKey) + .build() + + val output = AnySigner.sign(signingInput, CoinType.MULTIVERSX, MultiversX.SigningOutput.parser()) + val expectedSignature = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700" + + assertEquals(expectedSignature, output.signature) + assertEquals("""{"nonce":7,"value":"0","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"$expectedSignature"}""", output.encoded) + } + + @Test + fun signGenericActionWithGuardian() { + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + + val accounts = MultiversX.Accounts.newBuilder() + .setSenderNonce(42) + .setSender(aliceBech32) + .setReceiver(bobBech32) + .setGuardian(carolBech32) + .build() + + val genericAction = MultiversX.GenericAction.newBuilder() + .setAccounts(accounts) + .setValue("1000000000000000000") + .setVersion(2) + .setOptions(2) + .build() + + val signingInput = MultiversX.SigningInput.newBuilder() + .setGenericAction(genericAction) + .setGasPrice(1000000000) + .setGasLimit(100000) + .setChainId("1") + .setPrivateKey(privateKey) + .build() + + val output = AnySigner.sign(signingInput, CoinType.MULTIVERSX, MultiversX.SigningOutput.parser()) + val expectedSignature = "dae30e5cddb4a1f050009f939ce2c90843770870f9e6c77366be07e5cd7b3ebfdda38cd45d04e9070037d57761b6a68cee697e6043057f9dc565a4d0e632480d" + + assertEquals(expectedSignature, output.signature) + assertEquals("""{"nonce":42,"value":"1000000000000000000","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":100000,"chainID":"1","version":2,"signature":"$expectedSignature","options":2,"guardian":"$carolBech32"}""", output.encoded) + } + + @Test + fun signGenericActionUndelegate() { + // Successfully broadcasted https://explorer.multiversx.com/transactions/3301ae5a6a77f0ab9ceb5125258f12539a113b0c6787de76a5c5867f2c515d65 + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + + val accounts = MultiversX.Accounts.newBuilder() + .setSenderNonce(6) + .setSender("erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa") + .setReceiver("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r") + .build() + + val genericAction = MultiversX.GenericAction.newBuilder() + .setAccounts(accounts) + .setValue("0") + .setData("unDelegate@0de0b6b3a7640000") + .setVersion(1) + .build() + + val signingInput = MultiversX.SigningInput.newBuilder() + .setGenericAction(genericAction) + .setGasPrice(1000000000) + .setGasLimit(12000000) + .setChainId("1") + .setPrivateKey(privateKey) + .build() + + val output = AnySigner.sign(signingInput, CoinType.MULTIVERSX, MultiversX.SigningOutput.parser()) + val expectedSignature = "89f9683af92f7b835bff4e1d0dbfcff5245b3367df4d23538eb799e0ad0a90be29ac3bd3598ce55b35b35ebef68bfa5738eed39fd01adc33476f65bd1b966e0b" + + assertEquals(expectedSignature, output.signature) + assertEquals("""{"nonce":6,"value":"0","receiver":"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r","sender":"erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa","gasPrice":1000000000,"gasLimit":12000000,"data":"dW5EZWxlZ2F0ZUAwZGUwYjZiM2E3NjQwMDAw","chainID":"1","version":1,"signature":"$expectedSignature"}""", output.encoded) + } + + @Test + fun signGenericActionDelegate() { + // Successfully broadcasted https://explorer.multiversx.com/transactions/e5007662780f8ed677b37b156007c24bf60b7366000f66ec3525cfa16a4564e7 + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + + val accounts = MultiversX.Accounts.newBuilder() + .setSenderNonce(1) + .setSender("erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa") + .setReceiver("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r") + .build() + + val genericAction = MultiversX.GenericAction.newBuilder() + .setAccounts(accounts) + .setValue("1") + .setData("delegate") + .setVersion(1) + .build() + + val signingInput = MultiversX.SigningInput.newBuilder() + .setGenericAction(genericAction) + .setGasPrice(1000000000) + .setGasLimit(12000000) + .setChainId("1") + .setPrivateKey(privateKey) + .build() + + val output = AnySigner.sign(signingInput, CoinType.MULTIVERSX, MultiversX.SigningOutput.parser()) + val expectedSignature = "3b9164d47a4e3c0330ae387cd29ba6391f9295acf5e43a16a4a2611645e66e5fa46bf22294ca68fe1948adf45cec8cb47b8792afcdb248bd9adec7c6e6c27108" + + assertEquals(expectedSignature, output.signature) + assertEquals("""{"nonce":1,"value":"1","receiver":"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r","sender":"erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa","gasPrice":1000000000,"gasLimit":12000000,"data":"ZGVsZWdhdGU=","chainID":"1","version":1,"signature":"$expectedSignature"}""", output.encoded) + } + + @Test + fun signEGLDTransfer() { + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + + val accounts = MultiversX.Accounts.newBuilder() + .setSenderNonce(7) + .setSender(aliceBech32) + .setReceiver(bobBech32) + .build() + + val transfer = MultiversX.EGLDTransfer.newBuilder() + .setAccounts(accounts) + .setAmount("1000000000000000000") + .build() + + val signingInput = MultiversX.SigningInput.newBuilder() + .setEgldTransfer(transfer) + .setChainId("1") + .setPrivateKey(privateKey) + .build() + + val output = AnySigner.sign(signingInput, CoinType.MULTIVERSX, MultiversX.SigningOutput.parser()) + val expectedSignature = "0f40dec9d37bde3c67803fc535088e536344e271807bb7c1aa24af3c69bffa9b705e149ff7bcaf21678f4900c4ee72741fa6ef08bf4c67fc6da1c6b0f337730e" + + assertEquals(expectedSignature, output.signature) + assertEquals("""{"nonce":7,"value":"1000000000000000000","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":2,"signature":"$expectedSignature"}""", output.encoded) + } + + @Test + fun signEGLDTransferWithGuardian() { + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + + val accounts = MultiversX.Accounts.newBuilder() + .setSenderNonce(7) + .setSender(aliceBech32) + .setReceiver(bobBech32) + .setGuardian(carolBech32) + .build() + + val transfer = MultiversX.EGLDTransfer.newBuilder() + .setAccounts(accounts) + .setAmount("1000000000000000000") + .build() + + val signingInput = MultiversX.SigningInput.newBuilder() + .setEgldTransfer(transfer) + .setChainId("1") + .setPrivateKey(privateKey) + .build() + + val output = AnySigner.sign(signingInput, CoinType.MULTIVERSX, MultiversX.SigningOutput.parser()) + val expectedSignature = "741dd0d24db4df37a050f693f8481b6e51b8dd6dfc2f01a4f90aa1af3e59c89a8b0ef9d710af33103970e353d9f0cb9fd128a2e174731cbc88265d9737ed5604" + + assertEquals(expectedSignature, output.signature) + assertEquals("""{"nonce":7,"value":"1000000000000000000","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":100000,"chainID":"1","version":2,"signature":"$expectedSignature","options":2,"guardian":"$carolBech32"}""", output.encoded) + } + + @Test + fun signESDTTransfer() { + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + + val accounts = MultiversX.Accounts.newBuilder() + .setSenderNonce(7) + .setSender(aliceBech32) + .setReceiver(bobBech32) + .build() + + val transfer = MultiversX.ESDTTransfer.newBuilder() + .setAccounts(accounts) + .setTokenIdentifier("MYTOKEN-1234") + .setAmount("10000000000000") + .build() + + val signingInput = MultiversX.SigningInput.newBuilder() + .setEsdtTransfer(transfer) + .setChainId("1") + .setPrivateKey(privateKey) + .build() + + val output = AnySigner.sign(signingInput, CoinType.MULTIVERSX, MultiversX.SigningOutput.parser()) + val expectedSignature = "dd7cdc90aa09da6034b00a99e3ba0f1a2a38fa788fad018d53bf2e706f99e1a42c80063c28e6b48a5f2574c4054986f34c8eb36b1da63a22d19cf3ea5990b306" + val expectedData = "RVNEVFRyYW5zZmVyQDRkNTk1NDRmNGI0NTRlMmQzMTMyMzMzNEAwOTE4NGU3MmEwMDA=" + + assertEquals(expectedSignature, output.signature) + assertEquals("""{"nonce":7,"value":"0","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":425000,"data":"$expectedData","chainID":"1","version":2,"signature":"$expectedSignature"}""", output.encoded) + } + + @Test + fun signESDTNFTTransfer() { + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + + val accounts = MultiversX.Accounts.newBuilder() + .setSenderNonce(7) + .setSender(aliceBech32) + .setReceiver(bobBech32) + .build() + + val transfer = MultiversX.ESDTNFTTransfer.newBuilder() + .setAccounts(accounts) + .setTokenCollection("LKMEX-aab910") + .setTokenNonce(4) + .setAmount("184300000000000000") + .build() + + val signingInput = MultiversX.SigningInput.newBuilder() + .setEsdtnftTransfer(transfer) + .setChainId("1") + .setPrivateKey(privateKey) + .build() + + val output = AnySigner.sign(signingInput, CoinType.MULTIVERSX, MultiversX.SigningOutput.parser()) + val expectedSignature = "59af89d9a9ece1f35bc34323c42061cae27bb5f9830f5eb26772e680732cbd901a86caa7c3eadacd392fe1024bef4c1f08ce1dfcafec257d6f41444ccea30a0c" + val expectedData = "RVNEVE5GVFRyYW5zZmVyQDRjNGI0ZDQ1NTgyZDYxNjE2MjM5MzEzMEAwNEAwMjhlYzNkZmEwMWFjMDAwQDgwNDlkNjM5ZTVhNjk4MGQxY2QyMzkyYWJjY2U0MTAyOWNkYTc0YTE1NjM1MjNhMjAyZjA5NjQxY2MyNjE4Zjg=" + + assertEquals(expectedSignature, output.signature) + assertEquals("""{"nonce":7,"value":"0","receiver":"$aliceBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":937500,"data":"$expectedData","chainID":"1","version":2,"signature":"$expectedSignature"}""", output.encoded) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativeinjective/TestNativeInjectiveAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativeinjective/TestNativeInjectiveAddress.kt new file mode 100644 index 00000000000..8c70eaf1dfe --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativeinjective/TestNativeInjectiveAddress.kt @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.nativeinjective + +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestNativeInjectiveAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1".toHexByteArray()) + val pubKey = key.getPublicKeySecp256k1(false) + val address = AnyAddress(pubKey, CoinType.NATIVEINJECTIVE) + val expected = AnyAddress("inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a", CoinType.NATIVEINJECTIVE) + + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativeinjective/TestNativeInjectiveSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativeinjective/TestNativeInjectiveSigner.kt new file mode 100644 index 00000000000..b1295e2d823 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativeinjective/TestNativeInjectiveSigner.kt @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.nativeinjective + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.AnyAddress +import wallet.core.jni.CoinType +import wallet.core.jni.PrivateKey +import wallet.core.jni.proto.Cosmos + +class TestNativeInjectiveSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun NativeInjectiveTransactionSigning() { + val key = PrivateKey("9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(false) + val from = AnyAddress(publicKey, CoinType.NATIVEINJECTIVE).description() + + val transferAmount = Cosmos.Amount.newBuilder().apply { + amount = "10000000000" + denom = "inj" + }.build() + + val message = Cosmos.Message.newBuilder().apply { + sendCoinsMessage = Cosmos.Message.Send.newBuilder().apply { + fromAddress = from + toAddress = "inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd" + addAllAmounts(listOf(transferAmount)) + }.build() + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "100000000000000" + denom = "inj" + }.build() + + val transferFee = Cosmos.Fee.newBuilder().apply { + gas = 110000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = Cosmos.SigningMode.Protobuf + accountNumber = 17396 + chainId = "injective-1" + sequence = 1 + fee = transferFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, CoinType.NATIVEINJECTIVE, Cosmos.SigningOutput.parser()) + + // https://www.mintscan.io/injective/txs/135DD2C4A1910E4334A9C0F15125DA992E724EBF23FEB9638FCB71218BB064A5 + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Co8BCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKKmluajEzdTZnN3ZxZ3cwNzRtZ21mMnplMmNhZHp2a3o5c25sd2NydHE4YRIqaW5qMXhtcGtteHI0YXMwMGVtMjN0YzJ6Z211eXkyZ3I0aDN3Z2NsNnZkGhIKA2luahILMTAwMDAwMDAwMDASngEKfgp0Ci0vaW5qZWN0aXZlLmNyeXB0by52MWJldGExLmV0aHNlY3AyNTZrMS5QdWJLZXkSQwpBBFoMa4O4vZgn5QcnDK20mbfjqQlSRvaiITKB94PYd8mLJWdCdBsGOfMXdo/k9MJ2JmDCESKDp2hdgVUH3uMikXMSBAoCCAEYARIcChYKA2luahIPMTAwMDAwMDAwMDAwMDAwELDbBhpAx2vkplmzeK7n3puCFGPWhLd0l/ZC/CYkGl+stH+3S3hiCvIe7uwwMpUlNaSwvT8HwF1kNUp+Sx2m0Uo1x5xcFw==\"}") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainAddress.kt new file mode 100644 index 00000000000..93a2fbd2a3a --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainAddress.kt @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.nativezetachain + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestNativeZetaChainAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed".toHexByteArray()) + val pubKey = key.getPublicKeySecp256k1(false) + val address = AnyAddress(pubKey, CoinType.NATIVEZETACHAIN) + val expected = AnyAddress("zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne", CoinType.NATIVEZETACHAIN) + + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainSigner.kt new file mode 100644 index 00000000000..2dd00e4f2f8 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainSigner.kt @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.nativezetachain + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.AnyAddress +import wallet.core.jni.CoinType +import wallet.core.jni.PrivateKey +import wallet.core.jni.proto.Cosmos + +class TestNativeZetaChainSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun NativeZetaChainTransactionSigning() { + val key = PrivateKey("8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(false) + val from = AnyAddress(publicKey, CoinType.NATIVEZETACHAIN).description() + + val transferAmount = Cosmos.Amount.newBuilder().apply { + // 0.3 ZETA + amount = "300000000000000000" + denom = "azeta" + }.build() + + val message = Cosmos.Message.newBuilder().apply { + sendCoinsMessage = Cosmos.Message.Send.newBuilder().apply { + fromAddress = from + toAddress = "zeta1cscf4ldnkkz7f0wpveur6dpd0d6p2zxnsuu70y" + addAllAmounts(listOf(transferAmount)) + }.build() + }.build() + + val transferFee = Cosmos.Fee.newBuilder().apply { + gas = 200000 + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = Cosmos.SigningMode.Protobuf + accountNumber = 2726346 + chainId = "athens_7001-1" + sequence = 2 + fee = transferFee + privateKey = ByteString.copyFrom(key.data()) + txHasher = Cosmos.TxHasher.Keccak256 + signerInfo = Cosmos.SignerInfo.newBuilder().apply { + // Zetachain requires a compressed public key to sign a transaction, + // however an uncompressed public key is used to generate address. + publicKeyType = Cosmos.SignerPublicKeyType.Secp256k1 + jsonType = "ethermint/PubKeyEthSecp256k1" + protobufType = "/ethermint.crypto.v1.ethsecp256k1.PubKey" + }.build() + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, CoinType.NATIVEZETACHAIN, Cosmos.SigningOutput.parser()) + + // Successfully broadcasted (testnet): + // https://explorer.zetachain.com/cosmos/tx/A2FC8816657856ED274C4418C3CAEAEE645561275F6C63AB5F8B1DCFB37341A0 + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpoBCpcBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEncKK3pldGExNHB5MzZzeDU3dWQ4MnQ5eXJrczl6Nmhkc3JwbjV4NmtteHMwbmUSK3pldGExY3NjZjRsZG5ra3o3ZjB3cHZldXI2ZHBkMGQ2cDJ6eG5zdXU3MHkaGwoFYXpldGESEjMwMDAwMDAwMDAwMDAwMDAwMBJhClkKTwooL2V0aGVybWludC5jcnlwdG8udjEuZXRoc2VjcDI1NmsxLlB1YktleRIjCiECho5+FjRBfbKt/Z/jggW/oP6gGJin/TBWXRP3BWo3wGUSBAoCCAEYAhIEEMCaDBpAgGvqca0w2N9wnHnnxS9HiVud4aQ9lNCumzgNIW6wOR4kvPScacGS1G3kwCw7wyI2NJL8M1eVYjafFIt2FpKl3w==\"}") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/near/TestNEARSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/near/TestNEARSigner.kt index 3680868be56..8e5021abd4b 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/near/TestNEARSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/near/TestNEARSigner.kt @@ -26,9 +26,9 @@ class TestNEARSigner { signerId = "test.near" nonce = 1 receiverId = "whatever.near" - addActionsBuilder().apply { + addActions(NEAR.Action.newBuilder().apply { transfer = transferAction - } + }) blockHash = ByteString.copyFrom(Base58.decodeNoCheck("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM")) privateKey = ByteString.copyFrom(Base58.decodeNoCheck("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv").sliceArray(0..31)) }.build() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOAddress.kt index dd1ef2e86dc..8eb3f6ac165 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOAddress.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.neo diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOSigner.kt index d67f8d11559..eb40bbf1892 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOSigner.kt @@ -99,5 +99,10 @@ class TestNEOSigner { assertEquals( "8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf122956bc88746dc666759a2d67f120fe3ce1659f916d22a91e0b02421d3bddbd1232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac", hex) + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // assertEquals( + // "8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf1dd6a943678b9239a98a65d2980edf01beed0a0b4904573f31309a6a128a54980232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac", + // hex) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosAddress.kt new file mode 100644 index 00000000000..f77ce3858cd --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosAddress.kt @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.nervos + +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestNervosAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(true) + val address = AnyAddress(pubkey, CoinType.NERVOS) + val expected = AnyAddress("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras980hksatlslfaktks7epf25", CoinType.NERVOS) + + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosSigner.kt new file mode 100644 index 00000000000..d556f883f17 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosSigner.kt @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.nervos + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* +import wallet.core.jni.CoinType.NERVOS +import wallet.core.jni.proto.Nervos.* +import wallet.core.java.AnySigner + +class TestNervosSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSigning() { + val key = PrivateKey("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb".toHexByteArray()) + + val lockScript = Script.newBuilder().apply { + codeHash = "9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8".toHexBytesInByteString() + hashType = "type" + args = "c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da".toHexBytesInByteString() + }.build() + + val signingInput = SigningInput.newBuilder().apply { + addPrivateKey(ByteString.copyFrom(key.data())) + byteFee = 1 + nativeTransfer = NativeTransfer.newBuilder().apply { + toAddress = "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3" + changeAddress = "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78yze6eyfyvd537z66ur22c9mmrgz82ama" + amount = 10000000000 + }.build() + addAllCell(listOf( + Cell.newBuilder().apply { + capacity = 100000000000 + outPoint = OutPoint.newBuilder().apply { + txHash = "71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3".toHexBytesInByteString() + index = 1 + }.build() + lock = lockScript + }.build(), + Cell.newBuilder().apply { + capacity = 20000000000 + outPoint = OutPoint.newBuilder().apply { + txHash = "71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3".toHexBytesInByteString() + index = 0 + }.build() + lock = lockScript + }.build() + )) + }.build() + + val output = AnySigner.sign(signingInput, NERVOS, SigningOutput.parser()) + + assertEquals(output.transactionJson, "{\"cell_deps\":[{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":\"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c\"}}],\"header_deps\":[],\"inputs\":[{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":\"0x71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3\"},\"since\":\"0x0\"}],\"outputs\":[{\"capacity\":\"0x2540be400\",\"lock\":{\"args\":\"0xab201f55b02f53b385f79b34dfad548e549b48fc\",\"code_hash\":\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":\"type\"},\"type\":null},{\"capacity\":\"0x2540be230\",\"lock\":{\"args\":\"0xb0d65be39059d6489231b48f85ad706a560bbd8d\",\"code_hash\":\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":\"type\"},\"type\":null}],\"outputs_data\":[\"0x\",\"0x\"],\"version\":\"0x0\",\"witnesses\":[\"0x55000000100000005500000055000000410000002a9ef2ad7829e5ea0c7a32735d29a0cb2ec20434f6fd5bf6e29cda56b28e08140156191cbbf80313d3c9cae4b74607acce7b28eb21d52ef058ed8491cdde70b700\"]}") + assertEquals(output.transactionId, "0xf2c32afde7e72011985583873bc16c0a3c01fc01fc161eb4b914fcf84c53cdf8") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nuls/TestNULSSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nuls/TestNULSSigner.kt index 44284c35e02..f9399034258 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nuls/TestNULSSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nuls/TestNULSSigner.kt @@ -38,4 +38,84 @@ class TestNULSSigner { "0200f885885d00008c0117010001f7ec6473df12e751d64cf20a8baa7edd50810f8101000100201d9a0000000000000000000000000000000000000000000000000000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120100010080969800000000000000000000000000000000000000000000000000000000000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae143c1d7e3463044022028019c0099e2233c7adb84bb03a9a5666ece4a5b65a026a090fa460f3679654702204df0fcb8762b5944b3aba033fa1a287ccb098150035dd8b66f52dc58d3d0843a" ) } + + @Test + fun NULSTokenTransactionSigning() { + val signingInput = NULS.SigningInput.newBuilder() + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b"))) + .setFrom("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H") + .setTo("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe") + .setAmount(ByteString.copyFrom("0x989680".toHexByteArray())) + .setChainId(9) + .setIdassetsId(1) + .setNonce(ByteString.copyFrom("0000000000000000".toByteArray())) + .setRemark("") + .setBalance(ByteString.copyFrom("0x5F5E100".toHexByteArray())) + .setTimestamp(0x5D8885F8) + .setFeePayer("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H") + .setFeePayerBalance(ByteString.copyFrom("0xF4240".toHexByteArray())) + .setFeePayerNonce(ByteString.copyFrom("0000000000000000".toByteArray())) + .build() + + val output = AnySigner.sign(signingInput, CoinType.NULS, SigningOutput.parser()) + val encoded = output.encoded.toByteArray() + val hex = Numeric.toHexString(encoded, 0, encoded.size, false) + assertEquals(hex, + "0200f885885d0000d20217010001f7ec6473df12e751d64cf20a8baa7edd50810f810900010080969800000000000000000000000000000000000000000000000000000000000800000000000000000017010001f7ec6473df12e751d64cf20a8baa7edd50810f8101000100a086010000000000000000000000000000000000000000000000000000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120900010080969800000000000000000000000000000000000000000000000000000000000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae143c1d7e346304402204629e7e39708468a405f8d8dd208d0133a686beb5d3ae829b7a2f5867c74480902203d0dffac8189b1caa9087e480fd57581e8c382cc4e17034b492dd2178dac851d" + ) + } + + @Test + fun NULSTransactionWithFeePayerSigning() { + val signingInput = NULS.SigningInput.newBuilder() + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"))) + .setFrom("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac") + .setTo("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV") + .setAmount(ByteString.copyFrom("0x186A0".toHexByteArray())) + .setChainId(1) + .setIdassetsId(1) + .setNonce(ByteString.copyFrom("0000000000000000".toByteArray())) + .setRemark("") + .setBalance(ByteString.copyFrom("0xF4240".toHexByteArray())) + .setTimestamp(0x62FB3F9F) + .setFeePayer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA") + .setFeePayerNonce(ByteString.copyFrom("0000000000000000".toByteArray())) + .setFeePayerBalance(ByteString.copyFrom("0xF4240".toHexByteArray())) + .setFeePayerPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"))) + .build() + + val output = AnySigner.sign(signingInput, CoinType.NULS, SigningOutput.parser()) + val encoded = output.encoded.toByteArray() + val hex = Numeric.toHexString(encoded, 0, encoded.size, false) + assertEquals(hex, + "02009f3ffb620000d202170100014f019a4227bff89d51590fbf53fbd98d994485f801000100a086010000000000000000000000000000000000000000000000000000000000080000000000000000001701000152a6388c8bf54e8fcd73cc824813bfef0912299b01000100a086010000000000000000000000000000000000000000000000000000000000080000000000000000000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b701000100a0860100000000000000000000000000000000000000000000000000000000000000000000000000d22102764370693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022040b5820b93fd5e272f2a00518af45a722571834934ba20d9a866de8e6d6409ab022003c610c647648444c1e2193634b2c51817a5a6ac3fe781da1a9ea773506afd8221025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff4630440220140e46b260942a8475f38df39bf54a2eea56c77199fc7ba775aa4b7f147d0d2102206c82705cba509f37ba0e35520a97bccb71a9e35cadcb8d95dd7fde5c8aa9e428" + ) + } + + @Test + fun NULSTokenTransactionWithFeePayerSigning() { + val signingInput = NULS.SigningInput.newBuilder() + .setPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"))) + .setFrom("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac") + .setTo("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV") + .setAmount(ByteString.copyFrom("0x186A0".toHexByteArray())) + .setChainId(9) + .setIdassetsId(1) + .setNonce(ByteString.copyFrom("0000000000000000".toByteArray())) + .setRemark("") + .setBalance(ByteString.copyFrom("0xDBBA0".toHexByteArray())) + .setTimestamp(0x62FB4E4C) + .setFeePayer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA") + .setFeePayerBalance(ByteString.copyFrom("0xF4240".toHexByteArray())) + .setFeePayerNonce(ByteString.copyFrom("e05d03df6ede0e22".toByteArray())) + .setFeePayerPrivateKey(ByteString.copyFrom(Numeric.hexStringToByteArray("9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"))) + .build() + + val output = AnySigner.sign(signingInput, CoinType.NULS, SigningOutput.parser()) + val encoded = output.encoded.toByteArray() + val hex = Numeric.toHexString(encoded, 0, encoded.size, false) + assertEquals(hex, + "02004c4efb620000d2021701000152a6388c8bf54e8fcd73cc824813bfef0912299b09000100a08601000000000000000000000000000000000000000000000000000000000008000000000000000000170100014f019a4227bff89d51590fbf53fbd98d994485f801000100a08601000000000000000000000000000000000000000000000000000000000008e05d03df6ede0e22000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b709000100a0860100000000000000000000000000000000000000000000000000000000000000000000000000d32102764370693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022025cb5b40bda4e6fc0ba719bb0c1a907b2a0faa91316ef2c836310d49f906b6a802207d530a56a6c56d974036c86125da06d6e47f9d8ba1544ac3e620cebd883a707821025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff473045022100ff6f45a1c3856f9ea954baca6b2988295bbb22c958f87f0d3baf9989930549530220426ecb1520513710b99ab50e1f6c7e21b0175adef08aa05070bb9bfca8a001d8" + ) + } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/oasis/TestOasisAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/oasis/TestOasisAddress.kt index 2cdc8dfcd4a..00b315bc40a 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/oasis/TestOasisAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/oasis/TestOasisAddress.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.oasis diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/oasis/TestOasisSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/oasis/TestOasisSigner.kt index 536201188ca..ef3ad829694 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/oasis/TestOasisSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/oasis/TestOasisSigner.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.oasis @@ -45,7 +43,7 @@ class TestOasisSigner { val output = AnySigner.sign(signingInput.build(), OASIS, SigningOutput.parser()) assertEquals( - "0xa273756e747275737465645f7261775f76616c7565585ea4656e6f6e636500666d6574686f64707374616b696e672e5472616e7366657263666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680697369676e6174757265a26a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b697369676e61747572655840e331ce731ed819106586152b13cd98ecf3248a880bdc71174ee3d83f6d5f3f8ee8fc34c19b22032f2f1e3e06d382720125d7a517fba9295c813228cc2b63170b", + "0xa2697369676e6174757265a2697369676e617475726558406e51c18c9b2015c9b49414b3307336597f51ff331873d214ce2db81c9651a34d99529ccaa294a39ccd01c6b0bc2c2239d87c624e5ba4840cf99ac8f9283e240c6a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b73756e747275737465645f7261775f76616c7565585ea463666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680656e6f6e636500666d6574686f64707374616b696e672e5472616e73666572", Numeric.toHexString(output.encoded.toByteArray()) ) } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ontology/TestOntologySigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ontology/TestOntologySigning.kt index abff3b06c6c..2bf777966fb 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ontology/TestOntologySigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ontology/TestOntologySigning.kt @@ -76,6 +76,11 @@ class TestOntologySigning { assertEquals( "00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6ebb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb7cff28ddaf7f1048822c0ca21a0c4926323a2497875b963f3b8cbd3717aa6e7c2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", hex) + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // assertEquals( + // "00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6ebb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb8300d7215080efb87dd3f35de5f3b6d98aacd6161fbc0845b82d0d8be4b8b6d52321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + // hex) } @Test @@ -100,6 +105,11 @@ class TestOntologySigning { assertEquals( "00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efad62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f73b8ab199a4d757b4c7b9ed46c4ff8cfa8aefaa90b7fb6485e358034448cba752321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d743b078bd4e21bb4404c0182a32ee05260e22454dffb34dacccf458dfbee6d32db232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", hex) + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // assertEquals( + // "00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efad62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f8c4754e565b28a85b384612b93b00730143800049b97e83c95844a8eb7d66adc2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d74c4f8742a1de44bc0b3fe7d5cd11fad9edac2a5cdabe2c3b824743cc70df5f276232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + // hex) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisAddress.kt index 1d881bbdef1..88b33d1b9fd 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisAddress.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.osmosis diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt index 97f075947ba..679583594f7 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.osmosis @@ -73,6 +71,6 @@ class TestOsmosisSigner { val output = AnySigner.sign(signingInput, OSMOSIS, SigningOutput.parser()) assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKK29zbW8xbWt5Njljbjhla3R3eTA4NDV2ZWM5dXBzZHBoa3R4dDBlbjk3ZjUSK29zbW8xOHMwaGRuc2xsZ2NjbHdldTlheW13NG5na3RyMmswcmt2bjdqbW4aDgoFdW9zbW8SBTk5ODAwEmQKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLs71zkN6MCxn+VRo3ksx826RH0Z9fmpStBweE+HVY2SRIECgIIARISCgwKBXVvc21vEgMyMDAQwJoMGkAMY//Md5GRUR4lVZhk558hFS3kii9QZYoYKfg4+ac/xgNeyoiEweVDhcmEvlH1orVwjLUOnYs4ly2a/yIurYVj\"}") - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotAddress.kt index 82477b4bbbe..7ab39ef55f5 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotAddress.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.Polkadot diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotSigner.kt index 2e5a09f2885..da571e5ca1a 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotSigner.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.polkadot @@ -39,7 +37,7 @@ class TestPolkadotSigner { blockHash = genesisHashStr nonce = 0 specVersion = 17 - network = Polkadot.Network.POLKADOT + network = POLKADOT.ss58Prefix() transactionVersion = 3 privateKey = key stakingCall = Polkadot.Staking.newBuilder().apply { @@ -69,7 +67,7 @@ class TestPolkadotSigner { blockHash = genesisHashStr nonce = 4 specVersion = 30 - network = Polkadot.Network.POLKADOT + network = POLKADOT.ss58Prefix() transactionVersion = 7 privateKey = iOSTestKey stakingCall = Polkadot.Staking.newBuilder().apply { @@ -96,7 +94,7 @@ class TestPolkadotSigner { blockHash = "0x35ba668bb19453e8da6334cadcef2a27c8d4141bfc8b49e78e853c3d73e1ecd0".toHexBytesInByteString() nonce = 6 specVersion = 9200 - network = Polkadot.Network.POLKADOT + network = POLKADOT.ss58Prefix() transactionVersion = 12 privateKey = "298fcced2b497ed48367261d8340f647b3fca2d9415d57c2e3c5ef90482a2266".toHexBytesInByteString() era = Polkadot.Era.newBuilder().apply { diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polygon/TestPolygonAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polygon/TestPolygonAddress.kt index f6b21b1780c..ab807796d5a 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polygon/TestPolygonAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polygon/TestPolygonAddress.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.polygon diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ripple/TestRippleTransactionSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ripple/TestRippleTransactionSigner.kt index d98aab2490d..9ec91772b1b 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ripple/TestRippleTransactionSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ripple/TestRippleTransactionSigner.kt @@ -19,20 +19,25 @@ class TestRippleTransactionSigner { @Test fun testRippleTransactionSigning() { + val operation = Ripple.OperationPayment.newBuilder() + operation.apply { + amount = 10 + destination = "rU893viamSnsfP3zjzM2KPxjqZjXSXK6VF" + } val signingInput = Ripple.SigningInput.newBuilder() signingInput.apply { - account = "rDpysuumkweqeC7XdNgYNtzL5GxbdsmrtF" - amount = 29_000_000 - destination = "rU893viamSnsfP3zjzM2KPxjqZjXSXK6VF" - fee = 200_000 - sequence = 1 - privateKey = ByteString.copyFrom(PrivateKey("ba005cd605d8a02e3d5dfd04234cef3a3ee4f76bfbad2722d1fb5af8e12e6764".toHexByteArray()).data()) + account = "rfxdLwsZnoespnTDDb1Xhvbc8EFNdztaoq" + fee = 10 + sequence = 32268248 + lastLedgerSequence = 32268269 + privateKey = ByteString.copyFrom(PrivateKey("a5576c0f63da10e584568c8d134569ff44017b0a249eb70657127ae04f38cc77".toHexByteArray()).data()) + opPayment = operation.build() } val sign = AnySigner.sign(signingInput.build(), XRP, SigningOutput.parser()) val signBytes = sign.encoded.toByteArray() - assertEquals(signBytes.toHex(), "0x12000022800000002400000001614000000001ba8140684000000000030d407321026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6744630440220067f20b3eebfc7107dd0bcc72337a236ac3be042c0469f2341d76694a17d4bb9022048393d7ee7dcb729783b33f5038939ddce1bb8337e66d752974626854556bbb681148400b6b6d08d5d495653d73eda6804c249a5148883148132e4e20aecf29090ac428a9c43f230a829220d") + assertEquals(signBytes.toHex(), "0x12000022000000002401ec5fd8201b01ec5fed61400000000000000a68400000000000000a732103d13e1152965a51a4a9fd9a8b4ea3dd82a4eba6b25fcad5f460a2342bb650333f74463044022037d32835c9394f39b2cfd4eaf5b0a80e0db397ace06630fa2b099ff73e425dbc02205288f780330b7a88a1980fa83c647b5908502ad7de9a44500c08f0750b0d9e8481144c55f5a78067206507580be7bb2686c8460adff983148132e4e20aecf29090ac428a9c43f230a829220d") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/scroll/TestScrollAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/scroll/TestScrollAddress.kt new file mode 100644 index 00000000000..23d77ff4ccb --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/scroll/TestScrollAddress.kt @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.scroll + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestScrollAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(false) + val address = AnyAddress(pubkey, CoinType.SCROLL) + val expected = AnyAddress("0xe32DC46bfBF78D1eada7b0a68C96903e01418D64", CoinType.SCROLL) + + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretAddress.kt new file mode 100644 index 00000000000..5315c57c9ad --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretAddress.kt @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.secret + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestSecretAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("87201512d132ef7a1e57f9e24905fbc24300bd73f676b5716182be5f3e39dada".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(true) + val address = AnyAddress(pubkey, CoinType.SECRET) + val expected = AnyAddress("secret18mdrja40gfuftt5yx6tgj0fn5lurplezyp894y", CoinType.SECRET) + + assertEquals(pubkey.data().toHex(), "0x02466ac5d28cb4fab6c349060c6c1619e8d301e7741fb6b33cc1edac25f45d8646") + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretSigner.kt new file mode 100644 index 00000000000..3f1ef6330a0 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretSigner.kt @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.secret + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType.SECRET +import wallet.core.jni.proto.Cosmos +import wallet.core.jni.proto.Cosmos.SigningOutput +import wallet.core.jni.proto.Cosmos.SigningMode +import wallet.core.jni.* + +class TestSecretSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun SecretTransactionSigning() { + val key = PrivateKey("87201512d132ef7a1e57f9e24905fbc24300bd73f676b5716182be5f3e39dada".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, SECRET).description() + + val txAmount = Cosmos.Amount.newBuilder().apply { + amount = "100000" + denom = "uscrt" + }.build() + + val sendCoinsMsg = Cosmos.Message.Send.newBuilder().apply { + fromAddress = from + toAddress = "secret1rnq6hjfnalxeef87rmdeya3nu9dhpc7k9pujs3" + addAllAmounts(listOf(txAmount)) + }.build() + + val message = Cosmos.Message.newBuilder().apply { + sendCoinsMessage = sendCoinsMsg + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "2500" + denom = "uscrt" + }.build() + + val secretFee = Cosmos.Fee.newBuilder().apply { + gas = 25000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + accountNumber = 265538 + chainId = "secret-4" + memo = "" + sequence = 1 + fee = secretFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, SECRET, SigningOutput.parser()) + + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLXNlY3JldDE4bWRyamE0MGdmdWZ0dDV5eDZ0Z2owZm41bHVycGxlenlwODk0eRItc2VjcmV0MXJucTZoamZuYWx4ZWVmODdybWRleWEzbnU5ZGhwYzdrOXB1anMzGg8KBXVzY3J0EgYxMDAwMDASZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAkZqxdKMtPq2w0kGDGwWGejTAed0H7azPMHtrCX0XYZGEgQKAggBGAESEwoNCgV1c2NydBIEMjUwMBCowwEaQOcHd2gHpa5WKZ/5RRerEtrHlyDlojIEzUGhC9xMFgs7UQMWy+kTTN+NRf7zQ8rx3cPkIKeZhv0u1KRc8uRCc4o=\"}") + assertEquals(output.errorMessage, "") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartbitcoincash/TestSmartBitcoinCashAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartbitcoincash/TestSmartBitcoinCashAddress.kt index 852245d5a05..64c33ba9c58 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartbitcoincash/TestSmartBitcoinCashAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartbitcoincash/TestSmartBitcoinCashAddress.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.smartbitcoincash @@ -20,12 +18,12 @@ class TestSmartBitcoinCashAddress { @Test fun testAddress() { - val key = PrivateKey("ab4accc9310d90a61fc354d8f353bca4a2b3c0590685d3eb82d0216af3badddc".toHexByteArray()) + val key = PrivateKey("155cbd57319f3d938977b4c18000473eb3c432c4e31b667b63e88559c497d82d".toHexByteArray()) val pubkey = key.getPublicKeySecp256k1(false) val address = AnyAddress(pubkey, CoinType.SMARTBITCOINCASH) - val expected = AnyAddress("0xA3Dcd899C0f3832DFDFed9479a9d828c6A4EB2A7", CoinType.SMARTBITCOINCASH) + val expected = AnyAddress("0x8bFC9477684987dcAf0970b9bce5E3D9267C99C0", CoinType.SMARTBITCOINCASH) - assertEquals(pubkey.data().toHex(), "0x0448a9ffac8022f1c7eb5253746e24d11d9b6b2737c0aecd48335feabb95a179916b1f3a97bed6740a85a2d11c663d38566acfb08af48a47ce0c835c65c9b23d0d") + assertEquals(pubkey.data().toHex(), "0x046439f94100c802691c53ef18523be2c24d301f0e2bd3b425e832378a5405eff4331d5e57303785969073321fc76a8504a3854bdb21e6ab7b268a1737882a29c0") assertEquals(address.description(), expected.description()) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartchain/TestBinanceSmartChainAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartchain/TestBinanceSmartChainAddress.kt index 142cbc0babe..a9a83132c36 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartchain/TestBinanceSmartChainAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartchain/TestBinanceSmartChainAddress.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.binancesmartchain diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaTransaction.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaTransaction.kt new file mode 100644 index 00000000000..c2f8a3d461c --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaTransaction.kt @@ -0,0 +1,84 @@ +package com.trustwallet.core.app.blockchains.solana + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.Base58 +import wallet.core.java.AnySigner +import wallet.core.jni.Base64 +import wallet.core.jni.CoinType +import wallet.core.jni.CoinType.SOLANA +import wallet.core.jni.SolanaTransaction +import wallet.core.jni.DataVector +import wallet.core.jni.TransactionDecoder +import wallet.core.jni.proto.Common.SigningError +import wallet.core.jni.proto.Solana +import wallet.core.jni.proto.Solana.DecodingTransactionOutput +import wallet.core.jni.proto.Solana.SigningInput +import wallet.core.jni.proto.Solana.SigningOutput + +class TestSolanaTransaction { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testUpdateBlockhashAndSign() { + val encodedTx = "AnQTYwZpkm3fs4SdLxnV6gQj3hSLsyacpxDdLMALYWObm722f79IfYFTbZeFK9xHtMumiDOWAM2hHQP4r/GtbARpncaXgOVFv7OgbRLMbuCEJHO1qwcdCbtH72VzyzU8yw9sqqHIAaCUE8xaQTgT6Z5IyZfeyMe2QGJIfOjz65UPAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8Aqe6sdLXiXSDILEtzckCjkjchiSf6zVGpMYiAE5BE2IqHAQUEAgQDAQoMoA8AAAAAAAAG" + val newBlockhash = "CyPYVsYWrsJNfVpi8aazu7WsrswNFuDd385z6GNoBGUg" + + val myPrivateKey = "7f0932159226ddec9e1a4b0b8fe7cdc135049f9e549a867d722aa720dd64f32e".toHexByteArray() + val feePayerPrivateKey = "4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7".toHexByteArray() + + val privateKeys = DataVector() + privateKeys.add(myPrivateKey) + privateKeys.add(feePayerPrivateKey) + + val outputData = SolanaTransaction.updateBlockhashAndSign(encodedTx, newBlockhash, privateKeys) + val output = SigningOutput.parseFrom(outputData) + + assertEquals(output.error, SigningError.OK) + val expectedString = "Ajzc/Tke0CG8Cew5qFa6xZI/7Ya3DN0M8Ige6tKPsGzhg8Bw9DqL18KUrEZZ1F4YqZBo4Rv+FsDT8A7Nss7p4A6BNVZzzGprCJqYQeNg0EVIbmPc6mDitNniHXGeKgPZ6QZbM4FElw9O7IOFTpOBPvQFeqy0vZf/aayncL8EK/UEAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqbHiki6ThNH3auuyZPQpJntnN0mA//56nMpK/6HIuu8xAQUEAgQDAQoMoA8AAAAAAAAG" + assertEquals(output.encoded, expectedString) + } + + @Test + fun testDecodeUpdateBlockhashAndSign() { + // https://explorer.solana.com/tx/3KbvREZUat76wgWMtnJfWbJL74Vzh4U2eabVJa3Z3bb2fPtW8AREP5pbmRwUrxZCESbTomWpL41PeKDcPGbojsej?cluster=devnet + val encodedTx = Base64.decode("AnQTYwZpkm3fs4SdLxnV6gQj3hSLsyacpxDdLMALYWObm722f79IfYFTbZeFK9xHtMumiDOWAM2hHQP4r/GtbARpncaXgOVFv7OgbRLMbuCEJHO1qwcdCbtH72VzyzU8yw9sqqHIAaCUE8xaQTgT6Z5IyZfeyMe2QGJIfOjz65UPAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8Aqe6sdLXiXSDILEtzckCjkjchiSf6zVGpMYiAE5BE2IqHAQUEAgQDAQoMoA8AAAAAAAAG") + val newBlockhash = "CyPYVsYWrsJNfVpi8aazu7WsrswNFuDd385z6GNoBGUg" + + val senderPrivateKeyData = "7f0932159226ddec9e1a4b0b8fe7cdc135049f9e549a867d722aa720dd64f32e".toHexByteArray() + val feePayerPrivateKeyData = "4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7".toHexByteArray() + + // Step 1: Decode the transaction. + + val decodedData = TransactionDecoder.decode(SOLANA, encodedTx) + val decodedOutput = DecodingTransactionOutput.parseFrom(decodedData) + + assertEquals(decodedOutput.error, SigningError.OK) + assert(decodedOutput.transaction.hasLegacy()) + + // Step 2: Update recent blockhash. + + val rawTx = decodedOutput.transaction.toBuilder().apply { + legacy = decodedOutput.transaction.legacy.toBuilder().setRecentBlockhash(newBlockhash).build() + }.build() + + // Step 3: Re-sign the updated transaction. + + val signingInput = SigningInput.newBuilder().apply { + rawMessage = rawTx + privateKey = ByteString.copyFrom(senderPrivateKeyData) + feePayerPrivateKey = ByteString.copyFrom(feePayerPrivateKeyData) + txEncoding = Solana.Encoding.Base64 + }.build() + + val output = AnySigner.sign(signingInput, SOLANA, SigningOutput.parser()) + val expectedString = "Ajzc/Tke0CG8Cew5qFa6xZI/7Ya3DN0M8Ige6tKPsGzhg8Bw9DqL18KUrEZZ1F4YqZBo4Rv+FsDT8A7Nss7p4A6BNVZzzGprCJqYQeNg0EVIbmPc6mDitNniHXGeKgPZ6QZbM4FElw9O7IOFTpOBPvQFeqy0vZf/aayncL8EK/UEAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqbHiki6ThNH3auuyZPQpJntnN0mA//56nMpK/6HIuu8xAQUEAgQDAQoMoA8AAAAAAAAG" + assertEquals(output.encoded, expectedString) + } +} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaWalletConnectSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaWalletConnectSigning.kt new file mode 100644 index 00000000000..51360e7b5af --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaWalletConnectSigning.kt @@ -0,0 +1,52 @@ +package com.trustwallet.core.app.blockchains.solana + +import com.google.protobuf.ByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.Base58 +import wallet.core.jni.CoinType.SOLANA +import wallet.core.jni.WalletConnectRequest +import wallet.core.jni.proto.Common.SigningError +import wallet.core.jni.proto.Solana.Encoding +import wallet.core.jni.proto.Solana.SigningOutput +import wallet.core.jni.proto.WalletConnect + +class TestSolanaWalletConnectSigning { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSignSolanaTransactionFromWalletConnectRequest() { + // Step 1: Parse a signing request received through WalletConnect. + + val parsingInput = WalletConnect.ParseRequestInput.newBuilder().apply { + method = WalletConnect.Method.SolanaSignTransaction + payload = "{\"transaction\":\"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDZsL1CMnFVcrMn7JtiOiN1U4hC7WovOVof2DX51xM0H/GizyJTHgrBanCf8bGbrFNTn0x3pCGq30hKbywSTr6AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgIAAQwCAAAAKgAAAAAAAAA=\"}" + }.build() + + val parsingOutputBytes = WalletConnectRequest.parse(SOLANA, parsingInput.toByteArray()) + val parsingOutput = WalletConnect.ParseRequestOutput.parseFrom(parsingOutputBytes) + + assertEquals(parsingOutput.error, SigningError.OK) + + // Step 2: Set missing fields. + + val signingInput = parsingOutput.solana.toBuilder().apply { + privateKey = ByteString.copyFrom(Base58.decodeNoCheck("A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr")) + txEncoding = Encoding.Base64 + }.build() + + // Step 3: Sign the transaction. + + val output = AnySigner.sign(signingInput, SOLANA, SigningOutput.parser()) + + assertEquals(output.error, SigningError.OK) + assertEquals(output.encoded, "AQPWaOi7dMdmQpXi8HyQQKwiqIftrg1igGQxGtZeT50ksn4wAnyH4DtDrkkuE0fqgx80LTp4LwNN9a440SrmoA8BAAEDZsL1CMnFVcrMn7JtiOiN1U4hC7WovOVof2DX51xM0H/GizyJTHgrBanCf8bGbrFNTn0x3pCGq30hKbywSTr6AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgIAAQwCAAAAKgAAAAAAAAA=") + + assertEquals(output.getSignatures(0).pubkey, "7v91N7iZ9mNicL8WfG6cgSCKyRXydQjLh6UYBWwm6y1Q") + assertEquals(output.getSignatures(0).signature, "5T6uZBHnHFd8uWErDBTFRVkbKuhbcm94K5MJ2beTYDruzqv4FjS7EMKvC94ZfxNAiWUXZ6bZxS3WXUbhJwYNPWn") + } +} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeAddress.kt new file mode 100644 index 00000000000..54577d7971d --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeAddress.kt @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.stargaze + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestStargazeAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(true) + val address = AnyAddress(pubkey, CoinType.STARGAZE) + val expected = AnyAddress("stars1mry47pkga5tdswtluy0m8teslpalkdq02a8nhy", CoinType.STARGAZE) + + assertEquals(pubkey.data().toHex(), "0x02cbfdb5e472893322294e60cf0883d43df431e1089d29ecb447a9e6d55045aae5") + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeSigner.kt new file mode 100644 index 00000000000..63a7a166b60 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeSigner.kt @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.stargaze + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.AnyAddress +import wallet.core.jni.CoinType +import wallet.core.jni.PrivateKey +import wallet.core.jni.proto.Cosmos + +class TestStargazeSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun stargazeTransactionCW721Signing() { + val key = + PrivateKey("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433".toHexByteArray()) + + val txMsg = Cosmos.Message.WasmExecuteContractGeneric.newBuilder().apply { + senderAddress = "stars1mry47pkga5tdswtluy0m8teslpalkdq02a8nhy" + contractAddress = "stars14gmjlyfz5mpv5d8zrksn0tjhz2wwvdc4yk06754alfasq9qen7fsknry42" + executeMsg = """{"transfer_nft": {"recipient": "stars1kd5q7qejlqz94kpmd9pvr4v2gzgnca3lvt6xnp","token_id": "1209"}}""" + }.build() + + val message = Cosmos.Message.newBuilder().apply { + wasmExecuteContractGeneric = txMsg + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "1000" + denom = "ustars" + }.build() + + val stargazeFee = Cosmos.Fee.newBuilder().apply { + gas = 666666 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = Cosmos.SigningMode.Protobuf + accountNumber = 188393 + chainId = "stargaze-1" + memo = "" + sequence = 5 + fee = stargazeFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, CoinType.STARGAZE, Cosmos.SigningOutput.parser()) + // Successfully broadcasted: https://www.mintscan.io/stargaze/txs/300836A5BF9002CF38EE34A8C56E8E7E6854FA64F1DEB3AE108F381A48150F7C + val expected = """{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CoACCv0BCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QS1AEKLHN0YXJzMW1yeTQ3cGtnYTV0ZHN3dGx1eTBtOHRlc2xwYWxrZHEwMmE4bmh5EkBzdGFyczE0Z21qbHlmejVtcHY1ZDh6cmtzbjB0amh6Mnd3dmRjNHlrMDY3NTRhbGZhc3E5cWVuN2Zza25yeTQyGmJ7InRyYW5zZmVyX25mdCI6IHsicmVjaXBpZW50IjogInN0YXJzMWtkNXE3cWVqbHF6OTRrcG1kOXB2cjR2Mmd6Z25jYTNsdnQ2eG5wIiwidG9rZW5faWQiOiAiMTIwOSJ9fRJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECy/215HKJMyIpTmDPCIPUPfQx4QidKey0R6nm1VBFquUSBAoCCAEYBRIUCg4KBnVzdGFycxIEMTAwMBCq2CgaQMx+l2sdM5DAPbDyY1p173MLnjGyNWIcRmaFiVNphLuTV3tjhwPbsXEA0hyRxyWS3vN0/xUF/JEsO9wRspj2aJ4="}""".trimIndent() + assertEquals( + output.serialized, + expected + ) + assertEquals(output.errorMessage, "") + } + + @Test + fun stargazeTransactionSigning() { + val key = + PrivateKey("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, CoinType.STARGAZE).description() + + val txAmount = Cosmos.Amount.newBuilder().apply { + amount = "10000" + denom = "ustars" + }.build() + + val sendCoinsMsg = Cosmos.Message.Send.newBuilder().apply { + fromAddress = from + toAddress = "stars1mry47pkga5tdswtluy0m8teslpalkdq02a8nhy" + addAllAmounts(listOf(txAmount)) + }.build() + + val message = Cosmos.Message.newBuilder().apply { + sendCoinsMessage = sendCoinsMsg + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "1000" + denom = "ustars" + }.build() + + val stargazeFee = Cosmos.Fee.newBuilder().apply { + gas = 80000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = Cosmos.SigningMode.Protobuf + accountNumber = 188393 + chainId = "stargaze-1" + memo = "" + sequence = 0 + fee = stargazeFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, CoinType.STARGAZE, Cosmos.SigningOutput.parser()) + // Successfully broadcasted: https://www.mintscan.io/stargaze/txs/98D5E36CA7080DDB286FE924A5A9976ABD4EBE49C92A09D322F29AD30DE4BE4D + val expected = """{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpABCo0BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm0KLHN0YXJzMW1yeTQ3cGtnYTV0ZHN3dGx1eTBtOHRlc2xwYWxrZHEwMmE4bmh5EixzdGFyczFtcnk0N3BrZ2E1dGRzd3RsdXkwbTh0ZXNscGFsa2RxMDJhOG5oeRoPCgZ1c3RhcnMSBTEwMDAwEmYKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLL/bXkcokzIilOYM8Ig9Q99DHhCJ0p7LRHqebVUEWq5RIECgIIARIUCg4KBnVzdGFycxIEMTAwMBCA8QQaQHAkntxzC1oH7Yde4+KEmnB+K3XbJIYw0q6MqMPEY65YAwBDNDOdaTu/rpehus/20MvBfbAEZiw9+whzXLpkQ5A="}""".trimIndent() + assertEquals( + output.serialized, + expected + ) + assertEquals(output.errorMessage, "") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/starkex/TestStarkExMessageSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/starkex/TestStarkExMessageSigner.kt new file mode 100644 index 00000000000..d68cd722b80 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/starkex/TestStarkExMessageSigner.kt @@ -0,0 +1,27 @@ +package com.trustwallet.core.app.blockchains.starkex + +import com.trustwallet.core.app.utils.Numeric +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import wallet.core.jni.PrivateKey +import wallet.core.jni.PublicKeyType +import wallet.core.jni.StarkExMessageSigner + +class TestStarkExMessageSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testStarkExSignAndVerifyMessage() { + val data = Numeric.hexStringToByteArray("04be51a04e718c202e4dca60c2b72958252024cfc1070c090dd0f170298249de") + val privateKey = PrivateKey(data) + val publicKey = privateKey.getPublicKeyByType(PublicKeyType.STARKEX) + val msg = "463a2240432264a3aa71a5713f2a4e4c1b9e12bbb56083cd56af6d878217cf" + val signature = StarkExMessageSigner.signMessage(privateKey, msg) + assertEquals(signature, "04cf5f21333dd189ada3c0f2a51430d733501a9b1d5e07905273c1938cfb261e05b6013d74adde403e8953743a338c8d414bb96bf69d2ca1a91a85ed2700a528"); + assertTrue(StarkExMessageSigner.verifyMessage(publicKey, msg, signature)) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarSigner.kt index a0a35c869a5..a76ea84550a 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarSigner.kt @@ -31,7 +31,6 @@ class TestStellarTransactionSigner { sequence = 2 passphrase = StellarPassphrase.STELLAR.toString() opPayment = operation.build() - memoVoid = memoVoidBuilder.build() privateKey = ByteString.copyFrom(PrivateKey("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722".toHexByteArray()).data()) } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/sui/TestSuiAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/sui/TestSuiAddress.kt new file mode 100644 index 00000000000..503e6e8c8ad --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/sui/TestSuiAddress.kt @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.sui + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestSuiAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val any = AnyAddress("0x259ff8074ab425cbb489f236e18e08f03f1a7856bdf7c7a2877bd64f738b5015", CoinType.SUI) + assertEquals(any.coin(), CoinType.SUI) + assertEquals(any.description(), "0x259ff8074ab425cbb489f236e18e08f03f1a7856bdf7c7a2877bd64f738b5015") + + Assert.assertFalse( + AnyAddress.isValid( + "0xMQqpqMQgCBuiPkoXfgZZsJvuzCeI1zc00z6vHJj4", + CoinType.SUI + ) + ) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/sui/TestSuiSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/sui/TestSuiSigner.kt new file mode 100644 index 00000000000..84ab0914391 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/sui/TestSuiSigner.kt @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.sui + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType +import wallet.core.jni.proto.Sui + +class TestSuiSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSuiDirectSigning() { + // Successfully broadcasted https://explorer.sui.io/txblock/HkPo6rYPyDY53x1MBszvSZVZyixVN7CHvCJGX381czAh?network=devnet + val txBytes = """ + AAACAAgQJwAAAAAAAAAgJZ/4B0q0Jcu0ifI24Y4I8D8aeFa998eih3vWT3OLUBUCAgABAQAAAQEDAAAAAAEBANV1rX8Y6UhGKlz2mPVk7zlKdSpx/sYkk6+KBVwBLA1QAQbywsjB2JZN8QGdZhbpcFcZvrq9kx2idVy5SM635olk7AIAAAAAAAAgYEVuxmf1zRBGdoDr+VDtMpIFF12s2Ua7I2ru1XyGF8/Vda1/GOlIRipc9pj1ZO85SnUqcf7GJJOvigVcASwNUAEAAAAAAAAA0AcAAAAAAAAA + """.trimIndent() + val key = + "3823dce5288ab55dd1c00d97e91933c613417fdb282a0b8b01a7f5f5a533b266".toHexBytesInByteString() + val signDirect = Sui.SignDirect.newBuilder().setUnsignedTxMsg(txBytes).build() + val signingInput = + Sui.SigningInput.newBuilder().setSignDirectMessage(signDirect).setPrivateKey(key).build() + val result = AnySigner.sign(signingInput, CoinType.SUI, Sui.SigningOutput.parser()) + val expectedSignature = "APxPduNVvHj2CcRcHOtiP2aBR9qP3vO2Cb0g12PI64QofDB6ks33oqe/i/iCTLcop2rBrkczwrayZuJOdi7gvwNqfN7sFqdcD/Z4e8I1YQlGkDMCK7EOgmydRDqfH8C9jg==" + assertEquals(result.unsignedTx, txBytes); + assertEquals(result.signature, expectedSignature) + } + + @Test + fun testSuiTransfer() { + // Successfully broadcasted: https://suiscan.xyz/mainnet/tx/D4Ay9TdBJjXkGmrZSstZakpEWskEQHaWURP6xWPRXbAm + val txBytes = """ + AAAEAAjoAwAAAAAAAAAIUMMAAAAAAAAAIKcXWr3V7ZLr4605DbNmxqcGR4zfUXzebPmGMAZc2jd6ACBU6A1215DCd/WkTzzpL1PSb1iUiSvzld7mN1mIh2vmsgMCAAIBAAABAQABAQMAAAAAAQIAAQEDAAABAAEDAFToDXbXkMJ39aRPPOkvU9JvWJSJK/OV3uY3WYiHa+ayAWNgILOn3HsRw6pvQZsX+KnBLn95ox0b3S3mcLTt1jAFeHEaBQAAAAAgGGuNnxrqusosgjP3gQ3jBjnhapGNBlcU0yTaupXpa0BU6A1215DCd/WkTzzpL1PSb1iUiSvzld7mN1mIh2vmsu4CAAAAAAAAwMYtAAAAAAAA + """.trimIndent() + val key = + "7e6682f7bf479ef0f627823cffd4e1a940a7af33e5fb39d9e0f631d2ecc5daff".toHexBytesInByteString() + + val paySui = Sui.PaySui.newBuilder() + .addInputCoins(Sui.ObjectRef.newBuilder().apply { + objectId = "0x636020b3a7dc7b11c3aa6f419b17f8a9c12e7f79a31d1bdd2de670b4edd63005" + version = 85619064 + objectDigest = "2eKuWbZSVfpFVfg8FXY9wP6W5AFXnTchSoUdp7obyYZ5" + }) + .addRecipients("0xa7175abdd5ed92ebe3ad390db366c6a706478cdf517cde6cf98630065cda377a") + .addRecipients("0x54e80d76d790c277f5a44f3ce92f53d26f5894892bf395dee6375988876be6b2") + .addAmounts(1000) + .addAmounts(50000) + + val signingInput = Sui.SigningInput.newBuilder() + .setPaySui(paySui) + .setPrivateKey(key) + .setGasBudget(3000000) + .setReferenceGasPrice(750) + .build() + + val result = AnySigner.sign(signingInput, CoinType.SUI, Sui.SigningOutput.parser()) + val expectedSignature = "AEh44B7iGArEHF1wOLAQJMLNgGnaIwn3gKPC92vtDJqITDETAM5z9plaxio1xomt6/cZReQ5FZaQsMC6l7E0BwmF69FEH+T5VPvl3GB3vwCOEZpeJpKXxvcIPQAdKsh2/g==" + assertEquals(result.unsignedTx, txBytes); + assertEquals(result.signature, expectedSignature) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraClassicTxs.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraClassicTxs.kt new file mode 100644 index 00000000000..538d0efb76b --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraClassicTxs.kt @@ -0,0 +1,209 @@ +package com.trustwallet.core.app.blockchains.terra + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* +import wallet.core.jni.CoinType.TERRA +import wallet.core.jni.proto.Cosmos +import wallet.core.jni.proto.Cosmos.SigningOutput +import wallet.core.jni.proto.Cosmos.SigningMode +import wallet.core.java.AnySigner + +class TestTerraClassicTxs { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSigningTransaction() { + val key = + PrivateKey("1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, TERRA).description() + + val txAmount = Cosmos.Amount.newBuilder().apply { + amount = "1000000" + denom = "uluna" + }.build() + + val sendCoinsMsg = Cosmos.Message.Send.newBuilder().apply { + fromAddress = from + toAddress = "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms" + addAllAmounts(listOf(txAmount)) + typePrefix = "bank/MsgSend" + }.build() + + val message = Cosmos.Message.newBuilder().apply { + sendCoinsMessage = sendCoinsMsg + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "3000" + denom = "uluna" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 200000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + accountNumber = 158 + chainId = "soju-0013" + memo = "" + sequence = 0 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, TERRA, SigningOutput.parser()) + val jsonPayload = output.json + + val expectedJsonPayload = """{"mode":"block","tx":{"fee":{"amount":[{"amount":"3000","denom":"uluna"}],"gas":"200000"},"memo":"","msg":[{"type":"bank/MsgSend","value":{"amount":[{"amount":"1000000","denom":"uluna"}],"from_address":"terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe","to_address":"terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk"},"signature":"KPdiVsKpY12JG/VKEJVa/FpMKclxlS0qNNG6VOAypj10R5vY5UX5IgRJET1zNYnH0wvcXxfNXV+s8jtwN2UXiQ=="}]}}""" + assertEquals(expectedJsonPayload, jsonPayload) + + } + + @Test + fun testSigningWasmTerraTransferTxProtobuf() { + val key = + PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, TERRA).description() + + val wasmTransferMessage = Cosmos.Message.WasmTerraExecuteContractTransfer.newBuilder().apply { + senderAddress = from + contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" // ANC + amount = ByteString.copyFrom("0x3D090".toHexByteArray()) // 250000 + recipientAddress = "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" + }.build() + + val message = Cosmos.Message.newBuilder().apply { + wasmTerraExecuteContractTransferMessage = wasmTransferMessage + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "3000" + denom = "uluna" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 200000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + accountNumber = 3407705 + chainId = "columbus-5" + memo = "" + sequence = 3 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, TERRA, SigningOutput.parser()) + + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CucBCuQBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBK5AQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Glt7InRyYW5zZmVyIjp7ImFtb3VudCI6IjI1MDAwMCIsInJlY2lwaWVudCI6InRlcnJhMWpsZ2FxeTludm4yaGY1dDJzcmE5eWN6OHM3N3duZjlsMGttZ2NwIn19EmcKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNwZjrHsPmJKW/rXOWfukpQ1+lOHOJW3/IlFFnKLNmsABIECgIIARgDEhMKDQoFdWx1bmESBDMwMDAQwJoMGkAaprIEMLPH2HmFdwFGoaipb2GIyhXt6ombz+WMnG2mORBI6gFt0M+IymYgzZz6w1SW52R922yafDnn7yXfutRw\"}") + assertEquals(output.errorMessage, "") + } + + @Test + fun testSigningWasmTerraGenericProtobuf() { + val key = + PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, TERRA).description() + + val wasmGenericMessage = Cosmos.Message.WasmTerraExecuteContractGeneric.newBuilder().apply { + senderAddress = from + contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" // ANC + executeMsg = """{"transfer": { "amount": "250000", "recipient": "terra1d7048csap4wzcv5zm7z6tdqem2agyp9647vdyj" } }""" + }.build() + + val message = Cosmos.Message.newBuilder().apply { + wasmTerraExecuteContractGeneric = wasmGenericMessage + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "3000" + denom = "uluna" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 200000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + accountNumber = 3407705 + chainId = "columbus-5" + memo = "" + sequence = 7 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, TERRA, SigningOutput.parser()) + + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Cu4BCusBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLAAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2GmJ7InRyYW5zZmVyIjogeyAiYW1vdW50IjogIjI1MDAwMCIsICJyZWNpcGllbnQiOiAidGVycmExZDcwNDhjc2FwNHd6Y3Y1em03ejZ0ZHFlbTJhZ3lwOTY0N3ZkeWoiIH0gfRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYBxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAkPsS7xlSng2LMc9KiD1soN5NLaDcUh8I9okPmsdJN3le1B7yxRGNB4aQfhaRl/8Z0r5vitRT0AWuxDasd8wcFw==\"}") + assertEquals(output.errorMessage, "") + } + + @Test + fun testSigningWasmTerraGenericWithCoinsProtobuf() { + val key = + PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray()) + val publicKey = key.getPublicKeySecp256k1(true) + val from = AnyAddress(publicKey, TERRA).description() + + val coins = Cosmos.Amount.newBuilder().apply { + amount = "1000" + denom = "uusd" + }.build() + + val wasmGenericMessage = Cosmos.Message.WasmTerraExecuteContractGeneric.newBuilder().apply { + senderAddress = from + contractAddress = "terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s" // ANC Market + executeMsg = """{ "deposit_stable": {} }""" + addCoins(coins) + }.build() + + val message = Cosmos.Message.newBuilder().apply { + wasmTerraExecuteContractGeneric = wasmGenericMessage + }.build() + + val feeAmount = Cosmos.Amount.newBuilder().apply { + amount = "7000" + denom = "uluna" + }.build() + + val cosmosFee = Cosmos.Fee.newBuilder().apply { + gas = 600000 + addAllAmounts(listOf(feeAmount)) + }.build() + + val signingInput = Cosmos.SigningInput.newBuilder().apply { + signingMode = SigningMode.Protobuf + accountNumber = 3407705 + chainId = "columbus-5" + memo = "" + sequence = 9 + fee = cosmosFee + privateKey = ByteString.copyFrom(key.data()) + addAllMessages(listOf(message)) + }.build() + + val output = AnySigner.sign(signingInput, TERRA, SigningOutput.parser()) + + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CrIBCq8BCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBKEAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMXNlcGZqN3MwYWVnNTk2N3V4bmZrNHRoemxlcnJza3RrcGVsbTVzGhh7ICJkZXBvc2l0X3N0YWJsZSI6IHt9IH0qDAoEdXVzZBIEMTAwMBJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYCRITCg0KBXVsdW5hEgQ3MDAwEMDPJBpAGyi7f1ioY8XV6pjFq1s86Om4++CIUnd3rLHif2iopCcYvX0mLkTlQ6NUERg8nWTYgXcj6fOTO/ptgPuAtv0NWg==\"}") + assertEquals(output.errorMessage, "") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraTransactions.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraTransactions.kt index 0d472d980ef..e4591ff57e3 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraTransactions.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraTransactions.kt @@ -5,7 +5,7 @@ import com.trustwallet.core.app.utils.toHexByteArray import org.junit.Assert.assertEquals import org.junit.Test import wallet.core.jni.* -import wallet.core.jni.CoinType.TERRA +import wallet.core.jni.CoinType.TERRAV2 import wallet.core.jni.proto.Cosmos import wallet.core.jni.proto.Cosmos.SigningOutput import wallet.core.jni.proto.Cosmos.SigningMode @@ -20,9 +20,9 @@ class TestTerraTransactions { @Test fun testSigningTransaction() { val key = - PrivateKey("1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6".toHexByteArray()) + PrivateKey("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005".toHexByteArray()) val publicKey = key.getPublicKeySecp256k1(true) - val from = AnyAddress(publicKey, TERRA).description() + val from = AnyAddress(publicKey, TERRAV2).description() val txAmount = Cosmos.Amount.newBuilder().apply { amount = "1000000" @@ -31,9 +31,8 @@ class TestTerraTransactions { val sendCoinsMsg = Cosmos.Message.Send.newBuilder().apply { fromAddress = from - toAddress = "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms" + toAddress = "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" addAllAmounts(listOf(txAmount)) - typePrefix = "bank/MsgSend" }.build() val message = Cosmos.Message.newBuilder().apply { @@ -41,7 +40,7 @@ class TestTerraTransactions { }.build() val feeAmount = Cosmos.Amount.newBuilder().apply { - amount = "3000" + amount = "30000" denom = "uluna" }.build() @@ -51,39 +50,39 @@ class TestTerraTransactions { }.build() val signingInput = Cosmos.SigningInput.newBuilder().apply { - accountNumber = 158 - chainId = "soju-0013" + signingMode = SigningMode.Protobuf + accountNumber = 1037 + chainId = "phoenix-1" memo = "" - sequence = 0 + sequence = 1 fee = cosmosFee privateKey = ByteString.copyFrom(key.data()) addAllMessages(listOf(message)) }.build() - val output = AnySigner.sign(signingInput, TERRA, SigningOutput.parser()) + val output = AnySigner.sign(signingInput, TERRAV2, SigningOutput.parser()) val jsonPayload = output.json - val expectedJsonPayload = """{"mode":"block","tx":{"fee":{"amount":[{"amount":"3000","denom":"uluna"}],"gas":"200000"},"memo":"","msg":[{"type":"bank/MsgSend","value":{"amount":[{"amount":"1000000","denom":"uluna"}],"from_address":"terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe","to_address":"terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk"},"signature":"KPdiVsKpY12JG/VKEJVa/FpMKclxlS0qNNG6VOAypj10R5vY5UX5IgRJET1zNYnH0wvcXxfNXV+s8jtwN2UXiQ=="}]}}""" - assertEquals(expectedJsonPayload, jsonPayload) - + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpEBCo4BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm4KLHRlcnJhMWhzazZqcnl5cWpmaHA1ZGhjNTV0YzlqdGNreWd4MGVwMzdoZGQyEix0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcBoQCgV1bHVuYRIHMTAwMDAwMBJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYARIUCg4KBXVsdW5hEgUzMDAwMBDAmgwaQPh0C3rjzdixIUiyPx3FlWAxzbKILNAcSRVeQnaTl1vsI5DEfYa2oYlUBLqyilcMCcU/iaJLhex30No2ak0Zn1Q=\"}") + assertEquals(output.errorMessage, "") } @Test - fun testSigningWasmTerraTransferTxProtobuf() { + fun testSigningWasmTerraTransferTx() { val key = PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray()) val publicKey = key.getPublicKeySecp256k1(true) - val from = AnyAddress(publicKey, TERRA).description() + val from = AnyAddress(publicKey, TERRAV2).description() - val wasmTransferMessage = Cosmos.Message.WasmTerraExecuteContractTransfer.newBuilder().apply { + val wasmTransferMessage = Cosmos.Message.WasmExecuteContractTransfer.newBuilder().apply { senderAddress = from - contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" // ANC + contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" amount = ByteString.copyFrom("0x3D090".toHexByteArray()) // 250000 recipientAddress = "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" }.build() val message = Cosmos.Message.newBuilder().apply { - wasmTerraExecuteContractTransferMessage = wasmTransferMessage + wasmExecuteContractTransferMessage = wasmTransferMessage }.build() val feeAmount = Cosmos.Amount.newBuilder().apply { @@ -99,7 +98,7 @@ class TestTerraTransactions { val signingInput = Cosmos.SigningInput.newBuilder().apply { signingMode = SigningMode.Protobuf accountNumber = 3407705 - chainId = "columbus-5" + chainId = "phoenix-1" memo = "" sequence = 3 fee = cosmosFee @@ -107,103 +106,9 @@ class TestTerraTransactions { addAllMessages(listOf(message)) }.build() - val output = AnySigner.sign(signingInput, TERRA, SigningOutput.parser()) - - assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CucBCuQBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBK5AQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Glt7InRyYW5zZmVyIjp7ImFtb3VudCI6IjI1MDAwMCIsInJlY2lwaWVudCI6InRlcnJhMWpsZ2FxeTludm4yaGY1dDJzcmE5eWN6OHM3N3duZjlsMGttZ2NwIn19EmcKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNwZjrHsPmJKW/rXOWfukpQ1+lOHOJW3/IlFFnKLNmsABIECgIIARgDEhMKDQoFdWx1bmESBDMwMDAQwJoMGkAaprIEMLPH2HmFdwFGoaipb2GIyhXt6ombz+WMnG2mORBI6gFt0M+IymYgzZz6w1SW52R922yafDnn7yXfutRw\"}") - assertEquals(output.error, "") - } - - @Test - fun testSigningWasmTerraGenericProtobuf() { - val key = - PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray()) - val publicKey = key.getPublicKeySecp256k1(true) - val from = AnyAddress(publicKey, TERRA).description() - - val wasmGenericMessage = Cosmos.Message.WasmTerraExecuteContractGeneric.newBuilder().apply { - senderAddress = from - contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" // ANC - executeMsg = """{"transfer": { "amount": "250000", "recipient": "terra1d7048csap4wzcv5zm7z6tdqem2agyp9647vdyj" } }""" - }.build() - - val message = Cosmos.Message.newBuilder().apply { - wasmTerraExecuteContractGeneric = wasmGenericMessage - }.build() - - val feeAmount = Cosmos.Amount.newBuilder().apply { - amount = "3000" - denom = "uluna" - }.build() - - val cosmosFee = Cosmos.Fee.newBuilder().apply { - gas = 200000 - addAllAmounts(listOf(feeAmount)) - }.build() - - val signingInput = Cosmos.SigningInput.newBuilder().apply { - signingMode = SigningMode.Protobuf - accountNumber = 3407705 - chainId = "columbus-5" - memo = "" - sequence = 7 - fee = cosmosFee - privateKey = ByteString.copyFrom(key.data()) - addAllMessages(listOf(message)) - }.build() - - val output = AnySigner.sign(signingInput, TERRA, SigningOutput.parser()) - - assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Cu4BCusBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLAAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2GmJ7InRyYW5zZmVyIjogeyAiYW1vdW50IjogIjI1MDAwMCIsICJyZWNpcGllbnQiOiAidGVycmExZDcwNDhjc2FwNHd6Y3Y1em03ejZ0ZHFlbTJhZ3lwOTY0N3ZkeWoiIH0gfRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYBxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAkPsS7xlSng2LMc9KiD1soN5NLaDcUh8I9okPmsdJN3le1B7yxRGNB4aQfhaRl/8Z0r5vitRT0AWuxDasd8wcFw==\"}") - assertEquals(output.error, "") - } - - @Test - fun testSigningWasmTerraGenericWithCoinsProtobuf() { - val key = - PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray()) - val publicKey = key.getPublicKeySecp256k1(true) - val from = AnyAddress(publicKey, TERRA).description() - - val coins = Cosmos.Amount.newBuilder().apply { - amount = "1000" - denom = "uusd" - }.build() - - val wasmGenericMessage = Cosmos.Message.WasmTerraExecuteContractGeneric.newBuilder().apply { - senderAddress = from - contractAddress = "terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s" // ANC Market - executeMsg = """{ "deposit_stable": {} }""" - addCoins(coins) - }.build() - - val message = Cosmos.Message.newBuilder().apply { - wasmTerraExecuteContractGeneric = wasmGenericMessage - }.build() - - val feeAmount = Cosmos.Amount.newBuilder().apply { - amount = "7000" - denom = "uluna" - }.build() - - val cosmosFee = Cosmos.Fee.newBuilder().apply { - gas = 600000 - addAllAmounts(listOf(feeAmount)) - }.build() - - val signingInput = Cosmos.SigningInput.newBuilder().apply { - signingMode = SigningMode.Protobuf - accountNumber = 3407705 - chainId = "columbus-5" - memo = "" - sequence = 9 - fee = cosmosFee - privateKey = ByteString.copyFrom(key.data()) - addAllMessages(listOf(message)) - }.build() - - val output = AnySigner.sign(signingInput, TERRA, SigningOutput.parser()) + val output = AnySigner.sign(signingInput, TERRAV2, SigningOutput.parser()) - assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CrIBCq8BCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBKEAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMXNlcGZqN3MwYWVnNTk2N3V4bmZrNHRoemxlcnJza3RrcGVsbTVzGhh7ICJkZXBvc2l0X3N0YWJsZSI6IHt9IH0qDAoEdXVzZBIEMTAwMBJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYCRITCg0KBXVsdW5hEgQ3MDAwEMDPJBpAGyi7f1ioY8XV6pjFq1s86Om4++CIUnd3rLHif2iopCcYvX0mLkTlQ6NUERg8nWTYgXcj6fOTO/ptgPuAtv0NWg==\"}") - assertEquals(output.error, "") + assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CuUBCuIBCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QSuQEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTE0ejU2bDBmcDJsc2Y4Nnp5M2h0eTJ6NDdlemtobnRodHI5eXE3NhpbeyJ0cmFuc2ZlciI6eyJhbW91bnQiOiIyNTAwMDAiLCJyZWNpcGllbnQiOiJ0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcCJ9fRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYAxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAiBGbQaj+jsXE6/FssD3fC77QOxpli9GqsPea+KoNyMIEgVj89Hii+oU1bAEQS4qV0SaE2V6RNy24uCcFTIRbcQ==\"}") + assertEquals(output.errorMessage, "") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tezos/TestTezosMessageSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tezos/TestTezosMessageSigner.kt new file mode 100644 index 00000000000..57b06f83714 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tezos/TestTezosMessageSigner.kt @@ -0,0 +1,42 @@ +package com.trustwallet.core.app.blockchains.tezos + +import com.trustwallet.core.app.utils.Numeric +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import wallet.core.jni.CoinType +import wallet.core.jni.TezosMessageSigner +import wallet.core.jni.PrivateKey +import java.util.regex.Pattern + +class TestTezosMessageSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testMessageSignerSignAndVerify() { + val data = Numeric.hexStringToByteArray("91b4fb8d7348db2e7de2693f58ce1cceb966fa960739adac1d9dba2cbaa0940a") + val privateKey = PrivateKey(data) + val msg = "05010000004254657a6f73205369676e6564204d6573736167653a207465737455726c20323032332d30322d30385431303a33363a31382e3435345a2048656c6c6f20576f726c64" + val signature = TezosMessageSigner.signMessage(privateKey, msg) + assertEquals("edsigu3se2fcEJUCm1aqxjzbHdf7Wsugr4mLaA9YM2UVZ9Yy5meGv87VqHN3mmDeRwApTj1JKDaYjqmLZifSFdWCqBoghqaowwJ", signature) + val pubKey = privateKey.getPublicKey(CoinType.TEZOS) + assertTrue(TezosMessageSigner.verifyMessage(pubKey, msg, signature)) + } + + @Test + fun testMessageSignerInputToPayload() { + val payload = TezosMessageSigner.inputToPayload("Tezos Signed Message: testUrl 2023-02-08T10:36:18.454Z Hello World") + val expected = "05010000004254657a6f73205369676e6564204d6573736167653a207465737455726c20323032332d30322d30385431303a33363a31382e3435345a2048656c6c6f20576f726c64" + assertEquals(expected, payload) + } + + @Test + fun testMessageSignerFormatMessage() { + val formatedMsg = TezosMessageSigner.formatMessage("Hello World", "testUrl") + val regex = Pattern.compile("Tezos Signed Message: \\S+ \\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z .+") + assertTrue(regex.matcher(formatedMsg).matches()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tezos/TestTezosSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tezos/TestTezosSigner.kt index f84b3edb766..f14a6a6c689 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tezos/TestTezosSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tezos/TestTezosSigner.kt @@ -1,10 +1,13 @@ -package com.trustwallet.core.app.blockchains.waves +package com.trustwallet.core.app.blockchains.tezos +import com.trustwallet.core.app.utils.Numeric import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytesInByteString import org.junit.Assert.* import org.junit.Test import wallet.core.jni.CoinType.TEZOS import wallet.core.java.AnySigner +import wallet.core.jni.proto.Tezos.* class TestTezosTransactionSigner { @@ -12,6 +15,115 @@ class TestTezosTransactionSigner { System.loadLibrary("TrustWalletCore") } + @Test + fun testSigningFA2() { + val key = + "363265a0b3f06661001cab8b4f3ca8fd97ae70608184979cf7300836f57ec2d6".toHexBytesInByteString() + + val transferInfos = Txs.newBuilder() + .setAmount("10") + .setTokenId("0") + .setTo("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP") + .build() + + val txObj = TxObject.newBuilder() + .setFrom("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP") + .addTxs(transferInfos) + .build() + + val fa12 = FA2Parameters.newBuilder() + .setEntrypoint("transfer") + .addTxsObject(txObj) + .build() + + val parameters = OperationParameters.newBuilder() + .setFa2Parameters(fa12) + .build() + + val transactionData = TransactionOperationData.newBuilder() + .setAmount(0) + .setDestination("KT1DYk1XDzHredJq1EyNkDindiWDqZyekXGj") + .setParameters(parameters) + .build() + + val transaction = Operation.newBuilder() + .setSource("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP") + .setFee(100000) + .setCounter(2993173) + .setGasLimit(100000) + .setStorageLimit(0) + .setKind(Operation.OperationKind.TRANSACTION) + .setTransactionOperationData(transactionData) + .build(); + + val operationList = OperationList.newBuilder() + .setBranch("BKvEAX9HXfJZWYfTQbR1C7B3ADoKY6a1aKVRF7qQqvc9hS8Rr3m") + .addOperations(transaction) + .build(); + + val signingInput = SigningInput.newBuilder() + .setPrivateKey(key) + .setOperationList(operationList) + .build() + + val result = AnySigner.sign(signingInput, TEZOS, SigningOutput.parser()) + + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.encoded.toByteArray())), + "1b1f9345dc9f77bd24b09034d1d2f9a28f02ac837f49db54b8d68341f53dc4b76c00fe2ce0cccc0214af521ad60c140c5589b4039247a08d0695d8b601a08d0600000136767f88850bae28bfb9f46b73c5e87ede4de12700ffff087472616e7366657200000066020000006107070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b5550020000003107070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070000000a552d24710d6c59383286700c6c2917b25a6c1fa8b587e593c289dd47704278796792f1e522c1623845ec991e292b0935445e6994850bd03f035a006c5ed93806" + ) + } + + @Test + fun testSigningFA12() { + val key = + "363265a0b3f06661001cab8b4f3ca8fd97ae70608184979cf7300836f57ec2d6".toHexBytesInByteString() + + val fa12 = FA12Parameters.newBuilder() + .setEntrypoint("transfer") + .setFrom("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP") + .setTo("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP") + .setValue("123") + .build() + + val parameters = OperationParameters.newBuilder() + .setFa12Parameters(fa12) + .build() + + val transactionData = TransactionOperationData.newBuilder() + .setAmount(0) + .setDestination("KT1EwXFWoG9bYebmF4pYw72aGjwEnBWefgW5") + .setParameters(parameters) + .build() + + val transaction = Operation.newBuilder() + .setSource("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP") + .setFee(100000) + .setCounter(2993172) + .setGasLimit(100000) + .setStorageLimit(0) + .setKind(Operation.OperationKind.TRANSACTION) + .setTransactionOperationData(transactionData) + .build(); + + val operationList = OperationList.newBuilder() + .setBranch("BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp") + .addOperations(transaction) + .build(); + + val signingInput = SigningInput.newBuilder() + .setPrivateKey(key) + .setOperationList(operationList) + .build() + + val result = AnySigner.sign(signingInput, TEZOS, SigningOutput.parser()) + + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.encoded.toByteArray())), + "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016c00fe2ce0cccc0214af521ad60c140c5589b4039247a08d0694d8b601a08d0600000145bd8a65cc48159d8ea60a55df735b7c5ad45f0e00ffff087472616e736665720000005907070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555000bb012914d768155fba2df319a81136e8e3e573b9cadb1676834490c90212615d271da029b6b0531e290e9063bcdb40bea43627af048b18e036f02be2b6b22fc8b307" + ) + } + @Test fun testSigningJSON() { val json = """ @@ -43,9 +155,13 @@ class TestTezosTransactionSigner { } } """ - val key = "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f".toHexByteArray() + val key = + "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f".toHexByteArray() val result = AnySigner.signJSON(json, key, TEZOS.value()) assertTrue(AnySigner.supportsJSON(TEZOS.value())) - assertEquals(result, "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200429a986c8072a40a1f3a3e2ab5a5819bb1b2fb69993c5004837815b9dc55923e6c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80993f001f44e810201000081faa75f741ef614b0e35fcc8c90dfa3b0b957210001b86398d5b9be737dca8e4106ea18d70e69b75e92f892fb283546a99152b8d7794b919c0fbf1c31de386069a60014491c0e7505adef5781cead1cfe6608030b") + assertEquals( + result, + "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200429a986c8072a40a1f3a3e2ab5a5819bb1b2fb69993c5004837815b9dc55923e6c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80993f001f44e810201000081faa75f741ef614b0e35fcc8c90dfa3b0b957210001b86398d5b9be737dca8e4106ea18d70e69b75e92f892fb283546a99152b8d7794b919c0fbf1c31de386069a60014491c0e7505adef5781cead1cfe6608030b" + ) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkAddress.kt new file mode 100644 index 00000000000..f37d4c7e4e9 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkAddress.kt @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.theopennetwork + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestTheOpenNetworkAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddressFromPrivateKey() { + val privateKey = PrivateKey("63474e5fe9511f1526a50567ce142befc343e71a49b865ac3908f58667319cb8".toHexByteArray()) + val publicKey = privateKey.getPublicKeyEd25519() + val address = AnyAddress(publicKey, CoinType.TON) + assertEquals(publicKey.data().toHex(), "0xf42c77f931bea20ec5d0150731276bbb2e2860947661245b2319ef8133ee8d41") + assertEquals(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") + } + + @Test + fun testAddressFromPublicKey() { + val publicKey = PublicKey("f42c77f931bea20ec5d0150731276bbb2e2860947661245b2319ef8133ee8d41".toHexByteArray(), PublicKeyType.ED25519) + val address = AnyAddress(publicKey, CoinType.TON) + assertEquals(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") + } + + @Test + fun testAddressFromRawString() { + val addressString = "0:66fbe3c5c03bf5c82792f904c9f8bf28894a6aa3d213d41c20569b654aadedb3" + val address = AnyAddress(addressString, CoinType.TON) + assertEquals(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") + } + + @Test + fun testAddressFromBounceableString() { + val addressString = "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q" + val address = AnyAddress(addressString, CoinType.TON) + assertEquals(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") + } + + @Test + fun testAddressFromUserFriendlyString() { + val addressString = "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV" + val address = AnyAddress(addressString, CoinType.TON) + assertEquals(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") + } + + @Test + fun testAddressToBounceable() { + val addressString = "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV" + val bounceable = true + val testnet = false + val address = TONAddressConverter.toUserFriendly(addressString, bounceable, testnet) + assertEquals(address, "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q") + } + + @Test + fun testGenerateJettonAddress() { + val mainAddress = "UQBjKqthWBE6GEcqb_epTRFrQ1niS6Z1Z1MHMwR-mnAYRoYr" + val mainAddressBoc = TONAddressConverter.toBoc(mainAddress) + assertEquals(mainAddressBoc, "te6cckEBAQEAJAAAQ4AMZVVsKwInQwjlTf71KaItaGs8SXTOrOpg5mCP004DCNAptHQU") + + // curl --location 'https://toncenter.com/api/v2/runGetMethod' --header 'Content-Type: application/json' --data \ + // '{"address":"EQAvlWFDxGF2lXm67y4yzC17wYKD9A0guwPkMs1gOsM__NOT","method":"get_wallet_address","method":"get_wallet_address","stack":[["tvm.Slice","te6ccgICAAEAAQAAACQAAABDgAxlVWwrAidDCOVN/vUpoi1oazxJdM6s6mDmYI/TTgMI0A=="]]}' + + // Parse the `get_wallet_address` RPC response. + val jettonAddressBocEncoded = "te6cckEBAQEAJAAAQ4AFvT5rqwxcbKfITqnkwL+go4Zi9bulRHAtLt4cjjFdK7B8L+Cq" + val jettonAddress = TONAddressConverter.fromBoc(jettonAddressBocEncoded) + assertEquals(jettonAddress, "UQAt6fNdWGLjZT5CdU8mBf0FHDMXrd0qI4FpdvDkcYrpXV5H") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkMessageSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkMessageSigner.kt new file mode 100644 index 00000000000..64c4627cb97 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkMessageSigner.kt @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.theopennetwork + +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.PrivateKey +import wallet.core.jni.TONMessageSigner +import wallet.core.jni.TONWallet + +class TestTheOpenNetworkMessageSigner { + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun TheOpenNetworkMessageSignerSignMessage() { + // The private key has been derived by using [ton-mnemonic](https://www.npmjs.com/package/tonweb-mnemonic/v/0.0.2) + // from the following mnemonic: + // document shield addict crime broom point story depend suit satisfy test chicken valid tail speak fortune sound drill seek cube cheap body music recipe + val privateKey = PrivateKey("112d4e2e700a468f1eae699329202f1ee671d6b665caa2d92dea038cf3868c18".toHexByteArray()) + val message = "Hello world" + val signature = TONMessageSigner.signMessage(privateKey, message) + // The following signature has been computed by calling `window.ton.send("ton_personalSign", { data: "Hello world" });`. + assertEquals(signature, "2490fbaa72aec0b77b19162bbbe0b0e3f7afd42cc9ef469f0494cd4a366a4bf76643300cd5991f66bce6006336742b8d1d435d541d244dcc013d428472e89504") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkSigner.kt new file mode 100644 index 00000000000..59b3fcdd465 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkSigner.kt @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.theopennetwork + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType +import wallet.core.jni.PrivateKey +import wallet.core.jni.proto.TheOpenNetwork +import wallet.core.jni.proto.TheOpenNetwork.SigningOutput + +class TestTheOpenNetworkSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun TheOpenNetworkTransactionSigning() { + val privateKey = PrivateKey("c38f49de2fb13223a9e7d37d5d0ffbdd89a5eb7c8b0ee4d1c299f2cefe7dc4a0".toHexByteArray()) + + val transfer = TheOpenNetwork.Transfer.newBuilder() + .setDest("EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q") + .setAmount(10) + .setMode(TheOpenNetwork.SendMode.PAY_FEES_SEPARATELY_VALUE or TheOpenNetwork.SendMode.IGNORE_ACTION_PHASE_ERRORS_VALUE) + .setBounceable(true) + .build() + + val input = TheOpenNetwork.SigningInput.newBuilder() + .setPrivateKey(ByteString.copyFrom(privateKey.data())) + .addMessages(transfer) + .setSequenceNumber(6) + .setExpireAt(1671132440) + .setWalletVersion(TheOpenNetwork.WalletVersion.WALLET_V4_R2) + .build() + + val output = AnySigner.sign(input, CoinType.TON, SigningOutput.parser()) + + // tx: https://tonscan.org/tx/3Z4tHpXNLyprecgu5aTQHWtY7dpHXEoo11MAX61Xyg0= + val expectedString = "te6cckEBBAEArQABRYgBsLf6vJOEq42xW0AoyWX0K+uBMUcXFDLFqmkDg6k1Io4MAQGcEUPkil2aZ4s8KKparSep/OKHMC8vuXafFbW2HGp/9AcTRv0J5T4dwyW1G0JpHw+g5Ov6QI3Xo0O9RFr3KidICimpoxdjm3UYAAAABgADAgFiYgAzffHi4B365BPJfIJk/F+URKU1UekJ6g4QK02ypVb22YhQAAAAAAAAAAAAAAAAAQMAAA08Nzs=" + + assertEquals(output.encoded, expectedString) + } + + @Test + fun TheOpenNetworkJettonTransferSigning() { + val privateKey = PrivateKey("c054900a527538c1b4325688a421c0469b171c29f23a62da216e90b0df2412ee".toHexByteArray()) + + val jettonTransfer = TheOpenNetwork.JettonTransfer.newBuilder() + .setJettonAmount(500 * 1000 * 1000) + .setToOwner("EQAFwMs5ha8OgZ9M4hQr80z9NkE7rGxUpE1hCFndiY6JnDx8") + .setResponseAddress("EQBaKIMq5Am2p_rfR1IFTwsNWHxBkOpLTmwUain5Fj4llTXk") + .setForwardAmount(1) + .build() + + val transfer = TheOpenNetwork.Transfer.newBuilder() + .setDest("EQBiaD8PO1NwfbxSkwbcNT9rXDjqhiIvXWymNO-edV0H5lja") + .setAmount(100 * 1000 * 1000) + .setMode(TheOpenNetwork.SendMode.PAY_FEES_SEPARATELY_VALUE or TheOpenNetwork.SendMode.IGNORE_ACTION_PHASE_ERRORS_VALUE) + .setComment("test comment") + .setBounceable(true) + .setJettonTransfer(jettonTransfer) + + val input = TheOpenNetwork.SigningInput.newBuilder() + .setPrivateKey(ByteString.copyFrom(privateKey.data())) + .addMessages(transfer) + .setSequenceNumber(1) + .setExpireAt(1787693046) + .setWalletVersion(TheOpenNetwork.WalletVersion.WALLET_V4_R2) + .build() + + val output = AnySigner.sign(input, CoinType.TON, SigningOutput.parser()) + + // tx: https://testnet.tonscan.org/tx/Er_oT5R3QK7D-qVPBKUGkJAOOq6ayVls-mgEphpI9Ck= + val expectedString = "te6cckECBAEAARUAAUWIALRRBlXIE21P9b6OpAqeFhqw+IMh1Jac2CjUU/IsfEsqDAEBnGiFlaLItV573gJqBvctP5j3jVKlLuxmO+pnW0QGlXjXgzjw5YeTNwRG9upJHOl6GA3pFetKNojqGzfkxku+owUpqaMXao4H9gAAAAEAAwIBaGIAMTQfh52puD7eKUmDbhqfta4cdUMRF662Uxp3zzqug/MgL68IAAAAAAAAAAAAAAAAAAEDAMoPin6lAAAAAAAAAABB3NZQCAALgZZzC14dAz6ZxChX5pn6bIJ3WNipSJrCELO7Ex0TOQAWiiDKuQJtqf630dSBU8LDVh8QZDqS05sFGop+RY+JZUICAAAAAHRlc3QgY29tbWVudG/bd5c=" + + assertEquals(output.encoded, expectedString) + } + + @Test + fun TheOpenNetworkTransferCustomPayload() { + val privateKey = PrivateKey("5525e673087587bc0efd7ab09920ef7d3c1bf6b854a661430244ca59ab19e9d1".toHexByteArray()) + + // Doge chatbot contract payload to be deployed. + // Docs: https://docs.ton.org/develop/dapps/ton-connect/transactions#smart-contract-deployment + val dogeChatbotStateInit = "te6cckEBBAEAUwACATQBAgEU/wD0pBP0vPLICwMAEAAAAZDrkbgQAGrTMAGCCGlJILmRMODQ0wMx+kAwi0ZG9nZYcCCAGMjLBVAEzxaARfoCE8tqEssfAc8WyXP7AO4ioYU=" + // Doge chatbot's address after the contract is deployed. + val dogeChatbotDeployingAddress = "0:3042cd5480da232d5ac1d9cbe324e3c9eb58f167599f6b7c20c6e638aeed0335" + + // The comment has nothing to do with Doge chatbot. + // It's just used to attach the following ASCII comment to the transaction: + // "This transaction deploys Doge Chatbot contract" + val commentPayload = "te6cckEBAQEANAAAZAAAAABUaGlzIHRyYW5zYWN0aW9uIGRlcGxveXMgRG9nZSBDaGF0Ym90IGNvbnRyYWN0v84vSg==" + + val customPayload = TheOpenNetwork.CustomPayload.newBuilder() + .setStateInit(dogeChatbotStateInit) + .setPayload(commentPayload) + .build() + + val transfer = TheOpenNetwork.Transfer.newBuilder() + .setDest(dogeChatbotDeployingAddress) + // 0.069 TON + .setAmount(69_000_000) + .setMode(TheOpenNetwork.SendMode.PAY_FEES_SEPARATELY_VALUE or TheOpenNetwork.SendMode.IGNORE_ACTION_PHASE_ERRORS_VALUE) + .setBounceable(false) + .setCustomPayload(customPayload) + + val input = TheOpenNetwork.SigningInput.newBuilder() + .setPrivateKey(ByteString.copyFrom(privateKey.data())) + .addMessages(transfer) + .setSequenceNumber(4) + .setExpireAt(1721939714) + .setWalletVersion(TheOpenNetwork.WalletVersion.WALLET_V4_R2) + .build() + + val output = AnySigner.sign(input, CoinType.TON, SigningOutput.parser()) + + // Successfully broadcasted: https://tonviewer.com/transaction/f4b7ed2247b1adf54f33dd2fd99216fbd61beefb281542d0b330ccea9b8d0338 + val expectedString = "te6cckECCAEAATcAAUWIAfq4NsPLegfou/MPhtHE9YuzV3gnI/q6jm3MRJh2PtpaDAEBnPbyCSsWrOZpEjb7ZFxz5yYi+an6M6Lnq7rI7TFWdDS76LEtGBrVVrhMGziwxuy6LCVtsMBikI7RPVQ89FCIAAYpqaMXZqK3AgAAAAQAAwICaUIAGCFmqkBtEZatYOzl8ZJx5PWseLOsz7W+EGNzHFd2gZqgIObaAAAAAAAAAAAAAAAAAAPAAwQCATQFBgBkAAAAAFRoaXMgdHJhbnNhY3Rpb24gZGVwbG95cyBEb2dlIENoYXRib3QgY29udHJhY3QBFP8A9KQT9LzyyAsHABAAAAGQ65G4EABq0zABgghpSSC5kTDg0NMDMfpAMItGRvZ2WHAggBjIywVQBM8WgEX6AhPLahLLHwHPFslz+wAa2r/S" + + assertEquals(output.encoded, expectedString) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkWallet.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkWallet.kt new file mode 100644 index 00000000000..9305072bb75 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkWallet.kt @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.theopennetwork + +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.PublicKey +import wallet.core.jni.PublicKeyType +import wallet.core.jni.TONWallet + +class TestTheOpenNetworkWallet { + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun TheOpenNetworkWalletBuildV4R2StateInit() { + val publicKey = PublicKey("f229a9371fa7c2108b3d90ea22c9be705ff5d0cfeaee9cbb9366ff0171579357".toHexByteArray(), PublicKeyType.ED25519) + val baseWorkchain = 0 + val defaultWalletId = 0x29a9a317 + val stateInit = TONWallet.buildV4R2StateInit(publicKey, baseWorkchain, defaultWalletId) + + val expected = "te6cckECFgEAAwQAAgE0AQIBFP8A9KQT9LzyyAsDAFEAAAAAKamjF/IpqTcfp8IQiz2Q6iLJvnBf9dDP6u6cu5Nm/wFxV5NXQAIBIAQFAgFIBgcE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8ICQoLAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNDA0CASAODwBu0gf6ANTUIvkABcjKBxXL/8nQd3SAGMjLBcsCIs8WUAX6AhTLaxLMzMlz+wDIQBSBAQj0UfKnAgBwgQEI1xj6ANM/yFQgR4EBCPRR8qeCEG5vdGVwdIAYyMsFywJQBs8WUAT6AhTLahLLH8s/yXP7AAIAbIEBCNcY+gDTPzBSJIEBCPRZ8qeCEGRzdHJwdIAYyMsFywJQBc8WUAP6AhPLassfEss/yXP7AAAK9ADJ7VQAeAH6APQEMPgnbyIwUAqhIb7y4FCCEHBsdWeDHrFwgBhQBMsFJs8WWPoCGfQAy2kXyx9SYMs/IMmAQPsABgCKUASBAQj0WTDtRNCBAUDXIMgBzxb0AMntVAFysI4jghBkc3Rygx6xcIAYUAXLBVADzxYj+gITy2rLH8s/yYBA+wCSXwPiAgEgEBEAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAIBWBITABG4yX7UTQ1wsfgAPbKd+1E0IEBQNch9AQwAsjKB8v/ydABgQEI9ApvoTGACASAUFQAZrc52omhAIGuQ64X/wAAZrx32omhAEGuQ64WPwEXtMkg=" + assertEquals(stateInit, expected) + } +} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thetafuel/TestThetaFuelAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thetafuel/TestThetaFuelAddress.kt new file mode 100644 index 00000000000..17e60b89aeb --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thetafuel/TestThetaFuelAddress.kt @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.thetafuel + +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestThetaFuelAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("4646464646464646464646464646464646464646464646464646464646464646".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(false) + val address = AnyAddress(pubkey, CoinType.THETAFUEL) + val expected = AnyAddress("0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", CoinType.THETAFUEL) + + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainAddress.kt index 8a1c5768d90..0974995d3f1 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainAddress.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.thorchain diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt index 2314586d13e..fde6325529f 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.thorchain @@ -74,7 +72,7 @@ class TestTHORChainSigner { val output = AnySigner.sign(signingInput, THORCHAIN, SigningOutput.parser()) assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"ClIKUAoOL3R5cGVzLk1zZ1NlbmQSPgoUFSLnZ9tusZcIsAOAKb+9YHvJvQ4SFMqGRZ+wBVHH30JUDF54aRksgzrbGhAKBHJ1bmUSCDM4MDAwMDAwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQPtmX45bPQpL1/OWkK7pBWZzNXZbjExVKfJ6nBJ3jF8dxIECgIIARgVEhIKCwoEcnVuZRIDMjAwEKDLmAEaQKZtS3ATa26OOGvqdKm14ZbHeNfkPtIajXi5MkZ5XaX2SWOeX+YnCPZ9TxF9Jj5cVIo71m55xq4hVL3yDbRe89g=\"}") - assertEquals(output.error, "") + assertEquals(output.errorMessage, "") assertEquals(output.json, "") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORSwapSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORSwapSigning.kt index 2d1d3caf77e..68f0f8acc55 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORSwapSigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORSwapSigning.kt @@ -20,11 +20,13 @@ class TestTHORChainSwap { } @Test - fun testSwapEthBnb() { + fun testSwapEthBnbWithFee() { // prepare swap input val input = THORChainSwap.SwapInput.newBuilder() input.apply { - fromChain = THORChainSwap.Chain.ETH + fromAsset = THORChainSwap.Asset.newBuilder().apply { + chain = THORChainSwap.Chain.ETH + }.build() fromAddress = "0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7" toAsset = THORChainSwap.Asset.newBuilder().apply { chain = THORChainSwap.Chain.BNB @@ -36,15 +38,17 @@ class TestTHORChainSwap { routerAddress = "0x42A5Ed456650a09Dc10EBc6361A7480fDd61f27B" fromAmount = "50000000000000000" toAmountLimit = "600003" + affiliateFeeAddress = "tthor1ql2tcqyrqsgnql2tcqyj2n8kfdmt9lh0yzql2tcqy" + affiliateFeeRateBp = "10" } // serialize input val inputSerialized = input.build().toByteArray() - assertEquals(Numeric.toHexString(inputSerialized), "0x0802122a3078623966353737316332373636346266323238326439386530396437663530636563376362303161371a0708031203424e42222a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372782a2a307831303931633444653661336346303943644130304162444165443432633763334236394338334543322a3078343241354564343536363530613039446331304542633633363141373438306644643631663237423a1135303030303030303030303030303030304206363030303033") + assertEquals(Numeric.toHexString(inputSerialized), "0x0a020802122a3078623966353737316332373636346266323238326439386530396437663530636563376362303161371a0708031203424e42222a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372782a2a307831303931633444653661336346303943644130304162444165443432633763334236394338334543322a3078343241354564343536363530613039446331304542633633363141373438306644643631663237423a11353030303030303030303030303030303042063630303030334a2f7474686f7231716c3274637179727173676e716c32746371796a326e386b66646d74396c6830797a716c327463717952023130") // invoke swap val outputData = buildSwap(inputSerialized) - assertEquals(outputData.count(), 311) + assertEquals(outputData.count(), 192) // parse result in proto val outputProto = THORChainSwap.SwapOutput.newBuilder().mergeFrom(outputData) @@ -66,6 +70,6 @@ class TestTHORChainSwap { // sign and encode resulting input val output = AnySigner.sign(txInputFull, ETHEREUM, SigningOutput.parser()) - assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0xf90151038506fc23ac00830138809442a5ed456650a09dc10ebc6361a7480fdd61f27b87b1a2bc2ec50000b8e41fece7b40000000000000000000000001091c4de6a3cf09cda00abdaed42c7c3b69c83ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000003e535741503a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a363030303033000025a06ae104be3201baca38315352f81fac70ca4dd47339981914e64e91149813e780a066a3f0b2c44ddf5a96a38481274f623f552a593d723237d6742185f4885c0064") + assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x02f8d90103808083013880941091c4de6a3cf09cda00abdaed42c7c3b69c83ec87b1a2bc2ec50000b86e3d3a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a3630303030333a7474686f7231716c3274637179727173676e716c32746371796a326e386b66646d74396c6830797a716c32746371793a3130c001a05c16871b66fd0fa8f658d6f171310bab332d09e0533d6c97329a59ddc93a9a11a05ed2be94e6dbb640e58920c8be4fa597cd5f0a918123245acb899042dd43777f") } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronMessageSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronMessageSigner.kt new file mode 100644 index 00000000000..23cfb2302d3 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronMessageSigner.kt @@ -0,0 +1,27 @@ +package com.trustwallet.core.app.blockchains.tron + +import com.trustwallet.core.app.utils.Numeric +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import wallet.core.jni.CoinType +import wallet.core.jni.TronMessageSigner +import wallet.core.jni.PrivateKey + +class TestTronMessageSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testMessageSignerSignAndVerify() { + val data = Numeric.hexStringToByteArray("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a") + val privateKey = PrivateKey(data) + val msg = "Hello World" + val signature = TronMessageSigner.signMessage(privateKey, msg) + assertEquals("9bb6d11ec8a6a3fb686a8f55b123e7ec4e9746a26157f6f9e854dd72f5683b450397a7b0a9653865658de8f9243f877539882891bad30c7286c3bf5622b900471b", signature) + val pubKey = privateKey.getPublicKey(CoinType.TRON) + assertTrue(TronMessageSigner.verifyMessage(pubKey, msg, signature)) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronTransactionSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronTransactionSigner.kt index 254f79bb860..295416c5487 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronTransactionSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronTransactionSigner.kt @@ -17,6 +17,18 @@ class TestTronTransactionSigner { System.loadLibrary("TrustWalletCore") } + @Test + fun testSignDirect() { + val signingInput = Tron.SigningInput.newBuilder() + .setTxId("546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb") + .setPrivateKey(ByteString.copyFrom("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54".toHexByteArray())) + + val output = AnySigner.sign(signingInput.build(), TRON, Tron.SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.id.toByteArray()), "0x546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb") + assertEquals(Numeric.toHexString(output.signature.toByteArray()), "0x77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00") + } + @Test fun testSignTransferTrc20Contract() { val trc20Contract = Tron.TransferTRC20Contract.newBuilder() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zen/TestZenAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zen/TestZenAddress.kt new file mode 100644 index 00000000000..8398b636928 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zen/TestZenAddress.kt @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.zen + +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.* + +class TestZenAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + val key = PrivateKey("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a".toHexByteArray()) + val pubkey = key.getPublicKeySecp256k1(true) + val address = AnyAddress(pubkey, CoinType.ZEN) + val expected = AnyAddress("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg", CoinType.ZEN) + + assertEquals(pubkey.data().toHex(), "0x02b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfe") + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zen/TestZenSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zen/TestZenSigner.kt new file mode 100644 index 00000000000..36682ac0293 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zen/TestZenSigner.kt @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.zen + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexByteArray +import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test + +import wallet.core.java.AnySigner +import wallet.core.jni.BitcoinScript +import wallet.core.jni.BitcoinSigHashType +import wallet.core.jni.CoinType +import wallet.core.jni.proto.Bitcoin +import wallet.core.jni.proto.Common.SigningError + +class TestZenSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun ZenTransactionSigning() { + // prepare SigningInput + val input = Bitcoin.SigningInput.newBuilder() + .setHashType(BitcoinScript.hashTypeForCoin(CoinType.ZEN)) + .setAmount(10000) + .setByteFee(1) + .setToAddress("zngBGZGKaaBamofSuFw5WMnvU2HQAtwGeb5") + .setChangeAddress("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg") + .setCoinType(CoinType.ZEN.value()) + + val utxoKey0 = + (Numeric.hexStringToByteArray("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")) + input.addPrivateKey(ByteString.copyFrom(utxoKey0)) + + // build utxo + val txHash0 = (Numeric.hexStringToByteArray("a39e13b5ab406547e31284cd96fb40ed271813939c195ae7a86cd67fb8a4de62")) + val outpoint0 = Bitcoin.OutPoint.newBuilder() + .setHash(ByteString.copyFrom(txHash0)) + .setIndex(0) + .setSequence(Long.MAX_VALUE.toInt()) + .build() + + val utxo0 = Bitcoin.UnspentTransaction.newBuilder() + .setAmount(17600) + .setOutPoint(outpoint0) + .setScript(ByteString.copyFrom("76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4".toHexBytes())) + .build() + + input.addUtxo(utxo0) + + val plan = AnySigner.plan(input.build(), CoinType.ZEN, Bitcoin.TransactionPlan.parser()) + + input.plan = Bitcoin.TransactionPlan.newBuilder() + .mergeFrom(plan) + .setPreblockhash(ByteString.copyFrom("81dc725fd33fada1062323802eefb54d3325d924d4297a692214560400000000".toHexBytes())) + .setPreblockheight(1147624) + .build() + + + val output = AnySigner.sign(input.build(), CoinType.ZEN, Bitcoin.SigningOutput.parser()) + + assertEquals(output.error, SigningError.OK) + + val encoded = output.encoded + assertEquals("0x0100000001a39e13b5ab406547e31284cd96fb40ed271813939c195ae7a86cd67fb8a4de62000000006a473044022014d687c0bee0b7b584db2eecbbf73b545ee255c42b8edf0944665df3fa882cfe02203bce2412d93c5a56cb4806ddd8297ff05f8fc121306e870bae33377a58a02f05012102b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfeffffffff0210270000000000003f76a914a58d22659b1082d1fa8698fc51996b43281bfce788ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4ce1c0000000000003f76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b400000000", + Numeric.toHexString(encoded.toByteArray())); + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestAnyAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestAnyAddress.kt new file mode 100644 index 00000000000..ae4b47b4bd0 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestAnyAddress.kt @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.utils + +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHex +import com.trustwallet.core.app.utils.toHexByteArray +import wallet.core.jni.* +import java.security.InvalidParameterException +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Assert.fail +import org.junit.Test + +class TestAnyAddress { + init { + System.loadLibrary("TrustWalletCore"); + } + + val any_address_test_address = "bc1qcj2vfjec3c3luf9fx9vddnglhh9gawmncmgxhz" + val any_address_test_pubkey = "02753f5c275e1847ba4d2fd3df36ad00af2e165650b35fe3991e9c9c46f68b12bc" + + @Test + fun testCreateWithString() { + val coin = CoinType.BITCOIN + val address = AnyAddress(any_address_test_address, coin) + assertEquals(address.coin(), coin) + assertEquals(address.description(), any_address_test_address) + } + + @Test + fun testCreateWithStringBech32() { + val coin = CoinType.BITCOIN + val address1 = AnyAddress(any_address_test_address, coin, "bc") + assertEquals(address1.description(), any_address_test_address) + + val address2 = AnyAddress("tb1qcj2vfjec3c3luf9fx9vddnglhh9gawmnjan4v3", coin, "tb") + assertEquals(address2.description(), "tb1qcj2vfjec3c3luf9fx9vddnglhh9gawmnjan4v3") + } + + @Test + fun testCreateWithPublicKey() { + val coin = CoinType.BITCOIN + val pubkey = PublicKey(any_address_test_pubkey.toHexByteArray(), PublicKeyType.SECP256K1) + val address = AnyAddress(pubkey, coin) + assertEquals(address.description(), any_address_test_address) + } + + @Test + fun testCreateWithPublicKeyDerivation() { + val coin = CoinType.BITCOIN + val pubkey = PublicKey(any_address_test_pubkey.toHexByteArray(), PublicKeyType.SECP256K1) + val address1 = AnyAddress(pubkey, coin, Derivation.BITCOINSEGWIT) + assertEquals(address1.description(), any_address_test_address) + + val address2 = AnyAddress(pubkey, coin, Derivation.BITCOINLEGACY) + assertEquals(address2.description(), "1JvRfEQFv5q5qy9uTSAezH7kVQf4hqnHXx") + } + + @Test + fun testCreateBech32WithPublicKey() { + val coin = CoinType.BITCOIN + val pubkey = PublicKey(any_address_test_pubkey.toHexByteArray(), PublicKeyType.SECP256K1) + val address1 = AnyAddress(pubkey, coin, "bc") + assertEquals(address1.description(), any_address_test_address) + + val address2 = AnyAddress(pubkey, coin, "tb") + assertEquals(address2.description(), "tb1qcj2vfjec3c3luf9fx9vddnglhh9gawmnjan4v3") + } + + @Test + fun testIsValid() { + val coin = CoinType.BITCOIN + assertTrue(AnyAddress.isValid(any_address_test_address, coin)); + assertFalse(AnyAddress.isValid(any_address_test_address, CoinType.ETHEREUM)); + assertFalse(AnyAddress.isValid("__INVALID_ADDRESS__", CoinType.ETHEREUM)); + } + + @Test + fun testIsValidBech32() { + val coin = CoinType.BITCOIN + assertTrue(AnyAddress.isValidBech32(any_address_test_address, coin, "bc")); + assertFalse(AnyAddress.isValidBech32(any_address_test_address, coin, "tb")); + assertTrue(AnyAddress.isValidBech32("tb1qcj2vfjec3c3luf9fx9vddnglhh9gawmnjan4v3", coin, "tb")); + assertFalse(AnyAddress.isValidBech32("tb1qcj2vfjec3c3luf9fx9vddnglhh9gawmnjan4v3", coin, "bc")); + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestAsnParser.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestAsnParser.kt new file mode 100644 index 00000000000..7c896d7860c --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestAsnParser.kt @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.utils + +import wallet.core.jni.AsnParser +import org.junit.Assert.assertEquals +import org.junit.Test + +class TestAsnParser { + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testEcdsaSignatureFromDer() { + val encoded = "3046022100db421231f23d0320dbb8f1284b600cd34b8e9218628139539ff4f1f6c05495da022100ff715aab70d5317dbf8ee224eb18bec3120cfb9db1000dbb31eadaf96c71c1b1" + val expected = "0xdb421231f23d0320dbb8f1284b600cd34b8e9218628139539ff4f1f6c05495daff715aab70d5317dbf8ee224eb18bec3120cfb9db1000dbb31eadaf96c71c1b1" + val actual = AsnParser.ecdsaSignatureFromDer(encoded.toHexBytes()) + assertEquals(actual.toHex(), expected) + } +} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestBase32.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestBase32.kt new file mode 100644 index 00000000000..d564eff45d8 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestBase32.kt @@ -0,0 +1,35 @@ +package com.trustwallet.core.app.utils + +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.Base32 + +class TestBase32 { + init { + System.loadLibrary("TrustWalletCore"); + } + + @Test + fun testEncode() { + assertEquals(Base32.encode("HelloWorld".toByteArray()), "JBSWY3DPK5XXE3DE") + } + + @Test + fun testEncodeWithAlphabet() { + assertEquals(Base32.encodeWithAlphabet("7uoq6tp427uzv7fztkbsnn64iwotfrristwpryy".toByteArray(), "abcdefghijklmnopqrstuvwxyz234567"), "g52w64jworydimrxov5hmn3gpj2gwyttnzxdmndjo5xxiztsojuxg5dxobzhs6i") + } + + @Test + fun testDecode() { + var decoded = Base32.decode("JBSWY3DPK5XXE3DE") + + assertEquals(String(decoded, Charsets.UTF_8), "HelloWorld") + } + + @Test + fun testDecodeWithAlphabet() { + var decoded = Base32.decodeWithAlphabet("g52w64jworydimrxov5hmn3gpj2gwyttnzxdmndjo5xxiztsojuxg5dxobzhs6i", "abcdefghijklmnopqrstuvwxyz234567") + + assertEquals(String(decoded, Charsets.UTF_8), "7uoq6tp427uzv7fztkbsnn64iwotfrristwpryy") + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestBase64.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestBase64.kt new file mode 100644 index 00000000000..b69851da1b8 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestBase64.kt @@ -0,0 +1,34 @@ +package com.trustwallet.core.app.utils + +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.jni.Base64 + +class TestBase64 { + init { + System.loadLibrary("TrustWalletCore"); + } + + @Test + fun testEncode() { + assertEquals(Base64.encode("HelloWorld".toByteArray()), "SGVsbG9Xb3JsZA==") + } + + @Test + fun testDecode() { + val decoded = Base64.decode("SGVsbG9Xb3JsZA==") + assertEquals(String(decoded, Charsets.UTF_8), "HelloWorld") + } + + @Test + fun testEncodeUrl() { + assertEquals(Base64.encodeUrl("+\\?ab".toByteArray()), "K1w_YWI=") + } + + @Test + fun testDecodeUrl() { + val decoded = Base64.decodeUrl("K1w_YWI=") + assertEquals(String(decoded, Charsets.UTF_8), "+\\?ab") + } +} + diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestCryptoBox.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestCryptoBox.kt new file mode 100644 index 00000000000..4b5bc226ce9 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestCryptoBox.kt @@ -0,0 +1,47 @@ +package com.trustwallet.core.app.utils + +import com.trustwallet.core.app.utils.toHexBytes +import com.trustwallet.core.app.utils.toHex +import org.junit.Assert.* +import org.junit.Test +import wallet.core.jni.* + +class TestCryptoBox { + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testEncryptDecryptEasy() { + val mySecret = CryptoBoxSecretKey() + val myPubkey = mySecret.publicKey + + val otherSecret = CryptoBoxSecretKey() + val otherPubkey = otherSecret.publicKey + + val message = "Well done is better than well said. -Benjamin Franklin" + val encrypted = CryptoBox.encryptEasy(mySecret, otherPubkey, message.toByteArray()) + + // Step 2. Make sure the Box can be decrypted by the other side. + val decrypted = CryptoBox.decryptEasy(otherSecret, myPubkey, encrypted) + assertEquals(decrypted.toString(Charsets.UTF_8), message) + } + + @Test + fun testSecretKeyFromToBytes() { + val secretBytesHex = "0xdd87000d4805d6fbd89ae1352f5e4445648b79d5e901c92aebcb610e9be468e4" + val secretBytes = secretBytesHex.toHexByteArray() + assert(CryptoBoxSecretKey.isValid(secretBytes)) + val secret = CryptoBoxSecretKey(secretBytes) + assertEquals(secret.data().toHex(), secretBytesHex) + } + + @Test + fun testPublicKeyFromToBytes() { + val publicBytesHex = "0xafccabc5b28a8a1fd1cd880516f9c854ae2498d0d1b978b53a59f38e4ae55747" + val publicBytes = publicBytesHex.toHexByteArray() + assert(CryptoBoxPublicKey.isValid(publicBytes)) + val pubkey = CryptoBoxPublicKey(publicBytes) + assertEquals(pubkey.data().toHex(), publicBytesHex) + } +} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestHDWallet.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestHDWallet.kt index 0d85be821d8..e3ec6f29b12 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestHDWallet.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestHDWallet.kt @@ -1,18 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + package com.trustwallet.core.app.utils import com.trustwallet.core.app.utils.Numeric -import wallet.core.jni.CoinType -import wallet.core.jni.Curve -import wallet.core.jni.HDVersion -import wallet.core.jni.HDWallet -import wallet.core.jni.Mnemonic -import wallet.core.jni.Purpose +import com.trustwallet.core.app.utils.toHex import java.security.InvalidParameterException import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.Test +import wallet.core.jni.* class TestHDWallet { init { @@ -23,6 +23,39 @@ class TestHDWallet { "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal" val password = "TREZOR" + @Test + fun testCreateFromMnemonicImmutableXMainnetFromSignature() { + // Successfully register: https://api.x.immutable.com/v1/users/0xd0972E2312518Ca15A2304D56ff9cc0b7ea0Ea37 + val hd = HDWallet("obscure opera favorite shuffle mail tip age debate dirt pact cement loyal", "") + val derivationPath = Ethereum.eip2645GetPath("0xd0972E2312518Ca15A2304D56ff9cc0b7ea0Ea37", "starkex", "immutablex", "1") + assertEquals(derivationPath, "m/2645'/579218131'/211006541'/2124474935'/1609799702'/1") + + // Retrieve eth private key + val ethPrivateKey = hd.getKeyForCoin(CoinType.ETHEREUM) + assertEquals(Numeric.toHexString(ethPrivateKey.data()), "0x03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d") + + // Retrieve StarkKey DerivationPath + val starkDerivationPath = DerivationPath(derivationPath) + + // Retrieve Stark Private key part + val ethMsg = "Only sign this request if you’ve initiated an action with Immutable X." + val ethSignature = EthereumMessageSigner.signMessageImmutableX(ethPrivateKey, ethMsg) + assertEquals(ethSignature, "18b1be8b78807d3326e28bc286d7ee3d068dcd90b1949ce1d25c1f99825f26e70992c5eb7f44f76b202aceded00d74f771ed751f2fe538eec01e338164914fe001") + val starkPrivateKey = StarkWare.getStarkKeyFromSignature(starkDerivationPath, ethSignature) + val starkPublicKey = starkPrivateKey.getPublicKeyByType(PublicKeyType.STARKEX) + assertEquals(Numeric.toHexString(starkPrivateKey.data()), "0x04be51a04e718c202e4dca60c2b72958252024cfc1070c090dd0f170298249de") + assertEquals(Numeric.toHexString(starkPublicKey.data()), "0x00e5b9b11f8372610ef35d647a1dcaba1a4010716588d591189b27bf3c2d5095") + + // Account register + val ethMsgToRegister = "Only sign this key linking request from Immutable X" + val ethSignatureToRegister = EthereumMessageSigner.signMessageImmutableX(ethPrivateKey, ethMsgToRegister) + assertEquals(ethSignatureToRegister, "646da4160f7fc9205e6f502fb7691a0bf63ecbb74bbb653465cd62388dd9f56325ab1e4a9aba99b1661e3e6251b42822855a71e60017b310b9f90e990a12e1dc01") + val starkMsg = "463a2240432264a3aa71a5713f2a4e4c1b9e12bbb56083cd56af6d878217cf" + val starkSignature = StarkExMessageSigner.signMessage(starkPrivateKey, starkMsg) + assertEquals(starkSignature, "04cf5f21333dd189ada3c0f2a51430d733501a9b1d5e07905273c1938cfb261e05b6013d74adde403e8953743a338c8d414bb96bf69d2ca1a91a85ed2700a528") + assertTrue(StarkExMessageSigner.verifyMessage(starkPublicKey, starkMsg, starkSignature)) + } + @Test fun testCreateFromMnemonic() { val hd = HDWallet(words, password) @@ -65,6 +98,55 @@ class TestHDWallet { assertEquals(Numeric.toHexString(hd.entropy()), "0xba5821e8c356c05ba5f025d9532fe0f21f65d594") } + @Test + fun testGetKeyForCoin() { + val coin = CoinType.BITCOIN + val wallet = HDWallet(words, password) + val key = wallet.getKeyForCoin(coin) + + val address = coin.deriveAddress(key) + assertEquals(address, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85") + } + + @Test + fun testGetKeyDerivation() { + val coin = CoinType.BITCOIN + val wallet = HDWallet(words, password) + + val key1 = wallet.getKeyDerivation(coin, Derivation.BITCOINSEGWIT) + assertEquals(key1.data().toHex(), "0x1901b5994f075af71397f65bd68a9fff8d3025d65f5a2c731cf90f5e259d6aac") + + val key2 = wallet.getKeyDerivation(coin, Derivation.BITCOINLEGACY) + assertEquals(key2.data().toHex(), "0x28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4") + + val key3 = wallet.getKeyDerivation(coin, Derivation.BITCOINTESTNET) + assertEquals(key3.data().toHex(), "0xca5845e1b43e3adf577b7f110b60596479425695005a594c88f9901c3afe864f") + } + + @Test + fun testGetAddressForCoin() { + val coin = CoinType.BITCOIN + val wallet = HDWallet(words, password) + + val address = wallet.getAddressForCoin(coin) + assertEquals(address, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85") + } + + @Test + fun testGetAddressDerivation() { + val coin = CoinType.BITCOIN + val wallet = HDWallet(words, password) + + val address1 = wallet.getAddressDerivation(coin, Derivation.BITCOINSEGWIT) + assertEquals(address1, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85") + + val address2 = wallet.getAddressDerivation(coin, Derivation.BITCOINLEGACY) + assertEquals(address2, "1PeUvjuxyf31aJKX6kCXuaqxhmG78ZUdL1") + + val address3 = wallet.getAddressDerivation(coin, Derivation.BITCOINTESTNET) + assertEquals(address3, "tb1qwgpxgwn33z3ke9s7q65l976pseh4edrzfmyvl0") + } + @Test fun testDerive() { val wallet = HDWallet(words, password) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt index 3832db95139..599d3369216 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt @@ -4,6 +4,7 @@ import org.junit.Assert.* import org.junit.Test import wallet.core.jni.StoredKey import wallet.core.jni.CoinType +import wallet.core.jni.StoredKeyEncryption class TestKeyStore { @@ -21,6 +22,16 @@ class TestKeyStore { assertNotNull(result2) } + @Test + fun testDecryptMnemonicAes256() { + val keyStore = StoredKey("Test Wallet", "password".toByteArray(), StoredKeyEncryption.AES256CTR) + val result = keyStore.decryptMnemonic("wrong".toByteArray()) + val result2 = keyStore.decryptMnemonic("password".toByteArray()) + + assertNull(result) + assertNotNull(result2) + } + @Test fun testRemoveCoins() { val password = "password".toByteArray() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestLiquidStaking.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestLiquidStaking.kt new file mode 100644 index 00000000000..44701d5f2b1 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestLiquidStaking.kt @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.utils + +import com.google.protobuf.ByteString +import org.junit.Assert +import wallet.core.jni.* +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.proto.Ethereum +import wallet.core.jni.proto.LiquidStaking +import wallet.core.jni.LiquidStaking as WCLiquidStaking + +class TestLiquidStaking { + init { + System.loadLibrary("TrustWalletCore"); + } + + @Test + fun testStraderStakeMatic() { + val input = LiquidStaking.Input.newBuilder() + input.apply { + blockchain = LiquidStaking.Blockchain.POLYGON + protocol = LiquidStaking.Protocol.Strader + smartContractAddress = "0xfd225c9e6601c9d38d8f98d8731bf59efcf8c0e3" + stake = LiquidStaking.Stake.newBuilder().apply { + amount = "1000000000000000000" + asset = LiquidStaking.Asset.newBuilder().apply { + stakingToken = LiquidStaking.Coin.POL + }.build() + }.build() + } + + + val inputSerialized = input.build().toByteArray() + assertEquals(Numeric.toHexString(inputSerialized), "0x0a170a00121331303030303030303030303030303030303030222a3078666432323563396536363031633964333864386639386438373331626635396566636638633065333001") + val outputData = WCLiquidStaking.buildRequest(inputSerialized) + assertEquals(outputData.count(), 68) + + val outputProto = LiquidStaking.Output.newBuilder().mergeFrom(outputData) + Assert.assertTrue(outputProto.hasEthereum()) + val txInput = outputProto.ethereum + + val txInputFull = txInput.toBuilder().apply { + chainId = ByteString.copyFrom("0x89".toHexByteArray()) + nonce = ByteString.copyFrom("0x01".toHexByteArray()) + maxFeePerGas = ByteString.copyFrom("0x8fbcc8fcd8".toHexByteArray()) + maxInclusionFeePerGas = ByteString.copyFrom("0x085e42c7c0".toHexByteArray()) + gasLimit = ByteString.copyFrom("0x01c520".toHexByteArray()) + privateKey = ByteString.copyFrom(PrivateKey("0x4a160b803c4392ea54865d0c5286846e7ad5e68fbd78880547697472b22ea7ab".toHexByteArray()).data()) + }.build() + + val output = AnySigner.sign(txInputFull, CoinType.ETHEREUM, Ethereum.SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x02f87a81890185085e42c7c0858fbcc8fcd88301c52094fd225c9e6601c9d38d8f98d8731bf59efcf8c0e3880de0b6b3a764000084c78cf1a0c001a04bcf92394d53d4908130cc6d4f7b2491967f9d6c59292b84c1f56adc49f6c458a073e09f45d64078c41a7946ffdb1dee8e604eb76f318088490f8f661bb7ddfc54") + // Successfully broadcasted: https://polygonscan.com/tx/0x0f6c4f7a893c3f08be30d2ea24479d7ed4bdba40875d07cfd607cf97980b7cf0 + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPrivateKey.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPrivateKey.kt index abdbc8050ab..d84bccd69c5 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPrivateKey.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPrivateKey.kt @@ -3,11 +3,7 @@ package com.trustwallet.core.app.utils import com.trustwallet.core.app.utils.toHexBytes import org.junit.Assert.* import org.junit.Test -import wallet.core.jni.Curve -import wallet.core.jni.Hash -import wallet.core.jni.PrivateKey -import wallet.core.jni.PublicKey -import wallet.core.jni.PublicKeyType +import wallet.core.jni.* class TestPrivateKey { private val validPrivateKeyData = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5".toHexBytes() @@ -23,6 +19,24 @@ class TestPrivateKey { assertTrue(data.size == 32); } + @Test + fun testCreateStarkKey() { + val data = Numeric.hexStringToByteArray("06cf0a8bf113352eb863157a45c5e5567abb34f8d32cddafd2c22aa803f4892c") + assertTrue(PrivateKey.isValid(data, Curve.STARKEX)) + var privateKey = PrivateKey(data) + var pubKey = privateKey.getPublicKeyByType(PublicKeyType.STARKEX) + assertEquals(pubKey.data().toHex(), "0x02d2bbdc1adaf887b0027cdde2113cfd81c60493aa6dc15d7887ddf1a82bc831") + } + + @Test + fun testCreateStarkKeySigning() { + val data = Numeric.hexStringToByteArray("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79") + var privateKey = PrivateKey(data) + val digest = Numeric.hexStringToByteArray("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76") + val signature = privateKey.sign(digest, Curve.STARKEX) + assertEquals(signature.toHex(), "0x061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a") + } + @Test fun testInvalid() { val bytes = Numeric.hexStringToByteArray("deadbeaf") @@ -67,61 +81,9 @@ class TestPrivateKey { } @Test - fun testGetSharedKey() { - val privateKeyData = "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0".toHexBytes() - val privateKey = PrivateKey(privateKeyData) - - val publicKeyData = "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992".toHexBytes() - val publicKey = PublicKey(publicKeyData, PublicKeyType.SECP256K1) - - val derivedData = privateKey.getSharedKey(publicKey, Curve.SECP256K1) - assertNotNull(derivedData) - - assertEquals(derivedData?.toHex(), "0xef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a") - } - - @Test - fun testGetSharedKeyWycherproof() { - val privateKeyData = "f4b7ff7cccc98813a69fae3df222bfe3f4e28f764bf91b4a10d8096ce446b254".toHexBytes() + fun testGetPublicKeyCoinType() { + val privateKeyData = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5".toHexBytes() val privateKey = PrivateKey(privateKeyData) - - val publicKeyData = "02d8096af8a11e0b80037e1ee68246b5dcbb0aeb1cf1244fd767db80f3fa27da2b".toHexBytes() - val publicKey = PublicKey(publicKeyData, PublicKeyType.SECP256K1) - - val derivedData = privateKey.getSharedKey(publicKey, Curve.SECP256K1) - assertNotNull(derivedData) - - assertEquals(derivedData?.toHex(), "0x81165066322732362ca5d3f0991d7f1f7d0aad7ea533276496785d369e35159a") - } - - @Test - fun testGetSharedKeyBidirectional() { - val privateKeyData1 = "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0".toHexBytes() - val privateKey1 = PrivateKey(privateKeyData1) - val publicKey1 = privateKey1.getPublicKeySecp256k1(true) - - val privateKeyData2 = "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a".toHexBytes() - val privateKey2 = PrivateKey(privateKeyData2) - val publicKey2 = privateKey2.getPublicKeySecp256k1(true) - - val derivedData1 = privateKey1.getSharedKey(publicKey2, Curve.SECP256K1) - assertNotNull(derivedData1) - - val derivedData2 = privateKey2.getSharedKey(publicKey1, Curve.SECP256K1) - assertNotNull(derivedData2) - - assertEquals(derivedData1?.toHex(), derivedData2?.toHex()) + assertEquals(privateKey.getPublicKey(CoinType.ETHEREUM).data().toHex(), "0x0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91"); } - - @Test - fun testGetSharedKeyError() { - val privateKeyData = "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0".toHexBytes() - val privateKey = PrivateKey(privateKeyData) - - val publicKeyData = "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992".toHexBytes() - val publicKey = PublicKey(publicKeyData, PublicKeyType.SECP256K1) - - val derivedData = privateKey.getSharedKey(publicKey, Curve.ED25519) - assertNull(derivedData) - } } \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPublicKey.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPublicKey.kt index dbd57dc1425..bfecc51014e 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPublicKey.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPublicKey.kt @@ -4,10 +4,7 @@ import com.trustwallet.core.app.utils.toHexBytes import com.trustwallet.core.app.utils.toHex import org.junit.Assert.* import org.junit.Test -import wallet.core.jni.Curve -import wallet.core.jni.Hash -import wallet.core.jni.PrivateKey -import wallet.core.jni.PublicKey +import wallet.core.jni.* class TestPublicKey { @@ -28,6 +25,17 @@ class TestPublicKey { val publicKey = privateKey?.getPublicKeySecp256k1(true) assertEquals("0x0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1", publicKey?.data()?.toHex()) } + + @Test + fun testVerifyStarkey() { + val data = Numeric.hexStringToByteArray("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159") + val publicKey = PublicKey(data, PublicKeyType.STARKEX) + var signature = Numeric.hexStringToByteArray("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a") + val hash = Numeric.hexStringToByteArray("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76") + assertTrue(publicKey.verify(signature, hash)) + signature = Numeric.hexStringToByteArray("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9b") + assertFalse(publicKey.verify(signature, hash)) + } @Test fun testSign() { diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestWebAuthn.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestWebAuthn.kt new file mode 100644 index 00000000000..d24ff122dbe --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestWebAuthn.kt @@ -0,0 +1,36 @@ +package com.trustwallet.core.app.utils + +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import com.trustwallet.core.app.utils.Numeric +import wallet.core.jni.* + +class TestWebAuthn { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testGetPublicKey() { + val attestationObject = Numeric.hexStringToByteArray("0xa363666d74646e6f6e656761747453746d74a068617574684461746158a4f95bc73828ee210f9fd3bbe72d97908013b0a3759e9aea3d0ae318766cd2e1ad4500000000adce000235bcc60a648b0b25f1f055030020c720eb493e167ce93183dd91f5661e1004ed8cc1be23d3340d92381da5c0c80ca5010203262001215820a620a8cfc88fd062b11eab31663e56cad95278bef612959be214d98779f645b82258204e7b905b42917570148b0432f99ba21f2e7eebe018cbf837247e38150a89f771") + val result = WebAuthn.getPublicKey(attestationObject).data() + assertEquals(Numeric.toHexString(result), "0x04a620a8cfc88fd062b11eab31663e56cad95278bef612959be214d98779f645b84e7b905b42917570148b0432f99ba21f2e7eebe018cbf837247e38150a89f771") + } + + @Test + fun testGetRSValues() { + val signature = Numeric.hexStringToByteArray("0x30440220766589b461a838748708cdf88444b21b1fa52b57d70671b4f9bf60ad14b372ec022020cc439c9c20661bfa39bbea24a900ec1484b2395eb065ead8ef4e273144a57d") + val result = WebAuthn.getRSValues(signature) + assertEquals(Numeric.toHexString(result), "0x766589b461a838748708cdf88444b21b1fa52b57d70671b4f9bf60ad14b372ec20cc439c9c20661bfa39bbea24a900ec1484b2395eb065ead8ef4e273144a57d") + } + + @Test + fun testReconstructOriginalMessage() { + val authenticatorData = Numeric.hexStringToByteArray("0x1a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d00000000") + val clientDataJSON = Numeric.hexStringToByteArray("0x7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a224e5549794f5545774d6b45744e554535517930304d6b5a424c546847516a4174517a52474f4441794d3045304f546b30222c226f726967696e223a2268747470733a2f2f747275737477616c6c65742e636f6d227d") + val result = WebAuthn.reconstructOriginalMessage(authenticatorData, clientDataJSON) + assertEquals(Numeric.toHexString(result), "0x3254cdbd677e6e31e75d2135bad0cf56440d7c6b108c141a3509d76ce45c6731") + } +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ef7f63193fb..1df538f8715 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,21 +1,20 @@ - + + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/AppTheme"> - + - + - \ No newline at end of file + diff --git a/android/build.gradle b/android/build.gradle index b1ca946f2d1..ae4f70d4299 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,18 +1,12 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - buildscript { ext.kotlin_version = '1.6.10' repositories { google() mavenCentral() - maven { - url = uri("https://plugins.gradle.org/m2/") - } } dependencies { - classpath 'com.android.tools.build:gradle:4.2.2' + classpath 'com.android.tools.build:gradle:8.0.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' } } @@ -26,7 +20,7 @@ allprojects { subprojects { afterEvaluate { if (getPlugins().hasPlugin('android') || - getPlugins().hasPlugin('android-library')) { + getPlugins().hasPlugin('android-library')) { configure(android.lintOptions) { abortOnError false } diff --git a/android/gradle.properties b/android/gradle.properties index 53c570a2de0..395c1f03275 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -20,6 +20,8 @@ android.useAndroidX=true android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official +android.defaults.buildfeatures.buildconfig=false +android.defaults.buildfeatures.resvalues=false VERSION_NAME=0.12.1 VERSION_CODE=1 diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar index 62d4c053550..c1962a79e29 100644 Binary files a/android/gradle/wrapper/gradle-wrapper.jar and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 4cf5f4720c8..a21c6ebe28b 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Jan 19 18:01:57 JST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.2-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-all.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/gradlew b/android/gradlew index fbd7c515832..aeb74cbb43e 100755 --- a/android/gradlew +++ b/android/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,98 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +118,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +129,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +137,109 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat index a9f778a7a96..6689b85beec 100644 --- a/android/gradlew.bat +++ b/android/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +65,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,17 +72,19 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/android/settings.gradle b/android/settings.gradle index 1924f990d04..044d01a9482 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1 +1 @@ -include ':app', ':trustwalletcore' +include ':app', ':wallet-core', ':wallet-core-proto' diff --git a/android/trustwalletcore/build.gradle b/android/trustwalletcore/build.gradle deleted file mode 100644 index b89842051d6..00000000000 --- a/android/trustwalletcore/build.gradle +++ /dev/null @@ -1,53 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'maven-publish' -group='com.github.trustwallet' - -android { - compileSdkVersion 32 - ndkVersion '23.1.7779620' - defaultConfig { - minSdkVersion 23 - versionCode 1 - versionName "1.0" - externalNativeBuild { - cmake { - arguments "-DCMAKE_BUILD_TYPE=Release" - } - } - } - - lintOptions { - abortOnError false - disable 'InvalidPackage' - } - - buildTypes { - release { - minifyEnabled false - } - debug { - minifyEnabled false - // limit platforms built for testing - ndk { - abiFilters 'x86', 'arm64-v8a' - } - } - } - - sourceSets { - main.java.srcDirs += '../../jni/java' - } - - externalNativeBuild { - cmake { - version "3.18.1" - path "../../CMakeLists.txt" - } - } -} - -dependencies { - implementation 'io.grpc:grpc-protobuf:1.43.2' -} - -apply from: 'maven-push.gradle' diff --git a/android/trustwalletcore/maven-push.gradle b/android/trustwalletcore/maven-push.gradle deleted file mode 100644 index 12746f09c4a..00000000000 --- a/android/trustwalletcore/maven-push.gradle +++ /dev/null @@ -1,51 +0,0 @@ -apply plugin: 'maven-publish' - -version project.property('version') -group 'com.trustwallet' - -task sourcesJar(type: Jar) { - classifier = 'sources' - from android.sourceSets.main.java.sourceFiles -} - -artifacts { - archives sourcesJar -} - -publishing { - publications { - Production(MavenPublication) { - artifact("$buildDir/outputs/aar/trustwalletcore-release.aar") - artifact sourcesJar { - classifier "sources" - } - groupId this.group - artifactId 'wallet-core' - version this.version - - pom.withXml { - def dependenciesNode = asNode().appendNode('dependencies') - - configurations.implementation.allDependencies.each { - if (it.name != 'unspecified') { - def dependencyNode = dependenciesNode.appendNode('dependency') - dependencyNode.appendNode('groupId', it.group) - dependencyNode.appendNode('artifactId', it.name) - dependencyNode.appendNode('version', it.version) - } - } - } - } - } - - repositories { - maven { - name = "GitHubPackages" - url = uri("https://maven.pkg.github.com/trustwallet/wallet-core") - credentials { - username = System.getenv("WC_GITHUB_USER") - password = System.getenv("WC_GITHUB_TOKEN") - } - } - } -} diff --git a/android/trustwalletcore/src/main/AndroidManifest.xml b/android/trustwalletcore/src/main/AndroidManifest.xml deleted file mode 100644 index 3f53eb1d42b..00000000000 --- a/android/trustwalletcore/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - diff --git a/android/wallet-core-proto/build.gradle b/android/wallet-core-proto/build.gradle new file mode 100644 index 00000000000..c04e323f48e --- /dev/null +++ b/android/wallet-core-proto/build.gradle @@ -0,0 +1,21 @@ +apply plugin: 'java-library' +apply plugin: 'maven-publish' + +group = 'com.trustwallet' + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + + withSourcesJar() + + sourceSets { + main.java.srcDirs += '../../jni/proto' + } +} + +dependencies { + api 'com.google.protobuf:protobuf-javalite:3.22.3' +} + +apply from: 'maven-push.gradle' diff --git a/android/wallet-core-proto/maven-push.gradle b/android/wallet-core-proto/maven-push.gradle new file mode 100644 index 00000000000..cb48062952d --- /dev/null +++ b/android/wallet-core-proto/maven-push.gradle @@ -0,0 +1,20 @@ +apply plugin: 'maven-publish' + +publishing { + publications { + release(MavenPublication) { + from components.java + } + } + + repositories { + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/trustwallet/wallet-core") + credentials { + username = System.getenv("GITHUB_USER") + password = System.getenv("GITHUB_TOKEN") + } + } + } +} diff --git a/android/trustwalletcore/.gitignore b/android/wallet-core/.gitignore similarity index 100% rename from android/trustwalletcore/.gitignore rename to android/wallet-core/.gitignore diff --git a/android/wallet-core/build.gradle b/android/wallet-core/build.gradle new file mode 100644 index 00000000000..fcdf15afbbc --- /dev/null +++ b/android/wallet-core/build.gradle @@ -0,0 +1,61 @@ +apply plugin: 'com.android.library' +apply plugin: 'maven-publish' + +group = 'com.trustwallet' + +android { + namespace 'wallet.core' + compileSdkVersion 32 + ndkVersion '23.1.7779620' + defaultConfig { + minSdkVersion 23 + versionCode 1 + versionName "1.0" + externalNativeBuild { + cmake { + arguments "-DCMAKE_BUILD_TYPE=Release", "-DTW_UNITY_BUILD=ON" + } + } + } + + lintOptions { + abortOnError false + disable 'InvalidPackage' + } + + buildTypes { + release { + minifyEnabled false + } + debug { + minifyEnabled false + // limit platforms built for testing + ndk { + abiFilters 'x86', 'arm64-v8a' + } + } + } + + sourceSets { + main.java.srcDirs += '../../jni/java' + } + + externalNativeBuild { + cmake { + version "3.18.1" + path "../../CMakeLists.txt" + } + } + + publishing { + singleVariant('release') { + withSourcesJar() + } + } +} + +dependencies { + api project(':wallet-core-proto') +} + +apply from: 'maven-push.gradle' diff --git a/android/trustwalletcore/gradle.properties b/android/wallet-core/gradle.properties similarity index 100% rename from android/trustwalletcore/gradle.properties rename to android/wallet-core/gradle.properties diff --git a/android/wallet-core/maven-push.gradle b/android/wallet-core/maven-push.gradle new file mode 100644 index 00000000000..49dd6eec103 --- /dev/null +++ b/android/wallet-core/maven-push.gradle @@ -0,0 +1,22 @@ +apply plugin: 'maven-publish' + +publishing { + publications { + release(MavenPublication) { + afterEvaluate { + from components.release + } + } + } + + repositories { + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/trustwallet/wallet-core") + credentials { + username = System.getenv("GITHUB_USER") + password = System.getenv("GITHUB_TOKEN") + } + } + } +} diff --git a/audit/2023-09-15_TrustWallet_SecureCodeReviewReport_Public_v2.00.pdf b/audit/2023-09-15_TrustWallet_SecureCodeReviewReport_Public_v2.00.pdf new file mode 100644 index 00000000000..d0efac0de8d Binary files /dev/null and b/audit/2023-09-15_TrustWallet_SecureCodeReviewReport_Public_v2.00.pdf differ diff --git a/bootstrap.sh b/bootstrap.sh index e32cccfe915..cdb20a25993 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,28 +1,13 @@ #!/usr/bin/env bash +# +# Initializes the workspace with dependencies, then performs full build # Fail if any commands fails set -e -echo "#### Initializing... ####" +echo "#### Initializing workspace with dependencies ... ####" tools/install-dependencies +tools/install-rust-dependencies -echo "#### Generating files... ####" -tools/generate-files - -echo "#### Building... ####" -cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -make -Cbuild -j12 tests TrezorCryptoTests - -if [ -x "$(command -v clang-tidy)" ]; then - echo "#### Linting... ####" - tools/lint -fi - -echo "#### Testing... ####" -export CK_TIMEOUT_MULTIPLIER=4 -build/trezor-crypto/crypto/tests/TrezorCryptoTests - -ROOT="`dirname \"$0\"`" -TESTS_ROOT="`(cd \"$ROOT/tests\" && pwd)`" -build/tests/tests "$TESTS_ROOT" +echo "#### Building and running tests ... ####" +tools/build-and-test diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake new file mode 100644 index 00000000000..16fff2a832b --- /dev/null +++ b/cmake/CompilerWarnings.cmake @@ -0,0 +1,90 @@ +macro(target_enable_asan target) + message("-- ASAN Enabled, Configuring...") + target_compile_options(${target} PUBLIC + $<$,$>:-fsanitize=address -fno-omit-frame-pointer> + $<$,$>:-fsanitize=address -fno-omit-frame-pointer>) + target_link_options(${target} PUBLIC + $<$,$>:-fsanitize=address -fno-omit-frame-pointer> + $<$,$>:-fsanitize=address -fno-omit-frame-pointer>) +endmacro() + +macro(target_enable_coverage target) + message(STATUS "Code coverage ON") + # This option is used to compile and link code instrumented for coverage analysis. + # The option is a synonym for -fprofile-arcs -ftest-coverage (when compiling) and -lgcov (when linking). + # See the documentation for those options for more details. + # https://gcc.gnu.org/onlinedocs/gcc-9.3.0/gcc/Instrumentation-Options.html + if (TW_IDE_CLION) + message(STATUS "Code coverage for Clion ON") + target_compile_options(${target} PUBLIC + $<$,$>:-fprofile-instr-generate -fcoverage-mapping> + $<$,$>:-fprofile-instr-generate -fcoverage-mapping> + $<$,$>:-fprofile-instr-generate -fcoverage-mapping>) + target_link_options(${target} PUBLIC + $<$,$>:-fprofile-instr-generate -fcoverage-mapping> + $<$,$>:-fprofile-instr-generate -fcoverage-mapping> + $<$,$>:-fprofile-instr-generate -fcoverage-mapping>) + else() + target_compile_options(${target} PUBLIC + $<$,$>:--coverage> + $<$,$>:--coverage>) + target_link_options(${target} PUBLIC + $<$,$>:--coverage> + $<$,$>:--coverage>) + endif () +endmacro() + +add_library(tw_error_settings INTERFACE) +add_library(tw::error_settings ALIAS tw_error_settings) + +add_library(tw_defaults_features INTERFACE) +add_library(tw::defaults_features ALIAS tw_defaults_features) + +add_library(tw_optimize_settings INTERFACE) +add_library(tw::optimize_settings ALIAS tw_optimize_settings) + +target_compile_options( + tw_error_settings + INTERFACE + -Wall + -Wextra # reasonable and standard + -Wfatal-errors # short error report + -Wshadow # warn the user if a variable declaration shadows one from a + -Wshorten-64-to-32 + -Wno-nullability-completeness + # parent context + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a + # non-virtual destructor. This helps catch hard to track down memory errors + -Wcast-align # warn for potential performance problem casts + #-Wunused # warn on anything being unused + -Woverloaded-virtual # warn if you overload (not override) a virtual + # function + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output +) + +if (TW_WARNINGS_AS_ERRORS) + target_compile_options( + tw_error_settings + INTERFACE + -Werror + ) +endif () + +target_compile_features(tw_defaults_features INTERFACE cxx_std_20) + +target_compile_options(tw_optimize_settings INTERFACE + $<$,$>:-O0 -g> + $<$,$>:-O0 -g> + $<$,$>:-O2> + $<$,$>:-O2> + ) + +function(set_project_warnings project_name) + target_link_libraries(${project_name} INTERFACE tw::error_settings tw::defaults_features tw::optimize_settings) + + if (NOT TARGET ${project_name}) + message(AUTHOR_WARNING "${project_name} is not a target, thus no compiler warning were added.") + endif () +endfunction() diff --git a/cmake/FindHostPackage.cmake b/cmake/FindHostPackage.cmake new file mode 100644 index 00000000000..188a2ee7e9d --- /dev/null +++ b/cmake/FindHostPackage.cmake @@ -0,0 +1,9 @@ +macro(find_host_package) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) + find_package(${ARGN}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endmacro(find_host_package) diff --git a/cmake/PVS-Studio.cmake b/cmake/PVS-Studio.cmake new file mode 100644 index 00000000000..d19bc3b105e --- /dev/null +++ b/cmake/PVS-Studio.cmake @@ -0,0 +1,615 @@ +# 2006-2008 (c) Viva64.com Team +# 2008-2018 (c) OOO "Program Verification Systems" +# +# Version 12 + +cmake_minimum_required(VERSION 3.0.0) +cmake_policy(SET CMP0054 NEW) + +if (PVS_STUDIO_AS_SCRIPT) + # This code runs at build time. + # It executes pvs-studio-analyzer and propagates its return value. + + set(in_cl_params FALSE) + set(additional_args) + + foreach (arg ${PVS_STUDIO_COMMAND}) + if (NOT in_cl_params) + if ("${arg}" STREQUAL "--cl-params") + set(in_cl_params TRUE) + endif () + else () + # A workaround for macOS frameworks (e.g. QtWidgets.framework) + # You can test this workaround on this project: https://github.com/easyaspi314/MidiEditor/tree/gba + if (APPLE AND "${arg}" MATCHES "^-I(.*)\\.framework$") + STRING(REGEX REPLACE "^-I(.*)\\.framework$" "\\1.framework" framework "${arg}") + if (IS_ABSOLUTE "${framework}") + get_filename_component(framework "${framework}" DIRECTORY) + list(APPEND additional_args "-iframework") + list(APPEND additional_args "${framework}") + endif () + endif () + endif () + endforeach () + + file(REMOVE "${PVS_STUDIO_LOG_FILE}") + execute_process(COMMAND ${PVS_STUDIO_COMMAND} ${additional_args} + RESULT_VARIABLE result + OUTPUT_VARIABLE output + ERROR_VARIABLE error) + + if (result AND NOT output MATCHES "^No compilation units were found\\.") + message(FATAL_ERROR "PVS-Studio exited with non-zero code.\nStdout:\n${output}\nStderr:\n${error}\n") + endif() + + return() +endif () + +if(__PVS_STUDIO_INCLUDED) + return() +endif() +set(__PVS_STUDIO_INCLUDED TRUE) + +set(PVS_STUDIO_SCRIPT "${CMAKE_CURRENT_LIST_FILE}") + +function (pvs_studio_log TEXT) + if (PVS_STUDIO_DEBUG) + message("PVS-Studio: ${TEXT}") + endif () +endfunction () + +function (pvs_studio_relative_path VAR ROOT FILEPATH) + if (WIN32) + STRING(REGEX REPLACE "\\\\" "/" ROOT ${ROOT}) + STRING(REGEX REPLACE "\\\\" "/" FILEPATH ${FILEPATH}) + endif() + set("${VAR}" "${FILEPATH}" PARENT_SCOPE) + if (IS_ABSOLUTE "${FILEPATH}") + file(RELATIVE_PATH RPATH "${ROOT}" "${FILEPATH}") + if (NOT IS_ABSOLUTE "${RPATH}") + set("${VAR}" "${RPATH}" PARENT_SCOPE) + endif() + endif() +endfunction () + +function (pvs_studio_join_path VAR DIR1 DIR2) + if ("${DIR2}" MATCHES "^(/|~|.:/).*$" OR "${DIR1}" STREQUAL "") + set("${VAR}" "${DIR2}" PARENT_SCOPE) + else () + set("${VAR}" "${DIR1}/${DIR2}" PARENT_SCOPE) + endif () +endfunction () + +macro (pvs_studio_append_flags_from_property CXX C DIR PREFIX) + if (NOT "${PROPERTY}" STREQUAL "NOTFOUND" AND NOT "${PROPERTY}" STREQUAL "PROPERTY-NOTFOUND") + foreach (PROP ${PROPERTY}) + pvs_studio_join_path(PROP "${DIR}" "${PROP}") + + if (APPLE AND "${PREFIX}" STREQUAL "-I" AND IS_ABSOLUTE "${PROP}" AND "${PROP}" MATCHES "\\.framework$") + get_filename_component(FRAMEWORK "${PROP}" DIRECTORY) + list(APPEND "${CXX}" "-iframework") + list(APPEND "${CXX}" "${FRAMEWORK}") + list(APPEND "${C}" "-iframework") + list(APPEND "${C}" "${FRAMEWORK}") + pvs_studio_log("framework: ${FRAMEWORK}") + elseif (NOT "${PROP}" STREQUAL "") + list(APPEND "${CXX}" "${PREFIX}${PROP}") + list(APPEND "${C}" "${PREFIX}${PROP}") + endif() + endforeach () + endif () +endmacro () + +macro (pvs_studio_append_standard_flag FLAGS STANDARD) + if ("${STANDARD}" MATCHES "^(99|11|14|17|20)$") + if ("${PVS_STUDIO_PREPROCESSOR}" MATCHES "gcc|clang") + list(APPEND "${FLAGS}" "-std=c++${STANDARD}") + endif () + endif () +endmacro () + +function (pvs_studio_set_directory_flags DIRECTORY CXX C) + set(CXX_FLAGS "${${CXX}}") + set(C_FLAGS "${${C}}") + + get_directory_property(PROPERTY DIRECTORY "${DIRECTORY}" INCLUDE_DIRECTORIES) + pvs_studio_append_flags_from_property(CXX_FLAGS C_FLAGS "${DIRECTORY}" "-I") + + get_directory_property(PROPERTY DIRECTORY "${DIRECTORY}" COMPILE_DEFINITIONS) + pvs_studio_append_flags_from_property(CXX_FLAGS C_FLAGS "" "-D") + + set("${CXX}" "${CXX_FLAGS}" PARENT_SCOPE) + set("${C}" "${C_FLAGS}" PARENT_SCOPE) +endfunction () + +function (pvs_studio_set_target_flags TARGET CXX C) + set(CXX_FLAGS "${${CXX}}") + set(C_FLAGS "${${C}}") + + if (NOT MSVC) + list(APPEND CXX_FLAGS "$<$:--sysroot=${CMAKE_SYSROOT}>") + list(APPEND C_FLAGS "$<$:--sysroot=${CMAKE_SYSROOT}>") + endif () + + set(prop_incdirs "$") + list(APPEND CXX_FLAGS "$<$:-I$-I>>") + list(APPEND C_FLAGS "$<$:-I$-I>>") + + set(prop_compdefs "$") + list(APPEND CXX_FLAGS "$<$:-D$-D>>") + list(APPEND C_FLAGS "$<$:-D$-D>>") + + set(prop_compopt "$") + list(APPEND CXX_FLAGS "$<$:$>>") + list(APPEND C_FLAGS "$<$:$>>") + + set("${CXX}" "${CXX_FLAGS}" PARENT_SCOPE) + set("${C}" "${C_FLAGS}" PARENT_SCOPE) +endfunction () + +function (pvs_studio_set_source_file_flags SOURCE) + set(LANGUAGE "") + + string(TOLOWER "${SOURCE}" SOURCE_LOWER) + if ("${LANGUAGE}" STREQUAL "" AND "${SOURCE_LOWER}" MATCHES "^.*\\.(c|cpp|cc|cx|cxx|cp|c\\+\\+)$") + if ("${SOURCE}" MATCHES "^.*\\.c$") + set(LANGUAGE C) + else () + set(LANGUAGE CXX) + endif () + endif () + + if ("${LANGUAGE}" STREQUAL "C") + set(CL_PARAMS ${PVS_STUDIO_C_FLAGS} ${PVS_STUDIO_TARGET_C_FLAGS} -DPVS_STUDIO) + elseif ("${LANGUAGE}" STREQUAL "CXX") + set(CL_PARAMS ${PVS_STUDIO_CXX_FLAGS} ${PVS_STUDIO_TARGET_CXX_FLAGS} -DPVS_STUDIO) + endif () + + set(PVS_STUDIO_LANGUAGE "${LANGUAGE}" PARENT_SCOPE) + set(PVS_STUDIO_CL_PARAMS "${CL_PARAMS}" PARENT_SCOPE) +endfunction () + +function (pvs_studio_analyze_file SOURCE SOURCE_DIR BINARY_DIR) + set(PLOGS ${PVS_STUDIO_PLOGS}) + pvs_studio_set_source_file_flags("${SOURCE}") + + get_filename_component(SOURCE "${SOURCE}" REALPATH) + + get_source_file_property(PROPERTY "${SOURCE}" HEADER_FILE_ONLY) + if (PROPERTY) + return() + endif () + + pvs_studio_relative_path(SOURCE_RELATIVE "${SOURCE_DIR}" "${SOURCE}") + pvs_studio_join_path(SOURCE "${SOURCE_DIR}" "${SOURCE}") + + set(LOG "${BINARY_DIR}/PVS-Studio/${SOURCE_RELATIVE}.plog") + get_filename_component(LOG "${LOG}" REALPATH) + get_filename_component(PARENT_DIR "${LOG}" DIRECTORY) + + if (EXISTS "${SOURCE}" AND NOT TARGET "${LOG}" AND NOT "${PVS_STUDIO_LANGUAGE}" STREQUAL "") + # A workaround to support implicit dependencies for ninja generators. + set(depPvsArg) + set(depCommandArg) + if (CMAKE_VERSION VERSION_GREATER 3.6 AND "${CMAKE_GENERATOR}" STREQUAL "Ninja") + pvs_studio_relative_path(relLog "${CMAKE_BINARY_DIR}" "${LOG}") + set(depPvsArg --dep-file "${LOG}.d" --dep-file-target "${relLog}") + set(depCommandArg DEPFILE "${LOG}.d") + endif () + + # https://public.kitware.com/Bug/print_bug_page.php?bug_id=14353 + # https://public.kitware.com/Bug/file/5436/expand_command.cmake + # + # It is a workaround to expand generator expressions. + set(cmdline "${PVS_STUDIO_BIN}" analyze + --output-file "${LOG}" + --source-file "${SOURCE}" + ${depPvsArg} + ${PVS_STUDIO_ARGS} + --cl-params "${PVS_STUDIO_CL_PARAMS}" "${SOURCE}") + + string(REPLACE ";" "$" cmdline "${cmdline}") + set(pvscmd "${CMAKE_COMMAND}" + -D "PVS_STUDIO_AS_SCRIPT=TRUE" + -D "PVS_STUDIO_COMMAND=${cmdline}" + -D "PVS_STUDIO_LOG_FILE=${LOG}" + -P "${PVS_STUDIO_SCRIPT}" + ) + + add_custom_command(OUTPUT "${LOG}" + COMMAND "${CMAKE_COMMAND}" -E make_directory "${PARENT_DIR}" + COMMAND "${CMAKE_COMMAND}" -E remove_directory "${LOG}" + COMMAND ${pvscmd} + WORKING_DIRECTORY "${BINARY_DIR}" + DEPENDS "${SOURCE}" "${PVS_STUDIO_SUPPRESS_BASE}" "${PVS_STUDIO_DEPENDS}" + IMPLICIT_DEPENDS "${PVS_STUDIO_LANGUAGE}" "${SOURCE}" + ${depCommandArg} + VERBATIM + COMMENT "Analyzing ${PVS_STUDIO_LANGUAGE} file ${SOURCE_RELATIVE}") + list(APPEND PLOGS "${LOG}") + endif () + set(PVS_STUDIO_PLOGS "${PLOGS}" PARENT_SCOPE) +endfunction () + +function (pvs_studio_analyze_target TARGET DIR) + set(PVS_STUDIO_PLOGS "${PVS_STUDIO_PLOGS}") + set(PVS_STUDIO_TARGET_CXX_FLAGS "") + set(PVS_STUDIO_TARGET_C_FLAGS "") + + get_target_property(PROPERTY "${TARGET}" SOURCES) + pvs_studio_relative_path(BINARY_DIR "${CMAKE_SOURCE_DIR}" "${DIR}") + if ("${BINARY_DIR}" MATCHES "^/.*$") + pvs_studio_join_path(BINARY_DIR "${CMAKE_BINARY_DIR}" "PVS-Studio/__${BINARY_DIR}") + else () + pvs_studio_join_path(BINARY_DIR "${CMAKE_BINARY_DIR}" "${BINARY_DIR}") + endif () + + file(MAKE_DIRECTORY "${BINARY_DIR}") + + pvs_studio_set_directory_flags("${DIR}" PVS_STUDIO_TARGET_CXX_FLAGS PVS_STUDIO_TARGET_C_FLAGS) + pvs_studio_set_target_flags("${TARGET}" PVS_STUDIO_TARGET_CXX_FLAGS PVS_STUDIO_TARGET_C_FLAGS) + + if (NOT "${PROPERTY}" STREQUAL "NOTFOUND" AND NOT "${PROPERTY}" STREQUAL "PROPERTY-NOTFOUND") + foreach (SOURCE ${PROPERTY}) + pvs_studio_join_path(SOURCE "${DIR}" "${SOURCE}") + pvs_studio_analyze_file("${SOURCE}" "${DIR}" "${BINARY_DIR}") + endforeach () + endif () + + set(PVS_STUDIO_PLOGS "${PVS_STUDIO_PLOGS}" PARENT_SCOPE) +endfunction () + +set(PVS_STUDIO_RECURSIVE_TARGETS) +set(PVS_STUDIO_RECURSIVE_TARGETS_NEW) + +macro(pvs_studio_get_recursive_targets TARGET) + get_target_property(libs "${TARGET}" LINK_LIBRARIES) + foreach (lib IN LISTS libs) + list(FIND PVS_STUDIO_RECURSIVE_TARGETS "${lib}" index) + if (TARGET "${lib}" AND "${index}" STREQUAL -1) + get_target_property(target_type "${lib}" TYPE) + if (NOT "${target_type}" STREQUAL "INTERFACE_LIBRARY") + list(APPEND PVS_STUDIO_RECURSIVE_TARGETS "${lib}") + list(APPEND PVS_STUDIO_RECURSIVE_TARGETS_NEW "${lib}") + pvs_studio_get_recursive_targets("${lib}") + endif () + endif () + endforeach () +endmacro() + +option(PVS_STUDIO_DISABLE OFF "Disable PVS-Studio targets") +option(PVS_STUDIO_DEBUG OFF "Add debug info") + +# pvs_studio_add_target +# Target options: +# ALL add PVS-Studio target to default build (default: off) +# TARGET target name of analysis target (default: pvs) +# ANALYZE targets... targets to analyze +# RECURSIVE analyze target's dependencies (requires CMake 3.5+) +# COMPILE_COMMANDS use compile_commands.json instead of targets (specified by the 'ANALYZE' option) to determine files for analysis +# (set CMAKE_EXPORT_COMPILE_COMMANDS, available only for Makefile and Ninja generators) +# +# Output options: +# OUTPUT prints report to stdout +# LOG path path to report (default: ${CMAKE_CURRENT_BINARY_DIR}/PVS-Studio.log) +# FORMAT format format of report +# MODE mode analyzers/levels filter (default: GA:1,2) +# HIDE_HELP do not print help message +# +# Analyzer options: +# PLATFORM name linux32/linux64 (default: linux64) +# PREPROCESSOR name preprocessor type: gcc/clang (default: auto detected) +# LICENSE path path to PVS-Studio.lic (default: ~/.config/PVS-Studio/PVS-Studio.lic) +# CONFIG path path to PVS-Studio.cfg +# CFG_TEXT text embedded PVS-Studio.cfg +# SUPPRESS_BASE path to suppress base file +# KEEP_COMBINED_PLOG do not delete combined plog file *.pvs.raw for further processing with plog-converter +# +# Misc options: +# DEPENDS targets.. additional target dependencies +# SOURCES path... list of source files to analyze +# BIN path path to pvs-studio-analyzer (Unix) or CompilerCommandsAnalyzer.exe (Windows) +# CONVERTER path path to plog-converter (Unix) or HtmlGenerator.exe (Windows) +# C_FLAGS flags... additional C_FLAGS +# CXX_FLAGS flags... additional CXX_FLAGS +# ARGS args... additional pvs-studio-analyzer/CompilerCommandsAnalyzer.exe flags +# CONVERTER_ARGS args... additional plog-converter/HtmlGenerator.exe flags +function (pvs_studio_add_target) + macro (default VAR VALUE) + if ("${${VAR}}" STREQUAL "") + set("${VAR}" "${VALUE}") + endif () + endmacro () + + set(PVS_STUDIO_SUPPORTED_PREPROCESSORS "gcc|clang|visualcpp") + if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + set(DEFAULT_PREPROCESSOR "clang") + elseif (MSVC) + set(DEFAULT_PREPROCESSOR "visualcpp") + else () + set(DEFAULT_PREPROCESSOR "gcc") + endif () + + set(OPTIONAL OUTPUT ALL RECURSIVE HIDE_HELP KEEP_COMBINED_PLOG COMPILE_COMMANDS KEEP_INTERMEDIATE_FILES) + set(SINGLE LICENSE CONFIG TARGET LOG FORMAT BIN CONVERTER PLATFORM PREPROCESSOR CFG_TEXT SUPPRESS_BASE) + set(MULTI SOURCES C_FLAGS CXX_FLAGS ARGS DEPENDS ANALYZE MODE CONVERTER_ARGS) + cmake_parse_arguments(PVS_STUDIO "${OPTIONAL}" "${SINGLE}" "${MULTI}" ${ARGN}) + + + default(PVS_STUDIO_C_FLAGS "") + default(PVS_STUDIO_CXX_FLAGS "") + default(PVS_STUDIO_TARGET "pvs") + default(PVS_STUDIO_LOG "PVS-Studio.log") + + set(PATHS) + + if (WIN32) + # The registry value is only read when you do some cache operation on it. + # https://stackoverflow.com/questions/1762201/reading-registry-values-with-cmake + GET_FILENAME_COMPONENT(ROOT "[HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\ProgramVerificationSystems\\PVS-Studio;installDir]" ABSOLUTE CACHE) + + if(EXISTS "${ROOT}") + set(PATHS "${ROOT}") + else() + set(ROOT "PROGRAMFILES(X86)") + set(ROOT "$ENV{${ROOT}}/PVS-Studio") + string(REPLACE \\ / ROOT "${ROOT}") + + if (EXISTS "${ROOT}") + set(PATHS "${ROOT}") + else() + set(ROOT "PATH") + set(ROOT "$ENV{${ROOT}}") + set(PATHS "${ROOT}") + endif () + endif() + + + + default(PVS_STUDIO_BIN "CompilerCommandsAnalyzer.exe") + default(PVS_STUDIO_CONVERTER "HtmlGenerator.exe") + else () + default(PVS_STUDIO_BIN "pvs-studio-analyzer") + default(PVS_STUDIO_CONVERTER "plog-converter") + endif () + + find_program(PVS_STUDIO_BIN_PATH "${PVS_STUDIO_BIN}" ${PATHS}) + set(PVS_STUDIO_BIN "${PVS_STUDIO_BIN_PATH}") + + if (NOT EXISTS "${PVS_STUDIO_BIN}") + message(FATAL_ERROR "pvs-studio-analyzer is not found") + endif () + + find_program(PVS_STUDIO_CONVERTER_PATH "${PVS_STUDIO_CONVERTER}" ${PATHS}) + set(PVS_STUDIO_CONVERTER "${PVS_STUDIO_CONVERTER_PATH}") + + if (NOT EXISTS "${PVS_STUDIO_CONVERTER}") + message(FATAL_ERROR "plog-converter is not found") + endif () + + default(PVS_STUDIO_MODE "GA:1,2") + default(PVS_STUDIO_PREPROCESSOR "${DEFAULT_PREPROCESSOR}") + if (WIN32) + default(PVS_STUDIO_PLATFORM "x64") + else () + default(PVS_STUDIO_PLATFORM "linux64") + endif () + + string(REPLACE ";" "+" PVS_STUDIO_MODE "${PVS_STUDIO_MODE}") + + if ("${PVS_STUDIO_CONFIG}" STREQUAL "" AND NOT "${PVS_STUDIO_CFG_TEXT}" STREQUAL "") + set(PVS_STUDIO_CONFIG "${CMAKE_BINARY_DIR}/PVS-Studio.cfg") + + set(PVS_STUDIO_CONFIG_COMMAND "${CMAKE_COMMAND}" -E echo "${PVS_STUDIO_CFG_TEXT}" > "${PVS_STUDIO_CONFIG}") + + add_custom_command(OUTPUT "${PVS_STUDIO_CONFIG}" + COMMAND ${PVS_STUDIO_CONFIG_COMMAND} + WORKING_DIRECTORY "${BINARY_DIR}" + COMMENT "Generating PVS-Studio.cfg") + + list(APPEND PVS_STUDIO_DEPENDS "${PVS_STUDIO_CONFIG}") + endif () + if (NOT "${PVS_STUDIO_PREPROCESSOR}" MATCHES "^${PVS_STUDIO_SUPPORTED_PREPROCESSORS}$") + message(FATAL_ERROR "Preprocessor ${PVS_STUDIO_PREPROCESSOR} isn't supported. Available options: ${PVS_STUDIO_SUPPORTED_PREPROCESSORS}.") + endif () + + pvs_studio_append_standard_flag(PVS_STUDIO_CXX_FLAGS "${CMAKE_CXX_STANDARD}") + pvs_studio_set_directory_flags("${CMAKE_CURRENT_SOURCE_DIR}" PVS_STUDIO_CXX_FLAGS PVS_STUDIO_C_FLAGS) + + if (NOT "${PVS_STUDIO_LICENSE}" STREQUAL "") + list(APPEND PVS_STUDIO_ARGS --lic-file "${PVS_STUDIO_LICENSE}") + endif () + + if (NOT ${PVS_STUDIO_CONFIG} STREQUAL "") + list(APPEND PVS_STUDIO_ARGS --cfg "${PVS_STUDIO_CONFIG}") + endif () + + list(APPEND PVS_STUDIO_ARGS --platform "${PVS_STUDIO_PLATFORM}" + --preprocessor "${PVS_STUDIO_PREPROCESSOR}") + + if (NOT "${PVS_STUDIO_SUPPRESS_BASE}" STREQUAL "") + pvs_studio_join_path(PVS_STUDIO_SUPPRESS_BASE "${CMAKE_CURRENT_SOURCE_DIR}" "${PVS_STUDIO_SUPPRESS_BASE}") + list(APPEND PVS_STUDIO_ARGS --suppress-file "${PVS_STUDIO_SUPPRESS_BASE}") + endif () + + if (NOT "${CMAKE_CXX_COMPILER}" STREQUAL "") + list(APPEND PVS_STUDIO_ARGS --cxx "${CMAKE_CXX_COMPILER}") + endif () + + if (NOT "${CMAKE_C_COMPILER}" STREQUAL "") + list(APPEND PVS_STUDIO_ARGS --cc "${CMAKE_C_COMPILER}") + endif () + + if (PVS_STUDIO_KEEP_INTERMEDIATE_FILES) + list(APPEND PVS_STUDIO_ARGS --dump-files) + endif() + + string(REGEX REPLACE [123,:] "" ANALYZER_MODE ${PVS_STUDIO_MODE}) + if (NOT "$ANALYZER_MODE" STREQUAL "GA") + list (APPEND PVS_STUDIO_ARGS -a "${ANALYZER_MODE}") + endif () + + set(PVS_STUDIO_PLOGS "") + + set(PVS_STUDIO_RECURSIVE_TARGETS_NEW) + if (${PVS_STUDIO_RECURSIVE}) + foreach (TARGET IN LISTS PVS_STUDIO_ANALYZE) + list(APPEND PVS_STUDIO_RECURSIVE_TARGETS_NEW "${TARGET}") + pvs_studio_get_recursive_targets("${TARGET}") + endforeach () + endif () + + set(inc_path) + + foreach (TARGET ${PVS_STUDIO_ANALYZE}) + set(DIR "${CMAKE_CURRENT_SOURCE_DIR}") + string(FIND "${TARGET}" ":" DELIM) + if ("${DELIM}" GREATER "-1") + math(EXPR DELIMI "${DELIM}+1") + string(SUBSTRING "${TARGET}" "${DELIMI}" "-1" DIR) + string(SUBSTRING "${TARGET}" "0" "${DELIM}" TARGET) + pvs_studio_join_path(DIR "${CMAKE_CURRENT_SOURCE_DIR}" "${DIR}") + else () + get_target_property(TARGET_SOURCE_DIR "${TARGET}" SOURCE_DIR) + if (EXISTS "${TARGET_SOURCE_DIR}") + set(DIR "${TARGET_SOURCE_DIR}") + endif () + endif () + pvs_studio_analyze_target("${TARGET}" "${DIR}") + list(APPEND PVS_STUDIO_DEPENDS "${TARGET}") + + if ("${inc_path}" STREQUAL "") + set(inc_path "$") + else () + set(inc_path "${inc_path}$$") + endif () + endforeach () + + foreach (TARGET ${PVS_STUDIO_RECURSIVE_TARGETS_NEW}) + set(DIR "${CMAKE_CURRENT_SOURCE_DIR}") + get_target_property(TARGET_SOURCE_DIR "${TARGET}" SOURCE_DIR) + if (EXISTS "${TARGET_SOURCE_DIR}") + set(DIR "${TARGET_SOURCE_DIR}") + endif () + pvs_studio_analyze_target("${TARGET}" "${DIR}") + list(APPEND PVS_STUDIO_DEPENDS "${TARGET}") + endforeach () + + set(PVS_STUDIO_TARGET_CXX_FLAGS "") + set(PVS_STUDIO_TARGET_C_FLAGS "") + foreach (SOURCE ${PVS_STUDIO_SOURCES}) + pvs_studio_analyze_file("${SOURCE}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}") + endforeach () + + if (PVS_STUDIO_COMPILE_COMMANDS) + set(COMPILE_COMMANDS_LOG "${PVS_STUDIO_LOG}.pvs.analyzer.raw") + if (NOT CMAKE_EXPORT_COMPILE_COMMANDS) + message(FATAL_ERROR "You should set CMAKE_EXPORT_COMPILE_COMMANDS to TRUE") + endif () + add_custom_command( + OUTPUT "${COMPILE_COMMANDS_LOG}" + COMMAND "${PVS_STUDIO_BIN}" analyze -i + --output-file "${COMPILE_COMMANDS_LOG}.always" + ${PVS_STUDIO_ARGS} + COMMENT "Analyzing with PVS-Studio" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + DEPENDS "${PVS_STUDIO_SUPPRESS_BASE}" "${PVS_STUDIO_DEPENDS}" + ) + list(APPEND PVS_STUDIO_PLOGS_LOGS "${COMPILE_COMMANDS_LOG}.always") + list(APPEND PVS_STUDIO_PLOGS_DEPENDENCIES "${COMPILE_COMMANDS_LOG}") + endif () + + pvs_studio_relative_path(LOG_RELATIVE "${CMAKE_BINARY_DIR}" "${PVS_STUDIO_LOG}") + if (PVS_STUDIO_PLOGS OR PVS_STUDIO_COMPILE_COMMANDS) + if (WIN32) + string(REPLACE / \\ PVS_STUDIO_PLOGS "${PVS_STUDIO_PLOGS}") + endif () + if (WIN32) + set(COMMANDS COMMAND type ${PVS_STUDIO_PLOGS} ${PVS_STUDIO_PLOGS_LOGS} > "${PVS_STUDIO_LOG}" 2>nul || cd .) + else () + set(COMMANDS COMMAND cat ${PVS_STUDIO_PLOGS} ${PVS_STUDIO_PLOGS_LOGS} > "${PVS_STUDIO_LOG}" 2>/dev/null || true) + endif () + set(COMMENT "Generating ${LOG_RELATIVE}") + if (NOT "${PVS_STUDIO_FORMAT}" STREQUAL "" OR PVS_STUDIO_OUTPUT) + if ("${PVS_STUDIO_FORMAT}" STREQUAL "") + set(PVS_STUDIO_FORMAT "errorfile") + endif () + set(converter_no_help "") + if (PVS_STUDIO_HIDE_HELP) + set(converter_no_help "--noHelpMessages") + endif() + list(APPEND COMMANDS + COMMAND "${CMAKE_COMMAND}" -E remove -f "${PVS_STUDIO_LOG}.pvs.raw" + COMMAND "${CMAKE_COMMAND}" -E rename "${PVS_STUDIO_LOG}" "${PVS_STUDIO_LOG}.pvs.raw" + COMMAND "${PVS_STUDIO_CONVERTER}" "${PVS_STUDIO_CONVERTER_ARGS}" ${converter_no_help} -t "${PVS_STUDIO_FORMAT}" "${PVS_STUDIO_LOG}.pvs.raw" -o "${PVS_STUDIO_LOG}" -a "${PVS_STUDIO_MODE}" + ) + if(NOT PVS_STUDIO_KEEP_COMBINED_PLOG) + list(APPEND COMMANDS COMMAND "${CMAKE_COMMAND}" -E remove -f "${PVS_STUDIO_LOG}.pvs.raw") + endif() + endif () + else () + set(COMMANDS COMMAND "${CMAKE_COMMAND}" -E touch "${PVS_STUDIO_LOG}") + set(COMMENT "Generating ${LOG_RELATIVE}: no sources found") + endif () + + if (WIN32) + string(REPLACE / \\ PVS_STUDIO_LOG "${PVS_STUDIO_LOG}") + endif () + + if (CMAKE_GENERATOR STREQUAL "Unix Makefiles") + get_filename_component(LOG_NAME ${LOG_RELATIVE} NAME) + set(LOG_TARGET "${PVS_STUDIO_TARGET}-${LOG_NAME}-log") + add_custom_target("${LOG_TARGET}" + BYPRODUCTS "${PVS_STUDIO_LOG}" + ${COMMANDS} + COMMENT "${COMMENT}" + DEPENDS ${PVS_STUDIO_PLOGS} ${PVS_STUDIO_PLOGS_DEPENDENCIES} + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") + else() + set(LOG_TARGET "${PVS_STUDIO_LOG}") + add_custom_command(OUTPUT "${LOG_TARGET}" + ${COMMANDS} + COMMENT "${COMMENT}" + DEPENDS ${PVS_STUDIO_PLOGS} ${PVS_STUDIO_PLOGS_DEPENDENCIES} + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") + endif() + + if (PVS_STUDIO_ALL) + set(ALL "ALL") + else () + set(ALL "") + endif () + + if (PVS_STUDIO_OUTPUT) + if (WIN32) + set(COMMANDS COMMAND type "${PVS_STUDIO_LOG}" 1>&2) + else () + set(COMMANDS COMMAND cat "${PVS_STUDIO_LOG}" 1>&2) + endif() + else () + set(COMMANDS "") + endif () + + set(props_file "${CMAKE_BINARY_DIR}/${PVS_STUDIO_TARGET}.user.props") + file(WRITE "${props_file}" [=[ + + + + + + + + true + + + +]=]) + + add_custom_target("${PVS_STUDIO_TARGET}" ${ALL} ${COMMANDS} + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + DEPENDS ${PVS_STUDIO_DEPENDS} "${LOG_TARGET}") + set_target_properties("${PVS_STUDIO_TARGET}" PROPERTIES VS_USER_PROPS "${props_file}") + + # A workaround to add implicit dependencies of source files from include directories + set_target_properties("${PVS_STUDIO_TARGET}" PROPERTIES INCLUDE_DIRECTORIES "${inc_path}") +endfunction () diff --git a/cmake/Protobuf.cmake b/cmake/Protobuf.cmake index aef75b91077..8e0a93251d2 100644 --- a/cmake/Protobuf.cmake +++ b/cmake/Protobuf.cmake @@ -1,11 +1,9 @@ -# Copyright © 2017-2022 Trust Wallet. +# SPDX-License-Identifier: Apache-2.0 # -# This file is part of Trust. The full Trust copyright notice, including -# terms governing use, modification, and redistribution, is contained in the -# file LICENSE at the root of the source code distribution tree. +# Copyright © 2017 Trust Wallet. -set(protobuf_SOURCE_DIR ${CMAKE_SOURCE_DIR}/build/local/src/protobuf/protobuf-3.19.2) -set(protobuf_source_dir ${CMAKE_SOURCE_DIR}/build/local/src/protobuf/protobuf-3.19.2) +set(protobuf_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../build/local/src/protobuf/protobuf-3.19.2) +set(protobuf_source_dir ${CMAKE_CURRENT_LIST_DIR}/../build/local/src/protobuf/protobuf-3.19.2) # sort + uniq -u # https://github.com/protocolbuffers/protobuf/blob/master/cmake/libprotobuf.cmake @@ -202,7 +200,7 @@ add_library(protobuf ${protobuf_SOURCE_FILES} ${protobuf_HEADER_FILES}) set_target_properties( protobuf PROPERTIES - CXX_STANDARD 17 + CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON IMPORTED_CONFIGURATIONS Release INCLUDE_DIRECTORIES ${protobuf_source_dir}/src diff --git a/cmake/StandardSettings.cmake b/cmake/StandardSettings.cmake new file mode 100644 index 00000000000..bef5a651b52 --- /dev/null +++ b/cmake/StandardSettings.cmake @@ -0,0 +1,86 @@ +# +# Default settings +# +set(CMAKE_CXX_VISIBILITY_PRESET hidden) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS 1) +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version" FORCE) + +# +# IDE Settings +# +option(TW_IDE_CLION "Enable if your IDE is CLion" OFF) +option(TW_IDE_VSCODE "Enable if your IDE is VSCode" OFF) + +# +# Build Settings +# +option(TW_UNITY_BUILD "Enable Unity build for TrustWalletCore and unit tests." OFF) + +# +# Static analyzers +# +# Currently supporting: Clang-Tidy, PVS-Studio. +option(TW_ENABLE_CLANG_TIDY "Enable static analysis with Clang-Tidy." OFF) +option(TW_ENABLE_PVS_STUDIO "Enable static analysis with PVS-Studio." OFF) + +# +# Runtime analyzers +# +# Currently supporting: Clang ASAN. +option(TW_CLANG_ASAN "Enable ASAN dynamic address sanitizer" OFF) + +# +# Specific platforms support +# +# Currently supporting: Wasm. +option(TW_COMPILE_WASM "Target Wasm" OFF) + +# +# Coverage +# +option(TW_CODE_COVERAGE "Enable coverage reporting" OFF) + +# +# Compiler warnings options +# +option(TW_WARNINGS_AS_ERRORS "Compiler Options as Error" OFF) + +# +# Compilation Speed options +# +option(TW_ENABLE_CCACHE "Enable the usage of Ccache, in order to speed up rebuild times." ON) + +if (TW_ENABLE_CCACHE) + find_program(CCACHE_FOUND ccache) + if (CCACHE_FOUND) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) + message(STATUS "ccache activated") + endif () +endif () + +# +# Tests/Examples options +# +option(TW_UNIT_TESTS "Enable the unit tests of the project" ON) +option(TW_BUILD_EXAMPLES "Enable the examples builds of the project" ON) + +if (ANDROID OR IOS_PLATFORM OR TW_COMPILE_WASM OR TW_COMPILE_JAVA) + set(TW_UNIT_TESTS OFF) + set(TW_BUILD_EXAMPLES OFF) +endif() + +if (TW_UNIT_TESTS) + message(STATUS "Native unit tests activated") +else() + message(STATUS "Native unit tests skipped") +endif() + +if (TW_BUILD_EXAMPLES) + message(STATUS "Native examples activated") +else() + message(STATUS "Native examples skipped") +endif() + + diff --git a/cmake/StaticAnalyzers.cmake b/cmake/StaticAnalyzers.cmake new file mode 100644 index 00000000000..bb06683916e --- /dev/null +++ b/cmake/StaticAnalyzers.cmake @@ -0,0 +1,37 @@ +if (TW_ENABLE_CLANG_TIDY) + macro(tw_add_clang_tidy_target target) + find_program(CLANGTIDY clang-tidy) + if (CLANGTIDY) + set_property( + TARGET ${target} + PROPERTY CXX_CLANG_TIDY clang-tidy;-extra-arg=-Wno-unknown-warning-option) + message("Clang-Tidy finished setting up.") + else () + message(SEND_ERROR "Clang-Tidy requested but executable not found.") + endif () + endmacro() +endif () + +if (TW_ENABLE_PVS_STUDIO) + macro(tw_add_pvs_studio_target target) + message(STATUS "PVS-Studio analyzer enabled - ${CMAKE_SOURCE_DIR}/tools/pvs-studio/config.cfg") + include(cmake/PVS-Studio.cmake) + if (TW_IDE_VSCODE) + pvs_studio_add_target(TARGET TrustWalletCore.analyze ALL + OUTPUT FORMAT sarif-vscode + ANALYZE ${target} + MODE GA:1,2 + LOG result.sarif + CONFIG ${CMAKE_SOURCE_DIR}/tools/pvs-studio/config.cfg + ) + else () + pvs_studio_add_target(TARGET TrustWalletCore.analyze ALL + OUTPUT FORMAT json + ANALYZE ${target} + MODE GA:1,2 + LOG result.json + CONFIG ${CMAKE_SOURCE_DIR}/tools/pvs-studio/config.cfg + ) + endif () + endmacro() +endif () diff --git a/cmake/Wasm.cmake b/cmake/Wasm.cmake deleted file mode 100644 index 5775243f42c..00000000000 --- a/cmake/Wasm.cmake +++ /dev/null @@ -1,79 +0,0 @@ -# Copyright © 2017-2022 Trust Wallet. -# -# This file is part of Trust. The full Trust copyright notice, including -# terms governing use, modification, and redistribution, is contained in the -# file LICENSE at the root of the source code distribution tree. - -cmake_minimum_required(VERSION 3.8 FATAL_ERROR) - -project(TrustWalletCore) - -include(GNUInstallDirs) - -message(STATUS "Building for emscripten") - -# Configure warnings -set(TW_CXX_WARNINGS "-Wshorten-64-to-32") -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TW_CXX_WARNINGS}") -set(CMAKE_EXPORT_COMPILE_COMMANDS 1) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) -set(CMAKE_CXX_VISIBILITY_PRESET hidden) - -if ("$ENV{PREFIX}" STREQUAL "") - set(PREFIX "${CMAKE_SOURCE_DIR}/build/local") -else() - set(PREFIX "$ENV{PREFIX}") -endif() - -# Configure CCache if available -find_program(CCACHE_FOUND ccache) -if(CCACHE_FOUND) - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) -endif(CCACHE_FOUND) - -include_directories(${PREFIX}/include) -link_directories(${PREFIX}/lib) - -add_subdirectory(trezor-crypto) -add_subdirectory(wasm) - -find_package(Boost) - -include(cmake/Protobuf.cmake) - -file(GLOB_RECURSE sources src/*.c src/*.cc src/*.cpp src/*.h) -add_library(TrustWalletCore ${sources} ${PROTO_SRCS} ${PROTO_HDRS}) - -target_link_libraries(TrustWalletCore PRIVATE TrezorCrypto protobuf Boost::boost) -target_compile_options(TrustWalletCore PRIVATE "-Wall") - -set_target_properties(TrustWalletCore - PROPERTIES - CXX_STANDARD 17 - CXX_STANDARD_REQUIRED ON -) - -target_include_directories(TrustWalletCore - PUBLIC - $ - $ - PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src - ${CMAKE_CURRENT_SOURCE_DIR}/build/local/include -) - -install(TARGETS TrustWalletCore - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) - -install( - DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/ - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/WalletCore - FILES_MATCHING PATTERN "*.h" -) - -install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/codegen-v2/Cargo.lock b/codegen-v2/Cargo.lock new file mode 100644 index 00000000000..6ed11b1c48a --- /dev/null +++ b/codegen-v2/Cargo.lock @@ -0,0 +1,385 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "codegen-v2" +version = "0.1.0" +dependencies = [ + "aho-corasick", + "convert_case", + "handlebars", + "heck", + "pathdiff", + "serde", + "serde_json", + "serde_yaml", + "toml_edit", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "handlebars" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faa67bab9ff362228eb3d00bd024a4965d8231bbb7921167f0cfa66c6626b225" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "indexmap" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "pest" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0d24f72393fd16ab6ac5738bc33cdb6a9aa73f8b902e8fe29cf4e67d7dd1026" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc17e2a6c7d0a492f0158d7a4bd66cc17280308bbaff78d5bef566dca35ab80" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934cd7631c050f4674352a6e835d5f6711ffbfb9345c2fc0107155ac495ae293" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] diff --git a/codegen-v2/Cargo.toml b/codegen-v2/Cargo.toml new file mode 100644 index 00000000000..43c2afea1c6 --- /dev/null +++ b/codegen-v2/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "codegen-v2" +version = "0.1.0" +edition = "2021" + +[lib] +name = "libparser" +path = "src/lib.rs" + +[[bin]] +name = "parser" +path = "src/main.rs" + +[dependencies] +aho-corasick = "1.1.2" +convert_case = "0.6.0" +pathdiff = "0.2.1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_yaml = "0.9.21" +toml_edit = "0.21.0" +handlebars = "4.3.6" +heck = "0.4.1" diff --git a/codegen-v2/README.md b/codegen-v2/README.md new file mode 100644 index 00000000000..33e018ac407 --- /dev/null +++ b/codegen-v2/README.md @@ -0,0 +1,14 @@ +# About + +This is a _work-in-progress_ parser meant to deprecate the existing Ruby parser +in `codegen/`. As of now, we only support Swift binding generation. This project +will progress over multiple stages (PRs). + +## Execution + +```bash +$ cd codegen-v2 +$ cargo run -- swift +``` + +The bindings are saved to `bindings/`. diff --git a/codegen-v2/manifest/TWAES.yaml b/codegen-v2/manifest/TWAES.yaml new file mode 100644 index 00000000000..24637e9155b --- /dev/null +++ b/codegen-v2/manifest/TWAES.yaml @@ -0,0 +1,134 @@ +name: TWAES +structs: +- name: TWAES + is_public: true + is_class: false + fields: + - - unused + - variant: u_int8_t + is_constant: false + is_nullable: false + is_pointer: false +functions: +- name: TWAESEncryptCBC + is_public: true + is_static: true + params: + - name: key + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: iv + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: mode + type: + variant: enum + value: TWAESPaddingMode + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWAESDecryptCBC + is_public: true + is_static: true + params: + - name: key + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: iv + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: mode + type: + variant: enum + value: TWAESPaddingMode + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWAESEncryptCTR + is_public: true + is_static: true + params: + - name: key + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: iv + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWAESDecryptCTR + is_public: true + is_static: true + params: + - name: key + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: iv + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true diff --git a/codegen-v2/manifest/TWAESPaddingMode.yaml b/codegen-v2/manifest/TWAESPaddingMode.yaml new file mode 100644 index 00000000000..2776625e45f --- /dev/null +++ b/codegen-v2/manifest/TWAESPaddingMode.yaml @@ -0,0 +1,11 @@ +name: TWAESPaddingMode +enums: +- name: TWAESPaddingMode + is_public: true + value_type: + variant: u_int32_t + variants: + - name: zero + value: 0 + - name: pkcs7 + value: 1 diff --git a/codegen-v2/manifest/TWAccount.yaml b/codegen-v2/manifest/TWAccount.yaml new file mode 100644 index 00000000000..4a2be534e60 --- /dev/null +++ b/codegen-v2/manifest/TWAccount.yaml @@ -0,0 +1,95 @@ +name: TWAccount +structs: +- name: TWAccount + is_public: true + is_class: true +inits: +- name: TWAccountCreate + is_public: true + is_nullable: false + params: + - name: address + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: derivation + type: + variant: enum + value: TWDerivation + is_constant: false + is_nullable: false + is_pointer: false + - name: derivationPath + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: publicKey + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: extendedPublicKey + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +deinits: +- name: TWAccountDelete +properties: +- name: TWAccountAddress + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWAccountCoin + is_public: true + return_type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false +- name: TWAccountDerivation + is_public: true + return_type: + variant: enum + value: TWDerivation + is_constant: false + is_nullable: false + is_pointer: false +- name: TWAccountDerivationPath + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWAccountPublicKey + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWAccountExtendedPublicKey + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWAeternityProto.yaml b/codegen-v2/manifest/TWAeternityProto.yaml new file mode 100644 index 00000000000..4e2479caffb --- /dev/null +++ b/codegen-v2/manifest/TWAeternityProto.yaml @@ -0,0 +1,4 @@ +name: TWAeternityProto +protos: +- TW_Aeternity_Proto_SigningInput +- TW_Aeternity_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWAionProto.yaml b/codegen-v2/manifest/TWAionProto.yaml new file mode 100644 index 00000000000..f081c9c56dc --- /dev/null +++ b/codegen-v2/manifest/TWAionProto.yaml @@ -0,0 +1,4 @@ +name: TWAionProto +protos: +- TW_Aion_Proto_SigningInput +- TW_Aion_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWAlgorandProto.yaml b/codegen-v2/manifest/TWAlgorandProto.yaml new file mode 100644 index 00000000000..23e6f9e283d --- /dev/null +++ b/codegen-v2/manifest/TWAlgorandProto.yaml @@ -0,0 +1,7 @@ +name: TWAlgorandProto +protos: +- TW_Algorand_Proto_Transfer +- TW_Algorand_Proto_AssetTransfer +- TW_Algorand_Proto_AssetOptIn +- TW_Algorand_Proto_SigningInput +- TW_Algorand_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWAnyAddress.yaml b/codegen-v2/manifest/TWAnyAddress.yaml new file mode 100644 index 00000000000..52ce8860dd7 --- /dev/null +++ b/codegen-v2/manifest/TWAnyAddress.yaml @@ -0,0 +1,308 @@ +name: TWAnyAddress +structs: +- name: TWPublicKey + is_public: false + is_class: false +- name: TWAnyAddress + is_public: true + is_class: true +inits: +- name: TWAnyAddressCreateWithString + is_public: true + is_nullable: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false +- name: TWAnyAddressCreateBech32 + is_public: true + is_nullable: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: hrp + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWAnyAddressCreateSS58 + is_public: true + is_nullable: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: ss58Prefix + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWAnyAddressCreateWithPublicKey + is_public: true + is_nullable: false + params: + - name: publicKey + type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false +- name: TWAnyAddressCreateWithPublicKeyDerivation + is_public: true + is_nullable: false + params: + - name: publicKey + type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: derivation + type: + variant: enum + value: TWDerivation + is_constant: false + is_nullable: false + is_pointer: false +- name: TWAnyAddressCreateBech32WithPublicKey + is_public: true + is_nullable: false + params: + - name: publicKey + type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: hrp + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWAnyAddressCreateSS58WithPublicKey + is_public: true + is_nullable: false + params: + - name: publicKey + type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: ss58Prefix + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWAnyAddressCreateWithPublicKeyFilecoinAddressType + is_public: true + is_nullable: false + params: + - name: publicKey + type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true + - name: filecoinAddressType + type: + variant: enum + value: TWFilecoinAddressType + is_constant: false + is_nullable: false + is_pointer: false +deinits: +- name: TWAnyAddressDelete +functions: +- name: TWAnyAddressEqual + is_public: true + is_static: true + params: + - name: lhs + type: + variant: struct + value: TWAnyAddress + is_constant: false + is_nullable: false + is_pointer: true + - name: rhs + type: + variant: struct + value: TWAnyAddress + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWAnyAddressIsValid + is_public: true + is_static: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWAnyAddressIsValidBech32 + is_public: true + is_static: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: hrp + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWAnyAddressIsValidSS58 + is_public: true + is_static: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: ss58Prefix + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +properties: +- name: TWAnyAddressDescription + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWAnyAddressCoin + is_public: true + return_type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false +- name: TWAnyAddressData + is_public: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWAptosProto.yaml b/codegen-v2/manifest/TWAptosProto.yaml new file mode 100644 index 00000000000..f32f51b7632 --- /dev/null +++ b/codegen-v2/manifest/TWAptosProto.yaml @@ -0,0 +1,14 @@ +name: TWAptosProto +protos: +- TW_Aptos_Proto_TransferMessage +- TW_Aptos_Proto_StructTag +- TW_Aptos_Proto_TokenTransferMessage +- TW_Aptos_Proto_ManagedTokensRegisterMessage +- TW_Aptos_Proto_CreateAccountMessage +- TW_Aptos_Proto_OfferNftMessage +- TW_Aptos_Proto_CancelOfferNftMessage +- TW_Aptos_Proto_ClaimNftMessage +- TW_Aptos_Proto_NftMessage +- TW_Aptos_Proto_SigningInput +- TW_Aptos_Proto_TransactionAuthenticator +- TW_Aptos_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWBarz.yaml b/codegen-v2/manifest/TWBarz.yaml new file mode 100644 index 00000000000..8383f1c05cc --- /dev/null +++ b/codegen-v2/manifest/TWBarz.yaml @@ -0,0 +1,21 @@ +name: TWBarz +structs: + - name: TWBarz + is_public: true + is_class: false +functions: + - name: TWBarzGetCounterfactualAddress + is_public: true + is_static: true + params: + - name: input + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true \ No newline at end of file diff --git a/codegen-v2/manifest/TWBase32.yaml b/codegen-v2/manifest/TWBase32.yaml new file mode 100644 index 00000000000..3c1dd778457 --- /dev/null +++ b/codegen-v2/manifest/TWBase32.yaml @@ -0,0 +1,78 @@ +name: TWBase32 +structs: +- name: TWBase32 + is_public: true + is_class: false +functions: +- name: TWBase32DecodeWithAlphabet + is_public: true + is_static: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: alphabet + type: + variant: string + is_constant: true + is_nullable: true + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWBase32Decode + is_public: true + is_static: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWBase32EncodeWithAlphabet + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: alphabet + type: + variant: string + is_constant: true + is_nullable: true + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWBase32Encode + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWBase58.yaml b/codegen-v2/manifest/TWBase58.yaml new file mode 100644 index 00000000000..4806a101d67 --- /dev/null +++ b/codegen-v2/manifest/TWBase58.yaml @@ -0,0 +1,66 @@ +name: TWBase58 +structs: +- name: TWBase58 + is_public: true + is_class: false +functions: +- name: TWBase58Encode + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWBase58EncodeNoCheck + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWBase58Decode + is_public: true + is_static: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWBase58DecodeNoCheck + is_public: true + is_static: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true diff --git a/codegen-v2/manifest/TWBase64.yaml b/codegen-v2/manifest/TWBase64.yaml new file mode 100644 index 00000000000..bcb60202257 --- /dev/null +++ b/codegen-v2/manifest/TWBase64.yaml @@ -0,0 +1,66 @@ +name: TWBase64 +structs: +- name: TWBase64 + is_public: true + is_class: false +functions: +- name: TWBase64Decode + is_public: true + is_static: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWBase64DecodeUrl + is_public: true + is_static: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWBase64Encode + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWBase64EncodeUrl + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWBinanceProto.yaml b/codegen-v2/manifest/TWBinanceProto.yaml new file mode 100644 index 00000000000..44483799529 --- /dev/null +++ b/codegen-v2/manifest/TWBinanceProto.yaml @@ -0,0 +1,25 @@ +name: TWBinanceProto +protos: +- TW_Binance_Proto_Transaction +- TW_Binance_Proto_Signature +- TW_Binance_Proto_TradeOrder +- TW_Binance_Proto_CancelTradeOrder +- TW_Binance_Proto_SendOrder +- TW_Binance_Proto_TokenIssueOrder +- TW_Binance_Proto_TokenMintOrder +- TW_Binance_Proto_TokenBurnOrder +- TW_Binance_Proto_TokenFreezeOrder +- TW_Binance_Proto_TokenUnfreezeOrder +- TW_Binance_Proto_HTLTOrder +- TW_Binance_Proto_DepositHTLTOrder +- TW_Binance_Proto_ClaimHTLOrder +- TW_Binance_Proto_RefundHTLTOrder +- TW_Binance_Proto_TransferOut +- TW_Binance_Proto_SideChainDelegate +- TW_Binance_Proto_SideChainRedelegate +- TW_Binance_Proto_SideChainUndelegate +- TW_Binance_Proto_TimeLockOrder +- TW_Binance_Proto_TimeRelockOrder +- TW_Binance_Proto_TimeUnlockOrder +- TW_Binance_Proto_SigningInput +- TW_Binance_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWBitcoinAddress.yaml b/codegen-v2/manifest/TWBitcoinAddress.yaml new file mode 100644 index 00000000000..3e92416eba5 --- /dev/null +++ b/codegen-v2/manifest/TWBitcoinAddress.yaml @@ -0,0 +1,124 @@ +name: TWBitcoinAddress +structs: +- name: TWPublicKey + is_public: false + is_class: false +- name: TWBitcoinAddress + is_public: true + is_class: true +inits: +- name: TWBitcoinAddressCreateWithString + is_public: true + is_nullable: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWBitcoinAddressCreateWithData + is_public: true + is_nullable: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWBitcoinAddressCreateWithPublicKey + is_public: true + is_nullable: true + params: + - name: publicKey + type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true + - name: prefix + type: + variant: u_int8_t + is_constant: false + is_nullable: false + is_pointer: false +deinits: +- name: TWBitcoinAddressDelete +functions: +- name: TWBitcoinAddressEqual + is_public: true + is_static: true + params: + - name: lhs + type: + variant: struct + value: TWBitcoinAddress + is_constant: false + is_nullable: false + is_pointer: true + - name: rhs + type: + variant: struct + value: TWBitcoinAddress + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWBitcoinAddressIsValid + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWBitcoinAddressIsValidString + is_public: true + is_static: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +properties: +- name: TWBitcoinAddressDescription + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWBitcoinAddressPrefix + is_public: true + return_type: + variant: u_int8_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWBitcoinAddressKeyhash + is_public: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWBitcoinMessageSigner.yaml b/codegen-v2/manifest/TWBitcoinMessageSigner.yaml new file mode 100644 index 00000000000..e97a4209b14 --- /dev/null +++ b/codegen-v2/manifest/TWBitcoinMessageSigner.yaml @@ -0,0 +1,61 @@ +name: TWBitcoinMessageSigner +structs: +- name: TWBitcoinMessageSigner + is_public: true + is_class: false +functions: +- name: TWBitcoinMessageSignerSignMessage + is_public: true + is_static: true + params: + - name: privateKey + type: + variant: struct + value: TWPrivateKey + is_constant: true + is_nullable: false + is_pointer: true + - name: address + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: message + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWBitcoinMessageSignerVerifyMessage + is_public: true + is_static: true + params: + - name: address + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: message + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: signature + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false diff --git a/codegen-v2/manifest/TWBitcoinProto.yaml b/codegen-v2/manifest/TWBitcoinProto.yaml new file mode 100644 index 00000000000..7bc66e0c655 --- /dev/null +++ b/codegen-v2/manifest/TWBitcoinProto.yaml @@ -0,0 +1,12 @@ +name: TWBitcoinProto +protos: +- TW_Bitcoin_Proto_Transaction +- TW_Bitcoin_Proto_TransactionInput +- TW_Bitcoin_Proto_OutPoint +- TW_Bitcoin_Proto_TransactionOutput +- TW_Bitcoin_Proto_UnspentTransaction +- TW_Bitcoin_Proto_SigningInput +- TW_Bitcoin_Proto_TransactionPlan +- TW_Bitcoin_Proto_SigningOutput +- TW_Bitcoin_Proto_HashPublicKey +- TW_Bitcoin_Proto_PreSigningOutput diff --git a/codegen-v2/manifest/TWBitcoinScript.yaml b/codegen-v2/manifest/TWBitcoinScript.yaml new file mode 100644 index 00000000000..1a06577cd1a --- /dev/null +++ b/codegen-v2/manifest/TWBitcoinScript.yaml @@ -0,0 +1,321 @@ +name: TWBitcoinScript +structs: +- name: TWBitcoinScript + is_public: true + is_class: true +inits: +- name: TWBitcoinScriptCreate + is_public: true + is_nullable: false +- name: TWBitcoinScriptCreateWithData + is_public: true + is_nullable: false + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWBitcoinScriptCreateCopy + is_public: true + is_nullable: false + params: + - name: script + type: + variant: struct + value: TWBitcoinScript + is_constant: true + is_nullable: false + is_pointer: true +deinits: +- name: TWBitcoinScriptDelete +functions: +- name: TWBitcoinScriptEqual + is_public: true + is_static: true + params: + - name: lhs + type: + variant: struct + value: TWBitcoinScript + is_constant: true + is_nullable: false + is_pointer: true + - name: rhs + type: + variant: struct + value: TWBitcoinScript + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWBitcoinScriptMatchPayToPubkey + is_public: true + is_static: false + params: + - name: script + type: + variant: struct + value: TWBitcoinScript + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWBitcoinScriptMatchPayToPubkeyHash + is_public: true + is_static: false + params: + - name: script + type: + variant: struct + value: TWBitcoinScript + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWBitcoinScriptMatchPayToScriptHash + is_public: true + is_static: false + params: + - name: script + type: + variant: struct + value: TWBitcoinScript + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWBitcoinScriptMatchPayToWitnessPublicKeyHash + is_public: true + is_static: false + params: + - name: script + type: + variant: struct + value: TWBitcoinScript + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWBitcoinScriptMatchPayToWitnessScriptHash + is_public: true + is_static: false + params: + - name: script + type: + variant: struct + value: TWBitcoinScript + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWBitcoinScriptEncode + is_public: true + is_static: false + params: + - name: script + type: + variant: struct + value: TWBitcoinScript + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWBitcoinScriptBuildPayToPublicKey + is_public: true + is_static: true + params: + - name: pubkey + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWBitcoinScript + is_constant: false + is_nullable: false + is_pointer: true +- name: TWBitcoinScriptBuildPayToPublicKeyHash + is_public: true + is_static: true + params: + - name: hash + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWBitcoinScript + is_constant: false + is_nullable: false + is_pointer: true +- name: TWBitcoinScriptBuildPayToScriptHash + is_public: true + is_static: true + params: + - name: scriptHash + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWBitcoinScript + is_constant: false + is_nullable: false + is_pointer: true +- name: TWBitcoinScriptBuildPayToWitnessPubkeyHash + is_public: true + is_static: true + params: + - name: hash + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWBitcoinScript + is_constant: false + is_nullable: false + is_pointer: true +- name: TWBitcoinScriptBuildPayToWitnessScriptHash + is_public: true + is_static: true + params: + - name: scriptHash + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWBitcoinScript + is_constant: false + is_nullable: false + is_pointer: true +- name: TWBitcoinScriptLockScriptForAddress + is_public: true + is_static: true + params: + - name: address + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: struct + value: TWBitcoinScript + is_constant: false + is_nullable: false + is_pointer: true +- name: TWBitcoinScriptHashTypeForCoin + is_public: true + is_static: true + params: + - name: coinType + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false +properties: +- name: TWBitcoinScriptSize + is_public: true + return_type: + variant: size_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWBitcoinScriptData + is_public: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWBitcoinScriptScriptHash + is_public: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWBitcoinScriptIsPayToScriptHash + is_public: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWBitcoinScriptIsPayToWitnessScriptHash + is_public: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWBitcoinScriptIsPayToWitnessPublicKeyHash + is_public: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWBitcoinScriptIsWitnessProgram + is_public: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false diff --git a/codegen-v2/manifest/TWBitcoinSigHashType.yaml b/codegen-v2/manifest/TWBitcoinSigHashType.yaml new file mode 100644 index 00000000000..27e2c5f339f --- /dev/null +++ b/codegen-v2/manifest/TWBitcoinSigHashType.yaml @@ -0,0 +1,52 @@ +name: TWBitcoinSigHashType +enums: +- name: TWBitcoinSigHashType + is_public: true + value_type: + variant: u_int32_t + variants: + - name: all + value: 0x01 + - name: none + value: 0x02 + - name: single + value: 0x03 + - name: fork + value: 0x40 + - name: forkBtg + value: 0x4f40 + - name: anyoneCanPay + value: 0x80 +functions: +- name: TWBitcoinSigHashTypeIsSingle + is_public: true + is_static: false + params: + - name: type + type: + variant: enum + value: TWBitcoinSigHashType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWBitcoinSigHashTypeIsNone + is_public: true + is_static: false + params: + - name: type + type: + variant: enum + value: TWBitcoinSigHashType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false diff --git a/codegen-v2/manifest/TWBlockchain.yaml b/codegen-v2/manifest/TWBlockchain.yaml new file mode 100644 index 00000000000..545fafee77b --- /dev/null +++ b/codegen-v2/manifest/TWBlockchain.yaml @@ -0,0 +1,97 @@ +name: TWBlockchain +enums: +- name: TWBlockchain + is_public: true + value_type: + variant: u_int32_t + variants: + - name: bitcoin + value: 0 + - name: ethereum + value: 1 + - name: vechain + value: 3 + - name: tron + value: 4 + - name: icon + value: 5 + - name: binance + value: 6 + - name: ripple + value: 7 + - name: tezos + value: 8 + - name: nimiq + value: 9 + - name: stellar + value: 10 + - name: aion + value: 11 + - name: cosmos + value: 12 + - name: theta + value: 13 + - name: ontology + value: 14 + - name: zilliqa + value: 15 + - name: ioTeX + value: 16 + - name: eos + value: 17 + - name: nano + value: 18 + - name: nuls + value: 19 + - name: waves + value: 20 + - name: aeternity + value: 21 + - name: nebulas + value: 22 + - name: fio + value: 23 + - name: solana + value: 24 + - name: harmony + value: 25 + - name: near + value: 26 + - name: algorand + value: 27 + - name: polkadot + value: 29 + - name: cardano + value: 30 + - name: neo + value: 31 + - name: filecoin + value: 32 + - name: multiversX + value: 33 + - name: oasisNetwork + value: 34 + - name: decred + value: 35 + - name: zcash + value: 36 + - name: groestlcoin + value: 37 + - name: thorchain + value: 38 + - name: ronin + value: 39 + - name: kusama + value: 40 + - name: nervos + value: 41 + - name: everscale + value: 42 + - name: aptos + value: 43 + - name: hedera + value: 44 + - name: theOpenNetwork + value: 45 + - name: sui + value: 46 diff --git a/codegen-v2/manifest/TWCardano.yaml b/codegen-v2/manifest/TWCardano.yaml new file mode 100644 index 00000000000..d6489ffa40e --- /dev/null +++ b/codegen-v2/manifest/TWCardano.yaml @@ -0,0 +1,63 @@ +name: TWCardano +structs: +- name: TWCardano + is_public: true + is_class: false +functions: +- name: TWCardanoMinAdaAmount + is_public: true + is_static: true + params: + - name: tokenBundle + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: u_int64_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWCardanoOutputMinAdaAmount + is_public: true + is_static: true + params: + - name: toAddress + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: tokenBundle + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: coinsPerUtxoByte + type: + variant: u_int64_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: u_int64_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWCardanoGetStakingAddress + is_public: true + is_static: true + params: + - name: baseAddress + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWCardanoProto.yaml b/codegen-v2/manifest/TWCardanoProto.yaml new file mode 100644 index 00000000000..3d01f415f85 --- /dev/null +++ b/codegen-v2/manifest/TWCardanoProto.yaml @@ -0,0 +1,15 @@ +name: TWCardanoProto +protos: +- TW_Cardano_Proto_OutPoint +- TW_Cardano_Proto_TokenAmount +- TW_Cardano_Proto_TxInput +- TW_Cardano_Proto_TxOutput +- TW_Cardano_Proto_TokenBundle +- TW_Cardano_Proto_Transfer +- TW_Cardano_Proto_RegisterStakingKey +- TW_Cardano_Proto_DeregisterStakingKey +- TW_Cardano_Proto_Delegate +- TW_Cardano_Proto_Withdraw +- TW_Cardano_Proto_TransactionPlan +- TW_Cardano_Proto_SigningInput +- TW_Cardano_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWCoinType.yaml b/codegen-v2/manifest/TWCoinType.yaml new file mode 100644 index 00000000000..af01f64569b --- /dev/null +++ b/codegen-v2/manifest/TWCoinType.yaml @@ -0,0 +1,465 @@ +name: TWCoinType +structs: +- name: TWPrivateKey + is_public: false + is_class: false +- name: TWPublicKey + is_public: false + is_class: false +enums: +- name: TWCoinType + is_public: true + value_type: + variant: u_int32_t + variants: + - name: aeternity + value: 457 + - name: aion + value: 425 + - name: binance + value: 714 + - name: bitcoin + value: 0 + - name: bitcoinCash + value: 145 + - name: bitcoinGold + value: 156 + - name: callisto + value: 820 + - name: cardano + value: 1815 + - name: cosmos + value: 118 + - name: dash + value: 5 + - name: decred + value: 42 + - name: digiByte + value: 20 + - name: dogecoin + value: 3 + - name: eos + value: 194 + - name: wax + value: 14001 + - name: ethereum + value: 60 + - name: ethereumClassic + value: 61 + - name: fio + value: 235 + - name: goChain + value: 6060 + - name: groestlcoin + value: 17 + - name: icon + value: 74 + - name: ioTeX + value: 304 + - name: kava + value: 459 + - name: kin + value: 2017 + - name: litecoin + value: 2 + - name: monacoin + value: 22 + - name: nebulas + value: 2718 + - name: nuls + value: 8964 + - name: nano + value: 165 + - name: near + value: 397 + - name: nimiq + value: 242 + - name: ontology + value: 1024 + - name: poanetwork + value: 178 + - name: qtum + value: 2301 + - name: xrp + value: 144 + - name: solana + value: 501 + - name: stellar + value: 148 + - name: tezos + value: 1729 + - name: theta + value: 500 + - name: thunderCore + value: 1001 + - name: neo + value: 888 + - name: tomoChain + value: 889 + - name: tron + value: 195 + - name: veChain + value: 818 + - name: viacoin + value: 14 + - name: wanchain + value: 5718350 + - name: zcash + value: 133 + - name: firo + value: 136 + - name: zilliqa + value: 313 + - name: zelcash + value: 19167 + - name: ravencoin + value: 175 + - name: waves + value: 5741564 + - name: terra + value: 330 + - name: terraV2 + value: 10000330 + - name: harmony + value: 1023 + - name: algorand + value: 283 + - name: kusama + value: 434 + - name: polkadot + value: 354 + - name: filecoin + value: 461 + - name: multiversX + value: 508 + - name: bandChain + value: 494 + - name: smartChainLegacy + value: 10000714 + - name: smartChain + value: 20000714 + - name: oasis + value: 474 + - name: polygon + value: 966 + - name: thorchain + value: 931 + - name: bluzelle + value: 483 + - name: optimism + value: 10000070 + - name: zksync + value: 10000324 + - name: arbitrum + value: 10042221 + - name: ecochain + value: 10000553 + - name: avalancheCChain + value: 10009000 + - name: xdai + value: 10000100 + - name: fantom + value: 10000250 + - name: cryptoOrg + value: 394 + - name: celo + value: 52752 + - name: ronin + value: 10002020 + - name: osmosis + value: 10000118 + - name: ecash + value: 899 + - name: cronosChain + value: 10000025 + - name: smartBitcoinCash + value: 10000145 + - name: kuCoinCommunityChain + value: 10000321 + - name: boba + value: 10000288 + - name: metis + value: 10001088 + - name: aurora + value: 1323161554 + - name: evmos + value: 10009001 + - name: nativeEvmos + value: 20009001 + - name: moonriver + value: 10001285 + - name: moonbeam + value: 10001284 + - name: kavaEvm + value: 10002222 + - name: kaia + value: 10008217 + - name: meter + value: 18000 + - name: okxchain + value: 996 + - name: nervos + value: 309 + - name: everscale + value: 396 + - name: aptos + value: 637 + - name: hedera + value: 3030 + - name: secret + value: 529 + - name: nativeInjective + value: 10000060 + - name: agoric + value: 564 + - name: ton + value: 607 + - name: sui + value: 784 + - name: stargaze + value: 20000118 + - name: polygonzkEVM + value: 10001101 + - name: juno + value: 30000118 + - name: stride + value: 40000118 + - name: axelar + value: 50000118 + - name: crescent + value: 60000118 + - name: kujira + value: 70000118 + - name: ioTeXEVM + value: 10004689 + - name: nativeCanto + value: 10007700 + - name: comdex + value: 80000118 + - name: neutron + value: 90000118 + - name: sommelier + value: 11000118 + - name: fetchAI + value: 12000118 + - name: mars + value: 13000118 + - name: umee + value: 14000118 + - name: coreum + value: 10000990 + - name: quasar + value: 15000118 + - name: persistence + value: 16000118 + - name: akash + value: 17000118 + - name: noble + value: 18000118 +functions: +- name: TWCoinTypeValidate + is_public: true + is_static: false + params: + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: address + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWCoinTypeDerivationPath + is_public: true + is_static: false + params: + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWCoinTypeDerivationPathWithDerivation + is_public: true + is_static: false + params: + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: derivation + type: + variant: enum + value: TWDerivation + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWCoinTypeDeriveAddress + is_public: true + is_static: false + params: + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: privateKey + type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWCoinTypeDeriveAddressFromPublicKey + is_public: true + is_static: false + params: + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: publicKey + type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +properties: +- name: TWCoinTypeBlockchain + is_public: true + return_type: + variant: enum + value: TWBlockchain + is_constant: false + is_nullable: false + is_pointer: false +- name: TWCoinTypePurpose + is_public: true + return_type: + variant: enum + value: TWPurpose + is_constant: false + is_nullable: false + is_pointer: false +- name: TWCoinTypeCurve + is_public: true + return_type: + variant: enum + value: TWCurve + is_constant: false + is_nullable: false + is_pointer: false +- name: TWCoinTypeXpubVersion + is_public: true + return_type: + variant: enum + value: TWHDVersion + is_constant: false + is_nullable: false + is_pointer: false +- name: TWCoinTypeXprvVersion + is_public: true + return_type: + variant: enum + value: TWHDVersion + is_constant: false + is_nullable: false + is_pointer: false +- name: TWCoinTypeHRP + is_public: true + return_type: + variant: enum + value: TWHRP + is_constant: false + is_nullable: false + is_pointer: false +- name: TWCoinTypeP2pkhPrefix + is_public: true + return_type: + variant: u_int8_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWCoinTypeP2shPrefix + is_public: true + return_type: + variant: u_int8_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWCoinTypeStaticPrefix + is_public: true + return_type: + variant: u_int8_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWCoinTypeChainId + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWCoinTypeSlip44Id + is_public: true + return_type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWCoinTypeSS58Prefix + is_public: true + return_type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWCoinTypePublicKeyType + is_public: true + return_type: + variant: enum + value: TWPublicKeyType + is_constant: false + is_nullable: false + is_pointer: false diff --git a/codegen-v2/manifest/TWCoinTypeConfiguration.yaml b/codegen-v2/manifest/TWCoinTypeConfiguration.yaml new file mode 100644 index 00000000000..aafa2b803cd --- /dev/null +++ b/codegen-v2/manifest/TWCoinTypeConfiguration.yaml @@ -0,0 +1,120 @@ +name: TWCoinTypeConfiguration +structs: +- name: TWCoinTypeConfiguration + is_public: true + is_class: false + fields: + - - unused + - variant: u_int8_t + is_constant: false + is_nullable: false + is_pointer: false +functions: +- name: TWCoinTypeConfigurationGetSymbol + is_public: true + is_static: true + params: + - name: type + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWCoinTypeConfigurationGetDecimals + is_public: true + is_static: true + params: + - name: type + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWCoinTypeConfigurationGetTransactionURL + is_public: true + is_static: true + params: + - name: type + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: transactionID + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWCoinTypeConfigurationGetAccountURL + is_public: true + is_static: true + params: + - name: type + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: accountID + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWCoinTypeConfigurationGetID + is_public: true + is_static: true + params: + - name: type + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWCoinTypeConfigurationGetName + is_public: true + is_static: true + params: + - name: type + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWCommonProto.yaml b/codegen-v2/manifest/TWCommonProto.yaml new file mode 100644 index 00000000000..c5bbdcc1546 --- /dev/null +++ b/codegen-v2/manifest/TWCommonProto.yaml @@ -0,0 +1,3 @@ +name: TWCommonProto +protos: +- TW_Common_Proto_SigningError diff --git a/codegen-v2/manifest/TWCosmosProto.yaml b/codegen-v2/manifest/TWCosmosProto.yaml new file mode 100644 index 00000000000..e9ddb053872 --- /dev/null +++ b/codegen-v2/manifest/TWCosmosProto.yaml @@ -0,0 +1,8 @@ +name: TWCosmosProto +protos: +- TW_Cosmos_Proto_Amount +- TW_Cosmos_Proto_Fee +- TW_Cosmos_Proto_Height +- TW_Cosmos_Proto_Message +- TW_Cosmos_Proto_SigningInput +- TW_Cosmos_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWCurve.yaml b/codegen-v2/manifest/TWCurve.yaml new file mode 100644 index 00000000000..01f14d2dfd5 --- /dev/null +++ b/codegen-v2/manifest/TWCurve.yaml @@ -0,0 +1,28 @@ +name: TWCurve +enums: +- name: TWCurve + is_public: true + value_type: + variant: u_int32_t + variants: + - name: secp256k1 + value: 0 + as_string: secp256k1 + - name: ed25519 + value: 1 + as_string: ed25519 + - name: ed25519Blake2bNano + value: 2 + as_string: ed25519-blake2b-nano + - name: curve25519 + value: 3 + as_string: curve25519 + - name: nist256p1 + value: 4 + as_string: nist256p1 + - name: ed25519ExtendedCardano + value: 5 + as_string: ed25519-cardano-seed + - name: starkex + value: 6 + as_string: starkex diff --git a/codegen-v2/manifest/TWDataVector.yaml b/codegen-v2/manifest/TWDataVector.yaml new file mode 100644 index 00000000000..f9011148096 --- /dev/null +++ b/codegen-v2/manifest/TWDataVector.yaml @@ -0,0 +1,74 @@ +name: TWDataVector +structs: +- name: TWDataVector + is_public: true + is_class: true +inits: +- name: TWDataVectorCreate + is_public: true + is_nullable: false +- name: TWDataVectorCreateWithData + is_public: true + is_nullable: false + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +deinits: +- name: TWDataVectorDelete +functions: +- name: TWDataVectorAdd + is_public: true + is_static: false + params: + - name: dataVector + type: + variant: struct + value: TWDataVector + is_constant: false + is_nullable: false + is_pointer: true + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: void + is_constant: false + is_nullable: false + is_pointer: false +- name: TWDataVectorGet + is_public: true + is_static: false + params: + - name: dataVector + type: + variant: struct + value: TWDataVector + is_constant: true + is_nullable: false + is_pointer: true + - name: index + type: + variant: size_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +properties: +- name: TWDataVectorSize + is_public: true + return_type: + variant: size_t + is_constant: false + is_nullable: false + is_pointer: false diff --git a/codegen-v2/manifest/TWDecredProto.yaml b/codegen-v2/manifest/TWDecredProto.yaml new file mode 100644 index 00000000000..eb8bddcc303 --- /dev/null +++ b/codegen-v2/manifest/TWDecredProto.yaml @@ -0,0 +1,6 @@ +name: TWDecredProto +protos: +- TW_Decred_Proto_Transaction +- TW_Decred_Proto_TransactionInput +- TW_Decred_Proto_TransactionOutput +- TW_Decred_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWDerivation.yaml b/codegen-v2/manifest/TWDerivation.yaml new file mode 100644 index 00000000000..c2143b80431 --- /dev/null +++ b/codegen-v2/manifest/TWDerivation.yaml @@ -0,0 +1,21 @@ +name: TWDerivation +enums: +- name: TWDerivation + is_public: true + value_type: + variant: u_int32_t + variants: + - name: default + value: 0 + - name: custom + value: 1 + - name: bitcoinSegwit + value: 2 + - name: bitcoinLegacy + value: 3 + - name: bitcoinTestnet + value: 4 + - name: litecoinLegacy + value: 5 + - name: solanaSolana + value: 6 diff --git a/codegen-v2/manifest/TWDerivationPath.yaml b/codegen-v2/manifest/TWDerivationPath.yaml new file mode 100644 index 00000000000..8e550ded65e --- /dev/null +++ b/codegen-v2/manifest/TWDerivationPath.yaml @@ -0,0 +1,137 @@ +name: TWDerivationPath +structs: +- name: TWDerivationPath + is_public: true + is_class: true +inits: +- name: TWDerivationPathCreate + is_public: true + is_nullable: false + params: + - name: purpose + type: + variant: enum + value: TWPurpose + is_constant: false + is_nullable: false + is_pointer: false + - name: coin + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + - name: account + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + - name: change + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + - name: address + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWDerivationPathCreateWithString + is_public: true + is_nullable: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +deinits: +- name: TWDerivationPathDelete +functions: +- name: TWDerivationPathIndexAt + is_public: true + is_static: false + params: + - name: path + type: + variant: struct + value: TWDerivationPath + is_constant: false + is_nullable: false + is_pointer: true + - name: index + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: struct + value: TWDerivationPathIndex + is_constant: false + is_nullable: true + is_pointer: true +- name: TWDerivationPathIndicesCount + is_public: true + is_static: false + params: + - name: path + type: + variant: struct + value: TWDerivationPath + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false +properties: +- name: TWDerivationPathPurpose + is_public: true + return_type: + variant: enum + value: TWPurpose + is_constant: false + is_nullable: false + is_pointer: false +- name: TWDerivationPathCoin + is_public: true + return_type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWDerivationPathAccount + is_public: true + return_type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWDerivationPathChange + is_public: true + return_type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWDerivationPathAddress + is_public: true + return_type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWDerivationPathDescription + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWDerivationPathIndex.yaml b/codegen-v2/manifest/TWDerivationPathIndex.yaml new file mode 100644 index 00000000000..8106efe6dfe --- /dev/null +++ b/codegen-v2/manifest/TWDerivationPathIndex.yaml @@ -0,0 +1,46 @@ +name: TWDerivationPathIndex +structs: +- name: TWDerivationPathIndex + is_public: true + is_class: true +inits: +- name: TWDerivationPathIndexCreate + is_public: true + is_nullable: false + params: + - name: value + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + - name: hardened + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +deinits: +- name: TWDerivationPathIndexDelete +properties: +- name: TWDerivationPathIndexValue + is_public: true + return_type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWDerivationPathIndexHardened + is_public: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWDerivationPathIndexDescription + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWEOSProto.yaml b/codegen-v2/manifest/TWEOSProto.yaml new file mode 100644 index 00000000000..ea0032abfe9 --- /dev/null +++ b/codegen-v2/manifest/TWEOSProto.yaml @@ -0,0 +1,5 @@ +name: TWEOSProto +protos: +- TW_EOS_Proto_Asset +- TW_EOS_Proto_SigningInput +- TW_EOS_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWEthereum.yaml b/codegen-v2/manifest/TWEthereum.yaml new file mode 100644 index 00000000000..1d4aa9b9d97 --- /dev/null +++ b/codegen-v2/manifest/TWEthereum.yaml @@ -0,0 +1,66 @@ +name: TWEthereum +structs: +- name: TWEthereum + is_public: true + is_class: false +functions: +- name: TWEthereumEip2645GetPath + is_public: true + is_static: true + params: + - name: ethAddress + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: layer + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: application + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: index + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumEip4337GetDeploymentAddress + is_public: true + is_static: true + params: + - name: factoryAddress + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: logicAddress + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: ownerAddress + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWEthereumAbi.yaml b/codegen-v2/manifest/TWEthereumAbi.yaml new file mode 100644 index 00000000000..fda88c4c4c1 --- /dev/null +++ b/codegen-v2/manifest/TWEthereumAbi.yaml @@ -0,0 +1,83 @@ +name: TWEthereumAbi +structs: +- name: TWEthereumAbiFunction + is_public: false + is_class: false +- name: TWEthereumAbi + is_public: true + is_class: false +functions: +- name: TWEthereumAbiEncode + is_public: true + is_static: true + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumAbiDecodeOutput + is_public: true + is_static: true + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: encoded + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiDecodeCall + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: abi + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: true + is_pointer: true +- name: TWEthereumAbiEncodeTyped + is_public: true + is_static: true + params: + - name: messageJson + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWEthereumAbiFunction.yaml b/codegen-v2/manifest/TWEthereumAbiFunction.yaml new file mode 100644 index 00000000000..95a54c8338d --- /dev/null +++ b/codegen-v2/manifest/TWEthereumAbiFunction.yaml @@ -0,0 +1,1213 @@ +name: TWEthereumAbiFunction +structs: +- name: TWEthereumAbiFunction + is_public: true + is_class: true +inits: +- name: TWEthereumAbiFunctionCreateWithString + is_public: true + is_nullable: false + params: + - name: name + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +deinits: +- name: TWEthereumAbiFunctionDelete +functions: +- name: TWEthereumAbiFunctionGetType + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumAbiFunctionAddParamUInt8 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: val + type: + variant: u_int8_t + is_constant: false + is_nullable: false + is_pointer: false + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamUInt16 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: val + type: + variant: u_int16_t + is_constant: false + is_nullable: false + is_pointer: false + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamUInt32 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: val + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamUInt64 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: val + type: + variant: u_int64_t + is_constant: false + is_nullable: false + is_pointer: false + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamUInt256 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: val + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamUIntN + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: bits + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamInt8 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: val + type: + variant: int8_t + is_constant: false + is_nullable: false + is_pointer: false + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamInt16 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: val + type: + variant: int16_t + is_constant: false + is_nullable: false + is_pointer: false + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamInt32 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: val + type: + variant: int32_t + is_constant: false + is_nullable: false + is_pointer: false + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamInt64 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: val + type: + variant: int64_t + is_constant: false + is_nullable: false + is_pointer: false + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamInt256 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: val + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamIntN + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: bits + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamBool + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: val + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamString + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: val + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamAddress + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: val + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamBytes + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: val + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamBytesFix + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: size + type: + variant: size_t + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddParamArray + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionGetParamUInt8 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: idx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: u_int8_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionGetParamUInt64 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: idx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: u_int64_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionGetParamUInt256 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: idx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumAbiFunctionGetParamBool + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: idx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionGetParamString + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: idx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumAbiFunctionGetParamAddress + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: idx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: isOutput + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumAbiFunctionAddInArrayParamUInt8 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: u_int8_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddInArrayParamUInt16 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: u_int16_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddInArrayParamUInt32 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddInArrayParamUInt64 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: u_int64_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddInArrayParamUInt256 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddInArrayParamUIntN + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: bits + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddInArrayParamInt8 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: int8_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddInArrayParamInt16 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: int16_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddInArrayParamInt32 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: int32_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddInArrayParamInt64 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: int64_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddInArrayParamInt256 + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddInArrayParamIntN + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: bits + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddInArrayParamBool + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddInArrayParamString + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddInArrayParamAddress + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddInArrayParamBytes + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWEthereumAbiFunctionAddInArrayParamBytesFix + is_public: true + is_static: false + params: + - name: fn + type: + variant: struct + value: TWEthereumAbiFunction + is_constant: false + is_nullable: false + is_pointer: true + - name: arrayIdx + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: size + type: + variant: size_t + is_constant: false + is_nullable: false + is_pointer: false + - name: val + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false diff --git a/codegen-v2/manifest/TWEthereumAbiValue.yaml b/codegen-v2/manifest/TWEthereumAbiValue.yaml new file mode 100644 index 00000000000..31e60cf14f8 --- /dev/null +++ b/codegen-v2/manifest/TWEthereumAbiValue.yaml @@ -0,0 +1,198 @@ +name: TWEthereumAbiValue +structs: +- name: TWEthereumAbiValue + is_public: true + is_class: false +functions: +- name: TWEthereumAbiValueEncodeBool + is_public: true + is_static: true + params: + - name: value + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumAbiValueEncodeInt32 + is_public: true + is_static: true + params: + - name: value + type: + variant: int32_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumAbiValueEncodeUInt32 + is_public: true + is_static: true + params: + - name: value + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumAbiValueEncodeInt256 + is_public: true + is_static: true + params: + - name: value + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumAbiValueEncodeUInt256 + is_public: true + is_static: true + params: + - name: value + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumAbiValueEncodeAddress + is_public: true + is_static: true + params: + - name: value + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumAbiValueEncodeString + is_public: true + is_static: true + params: + - name: value + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumAbiValueEncodeBytes + is_public: true + is_static: true + params: + - name: value + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumAbiValueEncodeBytesDyn + is_public: true + is_static: true + params: + - name: value + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumAbiValueDecodeUInt256 + is_public: true + is_static: true + params: + - name: input + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumAbiValueDecodeValue + is_public: true + is_static: true + params: + - name: input + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: type + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumAbiValueDecodeArray + is_public: true + is_static: true + params: + - name: input + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: type + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWEthereumChainID.yaml b/codegen-v2/manifest/TWEthereumChainID.yaml new file mode 100644 index 00000000000..a8c93f297cc --- /dev/null +++ b/codegen-v2/manifest/TWEthereumChainID.yaml @@ -0,0 +1,77 @@ +name: TWEthereumChainID +enums: +- name: TWEthereumChainID + is_public: true + value_type: + variant: u_int32_t + variants: + - name: ethereum + value: 1 + - name: classic + value: 61 + - name: poa + value: 99 + - name: vechain + value: 74 + - name: callisto + value: 820 + - name: tomochain + value: 88 + - name: polygon + value: 137 + - name: okc + value: 66 + - name: thundertoken + value: 108 + - name: gochain + value: 60 + - name: meter + value: 82 + - name: celo + value: 42220 + - name: wanchain + value: 888 + - name: cronos + value: 25 + - name: optimism + value: 10 + - name: xdai + value: 100 + - name: smartbch + value: 10000 + - name: fantom + value: 250 + - name: boba + value: 288 + - name: kcc + value: 321 + - name: zksync + value: 324 + - name: heco + value: 128 + - name: metis + value: 1088 + - name: polygonzkevm + value: 1101 + - name: moonbeam + value: 1284 + - name: moonriver + value: 1285 + - name: ronin + value: 2020 + - name: kavaevm + value: 2222 + - name: iotexevm + value: 4689 + - name: kaia + value: 8217 + - name: avalanchec + value: 43114 + - name: evmos + value: 9001 + - name: arbitrum + value: 42161 + - name: smartchain + value: 56 + - name: aurora + value: 1313161554 diff --git a/codegen-v2/manifest/TWEthereumMessageSigner.yaml b/codegen-v2/manifest/TWEthereumMessageSigner.yaml new file mode 100644 index 00000000000..628ed6cbf76 --- /dev/null +++ b/codegen-v2/manifest/TWEthereumMessageSigner.yaml @@ -0,0 +1,156 @@ +name: TWEthereumMessageSigner +structs: +- name: TWEthereumMessageSigner + is_public: true + is_class: false +functions: +- name: TWEthereumMessageSignerSignTypedMessage + is_public: true + is_static: true + params: + - name: privateKey + type: + variant: struct + value: TWPrivateKey + is_constant: true + is_nullable: false + is_pointer: true + - name: messageJson + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumMessageSignerSignTypedMessageEip155 + is_public: true + is_static: true + params: + - name: privateKey + type: + variant: struct + value: TWPrivateKey + is_constant: true + is_nullable: false + is_pointer: true + - name: messageJson + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: chainId + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumMessageSignerSignMessage + is_public: true + is_static: true + params: + - name: privateKey + type: + variant: struct + value: TWPrivateKey + is_constant: true + is_nullable: false + is_pointer: true + - name: message + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumMessageSignerSignMessageImmutableX + is_public: true + is_static: true + params: + - name: privateKey + type: + variant: struct + value: TWPrivateKey + is_constant: true + is_nullable: false + is_pointer: true + - name: message + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumMessageSignerSignMessageEip155 + is_public: true + is_static: true + params: + - name: privateKey + type: + variant: struct + value: TWPrivateKey + is_constant: true + is_nullable: false + is_pointer: true + - name: message + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: chainId + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWEthereumMessageSignerVerifyMessage + is_public: true + is_static: true + params: + - name: pubKey + type: + variant: struct + value: TWPublicKey + is_constant: true + is_nullable: false + is_pointer: true + - name: message + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: signature + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false diff --git a/codegen-v2/manifest/TWEthereumProto.yaml b/codegen-v2/manifest/TWEthereumProto.yaml new file mode 100644 index 00000000000..0d7451a69fe --- /dev/null +++ b/codegen-v2/manifest/TWEthereumProto.yaml @@ -0,0 +1,6 @@ +name: TWEthereumProto +protos: +- TW_Ethereum_Proto_Transaction +- TW_Ethereum_Proto_UserOperation +- TW_Ethereum_Proto_SigningInput +- TW_Ethereum_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWEverscaleProto.yaml b/codegen-v2/manifest/TWEverscaleProto.yaml new file mode 100644 index 00000000000..4b932b376aa --- /dev/null +++ b/codegen-v2/manifest/TWEverscaleProto.yaml @@ -0,0 +1,5 @@ +name: TWEverscaleProto +protos: +- TW_Everscale_Proto_Transfer +- TW_Everscale_Proto_SigningInput +- TW_Everscale_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWFIOAccount.yaml b/codegen-v2/manifest/TWFIOAccount.yaml new file mode 100644 index 00000000000..b95d7f7f461 --- /dev/null +++ b/codegen-v2/manifest/TWFIOAccount.yaml @@ -0,0 +1,26 @@ +name: TWFIOAccount +structs: +- name: TWFIOAccount + is_public: true + is_class: true +inits: +- name: TWFIOAccountCreateWithString + is_public: true + is_nullable: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +deinits: +- name: TWFIOAccountDelete +properties: +- name: TWFIOAccountDescription + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWFIOProto.yaml b/codegen-v2/manifest/TWFIOProto.yaml new file mode 100644 index 00000000000..84e46536c2f --- /dev/null +++ b/codegen-v2/manifest/TWFIOProto.yaml @@ -0,0 +1,8 @@ +name: TWFIOProto +protos: +- TW_FIO_Proto_PublicAddress +- TW_FIO_Proto_NewFundsContent +- TW_FIO_Proto_Action +- TW_FIO_Proto_ChainParams +- TW_FIO_Proto_SigningInput +- TW_FIO_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWFilecoinAddressConverter.yaml b/codegen-v2/manifest/TWFilecoinAddressConverter.yaml new file mode 100644 index 00000000000..1823914ebef --- /dev/null +++ b/codegen-v2/manifest/TWFilecoinAddressConverter.yaml @@ -0,0 +1,36 @@ +name: TWFilecoinAddressConverter +structs: +- name: TWFilecoinAddressConverter + is_public: true + is_class: false +functions: +- name: TWFilecoinAddressConverterConvertToEthereum + is_public: true + is_static: true + params: + - name: filecoinAddress + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWFilecoinAddressConverterConvertFromEthereum + is_public: true + is_static: true + params: + - name: ethAddress + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWFilecoinAddressType.yaml b/codegen-v2/manifest/TWFilecoinAddressType.yaml new file mode 100644 index 00000000000..d7b3e3186d6 --- /dev/null +++ b/codegen-v2/manifest/TWFilecoinAddressType.yaml @@ -0,0 +1,11 @@ +name: TWFilecoinAddressType +enums: +- name: TWFilecoinAddressType + is_public: true + value_type: + variant: u_int32_t + variants: + - name: default + value: 0 + - name: delegated + value: 1 diff --git a/codegen-v2/manifest/TWFilecoinProto.yaml b/codegen-v2/manifest/TWFilecoinProto.yaml new file mode 100644 index 00000000000..34e11a135a5 --- /dev/null +++ b/codegen-v2/manifest/TWFilecoinProto.yaml @@ -0,0 +1,4 @@ +name: TWFilecoinProto +protos: +- TW_Filecoin_Proto_SigningInput +- TW_Filecoin_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWGroestlcoinAddress.yaml b/codegen-v2/manifest/TWGroestlcoinAddress.yaml new file mode 100644 index 00000000000..8f25de34905 --- /dev/null +++ b/codegen-v2/manifest/TWGroestlcoinAddress.yaml @@ -0,0 +1,85 @@ +name: TWGroestlcoinAddress +structs: +- name: TWPublicKey + is_public: false + is_class: false +- name: TWGroestlcoinAddress + is_public: true + is_class: true +inits: +- name: TWGroestlcoinAddressCreateWithString + is_public: true + is_nullable: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWGroestlcoinAddressCreateWithPublicKey + is_public: true + is_nullable: false + params: + - name: publicKey + type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true + - name: prefix + type: + variant: u_int8_t + is_constant: false + is_nullable: false + is_pointer: false +deinits: +- name: TWGroestlcoinAddressDelete +functions: +- name: TWGroestlcoinAddressEqual + is_public: true + is_static: true + params: + - name: lhs + type: + variant: struct + value: TWGroestlcoinAddress + is_constant: false + is_nullable: false + is_pointer: true + - name: rhs + type: + variant: struct + value: TWGroestlcoinAddress + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWGroestlcoinAddressIsValidString + is_public: true + is_static: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +properties: +- name: TWGroestlcoinAddressDescription + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWHDVersion.yaml b/codegen-v2/manifest/TWHDVersion.yaml new file mode 100644 index 00000000000..72cfb07914a --- /dev/null +++ b/codegen-v2/manifest/TWHDVersion.yaml @@ -0,0 +1,52 @@ +name: TWHDVersion +enums: +- name: TWHDVersion + is_public: true + value_type: + variant: u_int32_t + variants: + - name: none + value: 0 + - name: xpub + value: 0x0488b21e + - name: xprv + value: 0x0488ade4 + - name: ypub + value: 0x049d7cb2 + - name: yprv + value: 0x049d7878 + - name: zpub + value: 0x04b24746 + - name: zprv + value: 0x04b2430c + - name: ltub + value: 0x019da462 + - name: ltpv + value: 0x019d9cfe + - name: mtub + value: 0x01b26ef6 + - name: mtpv + value: 0x01b26792 + - name: dpub + value: 0x2fda926 + - name: dprv + value: 0x2fda4e8 + - name: dgub + value: 0x02facafd + - name: dgpv + value: 0x02fac398 +properties: +- name: TWHDVersionIsPublic + is_public: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWHDVersionIsPrivate + is_public: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false diff --git a/codegen-v2/manifest/TWHDWallet.yaml b/codegen-v2/manifest/TWHDWallet.yaml new file mode 100644 index 00000000000..34dd244a3c9 --- /dev/null +++ b/codegen-v2/manifest/TWHDWallet.yaml @@ -0,0 +1,626 @@ +name: TWHDWallet +structs: +- name: TWHDWallet + is_public: true + is_class: true +inits: +- name: TWHDWalletCreate + is_public: true + is_nullable: true + params: + - name: strength + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false + - name: passphrase + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHDWalletCreateWithMnemonic + is_public: true + is_nullable: true + params: + - name: mnemonic + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: passphrase + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHDWalletCreateWithMnemonicCheck + is_public: true + is_nullable: true + params: + - name: mnemonic + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: passphrase + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: check + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWHDWalletCreateWithEntropy + is_public: true + is_nullable: true + params: + - name: entropy + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: passphrase + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +deinits: +- name: TWHDWalletDelete +functions: +- name: TWHDWalletGetMasterKey + is_public: true + is_static: false + params: + - name: wallet + type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: false + is_pointer: true + - name: curve + type: + variant: enum + value: TWCurve + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true +- name: TWHDWalletGetKeyForCoin + is_public: true + is_static: false + params: + - name: wallet + type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true +- name: TWHDWalletGetAddressForCoin + is_public: true + is_static: false + params: + - name: wallet + type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHDWalletGetAddressDerivation + is_public: true + is_static: false + params: + - name: wallet + type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: derivation + type: + variant: enum + value: TWDerivation + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHDWalletGetKey + is_public: true + is_static: false + params: + - name: wallet + type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: derivationPath + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true +- name: TWHDWalletGetKeyDerivation + is_public: true + is_static: false + params: + - name: wallet + type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: derivation + type: + variant: enum + value: TWDerivation + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true +- name: TWHDWalletGetKeyByCurve + is_public: true + is_static: false + params: + - name: wallet + type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: false + is_pointer: true + - name: curve + type: + variant: enum + value: TWCurve + is_constant: false + is_nullable: false + is_pointer: false + - name: derivationPath + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true +- name: TWHDWalletGetDerivedKey + is_public: true + is_static: false + params: + - name: wallet + type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: account + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + - name: change + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + - name: address + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true +- name: TWHDWalletGetExtendedPrivateKey + is_public: true + is_static: false + params: + - name: wallet + type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: false + is_pointer: true + - name: purpose + type: + variant: enum + value: TWPurpose + is_constant: false + is_nullable: false + is_pointer: false + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: version + type: + variant: enum + value: TWHDVersion + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHDWalletGetExtendedPublicKey + is_public: true + is_static: false + params: + - name: wallet + type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: false + is_pointer: true + - name: purpose + type: + variant: enum + value: TWPurpose + is_constant: false + is_nullable: false + is_pointer: false + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: version + type: + variant: enum + value: TWHDVersion + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHDWalletGetExtendedPrivateKeyAccount + is_public: true + is_static: false + params: + - name: wallet + type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: false + is_pointer: true + - name: purpose + type: + variant: enum + value: TWPurpose + is_constant: false + is_nullable: false + is_pointer: false + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: derivation + type: + variant: enum + value: TWDerivation + is_constant: false + is_nullable: false + is_pointer: false + - name: version + type: + variant: enum + value: TWHDVersion + is_constant: false + is_nullable: false + is_pointer: false + - name: account + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHDWalletGetExtendedPublicKeyAccount + is_public: true + is_static: false + params: + - name: wallet + type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: false + is_pointer: true + - name: purpose + type: + variant: enum + value: TWPurpose + is_constant: false + is_nullable: false + is_pointer: false + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: derivation + type: + variant: enum + value: TWDerivation + is_constant: false + is_nullable: false + is_pointer: false + - name: version + type: + variant: enum + value: TWHDVersion + is_constant: false + is_nullable: false + is_pointer: false + - name: account + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHDWalletGetExtendedPrivateKeyDerivation + is_public: true + is_static: false + params: + - name: wallet + type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: false + is_pointer: true + - name: purpose + type: + variant: enum + value: TWPurpose + is_constant: false + is_nullable: false + is_pointer: false + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: derivation + type: + variant: enum + value: TWDerivation + is_constant: false + is_nullable: false + is_pointer: false + - name: version + type: + variant: enum + value: TWHDVersion + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHDWalletGetExtendedPublicKeyDerivation + is_public: true + is_static: false + params: + - name: wallet + type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: false + is_pointer: true + - name: purpose + type: + variant: enum + value: TWPurpose + is_constant: false + is_nullable: false + is_pointer: false + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: derivation + type: + variant: enum + value: TWDerivation + is_constant: false + is_nullable: false + is_pointer: false + - name: version + type: + variant: enum + value: TWHDVersion + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHDWalletGetPublicKeyFromExtended + is_public: true + is_static: true + params: + - name: extended + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: derivationPath + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: true + is_pointer: true +properties: +- name: TWHDWalletSeed + is_public: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHDWalletMnemonic + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHDWalletEntropy + is_public: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWHRP.yaml b/codegen-v2/manifest/TWHRP.yaml new file mode 100644 index 00000000000..3b02a2f6520 --- /dev/null +++ b/codegen-v2/manifest/TWHRP.yaml @@ -0,0 +1,190 @@ +name: TWHRP +enums: +- name: TWHRP + is_public: true + value_type: + variant: u_int32_t + variants: + - name: unknown + value: 0 + as_string: '' + - name: bitcoin + value: 1 + as_string: bc + - name: litecoin + value: 2 + as_string: ltc + - name: viacoin + value: 3 + as_string: via + - name: groestlcoin + value: 4 + as_string: grs + - name: digiByte + value: 5 + as_string: dgb + - name: monacoin + value: 6 + as_string: mona + - name: cosmos + value: 7 + as_string: cosmos + - name: bitcoinCash + value: 8 + as_string: bitcoincash + - name: bitcoinGold + value: 9 + as_string: btg + - name: ioTeX + value: 10 + as_string: io + - name: nervos + value: 11 + as_string: ckb + - name: zilliqa + value: 12 + as_string: zil + - name: terra + value: 13 + as_string: terra + - name: cryptoOrg + value: 14 + as_string: cro + - name: kava + value: 15 + as_string: kava + - name: oasis + value: 16 + as_string: oasis + - name: bluzelle + value: 17 + as_string: bluzelle + - name: bandChain + value: 18 + as_string: band + - name: multiversX + value: 19 + as_string: erd + - name: secret + value: 20 + as_string: secret + - name: agoric + value: 21 + as_string: agoric + - name: binance + value: 22 + as_string: bnb + - name: ecash + value: 23 + as_string: ecash + - name: thorchain + value: 24 + as_string: thor + - name: harmony + value: 25 + as_string: one + - name: cardano + value: 26 + as_string: addr + - name: qtum + value: 27 + as_string: qc + - name: nativeInjective + value: 28 + as_string: inj + - name: osmosis + value: 29 + as_string: osmo + - name: terraV2 + value: 30 + as_string: terra + - name: coreum + value: 31 + as_string: core + - name: nativeCanto + value: 32 + as_string: canto + - name: sommelier + value: 33 + as_string: somm + - name: fetchAI + value: 34 + as_string: fetch + - name: mars + value: 35 + as_string: mars + - name: umee + value: 36 + as_string: umee + - name: quasar + value: 37 + as_string: quasar + - name: persistence + value: 38 + as_string: persistence + - name: akash + value: 39 + as_string: akash + - name: noble + value: 40 + as_string: noble + - name: stargaze + value: 41 + as_string: stars + - name: nativeEvmos + value: 42 + as_string: evmos + - name: juno + value: 43 + as_string: juno + - name: stride + value: 44 + as_string: stride + - name: axelar + value: 45 + as_string: axelar + - name: crescent + value: 46 + as_string: cre + - name: kujira + value: 47 + as_string: kujira + - name: comdex + value: 48 + as_string: comdex + - name: neutron + value: 49 + as_string: neutron +functions: +- name: stringForHRP + is_public: false + is_static: false + params: + - name: hrp + type: + variant: enum + value: TWHRP + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: char + is_constant: true + is_nullable: true + is_pointer: true +- name: hrpForString + is_public: false + is_static: false + params: + - name: string + type: + variant: char + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: enum + value: TWHRP + is_constant: false + is_nullable: false + is_pointer: false diff --git a/codegen-v2/manifest/TWHarmonyProto.yaml b/codegen-v2/manifest/TWHarmonyProto.yaml new file mode 100644 index 00000000000..b11c9c5c6ca --- /dev/null +++ b/codegen-v2/manifest/TWHarmonyProto.yaml @@ -0,0 +1,14 @@ +name: TWHarmonyProto +protos: +- TW_Harmony_Proto_SigningInput +- TW_Harmony_Proto_SigningOutput +- TW_Harmony_Proto_TransactionMessage +- TW_Harmony_Proto_StakingMessage +- TW_Harmony_Proto_Description +- TW_Harmony_Proto_Decimal +- TW_Harmony_Proto_CommissionRate +- TW_Harmony_Proto_DirectiveCreateValidator +- TW_Harmony_Proto_DirectiveEditValidator +- TW_Harmony_Proto_DirectiveDelegate +- TW_Harmony_Proto_DirectiveUndelegate +- TW_Harmony_Proto_DirectiveCollectRewards diff --git a/codegen-v2/manifest/TWHash.yaml b/codegen-v2/manifest/TWHash.yaml new file mode 100644 index 00000000000..6f900c4cb9d --- /dev/null +++ b/codegen-v2/manifest/TWHash.yaml @@ -0,0 +1,288 @@ +name: TWHash +structs: +- name: TWHash + is_public: true + is_class: false + fields: + - - unused + - variant: u_int8_t + is_constant: false + is_nullable: false + is_pointer: false +functions: +- name: TWHashSHA1 + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashSHA256 + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashSHA512 + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashSHA512_256 + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashKeccak256 + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashKeccak512 + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashSHA3_256 + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashSHA3_512 + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashRIPEMD + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashBlake256 + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashBlake2b + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: size + type: + variant: size_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashGroestl512 + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashSHA256SHA256 + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashSHA256RIPEMD + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashSHA3_256RIPEMD + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashBlake256Blake256 + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashBlake256RIPEMD + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWHashGroestl512Groestl512 + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWHederaProto.yaml b/codegen-v2/manifest/TWHederaProto.yaml new file mode 100644 index 00000000000..96074d6e307 --- /dev/null +++ b/codegen-v2/manifest/TWHederaProto.yaml @@ -0,0 +1,8 @@ +name: TWHederaProto +protos: +- TW_Hedera_Proto_Timestamp +- TW_Hedera_Proto_TransactionID +- TW_Hedera_Proto_TransferMessage +- TW_Hedera_Proto_TransactionBody +- TW_Hedera_Proto_SigningInput +- TW_Hedera_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWIconProto.yaml b/codegen-v2/manifest/TWIconProto.yaml new file mode 100644 index 00000000000..86868b8dc30 --- /dev/null +++ b/codegen-v2/manifest/TWIconProto.yaml @@ -0,0 +1,4 @@ +name: TWIconProto +protos: +- TW_Icon_Proto_SigningInput +- TW_Icon_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWIoTeXProto.yaml b/codegen-v2/manifest/TWIoTeXProto.yaml new file mode 100644 index 00000000000..c05c82dcb2b --- /dev/null +++ b/codegen-v2/manifest/TWIoTeXProto.yaml @@ -0,0 +1,9 @@ +name: TWIoTeXProto +protos: +- TW_IoTeX_Proto_Transfer +- TW_IoTeX_Proto_Staking +- TW_IoTeX_Proto_ContractCall +- TW_IoTeX_Proto_SigningInput +- TW_IoTeX_Proto_SigningOutput +- TW_IoTeX_Proto_ActionCore +- TW_IoTeX_Proto_Action diff --git a/codegen-v2/manifest/TWLiquidStaking.yaml b/codegen-v2/manifest/TWLiquidStaking.yaml new file mode 100644 index 00000000000..e5cbae32b2d --- /dev/null +++ b/codegen-v2/manifest/TWLiquidStaking.yaml @@ -0,0 +1,21 @@ +name: TWLiquidStaking +structs: +- name: TWLiquidStaking + is_public: true + is_class: false +functions: +- name: TWLiquidStakingBuildRequest + is_public: true + is_static: true + params: + - name: input + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWLiquidStakingProto.yaml b/codegen-v2/manifest/TWLiquidStakingProto.yaml new file mode 100644 index 00000000000..f678e946b79 --- /dev/null +++ b/codegen-v2/manifest/TWLiquidStakingProto.yaml @@ -0,0 +1,9 @@ +name: TWLiquidStakingProto +protos: +- TW_LiquidStaking_Proto_Status +- TW_LiquidStaking_Proto_Asset +- TW_LiquidStaking_Proto_Stake +- TW_LiquidStaking_Proto_Unstake +- TW_LiquidStaking_Proto_Withdraw +- TW_LiquidStaking_Proto_Input +- TW_LiquidStaking_Proto_Output diff --git a/codegen-v2/manifest/TWMnemonic.yaml b/codegen-v2/manifest/TWMnemonic.yaml new file mode 100644 index 00000000000..f1b426ec636 --- /dev/null +++ b/codegen-v2/manifest/TWMnemonic.yaml @@ -0,0 +1,51 @@ +name: TWMnemonic +structs: +- name: TWMnemonic + is_public: true + is_class: false +functions: +- name: TWMnemonicIsValid + is_public: true + is_static: true + params: + - name: mnemonic + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWMnemonicIsValidWord + is_public: true + is_static: true + params: + - name: word + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWMnemonicSuggest + is_public: true + is_static: true + params: + - name: prefix + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWMultiversXProto.yaml b/codegen-v2/manifest/TWMultiversXProto.yaml new file mode 100644 index 00000000000..71df5e72131 --- /dev/null +++ b/codegen-v2/manifest/TWMultiversXProto.yaml @@ -0,0 +1,9 @@ +name: TWMultiversXProto +protos: +- TW_MultiversX_Proto_GenericAction +- TW_MultiversX_Proto_EGLDTransfer +- TW_MultiversX_Proto_ESDTTransfer +- TW_MultiversX_Proto_ESDTNFTTransfer +- TW_MultiversX_Proto_Accounts +- TW_MultiversX_Proto_SigningInput +- TW_MultiversX_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWNEARAccount.yaml b/codegen-v2/manifest/TWNEARAccount.yaml new file mode 100644 index 00000000000..05b607a0325 --- /dev/null +++ b/codegen-v2/manifest/TWNEARAccount.yaml @@ -0,0 +1,26 @@ +name: TWNEARAccount +structs: +- name: TWNEARAccount + is_public: true + is_class: true +inits: +- name: TWNEARAccountCreateWithString + is_public: true + is_nullable: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +deinits: +- name: TWNEARAccountDelete +properties: +- name: TWNEARAccountDescription + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWNEARProto.yaml b/codegen-v2/manifest/TWNEARProto.yaml new file mode 100644 index 00000000000..1d983a286c8 --- /dev/null +++ b/codegen-v2/manifest/TWNEARProto.yaml @@ -0,0 +1,17 @@ +name: TWNEARProto +protos: +- TW_NEAR_Proto_PublicKey +- TW_NEAR_Proto_FunctionCallPermission +- TW_NEAR_Proto_FullAccessPermission +- TW_NEAR_Proto_AccessKey +- TW_NEAR_Proto_CreateAccount +- TW_NEAR_Proto_DeployContract +- TW_NEAR_Proto_FunctionCall +- TW_NEAR_Proto_Transfer +- TW_NEAR_Proto_Stake +- TW_NEAR_Proto_AddKey +- TW_NEAR_Proto_DeleteKey +- TW_NEAR_Proto_DeleteAccount +- TW_NEAR_Proto_Action +- TW_NEAR_Proto_SigningInput +- TW_NEAR_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWNEOProto.yaml b/codegen-v2/manifest/TWNEOProto.yaml new file mode 100644 index 00000000000..b4db4f7ef01 --- /dev/null +++ b/codegen-v2/manifest/TWNEOProto.yaml @@ -0,0 +1,8 @@ +name: TWNEOProto +protos: +- TW_NEO_Proto_TransactionInput +- TW_NEO_Proto_TransactionOutput +- TW_NEO_Proto_SigningInput +- TW_NEO_Proto_SigningOutput +- TW_NEO_Proto_TransactionOutputPlan +- TW_NEO_Proto_TransactionPlan diff --git a/codegen-v2/manifest/TWNULSProto.yaml b/codegen-v2/manifest/TWNULSProto.yaml new file mode 100644 index 00000000000..0ecedf0290f --- /dev/null +++ b/codegen-v2/manifest/TWNULSProto.yaml @@ -0,0 +1,8 @@ +name: TWNULSProto +protos: +- TW_NULS_Proto_TransactionCoinFrom +- TW_NULS_Proto_TransactionCoinTo +- TW_NULS_Proto_Signature +- TW_NULS_Proto_Transaction +- TW_NULS_Proto_SigningInput +- TW_NULS_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWNanoProto.yaml b/codegen-v2/manifest/TWNanoProto.yaml new file mode 100644 index 00000000000..b153219bbce --- /dev/null +++ b/codegen-v2/manifest/TWNanoProto.yaml @@ -0,0 +1,4 @@ +name: TWNanoProto +protos: +- TW_Nano_Proto_SigningInput +- TW_Nano_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWNebulasProto.yaml b/codegen-v2/manifest/TWNebulasProto.yaml new file mode 100644 index 00000000000..a53a6975f97 --- /dev/null +++ b/codegen-v2/manifest/TWNebulasProto.yaml @@ -0,0 +1,6 @@ +name: TWNebulasProto +protos: +- TW_Nebulas_Proto_SigningInput +- TW_Nebulas_Proto_SigningOutput +- TW_Nebulas_Proto_Data +- TW_Nebulas_Proto_RawTransaction diff --git a/codegen-v2/manifest/TWNervosAddress.yaml b/codegen-v2/manifest/TWNervosAddress.yaml new file mode 100644 index 00000000000..33a5502426b --- /dev/null +++ b/codegen-v2/manifest/TWNervosAddress.yaml @@ -0,0 +1,86 @@ +name: TWNervosAddress +structs: +- name: TWNervosAddress + is_public: true + is_class: true +inits: +- name: TWNervosAddressCreateWithString + is_public: true + is_nullable: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +deinits: +- name: TWNervosAddressDelete +functions: +- name: TWNervosAddressEqual + is_public: true + is_static: true + params: + - name: lhs + type: + variant: struct + value: TWNervosAddress + is_constant: false + is_nullable: false + is_pointer: true + - name: rhs + type: + variant: struct + value: TWNervosAddress + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWNervosAddressIsValidString + is_public: true + is_static: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +properties: +- name: TWNervosAddressDescription + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWNervosAddressCodeHash + is_public: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWNervosAddressHashType + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWNervosAddressArgs + is_public: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWNervosProto.yaml b/codegen-v2/manifest/TWNervosProto.yaml new file mode 100644 index 00000000000..5c75962a775 --- /dev/null +++ b/codegen-v2/manifest/TWNervosProto.yaml @@ -0,0 +1,15 @@ +name: TWNervosProto +protos: +- TW_Nervos_Proto_TransactionPlan +- TW_Nervos_Proto_CellDep +- TW_Nervos_Proto_OutPoint +- TW_Nervos_Proto_CellOutput +- TW_Nervos_Proto_Script +- TW_Nervos_Proto_NativeTransfer +- TW_Nervos_Proto_SudtTransfer +- TW_Nervos_Proto_DaoDeposit +- TW_Nervos_Proto_DaoWithdrawPhase1 +- TW_Nervos_Proto_DaoWithdrawPhase2 +- TW_Nervos_Proto_SigningInput +- TW_Nervos_Proto_Cell +- TW_Nervos_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWNimiqProto.yaml b/codegen-v2/manifest/TWNimiqProto.yaml new file mode 100644 index 00000000000..874c5688186 --- /dev/null +++ b/codegen-v2/manifest/TWNimiqProto.yaml @@ -0,0 +1,4 @@ +name: TWNimiqProto +protos: +- TW_Nimiq_Proto_SigningInput +- TW_Nimiq_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWOasisProto.yaml b/codegen-v2/manifest/TWOasisProto.yaml new file mode 100644 index 00000000000..d20358b3002 --- /dev/null +++ b/codegen-v2/manifest/TWOasisProto.yaml @@ -0,0 +1,5 @@ +name: TWOasisProto +protos: +- TW_Oasis_Proto_TransferMessage +- TW_Oasis_Proto_SigningInput +- TW_Oasis_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWOntologyProto.yaml b/codegen-v2/manifest/TWOntologyProto.yaml new file mode 100644 index 00000000000..7d9c965172f --- /dev/null +++ b/codegen-v2/manifest/TWOntologyProto.yaml @@ -0,0 +1,4 @@ +name: TWOntologyProto +protos: +- TW_Ontology_Proto_SigningInput +- TW_Ontology_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWPBKDF2.yaml b/codegen-v2/manifest/TWPBKDF2.yaml new file mode 100644 index 00000000000..1e1a36a0b0f --- /dev/null +++ b/codegen-v2/manifest/TWPBKDF2.yaml @@ -0,0 +1,72 @@ +name: TWPBKDF2 +structs: +- name: TWPBKDF2 + is_public: true + is_class: false +functions: +- name: TWPBKDF2HmacSha256 + is_public: true + is_static: true + params: + - name: password + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: salt + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: iterations + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + - name: dkLen + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWPBKDF2HmacSha512 + is_public: true + is_static: true + params: + - name: password + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: salt + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: iterations + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + - name: dkLen + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true diff --git a/codegen-v2/manifest/TWPolkadotProto.yaml b/codegen-v2/manifest/TWPolkadotProto.yaml new file mode 100644 index 00000000000..80eb51c7c9d --- /dev/null +++ b/codegen-v2/manifest/TWPolkadotProto.yaml @@ -0,0 +1,7 @@ +name: TWPolkadotProto +protos: +- TW_Polkadot_Proto_Era +- TW_Polkadot_Proto_Balance +- TW_Polkadot_Proto_Staking +- TW_Polkadot_Proto_SigningInput +- TW_Polkadot_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWPrivateKey.yaml b/codegen-v2/manifest/TWPrivateKey.yaml new file mode 100644 index 00000000000..5ec6bfd486d --- /dev/null +++ b/codegen-v2/manifest/TWPrivateKey.yaml @@ -0,0 +1,322 @@ +name: TWPrivateKey +structs: +- name: TWPrivateKey + is_public: true + is_class: true +inits: +- name: TWPrivateKeyCreate + is_public: true + is_nullable: false +- name: TWPrivateKeyCreateWithData + is_public: true + is_nullable: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWPrivateKeyCreateCopy + is_public: true + is_nullable: true + params: + - name: key + type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true +deinits: +- name: TWPrivateKeyDelete +functions: +- name: TWPrivateKeyIsValid + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: curve + type: + variant: enum + value: TWCurve + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWPrivateKeyGetPublicKey + is_public: true + is_static: false + params: + - name: pk + type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true + - name: coinType + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true +- name: TWPrivateKeyGetPublicKeyByType + is_public: true + is_static: false + params: + - name: pk + type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true + - name: pubkeyType + type: + variant: enum + value: TWPublicKeyType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true +- name: TWPrivateKeyGetPublicKeySecp256k1 + is_public: true + is_static: false + params: + - name: pk + type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true + - name: compressed + type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true +- name: TWPrivateKeyGetPublicKeyNist256p1 + is_public: true + is_static: false + params: + - name: pk + type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true +- name: TWPrivateKeyGetPublicKeyEd25519 + is_public: true + is_static: false + params: + - name: pk + type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true +- name: TWPrivateKeyGetPublicKeyEd25519Blake2b + is_public: true + is_static: false + params: + - name: pk + type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true +- name: TWPrivateKeyGetPublicKeyEd25519Cardano + is_public: true + is_static: false + params: + - name: pk + type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true +- name: TWPrivateKeyGetPublicKeyCurve25519 + is_public: true + is_static: false + params: + - name: pk + type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true +- name: TWPrivateKeyGetSharedKey + is_public: true + is_static: false + params: + - name: pk + type: + variant: struct + value: TWPrivateKey + is_constant: true + is_nullable: false + is_pointer: true + - name: publicKey + type: + variant: struct + value: TWPublicKey + is_constant: true + is_nullable: false + is_pointer: true + - name: curve + type: + variant: enum + value: TWCurve + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWPrivateKeySign + is_public: true + is_static: false + params: + - name: pk + type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true + - name: digest + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: curve + type: + variant: enum + value: TWCurve + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWPrivateKeySignAsDER + is_public: true + is_static: false + params: + - name: pk + type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true + - name: digest + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWPrivateKeySignZilliqaSchnorr + is_public: true + is_static: false + params: + - name: pk + type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true + - name: message + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +properties: +- name: TWPrivateKeyData + is_public: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWPrivateKeyType.yaml b/codegen-v2/manifest/TWPrivateKeyType.yaml new file mode 100644 index 00000000000..c16c8082188 --- /dev/null +++ b/codegen-v2/manifest/TWPrivateKeyType.yaml @@ -0,0 +1,11 @@ +name: TWPrivateKeyType +enums: +- name: TWPrivateKeyType + is_public: true + value_type: + variant: u_int32_t + variants: + - name: default + value: 0 + - name: cardano + value: 1 diff --git a/codegen-v2/manifest/TWPublicKey.yaml b/codegen-v2/manifest/TWPublicKey.yaml new file mode 100644 index 00000000000..bacfe840deb --- /dev/null +++ b/codegen-v2/manifest/TWPublicKey.yaml @@ -0,0 +1,200 @@ +name: TWPublicKey +structs: +- name: TWPublicKey + is_public: true + is_class: true +inits: +- name: TWPublicKeyCreateWithData + is_public: true + is_nullable: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: type + type: + variant: enum + value: TWPublicKeyType + is_constant: false + is_nullable: false + is_pointer: false +deinits: +- name: TWPublicKeyDelete +functions: +- name: TWPublicKeyIsValid + is_public: true + is_static: true + params: + - name: data + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: type + type: + variant: enum + value: TWPublicKeyType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWPublicKeyVerify + is_public: true + is_static: false + params: + - name: pk + type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true + - name: signature + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: message + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWPublicKeyVerifyAsDER + is_public: true + is_static: false + params: + - name: pk + type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true + - name: signature + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: message + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWPublicKeyVerifyZilliqaSchnorr + is_public: true + is_static: false + params: + - name: pk + type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true + - name: signature + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: message + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWPublicKeyRecover + is_public: true + is_static: true + params: + - name: signature + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: message + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: true + is_pointer: true +properties: +- name: TWPublicKeyIsCompressed + is_public: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWPublicKeyCompressed + is_public: true + return_type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true +- name: TWPublicKeyUncompressed + is_public: true + return_type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true +- name: TWPublicKeyData + is_public: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWPublicKeyKeyType + is_public: true + return_type: + variant: enum + value: TWPublicKeyType + is_constant: false + is_nullable: false + is_pointer: false +- name: TWPublicKeyDescription + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWPublicKeyType.yaml b/codegen-v2/manifest/TWPublicKeyType.yaml new file mode 100644 index 00000000000..3498161aa65 --- /dev/null +++ b/codegen-v2/manifest/TWPublicKeyType.yaml @@ -0,0 +1,25 @@ +name: TWPublicKeyType +enums: +- name: TWPublicKeyType + is_public: true + value_type: + variant: u_int32_t + variants: + - name: secp256k1 + value: 0 + - name: secp256k1Extended + value: 1 + - name: nist256p1 + value: 2 + - name: nist256p1Extended + value: 3 + - name: ed25519 + value: 4 + - name: ed25519Blake2b + value: 5 + - name: curve25519 + value: 6 + - name: ed25519Cardano + value: 7 + - name: starkex + value: 8 diff --git a/codegen-v2/manifest/TWPurpose.yaml b/codegen-v2/manifest/TWPurpose.yaml new file mode 100644 index 00000000000..8ccf4d8cc0e --- /dev/null +++ b/codegen-v2/manifest/TWPurpose.yaml @@ -0,0 +1,15 @@ +name: TWPurpose +enums: +- name: TWPurpose + is_public: true + value_type: + variant: u_int32_t + variants: + - name: bip44 + value: 44 + - name: bip49 + value: 49 + - name: bip84 + value: 84 + - name: bip1852 + value: 1852 diff --git a/codegen-v2/manifest/TWRippleProto.yaml b/codegen-v2/manifest/TWRippleProto.yaml new file mode 100644 index 00000000000..de3b2af9e88 --- /dev/null +++ b/codegen-v2/manifest/TWRippleProto.yaml @@ -0,0 +1,11 @@ +name: TWRippleProto +protos: +- TW_Ripple_Proto_CurrencyAmount +- TW_Ripple_Proto_OperationTrustSet +- TW_Ripple_Proto_OperationPayment +- TW_Ripple_Proto_OperationNFTokenBurn +- TW_Ripple_Proto_OperationNFTokenCreateOffer +- TW_Ripple_Proto_OperationNFTokenAcceptOffer +- TW_Ripple_Proto_OperationNFTokenCancelOffer +- TW_Ripple_Proto_SigningInput +- TW_Ripple_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWRippleXAddress.yaml b/codegen-v2/manifest/TWRippleXAddress.yaml new file mode 100644 index 00000000000..8793e2ce5f4 --- /dev/null +++ b/codegen-v2/manifest/TWRippleXAddress.yaml @@ -0,0 +1,92 @@ +name: TWRippleXAddress +structs: +- name: TWPublicKey + is_public: false + is_class: false +- name: TWRippleXAddress + is_public: true + is_class: true +inits: +- name: TWRippleXAddressCreateWithString + is_public: true + is_nullable: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWRippleXAddressCreateWithPublicKey + is_public: true + is_nullable: false + params: + - name: publicKey + type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true + - name: tag + type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false +deinits: +- name: TWRippleXAddressDelete +functions: +- name: TWRippleXAddressEqual + is_public: true + is_static: true + params: + - name: lhs + type: + variant: struct + value: TWRippleXAddress + is_constant: false + is_nullable: false + is_pointer: true + - name: rhs + type: + variant: struct + value: TWRippleXAddress + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWRippleXAddressIsValidString + is_public: true + is_static: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +properties: +- name: TWRippleXAddressDescription + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWRippleXAddressTag + is_public: true + return_type: + variant: u_int32_t + is_constant: false + is_nullable: false + is_pointer: false diff --git a/codegen-v2/manifest/TWSS58AddressType.yaml b/codegen-v2/manifest/TWSS58AddressType.yaml new file mode 100644 index 00000000000..617d5ca49a0 --- /dev/null +++ b/codegen-v2/manifest/TWSS58AddressType.yaml @@ -0,0 +1,11 @@ +name: TWSS58AddressType +enums: +- name: TWSS58AddressType + is_public: true + value_type: + variant: u_int8_t + variants: + - name: polkadot + value: 0 + - name: kusama + value: 2 diff --git a/codegen-v2/manifest/TWSegwitAddress.yaml b/codegen-v2/manifest/TWSegwitAddress.yaml new file mode 100644 index 00000000000..6225484c27d --- /dev/null +++ b/codegen-v2/manifest/TWSegwitAddress.yaml @@ -0,0 +1,108 @@ +name: TWSegwitAddress +structs: +- name: TWPublicKey + is_public: false + is_class: false +- name: TWSegwitAddress + is_public: true + is_class: true +inits: +- name: TWSegwitAddressCreateWithString + is_public: true + is_nullable: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWSegwitAddressCreateWithPublicKey + is_public: true + is_nullable: false + params: + - name: hrp + type: + variant: enum + value: TWHRP + is_constant: false + is_nullable: false + is_pointer: false + - name: publicKey + type: + variant: struct + value: TWPublicKey + is_constant: false + is_nullable: false + is_pointer: true +deinits: +- name: TWSegwitAddressDelete +functions: +- name: TWSegwitAddressEqual + is_public: true + is_static: true + params: + - name: lhs + type: + variant: struct + value: TWSegwitAddress + is_constant: false + is_nullable: false + is_pointer: true + - name: rhs + type: + variant: struct + value: TWSegwitAddress + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWSegwitAddressIsValidString + is_public: true + is_static: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +properties: +- name: TWSegwitAddressDescription + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWSegwitAddressHRP + is_public: true + return_type: + variant: enum + value: TWHRP + is_constant: false + is_nullable: false + is_pointer: false +- name: TWSegwitAddressWitnessVersion + is_public: true + return_type: + variant: int + is_constant: false + is_nullable: false + is_pointer: false +- name: TWSegwitAddressWitnessProgram + is_public: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWSolanaAddress.yaml b/codegen-v2/manifest/TWSolanaAddress.yaml new file mode 100644 index 00000000000..28908ef2a9d --- /dev/null +++ b/codegen-v2/manifest/TWSolanaAddress.yaml @@ -0,0 +1,49 @@ +name: TWSolanaAddress +structs: +- name: TWSolanaAddress + is_public: true + is_class: true +inits: +- name: TWSolanaAddressCreateWithString + is_public: true + is_nullable: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +deinits: +- name: TWSolanaAddressDelete +functions: +- name: TWSolanaAddressDefaultTokenAddress + is_public: true + is_static: false + params: + - name: address + type: + variant: struct + value: TWSolanaAddress + is_constant: false + is_nullable: false + is_pointer: true + - name: tokenMintAddress + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: true + is_pointer: true +properties: +- name: TWSolanaAddressDescription + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWSolanaProto.yaml b/codegen-v2/manifest/TWSolanaProto.yaml new file mode 100644 index 00000000000..4d002e53fad --- /dev/null +++ b/codegen-v2/manifest/TWSolanaProto.yaml @@ -0,0 +1,14 @@ +name: TWSolanaProto +protos: +- TW_Solana_Proto_Transfer +- TW_Solana_Proto_DelegateStake +- TW_Solana_Proto_DeactivateStake +- TW_Solana_Proto_DeactivateAllStake +- TW_Solana_Proto_WithdrawStake +- TW_Solana_Proto_StakeAccountValue +- TW_Solana_Proto_WithdrawAllStake +- TW_Solana_Proto_CreateTokenAccount +- TW_Solana_Proto_TokenTransfer +- TW_Solana_Proto_CreateAndTransferToken +- TW_Solana_Proto_SigningInput +- TW_Solana_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWStarkExMessageSigner.yaml b/codegen-v2/manifest/TWStarkExMessageSigner.yaml new file mode 100644 index 00000000000..2c927399e52 --- /dev/null +++ b/codegen-v2/manifest/TWStarkExMessageSigner.yaml @@ -0,0 +1,56 @@ +name: TWStarkExMessageSigner +structs: +- name: TWStarkExMessageSigner + is_public: true + is_class: false +functions: +- name: TWStarkExMessageSignerSignMessage + is_public: true + is_static: true + params: + - name: privateKey + type: + variant: struct + value: TWPrivateKey + is_constant: true + is_nullable: false + is_pointer: true + - name: message + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWStarkExMessageSignerVerifyMessage + is_public: true + is_static: true + params: + - name: pubKey + type: + variant: struct + value: TWPublicKey + is_constant: true + is_nullable: false + is_pointer: true + - name: message + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: signature + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false diff --git a/codegen-v2/manifest/TWStarkWare.yaml b/codegen-v2/manifest/TWStarkWare.yaml new file mode 100644 index 00000000000..491f36d1826 --- /dev/null +++ b/codegen-v2/manifest/TWStarkWare.yaml @@ -0,0 +1,29 @@ +name: TWStarkWare +structs: +- name: TWStarkWare + is_public: true + is_class: false +functions: +- name: TWStarkWareGetStarkKeyFromSignature + is_public: true + is_static: true + params: + - name: derivationPath + type: + variant: struct + value: TWDerivationPath + is_constant: true + is_nullable: false + is_pointer: true + - name: signature + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWStellarMemoType.yaml b/codegen-v2/manifest/TWStellarMemoType.yaml new file mode 100644 index 00000000000..d086e056a0c --- /dev/null +++ b/codegen-v2/manifest/TWStellarMemoType.yaml @@ -0,0 +1,17 @@ +name: TWStellarMemoType +enums: +- name: TWStellarMemoType + is_public: true + value_type: + variant: u_int32_t + variants: + - name: none + value: 0 + - name: text + value: 1 + - name: id + value: 2 + - name: hash + value: 3 + - name: return + value: 4 diff --git a/codegen-v2/manifest/TWStellarPassphrase.yaml b/codegen-v2/manifest/TWStellarPassphrase.yaml new file mode 100644 index 00000000000..4daba41e364 --- /dev/null +++ b/codegen-v2/manifest/TWStellarPassphrase.yaml @@ -0,0 +1,13 @@ +name: TWStellarPassphrase +enums: +- name: TWStellarPassphrase + is_public: true + value_type: + variant: u_int32_t + variants: + - name: stellar + value: 0 + as_string: Public Global Stellar Network ; September 2015 + - name: kin + value: 1 + as_string: Kin Mainnet ; December 2018 diff --git a/codegen-v2/manifest/TWStellarProto.yaml b/codegen-v2/manifest/TWStellarProto.yaml new file mode 100644 index 00000000000..7c016b1627e --- /dev/null +++ b/codegen-v2/manifest/TWStellarProto.yaml @@ -0,0 +1,15 @@ +name: TWStellarProto +protos: +- TW_Stellar_Proto_Asset +- TW_Stellar_Proto_OperationCreateAccount +- TW_Stellar_Proto_OperationPayment +- TW_Stellar_Proto_OperationChangeTrust +- TW_Stellar_Proto_Claimant +- TW_Stellar_Proto_OperationCreateClaimableBalance +- TW_Stellar_Proto_OperationClaimClaimableBalance +- TW_Stellar_Proto_MemoVoid +- TW_Stellar_Proto_MemoText +- TW_Stellar_Proto_MemoId +- TW_Stellar_Proto_MemoHash +- TW_Stellar_Proto_SigningInput +- TW_Stellar_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWStellarVersionByte.yaml b/codegen-v2/manifest/TWStellarVersionByte.yaml new file mode 100644 index 00000000000..7e962ba517c --- /dev/null +++ b/codegen-v2/manifest/TWStellarVersionByte.yaml @@ -0,0 +1,15 @@ +name: TWStellarVersionByte +enums: +- name: TWStellarVersionByte + is_public: true + value_type: + variant: u_int16_t + variants: + - name: accountId + value: 0x30 + - name: seed + value: 0xc0 + - name: preAuthTx + value: 0xc8 + - name: sha256Hash + value: 0x118 diff --git a/codegen-v2/manifest/TWStoredKey.yaml b/codegen-v2/manifest/TWStoredKey.yaml new file mode 100644 index 00000000000..41080bad5c6 --- /dev/null +++ b/codegen-v2/manifest/TWStoredKey.yaml @@ -0,0 +1,755 @@ +name: TWStoredKey +structs: +- name: TWStoredKey + is_public: true + is_class: true +inits: +- name: TWStoredKeyCreateLevel + is_public: true + is_nullable: false + params: + - name: name + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: password + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: encryptionLevel + type: + variant: enum + value: TWStoredKeyEncryptionLevel + is_constant: false + is_nullable: false + is_pointer: false +- name: TWStoredKeyCreateLevelAndEncryption + is_public: true + is_nullable: false + params: + - name: name + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: password + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: encryptionLevel + type: + variant: enum + value: TWStoredKeyEncryptionLevel + is_constant: false + is_nullable: false + is_pointer: false + - name: encryption + type: + variant: enum + value: TWStoredKeyEncryption + is_constant: false + is_nullable: false + is_pointer: false +- name: TWStoredKeyCreate + is_public: true + is_nullable: false + params: + - name: name + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: password + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWStoredKeyCreateEncryption + is_public: true + is_nullable: false + params: + - name: name + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: password + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: encryption + type: + variant: enum + value: TWStoredKeyEncryption + is_constant: false + is_nullable: false + is_pointer: false +deinits: +- name: TWStoredKeyDelete +functions: +- name: TWStoredKeyLoad + is_public: true + is_static: true + params: + - name: path + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: true + is_pointer: true +- name: TWStoredKeyImportPrivateKey + is_public: true + is_static: true + params: + - name: privateKey + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: name + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: password + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: true + is_pointer: true +- name: TWStoredKeyImportPrivateKeyWithEncryption + is_public: true + is_static: true + params: + - name: privateKey + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: name + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: password + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: encryption + type: + variant: enum + value: TWStoredKeyEncryption + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: true + is_pointer: true +- name: TWStoredKeyImportHDWallet + is_public: true + is_static: true + params: + - name: mnemonic + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: name + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: password + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: true + is_pointer: true +- name: TWStoredKeyImportHDWalletWithEncryption + is_public: true + is_static: true + params: + - name: mnemonic + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: name + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: password + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: encryption + type: + variant: enum + value: TWStoredKeyEncryption + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: true + is_pointer: true +- name: TWStoredKeyImportJSON + is_public: true + is_static: true + params: + - name: json + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: true + is_pointer: true +- name: TWStoredKeyAccount + is_public: true + is_static: false + params: + - name: key + type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: false + is_pointer: true + - name: index + type: + variant: size_t + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: struct + value: TWAccount + is_constant: false + is_nullable: true + is_pointer: true +- name: TWStoredKeyAccountForCoin + is_public: true + is_static: false + params: + - name: key + type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: wallet + type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: true + is_pointer: true + return_type: + variant: struct + value: TWAccount + is_constant: false + is_nullable: true + is_pointer: true +- name: TWStoredKeyAccountForCoinDerivation + is_public: true + is_static: false + params: + - name: key + type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: derivation + type: + variant: enum + value: TWDerivation + is_constant: false + is_nullable: false + is_pointer: false + - name: wallet + type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: true + is_pointer: true + return_type: + variant: struct + value: TWAccount + is_constant: false + is_nullable: true + is_pointer: true +- name: TWStoredKeyAddAccountDerivation + is_public: true + is_static: false + params: + - name: key + type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: false + is_pointer: true + - name: address + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: derivation + type: + variant: enum + value: TWDerivation + is_constant: false + is_nullable: false + is_pointer: false + - name: derivationPath + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: publicKey + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: extendedPublicKey + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: void + is_constant: false + is_nullable: false + is_pointer: false +- name: TWStoredKeyAddAccount + is_public: true + is_static: false + params: + - name: key + type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: false + is_pointer: true + - name: address + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: derivationPath + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: publicKey + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: extendedPublicKey + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: void + is_constant: false + is_nullable: false + is_pointer: false +- name: TWStoredKeyRemoveAccountForCoin + is_public: true + is_static: false + params: + - name: key + type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: void + is_constant: false + is_nullable: false + is_pointer: false +- name: TWStoredKeyRemoveAccountForCoinDerivation + is_public: true + is_static: false + params: + - name: key + type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: derivation + type: + variant: enum + value: TWDerivation + is_constant: false + is_nullable: false + is_pointer: false + return_type: + variant: void + is_constant: false + is_nullable: false + is_pointer: false +- name: TWStoredKeyRemoveAccountForCoinDerivationPath + is_public: true + is_static: false + params: + - name: key + type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: derivationPath + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: void + is_constant: false + is_nullable: false + is_pointer: false +- name: TWStoredKeyStore + is_public: true + is_static: false + params: + - name: key + type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: false + is_pointer: true + - name: path + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWStoredKeyDecryptPrivateKey + is_public: true + is_static: false + params: + - name: key + type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: false + is_pointer: true + - name: password + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWStoredKeyDecryptMnemonic + is_public: true + is_static: false + params: + - name: key + type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: false + is_pointer: true + - name: password + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: true + is_pointer: true +- name: TWStoredKeyPrivateKey + is_public: true + is_static: false + params: + - name: key + type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: false + is_pointer: true + - name: coin + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: password + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWPrivateKey + is_constant: false + is_nullable: true + is_pointer: true +- name: TWStoredKeyWallet + is_public: true + is_static: false + params: + - name: key + type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: false + is_pointer: true + - name: password + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: struct + value: TWHDWallet + is_constant: false + is_nullable: true + is_pointer: true +- name: TWStoredKeyExportJSON + is_public: true + is_static: false + params: + - name: key + type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: true + is_pointer: true +- name: TWStoredKeyFixAddresses + is_public: true + is_static: false + params: + - name: key + type: + variant: struct + value: TWStoredKey + is_constant: false + is_nullable: false + is_pointer: true + - name: password + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +properties: +- name: TWStoredKeyIdentifier + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: true + is_pointer: true +- name: TWStoredKeyName + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWStoredKeyIsMnemonic + is_public: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: TWStoredKeyAccountCount + is_public: true + return_type: + variant: size_t + is_constant: false + is_nullable: false + is_pointer: false +- name: TWStoredKeyEncryptionParameters + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: true + is_pointer: true diff --git a/codegen-v2/manifest/TWStoredKeyEncryption.yaml b/codegen-v2/manifest/TWStoredKeyEncryption.yaml new file mode 100644 index 00000000000..417bea38cb3 --- /dev/null +++ b/codegen-v2/manifest/TWStoredKeyEncryption.yaml @@ -0,0 +1,15 @@ +name: TWStoredKeyEncryption +enums: +- name: TWStoredKeyEncryption + is_public: true + value_type: + variant: u_int32_t + variants: + - name: aes128Ctr + value: 0 + - name: aes128Cbc + value: 1 + - name: aes192Ctr + value: 2 + - name: aes256Ctr + value: 3 diff --git a/codegen-v2/manifest/TWStoredKeyEncryptionLevel.yaml b/codegen-v2/manifest/TWStoredKeyEncryptionLevel.yaml new file mode 100644 index 00000000000..4c0df079d88 --- /dev/null +++ b/codegen-v2/manifest/TWStoredKeyEncryptionLevel.yaml @@ -0,0 +1,15 @@ +name: TWStoredKeyEncryptionLevel +enums: +- name: TWStoredKeyEncryptionLevel + is_public: true + value_type: + variant: u_int32_t + variants: + - name: default + value: 0 + - name: minimal + value: 1 + - name: weak + value: 2 + - name: standard + value: 3 diff --git a/codegen-v2/manifest/TWSuiProto.yaml b/codegen-v2/manifest/TWSuiProto.yaml new file mode 100644 index 00000000000..c84041beac8 --- /dev/null +++ b/codegen-v2/manifest/TWSuiProto.yaml @@ -0,0 +1,5 @@ +name: TWSuiProto +protos: +- TW_Sui_Proto_SignDirect +- TW_Sui_Proto_SigningInput +- TW_Sui_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWTHORChainSwap.yaml b/codegen-v2/manifest/TWTHORChainSwap.yaml new file mode 100644 index 00000000000..f6e42c8966f --- /dev/null +++ b/codegen-v2/manifest/TWTHORChainSwap.yaml @@ -0,0 +1,21 @@ +name: TWTHORChainSwap +structs: +- name: TWTHORChainSwap + is_public: true + is_class: false +functions: +- name: TWTHORChainSwapBuildSwap + is_public: true + is_static: true + params: + - name: input + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWTHORChainSwapProto.yaml b/codegen-v2/manifest/TWTHORChainSwapProto.yaml new file mode 100644 index 00000000000..41a265cae34 --- /dev/null +++ b/codegen-v2/manifest/TWTHORChainSwapProto.yaml @@ -0,0 +1,6 @@ +name: TWTHORChainSwapProto +protos: +- TW_THORChainSwap_Proto_Error +- TW_THORChainSwap_Proto_Asset +- TW_THORChainSwap_Proto_SwapInput +- TW_THORChainSwap_Proto_SwapOutput diff --git a/codegen-v2/manifest/TWTezosMessageSigner.yaml b/codegen-v2/manifest/TWTezosMessageSigner.yaml new file mode 100644 index 00000000000..7019250a8d6 --- /dev/null +++ b/codegen-v2/manifest/TWTezosMessageSigner.yaml @@ -0,0 +1,92 @@ +name: TWTezosMessageSigner +structs: +- name: TWTezosMessageSigner + is_public: true + is_class: false +functions: +- name: TWTezosMessageSignerFormatMessage + is_public: true + is_static: true + params: + - name: message + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: url + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWTezosMessageSignerInputToPayload + is_public: true + is_static: true + params: + - name: message + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWTezosMessageSignerSignMessage + is_public: true + is_static: true + params: + - name: privateKey + type: + variant: struct + value: TWPrivateKey + is_constant: true + is_nullable: false + is_pointer: true + - name: message + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWTezosMessageSignerVerifyMessage + is_public: true + is_static: true + params: + - name: pubKey + type: + variant: struct + value: TWPublicKey + is_constant: true + is_nullable: false + is_pointer: true + - name: message + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: signature + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false diff --git a/codegen-v2/manifest/TWTezosProto.yaml b/codegen-v2/manifest/TWTezosProto.yaml new file mode 100644 index 00000000000..b6a974069bf --- /dev/null +++ b/codegen-v2/manifest/TWTezosProto.yaml @@ -0,0 +1,14 @@ +name: TWTezosProto +protos: +- TW_Tezos_Proto_SigningInput +- TW_Tezos_Proto_SigningOutput +- TW_Tezos_Proto_OperationList +- TW_Tezos_Proto_Operation +- TW_Tezos_Proto_FA12Parameters +- TW_Tezos_Proto_Txs +- TW_Tezos_Proto_TxObject +- TW_Tezos_Proto_FA2Parameters +- TW_Tezos_Proto_OperationParameters +- TW_Tezos_Proto_TransactionOperationData +- TW_Tezos_Proto_RevealOperationData +- TW_Tezos_Proto_DelegationOperationData diff --git a/codegen-v2/manifest/TWTheOpenNetworkProto.yaml b/codegen-v2/manifest/TWTheOpenNetworkProto.yaml new file mode 100644 index 00000000000..bbfb681aa75 --- /dev/null +++ b/codegen-v2/manifest/TWTheOpenNetworkProto.yaml @@ -0,0 +1,5 @@ +name: TWTheOpenNetworkProto +protos: +- TW_TheOpenNetwork_Proto_Transfer +- TW_TheOpenNetwork_Proto_SigningInput +- TW_TheOpenNetwork_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWThetaProto.yaml b/codegen-v2/manifest/TWThetaProto.yaml new file mode 100644 index 00000000000..4097843f588 --- /dev/null +++ b/codegen-v2/manifest/TWThetaProto.yaml @@ -0,0 +1,4 @@ +name: TWThetaProto +protos: +- TW_Theta_Proto_SigningInput +- TW_Theta_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWTransactionCompiler.yaml b/codegen-v2/manifest/TWTransactionCompiler.yaml new file mode 100644 index 00000000000..e31c93bd780 --- /dev/null +++ b/codegen-v2/manifest/TWTransactionCompiler.yaml @@ -0,0 +1,116 @@ +name: TWTransactionCompiler +structs: +- name: TWTransactionCompiler + is_public: true + is_class: false +functions: +- name: TWTransactionCompilerBuildInput + is_public: true + is_static: true + params: + - name: coinType + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: from + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: to + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: amount + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: asset + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: memo + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: chainId + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWTransactionCompilerPreImageHashes + is_public: true + is_static: true + params: + - name: coinType + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: txInputData + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true +- name: TWTransactionCompilerCompileWithSignatures + is_public: true + is_static: true + params: + - name: coinType + type: + variant: enum + value: TWCoinType + is_constant: false + is_nullable: false + is_pointer: false + - name: txInputData + type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true + - name: signatures + type: + variant: struct + value: TWDataVector + is_constant: true + is_nullable: false + is_pointer: true + - name: publicKeys + type: + variant: struct + value: TWDataVector + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: data + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/manifest/TWTransactionCompilerProto.yaml b/codegen-v2/manifest/TWTransactionCompilerProto.yaml new file mode 100644 index 00000000000..46dd9568d6f --- /dev/null +++ b/codegen-v2/manifest/TWTransactionCompilerProto.yaml @@ -0,0 +1,3 @@ +name: TWTransactionCompilerProto +protos: +- TW_TxCompiler_Proto_PreSigningOutput diff --git a/codegen-v2/manifest/TWTronMessageSigner.yaml b/codegen-v2/manifest/TWTronMessageSigner.yaml new file mode 100644 index 00000000000..12d9081737e --- /dev/null +++ b/codegen-v2/manifest/TWTronMessageSigner.yaml @@ -0,0 +1,56 @@ +name: TWTronMessageSigner +structs: +- name: TWTronMessageSigner + is_public: true + is_class: false +functions: +- name: TWTronMessageSignerSignMessage + is_public: true + is_static: true + params: + - name: privateKey + type: + variant: struct + value: TWPrivateKey + is_constant: true + is_nullable: false + is_pointer: true + - name: message + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +- name: TWTronMessageSignerVerifyMessage + is_public: true + is_static: true + params: + - name: pubKey + type: + variant: struct + value: TWPublicKey + is_constant: true + is_nullable: false + is_pointer: true + - name: message + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + - name: signature + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false diff --git a/codegen-v2/manifest/TWTronProto.yaml b/codegen-v2/manifest/TWTronProto.yaml new file mode 100644 index 00000000000..56bd975df44 --- /dev/null +++ b/codegen-v2/manifest/TWTronProto.yaml @@ -0,0 +1,21 @@ +name: TWTronProto +protos: +- TW_Tron_Proto_TransferContract +- TW_Tron_Proto_TransferAssetContract +- TW_Tron_Proto_TransferTRC20Contract +- TW_Tron_Proto_FreezeBalanceContract +- TW_Tron_Proto_FreezeBalanceV2Contract +- TW_Tron_Proto_UnfreezeBalanceV2Contract +- TW_Tron_Proto_WithdrawExpireUnfreezeContract +- TW_Tron_Proto_DelegateResourceContract +- TW_Tron_Proto_UnDelegateResourceContract +- TW_Tron_Proto_UnfreezeBalanceContract +- TW_Tron_Proto_UnfreezeAssetContract +- TW_Tron_Proto_VoteAssetContract +- TW_Tron_Proto_VoteWitnessContract +- TW_Tron_Proto_WithdrawBalanceContract +- TW_Tron_Proto_TriggerSmartContract +- TW_Tron_Proto_BlockHeader +- TW_Tron_Proto_Transaction +- TW_Tron_Proto_SigningInput +- TW_Tron_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWVeChainProto.yaml b/codegen-v2/manifest/TWVeChainProto.yaml new file mode 100644 index 00000000000..21acf417b22 --- /dev/null +++ b/codegen-v2/manifest/TWVeChainProto.yaml @@ -0,0 +1,5 @@ +name: TWVeChainProto +protos: +- TW_VeChain_Proto_Clause +- TW_VeChain_Proto_SigningInput +- TW_VeChain_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWWavesProto.yaml b/codegen-v2/manifest/TWWavesProto.yaml new file mode 100644 index 00000000000..03ff214aff8 --- /dev/null +++ b/codegen-v2/manifest/TWWavesProto.yaml @@ -0,0 +1,7 @@ +name: TWWavesProto +protos: +- TW_Waves_Proto_TransferMessage +- TW_Waves_Proto_LeaseMessage +- TW_Waves_Proto_CancelLeaseMessage +- TW_Waves_Proto_SigningInput +- TW_Waves_Proto_SigningOutput diff --git a/codegen-v2/manifest/TWZilliqaProto.yaml b/codegen-v2/manifest/TWZilliqaProto.yaml new file mode 100644 index 00000000000..671d3a1d432 --- /dev/null +++ b/codegen-v2/manifest/TWZilliqaProto.yaml @@ -0,0 +1,5 @@ +name: TWZilliqaProto +protos: +- TW_Zilliqa_Proto_Transaction +- TW_Zilliqa_Proto_SigningInput +- TW_Zilliqa_Proto_SigningOutput diff --git a/codegen-v2/src/codegen/cpp/blockchain_dispatcher_generator.rs b/codegen-v2/src/codegen/cpp/blockchain_dispatcher_generator.rs new file mode 100644 index 00000000000..0871d545995 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/blockchain_dispatcher_generator.rs @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::cpp::cpp_source_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const COIN_INCLUDES_END: &str = "end_of_coin_includes_marker_do_not_modify"; +const COIN_DISPATCHER_DECLARATIONS_END: &str = + "end_of_coin_dipatcher_declarations_marker_do_not_modify"; +const COIN_DISPATCHER_SWITCH_END: &str = "end_of_coin_dipatcher_switch_marker_do_not_modify"; + +fn dispatcher_coin_cpp_path() -> PathBuf { + cpp_source_directory().join("Coin.cpp") +} + +/// Represents `Coin.cpp`. +pub struct BlockchainDispatcherGenerator; + +impl BlockchainDispatcherGenerator { + pub fn generate_new_blockchain_type_dispatching(coin: &CoinItem) -> Result<()> { + let dispatcher_path = dispatcher_coin_cpp_path(); + println!("[EDIT] {dispatcher_path:?}"); + let mut file_content = FileContent::read(dispatcher_path)?; + + Self::generate_include_of_blockchain_entry(coin, &mut file_content)?; + Self::generate_blockchain_entry_constant(coin, &mut file_content)?; + Self::generate_blockchain_dispatcher_case(coin, &mut file_content)?; + + file_content.write() + } + + fn generate_include_of_blockchain_entry( + coin: &CoinItem, + file_content: &mut FileContent, + ) -> Result<()> { + let blockchain_type = coin.blockchain_type(); + + let mut line_marker = file_content.rfind_line(|line| line.contains(COIN_INCLUDES_END))?; + line_marker.push_line_before(format!(r#"#include "{blockchain_type}/Entry.h""#)); + + Ok(()) + } + + fn generate_blockchain_entry_constant( + coin: &CoinItem, + file_content: &mut FileContent, + ) -> Result<()> { + let blockchain_type = coin.blockchain_type(); + + let mut entries_region = + file_content.rfind_line(|line| line.contains(COIN_DISPATCHER_DECLARATIONS_END))?; + entries_region.push_line_before(format!("{blockchain_type}::Entry {blockchain_type}DP;")); + + Ok(()) + } + + fn generate_blockchain_dispatcher_case( + coin: &CoinItem, + file_content: &mut FileContent, + ) -> Result<()> { + let blockchain_type = coin.blockchain_type(); + + let mut entries_region = + file_content.rfind_line(|line| line.contains(COIN_DISPATCHER_SWITCH_END))?; + entries_region.push_line_before(format!( + " case TWBlockchain{blockchain_type}: entry = &{blockchain_type}DP; break;" + )); + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/cpp/entry_generator.rs b/codegen-v2/src/codegen/cpp/entry_generator.rs new file mode 100644 index 00000000000..639ee6c0488 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/entry_generator.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::cpp::cpp_source_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::registry::CoinItem; +use crate::Result; +use std::fs; +use std::path::PathBuf; + +const ENTRY_HEADER_TEMPLATE: &str = include_str!("templates/Entry.h"); + +pub fn coin_source_directory(coin: &CoinItem) -> PathBuf { + cpp_source_directory().join(coin.blockchain_type()) +} + +pub struct EntryGenerator; + +impl EntryGenerator { + pub fn generate(coin: &CoinItem) -> Result { + let blockchain_dir = coin_source_directory(coin); + let entry_header_path = blockchain_dir.join("Entry.h"); + + if blockchain_dir.exists() { + println!("[SKIP] Entry file already exists: {blockchain_dir:?}"); + return Ok(blockchain_dir); + } + + fs::create_dir_all(&blockchain_dir)?; + + println!("[ADD] {entry_header_path:?}"); + TemplateGenerator::new(ENTRY_HEADER_TEMPLATE) + .write_to(entry_header_path.clone()) + .with_default_patterns(coin) + .write()?; + + Ok(entry_header_path) + } +} diff --git a/codegen-v2/src/codegen/cpp/mod.rs b/codegen-v2/src/codegen/cpp/mod.rs new file mode 100644 index 00000000000..1ef7188d86f --- /dev/null +++ b/codegen-v2/src/codegen/cpp/mod.rs @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::registry::CoinItem; +use std::env; +use std::path::PathBuf; + +pub mod blockchain_dispatcher_generator; +pub mod entry_generator; +pub mod new_blockchain; +pub mod new_cosmos_chain; +pub mod new_evmchain; +pub mod tw_any_address_tests_generator; +pub mod tw_any_signer_tests_generator; +pub mod tw_blockchain; +pub mod tw_coin_address_derivation_tests_generator; +pub mod tw_coin_type_generator; +pub mod tw_coin_type_tests_generator; + +pub fn cpp_source_directory() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("src") +} + +pub fn cpp_include_directory() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("include") + .join("TrustWalletCore") +} + +pub fn integration_tests_directory() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("tests") +} + +pub fn coin_integration_tests_directory(coin: &CoinItem) -> PathBuf { + integration_tests_directory() + .join("chains") + .join(coin.coin_type()) +} + +pub fn cosmos_coin_integration_tests_directory(coin: &CoinItem) -> PathBuf { + integration_tests_directory() + .join("chains") + .join("Cosmos") + .join(coin.coin_type()) +} diff --git a/codegen-v2/src/codegen/cpp/new_blockchain.rs b/codegen-v2/src/codegen/cpp/new_blockchain.rs new file mode 100644 index 00000000000..df7c94c62bd --- /dev/null +++ b/codegen-v2/src/codegen/cpp/new_blockchain.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::cpp::blockchain_dispatcher_generator::BlockchainDispatcherGenerator; +use crate::codegen::cpp::entry_generator::EntryGenerator; +use crate::codegen::cpp::tw_any_address_tests_generator::TWAnyAddressTestsGenerator; +use crate::codegen::cpp::tw_any_signer_tests_generator::TWAnySignerTestsGenerator; +use crate::codegen::cpp::tw_blockchain::TWBlockchainGenerator; +use crate::codegen::cpp::tw_coin_address_derivation_tests_generator::CoinAddressDerivationTestsGenerator; +use crate::codegen::cpp::tw_coin_type_generator::TWCoinTypeGenerator; +use crate::codegen::cpp::tw_coin_type_tests_generator::TWCoinTypeTestsGenerator; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_blockchain(coin: &CoinItem) -> Result<()> { + // Generate C++ files. + EntryGenerator::generate(coin)?; + + // Add the new coin type to the `TWCoinType` enum. + TWCoinTypeGenerator::generate_coin_type_variant(coin)?; + // Add the new blockchain type to the `TWBlockchain` enum. + TWBlockchainGenerator::generate_blockchain_type_variant(coin)?; + // Add the blockchain entry to the dispatcher `Coin.cpp`. + BlockchainDispatcherGenerator::generate_new_blockchain_type_dispatching(coin)?; + + // Add integration tests. + TWCoinTypeTestsGenerator::generate(coin)?; + TWAnyAddressTestsGenerator::generate(coin)?; + TWAnySignerTestsGenerator::generate(coin)?; + CoinAddressDerivationTestsGenerator::generate_new_coin_type_case(coin)?; + + Ok(()) +} diff --git a/codegen-v2/src/codegen/cpp/new_cosmos_chain.rs b/codegen-v2/src/codegen/cpp/new_cosmos_chain.rs new file mode 100644 index 00000000000..08c4406f994 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/new_cosmos_chain.rs @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::cpp::tw_coin_address_derivation_tests_generator::CoinAddressDerivationTestsGenerator; +use crate::codegen::cpp::tw_coin_type_generator::TWCoinTypeGenerator; +use crate::codegen::cpp::tw_coin_type_tests_generator::TWCoinTypeTestsGenerator; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_cosmos_chain(coin: &CoinItem) -> Result<()> { + // Add the new coin type to the `TWCoinType` enum. + TWCoinTypeGenerator::generate_coin_type_variant(coin)?; + + // Add integration tests. + TWCoinTypeTestsGenerator::generate(coin)?; + CoinAddressDerivationTestsGenerator::generate_new_coin_type_case(coin)?; + + Ok(()) +} diff --git a/codegen-v2/src/codegen/cpp/new_evmchain.rs b/codegen-v2/src/codegen/cpp/new_evmchain.rs new file mode 100644 index 00000000000..2368c797ab3 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/new_evmchain.rs @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::cpp::tw_coin_address_derivation_tests_generator::CoinAddressDerivationTestsGenerator; +use crate::codegen::cpp::tw_coin_type_generator::TWCoinTypeGenerator; +use crate::codegen::cpp::tw_coin_type_tests_generator::TWCoinTypeTestsGenerator; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_evmchain(coin: &CoinItem) -> Result<()> { + // Add the new coin type to the `TWCoinType` enum. + TWCoinTypeGenerator::generate_coin_type_variant(coin)?; + + // Add integration tests. + TWCoinTypeTestsGenerator::generate(coin)?; + CoinAddressDerivationTestsGenerator::generate_new_evm_coin_type_case(coin)?; + + Ok(()) +} diff --git a/codegen-v2/src/codegen/cpp/templates/Entry.h b/codegen-v2/src/codegen/cpp/templates/Entry.h new file mode 100644 index 00000000000..2c2520f9ccb --- /dev/null +++ b/codegen-v2/src/codegen/cpp/templates/Entry.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "rust/RustCoinEntry.h" + +namespace TW::{BLOCKCHAIN} { + +/// Entry point for {BLOCKCHAIN} coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry : public Rust::RustCoinEntry { +}; + +} // namespace TW::{BLOCKCHAIN} + diff --git a/codegen-v2/src/codegen/cpp/templates/TWAnyAddressTests.cpp b/codegen-v2/src/codegen/cpp/templates/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..139234a20e4 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/templates/TWAnyAddressTests.cpp @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +// TODO: Finalize tests + +TEST(TW{COIN_TYPE}, Address) { + // TODO: Finalize test implementation + + auto string = STRING("__ADD_VALID_ADDRESS_HERE__"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinType{COIN_TYPE})); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "__CORRESPONDING_ADDRESS_DATA__"); +} diff --git a/codegen-v2/src/codegen/cpp/templates/TWAnySignerTests.cpp b/codegen-v2/src/codegen/cpp/templates/TWAnySignerTests.cpp new file mode 100644 index 00000000000..ce5b56f0467 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/templates/TWAnySignerTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +// TODO: Finalize tests + +TEST(TWAnySigner{COIN_TYPE}, Sign) { + // TODO: Finalize test implementation +} diff --git a/codegen-v2/src/codegen/cpp/templates/TWCoinTypeTests.cpp b/codegen-v2/src/codegen/cpp/templates/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..8e917150dd4 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/templates/TWCoinTypeTests.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +TEST(TW{COIN_TYPE}CoinType, TWCoinType) { + const auto coin = TWCoinType{COIN_TYPE}; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("{EXPLORER_SAMPLE_TX}")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("{EXPLORER_SAMPLE_ACCOUNT}")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "{COIN_ID}"); + assertStringsEqual(name, "{COIN_NAME}"); + assertStringsEqual(symbol, "{SYMBOL}"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), {DECIMALS}); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchain{BLOCKCHAIN}); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), {P2PKH_PREFIX}); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), {P2SH_PREFIX}); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), {STATIC_PREFIX}); + assertStringsEqual(txUrl, "{EXPLORER_URL}{EXPLORER_TX_PATH}{EXPLORER_SAMPLE_TX}"); + assertStringsEqual(accUrl, "{EXPLORER_URL}{EXPLORER_ACCOUNT_PATH}{EXPLORER_SAMPLE_ACCOUNT}"); +} diff --git a/codegen-v2/src/codegen/cpp/tw_any_address_tests_generator.rs b/codegen-v2/src/codegen/cpp/tw_any_address_tests_generator.rs new file mode 100644 index 00000000000..599ea265235 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_any_address_tests_generator.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::cpp::coin_integration_tests_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::registry::CoinItem; +use crate::Result; +use std::fs; +use std::path::PathBuf; + +const TW_ANY_ADDRESS_TESTS_TEMPLATE: &str = include_str!("templates/TWAnyAddressTests.cpp"); + +pub fn tw_any_address_tests_path(coin: &CoinItem) -> PathBuf { + coin_integration_tests_directory(coin).join("TWAnyAddressTests.cpp") +} + +pub struct TWAnyAddressTestsGenerator; + +impl TWAnyAddressTestsGenerator { + pub fn generate(coin: &CoinItem) -> Result<()> { + let coin_tests_dir = coin_integration_tests_directory(coin); + let tw_any_address_tests_path = coin_tests_dir.join("TWAnyAddressTests.cpp"); + + fs::create_dir_all(coin_tests_dir)?; + if tw_any_address_tests_path.exists() { + println!("[SKIP] {tw_any_address_tests_path:?} already exists"); + return Ok(()); + } + + println!("[ADD] {tw_any_address_tests_path:?}"); + TemplateGenerator::new(TW_ANY_ADDRESS_TESTS_TEMPLATE) + .write_to(tw_any_address_tests_path) + .with_default_patterns(coin) + .write()?; + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/cpp/tw_any_signer_tests_generator.rs b/codegen-v2/src/codegen/cpp/tw_any_signer_tests_generator.rs new file mode 100644 index 00000000000..9ea4337616a --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_any_signer_tests_generator.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::cpp::coin_integration_tests_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::registry::CoinItem; +use crate::Result; +use std::fs; +use std::path::PathBuf; + +const TW_ANY_SIGNER_TESTS_TEMPLATE: &str = include_str!("templates/TWAnySignerTests.cpp"); + +pub fn tw_any_signer_tests_path(coin: &CoinItem) -> PathBuf { + coin_integration_tests_directory(coin).join("TWAnySignerTests.cpp") +} + +pub struct TWAnySignerTestsGenerator; + +impl TWAnySignerTestsGenerator { + pub fn generate(coin: &CoinItem) -> Result<()> { + let coin_tests_dir = coin_integration_tests_directory(coin); + let tw_any_signer_tests_path = coin_tests_dir.join("TWAnySignerTests.cpp"); + + fs::create_dir_all(coin_tests_dir)?; + if tw_any_signer_tests_path.exists() { + println!("[SKIP] {tw_any_signer_tests_path:?} already exists"); + return Ok(()); + } + + println!("[ADD] {tw_any_signer_tests_path:?}"); + TemplateGenerator::new(TW_ANY_SIGNER_TESTS_TEMPLATE) + .write_to(tw_any_signer_tests_path) + .with_default_patterns(coin) + .write()?; + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/cpp/tw_blockchain.rs b/codegen-v2/src/codegen/cpp/tw_blockchain.rs new file mode 100644 index 00000000000..a76a73ab055 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_blockchain.rs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::cpp::cpp_include_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +/// An offset because of removed blockchain enum types. +const BLOCKCHAIN_TYPE_OFFSET: usize = 2; + +pub fn tw_blockchain_path() -> PathBuf { + cpp_include_directory().join("TWBlockchain.h") +} + +/// Represents `TWBlockchain.h`. +pub struct TWBlockchainGenerator; + +impl TWBlockchainGenerator { + pub fn generate_blockchain_type_variant(coin: &CoinItem) -> Result<()> { + let coin_type = coin.blockchain_type(); + let tw_blockchain_type_path = tw_blockchain_path(); + + println!("[EDIT] {tw_blockchain_type_path:?}"); + let mut tw_blockchain_type_rs = FileContent::read(tw_blockchain_type_path)?; + + { + let mut enum_region = + tw_blockchain_type_rs.find_region_with_prefix(" TWBlockchain")?; + // Add an offset because of removed blockchain enum types. + let new_blockchain_id = enum_region.count_lines() + BLOCKCHAIN_TYPE_OFFSET; + enum_region.push_line(format!( + " TWBlockchain{coin_type} = {new_blockchain_id}," + )); + } + + tw_blockchain_type_rs.write() + } +} diff --git a/codegen-v2/src/codegen/cpp/tw_coin_address_derivation_tests_generator.rs b/codegen-v2/src/codegen/cpp/tw_coin_address_derivation_tests_generator.rs new file mode 100644 index 00000000000..91321dffb05 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_coin_address_derivation_tests_generator.rs @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::cpp::integration_tests_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const COIN_ADDRESS_DERIVATION_TESTS_END: &str = + "end_of_coin_address_derivation_tests_marker_do_not_modify"; +const EVM_ADDRESS_DERIVATION_TESTS_END: &str = + "end_of_evm_address_derivation_tests_marker_do_not_modify"; + +pub fn coin_address_derivation_tests_path() -> PathBuf { + integration_tests_directory() + .join("common") + .join("CoinAddressDerivationTests.cpp") +} + +/// Represents `CoinAddressDerivationTests.cpp`. +pub struct CoinAddressDerivationTestsGenerator; + +impl CoinAddressDerivationTestsGenerator { + pub fn generate_new_coin_type_case(coin: &CoinItem) -> Result<()> { + let coin_type = coin.coin_type(); + let test_path = coin_address_derivation_tests_path(); + println!("[EDIT] {test_path:?}"); + + let mut coin_address_derivation_test_rs = FileContent::read(test_path)?; + + { + let mut switch_case_region = coin_address_derivation_test_rs + .rfind_line(|line| line.contains(COIN_ADDRESS_DERIVATION_TESTS_END))?; + + #[rustfmt::skip] + let test_case = format!( +r#" case TWCoinType{coin_type}: + EXPECT_EQ(address, "__TODO__"); + break;"# +); + + switch_case_region.push_paragraph_before(test_case); + } + + coin_address_derivation_test_rs.write() + } + + pub fn generate_new_evm_coin_type_case(coin: &CoinItem) -> Result<()> { + let coin_type = coin.coin_type(); + let test_path = coin_address_derivation_tests_path(); + println!("[EDIT] {test_path:?}"); + + let mut evm_address_derivation_test_rs = FileContent::read(test_path)?; + + { + let mut switch_case_region = evm_address_derivation_test_rs + .rfind_line(|line| line.contains(EVM_ADDRESS_DERIVATION_TESTS_END))?; + switch_case_region.push_line_before(format!(" case TWCoinType{coin_type}:")); + } + + evm_address_derivation_test_rs.write() + } +} diff --git a/codegen-v2/src/codegen/cpp/tw_coin_type_generator.rs b/codegen-v2/src/codegen/cpp/tw_coin_type_generator.rs new file mode 100644 index 00000000000..0271838af03 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_coin_type_generator.rs @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::cpp::cpp_include_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const TW_COIN_TYPE_END: &str = "end_of_tw_coin_type_marker_do_not_modify"; + +pub fn tw_coin_type_path() -> PathBuf { + cpp_include_directory().join("TWCoinType.h") +} + +/// Represents `TWCoinType.h`. +pub struct TWCoinTypeGenerator; + +impl TWCoinTypeGenerator { + pub fn generate_coin_type_variant(coin: &CoinItem) -> Result<()> { + let coin_type = coin.coin_type(); + let coin_id_number = coin.coin_id_number; + let tw_coin_type_file_path = tw_coin_type_path(); + + println!("[EDIT] {tw_coin_type_file_path:?}"); + let mut tw_coin_type_rs = FileContent::read(tw_coin_type_file_path)?; + + { + let mut enum_region = + tw_coin_type_rs.rfind_line(|line| line.contains(TW_COIN_TYPE_END))?; + enum_region.push_line_before(format!(" TWCoinType{coin_type} = {coin_id_number},")); + } + + tw_coin_type_rs.write() + } +} diff --git a/codegen-v2/src/codegen/cpp/tw_coin_type_tests_generator.rs b/codegen-v2/src/codegen/cpp/tw_coin_type_tests_generator.rs new file mode 100644 index 00000000000..a3f9f50c5da --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_coin_type_tests_generator.rs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::cpp::{ + coin_integration_tests_directory, cosmos_coin_integration_tests_directory, +}; +use crate::codegen::template_generator::TemplateGenerator; +use crate::registry::CoinItem; +use crate::Result; +use std::fs; +use std::path::PathBuf; + +const TW_COIN_TYPE_TESTS_TEMPLATE: &str = include_str!("templates/TWCoinTypeTests.cpp"); + +pub fn tw_coin_type_tests_path(coin: &CoinItem) -> PathBuf { + coin_integration_tests_directory(coin).join("TWCoinTypeTests.cpp") +} + +pub struct TWCoinTypeTestsGenerator; + +impl TWCoinTypeTestsGenerator { + pub fn generate(coin: &CoinItem) -> Result<()> { + let coin_tests_dir = coin_integration_tests_directory(coin); + Self::generate_at(coin, coin_tests_dir) + } + + pub fn generate_cosmos(coin: &CoinItem) -> Result<()> { + let cosmos_coin_tests_dir = cosmos_coin_integration_tests_directory(coin); + Self::generate_at(coin, cosmos_coin_tests_dir) + } + + fn generate_at(coin: &CoinItem, coin_tests_dir: PathBuf) -> Result<()> { + let tw_coin_type_tests_path = coin_tests_dir.join("TWCoinTypeTests.cpp"); + + fs::create_dir(coin_tests_dir)?; + if tw_coin_type_tests_path.exists() { + println!("[SKIP] {tw_coin_type_tests_path:?} already exists"); + return Ok(()); + } + + println!("[ADD] {tw_coin_type_tests_path:?}"); + TemplateGenerator::new(TW_COIN_TYPE_TESTS_TEMPLATE) + .write_to(tw_coin_type_tests_path) + .with_default_patterns(coin) + .write()?; + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/mod.rs b/codegen-v2/src/codegen/mod.rs new file mode 100644 index 00000000000..1d8522e6b9f --- /dev/null +++ b/codegen-v2/src/codegen/mod.rs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod cpp; +pub mod proto; +pub mod rust; +pub mod swift; +pub mod template_generator; diff --git a/codegen-v2/src/codegen/proto/mod.rs b/codegen-v2/src/codegen/proto/mod.rs new file mode 100644 index 00000000000..47d3e1a2dd5 --- /dev/null +++ b/codegen-v2/src/codegen/proto/mod.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::env; +use std::path::PathBuf; + +pub mod new_blockchain; +pub mod proto_generator; + +pub fn proto_source_directory() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("src") + .join("proto") +} diff --git a/codegen-v2/src/codegen/proto/new_blockchain.rs b/codegen-v2/src/codegen/proto/new_blockchain.rs new file mode 100644 index 00000000000..9e5f51a5387 --- /dev/null +++ b/codegen-v2/src/codegen/proto/new_blockchain.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::proto::proto_generator::ProtoGenerator; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_blockchain(coin: &CoinItem) -> Result<()> { + ProtoGenerator::generate(coin) +} diff --git a/codegen-v2/src/codegen/proto/proto_generator.rs b/codegen-v2/src/codegen/proto/proto_generator.rs new file mode 100644 index 00000000000..f2cdfd94a86 --- /dev/null +++ b/codegen-v2/src/codegen/proto/proto_generator.rs @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::proto::proto_source_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::registry::CoinItem; +use crate::Result; +use std::path::PathBuf; + +const PROTO_TEMPLATE: &str = include_str!("templates/Blockchain.proto"); + +pub fn blockchain_proto_path(coin: &CoinItem) -> PathBuf { + let blockchain_type = coin.blockchain_type(); + proto_source_directory().join(format!("{blockchain_type}.proto")) +} + +pub struct ProtoGenerator; + +impl ProtoGenerator { + pub fn generate(coin: &CoinItem) -> Result<()> { + let proto_path = blockchain_proto_path(coin); + + if proto_path.exists() { + println!("[SKIP] Protobuf interface already exists: {proto_path:?}"); + return Ok(()); + } + + println!("[ADD] {proto_path:?}"); + TemplateGenerator::new(PROTO_TEMPLATE) + .write_to(proto_path) + .with_default_patterns(coin) + .write()?; + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/proto/templates/Blockchain.proto b/codegen-v2/src/codegen/proto/templates/Blockchain.proto new file mode 100644 index 00000000000..9c4c6e34478 --- /dev/null +++ b/codegen-v2/src/codegen/proto/templates/Blockchain.proto @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +syntax = "proto3"; + +package TW.{BLOCKCHAIN}.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// TODO: typical balance transfer, add more fields needed to sign +message TransferMessage { + int64 amount = 1; + int64 fee = 2; + string to = 3; +} + +// TODO: Input data necessary to create a signed transaction. +message SigningInput { + bytes private_key = 1; + + oneof message_oneof { + TransferMessage transfer = 2; + } +} + +// Transaction signing output. +message SigningOutput { + // Signed and encoded transaction bytes. + bytes encoded = 1; + + // A possible error, `OK` if none. + Common.Proto.SigningError error = 2; + + string error_message = 3; +} diff --git a/codegen-v2/src/codegen/rust/blockchain_dispatcher_generator.rs b/codegen-v2/src/codegen/rust/blockchain_dispatcher_generator.rs new file mode 100644 index 00000000000..92f98bac940 --- /dev/null +++ b/codegen-v2/src/codegen/rust/blockchain_dispatcher_generator.rs @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::rust::coin_registry_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const BLOCKCHAIN_ENTRIES_START: &str = "start_of_blockchain_entries"; +const BLOCKCHAIN_ENTRIES_END: &str = "end_of_blockchain_entries"; +const BLOCKCHAIN_DISPATCHER_START: &str = "start_of_blockchain_dispatcher"; +const BLOCKCHAIN_DISPATCHER_END: &str = "end_of_blockchain_dispatcher"; + +pub fn dispatcher_path() -> PathBuf { + coin_registry_directory().join("src").join("dispatcher.rs") +} + +pub struct BlockchainDispatcherGenerator; + +impl BlockchainDispatcherGenerator { + pub fn generate_new_blockchain_type_dispatching(coin: &CoinItem) -> Result<()> { + let dispatcher_rs_path = dispatcher_path(); + println!("[EDIT] {dispatcher_rs_path:?}"); + let mut dispatcher_rs = FileContent::read(dispatcher_rs_path)?; + + Self::generate_use_of_blockchain_entry(coin, &mut dispatcher_rs)?; + Self::generate_blockchain_entry_constant(coin, &mut dispatcher_rs)?; + Self::generate_blockchain_dispatch(coin, &mut dispatcher_rs)?; + + dispatcher_rs.write() + } + + fn generate_use_of_blockchain_entry( + coin: &CoinItem, + file_content: &mut FileContent, + ) -> Result<()> { + let import_pattern = "use "; + let blockchain_entry = coin.blockchain_entry(); + let tw_crate_name = coin.id.to_tw_crate_name(); + + let mut last_entry = file_content.rfind_line(|line| line.contains(import_pattern))?; + last_entry.push_line_after(format!("use {tw_crate_name}::entry::{blockchain_entry};")); + + Ok(()) + } + + fn generate_blockchain_entry_constant( + coin: &CoinItem, + file_content: &mut FileContent, + ) -> Result<()> { + let blockchain_entry = coin.blockchain_entry(); + let blockchain_entry_const = coin.blockchain_entry_upper_snake(); + + let mut entries_region = file_content + .find_region_with_comments(BLOCKCHAIN_ENTRIES_START, BLOCKCHAIN_ENTRIES_END)?; + entries_region.push_line(format!( + "const {blockchain_entry_const}: {blockchain_entry} = {blockchain_entry};" + )); + entries_region.sort(); + + Ok(()) + } + + fn generate_blockchain_dispatch(coin: &CoinItem, file_content: &mut FileContent) -> Result<()> { + let blockchain_type = coin.blockchain_type(); + let blockchain_entry_const = coin.blockchain_entry_upper_snake(); + + let mut dispatcher_region = file_content + .find_region_with_comments(BLOCKCHAIN_DISPATCHER_START, BLOCKCHAIN_DISPATCHER_END)?; + dispatcher_region.push_line(format!( + " BlockchainType::{blockchain_type} => Ok(&{blockchain_entry_const})," + )); + dispatcher_region.sort(); + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/rust/blockchain_type_generator.rs b/codegen-v2/src/codegen/rust/blockchain_type_generator.rs new file mode 100644 index 00000000000..4895bf8a923 --- /dev/null +++ b/codegen-v2/src/codegen/rust/blockchain_type_generator.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::rust::coin_registry_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const BLOCKCHAIN_TYPE_START: &str = "start_of_blockchain_type"; +const BLOCKCHAIN_TYPE_END: &str = "end_of_blockchain_type"; + +pub fn blockchain_type_path() -> PathBuf { + coin_registry_directory() + .join("src") + .join("blockchain_type.rs") +} + +/// Represents `BlockchainType` enum generator. +pub struct BlockchainTypeGenerator; + +impl BlockchainTypeGenerator { + pub fn add_new_blockchain_type(coin: &CoinItem) -> Result<()> { + let blockchain_type_rs_path = blockchain_type_path(); + let blockchain_type = coin.blockchain_type(); + + println!("[EDIT] {blockchain_type_rs_path:?}"); + let mut blockchain_type_rs = FileContent::read(blockchain_type_rs_path)?; + + { + let mut enum_region = blockchain_type_rs + .find_region_with_comments(BLOCKCHAIN_TYPE_START, BLOCKCHAIN_TYPE_END)?; + enum_region.push_line(format!(" {blockchain_type},")); + enum_region.sort(); + } + + blockchain_type_rs.write() + } +} diff --git a/codegen-v2/src/codegen/rust/coin_address_derivation_test_generator.rs b/codegen-v2/src/codegen/rust/coin_address_derivation_test_generator.rs new file mode 100644 index 00000000000..4d339cb05ad --- /dev/null +++ b/codegen-v2/src/codegen/rust/coin_address_derivation_test_generator.rs @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::rust::tw_tests_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const COIN_ADDRESS_DERIVATION_TEST_END: &str = + "end_of_coin_address_derivation_tests_marker_do_not_modify"; +const EVM_ADDRESS_DERIVATION_TEST_END: &str = + "end_of_evm_address_derivation_tests_marker_do_not_modify"; + +pub fn coin_address_derivation_test_path() -> PathBuf { + tw_tests_directory() + .join("tests") + .join("coin_address_derivation_test.rs") +} + +pub struct CoinAddressDerivationTestGenerator; + +impl CoinAddressDerivationTestGenerator { + pub fn generate_new_coin_type_case(coin: &CoinItem) -> Result<()> { + let test_path = coin_address_derivation_test_path(); + let coin_type = coin.coin_type(); + + println!("[EDIT] {test_path:?}"); + let mut coin_address_derivation_test_rs = FileContent::read(test_path)?; + + { + let mut end_of_test = coin_address_derivation_test_rs + .rfind_line(|line| line.contains(COIN_ADDRESS_DERIVATION_TEST_END))?; + end_of_test.push_line_before(format!(" CoinType::{coin_type} => todo!(),")); + } + + coin_address_derivation_test_rs.write() + } + + pub fn generate_new_evm_coin_type_case(coin: &CoinItem) -> Result<()> { + let test_path = coin_address_derivation_test_path(); + let coin_type = coin.coin_type(); + + println!("[EDIT] {test_path:?}"); + let mut coin_address_derivation_test_rs = FileContent::read(test_path)?; + + { + let mut end_of_test = coin_address_derivation_test_rs + .rfind_line(|line| line.contains(EVM_ADDRESS_DERIVATION_TEST_END))?; + end_of_test.push_line_before(format!(" | CoinType::{coin_type}")); + } + + coin_address_derivation_test_rs.write() + } +} diff --git a/codegen-v2/src/codegen/rust/coin_crate.rs b/codegen-v2/src/codegen/rust/coin_crate.rs new file mode 100644 index 00000000000..0e1b133f171 --- /dev/null +++ b/codegen-v2/src/codegen/rust/coin_crate.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::rust::chains_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::coin_id::CoinId; +use crate::registry::CoinItem; +use crate::Result; +use std::fs; +use std::path::PathBuf; + +const BLOCKCHAIN_ADDRESS_TEMPLATE: &str = include_str!("templates/blockchain_crate/address.rs"); +const BLOCKCHAIN_COMPILER_TEMPLATE: &str = include_str!("templates/blockchain_crate/compiler.rs"); +const BLOCKCHAIN_ENTRY_TEMPLATE: &str = include_str!("templates/blockchain_crate/entry.rs"); +const BLOCKCHAIN_MANIFEST_TEMPLATE: &str = include_str!("templates/blockchain_crate/Cargo.toml"); +const BLOCKCHAIN_LIB_TEMPLATE: &str = include_str!("templates/blockchain_crate/lib.rs"); +const BLOCKCHAIN_SIGNER_TEMPLATE: &str = include_str!("templates/blockchain_crate/signer.rs"); + +pub fn coin_source_directory(id: &CoinId) -> PathBuf { + chains_directory().join(id.to_tw_crate_name()) +} + +pub struct CoinCrate { + coin: CoinItem, +} + +impl CoinCrate { + pub fn new(coin: CoinItem) -> CoinCrate { + CoinCrate { coin } + } + + /// Creates a Cargo crate with `entry.rs` file. + /// Returns the path to the create crate. + pub fn create(self) -> Result { + let blockchain_path = coin_source_directory(&self.coin.id); + let blockchain_toml_path = blockchain_path.join("Cargo.toml"); + + let blockchain_src_path = blockchain_path.join("src"); + let blockchain_lib_rs_path = blockchain_src_path.join("lib.rs"); + let blockchain_entry_path = blockchain_src_path.join("entry.rs"); + let blockchain_compiler_path = blockchain_src_path.join("compiler.rs"); + let blockchain_address_rs_path = blockchain_src_path.join("address.rs"); + let blockchain_signer_rs_path = blockchain_src_path.join("signer.rs"); + + if blockchain_path.exists() { + let tw_crate_name = self.coin.id.to_tw_crate_name(); + println!( + "[SKIP] '{tw_crate_name}' blockchain crate already exists: {blockchain_path:?}" + ); + return Ok(blockchain_path); + } + + fs::create_dir_all(&blockchain_path)?; + fs::create_dir_all(&blockchain_src_path)?; + + println!("[ADD] {blockchain_toml_path:?}"); + TemplateGenerator::new(BLOCKCHAIN_MANIFEST_TEMPLATE) + .write_to(blockchain_toml_path) + .with_default_patterns(&self.coin) + .write()?; + + println!("[ADD] {blockchain_lib_rs_path:?}"); + TemplateGenerator::new(BLOCKCHAIN_LIB_TEMPLATE) + .write_to(blockchain_lib_rs_path) + .with_default_patterns(&self.coin) + .write()?; + + println!("[ADD] {blockchain_entry_path:?}"); + TemplateGenerator::new(BLOCKCHAIN_ENTRY_TEMPLATE) + .write_to(blockchain_entry_path) + .with_default_patterns(&self.coin) + .write()?; + + println!("[ADD] {blockchain_compiler_path:?}"); + TemplateGenerator::new(BLOCKCHAIN_COMPILER_TEMPLATE) + .write_to(blockchain_compiler_path) + .with_default_patterns(&self.coin) + .write()?; + + println!("[ADD] {blockchain_address_rs_path:?}"); + TemplateGenerator::new(BLOCKCHAIN_ADDRESS_TEMPLATE) + .write_to(blockchain_address_rs_path) + .with_default_patterns(&self.coin) + .write()?; + + println!("[ADD] {blockchain_signer_rs_path:?}"); + TemplateGenerator::new(BLOCKCHAIN_SIGNER_TEMPLATE) + .write_to(blockchain_signer_rs_path) + .with_default_patterns(&self.coin) + .write()?; + + Ok(blockchain_path) + } +} diff --git a/codegen-v2/src/codegen/rust/coin_integration_tests.rs b/codegen-v2/src/codegen/rust/coin_integration_tests.rs new file mode 100644 index 00000000000..067462aacfc --- /dev/null +++ b/codegen-v2/src/codegen/rust/coin_integration_tests.rs @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::rust::tw_tests_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::coin_id::CoinId; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::fs; +use std::path::PathBuf; + +const ADDRESS_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/address_tests.rs"); +const COSMOS_ADDRESS_TESTS_TEMPLATE: &str = + include_str!("templates/integration_tests/cosmos_address_tests.rs"); +const COMPILE_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/compile_tests.rs"); +const MOD_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/mod.rs"); +const MOD_ADDRESS_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/mod_address.rs"); +const SIGN_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/sign_tests.rs"); + +pub fn chains_integration_tests_directory() -> PathBuf { + tw_tests_directory().join("tests").join("chains") +} + +pub fn coin_integration_tests_directory(id: &CoinId) -> PathBuf { + chains_integration_tests_directory().join(id.as_str()) +} + +pub fn coin_address_derivation_test_path() -> PathBuf { + tw_tests_directory() + .join("tests") + .join("coin_address_derivation_test.rs") +} + +pub struct CoinIntegrationTests { + coin: CoinItem, +} + +impl CoinIntegrationTests { + pub fn new(coin: CoinItem) -> CoinIntegrationTests { + CoinIntegrationTests { coin } + } + + pub fn create(self) -> Result { + let blockchain_tests_path = self.coin_tests_directory(); + if blockchain_tests_path.exists() { + println!("[SKIP] integration tests already exists: {blockchain_tests_path:?}"); + return Ok(blockchain_tests_path); + } + + fs::create_dir_all(&blockchain_tests_path)?; + + self.list_blockchain_in_chains_mod()?; + self.create_address_tests(ADDRESS_TESTS_TEMPLATE)?; + self.create_compile_tests()?; + self.create_sign_tests()?; + self.create_chain_tests_mod_rs(MOD_TESTS_TEMPLATE)?; + + Ok(blockchain_tests_path) + } + + /// For a Cosmos chain, it's enough to create address tests only, but with additional Bech32 prefix tests. + pub fn create_cosmos(self) -> Result { + let blockchain_tests_path = self.coin_tests_directory(); + if blockchain_tests_path.exists() { + println!("[SKIP] integration tests already exists: {blockchain_tests_path:?}"); + return Ok(blockchain_tests_path); + } + + fs::create_dir_all(&blockchain_tests_path)?; + + self.list_blockchain_in_chains_mod()?; + self.create_address_tests(COSMOS_ADDRESS_TESTS_TEMPLATE)?; + // `mod.rs` should contain `{COIN_TYPE}_address` module only. + self.create_chain_tests_mod_rs(MOD_ADDRESS_TESTS_TEMPLATE)?; + + Ok(blockchain_tests_path) + } + + fn coin_tests_directory(&self) -> PathBuf { + coin_integration_tests_directory(&self.coin.id) + } + + fn create_address_tests(&self, template: &'static str) -> Result<()> { + let coin_id = self.coin.id.as_str(); + let address_tests_path = self + .coin_tests_directory() + .join(format!("{coin_id}_address.rs")); + + println!("[ADD] {address_tests_path:?}"); + TemplateGenerator::new(template) + .write_to(address_tests_path) + .with_default_patterns(&self.coin) + .write() + } + + fn create_compile_tests(&self) -> Result<()> { + let coin_id = self.coin.id.as_str(); + let compile_tests_path = self + .coin_tests_directory() + .join(format!("{coin_id}_compile.rs")); + + println!("[ADD] {compile_tests_path:?}"); + TemplateGenerator::new(COMPILE_TESTS_TEMPLATE) + .write_to(compile_tests_path) + .with_default_patterns(&self.coin) + .write() + } + + fn create_sign_tests(&self) -> Result<()> { + let coin_id = self.coin.id.as_str(); + let sign_tests_path = self + .coin_tests_directory() + .join(format!("{coin_id}_sign.rs")); + + println!("[ADD] {sign_tests_path:?}"); + TemplateGenerator::new(SIGN_TESTS_TEMPLATE) + .write_to(sign_tests_path) + .with_default_patterns(&self.coin) + .write() + } + + fn create_chain_tests_mod_rs(&self, template: &'static str) -> Result<()> { + let blockchain_tests_mod_path = self.coin_tests_directory().join("mod.rs"); + + println!("[ADD] {blockchain_tests_mod_path:?}"); + TemplateGenerator::new(template) + .write_to(blockchain_tests_mod_path) + .with_default_patterns(&self.coin) + .write() + } + + fn list_blockchain_in_chains_mod(&self) -> Result<()> { + let chains_mod_path = chains_integration_tests_directory().join("mod.rs"); + let chain_id = self.coin.id.as_str(); + + println!("[EDIT] {chains_mod_path:?}"); + let mut chains_mod_rs = FileContent::read(chains_mod_path)?; + + { + let mod_pattern = "mod "; + let mut mod_region = chains_mod_rs.find_region_with_prefix(mod_pattern)?; + mod_region.push_line(format!("mod {chain_id};")); + mod_region.sort(); + } + + chains_mod_rs.write() + } +} diff --git a/codegen-v2/src/codegen/rust/coin_registry_manifest_generator.rs b/codegen-v2/src/codegen/rust/coin_registry_manifest_generator.rs new file mode 100644 index 00000000000..2a63e90e336 --- /dev/null +++ b/codegen-v2/src/codegen/rust/coin_registry_manifest_generator.rs @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::rust::coin_registry_directory; +use crate::codegen::rust::toml_editor::Dependencies; +use crate::registry::CoinItem; +use crate::Result; +use std::path::Path; + +pub struct CoinRegistryManifestGenerator; + +impl CoinRegistryManifestGenerator { + pub fn add_dependency(coin: &CoinItem, path_to_new_blockchain_crate: &Path) -> Result<()> { + let path_to_cargo_manifest = coin_registry_directory().join("Cargo.toml"); + println!("[EDIT] {path_to_cargo_manifest:?}"); + Dependencies::new(path_to_cargo_manifest) + .insert_dependency(&coin.id.to_tw_crate_name(), path_to_new_blockchain_crate) + } +} diff --git a/codegen-v2/src/codegen/rust/mod.rs b/codegen-v2/src/codegen/rust/mod.rs new file mode 100644 index 00000000000..a9de11f38fa --- /dev/null +++ b/codegen-v2/src/codegen/rust/mod.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::env; +use std::path::PathBuf; + +pub mod blockchain_dispatcher_generator; +pub mod blockchain_type_generator; +pub mod coin_address_derivation_test_generator; +pub mod coin_crate; +pub mod coin_integration_tests; +pub mod coin_registry_manifest_generator; +pub mod new_blockchain; +pub mod new_cosmos_chain; +pub mod new_evmchain; +pub mod toml_editor; + +pub fn rust_source_directory() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("rust") +} + +pub fn chains_directory() -> PathBuf { + rust_source_directory().join("chains") +} + +pub fn tw_tests_directory() -> PathBuf { + rust_source_directory().join("tw_tests") +} + +pub fn workspace_toml_path() -> PathBuf { + rust_source_directory().join("Cargo.toml") +} + +pub fn coin_registry_directory() -> PathBuf { + rust_source_directory().join("tw_coin_registry") +} diff --git a/codegen-v2/src/codegen/rust/new_blockchain.rs b/codegen-v2/src/codegen/rust/new_blockchain.rs new file mode 100644 index 00000000000..ca27f77b08e --- /dev/null +++ b/codegen-v2/src/codegen/rust/new_blockchain.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::rust::blockchain_dispatcher_generator::BlockchainDispatcherGenerator; +use crate::codegen::rust::blockchain_type_generator::BlockchainTypeGenerator; +use crate::codegen::rust::coin_address_derivation_test_generator::CoinAddressDerivationTestGenerator; +use crate::codegen::rust::coin_crate::CoinCrate; +use crate::codegen::rust::coin_integration_tests::CoinIntegrationTests; +use crate::codegen::rust::coin_registry_manifest_generator::CoinRegistryManifestGenerator; +use crate::codegen::rust::toml_editor::Workspace; +use crate::codegen::rust::workspace_toml_path; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_blockchain(coin: &CoinItem) -> Result<()> { + // Create blockchain's crate. + let blockchain_crate_path = CoinCrate::new(coin.clone()).create()?; + + // Insert the created crate to the workspace. + Workspace::new(workspace_toml_path()).insert_crate(&blockchain_crate_path)?; + + // Create integration tests. + CoinIntegrationTests::new(coin.clone()).create()?; + CoinAddressDerivationTestGenerator::generate_new_coin_type_case(coin)?; + + // Add the new blockchain to the `tw_coin_registry`. + BlockchainTypeGenerator::add_new_blockchain_type(coin)?; + CoinRegistryManifestGenerator::add_dependency(coin, &blockchain_crate_path)?; + BlockchainDispatcherGenerator::generate_new_blockchain_type_dispatching(coin)?; + + Ok(()) +} diff --git a/codegen-v2/src/codegen/rust/new_cosmos_chain.rs b/codegen-v2/src/codegen/rust/new_cosmos_chain.rs new file mode 100644 index 00000000000..96151ff6446 --- /dev/null +++ b/codegen-v2/src/codegen/rust/new_cosmos_chain.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::rust::coin_address_derivation_test_generator::CoinAddressDerivationTestGenerator; +use crate::codegen::rust::coin_integration_tests::CoinIntegrationTests; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_cosmos_chain(coin: &CoinItem) -> Result<()> { + // Create integration tests. + CoinIntegrationTests::new(coin.clone()).create_cosmos()?; + CoinAddressDerivationTestGenerator::generate_new_coin_type_case(coin)?; + + Ok(()) +} diff --git a/codegen-v2/src/codegen/rust/new_evmchain.rs b/codegen-v2/src/codegen/rust/new_evmchain.rs new file mode 100644 index 00000000000..f754fa5ba1b --- /dev/null +++ b/codegen-v2/src/codegen/rust/new_evmchain.rs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::rust::coin_address_derivation_test_generator::CoinAddressDerivationTestGenerator; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_evmchain(coin: &CoinItem) -> Result<()> { + // Modify integration tests. + CoinAddressDerivationTestGenerator::generate_new_evm_coin_type_case(coin) +} diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/Cargo.toml b/codegen-v2/src/codegen/rust/templates/blockchain_crate/Cargo.toml new file mode 100644 index 00000000000..9d5a7a7d04a --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "{TW_CRATE_NAME}" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/address.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/address.rs new file mode 100644 index 00000000000..3ec4f8516b5 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/address.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::fmt; +use std::str::FromStr; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::*; +use tw_memory::Data; + +pub struct {BLOCKCHAIN}Address { + // TODO add necessary fields. +} + +impl CoinAddress for {BLOCKCHAIN}Address { + #[inline] + fn data(&self) -> Data { + todo!() + } +} + +impl FromStr for {BLOCKCHAIN}Address { + type Err = AddressError; + + fn from_str(_s: &str) -> Result { + todo!() + } +} + +impl fmt::Display for {BLOCKCHAIN}Address { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + todo!() + } +} diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/compiler.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/compiler.rs new file mode 100644 index 00000000000..63d51f1df50 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/compiler.rs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_proto::{BLOCKCHAIN}::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct {BLOCKCHAIN}Compiler; + +impl {BLOCKCHAIN}Compiler { + #[inline] + pub fn preimage_hashes( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + Self::preimage_hashes_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + fn preimage_hashes_impl( + _coin: &dyn CoinContext, + _input: Proto::SigningInput<'_>, + ) -> SigningResult> { + todo!() + } + + #[inline] + pub fn compile( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + Self::compile_impl(coin, input, signatures, public_keys) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn compile_impl( + _coin: &dyn CoinContext, + _input: Proto::SigningInput<'_>, + _signatures: Vec, + _public_keys: Vec, + ) -> SigningResult> { + todo!() + } +} diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs new file mode 100644 index 00000000000..9b1194bbb68 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::{BLOCKCHAIN}Address; +use crate::compiler::{BLOCKCHAIN}Compiler; +use crate::signer::{BLOCKCHAIN}Signer; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; +use tw_coin_entry::modules::wallet_connector::NoWalletConnector; +use tw_coin_entry::prefix::NoPrefix; +use tw_keypair::tw::PublicKey; +use tw_proto::{BLOCKCHAIN}::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct {BLOCKCHAIN}Entry; + +impl CoinEntry for {BLOCKCHAIN}Entry { + type AddressPrefix = NoPrefix; + type Address = {BLOCKCHAIN}Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + type WalletConnector = NoWalletConnector; + type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + _address: &str, + _prefix: Option, + ) -> AddressResult { + todo!() + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + {BLOCKCHAIN}Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + _coin: &dyn CoinContext, + _public_key: PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + todo!() + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + {BLOCKCHAIN}Signer::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + {BLOCKCHAIN}Compiler::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + {BLOCKCHAIN}Compiler::compile(coin, input, signatures, public_keys) + } +} diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/lib.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/lib.rs new file mode 100644 index 00000000000..c41edeb4471 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/lib.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod address; +pub mod compiler; +pub mod entry; +pub mod signer; diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/signer.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/signer.rs new file mode 100644 index 00000000000..3a64018e599 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/signer.rs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_proto::{BLOCKCHAIN}::Proto; + +pub struct {BLOCKCHAIN}Signer; + +impl {BLOCKCHAIN}Signer { + pub fn sign( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> Proto::SigningOutput<'static> { + Self::sign_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_impl( + _coin: &dyn CoinContext, + _input: Proto::SigningInput<'_>, + ) -> SigningResult> { + todo!() + } +} diff --git a/codegen-v2/src/codegen/rust/templates/integration_tests/address_tests.rs b/codegen-v2/src/codegen/rust/templates/integration_tests/address_tests.rs new file mode 100644 index 00000000000..1d417a77ed4 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/address_tests.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_any_coin::test_utils::address_utils::{ + test_address_derive, test_address_get_data, test_address_invalid, test_address_normalization, + test_address_valid, +}; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_{COIN_ID}_address_derive() { + test_address_derive(CoinType::{COIN_TYPE}, "PRIVATE_KEY", "EXPECTED ADDRESS"); +} + +#[test] +fn test_{COIN_ID}_address_normalization() { + test_address_normalization(CoinType::{COIN_TYPE}, "DENORMALIZED", "EXPECTED"); +} + +#[test] +fn test_{COIN_ID}_address_is_valid() { + test_address_valid(CoinType::{COIN_TYPE}, "VALID ADDRESS"); +} + +#[test] +fn test_{COIN_ID}_address_invalid() { + test_address_invalid(CoinType::{COIN_TYPE}, "INVALID ADDRESS"); +} + +#[test] +fn test_{COIN_ID}_address_get_data() { + test_address_get_data(CoinType::{COIN_TYPE}, "ADDRESS", "HEX(DATA)"); +} diff --git a/codegen-v2/src/codegen/rust/templates/integration_tests/compile_tests.rs b/codegen-v2/src/codegen/rust/templates/integration_tests/compile_tests.rs new file mode 100644 index 00000000000..fad7f69330a --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/compile_tests.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#[test] +fn test_{COIN_ID}_compile() { + todo!() +} diff --git a/codegen-v2/src/codegen/rust/templates/integration_tests/cosmos_address_tests.rs b/codegen-v2/src/codegen/rust/templates/integration_tests/cosmos_address_tests.rs new file mode 100644 index 00000000000..6bf6079ac9d --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/cosmos_address_tests.rs @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_any_coin::test_utils::address_utils::{ + test_address_bech32_is_valid, test_address_create_bech32_with_public_key, + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, + AddressBech32IsValid, AddressCreateBech32WithPublicKey, +}; +use tw_coin_registry::coin_type::CoinType; +use tw_keypair::tw::PublicKeyType; + +#[test] +fn test_{COIN_ID}_address_normalization() { + test_address_normalization(CoinType::{COIN_TYPE}, "DENORMALIZED", "EXPECTED"); +} + +#[test] +fn test_{COIN_ID}_address_is_valid() { + test_address_valid(CoinType::{COIN_TYPE}, "VALID {COIN_TYPE} ADDRESS"); +} + +#[test] +fn test_{COIN_ID}_address_invalid() { + test_address_invalid(CoinType::{COIN_TYPE}, "INVALID ADDRESS"); + // Cosmos has a different `hrp`. + test_address_invalid(CoinType::Cosmos, "VALID {COIN_TYPE} ADDRESS"); +} + +#[test] +fn test_{COIN_ID}_address_get_data() { + test_address_get_data(CoinType::{COIN_TYPE}, "ADDRESS", "HEX(DATA)"); +} + +#[test] +fn test_{COIN_ID}_is_valid_bech32() { + // {COIN_TYPE} address must be valid if its Base32 prefix passed. + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::{COIN_TYPE}, + address: "{COIN_TYPE} ADDRESS", + hrp: "{HRP}", + }); + // {COIN_TYPE} address must be valid for the standard Cosmos hub if its Base32 prefix passed. + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::Cosmos, + address: "{COIN_TYPE} ADDRESS", + hrp: "{HRP}", + }); + // Cosmos address must be valid with "cosmos" hrp. + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::{COIN_TYPE}, + address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", + hrp: "cosmos", + }); +} + +#[test] +fn test_any_address_create_bech32_with_public_key() { + test_address_create_bech32_with_public_key(AddressCreateBech32WithPublicKey { + // TODO consider using `CoinType::{COIN_TYPE}` if the chain's `addressHasher` is different from Cosmos's. + coin: CoinType::Cosmos, + private_key: "PRIVATE_KEY", + // TODO consider using another `PublicKeyType` if the chain's `publicKeyType` is different from Cosmos's. + public_key_type: PublicKeyType::Secp256k1, + hrp: "{HRP}", + expected: "{COIN_TYPE} ADDRESS", + }); +} + diff --git a/codegen-v2/src/codegen/rust/templates/integration_tests/mod.rs b/codegen-v2/src/codegen/rust/templates/integration_tests/mod.rs new file mode 100644 index 00000000000..15dccad969f --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/mod.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +mod {COIN_ID}_address; +mod {COIN_ID}_compile; +mod {COIN_ID}_sign; diff --git a/codegen-v2/src/codegen/rust/templates/integration_tests/mod_address.rs b/codegen-v2/src/codegen/rust/templates/integration_tests/mod_address.rs new file mode 100644 index 00000000000..6f501e180bc --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/mod_address.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +mod {COIN_ID}_address; diff --git a/codegen-v2/src/codegen/rust/templates/integration_tests/sign_tests.rs b/codegen-v2/src/codegen/rust/templates/integration_tests/sign_tests.rs new file mode 100644 index 00000000000..38f01a4e0e1 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/sign_tests.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#[test] +fn test_{COIN_ID}_sign() { + todo!() +} diff --git a/codegen-v2/src/codegen/rust/toml_editor.rs b/codegen-v2/src/codegen/rust/toml_editor.rs new file mode 100644 index 00000000000..0811182365d --- /dev/null +++ b/codegen-v2/src/codegen/rust/toml_editor.rs @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::{Error, Result}; +use std::fs; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use toml_edit::{Document, InlineTable, Item, Value}; + +const NEW_LINE_TAB_DECORATOR: &str = "\n "; +const NO_DECORATOR: &str = ""; + +pub struct Workspace { + path_to_toml: PathBuf, +} + +impl Workspace { + pub fn new(path_to_toml: PathBuf) -> Workspace { + Workspace { path_to_toml } + } + + pub fn insert_crate(self, path_to_crate: &Path) -> Result<()> { + let manifest = fs::read_to_string(&self.path_to_toml)?; + let mut manifest = Document::from_str(&manifest)?; + + let members = manifest["workspace"]["members"] + .as_array_mut() + .ok_or(Error::TomlFormat( + "Invalid 'workspace' TOML format".to_string(), + ))?; + + // Try to get a path to the crate relative to the `Cargo.toml`. + let relative_path_to_crate = relative_path_to_crate(&self.path_to_toml, path_to_crate)?; + + // Push the new member, sort and save the manifest. + + let relative_path_to_crate_decorated = + Value::from(relative_path_to_crate).decorated(NEW_LINE_TAB_DECORATOR, NO_DECORATOR); + + members.push_formatted(relative_path_to_crate_decorated); + members.sort_by(|x, y| x.as_str().cmp(&y.as_str())); + + fs::write(self.path_to_toml, manifest.to_string())?; + Ok(()) + } +} + +pub struct Dependencies { + path_to_toml: PathBuf, +} + +impl Dependencies { + pub fn new(path_to_toml: PathBuf) -> Dependencies { + Dependencies { path_to_toml } + } + + pub fn insert_dependency(self, dep_name: &str, path_to_dep_crate: &Path) -> Result<()> { + let manifest = fs::read_to_string(&self.path_to_toml)?; + let mut manifest = Document::from_str(&manifest)?; + + let dependencies = manifest["dependencies"] + .as_table_like_mut() + .ok_or(Error::TomlFormat("Invalid 'Cargo.toml' format".to_string()))?; + + // Try to get a path to the crate relative to the `Cargo.toml`. + let relative_path_to_crate = relative_path_to_crate(&self.path_to_toml, path_to_dep_crate)?; + + // Create the new dependency member (aka a TOML inline table with `path` key-value). + let mut new_member = InlineTable::new(); + new_member.insert("path", relative_path_to_crate.into()); + + // Push the new member, sort and save the manifest. + dependencies.insert(dep_name, Item::Value(Value::InlineTable(new_member))); + dependencies.sort_values(); + + fs::write(self.path_to_toml, manifest.to_string())?; + + Ok(()) + } +} + +/// Returns a path to the dependency accordingly to the Cargo manifest file. +/// The result string can be put to `Cargo.toml` as: +/// ```toml +/// tw_foo = { path = "" } +/// ``` +fn relative_path_to_crate( + path_to_cargo_manifest: &Path, + path_to_dependency: &Path, +) -> Result { + let absolute_path_to_crate_directory = path_to_cargo_manifest + .parent() + .ok_or_else(|| Error::io_error_other("Cannot get a parent directory".to_string()))? + .canonicalize()?; + let absolute_path_to_dependency = path_to_dependency.canonicalize()?; + + let relative_path_to_dependency = pathdiff::diff_paths( + absolute_path_to_dependency, + absolute_path_to_crate_directory, + ) + .ok_or_else(|| { + Error::io_error_other("Cannot get a relative path to the dependency".to_string()) + })? + .to_str() + .ok_or_else(|| Error::io_error_other("Invalid path to the crate".to_string()))? + .to_string(); + + Ok(relative_path_to_dependency) +} diff --git a/codegen-v2/src/codegen/swift/functions.rs b/codegen-v2/src/codegen/swift/functions.rs new file mode 100644 index 00000000000..1f7e4aa8d39 --- /dev/null +++ b/codegen-v2/src/codegen/swift/functions.rs @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use super::*; +use crate::manifest::{FunctionInfo, TypeVariant}; +use heck::ToLowerCamelCase; + +/// This function checks each function and determines whether there's an +/// association with the passed on object (struct or enum), based on common name +/// prefix, and maps the data into a Swift structure. +/// +/// This function returns a tuple of associated Swift functions and the skipped +/// respectively non-associated functions. +pub(super) fn process_methods( + object: &ObjectVariant, + functions: Vec, +) -> Result<(Vec, Vec)> { + let mut swift_funcs = vec![]; + let mut skipped_funcs = vec![]; + + for func in functions { + if !func.name.starts_with(object.name()) { + // Function is not assciated with the object. + skipped_funcs.push(func); + continue; + } + + let mut ops = vec![]; + + // Initalize the 'self' type, which is then passed on to the underlying + // C FFI function, assuming the function is not static. + // + // E.g: + // - `let obj = self.rawValue` + // - `let obj = TWSomeEnum(rawValue: self.RawValue")` + if !func.is_static { + ops.push(match object { + ObjectVariant::Struct(_) => SwiftOperation::Call { + var_name: "obj".to_string(), + call: "self.rawValue".to_string(), + defer: None, + }, + ObjectVariant::Enum(name) => SwiftOperation::Call { + var_name: "obj".to_string(), + call: format!("{}(rawValue: self.rawValue)", name), + defer: None, + }, + }); + } + + // For each parameter, we track a list of `params` which is used for the + // function interface and add the necessary operations on how to process + // those parameters. + let mut params = vec![]; + for param in func.params { + // Skip self parameter + match ¶m.ty.variant { + TypeVariant::Enum(name) | TypeVariant::Struct(name) if name == object.name() => { + continue + } + _ => {} + } + + // Convert parameter to Swift parameter for the function interface. + params.push(SwiftParam { + name: param.name.clone(), + param_type: SwiftType::from(param.ty.variant.clone()), + is_nullable: param.ty.is_nullable, + }); + + // Process parameter. + if let Some(op) = param_c_ffi_call(¶m) { + ops.push(op) + } + } + + // Prepepare parameter list to be passed on to the underlying C FFI function. + let param_name = if func.is_static { vec![] } else { vec!["obj"] }; + let param_names = param_name + .into_iter() + .chain(params.iter().map(|p| p.name.as_str())) + .collect::>() + .join(","); + + // Call the underlying C FFI function, passing on the parameter list. + let (var_name, call) = ( + "result".to_string(), + format!("{}({})", func.name, param_names), + ); + if func.return_type.is_nullable { + ops.push(SwiftOperation::GuardedCall { var_name, call }); + } else { + ops.push(SwiftOperation::Call { + var_name, + call, + defer: None, + }); + } + + // Wrap result. + ops.push(wrap_return(&func.return_type)); + + // Convert return type for function interface. + let return_type = SwiftReturn { + param_type: SwiftType::from(func.return_type.variant), + is_nullable: func.return_type.is_nullable, + }; + + // Prettify name, remove object name prefix from this property. + let pretty_name = func + .name + .strip_prefix(object.name()) + // Panicing implies bug, checked at the start of the loop. + .unwrap() + .to_lower_camel_case(); + + // Special handling: some functions do not follow standard camelCase + // convention. + #[rustfmt::skip] + let pretty_name = if object.name() == "TWStoredKey" { + pretty_name + .replace("Json", "JSON") + .replace("Hd", "HD") + } else if object.name() == "TWPublicKey" { + pretty_name + .replace("Der", "DER") + } else if object.name() == "TWHash" { + pretty_name + .replace("ripemd", "RIPEMD") + .replace("Ripemd", "RIPEMD") + .replace("sha512256", "sha512_256") + .replace("sha3256", "sha3_256") + .replace("sha256sha256", "sha256SHA256") + } else if object.name() == "TWAES" { + pretty_name + .replace("Cbc", "CBC") + .replace("Ctr", "CTR") + } else { + pretty_name + }; + + swift_funcs.push(SwiftFunction { + name: pretty_name, + is_public: func.is_public, + is_static: func.is_static, + operations: ops, + params, + return_type, + comments: vec![], + }); + } + + Ok((swift_funcs, skipped_funcs)) +} diff --git a/codegen-v2/src/codegen/swift/inits.rs b/codegen-v2/src/codegen/swift/inits.rs new file mode 100644 index 00000000000..a1f140c326e --- /dev/null +++ b/codegen-v2/src/codegen/swift/inits.rs @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use super::*; +use crate::manifest::InitInfo; + +/// This function checks each constructor and determines whether there's an +/// association with the passed on object (struct or enum), based on common name +/// prefix, and maps the data into a Swift structure. +/// +/// This function returns a tuple of associated Swift constructor and the skipped +/// respectively non-associated constructors. +pub(super) fn process_inits( + object: &ObjectVariant, + inits: Vec, +) -> Result<(Vec, Vec)> { + let mut swift_inits = vec![]; + let mut skipped_inits = vec![]; + + for init in inits { + if !init.name.starts_with(object.name()) { + // Init is not assciated with the object. + skipped_inits.push(init); + continue; + } + + let mut ops = vec![]; + + // For each parameter, we track a list of `params` which is used for the + // function interface and add the necessary operations on how to process + // those parameters. + let mut params = vec![]; + for param in init.params { + // Convert parameter to Swift parameter. + params.push(SwiftParam { + name: param.name.clone(), + param_type: SwiftType::from(param.ty.variant.clone()), + is_nullable: param.ty.is_nullable, + }); + + // Process parameter. + if let Some(op) = param_c_ffi_call(¶m) { + ops.push(op); + } + } + + // Prepepare parameter list to be passed on to the underlying C FFI function. + let param_names = params + .iter() + .map(|p| p.name.as_str()) + .collect::>() + .join(","); + + // Call the underlying C FFI function, passing on the parameter list. + if init.is_nullable { + ops.push(SwiftOperation::GuardedCall { + var_name: "result".to_string(), + call: format!("{}({})", init.name, param_names), + }); + } else { + ops.push(SwiftOperation::Call { + var_name: "result".to_string(), + call: format!("{}({})", init.name, param_names), + defer: None, + }); + } + + // Note that we do not return a value here; the template sets a + // `self.rawValue = result` entry at the end of the constructor. + + // Prettify name, remove object name prefix from this property. + let pretty_name = init + .name + .strip_prefix(object.name()) + // Panicing implies bug, checked at the start of the loop. + .unwrap() + .to_string(); + + swift_inits.push(SwiftInit { + name: pretty_name, + is_nullable: init.is_nullable, + is_public: init.is_public, + params, + operations: ops, + comments: vec![], + }); + } + + Ok((swift_inits, skipped_inits)) +} + +pub(super) fn process_deinits( + object: &ObjectVariant, + deinit: Vec, +) -> Result<(Vec, Vec)> { + let mut swift_deinits = vec![]; + let mut skipped_deinits = vec![]; + + for deinit in deinit { + if deinit.name.starts_with(object.name()) { + swift_deinits.push(deinit) + } else { + // Deinit is not assciated with the object. + skipped_deinits.push(deinit); + continue; + } + } + + Ok((swift_deinits, skipped_deinits)) +} diff --git a/codegen-v2/src/codegen/swift/mod.rs b/codegen-v2/src/codegen/swift/mod.rs new file mode 100644 index 00000000000..4bc70e7e416 --- /dev/null +++ b/codegen-v2/src/codegen/swift/mod.rs @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use self::functions::process_methods; +use self::inits::process_inits; +use self::properties::process_properties; +use self::render::pretty_name; +use crate::manifest::{DeinitInfo, FileInfo, ParamInfo, ProtoInfo, TypeInfo, TypeVariant}; +use crate::{Error, Result}; +use handlebars::Handlebars; +use serde_json::json; +use std::fmt::Display; + +mod functions; +mod inits; +mod properties; +mod render; + +// Re-exports +pub use self::render::{ + generate_swift_types, render_to_strings, GeneratedSwiftTypes, GeneratedSwiftTypesStrings, + RenderIntput, +}; + +/// Represents a Swift struct or class. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SwiftStruct { + name: String, + is_class: bool, + is_public: bool, + init_instance: bool, + superclasses: Vec, + eq_operator: Option, + inits: Vec, + deinits: Vec, + methods: Vec, + properties: Vec, +} + +/// Represents a Swift enum. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SwiftEnum { + name: String, + is_public: bool, + add_description: bool, + superclasses: Vec, + variants: Vec, +} + +/// Represents a Swift enum variant. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SwiftEnumVariant { + name: String, + value: String, + as_string: Option, +} + +/// Represents associated methods and properties of an enum. Based on the first +/// codegen, those extensions are placed in a separate file. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SwiftEnumExtension { + name: String, + init_instance: bool, + methods: Vec, + properties: Vec, +} + +// Wrapper around a valid Swift type (built in or custom). Meant to be used as +// `>::from(...)`. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SwiftType(String); + +impl Display for SwiftType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Represents a Swift function or method. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SwiftFunction { + pub name: String, + pub is_public: bool, + pub is_static: bool, + pub params: Vec, + pub operations: Vec, + #[serde(rename = "return")] + pub return_type: SwiftReturn, + pub comments: Vec, +} + +/// Represents a Swift property of a struct/class or enum. +#[derive(Debug, Clone, Serialize, Deserialize)] +struct SwiftProperty { + pub name: String, + pub is_public: bool, + pub operations: Vec, + #[serde(rename = "return")] + pub return_type: SwiftReturn, + pub comments: Vec, +} + +/// The operation to be interpreted by the templating engine. This handles +/// parameters and C FFI calls in an appropriate way, depending on context. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum SwiftOperation { + // Results in: + // ```swift + // let = + // defer { + // () + // } + // ``` + Call { + var_name: String, + call: String, + defer: Option, + }, + // Results in: + // ```swift + // let ptr: UnsafeRawPointer? + // if let alphabet = alphabet { + // ptr = TWStringCreateWithNSString(alphabet) + // } else { + // ptr = nil + // } + // ``` + // ... with an optional `defer` operation. + CallOptional { + var_name: String, + call: String, + defer: Option, + }, + // Results in: + // ```swift + // let = + // guard let = else { + // return nil + // } + // ``` + GuardedCall { + var_name: String, + call: String, + }, + // Results in: + // ```swift + // return + // ``` + Return { + call: String, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SwiftParam { + pub name: String, + #[serde(rename = "type")] + pub param_type: SwiftType, + pub is_nullable: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SwiftReturn { + #[serde(rename = "type")] + pub param_type: SwiftType, + pub is_nullable: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SwiftInit { + pub name: String, + pub is_nullable: bool, + pub is_public: bool, + pub params: Vec, + pub operations: Vec, + pub comments: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SwiftProto { + pub name: String, + pub c_ffi_name: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SwiftOperatorEquality { + pub c_ffi_name: String, +} + +/// Used for the individual `process_*` functions. +enum ObjectVariant<'a> { + Struct(&'a str), + Enum(&'a str), +} + +impl<'a> ObjectVariant<'a> { + fn name(&'a self) -> &'a str { + match self { + ObjectVariant::Struct(n) | ObjectVariant::Enum(n) => n, + } + } +} + +impl TryFrom for SwiftProto { + type Error = Error; + + fn try_from(value: ProtoInfo) -> std::result::Result { + Ok(SwiftProto { + // Convert the name into an appropriate format. + name: pretty_name(value.0.clone()), + c_ffi_name: value.0, + }) + } +} + +/// Convert the `TypeVariant` into the appropriate Swift type. +impl From for SwiftType { + fn from(value: TypeVariant) -> Self { + let res = match value { + TypeVariant::Void => "Void".to_string(), + TypeVariant::Bool => "Bool".to_string(), + TypeVariant::Char => "Character".to_string(), + TypeVariant::ShortInt => "Int16".to_string(), + TypeVariant::Int => "Int32".to_string(), + TypeVariant::UnsignedInt => "UInt32".to_string(), + TypeVariant::LongInt => "Int64".to_string(), + TypeVariant::Float => "Float".to_string(), + TypeVariant::Double => "Double".to_string(), + TypeVariant::SizeT => "Int".to_string(), + TypeVariant::Int8T => "Int8".to_string(), + TypeVariant::Int16T => "Int16".to_string(), + TypeVariant::Int32T => "Int32".to_string(), + TypeVariant::Int64T => "Int64".to_string(), + TypeVariant::UInt8T => "UInt8".to_string(), + TypeVariant::UInt16T => "UInt16".to_string(), + TypeVariant::UInt32T => "UInt32".to_string(), + TypeVariant::UInt64T => "UInt64".to_string(), + TypeVariant::String => "String".to_string(), + TypeVariant::Data => "Data".to_string(), + TypeVariant::Struct(n) | TypeVariant::Enum(n) => { + // We strip the "TW" prefix for Swift representations of + // structs/enums. + n.strip_prefix("TW").map(|n| n.to_string()).unwrap_or(n) + } + }; + + SwiftType(res) + } +} + +// Covenience function: process the parameter, returning the operation for +// handling the C FFI call (if any). +fn param_c_ffi_call(param: &ParamInfo) -> Option { + let op = match ¶m.ty.variant { + // E.g. `let param = TWStringCreateWithNSString(param)` + TypeVariant::String => { + let (var_name, call, defer) = ( + param.name.clone(), + format!("TWStringCreateWithNSString({})", param.name), + Some(format!("TWStringDelete({})", param.name)), + ); + + // If the parameter is nullable, add special handler. + if param.ty.is_nullable { + SwiftOperation::CallOptional { + var_name, + call, + defer, + } + } else { + SwiftOperation::Call { + var_name, + call, + defer, + } + } + } + TypeVariant::Data => { + let (var_name, call, defer) = ( + param.name.clone(), + format!("TWDataCreateWithNSData({})", param.name), + Some(format!("TWDataDelete({})", param.name)), + ); + + // If the parameter is nullable, add special handler. + if param.ty.is_nullable { + SwiftOperation::CallOptional { + var_name, + call, + defer, + } + } else { + SwiftOperation::Call { + var_name, + call, + defer, + } + } + } + // E.g. + // - `let param = param.rawValue` + // - `let param = param?.rawValue` + TypeVariant::Struct(_) => { + // For nullable structs, we do not use the special + // `CallOptional` handler but rather use the question mark + // operator. + let (var_name, call, defer) = if param.ty.is_nullable { + ( + param.name.clone(), + format!("{}?.rawValue", param.name), + None, + ) + } else { + (param.name.clone(), format!("{}.rawValue", param.name), None) + }; + + SwiftOperation::Call { + var_name, + call, + defer, + } + } + // E.g. `let param = TWSomeEnum(rawValue: param.rawValue)` + // Note that it calls the constructor of the enum, which calls + // the underlying "*Create*" C FFI function. + TypeVariant::Enum(enm) => SwiftOperation::Call { + var_name: param.name.clone(), + call: format!("{enm}(rawValue: {}.rawValue)", param.name), + defer: None, + }, + // Skip processing parameter, reference the parameter by name + // directly, as defined in the function interface (usually the + // case for primitive types). + _ => return None, + }; + + Some(op) +} + +// Convenience funcion: wrap the return value, returning the operation. Note +// that types are wrapped differently when returning, compared to +// `param_c_ffi_call`; such as using `TWStringNSString` instead of +// `TWDataCreateWithNSData` for Strings. +fn wrap_return(ty: &TypeInfo) -> SwiftOperation { + match &ty.variant { + // E.g.`return TWStringNSString(result)` + TypeVariant::String => SwiftOperation::Return { + call: "TWStringNSString(result)".to_string(), + }, + TypeVariant::Data => SwiftOperation::Return { + call: "TWDataNSData(result)".to_string(), + }, + // E.g. `return SomeEnum(rawValue: result.rawValue)` + TypeVariant::Enum(_) => SwiftOperation::Return { + call: format!( + "{}(rawValue: result.rawValue)!", + SwiftType::from(ty.variant.clone()) + ), + }, + // E.g. `return SomeStruct(rawValue: result)` + TypeVariant::Struct(_) => SwiftOperation::Return { + call: format!("{}(rawValue: result)", SwiftType::from(ty.variant.clone())), + }, + _ => SwiftOperation::Return { + call: "result".to_string(), + }, + } +} diff --git a/codegen-v2/src/codegen/swift/properties.rs b/codegen-v2/src/codegen/swift/properties.rs new file mode 100644 index 00000000000..61d73e7171f --- /dev/null +++ b/codegen-v2/src/codegen/swift/properties.rs @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use super::*; +use crate::manifest::PropertyInfo; +use heck::ToLowerCamelCase; + +/// This function checks each property and determines whether there's an +/// association with the passed on object (struct or enum), based on common name +/// prefix, and maps the data into a Swift structure. +/// +/// This function returns a tuple of associated Swift properties and skipped +/// respectively non-associated properties. +pub(super) fn process_properties( + object: &ObjectVariant, + properties: Vec, +) -> Result<(Vec, Vec)> { + let mut swift_props = vec![]; + let mut skipped_props = vec![]; + + for prop in properties { + if !prop.name.starts_with(object.name()) { + // Property is not assciated with the object. + skipped_props.push(prop); + continue; + } + + let mut ops = vec![]; + + // Initalize the 'self' type, which is then passed on to the underlying + // C FFI function. + ops.push(match object { + // E.g. `let obj = self.rawValue` + ObjectVariant::Struct(_) => SwiftOperation::Call { + var_name: "obj".to_string(), + call: "self.rawValue".to_string(), + defer: None, + }, + // E.g. `let obj = TWSomeEnum(rawValue: self.rawValue")` + ObjectVariant::Enum(name) => SwiftOperation::Call { + var_name: "obj".to_string(), + call: format!("{}(rawValue: self.rawValue)", name), + defer: None, + }, + }); + + // Call the underlying C FFI function, passing on the `obj` instance. + // + // E.g: `let result = TWSomeFunc(obj)`. + let (var_name, call) = ("result".to_string(), format!("{}(obj)", prop.name)); + if prop.return_type.is_nullable { + ops.push(SwiftOperation::GuardedCall { var_name, call }); + } else { + ops.push(SwiftOperation::Call { + var_name, + call, + defer: None, + }); + } + + // Wrap result. + ops.push(wrap_return(&prop.return_type)); + + // Prettify name, remove object name prefix from this property. + let pretty_name = prop + .name + .strip_prefix(object.name()) + // Panicing implies bug, checked at the start of the loop. + .unwrap() + .to_lower_camel_case(); + + // Convert return type for property interface. + let return_type = SwiftReturn { + param_type: SwiftType::from(prop.return_type.variant), + is_nullable: prop.return_type.is_nullable, + }; + + swift_props.push(SwiftProperty { + name: pretty_name, + is_public: prop.is_public, + operations: ops, + return_type, + comments: vec![], + }); + } + + Ok((swift_props, skipped_props)) +} diff --git a/codegen-v2/src/codegen/swift/render.rs b/codegen-v2/src/codegen/swift/render.rs new file mode 100644 index 00000000000..465a7277682 --- /dev/null +++ b/codegen-v2/src/codegen/swift/render.rs @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use super::{inits::process_deinits, *}; + +#[derive(Debug, Clone)] +pub struct RenderIntput<'a> { + pub file_info: FileInfo, + pub struct_template: &'a str, + pub enum_template: &'a str, + pub extension_template: &'a str, + pub proto_template: &'a str, + pub partial_init_template: &'a str, + pub partial_func_tempalte: &'a str, + pub partial_prop_tempalte: &'a str, +} + +#[derive(Debug, Clone, Default)] +pub struct GeneratedSwiftTypesStrings { + pub structs: Vec<(String, String)>, + pub enums: Vec<(String, String)>, + pub extensions: Vec<(String, String)>, + pub protos: Vec<(String, String)>, +} + +#[derive(Debug, Clone, Default)] +pub struct GeneratedSwiftTypes { + pub structs: Vec, + pub enums: Vec, + pub extensions: Vec, + pub protos: Vec, +} + +/// Convenience wrapper for setting copyright year when generating bindings. +#[derive(Debug, Clone, Serialize)] +struct WithYear<'a, T> { + pub current_year: u64, + #[serde(flatten)] + pub data: &'a T, +} + +pub fn pretty_name(name: String) -> String { + name.replace("_", "").replace("TW", "").replace("Proto", "") +} + +pub fn render_to_strings<'a>(input: RenderIntput<'a>) -> Result { + // The current year for the copyright header in the generated bindings. + let current_year = crate::current_year(); + // Convert the name into an appropriate format. + let pretty_file_name = pretty_name(input.file_info.name.clone()); + + let mut engine = Handlebars::new(); + // Unmatched variables should result in an error. + engine.set_strict_mode(true); + + engine.register_partial("struct", input.struct_template)?; + engine.register_partial("enum", input.enum_template)?; + engine.register_partial("extension", input.extension_template)?; + engine.register_partial("proto", input.proto_template)?; + engine.register_partial("partial_init", input.partial_init_template)?; + engine.register_partial("partial_func", input.partial_func_tempalte)?; + engine.register_partial("partial_prop", input.partial_prop_tempalte)?; + + let rendered = generate_swift_types(input.file_info)?; + let mut out_str = GeneratedSwiftTypesStrings::default(); + + // Render structs. + for strct in rendered.structs { + let out = engine.render( + "struct", + &WithYear { + current_year, + data: &strct, + }, + )?; + + out_str.structs.push((strct.name, out)); + } + + // Render enums. + for enm in rendered.enums { + let out = engine.render( + "enum", + &WithYear { + current_year, + data: &enm, + }, + )?; + + out_str.enums.push((enm.name, out)); + } + + // Render extensions. + for ext in rendered.extensions { + let out = engine.render( + "extension", + &WithYear { + current_year, + data: &ext, + }, + )?; + + out_str.extensions.push((ext.name, out)); + } + + // Render protos. + if !rendered.protos.is_empty() { + let out = engine.render( + "proto", + &WithYear { + current_year, + data: &json!({ + "protos": &rendered.protos + }), + }, + )?; + + out_str.protos.push((pretty_file_name, out)); + } + + Ok(out_str) +} + +/// Uses the given input templates to render all files. +pub fn generate_swift_types(mut info: FileInfo) -> Result { + let mut outputs = GeneratedSwiftTypes::default(); + + // Render structs/classes. + for strct in info.structs { + let obj = ObjectVariant::Struct(&strct.name); + + // Process items. + let (inits, deinits, mut methods, properties); + (inits, info.inits) = process_inits(&obj, info.inits)?; + (deinits, info.deinits) = process_deinits(&obj, info.deinits)?; + (methods, info.functions) = process_methods(&obj, info.functions)?; + (properties, info.properties) = process_properties(&obj, info.properties)?; + + // Avoid rendering empty structs. + if inits.is_empty() && methods.is_empty() && properties.is_empty() { + continue; + } + + // Convert the name into an appropriate format. + let pretty_struct_name = pretty_name(strct.name.clone()); + + // Add superclasses. + let superclasses = if pretty_struct_name.ends_with("Address") { + vec!["Address".to_string()] + } else { + vec![] + }; + + // Handle equality operator. + let eq_method = methods.iter().enumerate().find(|(_, f)| f.name == "equal"); + let eq_operator = if let Some((idx, _)) = eq_method { + let operator = SwiftOperatorEquality { + c_ffi_name: format!("{}Equal", strct.name), + }; + + // Remove that method from the `methods` list. + methods.remove(idx); + + Some(operator) + } else { + None + }; + + outputs.structs.push(SwiftStruct { + name: pretty_struct_name, + is_class: strct.is_class, + is_public: strct.is_public, + init_instance: strct.is_class, + superclasses, + eq_operator, + inits: inits, + deinits: deinits, + methods, + properties, + }); + } + + // Render enums. + for enm in info.enums { + let obj = ObjectVariant::Enum(&enm.name); + + // Process items. + let (methods, properties); + (methods, info.functions) = process_methods(&obj, info.functions)?; + (properties, info.properties) = process_properties(&obj, info.properties)?; + + // Convert the name into an appropriate format. + let pretty_enum_name = pretty_name(enm.name); + + // Add superclasses. + let value_type = SwiftType::from(enm.value_type); + let mut superclasses = vec![value_type.0, "CaseIterable".to_string()]; + + let mut add_class = false; + + // Convert to Swift enum variants + let variants = enm + .variants + .into_iter() + .map(|info| { + if info.as_string.is_some() { + add_class = true; + } + + SwiftEnumVariant { + name: info.name, + value: info.value, + as_string: info.as_string, + } + }) + .collect(); + + if add_class { + superclasses.push("CustomStringConvertible".to_string()); + } + + outputs.enums.push(SwiftEnum { + name: pretty_enum_name.clone(), + is_public: enm.is_public, + add_description: add_class, + superclasses, + variants, + }); + + // Avoid rendering empty extension for enums. + if methods.is_empty() && properties.is_empty() { + continue; + } + + outputs.extensions.push(SwiftEnumExtension { + name: pretty_enum_name, + init_instance: true, + methods, + properties, + }); + } + + // Render Protobufs. + if !info.protos.is_empty() { + for proto in info.protos { + outputs.protos.push(SwiftProto::try_from(proto)?); + } + } + + Ok(outputs) +} diff --git a/codegen-v2/src/codegen/swift/templates/WalletCore.h b/codegen-v2/src/codegen/swift/templates/WalletCore.h new file mode 100644 index 00000000000..76f61b9fa74 --- /dev/null +++ b/codegen-v2/src/codegen/swift/templates/WalletCore.h @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#import + +//! Project version number for TrustWalletCore. +FOUNDATION_EXPORT double WalletCoreVersionNumber; + +//! Project version string for TrustWalletCore. +FOUNDATION_EXPORT const unsigned char WalletCoreVersionString[]; + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" + +#include "TWAnySigner.h" + +#include "TWAES.h" +#include "TWAESPaddingMode.h" +#include "TWAccount.h" +#include "TWAnyAddress.h" +#include "TWAsnParser.h" +#include "TWBarz.h" +#include "TWBase32.h" +#include "TWBase58.h" +#include "TWBase64.h" +#include "TWBitcoinAddress.h" +#include "TWBitcoinMessageSigner.h" +#include "TWBitcoinScript.h" +#include "TWBitcoinSigHashType.h" +#include "TWBlockchain.h" +#include "TWCardano.h" +#include "TWCoinType.h" +#include "TWCoinTypeConfiguration.h" +#include "TWCurve.h" +#include "TWDataVector.h" +#include "TWDerivation.h" +#include "TWDerivationPath.h" +#include "TWDerivationPathIndex.h" +#include "TWEthereum.h" +#include "TWEthereumAbi.h" +#include "TWEthereumAbiFunction.h" +#include "TWEthereumAbiValue.h" +#include "TWEthereumChainID.h" +#include "TWEthereumRlp.h" +#include "TWEthereumMessageSigner.h" +#include "TWFIOAccount.h" +#include "TWFilecoinAddressConverter.h" +#include "TWFilecoinAddressType.h" +#include "TWGroestlcoinAddress.h" +#include "TWHDVersion.h" +#include "TWHDWallet.h" +#include "TWHRP.h" +#include "TWHash.h" +#include "TWLiquidStaking.h" +#include "TWMnemonic.h" +#include "TWNEARAccount.h" +#include "TWNervosAddress.h" +#include "TWPBKDF2.h" +#include "TWPrivateKey.h" +#include "TWPrivateKeyType.h" +#include "TWPublicKey.h" +#include "TWPublicKeyType.h" +#include "TWPurpose.h" +#include "TWRippleXAddress.h" +#include "TWSS58AddressType.h" +#include "TWSegwitAddress.h" +#include "TWSolanaAddress.h" +#include "TWStarkExMessageSigner.h" +#include "TWStarkWare.h" +#include "TWStellarMemoType.h" +#include "TWStellarPassphrase.h" +#include "TWStellarVersionByte.h" +#include "TWStoredKey.h" +#include "TWStoredKeyEncryption.h" +#include "TWStoredKeyEncryptionLevel.h" +#include "TWTHORChainSwap.h" +#include "TWTezosMessageSigner.h" +#include "TWTransactionCompiler.h" +#include "TWTronMessageSigner.h" diff --git a/codegen-v2/src/codegen/swift/templates/enum.hbs b/codegen-v2/src/codegen/swift/templates/enum.hbs new file mode 100644 index 00000000000..5363fb776b4 --- /dev/null +++ b/codegen-v2/src/codegen/swift/templates/enum.hbs @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here WILL BE LOST. +// + +{{#if is_public}}public {{/if}}enum {{name}} + {{~#if superclasses}}: {{/if}}{{#each superclasses}}{{this}}{{#unless @last}}, {{/unless}}{{/each}} { + {{#each variants}} + case `{{this.name}}` = {{this.value}} + {{/each}} + {{#if add_description}} + + public var description: String { + switch self { + {{#each variants}} + case .{{this.name}}: return "{{this.as_string}}" + {{/each}} + } + } + {{/if}} +} diff --git a/codegen-v2/src/codegen/swift/templates/extension.hbs b/codegen-v2/src/codegen/swift/templates/extension.hbs new file mode 100644 index 00000000000..ddae1f95ba6 --- /dev/null +++ b/codegen-v2/src/codegen/swift/templates/extension.hbs @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here WILL BE LOST. +// + +extension {{name}} { + {{! Methods }} + {{#each methods}} + {{~> partial_func}} + {{#unless @last}} + + {{/unless}} + {{/each}} + {{! Properties }} + {{#each properties}} + {{~> partial_prop}} + {{#unless @last}} + + {{/unless}} + {{/each}} +} diff --git a/codegen-v2/src/codegen/swift/templates/partial_func.hbs b/codegen-v2/src/codegen/swift/templates/partial_func.hbs new file mode 100644 index 00000000000..5ebc73fd8e8 --- /dev/null +++ b/codegen-v2/src/codegen/swift/templates/partial_func.hbs @@ -0,0 +1,38 @@ + {{#if is_public}}public {{/if}}{{#if is_static}}static {{/if}}func {{name}}({{#each params}}{{name}}: {{type}}{{#if is_nullable}}?{{/if}}{{#unless @last}}, {{/unless}}{{/each}}) -> {{return.type}}{{#if return.is_nullable}}?{{/if}} { + {{#each operations}} + {{#if this.call}} + let {{this.call.var_name}} = {{this.call.call}} + {{#if this.call.defer}} + defer { + {{this.call.defer}} + } + + {{/if}} + {{/if}} + {{#if this.call_optional}} + let ptr: UnsafeRawPointer? + if let {{this.call_optional.var_name}} = {{this.call_optional.var_name}} { + ptr = {{this.call_optional.call}} + } else { + ptr = nil + } + {{#if this.call_optional.defer}} + defer { + if let {{this.call_optional.var_name}} = ptr { + {{this.call_optional.defer}} + } + } + {{/if}} + let {{this.call_optional.var_name}} = ptr + + {{/if}} + {{#if this.guarded_call}} + guard let {{this.guarded_call.var_name}} = {{this.guarded_call.call}} else { + return nil + } + {{/if}} + {{#if this.return}} + return {{this.return.call}} + {{/if}} + {{/each}} + } diff --git a/codegen-v2/src/codegen/swift/templates/partial_init.hbs b/codegen-v2/src/codegen/swift/templates/partial_init.hbs new file mode 100644 index 00000000000..c367bde8435 --- /dev/null +++ b/codegen-v2/src/codegen/swift/templates/partial_init.hbs @@ -0,0 +1,23 @@ + {{#if is_public}}public {{/if}}init{{#if is_nullable}}?{{/if}}({{#each params}}{{name}}: {{type}}{{#if is_nullable}}?{{/if}}{{#unless @last}}, {{/unless}}{{/each}}) { + {{#each operations}} + {{#if this.call}} + let {{this.call.var_name}} = {{this.call.call}} + {{#if this.call.defer}} + defer { + {{this.call.defer}} + } + + {{/if}} + {{/if}} + {{#if this.guarded_call}} + guard let {{this.guarded_call.var_name}} = {{this.guarded_call.call}} else { + return nil + } + {{/if}} + {{#if this.return}} + return {{this.return.call}} + {{/if}} + {{/each}} + + self.rawValue = result + } diff --git a/codegen-v2/src/codegen/swift/templates/partial_prop.hbs b/codegen-v2/src/codegen/swift/templates/partial_prop.hbs new file mode 100644 index 00000000000..fd1d5697866 --- /dev/null +++ b/codegen-v2/src/codegen/swift/templates/partial_prop.hbs @@ -0,0 +1,38 @@ + {{#if is_public}}public {{/if}}var {{name}}: {{return.type}}{{#if return.is_nullable}}?{{/if}} { + {{#each operations}} + {{#if this.call}} + let {{this.call.var_name}} = {{this.call.call}} + {{#if this.call.defer}} + defer { + {{this.call.defer}} + } + + {{/if}} + {{/if}} + {{#if this.call_optional}} + let ptr: UnsafeRawPointer? + if let {{this.call_optional.var_name}} = {{this.call_optional.var_name}} { + ptr = {{this.call_optional.call}} + } else { + ptr = nil + } + {{#if this.call_optional.defer}} + defer { + if let {{this.call_optional.var_name}} = ptr { + {{this.call_optional.defer}} + } + } + {{/if}} + let {{this.call_optional.var_name}} = ptr + + {{/if}} + {{#if this.guarded_call}} + guard let {{this.guarded_call.var_name}} = {{this.guarded_call.call}} else { + return nil + } + {{/if}} + {{#if this.return}} + return {{this.return.call}} + {{/if}} + {{/each}} + } diff --git a/codegen-v2/src/codegen/swift/templates/proto.hbs b/codegen-v2/src/codegen/swift/templates/proto.hbs new file mode 100644 index 00000000000..23e1c541698 --- /dev/null +++ b/codegen-v2/src/codegen/swift/templates/proto.hbs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here WILL BE LOST. +// + +{{#each protos}} +public typealias {{name}} = {{c_ffi_name}} +{{/each}} diff --git a/codegen-v2/src/codegen/swift/templates/struct.hbs b/codegen-v2/src/codegen/swift/templates/struct.hbs new file mode 100644 index 00000000000..eccc0499fd7 --- /dev/null +++ b/codegen-v2/src/codegen/swift/templates/struct.hbs @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here WILL BE LOST. +// + +import Foundation + +{{#if is_public}}public {{/if}}{{#if is_class}}final class {{else}}struct {{/if}}{{name}} + {{~#if superclasses}}: {{/if}}{{#each superclasses}}{{this}}{{#unless @last}}, {{/unless}}{{/each}} { + {{#if init_instance}} + let rawValue: OpaquePointer + + init(rawValue: OpaquePointer) { + self.rawValue = rawValue + } + {{else}} + init() {} + {{/if}} + + {{! Equality operator, if available }} + {{#if eq_operator}} + public static func == (lhs: {{name}}, rhs: {{name}}) -> Bool { + return {{eq_operator.c_ffi_name}}(lhs.rawValue, rhs.rawValue) + } + + {{/if}} + {{! Inits }} + {{#each inits}} + {{~> partial_init}} + + {{/each}} + {{! Deinits }} + {{#each deinits}} + deinit { + {{name}}(self.rawValue) + } + + {{/each}} + {{! Methods }} + {{#each methods}} + {{~> partial_func}} + + {{/each}} + {{! Properties }} + {{#each properties}} + {{~> partial_prop}} + {{#unless @last}} + + {{/unless}} + {{/each}} +} diff --git a/codegen-v2/src/codegen/template_generator.rs b/codegen-v2/src/codegen/template_generator.rs new file mode 100644 index 00000000000..7b725ba74e7 --- /dev/null +++ b/codegen-v2/src/codegen/template_generator.rs @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::registry::CoinItem; +use crate::{current_year, Error, Result}; +use std::fs; +use std::path::PathBuf; + +const PATTERNS_CAPACITY: usize = 20; + +pub struct TemplateGenerator { + template_content: &'static str, + write_to: Option, + to_replace: Vec, + replace_with: Vec, +} + +impl TemplateGenerator { + pub fn new(template_content: &'static str) -> TemplateGenerator { + TemplateGenerator { + template_content, + write_to: None, + to_replace: Vec::with_capacity(PATTERNS_CAPACITY), + replace_with: Vec::with_capacity(PATTERNS_CAPACITY), + } + } + + pub fn write_to(mut self, write_to: PathBuf) -> TemplateGenerator { + self.write_to = Some(write_to); + self + } + + /// Use default patterns. + pub fn with_default_patterns(self, coin: &CoinItem) -> TemplateGenerator { + self.add_pattern("{YEAR}", current_year()) + .add_pattern("{BLOCKCHAIN}", coin.blockchain_type()) + .add_pattern("{TW_CRATE_NAME}", coin.id.to_tw_crate_name()) + .add_pattern("{COIN_ID}", coin.id.as_str()) + .add_pattern("{COIN_TYPE}", coin.coin_type()) + .add_pattern("{COIN_NAME}", if coin.display_name.len() > 0 { &coin.display_name } else { &coin.name }) + .add_pattern("{SYMBOL}", &coin.symbol) + .add_pattern("{DECIMALS}", coin.decimals) + .add_pattern("{P2PKH_PREFIX}", coin.p2pkh_prefix) + .add_pattern("{P2SH_PREFIX}", coin.p2sh_prefix) + .add_pattern("{HRP}", coin.hrp.as_str()) + .add_pattern("{STATIC_PREFIX}", coin.static_prefix) + .add_pattern("{EXPLORER_URL}", &coin.explorer.url) + .add_pattern("{EXPLORER_TX_PATH}", &coin.explorer.tx_path) + .add_pattern("{EXPLORER_ACCOUNT_PATH}", &coin.explorer.account_path) + .add_pattern("{EXPLORER_SAMPLE_TX}", &coin.explorer.sample_tx) + .add_pattern("{EXPLORER_SAMPLE_ACCOUNT}", &coin.explorer.sample_account) + } + + pub fn add_pattern( + mut self, + to_replace: K, + replace_with: V, + ) -> TemplateGenerator { + self.to_replace.push(to_replace.to_string()); + self.replace_with.push(replace_with.to_string()); + self + } + + pub fn write(self) -> Result<()> { + let write_to_path = self.write_to.ok_or_else(|| { + Error::io_error_other("Incorrect use of 'TemplateGenerator'".to_string()) + })?; + let file_to_write = fs::File::create(write_to_path)?; + + aho_corasick::AhoCorasick::new(self.to_replace) + .map_err(|e| Error::io_error_other(format!("Invalid patterns: {e}")))? + .try_stream_replace_all( + self.template_content.as_bytes(), + file_to_write, + &self.replace_with, + ) + .map_err(Error::from) + } +} diff --git a/codegen-v2/src/coin_id.rs b/codegen-v2/src/coin_id.rs new file mode 100644 index 00000000000..87a6b9e13c4 --- /dev/null +++ b/codegen-v2/src/coin_id.rs @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::{Error, Result}; +use serde::de::Error as SerdeError; +use serde::{Deserialize, Deserializer}; + +#[derive(Clone, Eq, PartialEq)] +pub struct CoinId(String); + +impl CoinId { + /// Returns `Ok` if only the given `id` is a valid Rust identifier. + pub fn new(id: String) -> Result { + let first_letter = id + .chars() + .next() + .ok_or(Error::RegistryError("Invalid 'id'".to_string()))?; + let valid_chars = id.chars().all(|ch| ch.is_ascii_alphanumeric() || ch == '_'); + + if first_letter.is_numeric() || !valid_chars { + return Err(Error::RegistryError("Invalid 'id'".to_string())); + } + Ok(CoinId(id)) + } + + pub fn to_tw_crate_name(&self) -> String { + format!("tw_{}", self.0) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl<'de> Deserialize<'de> for CoinId { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let id = String::deserialize(deserializer)?; + CoinId::new(id).map_err(|e| SerdeError::custom(format!("{e:?}"))) + } +} diff --git a/codegen-v2/src/lib.rs b/codegen-v2/src/lib.rs new file mode 100644 index 00000000000..3e2c2e2e59d --- /dev/null +++ b/codegen-v2/src/lib.rs @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#[macro_use] +extern crate serde; + +use handlebars::{RenderError, TemplateError}; +use serde_yaml::Error as YamlError; +use std::io; +use std::io::Error as IoError; +use toml_edit::TomlError; + +pub mod codegen; +pub mod coin_id; +pub mod manifest; +pub mod registry; +#[cfg(test)] +mod tests; +pub mod utils; + +pub type Result = std::result::Result; + +#[derive(Debug)] +pub enum Error { + IoError(IoError), + YamlError(YamlError), + RenderError(RenderError), + TemplateError(TemplateError), + BadFormat(String), + RegistryError(String), + TomlFormat(String), + InvalidCommand, +} + +impl Error { + pub fn io_error_other(err: String) -> Error { + Error::IoError(IoError::new(io::ErrorKind::Other, err)) + } +} + +impl From for Error { + fn from(err: IoError) -> Self { + Error::IoError(err) + } +} + +impl From for Error { + fn from(err: YamlError) -> Self { + Error::YamlError(err) + } +} + +impl From for Error { + fn from(err: RenderError) -> Self { + Error::RenderError(err) + } +} + +impl From for Error { + fn from(err: TemplateError) -> Self { + Error::TemplateError(err) + } +} + +impl From for Error { + fn from(err: TomlError) -> Self { + Error::TomlFormat(err.to_string()) + } +} + +fn current_year() -> u64 { + use std::time::{SystemTime, UNIX_EPOCH}; + + let now = SystemTime::now(); + let seconds_since_epoch = now + .duration_since(UNIX_EPOCH) + .expect("System's time is set before the start of the Unix epoch"); + + // One Gregorian calendar year has 365.2425 days, + // respectively 31556952 seconds. + 1970 + (seconds_since_epoch.as_secs() / 31556952) +} diff --git a/codegen-v2/src/main.rs b/codegen-v2/src/main.rs new file mode 100644 index 00000000000..53fd140cc65 --- /dev/null +++ b/codegen-v2/src/main.rs @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use libparser::codegen::swift::RenderIntput; +use libparser::codegen::{cpp, proto, rust}; +use libparser::coin_id::CoinId; +use libparser::manifest::parse_dir; +use libparser::registry::read_coin_from_registry; +use libparser::{Error, Result}; +use std::fs::read_to_string; + +fn main() -> Result<()> { + let args: Vec = std::env::args().collect(); + + if args.len() < 2 { + panic!("Invalid command"); + } + + match args[1].as_str() { + "new-blockchain-rust" => new_blockchain_rust(&args[2..]), + "new-blockchain" => new_blockchain(&args[2..]), + "new-evmchain" => new_evmchain(&args[2..]), + "new-cosmos-chain" => new_cosmos_chain(&args[2..]), + "swift" => generate_swift_bindings(), + _ => Err(Error::InvalidCommand), + } +} + +fn new_blockchain_rust(args: &[String]) -> Result<()> { + let coin_str = args.iter().next().ok_or_else(|| Error::InvalidCommand)?; + let coin_id = CoinId::new(coin_str.clone())?; + let coin_item = read_coin_from_registry(&coin_id)?; + + println!("New Rust blockchain template for coin '{coin_str}' requested"); + rust::new_blockchain::new_blockchain(&coin_item)?; + + Ok(()) +} + +fn new_blockchain(args: &[String]) -> Result<()> { + let coin_str = args.iter().next().ok_or_else(|| Error::InvalidCommand)?; + let coin_id = CoinId::new(coin_str.clone())?; + let coin_item = read_coin_from_registry(&coin_id)?; + + println!("New '{coin_str}' blockchain template requested"); + + proto::new_blockchain::new_blockchain(&coin_item)?; + rust::new_blockchain::new_blockchain(&coin_item)?; + cpp::new_blockchain::new_blockchain(&coin_item)?; + + Ok(()) +} + +fn new_evmchain(args: &[String]) -> Result<()> { + let coin_str = args.iter().next().ok_or_else(|| Error::InvalidCommand)?; + let coin_id = CoinId::new(coin_str.clone())?; + let coin_item = read_coin_from_registry(&coin_id)?; + + println!("New '{coin_str}' EVM chain template requested"); + + rust::new_evmchain::new_evmchain(&coin_item)?; + cpp::new_evmchain::new_evmchain(&coin_item)?; + + Ok(()) +} + +fn new_cosmos_chain(args: &[String]) -> Result<()> { + let coin_str = args.iter().next().ok_or_else(|| Error::InvalidCommand)?; + let coin_id = CoinId::new(coin_str.clone())?; + let coin_item = read_coin_from_registry(&coin_id)?; + + println!("New '{coin_str}' Cosmos chain template requested"); + + rust::new_cosmos_chain::new_cosmos_chain(&coin_item)?; + cpp::new_cosmos_chain::new_cosmos_chain(&coin_item)?; + + Ok(()) +} + +fn generate_swift_bindings() -> Result<()> { + // NOTE: The paths will be configurable, eventually. + const OUT_DIR: &str = "bindings/"; + const IN_DIR: &str = "src/codegen/swift/templates"; + + std::fs::create_dir_all(OUT_DIR)?; + + let struct_t = read_to_string(&format!("{IN_DIR}/struct.hbs"))?; + let enum_t = read_to_string(&format!("{IN_DIR}/enum.hbs"))?; + let ext_t = read_to_string(&format!("{IN_DIR}/extension.hbs"))?; + let proto_t = read_to_string(&format!("{IN_DIR}/proto.hbs"))?; + let part_init_t = read_to_string(&format!("{IN_DIR}/partial_init.hbs"))?; + let part_func_t = read_to_string(&format!("{IN_DIR}/partial_func.hbs"))?; + let part_prop_t = read_to_string(&format!("{IN_DIR}/partial_prop.hbs"))?; + + // Read the manifest dir, generate bindings for each entry. + let file_infos = parse_dir("manifest/")?; + + for file_info in file_infos { + let input = RenderIntput { + file_info, + struct_template: &struct_t, + enum_template: &enum_t, + extension_template: &ext_t, + proto_template: &proto_t, + partial_init_template: &part_init_t, + partial_func_tempalte: &part_func_t, + partial_prop_tempalte: &part_prop_t, + }; + + let rendered = libparser::codegen::swift::render_to_strings(input)?; + + // Enum declarations go into their own subfolder. + if !rendered.enums.is_empty() { + std::fs::create_dir_all(format!("{OUT_DIR}/Enums"))?; + } + + // Protobuf declarations go into their own subfolder. + if !rendered.protos.is_empty() { + std::fs::create_dir_all(format!("{OUT_DIR}/Protobuf"))?; + } + + for (name, rendered) in rendered.structs { + let file_path = format!("{OUT_DIR}/{name}.swift"); + std::fs::write(&file_path, rendered.as_bytes())?; + } + + for (name, rendered) in rendered.enums { + let file_path = format!("{OUT_DIR}/Enums/{name}.swift"); + std::fs::write(&file_path, rendered.as_bytes())?; + } + + // Enum extensions. + for (name, rendered) in rendered.extensions { + let file_path = format!("{OUT_DIR}/{name}+Extension.swift"); + std::fs::write(&file_path, rendered.as_bytes())?; + } + + // Protobuf messages. + for (name, rendered) in rendered.protos { + let file_path = format!("{OUT_DIR}/Protobuf/{name}+Proto.swift"); + std::fs::write(&file_path, rendered.as_bytes())?; + } + } + + println!("Created bindings in directory 'bindings/'!"); + Ok(()) +} diff --git a/codegen-v2/src/manifest.rs b/codegen-v2/src/manifest.rs new file mode 100644 index 00000000000..f70c24b2e5a --- /dev/null +++ b/codegen-v2/src/manifest.rs @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use super::Result; +use std::fs; +use std::path::Path; + +pub fn parse_dir>(path: P) -> Result> { + // Get a list of all files in the directory + let entries = fs::read_dir(path)?; + + let mut file_infos = vec![]; + for entry in entries { + let entry = entry?; + let file_path = entry.path(); + + // Skip directories + if file_path.is_dir() { + println!("Found unexpected directory: {}", file_path.display()); + continue; + } + + // Read the file into a string + let file_contents = fs::read_to_string(&file_path)?; + + // Deserialize the JSON into a struct + let info = parse_str(&file_contents)?; + file_infos.push(info); + } + + Ok(file_infos) +} + +pub fn parse_str(str: &str) -> Result { + serde_yaml::from_str(str).map_err(|err| err.into()) +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct TypeInfo { + #[serde(flatten)] + pub variant: TypeVariant, + pub is_constant: bool, + pub is_nullable: bool, + pub is_pointer: bool, +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(tag = "variant", content = "value", rename_all = "snake_case")] +pub enum TypeVariant { + Void, + Bool, + Char, + ShortInt, + Int, + UnsignedInt, + LongInt, + Float, + Double, + SizeT, + Int8T, + Int16T, + Int32T, + Int64T, + UInt8T, + UInt16T, + UInt32T, + UInt64T, + Struct(String), + Enum(String), + Data, + String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FileInfo { + pub name: String, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub structs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub inits: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub deinits: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub enums: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub functions: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub properties: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub protos: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ImportInfo { + // Expressed as directories plus the final file. + // E.g. `to/some/file.h` ~= ["to", "some", "file.h"] + pub path: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ProtoInfo(pub String); + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EnumInfo { + pub name: String, + pub is_public: bool, + pub value_type: TypeVariant, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub variants: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EnumVariantInfo { + pub name: String, + pub value: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub as_string: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StructInfo { + pub name: String, + pub is_public: bool, + pub is_class: bool, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub fields: Vec<(String, TypeInfo)>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InitInfo { + pub name: String, + pub is_public: bool, + pub is_nullable: bool, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub params: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub comments: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DeinitInfo { + pub name: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FunctionInfo { + pub name: String, + pub is_public: bool, + pub is_static: bool, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub params: Vec, + pub return_type: TypeInfo, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub comments: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PropertyInfo { + pub name: String, + pub is_public: bool, + pub return_type: TypeInfo, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub comments: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ParamInfo { + pub name: String, + #[serde(rename = "type")] + pub ty: TypeInfo, +} diff --git a/codegen-v2/src/registry.rs b/codegen-v2/src/registry.rs new file mode 100644 index 00000000000..c83ef174dd0 --- /dev/null +++ b/codegen-v2/src/registry.rs @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::coin_id::CoinId; +use crate::{Error, Result}; +use convert_case::{Case, Casing}; +use std::path::PathBuf; +use std::{env, fs}; + +pub fn registry_json_path() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("registry.json") +} + +#[derive(Clone, Deserialize)] +pub struct CoinExplorer { + pub url: String, + #[serde(rename = "txPath")] + pub tx_path: String, + #[serde(rename = "accountPath")] + pub account_path: String, + #[serde(rename = "sampleTx")] + #[serde(default)] + pub sample_tx: String, + #[serde(rename = "sampleAccount")] + #[serde(default)] + pub sample_account: String, +} + +#[derive(Clone, Deserialize)] +pub struct CoinItem { + pub id: CoinId, + pub name: String, + #[serde(rename = "displayName")] + #[serde(default)] + pub display_name: String, + #[serde(rename = "coinId")] + pub coin_id_number: u32, + pub symbol: String, + pub decimals: u8, + pub blockchain: String, + #[serde(rename = "p2pkhPrefix")] + #[serde(default)] + pub p2pkh_prefix: u8, + #[serde(rename = "p2shPrefix")] + #[serde(default)] + pub p2sh_prefix: u8, + #[serde(rename = "staticPrefix")] + #[serde(default)] + pub static_prefix: u8, + #[serde(default)] + pub hrp: String, + pub explorer: CoinExplorer, +} + +impl CoinItem { + /// Transforms a coin name to a Rust name. + /// https://github.com/trustwallet/wallet-core/blob/3769f31b7d0c75126b2f426bb065364429aaa379/codegen/lib/coin_skeleton_gen.rb#L15-L22 + pub fn coin_type(&self) -> String { + self.name.replace([' ', '.', '-'], "") + } + + /// Returns the blockchain type in `UpperCamel` case. + pub fn blockchain_type(&self) -> String { + self.blockchain.to_case(Case::UpperCamel) + } + + /// Returns the blockchain type in `UPPER_SNAKE` case. + pub fn blockchain_entry_upper_snake(&self) -> String { + self.blockchain.to_case(Case::UpperSnake) + } + + /// Returns a Rust blockchain entry of the blockchain. + pub fn blockchain_entry(&self) -> String { + format!("{}Entry", self.blockchain_type()) + } +} + +pub fn read_coin_from_registry(coin: &CoinId) -> Result { + let registry_path = registry_json_path(); + + let registry_bytes = fs::read(registry_path)?; + let coins: Vec = + serde_json::from_slice(®istry_bytes).map_err(|e| Error::RegistryError(e.to_string()))?; + + coins + .into_iter() + .find(|item| item.id == *coin) + .ok_or(Error::InvalidCommand) +} diff --git a/codegen-v2/src/tests/mod.rs b/codegen-v2/src/tests/mod.rs new file mode 100644 index 00000000000..6a889017da4 --- /dev/null +++ b/codegen-v2/src/tests/mod.rs @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::codegen::swift::{render_to_strings, RenderIntput}; +use crate::manifest::parse_str; + +/// Convenience function. +fn create_intput(yaml: &str) -> RenderIntput { + let file_info = parse_str(yaml).unwrap(); + + RenderIntput { + file_info, + struct_template: include_str!("../codegen/swift/templates/struct.hbs"), + enum_template: include_str!("../codegen/swift/templates/enum.hbs"), + extension_template: include_str!("../codegen/swift/templates/extension.hbs"), + proto_template: include_str!("../codegen/swift/templates/proto.hbs"), + partial_init_template: include_str!("../codegen/swift/templates/partial_init.hbs"), + partial_func_tempalte: include_str!("../codegen/swift/templates/partial_func.hbs"), + partial_prop_tempalte: include_str!("../codegen/swift/templates/partial_prop.hbs"), + } +} + +// Convenience function: runs the codegen on the given `input` and compares it +// with the `expected` value. Expects a single, rendered file as output. +fn render_and_compare_struct(input: &str, expected: &str) { + let input = create_intput(input); + let rendered = render_to_strings(input).unwrap(); + + assert_eq!(rendered.structs.len(), 1); + assert!(rendered.enums.is_empty()); + assert!(rendered.extensions.is_empty()); + assert!(rendered.protos.is_empty()); + + let (_name, output) = &rendered.structs[0]; + println!("{output}"); + assert_eq!(output, expected); +} + +fn render_and_compare_enum(input: &str, expected: &str) { + let input = create_intput(input); + let rendered = render_to_strings(input).unwrap(); + + assert!(rendered.structs.is_empty()); + assert_eq!(rendered.enums.len(), 1); + assert!(rendered.extensions.is_empty()); + assert!(rendered.protos.is_empty()); + + let (_name, output) = &rendered.enums[0]; + assert_eq!(output, expected); +} + +#[test] +fn single_struct() { + const INPUT: &str = include_str!("samples/struct.input.yaml"); + const EXPECTED: &str = include_str!("samples/struct.output.swift"); + + render_and_compare_struct(INPUT, EXPECTED); +} + +#[test] +fn single_class() { + const INPUT: &str = include_str!("samples/class.input.yaml"); + const EXPECTED: &str = include_str!("samples/class.output.swift"); + + render_and_compare_struct(INPUT, EXPECTED); +} + +#[test] +fn private() { + const INPUT: &str = include_str!("samples/private_class.input.yaml"); + const EXPECTED: &str = include_str!("samples/private_class.output.swift"); + + render_and_compare_struct(INPUT, EXPECTED); +} + +#[test] +fn optional() { + const INPUT: &str = include_str!("samples/optional.input.yaml"); + const EXPECTED: &str = include_str!("samples/optional.output.swift"); + + render_and_compare_struct(INPUT, EXPECTED); +} + +#[test] +fn enum_with_description() { + const INPUT: &str = include_str!("samples/enum.input.yaml"); + const EXPECTED: &str = include_str!("samples/enum.output.swift"); + + render_and_compare_enum(INPUT, EXPECTED); +} + +#[test] +fn privat_enum_with_description() { + const INPUT: &str = include_str!("samples/enum_private.input.yaml"); + const EXPECTED: &str = include_str!("samples/enum_private.output.swift"); + + render_and_compare_enum(INPUT, EXPECTED); +} + +#[test] +fn enum_with_extension() { + const INPUT: &str = include_str!("samples/enum_extension.input.yaml"); + const EXPECTED_ENUM: &str = include_str!("samples/enum.output.swift"); + const EXPECTED_EXTENSION: &str = include_str!("samples/enum_extension.output.swift"); + + let input = create_intput(INPUT); + let rendered = render_to_strings(input).unwrap(); + + assert!(rendered.structs.is_empty()); + assert_eq!(rendered.enums.len(), 1); + assert_eq!(rendered.extensions.len(), 1); + assert!(rendered.protos.is_empty()); + + // Check generated enum. + let (_name, output) = &rendered.enums[0]; + assert_eq!(output, EXPECTED_ENUM); + + // Check generated extension. + let (_name, output) = &rendered.extensions[0]; + assert_eq!(output, EXPECTED_EXTENSION); +} + +#[test] +fn non_associated() { + const INPUT: &str = include_str!("samples/non-associated.input.yaml"); + const EXPECTED: &str = include_str!("samples/non-associated.output.swift"); + + render_and_compare_struct(INPUT, EXPECTED); +} diff --git a/codegen-v2/src/tests/samples/class.input.yaml b/codegen-v2/src/tests/samples/class.input.yaml new file mode 100644 index 00000000000..2593f18a6dc --- /dev/null +++ b/codegen-v2/src/tests/samples/class.input.yaml @@ -0,0 +1,42 @@ +name: Class +structs: +- name: MainStruct + is_public: true + is_class: true +inits: +- name: MainStructCreate + is_public: true + is_nullable: false + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +deinits: +- name: MainStructDelete +functions: +- name: MainStructFirstFunction + is_public: true + is_static: true + params: + - name: first_param + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +properties: +- name: MainStructFirstProperty + is_public: true + return_type: + variant: bool + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/src/tests/samples/class.output.swift b/codegen-v2/src/tests/samples/class.output.swift new file mode 100644 index 00000000000..ed7c0b000ac --- /dev/null +++ b/codegen-v2/src/tests/samples/class.output.swift @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here WILL BE LOST. +// + +import Foundation + +public final class MainStruct { + let rawValue: OpaquePointer + + init(rawValue: OpaquePointer) { + self.rawValue = rawValue + } + + public init(string: String) { + let string = TWStringCreateWithNSString(string) + defer { + TWStringDelete(string) + } + + let result = MainStructCreate(string) + + self.rawValue = result + } + + deinit { + MainStructDelete(self.rawValue) + } + + public static func firstFunction(first_param: Int32) -> Bool { + let result = MainStructFirstFunction(first_param) + return result + } + + public var firstProperty: Bool { + let obj = self.rawValue + let result = MainStructFirstProperty(obj) + return result + } +} diff --git a/codegen-v2/src/tests/samples/enum.input.yaml b/codegen-v2/src/tests/samples/enum.input.yaml new file mode 100644 index 00000000000..32beb097c95 --- /dev/null +++ b/codegen-v2/src/tests/samples/enum.input.yaml @@ -0,0 +1,15 @@ +name: Enum +enums: +- name: MainEnum + is_public: true + value_type: + variant: u_int32_t + variants: + - name: one + value: 0 + as_string: one_string + - name: two + value: 1 + - name: three + value: 2 + as_string: three_string diff --git a/codegen-v2/src/tests/samples/enum.output.swift b/codegen-v2/src/tests/samples/enum.output.swift new file mode 100644 index 00000000000..0e5b8346d6f --- /dev/null +++ b/codegen-v2/src/tests/samples/enum.output.swift @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here WILL BE LOST. +// + +public enum MainEnum: UInt32, CaseIterable, CustomStringConvertible { + case `one` = 0 + case `two` = 1 + case `three` = 2 + + public var description: String { + switch self { + case .one: return "one_string" + case .two: return "" + case .three: return "three_string" + } + } +} diff --git a/codegen-v2/src/tests/samples/enum_extension.input.yaml b/codegen-v2/src/tests/samples/enum_extension.input.yaml new file mode 100644 index 00000000000..86551b52a83 --- /dev/null +++ b/codegen-v2/src/tests/samples/enum_extension.input.yaml @@ -0,0 +1,47 @@ +name: EnumExtension +enums: +- name: MainEnum + is_public: true + value_type: + variant: u_int32_t + variants: + - name: one + value: 0 + as_string: one_string + - name: two + value: 1 + - name: three + value: 2 + as_string: three_string +functions: +- name: MainEnumFirstFunction + is_public: true + is_static: true + params: + - name: first_param + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: MainEnumSecondFunction + is_public: true + is_static: true + params: + - name: first_param + type: + variant: struct + value: SomeStruct + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false diff --git a/codegen-v2/src/tests/samples/enum_extension.output.swift b/codegen-v2/src/tests/samples/enum_extension.output.swift new file mode 100644 index 00000000000..706368ee760 --- /dev/null +++ b/codegen-v2/src/tests/samples/enum_extension.output.swift @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here WILL BE LOST. +// + +extension MainEnum { + public static func firstFunction(first_param: Int32) -> Bool { + let result = MainEnumFirstFunction(first_param) + return result + } + + public static func secondFunction(first_param: SomeStruct) -> Bool { + let first_param = first_param.rawValue + let result = MainEnumSecondFunction(first_param) + return result + } +} diff --git a/codegen-v2/src/tests/samples/enum_private.input.yaml b/codegen-v2/src/tests/samples/enum_private.input.yaml new file mode 100644 index 00000000000..db0ca4d0850 --- /dev/null +++ b/codegen-v2/src/tests/samples/enum_private.input.yaml @@ -0,0 +1,16 @@ +name: EnumPrivate +enums: +- name: MainEnum + is_public: false + value_type: + variant: u_int32_t + # Note that the `description` method is always public. + variants: + - name: one + value: 0 + as_string: one_string + - name: two + value: 1 + - name: three + value: 2 + as_string: three_string diff --git a/codegen-v2/src/tests/samples/enum_private.output.swift b/codegen-v2/src/tests/samples/enum_private.output.swift new file mode 100644 index 00000000000..b2cb22aea3a --- /dev/null +++ b/codegen-v2/src/tests/samples/enum_private.output.swift @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here WILL BE LOST. +// + +enum MainEnum: UInt32, CaseIterable, CustomStringConvertible { + case `one` = 0 + case `two` = 1 + case `three` = 2 + + public var description: String { + switch self { + case .one: return "one_string" + case .two: return "" + case .three: return "three_string" + } + } +} diff --git a/codegen-v2/src/tests/samples/non-associated.input.yaml b/codegen-v2/src/tests/samples/non-associated.input.yaml new file mode 100644 index 00000000000..21a42bf3a3d --- /dev/null +++ b/codegen-v2/src/tests/samples/non-associated.input.yaml @@ -0,0 +1,79 @@ +name: NonAssociated +structs: +- name: MainStruct + is_public: true + is_class: true +inits: +- name: MainStructCreate + is_public: true + is_nullable: false + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +# Non-associated. +- name: OtherStructCreate + is_public: true + is_nullable: false + params: + - name: number + type: + variant: int + is_constant: true + is_nullable: false + is_pointer: true +deinits: +- name: MainStructDelete +# Non-associated. +- name: OtherStructDelete +functions: +# Non-associated. +- name: OtherStructFirstFunction + is_public: true + is_static: true + params: + - name: first_param + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +- name: MainStructSecondFunction + is_public: true + is_static: true + params: + - name: first_param + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +properties: +- name: MainStructFirstProperty + is_public: true + return_type: + variant: bool + is_constant: true + is_nullable: false + is_pointer: true +# Non-associated. +- name: OtherStructSecondProperty + is_public: true + return_type: + variant: bool + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/src/tests/samples/non-associated.output.swift b/codegen-v2/src/tests/samples/non-associated.output.swift new file mode 100644 index 00000000000..478b8b1d6b5 --- /dev/null +++ b/codegen-v2/src/tests/samples/non-associated.output.swift @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here WILL BE LOST. +// + +import Foundation + +public final class MainStruct { + let rawValue: OpaquePointer + + init(rawValue: OpaquePointer) { + self.rawValue = rawValue + } + + public init(string: String) { + let string = TWStringCreateWithNSString(string) + defer { + TWStringDelete(string) + } + + let result = MainStructCreate(string) + + self.rawValue = result + } + + deinit { + MainStructDelete(self.rawValue) + } + + public static func secondFunction(first_param: Int32) -> Bool { + let result = MainStructSecondFunction(first_param) + return result + } + + public var firstProperty: Bool { + let obj = self.rawValue + let result = MainStructFirstProperty(obj) + return result + } +} diff --git a/codegen-v2/src/tests/samples/optional.input.yaml b/codegen-v2/src/tests/samples/optional.input.yaml new file mode 100644 index 00000000000..d235f0d6262 --- /dev/null +++ b/codegen-v2/src/tests/samples/optional.input.yaml @@ -0,0 +1,112 @@ +name: Optional +structs: +- name: MainStruct + is_public: true + is_class: true +inits: +- name: MainStructCreate + is_public: true + is_nullable: true + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: true + is_pointer: true +deinits: +- name: MainStructDelete +functions: +- name: MainStructWithOptionalInt + is_public: true + is_static: true + params: + - name: first_param + type: + variant: int + is_constant: false + is_nullable: true + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: true + is_pointer: false +- name: MainStructWithOptionalStruct + is_public: true + is_static: true + params: + - name: first_param + type: + variant: struct + value: SomeStruct + is_constant: false + is_nullable: true + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: true + is_pointer: false +- name: MainStructWithOptionalString + is_public: true + is_static: true + params: + - name: first_param + type: + variant: string + is_constant: false + is_nullable: true + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: true + is_pointer: false +- name: MainStructWithOptionalEnum + is_public: true + is_static: true + params: + - name: first_param + type: + variant: enum + value: SomeEnum + is_constant: false + is_nullable: true + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: true + is_pointer: false +properties: +- name: MainStructWithOptionalInt + is_public: true + return_type: + variant: int + is_constant: true + is_nullable: true + is_pointer: true +- name: MainStructWithOptionalString + is_public: true + return_type: + variant: string + is_constant: true + is_nullable: true + is_pointer: true +- name: MainStructWithOptionalStruct + is_public: true + return_type: + variant: struct + value: SomeStruct + is_constant: true + is_nullable: true + is_pointer: true +- name: MainStructWithOptionalEnum + is_public: true + return_type: + variant: enum + value: SomeEnum + is_constant: true + is_nullable: true + is_pointer: true diff --git a/codegen-v2/src/tests/samples/optional.output.swift b/codegen-v2/src/tests/samples/optional.output.swift new file mode 100644 index 00000000000..a33b4afea58 --- /dev/null +++ b/codegen-v2/src/tests/samples/optional.output.swift @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here WILL BE LOST. +// + +import Foundation + +public final class MainStruct { + let rawValue: OpaquePointer + + init(rawValue: OpaquePointer) { + self.rawValue = rawValue + } + + public init?(string: String?) { + guard let result = MainStructCreate(string) else { + return nil + } + + self.rawValue = result + } + + deinit { + MainStructDelete(self.rawValue) + } + + public static func withOptionalInt(first_param: Int32?) -> Bool? { + guard let result = MainStructWithOptionalInt(first_param) else { + return nil + } + return result + } + + public static func withOptionalStruct(first_param: SomeStruct?) -> Bool? { + let first_param = first_param?.rawValue + guard let result = MainStructWithOptionalStruct(first_param) else { + return nil + } + return result + } + + public static func withOptionalString(first_param: String?) -> Bool? { + let ptr: UnsafeRawPointer? + if let first_param = first_param { + ptr = TWStringCreateWithNSString(first_param) + } else { + ptr = nil + } + defer { + if let first_param = ptr { + TWStringDelete(first_param) + } + } + let first_param = ptr + + guard let result = MainStructWithOptionalString(first_param) else { + return nil + } + return result + } + + public static func withOptionalEnum(first_param: SomeEnum?) -> Bool? { + let first_param = SomeEnum(rawValue: first_param.rawValue) + guard let result = MainStructWithOptionalEnum(first_param) else { + return nil + } + return result + } + + public var withOptionalInt: Int32? { + let obj = self.rawValue + guard let result = MainStructWithOptionalInt(obj) else { + return nil + } + return result + } + + public var withOptionalString: String? { + let obj = self.rawValue + guard let result = MainStructWithOptionalString(obj) else { + return nil + } + return TWStringNSString(result) + } + + public var withOptionalStruct: SomeStruct? { + let obj = self.rawValue + guard let result = MainStructWithOptionalStruct(obj) else { + return nil + } + return SomeStruct(rawValue: result) + } + + public var withOptionalEnum: SomeEnum? { + let obj = self.rawValue + guard let result = MainStructWithOptionalEnum(obj) else { + return nil + } + return SomeEnum(rawValue: result.rawValue)! + } +} diff --git a/codegen-v2/src/tests/samples/private_class.input.yaml b/codegen-v2/src/tests/samples/private_class.input.yaml new file mode 100644 index 00000000000..65365d45781 --- /dev/null +++ b/codegen-v2/src/tests/samples/private_class.input.yaml @@ -0,0 +1,42 @@ +name: PrivateClass +structs: +- name: MainStruct + is_public: false + is_class: true +inits: +- name: MainStructCreate + is_public: false + is_nullable: false + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +deinits: +- name: MainStructDelete +functions: +- name: MainStructFirstFunction + is_public: false + is_static: true + params: + - name: first_param + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +properties: +- name: MainStructFirstProperty + is_public: false + return_type: + variant: bool + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/src/tests/samples/private_class.output.swift b/codegen-v2/src/tests/samples/private_class.output.swift new file mode 100644 index 00000000000..2362367af00 --- /dev/null +++ b/codegen-v2/src/tests/samples/private_class.output.swift @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here WILL BE LOST. +// + +import Foundation + +final class MainStruct { + let rawValue: OpaquePointer + + init(rawValue: OpaquePointer) { + self.rawValue = rawValue + } + + init(string: String) { + let string = TWStringCreateWithNSString(string) + defer { + TWStringDelete(string) + } + + let result = MainStructCreate(string) + + self.rawValue = result + } + + deinit { + MainStructDelete(self.rawValue) + } + + static func firstFunction(first_param: Int32) -> Bool { + let result = MainStructFirstFunction(first_param) + return result + } + + var firstProperty: Bool { + let obj = self.rawValue + let result = MainStructFirstProperty(obj) + return result + } +} diff --git a/codegen-v2/src/tests/samples/struct.input.yaml b/codegen-v2/src/tests/samples/struct.input.yaml new file mode 100644 index 00000000000..1e21a100e81 --- /dev/null +++ b/codegen-v2/src/tests/samples/struct.input.yaml @@ -0,0 +1,42 @@ +name: Struct +structs: +- name: MainStruct + is_public: true + is_class: false +inits: +- name: MainStructCreate + is_public: true + is_nullable: false + params: + - name: string + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true +deinits: +- name: MainStructDelete +functions: +- name: MainStructFirstFunction + is_public: true + is_static: true + params: + - name: first_param + type: + variant: int + is_constant: false + is_nullable: false + is_pointer: true + return_type: + variant: bool + is_constant: false + is_nullable: false + is_pointer: false +properties: +- name: MainStructFirstProperty + is_public: true + return_type: + variant: bool + is_constant: true + is_nullable: false + is_pointer: true diff --git a/codegen-v2/src/tests/samples/struct.output.swift b/codegen-v2/src/tests/samples/struct.output.swift new file mode 100644 index 00000000000..34f310c1eb8 --- /dev/null +++ b/codegen-v2/src/tests/samples/struct.output.swift @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here WILL BE LOST. +// + +import Foundation + +public struct MainStruct { + init() {} + + public init(string: String) { + let string = TWStringCreateWithNSString(string) + defer { + TWStringDelete(string) + } + + let result = MainStructCreate(string) + + self.rawValue = result + } + + deinit { + MainStructDelete(self.rawValue) + } + + public static func firstFunction(first_param: Int32) -> Bool { + let result = MainStructFirstFunction(first_param) + return result + } + + public var firstProperty: Bool { + let obj = self.rawValue + let result = MainStructFirstProperty(obj) + return result + } +} diff --git a/codegen-v2/src/utils.rs b/codegen-v2/src/utils.rs new file mode 100644 index 00000000000..a61222fc3fd --- /dev/null +++ b/codegen-v2/src/utils.rs @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::{Error, Result}; +use std::fs; +use std::path::{Path, PathBuf}; + +pub fn read_lines>(path: P) -> Result> { + let lines = fs::read_to_string(path)? + .split('\n') + .map(|line| line.to_string()) + .collect(); + Ok(lines) +} + +pub fn write_lines>(path: P, lines: Vec) -> Result<()> { + let content = lines.join("\n"); + fs::write(path, content).map_err(Error::from) +} + +pub struct FileContent { + path: PathBuf, + lines: Vec, +} + +impl FileContent { + pub fn read(path: PathBuf) -> Result { + read_lines(&path).map(|lines| FileContent { path, lines }) + } + + pub fn find_region_with_prefix(&mut self, prefix: &str) -> Result> { + // Find the first line that starts with the `prefix`. + let region_starts_at = self + .lines + .iter() + .position(|line| line.starts_with(prefix)) + .ok_or_else(|| Error::io_error_other(format!("Cannot find the `{prefix}` region")))?; + + // Find the last line that starts with the `prefix`. + let region_ends_at = self + .lines + .iter() + .rposition(|line| line.starts_with(prefix)) + .ok_or_else(|| Error::io_error_other(format!("Cannot find the `{prefix}` region")))?; + + Ok(FileRegion { + lines: &mut self.lines, + region_starts_at, + region_ends_at, + }) + } + + pub fn find_region_with_comments( + &mut self, + start_comment: &str, + end_comment: &str, + ) -> Result> { + // Find the position of the `start_comment`. + let start_comment_at = self + .lines + .iter() + .position(|line| line.contains(start_comment)) + .ok_or_else(|| { + Error::io_error_other(format!("Cannot find the `{start_comment}` line")) + })?; + let end_comment_at = self + .lines + .iter() + .skip(start_comment_at) + .position(|line| line.contains(end_comment)) + .ok_or_else(|| { + Error::io_error_other(format!("Cannot find the `{end_comment}` line")) + })? + + start_comment_at; + + let region_starts_at = start_comment_at + 1; + let region_ends_at = end_comment_at - 1; + + if region_starts_at > region_ends_at { + return Err(Error::io_error_other(format!( + "There must be the content between {start_comment} and {end_comment}" + ))); + } + + Ok(FileRegion { + lines: &mut self.lines, + region_starts_at, + region_ends_at, + }) + } + + pub fn rfind_line(&mut self, f: F) -> Result> + where + F: Fn(&str) -> bool, + { + let line_idx = self.lines.iter().rposition(|line| f(line)).ok_or_else(|| { + Error::io_error_other(format!( + "{:?} file does not contain a required pattern", + self.path + )) + })?; + Ok(LinePointer { + lines: &mut self.lines, + line_idx, + }) + } + + pub fn write(self) -> Result<()> { + write_lines(self.path, self.lines) + } +} + +pub struct FileRegion<'a> { + lines: &'a mut Vec, + region_starts_at: usize, + region_ends_at: usize, +} + +impl<'a> FileRegion<'a> { + pub fn push_line(&mut self, line: String) { + self.lines.insert(self.region_ends_at + 1, line); + self.region_ends_at += 1; + } + + pub fn sort(&mut self) { + self.lines[self.region_starts_at..=self.region_ends_at].sort() + } + + pub fn count_lines(&self) -> usize { + self.region_ends_at - self.region_starts_at + } +} + +pub struct LinePointer<'a> { + lines: &'a mut Vec, + line_idx: usize, +} + +impl<'a> LinePointer<'a> { + /// Please note that the line pointer will be shifted to the same line on which it pointed before. + pub fn push_line_before(&mut self, line: String) { + self.lines.insert(self.line_idx, line); + self.line_idx += 1; + } + + pub fn push_paragraph_before(&mut self, paragraph: String) { + for line in paragraph.split("\n") { + self.push_line_before(line.to_string()); + } + } + + /// Please note that the line pointer will not be shifted to the pushed element. + pub fn push_line_after(&mut self, line: String) { + self.lines.insert(self.line_idx + 1, line); + } +} diff --git a/codegen/bin/codegen b/codegen/bin/codegen index 8fc86c7e86e..5cd99179bb8 100755 --- a/codegen/bin/codegen +++ b/codegen/bin/codegen @@ -22,6 +22,7 @@ options.jni_h = true options.jni_c = true options.wasm_cpp = true options.ts_declaration = true +options.kotlin = true OptionParser.new do |opts| opts.banner = 'Usage: codegen [options]' @@ -50,6 +51,9 @@ OptionParser.new do |opts| opts.on('-t', '--typescript-declaration', "Generate typescript declaration file Default: #{options.ts_declaration}") do |v| options.ts_declaration = v end + opts.on('-k', '--kotlin', "Generate Kotlin code. Default: #{options.kotlin}") do |v| + options.kotlin = v + end opts.on_tail('-h', '--help', 'Show this message') do puts opts exit @@ -88,3 +92,12 @@ end if options.ts_declaration generator.render_ts_declaration end +if options.kotlin + generator.render_kotlin_common + generator.render_kotlin_android + generator.render_kotlin_ios + generator.render_kotlin_js + generator.render_kotlin_js_accessors + generator.render_kotlin_jni_h + generator.render_kotlin_jni_c +end diff --git a/codegen/bin/cointests b/codegen/bin/cointests index b30fc1e91af..a40f56b0250 100755 --- a/codegen/bin/cointests +++ b/codegen/bin/cointests @@ -2,25 +2,25 @@ # Sript for creating/updating CoinType unit tests, based on the registry.json file # It is intended as a one-time or occasional generation, not every time! (that way the tests would have zero added value) -# Usage: codegen/bin/cointests +# Usage: codegen/bin/cointests [--coin-id coinid] [--no-skip-existing] # Files are generated to: tests//TWCoinTypeTests.cpp require 'erb' require 'fileutils' require 'json' +require 'optparse' + +options = { :no_skip_existing => false } +OptionParser.new do |opt| + opt.banner = "Usage: codegen/bin/cointests [options]" + opt.on('--coin-id ') { |o| options[:coin_id] = o.downcase } + opt.on('--no-skip-existing') { options[:no_skip_existing] = true } +end.parse! CurrentDir = File.dirname(__FILE__) $LOAD_PATH.unshift(File.join(CurrentDir, '..', 'lib')) require 'coin_test_gen' -# Transforms a coin name to a C++ name -def self.format_name(n) - formatted = n - #formatted = formatted.sub(/^([a-z]+)/, &:upcase) - formatted = formatted.sub(/\s/, '') - formatted -end - # Transforms number to hex def self.to_hex(i) hex = i.to_i().to_s(16) @@ -68,6 +68,19 @@ erbs = [ coin_test_gen = CoinTestGen.new() templateFile = 'TWCoinTypeTests.cpp.erb' +foundCoinId = false coins.each do |coin| - coin_test_gen.generate_coin_test_file(coin, templateFile) + if options[:coin_id].nil? or coin['id'] == options[:coin_id] + coin_test_gen.generate_coin_test_file(coin, templateFile, options[:no_skip_existing]) + foundCoinId = true + end end + +if not options[:coin_id].nil? and not foundCoinId + puts "Not found specified coin-id " + options[:coin_id] + supportedIds = [] + coins.each do |coin| + supportedIds << coin['id'] + end + puts "Supported coin-ids: " + supportedIds.join(", ") +end diff --git a/codegen/bin/newcoin b/codegen/bin/newcoin index 92ffef39977..ee14b241f7a 100755 --- a/codegen/bin/newcoin +++ b/codegen/bin/newcoin @@ -1,96 +1,14 @@ #!/usr/bin/env ruby -# Sript for creating new skeleton files for a new coin -# 1. Add relevsant entry to registry.json (in order to minimize merge conflict, don't add at the very end) +# Sript for creating new skeleton files for a new coin. See also `newevmchain`. +# 1. Add relevant entry to registry.json (in order to minimize merge conflict, don't add at the very end) # 2. Invoke this script with the id of the coin, e.g.: codegen/bin/newcoin ethereum -require 'erb' require 'fileutils' -require 'json' CurrentDir = File.dirname(__FILE__) $LOAD_PATH.unshift(File.join(CurrentDir, '..', 'lib')) -require 'entity_decl' -require 'code_generator' -require 'coin_test_gen' - -$flag_comment = " // TODO remove if the blockchain already exists, or just remove this comment if not" - -# Transforms a coin name to a C++ name -def self.format_name(coin) - formatted = coin['name'] - formatted = formatted.gsub(/-/, ' ') - formatted = formatted.gsub(/\./, ' ') - formatted = formatted.gsub(/\s/, '') - formatted -end - -def self.format_name_lowercase(coin) - format_name(coin).downcase -end - -def self.format_name_uppercase(coin) - format_name(coin).upcase -end - -def self.generate_file(templateFile, folder, fileName, coin) - @coin = coin - name = format_name(coin) - path = File.expand_path(templateFile, File.join(File.dirname(__FILE__), '..', 'lib', 'templates')) - template = ERB.new(File.read(path), nil, '-') - result = template.result(binding) - - FileUtils.mkdir_p folder - path = File.join(folder, fileName) - File.write(path, result) - puts "Generated file " + path -end - -def self.insert_coin_type(coin) - target_file = "include/TrustWalletCore/TWCoinType.h" - target_line = " TWCoinType#{format_name(coin)} = #{coin['coinId']},\n" - if insert_target_line(target_file, target_line, "};\n") - insert_blockchain_type(coin) - end -end - -def insert_blockchain_type(coin) - target_file = "include/TrustWalletCore/TWBlockchain.h" - line_number = File.readlines(target_file).count + 2 # add offset because of removed blockchain enum type - target_line = " TWBlockchain#{coin['blockchain']} = #{line_number - 17}, " + $flag_comment + "\n" - insert_target_line(target_file, target_line, "};\n") -end - -def insert_coin_entry(coin) - target_file = "src/Coin.cpp" - entryName = coin['blockchain'] - target_line = "#include \"#{entryName}/Entry.h\"" + $flag_comment + "\n" - insert_target_line(target_file, target_line, "// end_of_coin_includes_marker_do_not_modify\n") - target_line = "#{entryName}::Entry #{entryName}DP;" + $flag_comment + "\n" - insert_target_line(target_file, target_line, "// end_of_coin_dipatcher_declarations_marker_do_not_modify\n") - target_line = " case TWCoinType#{entryName}: entry = &#{entryName}DP; break;" + $flag_comment + "\n" - insert_target_line(target_file, target_line, " // end_of_coin_dipatcher_switch_marker_do_not_modify\n") -end - -def self.insert_target_line(target_file, target_line, original_line) - lines = File.readlines(target_file) - index = lines.index(target_line) - if !index.nil? - puts "Line is already present, file: #{target_file} line: #{target_line}" - return true - end - index = lines.index(original_line) - if index.nil? - puts "WARNING: Could not find line! file: #{target_file} line: #{original_line}" - return false - end - lines.insert(index, target_line) - File.open(target_file, "w+") do |f| - f.puts(lines) - end - puts "Updated file: #{target_file} new line: #{target_line}" - return true -end +require 'coin_skeleton_gen' command_line_args = ARGV if command_line_args.length < 1 @@ -99,49 +17,5 @@ if command_line_args.length < 1 end coin_id = command_line_args[0] -puts "New coin template for coin '#{coin_id}' requested" - -json_string = File.read('registry.json') -coins = JSON.parse(json_string).sort_by { |x| x['name'] } - -entity = EntityDecl.new(name: "New" + coin_id, is_struct: false, comment: '') -file = "new"+ coin_id - -generator = CodeGenerator.new(entities: [entity], files: [file], output_folder: ".") - -@coins = coins - -coin_test_gen = CoinTestGen.new() - -# Find coin in list of coins, by Id -coinSelect = coins.select {|c| c['id'] == coin_id} -if coinSelect.length() == 0 - puts "Error: coin #{coin_id} not found!" - return -end -coin = coinSelect.first -name = format_name(coin) - - -insert_coin_type(coin) -insert_coin_entry(coin) - -generate_file("newcoin/Address.h.erb", "src/#{name}", "Address.h", coin) -generate_file("newcoin/Address.cpp.erb", "src/#{name}", "Address.cpp", coin) -generate_file("newcoin/Entry.h.erb", "src/#{name}", "Entry.h", coin) -generate_file("newcoin/Entry.cpp.erb", "src/#{name}", "Entry.cpp", coin) -generate_file("newcoin/Proto.erb", "src/proto", "#{name}.proto", coin) -generate_file("newcoin/Signer.h.erb", "src/#{name}", "Signer.h", coin) -generate_file("newcoin/Signer.cpp.erb", "src/#{name}", "Signer.cpp", coin) - -generate_file("newcoin/AddressTests.cpp.erb", "tests/#{name}", "AddressTests.cpp", coin) -generate_file("newcoin/SignerTests.cpp.erb", "tests/#{name}", "SignerTests.cpp", coin) -generate_file("newcoin/TWAddressTests.cpp.erb", "tests/#{name}", "TWAnyAddressTests.cpp", coin) -generate_file("newcoin/TWSignerTests.cpp.erb", "tests/#{name}", "TWAnySignerTests.cpp", coin) -generate_file("newcoin/AddressTests.kt.erb", "android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/#{format_name_lowercase(coin)}", "Test#{name}Address.kt", coin) -generate_file("newcoin/SignerTests.kt.erb", "android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/#{format_name_lowercase(coin)}", "Test#{name}Signer.kt", coin) -generate_file("newcoin/Tests.swift.erb", "swift/Tests/Blockchains", "#{name}Tests.swift", coin) - -coin_test_gen.generate_coin_test_file(coin, 'TWCoinTypeTests.cpp.erb') -puts "please tools/generate-files to generate Swift/Java/Protobuf files" +generate_skeleton(coin_id, "full") diff --git a/codegen/bin/newcoin-mobile-tests b/codegen/bin/newcoin-mobile-tests new file mode 100755 index 00000000000..d6ea29a84c9 --- /dev/null +++ b/codegen/bin/newcoin-mobile-tests @@ -0,0 +1,22 @@ +#!/usr/bin/env ruby + +# Sript for creating new skeleton files for new coin mobile tests. See also `newcoin` or `newevmchain`. +# It is considered to be used by codegen-v2 tool until Swift and Android tests generating supported. +# 1. Add relevant entry to registry.json (in order to minimize merge conflict, don't add at the very end) +# 2. Invoke this script with the id of the coin, e.g.: codegen/bin/newcoin-mobile-tests ethereum + +require 'fileutils' + +CurrentDir = File.dirname(__FILE__) +$LOAD_PATH.unshift(File.join(CurrentDir, '..', 'lib')) +require 'coin_skeleton_gen' + +command_line_args = ARGV +if command_line_args.length < 1 + puts "Usage: newcoin-mobile-tests " + return +end + +coin_id = command_line_args[0] + +generate_skeleton(coin_id, "mobile-tests") diff --git a/codegen/bin/newevmchain b/codegen/bin/newevmchain new file mode 100755 index 00000000000..e5fed2a97fb --- /dev/null +++ b/codegen/bin/newevmchain @@ -0,0 +1,21 @@ +#!/usr/bin/env ruby + +# Sript for creating new skeleton files for a new EVM chain, subset of newcoin +# 1. Add relevant entry to registry.json (in order to minimize merge conflict, don't add at the very end) +# 2. Invoke this script with the id of the coin, e.g.: codegen/bin/newevmchain ethereumclone + +require 'fileutils' + +CurrentDir = File.dirname(__FILE__) +$LOAD_PATH.unshift(File.join(CurrentDir, '..', 'lib')) +require 'coin_skeleton_gen' + +command_line_args = ARGV +if command_line_args.length < 1 + puts "Usage: newevmchain " + return +end + +coin_id = command_line_args[0] + +generate_skeleton(coin_id, "evm") diff --git a/codegen/lib/code_generator.rb b/codegen/lib/code_generator.rb index 7896c2f8dca..087a75b34c2 100644 --- a/codegen/lib/code_generator.rb +++ b/codegen/lib/code_generator.rb @@ -7,6 +7,8 @@ require 'swift_helper' require 'wasm_cpp_helper' require 'ts_helper' +require 'kotlin_helper' +require 'kotlin_jni_helper' # Code generation class CodeGenerator @@ -47,7 +49,7 @@ def render_swift_enum_template(file:, header:, template:, output_subfolder:, ext end # Renders a template - def render_template(header:, template:, output_subfolder:, extension:) + def render_template(header:, template:, output_subfolder:, extension:, file_prefix: "") FileUtils.mkdir_p File.join(output_folder, output_subfolder) @entities.zip(files) do |entity, file| # Make current entity available to templates @@ -63,7 +65,7 @@ def render_template(header:, template:, output_subfolder:, extension:) code << "\n" unless header.nil? code << string - path = File.expand_path(File.join(output_folder, output_subfolder, "#{file}.#{extension}")) + path = File.expand_path(File.join(output_folder, output_subfolder, "#{file_prefix}#{file}.#{extension}")) File.write(path, code) end end @@ -83,11 +85,11 @@ def render_java end def render_jni_h - render_template(header: 'copyright_header.erb', template: 'jni_h.erb', output_subfolder: 'jni/cpp/generated', extension: 'h') + render_template(header: 'copyright_header.erb', template: 'jni_h.erb', output_subfolder: 'jni/android/generated', extension: 'h') end def render_jni_c - render_template(header: 'copyright_header.erb', template: 'jni_c.erb', output_subfolder: 'jni/cpp/generated', extension: 'c') + render_template(header: 'copyright_header.erb', template: 'jni_c.erb', output_subfolder: 'jni/android/generated', extension: 'c') end def render_wasm_h @@ -103,6 +105,34 @@ def render_ts_declaration TsHelper.combine_declaration_files() end + def render_kotlin_common + render_template(header: nil, template: 'kotlin_common.erb', output_subfolder: 'kotlin/wallet-core-kotlin/src/commonMain/generated/com/trustwallet/core', extension: 'kt') + end + + def render_kotlin_android + render_template(header: nil, template: 'kotlin_android.erb', output_subfolder: 'kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/generated/com/trustwallet/core', extension: 'kt') + end + + def render_kotlin_ios + render_template(header: nil, template: 'kotlin_ios.erb', output_subfolder: 'kotlin/wallet-core-kotlin/src/iosMain/generated/com/trustwallet/core', extension: 'kt') + end + + def render_kotlin_js + render_template(header: nil, template: 'kotlin_js.erb', output_subfolder: 'kotlin/wallet-core-kotlin/src/jsMain/generated/com/trustwallet/core', extension: 'kt') + end + + def render_kotlin_js_accessors + render_template(header: nil, template: 'kotlin_js_accessors.erb', output_subfolder: 'kotlin/wallet-core-kotlin/src/jsMain/generated/com/trustwallet/core', extension: 'kt', file_prefix: "Js") + end + + def render_kotlin_jni_h + render_template(header: 'copyright_header.erb', template: 'kotlin_jni_h.erb', output_subfolder: 'kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/cpp/generated', extension: 'h') + end + + def render_kotlin_jni_c + render_template(header: 'copyright_header.erb', template: 'kotlin_jni_c.erb', output_subfolder: 'kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/cpp/generated', extension: 'c') + end + def render(file, locals = {}) @locals = locals path = File.expand_path(file, File.join(File.dirname(__FILE__), 'templates')) diff --git a/codegen/lib/coin_skeleton_gen.rb b/codegen/lib/coin_skeleton_gen.rb new file mode 100755 index 00000000000..b0bcbe85e80 --- /dev/null +++ b/codegen/lib/coin_skeleton_gen.rb @@ -0,0 +1,159 @@ +# frozen_string_literal: true + +require 'erb' +require 'fileutils' +require 'json' + +require 'entity_decl' +require 'code_generator' +require 'coin_test_gen' + +# Coin template generation + +$flag_comment = " // TODO remove if the blockchain already exists, or just remove this comment if not" + +# Transforms a coin name to a C++ name +def self.format_name(coin) + formatted = coin['name'] + formatted = formatted.gsub(/-/, ' ') + formatted = formatted.gsub(/\./, ' ') + formatted = formatted.gsub(/\s/, '') + formatted +end + +def self.format_name_lowercase(coin) +format_name(coin).downcase +end + +def self.format_name_uppercase(coin) +format_name(coin).upcase +end + +def self.generate_file(templateFile, folder, fileName, coin) + @coin = coin + name = format_name(coin) + path = File.expand_path(templateFile, File.join(File.dirname(__FILE__), '..', 'lib', 'templates')) + template = ERB.new(File.read(path), nil, '-') + result = template.result(binding) + + FileUtils.mkdir_p folder + path = File.join(folder, fileName) + File.write(path, result) + puts "Generated file " + path +end + +def self.insert_coin_type(coin, mode) + target_file = "include/TrustWalletCore/TWCoinType.h" + target_line = " TWCoinType#{format_name(coin)} = #{coin['coinId']},\n" + if insert_target_line(target_file, target_line, "};\n") + if (mode != "evm") + insert_blockchain_type(coin) + end + end +end + +def insert_blockchain_type(coin) + target_file = "include/TrustWalletCore/TWBlockchain.h" + line_number = File.readlines(target_file).count + 2 # add offset because of removed blockchain enum type + target_line = " TWBlockchain#{coin['blockchain']} = #{line_number - 17}, " + $flag_comment + "\n" + insert_target_line(target_file, target_line, "};\n") +end + +def insert_coin_entry(coin) + target_file = "src/Coin.cpp" + entryName = coin['blockchain'] + target_line = "#include \"#{entryName}/Entry.h\"" + $flag_comment + "\n" + insert_target_line(target_file, target_line, "// end_of_coin_includes_marker_do_not_modify\n") + target_line = "#{entryName}::Entry #{entryName}DP;" + $flag_comment + "\n" + insert_target_line(target_file, target_line, "// end_of_coin_dipatcher_declarations_marker_do_not_modify\n") + target_line = " case TWBlockchain#{entryName}: entry = &#{entryName}DP; break;" + $flag_comment + "\n" + insert_target_line(target_file, target_line, " // end_of_coin_dipatcher_switch_marker_do_not_modify\n") +end + +def self.insert_target_line(target_file, target_line, original_line) + lines = File.readlines(target_file) + index = lines.index(target_line) + if !index.nil? + puts "Line is already present, file: #{target_file} line: #{target_line}" + return true + end + index = lines.index(original_line) + if index.nil? + puts "WARNING: Could not find line! file: #{target_file} line: #{original_line}" + return false + end + lines.insert(index, target_line) + File.open(target_file, "w+") do |f| + f.puts(lines) + end + puts "Updated file: #{target_file} new line: #{target_line}" + return true +end + +def generate_blockchain_files(coin) + name = format_name(coin) + + generate_file("newcoin/Address.h.erb", "src/#{name}", "Address.h", coin) + generate_file("newcoin/Address.cpp.erb", "src/#{name}", "Address.cpp", coin) + generate_file("newcoin/Entry.h.erb", "src/#{name}", "Entry.h", coin) + generate_file("newcoin/Entry.cpp.erb", "src/#{name}", "Entry.cpp", coin) + generate_file("newcoin/Proto.erb", "src/proto", "#{name}.proto", coin) + generate_file("newcoin/Signer.h.erb", "src/#{name}", "Signer.h", coin) + generate_file("newcoin/Signer.cpp.erb", "src/#{name}", "Signer.cpp", coin) + + generate_file("newcoin/AddressTests.cpp.erb", "tests/chains/#{name}", "AddressTests.cpp", coin) + generate_file("newcoin/SignerTests.cpp.erb", "tests/chains/#{name}", "SignerTests.cpp", coin) + generate_file("newcoin/TransactionCompilerTests.cpp.erb", "tests/chains/#{name}", "TransactionCompilerTests.cpp", coin) + generate_file("newcoin/TWAddressTests.cpp.erb", "tests/chains/#{name}", "TWAnyAddressTests.cpp", coin) + generate_file("newcoin/TWSignerTests.cpp.erb", "tests/chains/#{name}", "TWAnySignerTests.cpp", coin) +end + +def generate_mobile_tests(coin) + name = format_name(coin) + + generate_file("newcoin/AddressTests.kt.erb", "android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/#{format_name_lowercase(coin)}", "Test#{name}Address.kt", coin) + generate_file("newcoin/SignerTests.kt.erb", "android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/#{format_name_lowercase(coin)}", "Test#{name}Signer.kt", coin) + generate_file("newcoin/Tests.swift.erb", "swift/Tests/Blockchains", "#{name}Tests.swift", coin) +end + +def generate_coin_type_tests(coin) + coin_test_gen = CoinTestGen.new() + coin_test_gen.generate_coin_test_file(coin, 'TWCoinTypeTests.cpp.erb', true) +end + +def generate_skeleton(coin_id, mode) + puts "New coin template for coin '#{coin_id}' #{mode} requested" + + json_string = File.read('registry.json') + coins = JSON.parse(json_string).sort_by { |x| x['name'] } + + entity = EntityDecl.new(name: "New" + coin_id, is_struct: false, comment: '') + file = "new"+ coin_id + + generator = CodeGenerator.new(entities: [entity], files: [file], output_folder: ".") + + @coins = coins + + # Find coin in list of coins, by Id + coinSelect = coins.select {|c| c['id'] == coin_id} + if coinSelect.length() == 0 + puts "Error: coin #{coin_id} not found!" + return + end + coin = coinSelect.first + + if (mode == "full") + insert_coin_type(coin, mode) + insert_coin_entry(coin) + generate_blockchain_files(coin) + generate_mobile_tests(coin) + generate_coin_type_tests(coin) + elsif (mode == "evm") + insert_coin_type(coin, mode) + generate_coin_type_tests(coin) + elsif (mode == "mobile-tests") + generate_mobile_tests(coin) + end + + puts "please tools/generate-files to generate Swift/Java/Protobuf files" +end diff --git a/codegen/lib/coin_test_gen.rb b/codegen/lib/coin_test_gen.rb index 2583e549a1b..0b831bfb277 100755 --- a/codegen/lib/coin_test_gen.rb +++ b/codegen/lib/coin_test_gen.rb @@ -14,8 +14,10 @@ def initialize() # Transforms a coin name to a C++ name def format_name(n) formatted = n - #formatted = formatted.sub(/^([a-z]+)/, &:upcase) - formatted = formatted.sub(/\s/, '') + + # Remove whitespaces + formatted.gsub!(/\s+/, '') + formatted end @@ -56,16 +58,24 @@ def explorer_sample_account(c) end end - def generate_coin_test_file(coin, templateFile) + def generate_coin_test_file(coin, templateFile, overwriteExisting = true) path = File.expand_path(templateFile, File.join(File.dirname(__FILE__), '..', 'lib', 'templates')) template = ERB.new(File.read(path), nil, '-') result = template.result(binding) - folder = 'tests/' + format_name(coin['name']) + folder = 'tests/chains/' + if coin.key?('testFolderName') + folder += format_name(coin['testFolderName']) + else + folder += format_name(coin['name']) + end + file = 'TWCoinTypeTests.cpp' FileUtils.mkdir_p folder path = File.join(folder, file) - File.write(path, result) - puts "Generated file " + path + if not File.exist?(path) or overwriteExisting + File.write(path, result) + puts "Generated file " + path + end end end diff --git a/codegen/lib/function_decl.rb b/codegen/lib/function_decl.rb index fd0a5c8311a..de0a3f7853c 100644 --- a/codegen/lib/function_decl.rb +++ b/codegen/lib/function_decl.rb @@ -3,7 +3,8 @@ # Function or method declaration class FunctionDecl attr_reader :name, :entity - attr_accessor :is_method, :return_type, :parameters, :static, :discardable_result, :comment + attr_accessor :is_method, :return_type, :parameters, :static, :discardable_result + attr_accessor :comment, :comment_with_indent def initialize(name:, entity:, is_method:, return_type: :void, parameters: [], static: false, discardable_result: false, comment: '') @name = name @@ -14,6 +15,7 @@ def initialize(name:, entity:, is_method:, return_type: :void, parameters: [], s @static = static @discardable_result = discardable_result @comment = comment + @comment_with_indent = comment.to_s.gsub('///', ' ///') end end diff --git a/codegen/lib/kotlin_helper.rb b/codegen/lib/kotlin_helper.rb new file mode 100644 index 00000000000..b37537b4fa4 --- /dev/null +++ b/codegen/lib/kotlin_helper.rb @@ -0,0 +1,252 @@ +# frozen_string_literal: true + +module KotlinHelper + # Transforms an interface name to a Java method name + def self.format_name(name) + return 'equals' if name == 'Equal' + + result = name + match = /^([A-Z]+)/.match(name) + result = name.sub(match[1], match[1].downcase) unless match.nil? + + result.sub(/_/, '') + end + + def self.parameters(params) + names = params.map do |param| + name = fix_name(param.name) + "#{name}: #{type(param.type)}" + end + names.join(', ') + end + + def self.js_parameters(params) + names = params.map do |param| + name = fix_name(param.name) + "#{name}: #{js_type(param.type)}" + end + names.join(', ') + end + + def self.calling_parameters_ios(params) + names = params.map do |param| + name = fix_name(param.name) + "#{name}#{convert_calling_type_ios(param.type)}" + end + names.join(', ') + end + + def self.calling_parameters_android(params) + names = params.map do |param| + fix_name(param.name) + end + names.join(', ') + end + + def self.calling_parameters_js(params) + names = params.map do |param| + name = fix_name(param.name) + "#{name}#{convert_calling_type_js(param.type)}" + end + names.join(', ') + end + + def self.fix_name(name) + case name + when '' + "value" + when 'val' + "value" + when 'return' + '`return`' + else + name + end + end + + def self.convert_calling_type_ios(t) + case t.name + when :data + "#{if t.is_nullable then '?' else '' end}.toTwData()" + when :string + "#{if t.is_nullable then '?' else '' end}.toTwString()" + else + if t.is_enum + "#{if t.is_nullable then '?' else '' end}.nativeValue" + elsif t.is_class + "#{if t.is_nullable then '?' else '' end}.pointer" + else + '' + end + end + end + + def self.convert_calling_type_js(t) + case t.name + when :data + "#{if t.is_nullable then '?' else '' end}.asUInt8Array()" + when :uint8 + ".toByte()" + when :uint16 + ".toShort()" + when :uint32 + ".toInt()" + when :uint64 + ".toInt()" + when :int64 + ".toInt()" + when :size + ".toInt()" + else + if t.is_enum + "#{if t.is_nullable then '?' else '' end}.jsValue" + elsif t.is_class + "#{if t.is_nullable then '?' else '' end}.jsValue" + else + '' + end + end + end + + def self.convert_calling_return_type_ios(t, expression = '') + case t.name + when :data + "#{expression}.readTwBytes()#{if t.is_nullable then '' else '!!' end}" + when :string + "#{expression}.fromTwString()#{if t.is_nullable then '' else '!!' end}" + else + if t.is_enum + "#{t.name}.fromValue(#{expression})#{if t.is_nullable then '' else '!!' end}" + elsif t.is_class + if t.is_nullable + "#{expression}?.let { #{t.name}(it) }" + else + "#{t.name}(#{expression}!!)" + end + else + expression + end + end + end + + def self.convert_calling_return_type_js(t, expression = '') + nullable = "#{if t.is_nullable then '?' else '' end}" + case t.name + when :void + expression + when :data + "#{expression}#{nullable}.asByteArray()" + when :int + "#{expression}.toInt()" + when :uint8 + "#{expression}.toByte().toUByte()" + when :uint16 + "#{expression}.toShort().toUShort()" + when :uint32 + "#{expression}.toInt().toUInt()" + when :uint64 + "#{expression}.toLong().toULong()" + when :int8 + "#{expression}.toByte()" + when :int16 + "#{expression}.toShort()" + when :int32 + "#{expression}.toInt()" + when :int64 + "#{expression}.toLong()" + when :size + "#{expression}.toLong().toULong()" + else + if t.is_enum + "#{t.name}.fromValue(#{expression})" + elsif t.is_class + if t.is_nullable + "#{expression}?.let { #{t.name}(it) }" + else + "#{t.name}(#{expression})" + end + else + expression + end + end + end + + def self.arguments(params) + params.map do |param| + param.name || 'value' + end.join(', ') + end + + def self.type(t) + nullable = "#{if t.is_nullable then '?' else '' end}" + case t.name + when :void + "" + when :bool + "Boolean#{nullable}" + when :int + "Int#{nullable}" + when :uint8 + "UByte#{nullable}" + when :uint16 + "UShort#{nullable}" + when :uint32 + "UInt#{nullable}" + when :uint64 + "ULong#{nullable}" + when :int8 + "Byte#{nullable}" + when :int16 + "Short#{nullable}" + when :int32 + "Int#{nullable}" + when :int64 + "Long#{nullable}" + when :size + "ULong#{nullable}" + when :data + "ByteArray#{nullable}" + when :string + "String#{nullable}" + else + "#{t.name}#{nullable}" + end + end + + def self.return_type(t) + case t.name + when :void + "" + else + ": #{type(t)}" + end + end + + def self.js_type(t) + nullable = "#{if t.is_nullable then '?' else '' end}" + case t.name + when :void + "" + when :bool + "Boolean#{nullable}" + when :int, :uint8, :int8, :uint16, :int16, :uint32, :int32, :uint64, :int64, :size + "Number#{nullable}" + when :data + "UInt8Array#{nullable}" + when :string + "String#{nullable}" + else + "Js#{t.name}#{nullable}" + end + end + + def self.js_return_type(t) + case t.name + when :void + "" + else + ": #{js_type(t)}" + end + end + +end diff --git a/codegen/lib/kotlin_jni_helper.rb b/codegen/lib/kotlin_jni_helper.rb new file mode 100644 index 00000000000..b1303a6d69e --- /dev/null +++ b/codegen/lib/kotlin_jni_helper.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +module KotlinJniHelper + # Transforms an interface name to a JNI method name + def self.format_name(name) + return 'equals' if name == 'Equal' + + result = name + match = /^([A-Z]+)/.match(name) + result = name.sub(match[1], match[1].downcase) unless match.nil? + + result.sub(/_/, '') + end + + # Transforms a method/property name to a JNI function name + def self.function_name(entity:, function:, native_prefix: false) + "Java_com_trustwallet_core_#{entity.name}_#{format_name(function.name)}" + end + + # Transforms a proto name name to a JNI class name + def self.proto_to_class(name) + parts = name.split('_') + return nil if parts.count < 3 || parts[0] != 'TW' + + if parts.count == 3 + "wallet/core/jni/proto/Common$#{parts.last}" + else + "wallet/core/jni/proto/#{parts[1]}$#{parts[3]}" + end + end + + def self.parameters(params) + names = params.map do |param| + ", #{type(param.type)} #{param.name || 'value'}" + end + names.join('') + end + + def self.arguments(params) + params.map do |param| + if param.type.is_class + (param.name || 'value') + 'Instance' + elsif param.type.is_struct + '*' + (param.name || 'value') + 'Instance' + elsif param.type.name == :data + (param.name || 'value') + 'Data' + elsif param.type.name == :string + (param.name || 'value') + 'String' + elsif param.type.is_enum + (param.name || 'value') + 'Value' + elsif param.type.is_proto + (param.name || 'value') + 'Data' + else + param.name || 'value' + end + end + end + + def self.type(t) + case t.name + when :void + 'void' + when :bool + 'jboolean' + when :int + 'jint' + when :uint8 + 'jchar' + when :uint16 + 'jshort' + when :uint32 + 'jint' + when :uint64 + 'jlong' + when :int8 + 'jbyte' + when :int16 + 'jshort' + when :int32 + 'jint' + when :int64 + 'jlong' + when :size + 'jsize' + when :data + 'jbyteArray' + when 'Data' + 'jbyteArray' + when :string + 'jstring' + else + if t.is_class || t.is_struct + 'jobject' + elsif t.is_enum + 'jobject' + elsif t.is_proto + 'jobject' + else + raise "Invalid type #{t.name}" + end + end + end + + def self.compareMethod(entity) + FunctionDecl.new( + name: 'compareTo', + entity: entity, + is_method: true, + return_type: TypeDecl.new(name: :int), + parameters: [Parameter.new(name: 'thisObject', type: entity.type), Parameter.new(name: 'other', type: entity.type)], + static: false) + end +end diff --git a/codegen/lib/parser.rb b/codegen/lib/parser.rb index afa2acfe1c4..e4e287614a0 100644 --- a/codegen/lib/parser.rb +++ b/codegen/lib/parser.rb @@ -34,6 +34,8 @@ def parse next end + @entity_comment = @entity_comment.strip + # Look for TW_EXPORT statements if !@buffer.scan(/TW_EXPORT_[A-Z_]+/).nil? # Handle statements diff --git a/codegen/lib/templates/CoinInfoData.cpp.erb b/codegen/lib/templates/CoinInfoData.cpp.erb index 44dd8acd044..d2bbb5d6b71 100644 --- a/codegen/lib/templates/CoinInfoData.cpp.erb +++ b/codegen/lib/templates/CoinInfoData.cpp.erb @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // // This is a GENERATED FILE, changes made here WILL BE LOST. // @@ -36,6 +34,7 @@ static const CoinInfo defaultsForMissing = { "", "", 0, + 0 }; /// Get coin from map, if missing returns defaults (not to have contains-check in each accessor method) @@ -73,6 +72,7 @@ const CoinInfo getCoinInfo(TWCoinType coin) { "<%= explorer_tx_url(coin) %>", "<%= explorer_account_url(coin) %>", <% if coin['slip44'].nil? -%><%= coin['coinId'] %><% else -%><%= coin['slip44'] %><% end -%>, + <% if coin['ss58Prefix'].nil? -%>0<% else -%><%= coin['ss58Prefix'] %><% end -%>, }; <% end -%> default: diff --git a/codegen/lib/templates/TWCoinTypeTests.cpp.erb b/codegen/lib/templates/TWCoinTypeTests.cpp.erb index 4c8cd77e4fc..bb8cbdadb79 100644 --- a/codegen/lib/templates/TWCoinTypeTests.cpp.erb +++ b/codegen/lib/templates/TWCoinTypeTests.cpp.erb @@ -1,14 +1,12 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // // This is a GENERATED FILE, changes made here MAY BE LOST. // Generated one-time (codegen/bin/cointests) // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/codegen/lib/templates/TWDerivation.h.erb b/codegen/lib/templates/TWDerivation.h.erb index 06266d5cb82..e2c9a53a494 100644 --- a/codegen/lib/templates/TWDerivation.h.erb +++ b/codegen/lib/templates/TWDerivation.h.erb @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // // This is a GENERATED FILE from \registry.json, changes made here WILL BE LOST. // @@ -20,14 +18,12 @@ enum TWDerivation { TWDerivationCustom = 1, // custom, for any coin <% enum_count += 1 -%> <% coins.each do |coin| -%> -<% if coin['derivation'].count > 1 -%> <% coin['derivation'].each_with_index do |deriv, index| -%> <% if index > 0 or !deriv['name'].nil? -%> <%= derivation_enum_name(deriv, coin) %> = <% enum_count += 1 -%><%= enum_count %>, <% end -%> <% end -%> <% end -%> -<% end -%> }; TW_EXTERN_C_END diff --git a/codegen/lib/templates/TWEthereumChainID.h.erb b/codegen/lib/templates/TWEthereumChainID.h.erb index 99862693b3f..3e6385e1b77 100644 --- a/codegen/lib/templates/TWEthereumChainID.h.erb +++ b/codegen/lib/templates/TWEthereumChainID.h.erb @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // // This is a GENERATED FILE from \registry.json, changes made here WILL BE LOST. // diff --git a/codegen/lib/templates/copyright_header.erb b/codegen/lib/templates/copyright_header.erb index be628ee1f6e..39239c27440 100644 --- a/codegen/lib/templates/copyright_header.erb +++ b/codegen/lib/templates/copyright_header.erb @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // // This is a GENERATED FILE, changes made here WILL BE LOST. // diff --git a/codegen/lib/templates/hrp.cpp.erb b/codegen/lib/templates/hrp.cpp.erb index 24eede081df..c3705bd5a9f 100644 --- a/codegen/lib/templates/hrp.cpp.erb +++ b/codegen/lib/templates/hrp.cpp.erb @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // // This is a GENERATED FILE from \registry.json, changes made here WILL BE LOST. // diff --git a/codegen/lib/templates/hrp.h.erb b/codegen/lib/templates/hrp.h.erb index 0f4ed0432f1..4691fbafc5b 100644 --- a/codegen/lib/templates/hrp.h.erb +++ b/codegen/lib/templates/hrp.h.erb @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // // This is a GENERATED FILE from \registry.json, changes made here WILL BE LOST. // diff --git a/codegen/lib/templates/java/class.erb b/codegen/lib/templates/java/class.erb index b6e44d0d7d0..fa491ff2560 100644 --- a/codegen/lib/templates/java/class.erb +++ b/codegen/lib/templates/java/class.erb @@ -5,9 +5,9 @@ import java.util.HashSet; <% equal = entity.static_methods.detect{ |i| i.name == 'Equal' } -%> <%= entity.comment %> <% if !less.nil? && !equal.nil? -%> -public class <%= entity.name %> implements Comparable<<%= entity.name %>> { +public final class <%= entity.name %> implements Comparable<<%= entity.name %>> { <% else -%> -public class <%= entity.name %> { +public final class <%= entity.name %> { <% end -%> private long nativeHandle; @@ -25,23 +25,20 @@ public class <%= entity.name %> { <% end -%> return instance; } - <%# Constructor declarations -%> <% entity.static_methods.each do |method| -%> <% next unless method.name.start_with?('Create') -%> - <%= method.comment %> +<%= method.comment_with_indent %> static native long native<%= method.name %>(<%= JavaHelper.parameters(method.parameters) %>); <% end -%> - <%# Destructor declarations -%> <% entity.methods.each do |method| -%> <% next unless method.name.start_with?('Delete') -%> static native void native<%= method.name %>(long handle); <% end -%> - <%# Static property declarations -%> <% entity.static_properties.each do |property| -%> - <%= property.comment %> +<%= property.comment_with_indent %> <% if should_return_data(property) -%> public static native byte[] <%= JavaHelper.format_name(property.name) %>(<%= JavaHelper.parameters(property.parameters) %>); <% elsif should_return_string(property) -%> @@ -50,11 +47,10 @@ public class <%= entity.name %> { public static native <%= JavaHelper.type(property.return_type) %> <%= JavaHelper.format_name(property.name) %>(<%= JavaHelper.parameters(property.parameters) %>); <% end -%> <% end -%> - <%# Static method declarations -%> <% entity.static_methods.each do |method| -%> <% next if method.name.start_with?('Create') -%> - <%= method.comment %> +<%= method.comment_with_indent %> <% if should_return_data(method) -%> public static native byte[] <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.parameters(method.parameters) %>); <% elsif should_return_string(method) -%> @@ -63,10 +59,9 @@ public class <%= entity.name %> { public static native <%= JavaHelper.type(method.return_type) %> <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.parameters(method.parameters) %>); <% end -%> <% end -%> - <%# Property declarations -%> <% entity.properties.each do |property| -%> - <%= property.comment %> +<%= property.comment_with_indent %> <% if should_return_data(property) -%> public native byte[] <%= JavaHelper.format_name(property.name) %>(<%= JavaHelper.parameters(property.parameters.drop(1)) %>); <% elsif should_return_string(property) -%> @@ -75,11 +70,10 @@ public class <%= entity.name %> { public native <%= JavaHelper.type(property.return_type) %> <%= JavaHelper.format_name(property.name) %>(<%= JavaHelper.parameters(property.parameters.drop(1)) %>); <% end -%> <% end -%> - <%# Method declarations -%> <% entity.methods.each do |method| -%> <% next if method.name.start_with?('Delete') -%> - <%= method.comment %> +<%= method.comment_with_indent %> <% if should_return_data(method) -%> public native byte[] <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.parameters(method.parameters.drop(1)) %>); <% elsif should_return_string(method) -%> @@ -92,11 +86,10 @@ public class <%= entity.name %> { <% compareMethod = JNIHelper.compareMethod(entity) -%> public native <%= JavaHelper.type(compareMethod.return_type) %> <%= JavaHelper.format_name(compareMethod.name) %>(<%= JavaHelper.parameters(compareMethod.parameters.drop(1)) %>); <% end -%> - <%# Constructors -%> <%- entity.static_methods.each do |method| -%> <%- next unless method.name.start_with?('Create') -%> - <%= method.comment %> +<%= method.comment_with_indent %> public <%= entity.name %>(<%= JavaHelper.parameters(method.parameters) %>) { nativeHandle = native<%= method.name %>(<%= JavaHelper.arguments(method.parameters) %>); if (nativeHandle == 0) { @@ -108,7 +101,6 @@ public class <%= entity.name %> { <%- end -%> } - <% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> class <%= entity.name %>PhantomReference extends java.lang.ref.PhantomReference<<%= entity.name %>> { private static java.util.Set<<%= entity.name %>PhantomReference> references = new HashSet<<%= entity.name %>PhantomReference>(); diff --git a/codegen/lib/templates/java/header.erb b/codegen/lib/templates/java/header.erb index b1b1aeabc86..3c5da1f81ed 100644 --- a/codegen/lib/templates/java/header.erb +++ b/codegen/lib/templates/java/header.erb @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // // This is a GENERATED FILE, changes made here WILL BE LOST. // diff --git a/codegen/lib/templates/java/struct.erb b/codegen/lib/templates/java/struct.erb index 7c8b234d33b..6f09dda87d2 100644 --- a/codegen/lib/templates/java/struct.erb +++ b/codegen/lib/templates/java/struct.erb @@ -4,9 +4,9 @@ import java.security.InvalidParameterException; <% equal = entity.static_methods.detect{ |i| i.name == 'Equal' } -%> <%= entity.comment %> <% if !less.nil? && !equal.nil? -%> -public class <%= entity.name %> implements Comparable<<%= entity.name %>> { +public final class <%= entity.name %> implements Comparable<<%= entity.name %>> { <% else -%> -public class <%= entity.name %> { +public final class <%= entity.name %> { <% end -%> private byte[] bytes; @@ -18,71 +18,63 @@ public class <%= entity.name %> { instance.bytes = bytes; return instance; } - <%# Constructor declarations -%> <%- entity.static_methods.each do |method| -%> <%- next unless method.name.start_with?('Init') -%> - <%= method.comment %> +<%= method.comment_with_indent %> static native byte[] <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.parameters(method.parameters.drop(1)) %>); <%- end -%> - <%# Static property declarations -%> <%- entity.static_properties.each do |property| -%> - <%= property.comment %> +<%= property.comment_with_indent %> <%- if should_return_data(property) -%> public static native byte[] <%= JavaHelper.format_name(property.name) %>(<%= JavaHelper.parameters(property.parameters) %>); <%- else -%> public static native <%= JavaHelper.type(property.return_type) %> <%= JavaHelper.format_name(property.name) %>(<%= JavaHelper.parameters(property.parameters) %>); <%- end -%> <%- end -%> - <%# Static method declarations -%> <%- entity.static_methods.each do |method| -%> <%- next if method.name.start_with?('Init') -%> - <%= method.comment %> +<%= method.comment_with_indent %> <%- if should_return_data(method) -%> public static native byte[] <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.parameters(method.parameters) %>); <%- else -%> public static native <%= JavaHelper.type(method.return_type) %> <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.parameters(method.parameters) %>); <%- end -%> <%- end -%> - <%# Property declarations -%> <%- entity.properties.each do |property| -%> - <%= property.comment %> +<%= property.comment_with_indent %> <%- if should_return_data(property) -%> public native byte[] <%= JavaHelper.format_name(property.name) %>(<%= JavaHelper.parameters(property.parameters.drop(1)) %>); <%- else -%> public native <%= JavaHelper.type(property.return_type) %> <%= JavaHelper.format_name(property.name) %>(<%= JavaHelper.parameters(property.parameters.drop(1)) %>); <%- end -%> <%- end -%> - <%# Method declarations -%> <%- entity.methods.each do |method| -%> <%- next if method.name.start_with?('Delete') -%> - <%= method.comment %> +<%= method.comment_with_indent %> <%- if should_return_data(method) -%> public native byte[] <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.parameters(method.parameters.drop(1)) %>); <%- else -%> public native <%= JavaHelper.type(method.return_type) %> <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.parameters(method.parameters.drop(1)) %>); <%- end -%> <%- end -%> - <% if !less.nil? && !equal.nil? -%> <% compareMethod = JNIHelper.compareMethod(entity) -%> public native <%= JavaHelper.type(compareMethod.return_type) %> <%= JavaHelper.format_name(compareMethod.name) %>(<%= JavaHelper.parameters(compareMethod.parameters.drop(1)) %>); <% end -%> - <%# Constructors -%> <%- entity.static_methods.each do |method| -%> <%- next unless method.name.start_with?('Init') -%> - <%= method.comment %> +<%= method.comment_with_indent %> public <%= entity.name %>(<%= JavaHelper.parameters(method.parameters.drop(1)) %>) { bytes = <%= JavaHelper.format_name(method.name) %>(<%= JavaHelper.arguments(method.parameters.drop(1)) %>); if (bytes == null) { throw new InvalidParameterException(); } } - <%- end -%> } diff --git a/codegen/lib/templates/jni/parameter_access.erb b/codegen/lib/templates/jni/parameter_access.erb index 71bc2641c41..4c3ceb9f2fa 100644 --- a/codegen/lib/templates/jni/parameter_access.erb +++ b/codegen/lib/templates/jni/parameter_access.erb @@ -1,6 +1,6 @@ <% method = locals[:method] - if method.static && !method.name.include?('Init') + if method.static && !method.name.start_with?('Init') parameters = method.parameters else parameters = method.parameters.drop(1) diff --git a/codegen/lib/templates/jni/parameter_release.erb b/codegen/lib/templates/jni/parameter_release.erb index 147e40c3496..1db6f1a86e1 100644 --- a/codegen/lib/templates/jni/parameter_release.erb +++ b/codegen/lib/templates/jni/parameter_release.erb @@ -1,6 +1,6 @@ <% method = locals[:method] - if method.static && !method.name.include?('Init') + if method.static && !method.name.start_with?('Init') parameters = method.parameters else parameters = method.parameters.drop(1) diff --git a/codegen/lib/templates/kotlin/android_class.erb b/codegen/lib/templates/kotlin/android_class.erb new file mode 100644 index 00000000000..3091b094ca7 --- /dev/null +++ b/codegen/lib/templates/kotlin/android_class.erb @@ -0,0 +1,63 @@ +<%= render('kotlin/package.erb') %> + +<% constructors = entity.static_methods.select { |method| method.name.start_with?('Create') } -%> +<% methods = entity.methods.select { |method| not method.name.start_with?('Delete') } -%> +<% static_methods = entity.static_methods.select { |method| not method.name.start_with?('Create') } -%> +actual class <%= entity.name %> private constructor( + private val nativeHandle: Long, +) { + + init { + if (nativeHandle == 0L) throw IllegalArgumentException() + } +<%# Constructors -%> +<%- constructors.each do |constructor| -%> + +<% if constructor.return_type.is_nullable -%> + @Throws(IllegalArgumentException::class) +<% end -%> + actual constructor(<%= KotlinHelper.parameters(constructor.parameters) %>) : this(<%= KotlinHelper.format_name(constructor.name) %>(<%= KotlinHelper.calling_parameters_android(constructor.parameters) %>)) +<% end -%> +<%# Property declarations -%> +<% entity.properties.each do |property| -%> + + actual val <%= KotlinHelper.format_name(property.name) %><%= KotlinHelper.return_type(property.return_type) %> + @JvmName("<%= KotlinHelper.format_name(property.name) %>") + external get +<% end -%> +<%# Method declarations -%> +<% methods.each do |method| -%> + + @JvmName("<%= KotlinHelper.format_name(method.name) %>") + actual external fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters.drop(1)) %>)<%= KotlinHelper.return_type(method.return_type) %> +<% end -%> +<% if entity.static_properties.any? || static_methods.any? || constructors.any? -%> + + <%= if entity.static_properties.any? || static_methods.any? then "actual" else "private" end %> companion object { +<%# Static property declarations -%> +<% entity.static_properties.each do |property| -%> + + actual val <%= KotlinHelper.format_name(property.name) %><%= KotlinHelper.return_type(property.return_type) %> + @JvmName("<%= KotlinHelper.format_name(property.name) %>") + external get +<% end -%> +<%# Static method declarations -%> +<% static_methods.each do |method| -%> + + @JvmStatic + @JvmName("<%= KotlinHelper.format_name(method.name) %>") + actual external fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters) %>)<%= KotlinHelper.return_type(method.return_type) %> +<% end -%> + + @JvmStatic + @JvmName("createFromNative") + private fun createFromNative(nativeHandle: Long) = <%= entity.name %>(nativeHandle) +<%- constructors.each do |constructor| -%> + + @JvmStatic + @JvmName("<%= KotlinHelper.format_name(constructor.name) %>") + private external fun <%= KotlinHelper.format_name(constructor.name) %>(<%= KotlinHelper.parameters(constructor.parameters) %>): Long +<%- end -%> + } +<% end -%> +} diff --git a/codegen/lib/templates/kotlin/android_enum.erb b/codegen/lib/templates/kotlin/android_enum.erb new file mode 100644 index 00000000000..e00cb92f5bc --- /dev/null +++ b/codegen/lib/templates/kotlin/android_enum.erb @@ -0,0 +1,44 @@ +<%= render('kotlin/package.erb') %> + +<% has_string = entity.cases.all? { |c| !c.string.nil? } -%> +actual enum class <%= entity.name %>( + @get:JvmName("value") + actual val value: UInt, +<% if has_string -%> + actual val stringValue: String, +<% end -%> +) { +<%# Cases -%> +<% entity.cases.each_with_index do |c, i| -%> +<% if has_string -%> + <%= c.name %>(<%= c.value %>u, <%= c.string %>), +<% else -%> + <%= c.name %>(<%= c.value %>u), +<% end -%> +<% end -%> + ; +<%# Property declarations -%> +<%- entity.properties.each do |property| -%> + + actual val <%= KotlinHelper.format_name(property.name) %><%= KotlinHelper.return_type(property.return_type) %> + @JvmName("<%= KotlinHelper.format_name(property.name) %>") + external get +<%- end -%> +<%# Method declarations -%> +<%- entity.methods.each do |method| -%> +<%- next if method.name.start_with?('Delete') -%> + + @JvmName("<%= KotlinHelper.format_name(method.name) %>") + actual external fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters.drop(1)) %>)<%= KotlinHelper.return_type(method.return_type) %> +<%- end -%> +<%# Value -%> +<% if entity.cases.any? { |e| !e.value.nil? } -%> + + actual companion object { + @JvmStatic + @JvmName("createFromValue") + actual fun fromValue(value: UInt): <%= entity.name %>? = + values().firstOrNull { it.value == value } + } +<% end -%> +} diff --git a/codegen/lib/templates/kotlin/android_struct.erb b/codegen/lib/templates/kotlin/android_struct.erb new file mode 100644 index 00000000000..099704dd35b --- /dev/null +++ b/codegen/lib/templates/kotlin/android_struct.erb @@ -0,0 +1,19 @@ +<%= render('kotlin/package.erb') %> + +actual object <%= entity.name %> { +<%# Static property declarations -%> +<% entity.static_properties.each do |property| -%> + + actual val <%= KotlinHelper.format_name(property.name) %><%= KotlinHelper.return_type(property.return_type) %> + @JvmName("<%= KotlinHelper.format_name(property.name) %>") + external get +<% end -%> +<%# Static method declarations -%> +<% entity.static_methods.each do |method| -%> +<% next if method.name.start_with?('Create') -%> + + @JvmStatic + @JvmName("<%= KotlinHelper.format_name(method.name) %>") + actual external fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters) %>)<%= KotlinHelper.return_type(method.return_type) %> +<% end -%> +} diff --git a/codegen/lib/templates/kotlin/common_class.erb b/codegen/lib/templates/kotlin/common_class.erb new file mode 100644 index 00000000000..0cd6d4a506a --- /dev/null +++ b/codegen/lib/templates/kotlin/common_class.erb @@ -0,0 +1,60 @@ +<%= render('kotlin/package.erb') %> + +<% constructors = entity.static_methods.select { |method| method.name.start_with?('Create') } -%> +<% methods = entity.methods.select { |method| not method.name.start_with?('Delete') } -%> +<% static_methods = entity.static_methods.select { |method| not method.name.start_with?('Create') } -%> +<% if constructors.one? -%> +<% if constructors.first.return_type.is_nullable -%> +expect class <%= entity.name %> @Throws(IllegalArgumentException::class) constructor( +<% else -%> +expect class <%= entity.name %>( +<% end -%> +<%- constructors.first.parameters.each do |parameter| -%> + <%= KotlinHelper.parameters([parameter]) %>, +<%- end -%> +) { +<%- else -%> +expect class <%= entity.name %> { +<%# Constructors -%> +<%- constructors.each do |constructor| -%> + +<% if constructor.return_type.is_nullable -%> + @Throws(IllegalArgumentException::class) +<% end -%> + constructor(<%= KotlinHelper.parameters(constructor.parameters) %>) +<%- end -%> +<% end -%> +<%# Property declarations -%> +<% if entity.properties.any? -%> + +<% end -%> +<% entity.properties.each do |property| -%> + val <%= KotlinHelper.format_name(property.name) %><%= KotlinHelper.return_type(property.return_type) %> +<% end -%> +<%# Method declarations -%> +<% if methods.any? -%> + +<% end -%> +<% methods.each do |method| -%> + fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters.drop(1)) %>)<%= KotlinHelper.return_type(method.return_type) %> +<% end -%> +<% if entity.static_properties.any? || static_methods.any? -%> + + companion object { +<%# Static property declarations -%> +<% if entity.static_properties.any? -%> + +<% end -%> +<% entity.static_properties.each do |property| -%> + val <%= KotlinHelper.format_name(property.name) %><%= KotlinHelper.return_type(property.return_type) %> +<% end -%> +<%# Static method declarations -%> +<% if static_methods.any? -%> + +<% end -%> +<% static_methods.each do |method| -%> + fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters) %>)<%= KotlinHelper.return_type(method.return_type) %> +<% end -%> + } +<% end -%> +} diff --git a/codegen/lib/templates/kotlin/common_enum.erb b/codegen/lib/templates/kotlin/common_enum.erb new file mode 100644 index 00000000000..234df7e8d28 --- /dev/null +++ b/codegen/lib/templates/kotlin/common_enum.erb @@ -0,0 +1,34 @@ +<%= render('kotlin/package.erb') %> + +<% has_string = entity.cases.all? { |c| !c.string.nil? } -%> +expect enum class <%= entity.name %> { +<%# Cases -%> +<% entity.cases.each_with_index do |c, i| -%> + <%= c.name %>, +<% end -%> + ; + + val value: UInt +<% if has_string -%> + val stringValue: String +<% end -%> +<%# Property declarations -%> +<%- entity.properties.each do |property| -%> + val <%= KotlinHelper.format_name(property.name) %><%= KotlinHelper.return_type(property.return_type) %> +<%- end -%> +<%# Method declarations -%> +<% if entity.methods.any? -%> + +<% end -%> +<%- entity.methods.each do |method| -%> +<%- next if method.name.start_with?('Delete') -%> + fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters.drop(1)) %>)<%= KotlinHelper.return_type(method.return_type) %> +<%- end -%> +<%# Value -%> +<% if entity.cases.any? { |e| !e.value.nil? } -%> + + companion object { + fun fromValue(value: UInt): <%= entity.name %>? + } +<% end -%> +} diff --git a/codegen/lib/templates/kotlin/common_struct.erb b/codegen/lib/templates/kotlin/common_struct.erb new file mode 100644 index 00000000000..39e085799ea --- /dev/null +++ b/codegen/lib/templates/kotlin/common_struct.erb @@ -0,0 +1,13 @@ +<%= render('kotlin/package.erb') %> + +expect object <%= entity.name %> { +<%# Static property declarations -%> +<% entity.static_properties.each do |property| -%> + val <%= KotlinHelper.format_name(property.name) %><%= KotlinHelper.return_type(property.return_type) %> +<% end -%> +<%# Static method declarations -%> +<% entity.static_methods.each do |method| -%> +<% next if method.name.start_with?('Create') -%> + fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters) %>)<%= KotlinHelper.return_type(method.return_type) %> +<% end -%> +} diff --git a/codegen/lib/templates/kotlin/ios_class.erb b/codegen/lib/templates/kotlin/ios_class.erb new file mode 100644 index 00000000000..36bc94e8276 --- /dev/null +++ b/codegen/lib/templates/kotlin/ios_class.erb @@ -0,0 +1,54 @@ +<%= render('kotlin/package.erb') %> + +import cnames.structs.TW<%= entity.name %> +import kotlinx.cinterop.CPointer + +<% constructors = entity.static_methods.select { |method| method.name.start_with?('Create') } -%> +<% methods = entity.methods.select { |method| not method.name.start_with?('Delete') } -%> +<% static_methods = entity.static_methods.select { |method| not method.name.start_with?('Create') } -%> +actual class <%= entity.name %> constructor( + val pointer: CPointer>, +) { +<%# Constructors -%> +<%- constructors.each do |constructor| -%> + +<% if constructor.return_type.is_nullable -%> + @Throws(IllegalArgumentException::class) + actual constructor(<%= KotlinHelper.parameters(constructor.parameters) %>) : this( + TW<%= entity.name %><%= constructor.name %>(<%= KotlinHelper.calling_parameters_ios(constructor.parameters) %>) ?: throw IllegalArgumentException() +<% else -%> + actual constructor(<%= KotlinHelper.parameters(constructor.parameters) %>) : this( + TW<%= entity.name %><%= constructor.name %>(<%= KotlinHelper.calling_parameters_ios(constructor.parameters) %>)!! +<% end -%> + ) +<% end -%> +<%# Property declarations -%> +<% entity.properties.each do |property| -%> + + actual val <%= KotlinHelper.format_name(property.name) %><%= KotlinHelper.return_type(property.return_type) %> + get() = <%= KotlinHelper.convert_calling_return_type_ios(property.return_type, "TW#{entity.name}#{property.name}(pointer)") %> +<% end -%> +<%# Method declarations -%> +<% methods.each do |method| -%> + + actual fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters.drop(1)) %>)<%= KotlinHelper.return_type(method.return_type) %> = + <%= KotlinHelper.convert_calling_return_type_ios(method.return_type, "TW#{entity.name}#{method.name}(pointer#{', ' if not method.parameters.one?}#{KotlinHelper.calling_parameters_ios(method.parameters.drop(1))})") %> +<% end -%> +<% if entity.static_properties.any? || static_methods.any? -%> + + actual companion object { +<%# Static property declarations -%> +<% entity.static_properties.each do |property| -%> + + actual val <%= KotlinHelper.format_name(property.name) %><%= KotlinHelper.return_type(property.return_type) %> + get() = TW<%= entity.name %><%= property.name %>()<%= KotlinHelper.convert_calling_return_type_ios(property.return_type) %> +<% end -%> +<%# Static method declarations -%> +<% static_methods.each do |method| -%> + + actual fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters) %>)<%= KotlinHelper.return_type(method.return_type) %> = + <%= KotlinHelper.convert_calling_return_type_ios(method.return_type, "TW#{entity.name}#{method.name}(#{KotlinHelper.calling_parameters_ios(method.parameters)})") %> +<% end -%> + } +<% end -%> +} diff --git a/codegen/lib/templates/kotlin/ios_enum.erb b/codegen/lib/templates/kotlin/ios_enum.erb new file mode 100644 index 00000000000..5f64c80a7fa --- /dev/null +++ b/codegen/lib/templates/kotlin/ios_enum.erb @@ -0,0 +1,63 @@ +<%= render('kotlin/package.erb') %> + +<% has_string = entity.cases.all? { |c| !c.string.nil? } -%> +<% if has_string -%> +import com.trustwallet.core.<%= "TW#{entity.name}" %>.* + +<% end -%> +<% type = ": TW#{entity.name}" -%> +actual enum class <%= entity.name %>( +<% if has_string -%> + val nativeValue<%= type %>, + actual val stringValue: String, +<% else -%> + val nativeValue<%= type %>, +<% end -%> +) { +<%# Cases -%> +<% entity.cases.each_with_index do |c, i| -%> +<% if has_string -%> + <%= c.name %>(TW<%= entity.name %><%= c.name %>, <%= c.string %>), +<% else -%> + <%= c.name %>(TW<%= entity.name %><%= c.name %>), +<% end -%> +<% end -%> + ; + +<% if has_string -%> + actual val value: UInt + get() = nativeValue.value +<% else -%> + actual val value<%= type %> + get() = nativeValue +<% end -%> +<%# Property declarations -%> +<%- entity.properties.each do |property| -%> + + actual val <%= KotlinHelper.format_name(property.name) %><%= KotlinHelper.return_type(property.return_type) %> + get() = <%= KotlinHelper.convert_calling_return_type_ios(property.return_type, "TW#{entity.name}#{property.name}(value)") %> +<%- end -%> +<%# Method declarations -%> +<%- entity.methods.each do |method| -%> +<%- next if method.name.start_with?('Delete') -%> + + actual fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters.drop(1)) %>)<%= KotlinHelper.return_type(method.return_type) %> = + TW<%= entity.name %><%= method.name %>(value<%= ', ' if not method.parameters.one? %><%= KotlinHelper.calling_parameters_ios(method.parameters.drop(1)) %>)<%= KotlinHelper.convert_calling_return_type_ios(method.return_type) %> +<%- end -%> +<%# Value -%> +<% if entity.cases.any? { |e| !e.value.nil? } -%> + + actual companion object { +<% if has_string -%> + actual fun fromValue(value: UInt): <%= entity.name %>? = + values().firstOrNull { it.value == value } + + fun fromValue(value<%= type %>): <%= entity.name %> = + fromValue(value.value)!! +<% else -%> + actual fun fromValue(value<%= type %>): <%= entity.name %>? = + values().firstOrNull { it.value == value } +<% end -%> + } +<% end -%> +} diff --git a/codegen/lib/templates/kotlin/ios_struct.erb b/codegen/lib/templates/kotlin/ios_struct.erb new file mode 100644 index 00000000000..6bf3da876ee --- /dev/null +++ b/codegen/lib/templates/kotlin/ios_struct.erb @@ -0,0 +1,16 @@ +<%= render('kotlin/package.erb') %> + +actual object <%= entity.name %> { +<%# Static property declarations -%> +<% entity.static_properties.each do |property| -%> + actual val <%= KotlinHelper.format_name(property.name) %><%= KotlinHelper.return_type(property.return_type) %> + get() = TODO() +<% end -%> +<%# Static method declarations -%> +<% entity.static_methods.each do |method| -%> +<% next if method.name.start_with?('Create') -%> + + actual fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters) %>)<%= KotlinHelper.return_type(method.return_type) %> = + <%= KotlinHelper.convert_calling_return_type_ios(method.return_type, "TW#{entity.name}#{method.name}(#{KotlinHelper.calling_parameters_ios(method.parameters)})") %> +<% end -%> +} diff --git a/codegen/lib/templates/kotlin/js_accessors_class.erb b/codegen/lib/templates/kotlin/js_accessors_class.erb new file mode 100644 index 00000000000..77645552c83 --- /dev/null +++ b/codegen/lib/templates/kotlin/js_accessors_class.erb @@ -0,0 +1,23 @@ +@file:Suppress("NESTED_CLASS_IN_EXTERNAL_INTERFACE") + +<%= render('kotlin/package.erb') %> + +@JsModule("@trustwallet/wallet-core") +@JsName("<%= entity.name %>") +external interface Js<%= entity.name %> { +<%- entity.properties.each do |property| -%> + fun <%= KotlinHelper.fix_name(WasmCppHelper.format_name(property.name)) %>()<%= KotlinHelper.js_return_type(property.return_type) %> +<%- end -%> +<% entity.methods.each do |method| -%> +<% next if method.name == "Delete" -%> + fun <%= KotlinHelper.fix_name(WasmCppHelper.format_name(method.name)) %>(<%= KotlinHelper.js_parameters(method.parameters.drop(1)) %>)<%= KotlinHelper.js_return_type(method.return_type) %> +<% end -%> + companion object { +<% entity.static_methods.each do |method| -%> + fun <%= KotlinHelper.fix_name(WasmCppHelper.function_name(entity: entity, function: method)) %>(<%= KotlinHelper.js_parameters(method.parameters) %>)<%= KotlinHelper.js_return_type(method.return_type) %> +<% end -%> + } +} + +inline val JsWalletCore.<%= entity.name %>: Js<%= entity.name %>.Companion + get() = asDynamic().<%= entity.name %>.unsafeCast.Companion>() diff --git a/codegen/lib/templates/kotlin/js_accessors_enum.erb b/codegen/lib/templates/kotlin/js_accessors_enum.erb new file mode 100644 index 00000000000..feaa2e82caf --- /dev/null +++ b/codegen/lib/templates/kotlin/js_accessors_enum.erb @@ -0,0 +1,36 @@ +@file:Suppress("NESTED_CLASS_IN_EXTERNAL_INTERFACE") + +<%= render('kotlin/package.erb') %> + +@JsModule("@trustwallet/wallet-core") +@JsName("<%= entity.name %>") +external interface Js<%= entity.name %> { + val value: Number + companion object { +<% entity.cases.each do |c| -%> + val <%= KotlinHelper.fix_name(WasmCppHelper.format_name(c.name)) %>: Js<%= entity.name %> +<% end -%> + } +} + +inline val JsWalletCore.<%= entity.name %>: Js<%= entity.name %>.Companion + get() = asDynamic().<%= entity.name %>.unsafeCast.Companion>() + +<% if entity.properties.any? || entity.methods.any? -%> +@JsModule("@trustwallet/wallet-core") +@JsName("<%= entity.name %>Ext") +external interface Js<%= entity.name %>Ext { +<%# Static method declarations -%> +<%- entity.properties.each do |property| -%> + fun <%= KotlinHelper.fix_name(WasmCppHelper.format_name(property.name)) %>(<%= KotlinHelper.js_parameters(property.parameters) %>)<%= KotlinHelper.js_return_type(property.return_type) %> +<%- end -%> +<% entity.methods.each do |method| -%> +<% next if method.name.start_with?('Create') -%> + fun <%= KotlinHelper.fix_name(WasmCppHelper.function_name(entity: entity, function: method)) %>(<%= KotlinHelper.js_parameters(method.parameters) %>)<%= KotlinHelper.js_return_type(method.return_type) %> +<% end -%> +} + +inline val JsWalletCore.<%= entity.name %>Ext: Js<%= entity.name %>Ext + get() = asDynamic().<%= entity.name %>Ext.unsafeCastExt>() + +<% end -%> \ No newline at end of file diff --git a/codegen/lib/templates/kotlin/js_accessors_struct.erb b/codegen/lib/templates/kotlin/js_accessors_struct.erb new file mode 100644 index 00000000000..911024dc9ee --- /dev/null +++ b/codegen/lib/templates/kotlin/js_accessors_struct.erb @@ -0,0 +1,14 @@ +<%= render('kotlin/package.erb') %> + +@JsModule("@trustwallet/wallet-core") +@JsName("<%= entity.name %>") +external interface Js<%= entity.name %> { +<%# Static method declarations -%> +<% entity.static_methods.each do |method| -%> +<% next if method.name.start_with?('Create') -%> + fun <%= KotlinHelper.fix_name(WasmCppHelper.function_name(entity: entity, function: method)) %>(<%= KotlinHelper.js_parameters(method.parameters) %>)<%= KotlinHelper.js_return_type(method.return_type) %> +<% end -%> +} + +inline val JsWalletCore.<%= entity.name %>: Js<%= entity.name %> + get() = asDynamic().<%= entity.name %>.unsafeCast>() diff --git a/codegen/lib/templates/kotlin/js_class.erb b/codegen/lib/templates/kotlin/js_class.erb new file mode 100644 index 00000000000..14cd6ef5e40 --- /dev/null +++ b/codegen/lib/templates/kotlin/js_class.erb @@ -0,0 +1,48 @@ +<%= render('kotlin/package.erb') %> + +<% constructors = entity.static_methods.select { |method| method.name.start_with?('Create') } -%> +<% methods = entity.methods.select { |method| not method.name.start_with?('Delete') } -%> +<% static_methods = entity.static_methods.select { |method| not method.name.start_with?('Create') } -%> +actual class <%= entity.name %> constructor( + val jsValue: Js<%= entity.name %>, +) { +<%# Constructors -%> +<%- constructors.each do |constructor| -%> + + actual constructor(<%= KotlinHelper.parameters(constructor.parameters) %>) : +<% if constructor.return_type.is_nullable -%> + this(WalletCore.Instance.<%= entity.name %>.<%= WasmCppHelper.function_name(entity: entity, function: constructor) %>(<%= KotlinHelper.calling_parameters_js(constructor.parameters) %>) ?: throw IllegalArgumentException()) +<% else -%> + this(WalletCore.Instance.<%= entity.name %>.<%= WasmCppHelper.function_name(entity: entity, function: constructor) %>(<%= KotlinHelper.calling_parameters_js(constructor.parameters) %>)) +<% end -%> +<% end -%> +<%# Property declarations -%> +<% entity.properties.each do |property| -%> + + actual val <%= KotlinHelper.format_name(property.name) %><%= KotlinHelper.return_type(property.return_type) %> + get() = <%= KotlinHelper.convert_calling_return_type_js(property.return_type, "jsValue.#{WasmCppHelper.function_name(entity: entity, function: property)}()") %> +<% end -%> +<%# Method declarations -%> +<% methods.each do |method| -%> + + actual fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters.drop(1)) %>)<%= KotlinHelper.return_type(method.return_type) %> = + <%= KotlinHelper.convert_calling_return_type_js(method.return_type, "jsValue.#{WasmCppHelper.function_name(entity: entity, function: method)}(#{KotlinHelper.calling_parameters_js(method.parameters.drop(1))})") %> +<% end -%> +<% if entity.static_properties.any? || static_methods.any? -%> + + <%= if entity.static_properties.any? || static_methods.any? then "actual" else "private" end %> companion object { +<%# Static property declarations -%> +<% entity.static_properties.each do |property| -%> + + actual val <%= KotlinHelper.format_name(property.name) %><%= KotlinHelper.return_type(property.return_type) %> + get() = TODO() +<% end -%> +<%# Static method declarations -%> +<% static_methods.each do |method| -%> + + actual fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters) %>)<%= KotlinHelper.return_type(method.return_type) %> = + <%= KotlinHelper.convert_calling_return_type_js(method.return_type, "WalletCore.Instance.#{entity.name}.#{WasmCppHelper.function_name(entity: entity, function: method)}(#{KotlinHelper.calling_parameters_js(method.parameters)})") %> +<% end -%> + } +<% end -%> +} diff --git a/codegen/lib/templates/kotlin/js_enum.erb b/codegen/lib/templates/kotlin/js_enum.erb new file mode 100644 index 00000000000..2b910a5e7aa --- /dev/null +++ b/codegen/lib/templates/kotlin/js_enum.erb @@ -0,0 +1,46 @@ +<%= render('kotlin/package.erb') %> + +<% has_string = entity.cases.all? { |c| !c.string.nil? } -%> +actual enum class <%= entity.name %>( + val jsValue: Js<%= entity.name %>, +<% if has_string -%> + actual val stringValue: String, +<% end -%> +) { +<%# Cases -%> +<% entity.cases.each_with_index do |c, i| -%> +<% if has_string -%> + <%= c.name %>(WalletCore.Instance.<%= entity.name %>.<%= KotlinHelper.fix_name(WasmCppHelper.format_name(c.name)) %>, <%= c.string %>), +<% else -%> + <%= c.name %>(WalletCore.Instance.<%= entity.name %>.<%= KotlinHelper.fix_name(WasmCppHelper.format_name(c.name)) %>), +<% end -%> +<% end -%> + ; + + actual val value: UInt + get() = jsValue.value.toInt().toUInt() +<%# Property declarations -%> +<%- entity.properties.each do |property| -%> + + actual val <%= KotlinHelper.format_name(property.name) %><%= KotlinHelper.return_type(property.return_type) %> + get() = <%= KotlinHelper.convert_calling_return_type_js(property.return_type, "WalletCore.Instance.#{entity.name}Ext.#{WasmCppHelper.function_name(entity: entity, function: property)}(jsValue)") %> +<%- end -%> +<%# Method declarations -%> +<%- entity.methods.each do |method| -%> +<%- next if method.name.start_with?('Delete') -%> + + actual fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters.drop(1)) %>)<%= KotlinHelper.return_type(method.return_type) %> = + <%= KotlinHelper.convert_calling_return_type_js(method.return_type, "WalletCore.Instance.#{entity.name}Ext.#{WasmCppHelper.function_name(entity: entity, function: method)}(jsValue#{', ' if not method.parameters.one?}#{KotlinHelper.calling_parameters_js(method.parameters.drop(1))})") %> +<%- end -%> +<%# Value -%> +<% if entity.cases.any? { |e| !e.value.nil? } -%> + + actual companion object { + actual fun fromValue(value: UInt): <%= entity.name %>? = + values().first { it.value == value } + + fun fromValue(jsValue: Js<%= entity.name %>): <%= entity.name %> = + values().first { it.jsValue.value == jsValue.value } + } +<% end -%> +} diff --git a/codegen/lib/templates/kotlin/js_struct.erb b/codegen/lib/templates/kotlin/js_struct.erb new file mode 100644 index 00000000000..80fd05feb72 --- /dev/null +++ b/codegen/lib/templates/kotlin/js_struct.erb @@ -0,0 +1,16 @@ +<%= render('kotlin/package.erb') %> + +actual object <%= entity.name %> { +<%# Static property declarations -%> +<% entity.static_properties.each do |property| -%> + actual val <%= KotlinHelper.format_name(property.name) %><%= KotlinHelper.return_type(property.return_type) %> + get() = TODO() +<% end -%> +<%# Static method declarations -%> +<% entity.static_methods.each do |method| -%> +<% next if method.name.start_with?('Create') -%> + + actual fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters) %>)<%= KotlinHelper.return_type(method.return_type) %> = + <%= KotlinHelper.convert_calling_return_type_js(method.return_type, "WalletCore.Instance.#{entity.name}.#{WasmCppHelper.function_name(entity: entity, function: method)}(#{KotlinHelper.calling_parameters_js(method.parameters)})") %> +<% end -%> +} diff --git a/codegen/lib/templates/kotlin/package.erb b/codegen/lib/templates/kotlin/package.erb new file mode 100644 index 00000000000..64142a21c30 --- /dev/null +++ b/codegen/lib/templates/kotlin/package.erb @@ -0,0 +1 @@ +package com.trustwallet.core \ No newline at end of file diff --git a/codegen/lib/templates/kotlin_android.erb b/codegen/lib/templates/kotlin_android.erb new file mode 100644 index 00000000000..a9767e71d97 --- /dev/null +++ b/codegen/lib/templates/kotlin_android.erb @@ -0,0 +1,7 @@ +<%- if entity.is_a?(EnumDecl) -%> +<%= render('kotlin/android_enum.erb') -%> +<%- elsif entity.is_struct -%> +<%= render('kotlin/android_struct.erb') -%> +<%- else -%> +<%= render('kotlin/android_class.erb') -%> +<%- end -%> diff --git a/codegen/lib/templates/kotlin_common.erb b/codegen/lib/templates/kotlin_common.erb new file mode 100644 index 00000000000..31db319babd --- /dev/null +++ b/codegen/lib/templates/kotlin_common.erb @@ -0,0 +1,7 @@ +<%- if entity.is_a?(EnumDecl) -%> +<%= render('kotlin/common_enum.erb') -%> +<%- elsif entity.is_struct -%> +<%= render('kotlin/common_struct.erb') -%> +<%- else -%> +<%= render('kotlin/common_class.erb') -%> +<%- end -%> diff --git a/codegen/lib/templates/kotlin_ios.erb b/codegen/lib/templates/kotlin_ios.erb new file mode 100644 index 00000000000..272ad2cf1df --- /dev/null +++ b/codegen/lib/templates/kotlin_ios.erb @@ -0,0 +1,7 @@ +<%- if entity.is_a?(EnumDecl) -%> +<%= render('kotlin/ios_enum.erb') -%> +<%- elsif entity.is_struct -%> +<%= render('kotlin/ios_struct.erb') -%> +<%- else -%> +<%= render('kotlin/ios_class.erb') -%> +<%- end -%> diff --git a/codegen/lib/templates/kotlin_jni/class_access.erb b/codegen/lib/templates/kotlin_jni/class_access.erb new file mode 100644 index 00000000000..a260b8cfd35 --- /dev/null +++ b/codegen/lib/templates/kotlin_jni/class_access.erb @@ -0,0 +1,6 @@ +<% param = locals[:param] -%> +<% name = param.name -%> +<% type = param.type -%> + jclass <%= name %>Class = (*env)->GetObjectClass(env, <%= name %>); + jfieldID <%= name %>HandleFieldID = (*env)->GetFieldID(env, <%= name %>Class, "nativeHandle", "J"); + struct TW<%= type.name %> *<%= name %>Instance = (struct TW<%= type.name %> *) (*env)->GetLongField(env, <%= name %>, <%= name %>HandleFieldID); diff --git a/codegen/lib/templates/kotlin_jni/compare_to.erb b/codegen/lib/templates/kotlin_jni/compare_to.erb new file mode 100644 index 00000000000..253a8584b6e --- /dev/null +++ b/codegen/lib/templates/kotlin_jni/compare_to.erb @@ -0,0 +1,22 @@ +<% less = locals[:less] -%> +<% equal = locals[:equal] -%> +<% compareMethod = KotlinJniHelper.compareMethod(entity) -%> +<%= render('kotlin_jni/method_prototype.erb', { method: compareMethod }) %> { +<%= render('kotlin_jni/instance_access.erb', { entity: entity }) %> +<%= render('kotlin_jni/parameter_access.erb', { method: compareMethod }) -%> +<% if entity.struct? -%> + jboolean equal = (jboolean) TW<%= entity.name %>Equal(*instance, *otherInstance); +<% else -%> + jboolean equal = (jboolean) TW<%= entity.name %>Equal(instance, otherInstance); +<% end -%> + if (equal) { + return 0; + } +<% if entity.struct? -%> + jboolean less = (jboolean) TW<%= entity.name %>Less(*instance, *otherInstance); +<% else -%> + jboolean less = (jboolean) TW<%= entity.name %>Less(instance, otherInstance); +<% end -%> +<%= render('kotlin_jni/instance_release.erb', { entity: entity }) %> + return less ? -1 : 1; +} diff --git a/codegen/lib/templates/kotlin_jni/enum_access.erb b/codegen/lib/templates/kotlin_jni/enum_access.erb new file mode 100644 index 00000000000..43d612c0fa0 --- /dev/null +++ b/codegen/lib/templates/kotlin_jni/enum_access.erb @@ -0,0 +1,6 @@ +<% param = locals[:param] -%> +<% name = param.name -%> +<% type = param.type -%> + jclass <%= name %>Class = (*env)->GetObjectClass(env, <%= name %>); + jmethodID <%= name %>ValueMethodID = (*env)->GetMethodID(env, <%= name %>Class, "value", "()I"); + jint <%= name %>Value = (*env)->CallIntMethod(env, <%= name %>, <%= name %>ValueMethodID); diff --git a/codegen/lib/templates/kotlin_jni/instance_access.erb b/codegen/lib/templates/kotlin_jni/instance_access.erb new file mode 100644 index 00000000000..5654c472858 --- /dev/null +++ b/codegen/lib/templates/kotlin_jni/instance_access.erb @@ -0,0 +1,14 @@ +<% entity = locals[:entity] -%> + jclass thisClass = (*env)->GetObjectClass(env, thisObject); +<% if entity.struct? -%> + jfieldID bytesFieldID = (*env)->GetFieldID(env, thisClass, "bytes", "[B"); + jbyteArray bytesArray = (*env)->GetObjectField(env, thisObject, bytesFieldID); + jbyte* bytesBuffer = (*env)->GetByteArrayElements(env, bytesArray, NULL); + struct TW<%= entity.name %> *instance = (struct TW<%= entity.name %> *) bytesBuffer; +<% elsif entity.enum? -%> + jfieldID handleFieldID = (*env)->GetFieldID(env, thisClass, "value", "I"); + enum TW<%= entity.name %> instance = (enum TW<%= entity.name %>) (*env)->GetIntField(env, thisObject, handleFieldID); +<% else -%> + jfieldID handleFieldID = (*env)->GetFieldID(env, thisClass, "nativeHandle", "J"); + struct TW<%= entity.name %> *instance = (struct TW<%= entity.name %> *) (*env)->GetLongField(env, thisObject, handleFieldID); +<% end -%> diff --git a/codegen/lib/templates/kotlin_jni/instance_release.erb b/codegen/lib/templates/kotlin_jni/instance_release.erb new file mode 100644 index 00000000000..1231d03119b --- /dev/null +++ b/codegen/lib/templates/kotlin_jni/instance_release.erb @@ -0,0 +1,6 @@ +<% entity = locals[:entity] -%> +<% if entity.struct? -%> + (*env)->ReleaseByteArrayElements(env, bytesArray, bytesBuffer, JNI_ABORT); + (*env)->DeleteLocalRef(env, bytesArray); +<% end -%> + (*env)->DeleteLocalRef(env, thisClass); \ No newline at end of file diff --git a/codegen/lib/templates/kotlin_jni/method.erb b/codegen/lib/templates/kotlin_jni/method.erb new file mode 100644 index 00000000000..f41b05435a8 --- /dev/null +++ b/codegen/lib/templates/kotlin_jni/method.erb @@ -0,0 +1,8 @@ +<% method = locals[:method] -%> +<%= render('kotlin_jni/method_prototype.erb', { method: method }) %> { +<% if !method.static -%> +<%= render('kotlin_jni/instance_access.erb', { entity: entity }) %> +<% end -%> +<%= render('kotlin_jni/parameter_access.erb', { method: method }) -%> +<%= render('kotlin_jni/method_forward.erb', { method: method }) -%> +} diff --git a/codegen/lib/templates/kotlin_jni/method_call.erb b/codegen/lib/templates/kotlin_jni/method_call.erb new file mode 100644 index 00000000000..68dfee4205a --- /dev/null +++ b/codegen/lib/templates/kotlin_jni/method_call.erb @@ -0,0 +1,6 @@ +<% + method = locals[:method] + instance = (method.entity.struct? ? '*' : '') + 'instance' + arguments = locals[:arguments] || [instance] + KotlinJniHelper.arguments(method.parameters.drop(1)) +-%> +TW<%= entity.name %><%= method.name %>(<%= arguments.join(', ') %>) \ No newline at end of file diff --git a/codegen/lib/templates/kotlin_jni/method_forward.erb b/codegen/lib/templates/kotlin_jni/method_forward.erb new file mode 100644 index 00000000000..3673db58a48 --- /dev/null +++ b/codegen/lib/templates/kotlin_jni/method_forward.erb @@ -0,0 +1,118 @@ +<% + method = locals[:method] + if method.static + arguments = locals[:arguments] || KotlinJniHelper.arguments(method.parameters) + call = render('kotlin_jni/method_call.erb', { method: method, arguments: arguments }) + else + instance = (method.entity.struct? ? '*' : '') + 'instance' + arguments = locals[:arguments] || [instance] + KotlinJniHelper.arguments(method.parameters.drop(1)) + call = render('kotlin_jni/method_call.erb', { method: method, arguments: arguments }) + end + + # Method returns data + if should_return_data(method) -%> + <%= KotlinJniHelper.type(method.return_type) %> result = NULL; + TWData *resultData = <%= call %>; +<% if method.return_type.is_nullable %> + if (resultData == NULL) { + goto cleanup; + } +<% end -%> + result = TWDataJByteArray(resultData, env); +<% if method.return_type.is_nullable %> +cleanup: +<% end -%> +<%= render('kotlin_jni/parameter_release.erb', { method: method }) -%> +<% if !method.static %> +<%= render('kotlin_jni/instance_release.erb', { entity: entity }) %> +<% end -%> + return result; +<% + # Method returns a string + elsif should_return_string(method) -%> + jstring result = NULL; + TWString *resultString = <%= call %>; +<% if method.return_type.is_nullable %> + if (resultString == NULL) { + goto cleanup; + } +<% end -%> + result = TWStringJString(resultString, env); +<% if method.return_type.is_nullable %> +cleanup: +<% end -%> +<%= render('kotlin_jni/parameter_release.erb', { method: method }) -%> +<% if !method.static %> +<%= render('kotlin_jni/instance_release.erb', { entity: entity }) %> +<% end -%> + return result; +<% + # Method returns proto + elsif method.return_type.is_proto -%> + jbyteArray resultData = TWDataJByteArray(<%= call %>, env); + jclass resultClass = (*env)->FindClass(env, "<%= KotlinJniHelper.proto_to_class(method.return_type.name) %>"); + jmethodID parseFromMethodID = (*env)->GetStaticMethodID(env, resultClass, "parseFrom", "([B)L<%= KotlinJniHelper.proto_to_class(method.return_type.name) %>;"); + jobject result = (*env)->CallStaticObjectMethod(env, resultClass, parseFromMethodID, resultData); + + (*env)->DeleteLocalRef(env, resultClass); +<%= render('kotlin_jni/parameter_release.erb', { method: method }) -%> +<% if !method.static %> +<%= render('kotlin_jni/instance_release.erb', { entity: entity }) %> +<% end -%> + + return result; +<% + # Method returns an object + elsif method.return_type.is_struct || method.return_type.is_class || method.return_type.is_enum + if method.return_type.is_struct -%> + struct TW<%= method.return_type.name %> result = <%= call %>; +<% elsif method.return_type.is_class -%> + struct TW<%= method.return_type.name %> *result = <%= call %>; +<% elsif method.return_type.is_enum -%> + enum TW<%= method.return_type.name %> result = <%= call %>; +<% else -%> + TW<%= method.return_type.name %> *result = <%= call %>; +<% end -%> + +<%= render('kotlin_jni/parameter_release.erb', { method: method }) -%> +<% if !method.static %> +<%= render('kotlin_jni/instance_release.erb', { entity: entity }) %> +<% end -%> + + jclass class = (*env)->FindClass(env, "com/trustwallet/core/<%= method.return_type.name %>"); +<% if method.return_type.is_struct -%> + jbyteArray resultArray = (*env)->NewByteArray(env, sizeof(struct TW<%= method.return_type.name %>)); + (*env)->SetByteArrayRegion(env, resultArray, 0, sizeof(struct TW<%= method.return_type.name %>), (jbyte *) &result); + jmethodID method = (*env)->GetStaticMethodID(env, class, "createFromNative", "([B)Lcom/trustwallet/core/<%= method.return_type.name %>;"); + return (*env)->CallStaticObjectMethod(env, class, method, resultArray); +<% elsif method.return_type.is_enum -%> + jmethodID method = (*env)->GetStaticMethodID(env, class, "createFromValue", "(I)Lcom/trustwallet/core/<%= method.return_type.name %>;"); + return (*env)->CallStaticObjectMethod(env, class, method, (jint) result); +<% else -%> + if (result == NULL) { + return NULL; + } + jmethodID method = (*env)->GetStaticMethodID(env, class, "createFromNative", "(J)Lcom/trustwallet/core/<%= method.return_type.name %>;"); + return (*env)->CallStaticObjectMethod(env, class, method, (jlong) result); +<% end + + # Method returns void + elsif method.return_type.name == :void -%> + <%= call %>; + +<%= render('kotlin_jni/parameter_release.erb', { method: method }) -%> +<% if !method.static %> +<%= render('kotlin_jni/instance_release.erb', { entity: entity }) %> +<% end + + # Method returns a primitive + else -%> + <%= KotlinJniHelper.type(method.return_type) %> resultValue = (<%= KotlinJniHelper.type(method.return_type) %>) <%= call %>; + +<%= render('kotlin_jni/parameter_release.erb', { method: method }) -%> +<% if !method.static %> +<%= render('kotlin_jni/instance_release.erb', { entity: entity }) %> +<% end -%> + + return resultValue; +<%end -%> \ No newline at end of file diff --git a/codegen/lib/templates/kotlin_jni/method_prototype.erb b/codegen/lib/templates/kotlin_jni/method_prototype.erb new file mode 100644 index 00000000000..a971ce95bd5 --- /dev/null +++ b/codegen/lib/templates/kotlin_jni/method_prototype.erb @@ -0,0 +1,9 @@ +<% + method = locals[:method] + if method.static + parameters = 'jclass thisClass' + KotlinJniHelper.parameters(method.parameters) + else + parameters = 'jobject thisObject' + KotlinJniHelper.parameters(method.parameters.drop(1)) + end +-%> +<%= KotlinJniHelper.type(method.return_type) %> JNICALL <%= KotlinJniHelper.function_name(entity: entity, function: method) %>(JNIEnv *env, <%= parameters %>)<% -%> diff --git a/codegen/lib/templates/kotlin_jni/parameter_access.erb b/codegen/lib/templates/kotlin_jni/parameter_access.erb new file mode 100644 index 00000000000..a5dcae13aa1 --- /dev/null +++ b/codegen/lib/templates/kotlin_jni/parameter_access.erb @@ -0,0 +1,23 @@ +<% + method = locals[:method] + if method.static && !method.name.start_with?('Init') + parameters = method.parameters + else + parameters = method.parameters.drop(1) + end + + parameters.each do |param| + if param.type.name == :data -%> + TWData *<%= param.name %>Data = TWDataCreateWithJByteArray(env, <%= param.name %>); +<% elsif param.type.name == :string -%> + TWString *<%= param.name %>String = TWStringCreateWithJString(env, <%= param.name %>); +<% elsif param.type.is_struct -%> +<%= render('kotlin_jni/struct_access.erb', { param: param }) -%> +<% elsif param.type.is_class -%> +<%= render('kotlin_jni/class_access.erb', { param: param }) -%> +<% elsif param.type.is_enum -%> +<%= render('kotlin_jni/enum_access.erb', { param: param }) -%> +<% elsif param.type.is_proto -%> +<%= render('kotlin_jni/proto_access.erb', { param: param }) -%> +<% end -%> +<%end -%> diff --git a/codegen/lib/templates/kotlin_jni/parameter_release.erb b/codegen/lib/templates/kotlin_jni/parameter_release.erb new file mode 100644 index 00000000000..1db6f1a86e1 --- /dev/null +++ b/codegen/lib/templates/kotlin_jni/parameter_release.erb @@ -0,0 +1,26 @@ +<% + method = locals[:method] + if method.static && !method.name.start_with?('Init') + parameters = method.parameters + else + parameters = method.parameters.drop(1) + end + + parameters.each do |param| + if param.type.name == :data -%> + TWDataDelete(<%= param.name %>Data); +<% elsif param.type.name == :string -%> + TWStringDelete(<%= param.name %>String); +<% elsif param.type.is_struct -%> + (*env)->ReleaseByteArrayElements(env, <%= param.name %>BytesArray, <%= param.name %>BytesBuffer, JNI_ABORT); + (*env)->DeleteLocalRef(env, <%= param.name %>BytesArray); + (*env)->DeleteLocalRef(env, <%= param.name %>Class); +<% elsif param.type.is_class -%> + (*env)->DeleteLocalRef(env, <%= param.name %>Class); +<% elsif param.type.is_enum -%> + (*env)->DeleteLocalRef(env, <%= param.name %>Class); +<% elsif param.type.is_proto -%> + (*env)->DeleteLocalRef(env, <%= param.name %>ByteArray); + (*env)->DeleteLocalRef(env, <%= param.name %>Class); +<% end -%> +<%end -%> diff --git a/codegen/lib/templates/kotlin_jni/proto_access.erb b/codegen/lib/templates/kotlin_jni/proto_access.erb new file mode 100644 index 00000000000..36d930a73cd --- /dev/null +++ b/codegen/lib/templates/kotlin_jni/proto_access.erb @@ -0,0 +1,7 @@ +<% param = locals[:param] -%> +<% name = param.name -%> +<% type = param.type -%> + jclass <%= name %>Class = (*env)->GetObjectClass(env, <%= name %>); + jmethodID <%= name %>ToByteArrayMethodID = (*env)->GetMethodID(env, <%= name %>Class, "toByteArray", "()[B"); + jbyteArray <%= name %>ByteArray = (*env)->CallObjectMethod(env, <%= name %>, <%= name %>ToByteArrayMethodID); + TWData *<%= param.name %>Data = TWDataCreateWithJByteArray(env, <%= name %>ByteArray); diff --git a/codegen/lib/templates/kotlin_jni/struct_access.erb b/codegen/lib/templates/kotlin_jni/struct_access.erb new file mode 100644 index 00000000000..ed8c88c027b --- /dev/null +++ b/codegen/lib/templates/kotlin_jni/struct_access.erb @@ -0,0 +1,8 @@ +<% param = locals[:param] -%> +<% name = param.name -%> +<% type = param.type -%> + jclass <%= name %>Class = (*env)->GetObjectClass(env, <%= name %>); + jfieldID <%= name %>BytesFieldID = (*env)->GetFieldID(env, <%= name %>Class, "bytes", "[B"); + jbyteArray <%= name %>BytesArray = (*env)->GetObjectField(env, <%= name %>, <%= name %>BytesFieldID); + jbyte* <%= name %>BytesBuffer = (*env)->GetByteArrayElements(env, <%= name %>BytesArray, NULL); + struct TW<%= type.name %> *<%= name %>Instance = (struct TW<%= type.name %> *) <%= name %>BytesBuffer; diff --git a/codegen/lib/templates/kotlin_jni_c.erb b/codegen/lib/templates/kotlin_jni_c.erb new file mode 100644 index 00000000000..3b1f05b0588 --- /dev/null +++ b/codegen/lib/templates/kotlin_jni_c.erb @@ -0,0 +1,90 @@ +#include +#include +#include + +<% require 'set' -%> +<% includes = Set.new([entity.name]) -%> +<% entity.static_methods.each do |method| -%> +<% includes << method.return_type.name if method.return_type.is_struct || method.return_type.is_class -%> +<% method.parameters.each do |param| -%> +<% includes << param.type.name if param.type.is_struct || param.type.is_class -%> +<% end -%> +<% end -%> +<% includes.each do |include| -%> +#include .h> +<% end -%> + +#include "TWJNI.h" +#include "<%= entity.name %>.h" + +<%# Constructors -%> +<% entity.static_methods.each do |method| -%> +<% next unless method.name.start_with?('Create') -%> +jlong JNICALL <%= KotlinJniHelper.function_name(entity: entity, function: method, native_prefix: true) %>(JNIEnv *env, jclass thisClass<%= KotlinJniHelper.parameters(method.parameters) %>) { +<%= render('kotlin_jni/parameter_access.erb', { method: method }) -%> + struct TW<%= entity.name %> *instance = TW<%= entity.name %><%= method.name %>(<%= KotlinJniHelper.arguments(method.parameters).join(', ') %>); +<%= render('kotlin_jni/parameter_release.erb', { method: method }) -%> + return (jlong) instance; +} + +<% end -%> +<%# Destructors -%> +<% entity.methods.each do |method| -%> +<% next unless method.name.start_with?('Delete') -%> +void JNICALL <%= KotlinJniHelper.function_name(entity: entity, function: method, native_prefix: true) %>(JNIEnv *env, jclass thisClass, jlong handle) { + TW<%= entity.name %>Delete((struct TW<%= entity.name %> *) handle); +} + +<% end -%> +<%# Initializers -%> +<% entity.static_methods.each do |method| -%> +<% next unless method.name.start_with?('Init') -%> +jbyteArray JNICALL <%= KotlinJniHelper.function_name(entity: entity, function: method) %>(JNIEnv *env, jclass thisClass<%= KotlinJniHelper.parameters(method.parameters.drop(1)) %>) { + jbyteArray array = (*env)->NewByteArray(env, sizeof(struct TW<%= entity.name %>)); + jbyte* bytesBuffer = (*env)->GetByteArrayElements(env, array, NULL); + struct TW<%= entity.name %> *instance = (struct TW<%= entity.name %> *) bytesBuffer; +<%= render('kotlin_jni/parameter_access.erb', { method: method }) -%> +<% if method.return_type.name != :void -%> + <%= KotlinJniHelper.type(method.return_type) %> result = (<%= KotlinJniHelper.type(method.return_type) %>) TW<%= entity.name %><%= method.name %>(instance, <%= KotlinJniHelper.arguments(method.parameters.drop(1)).join(', ') %>); +<% else -%> + TW<%= entity.name %><%= method.name %>(instance, <%= KotlinJniHelper.arguments(method.parameters.drop(1)).join(', ') %>); +<% end -%> +<%= render('kotlin_jni/parameter_release.erb', { method: method }) -%> + (*env)->ReleaseByteArrayElements(env, array, bytesBuffer, 0); + +<% if method.return_type.name != :void -%> + if (result) { + return array; + } else { + (*env)->DeleteLocalRef(env, array); + return NULL; + } +<% else -%> + return array; +<% end -%> +} + +<% end -%> +<%# Static properties -%> +<% entity.static_properties.each do |method| -%> +<%= render('kotlin_jni/method.erb', { method: method }) %> +<% end -%> +<%# Static methods -%> +<% entity.static_methods.each do |method| -%> +<% next if method.name.start_with?('Create') || method.name.start_with?('Init') -%> +<%= render('kotlin_jni/method.erb', { method: method }) %> +<% end -%> +<%# Properties -%> +<% entity.properties.each do |method| -%> +<%= render('kotlin_jni/method.erb', { method: method }) %> +<% end -%> +<%# Methods -%> +<% entity.methods.each do |method| -%> +<% next if method.name == "Delete" -%> +<%= render('kotlin_jni/method.erb', { method: method }) %> +<% end -%> +<% less = entity.static_methods.detect{ |i| i.name == 'Less' } -%> +<% equal = entity.static_methods.detect{ |i| i.name == 'Equal' } -%> +<% if !less.nil? && !equal.nil? -%> +<%= render('kotlin_jni/compare_to.erb', { less: less, equal: equal }) %> +<% end -%> diff --git a/codegen/lib/templates/kotlin_jni_h.erb b/codegen/lib/templates/kotlin_jni_h.erb new file mode 100644 index 00000000000..44e3bec9cf8 --- /dev/null +++ b/codegen/lib/templates/kotlin_jni_h.erb @@ -0,0 +1,66 @@ +#ifndef JNI_TW_<%= entity.name.upcase %>_H +#define JNI_TW_<%= entity.name.upcase %>_H + +#include +#include + +TW_EXTERN_C_BEGIN + +<%# Constructor declarations -%> +<% entity.static_methods.each do |method| -%> +<% next unless method.name.start_with?('Create') -%> +JNIEXPORT +jlong JNICALL <%= KotlinJniHelper.function_name(entity: entity, function: method, native_prefix: true) %>(JNIEnv *env, jclass thisClass<%= KotlinJniHelper.parameters(method.parameters) %>); + +<% end -%> +<%# Destructor declarations -%> +<% entity.methods.each do |method| -%> +<% next unless method.name.start_with?('Delete') -%> +JNIEXPORT +void JNICALL <%= KotlinJniHelper.function_name(entity: entity, function: method, native_prefix: true) %>(JNIEnv *env, jclass thisClass, jlong handle); + +<% end -%> +<%# Initializer declarations -%> +<% entity.static_methods.each do |method| -%> +<% next unless method.name.start_with?('Init') -%> +JNIEXPORT +jbyteArray JNICALL <%= KotlinJniHelper.function_name(entity: entity, function: method) %>(JNIEnv *env, jclass thisClass<%= KotlinJniHelper.parameters(method.parameters.drop(1)) %>); + +<% end -%> +<%# Static property declarations -%> +<% entity.static_properties.each do |property| -%> +JNIEXPORT +<%= render('kotlin_jni/method_prototype.erb', { method: property }) %>; + +<% end -%> +<%# Static method declarations -%> +<% entity.static_methods.each do |method| -%> +<% next if method.name.start_with?('Create') || method.name.start_with?('Init') -%> +JNIEXPORT +<%= render('kotlin_jni/method_prototype.erb', { method: method }) %>; + +<% end -%> +<%# Property declarations -%> +<% entity.properties.each do |property| -%> +JNIEXPORT +<%= render('kotlin_jni/method_prototype.erb', { method: property }) %>; + +<% end -%> +<%# Method declarations -%> +<% entity.methods.each do |method| -%> +<% next if method.name.start_with?('Delete') -%> +JNIEXPORT +<%= render('kotlin_jni/method_prototype.erb', { method: method }) %>; + +<% end -%> +<% less = entity.static_methods.detect{ |i| i.name == 'Less' } -%> +<% equal = entity.static_methods.detect{ |i| i.name == 'Equal' } -%> +<% if !less.nil? && !equal.nil? -%> +JNIEXPORT +<%= render('kotlin_jni/method_prototype.erb', { method: KotlinJniHelper.compareMethod(entity) }) %>; + +<% end -%> + +TW_EXTERN_C_END + +#endif // JNI_TW_<%= entity.name.upcase %>_H diff --git a/codegen/lib/templates/kotlin_js.erb b/codegen/lib/templates/kotlin_js.erb new file mode 100644 index 00000000000..db9c23dd567 --- /dev/null +++ b/codegen/lib/templates/kotlin_js.erb @@ -0,0 +1,7 @@ +<%- if entity.is_a?(EnumDecl) -%> +<%= render('kotlin/js_enum.erb') -%> +<%- elsif entity.is_struct -%> +<%= render('kotlin/js_struct.erb') -%> +<%- else -%> +<%= render('kotlin/js_class.erb') -%> +<%- end -%> diff --git a/codegen/lib/templates/kotlin_js_accessors.erb b/codegen/lib/templates/kotlin_js_accessors.erb new file mode 100644 index 00000000000..63dda74b3b2 --- /dev/null +++ b/codegen/lib/templates/kotlin_js_accessors.erb @@ -0,0 +1,7 @@ +<%- if entity.is_a?(EnumDecl) -%> +<%= render('kotlin/js_accessors_enum.erb') -%> +<%- elsif entity.is_struct -%> +<%= render('kotlin/js_accessors_struct.erb') -%> +<%- else -%> +<%= render('kotlin/js_accessors_class.erb') -%> +<%- end -%> diff --git a/codegen/lib/templates/newcoin/Address.cpp.erb b/codegen/lib/templates/newcoin/Address.cpp.erb index 0f15d92edcc..ea1c354db0e 100644 --- a/codegen/lib/templates/newcoin/Address.cpp.erb +++ b/codegen/lib/templates/newcoin/Address.cpp.erb @@ -1,12 +1,10 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" -using namespace TW::<%= format_name(coin) %>; +namespace TW::<%= format_name(coin) %> { bool Address::isValid(const std::string& string) { // TODO: Finalize implementation @@ -29,3 +27,5 @@ std::string Address::string() const { // TODO: Finalize implementation return "TODO"; } + +} // namespace TW::<%= format_name(coin) %> diff --git a/codegen/lib/templates/newcoin/Address.h.erb b/codegen/lib/templates/newcoin/Address.h.erb index 8ba3008d088..7fe392b92df 100644 --- a/codegen/lib/templates/newcoin/Address.h.erb +++ b/codegen/lib/templates/newcoin/Address.h.erb @@ -1,13 +1,11 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" -#include "../PublicKey.h" +#include "Data.h" +#include "PublicKey.h" #include diff --git a/codegen/lib/templates/newcoin/AddressTests.cpp.erb b/codegen/lib/templates/newcoin/AddressTests.cpp.erb index 9953e01fbdc..27c4c09ab89 100644 --- a/codegen/lib/templates/newcoin/AddressTests.cpp.erb +++ b/codegen/lib/templates/newcoin/AddressTests.cpp.erb @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "HexCoding.h" #include "<%= format_name(coin) %>/Address.h" @@ -11,8 +9,7 @@ #include #include -using namespace TW; -using namespace TW::<%= format_name(coin) %>; +namespace TW::<%= format_name(coin) %>::tests { TEST(<%= format_name(coin) %>Address, Valid) { ASSERT_TRUE(Address::isValid("__ADD_VALID_ADDRESS_HERE__")); @@ -46,3 +43,5 @@ TEST(<%= format_name(coin) %>Address, FromString) { auto address = Address("__ADD_VALID_ADDRESS_HERE__"); ASSERT_EQ(address.string(), "__ADD_SAME_VALID_ADDRESS_HERE__"); } + +} // namespace TW::<%= format_name(coin) %>::tests diff --git a/codegen/lib/templates/newcoin/AddressTests.kt.erb b/codegen/lib/templates/newcoin/AddressTests.kt.erb index f118db94a20..20bbd774cc9 100644 --- a/codegen/lib/templates/newcoin/AddressTests.kt.erb +++ b/codegen/lib/templates/newcoin/AddressTests.kt.erb @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.<%= format_name_lowercase(coin) %> diff --git a/codegen/lib/templates/newcoin/Entry.cpp.erb b/codegen/lib/templates/newcoin/Entry.cpp.erb index 348e884d9f0..793f5bf8f3f 100644 --- a/codegen/lib/templates/newcoin/Entry.cpp.erb +++ b/codegen/lib/templates/newcoin/Entry.cpp.erb @@ -1,27 +1,34 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" #include "Signer.h" -using namespace TW::<%= format_name(coin) %>; -using namespace std; +namespace TW::<%= format_name(coin) %> { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +TW::Data Entry::preImageHashes(TWCoinType coin, const Data& txInputData) const { + return TW::Data(); +} + +void Entry::compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + +} + +} // namespace TW::<%= format_name(coin) %> diff --git a/codegen/lib/templates/newcoin/Entry.h.erb b/codegen/lib/templates/newcoin/Entry.h.erb index b5105bac6ae..32b48e0e42c 100644 --- a/codegen/lib/templates/newcoin/Entry.h.erb +++ b/codegen/lib/templates/newcoin/Entry.h.erb @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,13 +10,16 @@ namespace TW::<%= format_name(coin) %> { /// Entry point for implementation of <%= format_name(coin) %> coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; // normalizeAddress(): implement this if needed, e.g. Ethereum address is EIP55 checksummed // plan(): implement this if the blockchain is UTXO based + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::<%= format_name(coin) %> diff --git a/codegen/lib/templates/newcoin/Proto.erb b/codegen/lib/templates/newcoin/Proto.erb index d7bdb047f8e..bbd242d66be 100644 --- a/codegen/lib/templates/newcoin/Proto.erb +++ b/codegen/lib/templates/newcoin/Proto.erb @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. syntax = "proto3"; diff --git a/codegen/lib/templates/newcoin/Signer.cpp.erb b/codegen/lib/templates/newcoin/Signer.cpp.erb index 381832fb176..aeedceda863 100644 --- a/codegen/lib/templates/newcoin/Signer.cpp.erb +++ b/codegen/lib/templates/newcoin/Signer.cpp.erb @@ -1,16 +1,12 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "Address.h" #include "../PublicKey.h" -using namespace TW; -using namespace TW::<%= name %>; - +namespace TW::<%= name %> { Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { // TODO: Check and finalize implementation @@ -24,3 +20,5 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { protoOutput.set_encoded(encoded.data(), encoded.size()); return protoOutput; } + +} // namespace TW::<%= name %> diff --git a/codegen/lib/templates/newcoin/Signer.h.erb b/codegen/lib/templates/newcoin/Signer.h.erb index 7b04793f3c8..f77982f3ac3 100644 --- a/codegen/lib/templates/newcoin/Signer.h.erb +++ b/codegen/lib/templates/newcoin/Signer.h.erb @@ -1,12 +1,10 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/<%= name %>.pb.h" @@ -19,7 +17,10 @@ public: Signer() = delete; /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + static Proto::SigningOutput sign(const Proto::SigningInput& input, std::optional optionalExternalSigs = {}) noexcept; + + /// Collect pre-image hashes to be signed + static PreSigningOutput preImageHashes(const SigningInput& input) noexcept; }; } // namespace TW::<%= name %> diff --git a/codegen/lib/templates/newcoin/SignerTests.cpp.erb b/codegen/lib/templates/newcoin/SignerTests.cpp.erb index 27eb5ef11ee..d88ad46994f 100644 --- a/codegen/lib/templates/newcoin/SignerTests.cpp.erb +++ b/codegen/lib/templates/newcoin/SignerTests.cpp.erb @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "<%= format_name(coin) %>/Signer.h" #include "<%= format_name(coin) %>/Address.h" @@ -12,8 +10,7 @@ #include -using namespace TW; -using namespace TW::<%= format_name(coin) %>; +namespace TW::<%= format_name(coin) %>::tests { // TODO: Add tests @@ -32,3 +29,5 @@ TEST(<%= format_name(coin) %>Signer, Sign) { //ASSERT_EQ(hex(serialized), "__RESULT__"); //ASSERT_EQ(...) } + +} // namespace TW::<%= format_name(coin) %>::tests diff --git a/codegen/lib/templates/newcoin/SignerTests.kt.erb b/codegen/lib/templates/newcoin/SignerTests.kt.erb index ba0bbafcfd9..4add9add89f 100644 --- a/codegen/lib/templates/newcoin/SignerTests.kt.erb +++ b/codegen/lib/templates/newcoin/SignerTests.kt.erb @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package com.trustwallet.core.app.blockchains.<%= format_name_lowercase(coin) %> diff --git a/codegen/lib/templates/newcoin/TWAddressTests.cpp.erb b/codegen/lib/templates/newcoin/TWAddressTests.cpp.erb index 11331829f38..91a0cbf0729 100644 --- a/codegen/lib/templates/newcoin/TWAddressTests.cpp.erb +++ b/codegen/lib/templates/newcoin/TWAddressTests.cpp.erb @@ -1,13 +1,11 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; diff --git a/codegen/lib/templates/newcoin/TWSignerTests.cpp.erb b/codegen/lib/templates/newcoin/TWSignerTests.cpp.erb index a59a2fdc772..8e8193db07c 100644 --- a/codegen/lib/templates/newcoin/TWSignerTests.cpp.erb +++ b/codegen/lib/templates/newcoin/TWSignerTests.cpp.erb @@ -1,13 +1,11 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; diff --git a/codegen/lib/templates/newcoin/Tests.swift.erb b/codegen/lib/templates/newcoin/Tests.swift.erb index 58851cb7024..e28d0ec1bc2 100644 --- a/codegen/lib/templates/newcoin/Tests.swift.erb +++ b/codegen/lib/templates/newcoin/Tests.swift.erb @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/codegen/lib/templates/newcoin/TransactionCompilerTests.cpp.erb b/codegen/lib/templates/newcoin/TransactionCompilerTests.cpp.erb new file mode 100644 index 00000000000..c996dd2c6ab --- /dev/null +++ b/codegen/lib/templates/newcoin/TransactionCompilerTests.cpp.erb @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "<%= format_name(coin) %>/Signer.h" +#include "<%= format_name(coin) %>/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TestUtilities.h" +#include "TransactionCompiler.h" + +#include "proto/<%= format_name(coin) %>.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include + +#include + +using namespace TW; + +namespace TW::<%= format_name(coin) %> { + +TEST(<%= format_name(coin) %>Compiler, CompileWithSignatures) { + // TODO: Finalize test implementation +} + +} // namespace TW::<%= format_name(coin) %> diff --git a/codegen/lib/templates/swift/TrustWalletCore.h.erb b/codegen/lib/templates/swift/TrustWalletCore.h.erb index 085c7e80064..5e2ac88537a 100644 --- a/codegen/lib/templates/swift/TrustWalletCore.h.erb +++ b/codegen/lib/templates/swift/TrustWalletCore.h.erb @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #import diff --git a/codegen/lib/templates/swift/class.erb b/codegen/lib/templates/swift/class.erb index 93b375de287..3404e02778d 100644 --- a/codegen/lib/templates/swift/class.erb +++ b/codegen/lib/templates/swift/class.erb @@ -1,11 +1,11 @@ import Foundation <% protocols = SwiftHelper.protocol(entity) -%> - <%= entity.comment %> +<%= entity.comment %> public final class <%= entity.name %><% unless protocols.empty? %>: <%= protocols.join(', ') %><% end %> { <%# Static properties -%> <% entity.static_properties.each do |property| -%> - <%= property.comment %> +<%= property.comment_with_indent %> public static var <%= SwiftHelper.format_name(property.name) %>: <%= SwiftHelper.type(property.return_type) %> { <%- if property.return_type.is_class || property.return_type.is_struct -%> return <%= SwiftHelper.type(property.return_type) %>(rawValue: TW<%= entity.name %><%= property.name %>()) diff --git a/codegen/lib/templates/swift/class_properties.erb b/codegen/lib/templates/swift/class_properties.erb index f68ff3fd0fa..25c9a7c4c9d 100644 --- a/codegen/lib/templates/swift/class_properties.erb +++ b/codegen/lib/templates/swift/class_properties.erb @@ -1,6 +1,6 @@ <%# Properties -%> <%- entity.properties.each do |property| -%> - <%= property.comment %> +<%= property.comment_with_indent %> public var <%= SwiftHelper.format_name(property.name) %>: <%= SwiftHelper.type(property.return_type) %> { <%= render('swift/method_forward.erb', { method: property }) -%> } diff --git a/codegen/lib/templates/swift/enum.erb b/codegen/lib/templates/swift/enum.erb index 9b9f9ccdaf3..6c1165edab5 100644 --- a/codegen/lib/templates/swift/enum.erb +++ b/codegen/lib/templates/swift/enum.erb @@ -1,5 +1,5 @@ <% has_string = entity.cases.all? { |c| !c.string.nil? } -%> - <%= entity.comment %> +<%= entity.comment %> <% type = entity.raw_type ? SwiftHelper.type(entity.raw_type) : 'UInt32' -%> public enum <%= entity.name %>: <%= type %>, CaseIterable<% if has_string %>, CustomStringConvertible <% end %> { <%# Cases -%> diff --git a/codegen/lib/templates/swift/enum_extension.erb b/codegen/lib/templates/swift/enum_extension.erb index be672bb0e3b..c5f4f2d3f0c 100644 --- a/codegen/lib/templates/swift/enum_extension.erb +++ b/codegen/lib/templates/swift/enum_extension.erb @@ -1,7 +1,7 @@ extension <%= entity.name %> { <%# Properties -%> <%- entity.properties.each do |property| -%> - <%= property.comment %> +<%= property.comment_with_indent %> public var <%= SwiftHelper.format_name(property.name) %>: <%= SwiftHelper.type(property.return_type) %> { <%= render('swift/method_forward.erb', { method: property, arguments: ["TW#{entity.name}(rawValue: rawValue)"] }) -%> } diff --git a/codegen/lib/templates/swift/method.erb b/codegen/lib/templates/swift/method.erb index 75c63813259..d9700123165 100644 --- a/codegen/lib/templates/swift/method.erb +++ b/codegen/lib/templates/swift/method.erb @@ -1,5 +1,5 @@ <% method = locals[:method] -%> - <%= method.comment %> +<%= method.comment_with_indent %> <% arguments = locals[:arguments] || ['rawValue'] + SwiftHelper.arguments(method.parameters.drop(1)) -%> <% if method.discardable_result -%> @discardableResult diff --git a/codegen/lib/templates/swift/parameter_access.erb b/codegen/lib/templates/swift/parameter_access.erb index 8521c18bb4a..ddf4335753f 100644 --- a/codegen/lib/templates/swift/parameter_access.erb +++ b/codegen/lib/templates/swift/parameter_access.erb @@ -10,12 +10,14 @@ let <%= param.name %>String: UnsafeRawPointer? if let s = <%= param.name %> { <%= param.name %>String = TWStringCreateWithNSString(s) - defer { - TWStringDelete(s) - } } else { <%= param.name %>String = nil } + defer { + if let s = <%= param.name %>String { + TWStringDelete(s) + } + } <% else -%> let <%= param.name %>String = TWStringCreateWithNSString(<%= param.name %>) defer { diff --git a/codegen/lib/templates/swift/static_method.erb b/codegen/lib/templates/swift/static_method.erb index f0e04488d62..8ecd73229be 100644 --- a/codegen/lib/templates/swift/static_method.erb +++ b/codegen/lib/templates/swift/static_method.erb @@ -1,5 +1,5 @@ <% method = locals[:method] -%> - <%= method.comment %> +<%= method.comment_with_indent %> <% arguments = SwiftHelper.arguments(method.parameters) -%> <% if method.discardable_result -%> @discardableResult diff --git a/codegen/lib/templates/swift/struct.erb b/codegen/lib/templates/swift/struct.erb index 27ab3a20462..518588bd0ac 100644 --- a/codegen/lib/templates/swift/struct.erb +++ b/codegen/lib/templates/swift/struct.erb @@ -5,7 +5,7 @@ import Foundation public struct <%= entity.name %><% unless protocols.empty? %>: <%= protocols.join(', ') %><% end %> { <%# Static properties -%> <% entity.static_properties.each do |property| -%> - <%= property.comment %> +<%= property.comment_with_indent %> public static var <%= SwiftHelper.format_name(property.name) %>: <%= SwiftHelper.type(property.return_type) %> { <%- if property.return_type.is_class || property.return_type.is_struct -%> return <%= SwiftHelper.type(property.return_type) %>(rawValue: TW<%= entity.name %><%= property.name %>()) diff --git a/codegen/lib/templates/swift/struct_properties.erb b/codegen/lib/templates/swift/struct_properties.erb index b20f49fc60d..10d896df1e4 100644 --- a/codegen/lib/templates/swift/struct_properties.erb +++ b/codegen/lib/templates/swift/struct_properties.erb @@ -1,7 +1,7 @@ <%# Properties -%> <%- entity.properties.each do |property| -%> - <%= property.comment %> +<%= property.comment_with_indent %> public var <%= SwiftHelper.format_name(property.name) %>: <%= SwiftHelper.type(property.return_type) %> { <%= render('swift/method_forward.erb', { method: property }) -%> } diff --git a/codegen/lib/ts_helper.rb b/codegen/lib/ts_helper.rb index cd54b6b0e66..a75027b8999 100644 --- a/codegen/lib/ts_helper.rb +++ b/codegen/lib/ts_helper.rb @@ -47,8 +47,9 @@ def self.type(t) def self.combine_declaration_files wasm_src = File.expand_path(File.join(File.dirname(__FILE__), '../../wasm')) header = File.expand_path('copyright_header.erb', File.join(File.dirname(__FILE__), 'templates')) + combined_path = "#{wasm_src}/src/wallet-core.d.ts" - combined = File.open("#{wasm_src}/lib/wallet-core.d.ts", 'w') + combined = File.open(combined_path, 'w') # append header combined.write(File.read(header)) @@ -63,5 +64,30 @@ def self.combine_declaration_files end combined.close FileUtils.remove_dir("#{wasm_src}/lib/generated", true) + + # generate WalletCore interface + interface = "export interface WalletCore {\n" + + combined = File.open(combined_path, 'r') + all_lines = combined.read + combined.close + + export_regex = /^export (class|namespace) (.*)\b/ + declare_regex = /^declare function (.+?(?=\())/ + + all_lines.scan(export_regex).each do |match| + matched = match[1] + interface += " #{matched}: typeof #{matched};\n" + end + + all_lines.scan(declare_regex).each do |match| + matched = match[0] + interface += " #{matched}: typeof #{matched};\n" + end + interface += "}\n" + + File.open(combined_path, 'a') do |file| + file << interface + end end end diff --git a/codegen/test/test_parser.rb b/codegen/test/test_parser.rb index ba9677c2c5a..7ec0af335e8 100644 --- a/codegen/test/test_parser.rb +++ b/codegen/test/test_parser.rb @@ -63,7 +63,7 @@ def test_parse_method_discardable_result assert_equal(method.return_type.name, :data) assert_equal(method.name, 'Encode') assert_equal(method.discardable_result, true) - assert_equal(method.comment, '// Encode function to Eth ABI binary' + "\n") + assert_equal(method.comment, '// Encode function to Eth ABI binary') end def test_init diff --git a/coverage.stats b/coverage.stats index a13c286215c..8b3541cc00d 100644 --- a/coverage.stats +++ b/coverage.stats @@ -1 +1 @@ -94.7 +93.0 diff --git a/docs/registry-fields.md b/docs/registry-fields.md index f4662d510cd..ff6b0e539e6 100644 --- a/docs/registry-fields.md +++ b/docs/registry-fields.md @@ -31,7 +31,7 @@ Ex.: `10000118` for Osmosis, `118` for Cosmos; `20000714` for BNB Smart Chain. See also: `slip44` and `chainId`. **`slip44`** -Optionally, SLIP-44 (BIP-44) coin ID can be specified here, in case it differs from `coinId`. Most of the case the two are the same, so this can be omitted. +Optionally, SLIP-44 (BIP-44) coin ID can be specified here, in case it differs from `coinId`. In most cases the two are the same, so this can be omitted. Ex.: `60` for Optimism (coinID is `10000070`). **`symbol`** @@ -67,7 +67,7 @@ Note that the second number, the BIP-44 ID, usually matches the coinId. Some blockchains may support additional alternative derivations. These have: - a name -- a alternative derivation path (optional) +- an alternative derivation path (optional) Derivation may differ in the derivation path, or by address generation method (based on the derivation name). The first derivation is considered the default. @@ -126,7 +126,7 @@ Defines the prefix byte used in P2PKH and P2SH addresses, Bitcoin style. Ex. `0` and `5` for Bitcoin. **`hrp`** -Human Readable Prefix used to prefix an address, used to indicate type of address, to minimalize risk of accidental address mix-up across chains. +Human Readable Prefix used to prefix an address, used to indicate type of address, to minimize risk of accidental address mix-up across chains. Ex. `'bc'` for Bitcoin, `'cosmos'` for Cosmos. **`chainId`** @@ -150,7 +150,7 @@ Ex.: `'sha256d'` for Bitcoin, `'blake256d'` for Decred. **`addressHasher`** Hash method used in the publicKey -> address generation. -Only some chain implementation use this setting, in most implementation this is fixed (and value here is only informative). +Only some chain implementations use this setting, in most implementations this is fixed (and value here is only informative). Default is `sha256ripemd`. Ex.: missing ('sha256ripemd') for Bitcoin, `'keccak256'` for Ethereum, `'sha256ripemd'` for Cosmos, `'keccak256'` for Native Evmos, despite being a Cosmos fork. @@ -189,7 +189,7 @@ Link to the default implementation of the node or RPC gateway that can be used b Optional URL to an available public RPC service. **`info/documentation`** -Main porject documentation site/subsite. +Main project documentation site/subsite. **`deprecated`** If set to `true`, the project is considered deprecated: its info is kept here, but it will not be supported. diff --git a/docs/registry.md b/docs/registry.md index 192cbfe7486..7606e14a45b 100644 --- a/docs/registry.md +++ b/docs/registry.md @@ -13,28 +13,43 @@ This list is generated from [./registry.json](../registry.json) | 20 | DigiByte | DGB | | | | 22 | Monacoin | MONA | | | | 42 | Decred | DCR | | | +| 57 | Syscoin | SYS | | | | 60 | Ethereum | ETH | | | | 61 | Ethereum Classic | ETC | | | | 74 | ICON | ICX | | | +| 77 | Verge | XVG | | | | 118 | Cosmos Hub | ATOM | | | +| 119 | Pivx | PIVX | | | +| 121 | Zen | ZEN | | | | 133 | Zcash | ZEC | | | | 136 | Firo | FIRO | | | +| 137 | Rootstock | RBTC | | | +| 141 | Komodo | KMD | | | | 144 | XRP | XRP | | | | 145 | Bitcoin Cash | BCH | | | +| 146 | Nebl | NEBL | | | | 148 | Stellar | XLM | | | | 156 | Bitcoin Gold | BTG | | | | 165 | Nano | XNO | | | +| 169 | Manta Pacific | ETH | | | | 175 | Ravencoin | RVN | | | | 178 | POA Network | POA | | | | 194 | EOS | EOS | | | | 195 | Tron | TRX | | | +| 204 | OpBNB | BNB | | | +| 223 | Internet Computer | ICP | | | | 235 | FIO | FIO | | | | 242 | Nimiq | NIM | | | | 283 | Algorand | ALGO | | | +| 291 | IOST | IOST | | | | 304 | IoTeX | IOTX | | | +| 309 | Nervos | CKB | | | | 313 | Zilliqa | ZIL | | | +| 330 | Terra Classic | LUNC | | | | 354 | Polkadot | DOT | | | +| 361 | Theta Fuel | TFUEL | | | | 394 | Crypto.org | CRO | | | +| 396 | Everscale | EVER | | | | 397 | NEAR | NEAR | | | | 425 | Aion | AION | | | | 434 | Kusama | KSM | | | @@ -46,45 +61,102 @@ This list is generated from [./registry.json](../registry.json) | 494 | BandChain | BAND | | | | 500 | Theta | THETA | | | | 501 | Solana | SOL | | | -| 508 | Elrond | eGLD | | | -| 714 | BNB Beacon Chain | BNB | | | +| 508 | MultiversX | eGLD | | | +| 529 | Secret | SCRT | | | +| 564 | Agoric | BLD | | | +| 607 | TON | TON | | | +| 637 | Aptos | APT | | | +| 714 | BNB Beacon Chain | BNB | | | +| 784 | Sui | SUI | | | +| 787 | Acala | ACA | | | | 818 | VeChain | VET | | | | 820 | Callisto | CLO | | | | 888 | NEO | NEO | | | -| 889 | TomoChain | TOMO | | | +| 889 | Viction | VIC | | | | 899 | eCash | XEC | | | | 931 | THORChain | RUNE | | | -| 966 | Polygon | MATIC | | | -| 1001 | Thunder Token | TT | | | +| 966 | Polygon | POL | | | +| 996 | OKX Chain | OKT | | | +| 999 | Bitcoin Diamond | BCD | | | +| 1001 | ThunderCore | TT | | | | 1023 | Harmony | ONE | | | | 1024 | Ontology | ONT | | | +| 1030 | Conflux eSpace | CFX | | | | 1729 | Tezos | XTZ | | | | 1815 | Cardano | ADA | | | +| 1890 | Lightlink Phoenix | ETH | | | | 2301 | Qtum | QTUM | | | | 2718 | Nebulas | NAS | | | +| 3030 | Hedera | HBAR | | | +| 4200 | Merlin | BTC | | | +| 5000 | Mantle | MNT | | | +| 5600 | BNB Greenfield | BNB | | | +| 6001 | BounceBit | BB | | | | 6060 | GoChain | GO | | | +| 7332 | Zen EON | ZEN | | | +| 8453 | Base | ETH | | | | 8964 | NULS | NULS | | | +| 14001 | WAX | WAXP | | | +| 18000 | Meter | MTR | | | | 19167 | Flux | FLUX | | | | 52752 | Celo | CELO | | | +| 59144 | Linea | ETH | | | +| 81457 | Blast | ETH | | | +| 105105 | Stratis | STRAX | | | +| 534352 | Scroll | ETH | | | +| 810180 | zkLink Nova Mainnet | ETH | | | | 5718350 | Wanchain | WAN | | | | 5741564 | Waves | WAVES | | | | 10000025 | Cronos Chain | CRO | | | -| 10000070 | Optimistic Ethereum | ETH | | | +| 10000060 | Native Injective | INJ | | | +| 10000070 | OP Mainnet | ETH | | | | 10000100 | Gnosis Chain | xDAI | | | | 10000118 | Osmosis | OSMO | | | | 10000145 | Smart Bitcoin Cash | BCH | | | | 10000250 | Fantom | FTM | | | | 10000288 | Boba | BOBAETH | | | | 10000321 | KuCoin Community Chain | KCS | | | +| 10000324 | zkSync Era | ETH | | | +| 10000330 | Terra | LUNA | | | | 10000553 | Huobi ECO Chain | HT | | | +| 10000787 | Acala EVM | ACA | | | +| 10000990 | Coreum | CORE | | | | 10001088 | Metis | METIS | | | +| 10001101 | Polygon zkEVM | ETH | | | | 10001284 | Moonbeam | GLMR | | | | 10001285 | Moonriver | MOVR | | | | 10002020 | Ronin | RON | | | -| 10008217 | Klaytn | KLAY | | | +| 10002222 | KavaEvm | KAVA | | | +| 10004689 | IoTeX EVM | IOTX | | | +| 10007000 | NativeZetaChain | ZETA | | | +| 10007700 | NativeCanto | CANTO | | | +| 10008217 | Kaia | KLAY | | | | 10009000 | Avalanche C-Chain | AVAX | | | | 10009001 | Evmos | EVMOS | | | +| 10042170 | Arbitrum Nova | ETH | | | | 10042221 | Arbitrum | ETH | | | +| 11000118 | Sommelier | SOMM | | | +| 12000118 | Fetch AI | FET | | | +| 13000118 | Mars Hub | MARS | | | +| 14000118 | Umee | UMEE | | | +| 15000118 | Quasar | QSR | | | +| 16000118 | Persistence | XPRT | | | +| 17000118 | Akash | AKT | | | +| 18000118 | Noble | USDC | | | +| 19000118 | Sei | SEI | | | +| 20000118 | Stargaze | STARS | | | | 20000714 | BNB Smart Chain | BNB | | | +| 20007000 | Zeta EVM | ZETA | | | | 20009001 | Native Evmos | EVMOS | | | +| 21000118 | Celestia | TIA | | | +| 22000118 | dYdX | DYDX | | | +| 30000118 | Juno | JUNO | | | +| 30000714 | TBNB | BNB | | | +| 40000118 | Stride | STRD | | | +| 50000118 | Axelar | AXL | | | +| 60000118 | Crescent | CRE | | | +| 70000118 | Kujira | KUJI | | | +| 80000118 | Comdex | CMDX | | | +| 90000118 | Neutron | NTRN | | | +| 245022934 | Neon | NEON | | | | 1323161554 | Aurora | ETH | | | diff --git a/include/TrustWalletCore/TWAES.h b/include/TrustWalletCore/TWAES.h index 4b860df1a6e..e4a30c656d1 100644 --- a/include/TrustWalletCore/TWAES.h +++ b/include/TrustWalletCore/TWAES.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -18,35 +16,41 @@ struct TWAES { uint8_t unused; // C doesn't allow zero-sized struct }; -/// Encrypts a block of data using AES in Cipher Block Chaining (CBC) mode. +/// Encrypts a block of Data using AES in Cipher Block Chaining (CBC) mode. /// -/// \param key encryption key, must be 16, 24, or 32 bytes long. -/// \param data data to encrypt. +/// \param key encryption key Data, must be 16, 24, or 32 bytes long. +/// \param data Data to encrypt. /// \param iv initialization vector. +/// \param mode padding mode. +/// \return encrypted Data. TW_EXPORT_STATIC_METHOD TWData *_Nullable TWAESEncryptCBC(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv, enum TWAESPaddingMode mode); /// Decrypts a block of data using AES in Cipher Block Chaining (CBC) mode. /// -/// \param key decryption key, must be 16, 24, or 32 bytes long. -/// \param data data to decrypt. -/// \param iv initialization vector. +/// \param key decryption key Data, must be 16, 24, or 32 bytes long. +/// \param data Data to decrypt. +/// \param iv initialization vector Data. +/// \param mode padding mode. +/// \return decrypted Data. TW_EXPORT_STATIC_METHOD TWData *_Nullable TWAESDecryptCBC(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv, enum TWAESPaddingMode mode); /// Encrypts a block of data using AES in Counter (CTR) mode. /// -/// \param key encryption key, must be 16, 24, or 32 bytes long. -/// \param data data to encrypt. -/// \param iv initialization vector. +/// \param key encryption key Data, must be 16, 24, or 32 bytes long. +/// \param data Data to encrypt. +/// \param iv initialization vector Data. +/// \return encrypted Data. TW_EXPORT_STATIC_METHOD TWData *_Nullable TWAESEncryptCTR(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); /// Decrypts a block of data using AES in Counter (CTR) mode. /// -/// \param key decryption key, must be 16, 24, or 32 bytes long. -/// \param data data to decrypt. -/// \param iv initialization vector. +/// \param key decryption key Data, must be 16, 24, or 32 bytes long. +/// \param data Data to decrypt. +/// \param iv initialization vector Data. +/// \return decrypted Data. TW_EXPORT_STATIC_METHOD TWData *_Nullable TWAESDecryptCTR(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); diff --git a/include/TrustWalletCore/TWAESPaddingMode.h b/include/TrustWalletCore/TWAESPaddingMode.h index 9e4713d0ed6..da271a5b989 100644 --- a/include/TrustWalletCore/TWAESPaddingMode.h +++ b/include/TrustWalletCore/TWAESPaddingMode.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -10,6 +8,7 @@ TW_EXTERN_C_BEGIN +/// Padding mode used in AES encryption. TW_EXPORT_ENUM(uint32_t) enum TWAESPaddingMode { TWAESPaddingModeZero = 0, // padding value is zero diff --git a/include/TrustWalletCore/TWAccount.h b/include/TrustWalletCore/TWAccount.h index 591005d5ceb..a5cc0fc0e2f 100644 --- a/include/TrustWalletCore/TWAccount.h +++ b/include/TrustWalletCore/TWAccount.h @@ -1,44 +1,77 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "TWBase.h" -#include "TWString.h" #include "TWCoinType.h" #include "TWDerivation.h" +#include "TWString.h" TW_EXTERN_C_BEGIN -/// Account for a particular coin within a wallet. +/// Represents an Account in C++ with address, coin type and public key info, an item within a keystore. TW_EXPORT_CLASS struct TWAccount; +/// Creates a new Account with an address, a coin type, derivation enum, derivationPath, publicKey, +/// and extendedPublicKey. Must be deleted with TWAccountDelete after use. +/// +/// \param address The address of the Account. +/// \param coin The coin type of the Account. +/// \param derivation The derivation of the Account. +/// \param derivationPath The derivation path of the Account. +/// \param publicKey hex encoded public key. +/// \param extendedPublicKey Base58 encoded extended public key. +/// \return A new Account. TW_EXPORT_STATIC_METHOD -struct TWAccount *_Nonnull TWAccountCreate(TWString *_Nonnull address, enum TWCoinType coin, enum TWDerivation derivation, TWString *_Nonnull derivationPath, TWString *_Nonnull publicKey, TWString *_Nonnull extendedPublicKey); - +struct TWAccount* _Nonnull TWAccountCreate(TWString* _Nonnull address, + enum TWCoinType coin, + enum TWDerivation derivation, + TWString* _Nonnull derivationPath, + TWString* _Nonnull publicKey, + TWString* _Nonnull extendedPublicKey); +/// Deletes an account. +/// +/// \param account Account to delete. TW_EXPORT_METHOD void TWAccountDelete(struct TWAccount *_Nonnull account); +/// Returns the address of an account. +/// +/// \param account Account to get the address of. TW_EXPORT_PROPERTY TWString *_Nonnull TWAccountAddress(struct TWAccount *_Nonnull account); +/// Return CoinType enum of an account. +/// +/// \param account Account to get the coin type of. TW_EXPORT_PROPERTY -enum TWDerivation TWAccountDerivation(struct TWAccount *_Nonnull account); +enum TWCoinType TWAccountCoin(struct TWAccount* _Nonnull account); +/// Returns the derivation enum of an account. +/// +/// \param account Account to get the derivation enum of. TW_EXPORT_PROPERTY -TWString *_Nonnull TWAccountDerivationPath(struct TWAccount *_Nonnull account); +enum TWDerivation TWAccountDerivation(struct TWAccount *_Nonnull account); +/// Returns derivationPath of an account. +/// +/// \param account Account to get the derivation path of. TW_EXPORT_PROPERTY -TWString *_Nonnull TWAccountPublicKey(struct TWAccount *_Nonnull account); +TWString *_Nonnull TWAccountDerivationPath(struct TWAccount *_Nonnull account); +/// Returns hex encoded publicKey of an account. +/// +/// \param account Account to get the public key of. TW_EXPORT_PROPERTY -TWString *_Nonnull TWAccountExtendedPublicKey(struct TWAccount *_Nonnull account); +TWString* _Nonnull TWAccountPublicKey(struct TWAccount* _Nonnull account); +/// Returns Base58 encoded extendedPublicKey of an account. +/// +/// \param account Account to get the extended public key of. TW_EXPORT_PROPERTY -enum TWCoinType TWAccountCoin(struct TWAccount *_Nonnull account); +TWString* _Nonnull TWAccountExtendedPublicKey(struct TWAccount* _Nonnull account); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWAnyAddress.h b/include/TrustWalletCore/TWAnyAddress.h index ff84d55e907..49b604aa9fc 100644 --- a/include/TrustWalletCore/TWAnyAddress.h +++ b/include/TrustWalletCore/TWAnyAddress.h @@ -1,52 +1,157 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "TWBase.h" #include "TWCoinType.h" #include "TWData.h" +#include "TWFilecoinAddressType.h" +#include "TWFiroAddressType.h" #include "TWString.h" TW_EXTERN_C_BEGIN struct TWPublicKey; -/// Represents Any blockchain address. +/// Represents an address in C++ for almost any blockchain. TW_EXPORT_CLASS struct TWAnyAddress; /// Compares two addresses for equality. +/// +/// \param lhs The first address to compare. +/// \param rhs The second address to compare. +/// \return bool indicating the addresses are equal. TW_EXPORT_STATIC_METHOD bool TWAnyAddressEqual(struct TWAnyAddress* _Nonnull lhs, struct TWAnyAddress* _Nonnull rhs); /// Determines if the string is a valid Any address. +/// +/// \param string address to validate. +/// \param coin coin type of the address. +/// \return bool indicating if the address is valid. TW_EXPORT_STATIC_METHOD bool TWAnyAddressIsValid(TWString* _Nonnull string, enum TWCoinType coin); -/// Creates an address from a string representation. +/// Determines if the string is a valid Any address with the given hrp. +/// +/// \param string address to validate. +/// \param coin coin type of the address. +/// \param hrp explicit given hrp of the given address. +/// \return bool indicating if the address is valid. +TW_EXPORT_STATIC_METHOD +bool TWAnyAddressIsValidBech32(TWString* _Nonnull string, enum TWCoinType coin, TWString* _Nonnull hrp); + +/// Determines if the string is a valid Any address with the given SS58 network prefix. +/// +/// \param string address to validate. +/// \param coin coin type of the address. +/// \param ss58Prefix ss58Prefix of the given address. +/// \return bool indicating if the address is valid. +TW_EXPORT_STATIC_METHOD +bool TWAnyAddressIsValidSS58(TWString* _Nonnull string, enum TWCoinType coin, uint32_t ss58Prefix); + +/// Creates an address from a string representation and a coin type. Must be deleted with TWAnyAddressDelete after use. +/// +/// \param string address to create. +/// \param coin coin type of the address. +/// \return TWAnyAddress pointer or nullptr if address and coin are invalid. TW_EXPORT_STATIC_METHOD struct TWAnyAddress* _Nullable TWAnyAddressCreateWithString(TWString* _Nonnull string, enum TWCoinType coin); +/// Creates an bech32 address from a string representation, a coin type and the given hrp. Must be deleted with TWAnyAddressDelete after use. +/// +/// \param string address to create. +/// \param coin coin type of the address. +/// \param hrp hrp of the address. +/// \return TWAnyAddress pointer or nullptr if address and coin are invalid. +TW_EXPORT_STATIC_METHOD +struct TWAnyAddress* _Nullable TWAnyAddressCreateBech32(TWString* _Nonnull string, enum TWCoinType coin, TWString* _Nonnull hrp); + +/// Creates an SS58 address from a string representation, a coin type and the given ss58Prefix. Must be deleted with TWAnyAddressDelete after use. +/// +/// \param string address to create. +/// \param coin coin type of the address. +/// \param ss58Prefix ss58Prefix of the SS58 address. +/// \return TWAnyAddress pointer or nullptr if address and coin are invalid. +TW_EXPORT_STATIC_METHOD +struct TWAnyAddress* _Nullable TWAnyAddressCreateSS58(TWString* _Nonnull string, enum TWCoinType coin, uint32_t ss58Prefix); + + /// Creates an address from a public key. +/// +/// \param publicKey derivates the address from the public key. +/// \param coin coin type of the address. +/// \return TWAnyAddress pointer or nullptr if public key is invalid. TW_EXPORT_STATIC_METHOD struct TWAnyAddress* _Nonnull TWAnyAddressCreateWithPublicKey(struct TWPublicKey* _Nonnull publicKey, enum TWCoinType coin); +/// Creates an address from a public key and derivation option. +/// +/// \param publicKey derivates the address from the public key. +/// \param coin coin type of the address. +/// \param derivation the custom derivation to use. +/// \return TWAnyAddress pointer or nullptr if public key is invalid. +TW_EXPORT_STATIC_METHOD +struct TWAnyAddress* _Nonnull TWAnyAddressCreateWithPublicKeyDerivation(struct TWPublicKey* _Nonnull publicKey, enum TWCoinType coin, enum TWDerivation derivation); + +/// Creates an bech32 address from a public key and a given hrp. +/// +/// \param publicKey derivates the address from the public key. +/// \param coin coin type of the address. +/// \param hrp hrp of the address. +/// \return TWAnyAddress pointer or nullptr if public key is invalid. +TW_EXPORT_STATIC_METHOD +struct TWAnyAddress* _Nonnull TWAnyAddressCreateBech32WithPublicKey(struct TWPublicKey* _Nonnull publicKey, enum TWCoinType coin, TWString* _Nonnull hrp); + +/// Creates an SS58 address from a public key and a given ss58Prefix. +/// +/// \param publicKey derivates the address from the public key. +/// \param coin coin type of the address. +/// \param ss58Prefix ss58Prefix of the SS58 address. +/// \return TWAnyAddress pointer or nullptr if public key is invalid. +TW_EXPORT_STATIC_METHOD +struct TWAnyAddress* _Nonnull TWAnyAddressCreateSS58WithPublicKey(struct TWPublicKey* _Nonnull publicKey, enum TWCoinType coin, uint32_t ss58Prefix); + +/// Creates a Filecoin address from a public key and a given address type. +/// +/// \param publicKey derivates the address from the public key. +/// \param filecoinAddressType Filecoin address type. +/// \return TWAnyAddress pointer or nullptr if public key is invalid. +TW_EXPORT_STATIC_METHOD +struct TWAnyAddress* _Nonnull TWAnyAddressCreateWithPublicKeyFilecoinAddressType(struct TWPublicKey* _Nonnull publicKey, enum TWFilecoinAddressType filecoinAddressType); + +/// Creates a Firo address from a public key and a given address type. +/// +/// \param publicKey derivates the address from the public key. +/// \param firoAddressType Firo address type. +/// \return TWAnyAddress pointer or nullptr if public key is invalid. +TW_EXPORT_STATIC_METHOD +struct TWAnyAddress* _Nonnull TWAnyAddressCreateWithPublicKeyFiroAddressType(struct TWPublicKey* _Nonnull publicKey, enum TWFiroAddressType firoAddressType); + +/// Deletes an address. +/// +/// \param address address to delete. TW_EXPORT_METHOD void TWAnyAddressDelete(struct TWAnyAddress* _Nonnull address); /// Returns the address string representation. +/// +/// \param address address to get the string representation of. TW_EXPORT_PROPERTY TWString* _Nonnull TWAnyAddressDescription(struct TWAnyAddress* _Nonnull address); /// Returns coin type of address. +/// +/// \param address address to get the coin type of. TW_EXPORT_PROPERTY enum TWCoinType TWAnyAddressCoin(struct TWAnyAddress* _Nonnull address); /// Returns underlaying data (public key or key hash) +/// +/// \param address address to get the data of. TW_EXPORT_PROPERTY TWData* _Nonnull TWAnyAddressData(struct TWAnyAddress* _Nonnull address); diff --git a/include/TrustWalletCore/TWAnySigner.h b/include/TrustWalletCore/TWAnySigner.h index f4246843baf..a50f1246618 100644 --- a/include/TrustWalletCore/TWAnySigner.h +++ b/include/TrustWalletCore/TWAnySigner.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "TWBase.h" @@ -12,18 +10,35 @@ TW_EXTERN_C_BEGIN -/// Helper class to sign any transactions. +/// Represents a signer to sign transactions for any blockchain. struct TWAnySigner; -/// Signs a transaction. +/// Signs a transaction specified by the signing input and coin type. +/// +/// \param input The serialized data of a signing input (e.g. TW.Bitcoin.Proto.SigningInput). +/// \param coin The given coin type to sign the transaction for. +/// \return The serialized data of a `SigningOutput` proto object. (e.g. TW.Bitcoin.Proto.SigningOutput). extern TWData *_Nonnull TWAnySignerSign(TWData *_Nonnull input, enum TWCoinType coin); -/// Signs a json transaction with private key. +/// Signs a transaction specified by the JSON representation of signing input, coin type and a private key, returning the JSON representation of the signing output. +/// +/// \param json JSON representation of a signing input +/// \param key The private key to sign with. +/// \param coin The given coin type to sign the transaction for. +/// \return The JSON representation of a `SigningOutput` proto object. extern TWString *_Nonnull TWAnySignerSignJSON(TWString *_Nonnull json, TWData *_Nonnull key, enum TWCoinType coin); +/// Check if AnySigner supports signing JSON representation of signing input. +/// +/// \param coin The given coin type to sign the transaction for. +/// \return true if AnySigner supports signing JSON representation of signing input for a given coin. extern bool TWAnySignerSupportsJSON(enum TWCoinType coin); -/// Plan a transaction (for UTXO chains). +/// Plans a transaction (for UTXO chains only). +/// +/// \param input The serialized data of a signing input +/// \param coin The given coin type to plan the transaction for. +/// \return The serialized data of a `TransactionPlan` proto object. extern TWData *_Nonnull TWAnySignerPlan(TWData *_Nonnull input, enum TWCoinType coin); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWAsnParser.h b/include/TrustWalletCore/TWAsnParser.h new file mode 100644 index 00000000000..b008a0578d8 --- /dev/null +++ b/include/TrustWalletCore/TWAsnParser.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWData.h" + +TW_EXTERN_C_BEGIN + +/// Represents an ASN.1 DER parser. +TW_EXPORT_STRUCT +struct TWAsnParser; + +/// Parses the given ECDSA signature from ASN.1 DER encoded bytes. +/// +/// \param encoded The ASN.1 DER encoded signature. +/// \return The ECDSA signature standard binary representation: RS, where R - 32 byte array, S - 32 byte array. +TW_EXPORT_STATIC_METHOD +TWData* _Nullable TWAsnParserEcdsaSignatureFromDer(TWData* _Nonnull encoded); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWBarz.h b/include/TrustWalletCore/TWBarz.h new file mode 100644 index 00000000000..1454e219b5d --- /dev/null +++ b/include/TrustWalletCore/TWBarz.h @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" +#include "TWPublicKey.h" + +TW_EXTERN_C_BEGIN + +/// Barz functions +TW_EXPORT_STRUCT +struct TWBarz; + +/// Calculate a counterfactual address for the smart contract wallet +/// +/// \param input The serialized data of ContractAddressInput. +/// \return The address. +TW_EXPORT_STATIC_METHOD +TWString *_Nonnull TWBarzGetCounterfactualAddress(TWData *_Nonnull input); + +/// Returns the init code parameter of ERC-4337 User Operation +/// +/// \param factory Wallet factory address (BarzFactory) +/// \param publicKey Public key for the verification facet +/// \param verificationFacet Verification facet address +/// \return The address. +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWBarzGetInitCode(TWString* _Nonnull factory, struct TWPublicKey* _Nonnull publicKey, TWString* _Nonnull verificationFacet, uint32_t salt); + +/// Converts the original ASN-encoded signature from webauthn to the format accepted by Barz +/// +/// \param signature Original signature +/// \param challenge The original challenge that was signed +/// \param authenticatorData Returned from Webauthn API +/// \param clientDataJSON Returned from Webauthn API +/// \return Bytes of the formatted signature +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWBarzGetFormattedSignature(TWData* _Nonnull signature, TWData* _Nonnull challenge, TWData* _Nonnull authenticatorData, TWString* _Nonnull clientDataJSON); + +/// Returns the final hash to be signed by Barz for signing messages & typed data +/// +/// \param msgHash Original msgHash +/// \param barzAddress The address of Barz wallet signing the message +/// \param chainId The chainId of the network the verification will happen +/// \return The final hash to be signed +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWBarzGetPrefixedMsgHash(TWData* _Nonnull msgHash, TWString* _Nonnull barzAddress, uint32_t chainId); + +/// Returns the encoded diamondCut function call for Barz contract upgrades +/// +/// \param input The serialized data of DiamondCutInput +/// \return The encoded bytes of diamondCut function call +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWBarzGetDiamondCutCode(TWData *_Nonnull input); +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWBase.h b/include/TrustWalletCore/TWBase.h index 17e618ace45..1e334d18679 100644 --- a/include/TrustWalletCore/TWBase.h +++ b/include/TrustWalletCore/TWBase.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #if !defined(TW_EXTERN_C_BEGIN) #if defined(__cplusplus) @@ -55,6 +53,13 @@ #define TW_ASSUME_NONNULL_END #endif +#if defined(__cplusplus) && (__cplusplus >= 201402L) +# define TW_DEPRECATED(since) [[deprecated("Since " #since)]] +# define TW_DEPRECATED_FOR(since, replacement) [[deprecated("Since " #since "; use " #replacement)]] +#else +# define TW_DEPRECATED(since) +# define TW_DEPRECATED_FOR(since, replacement) +#endif #if !__has_feature(nullability) #ifndef _Nullable diff --git a/include/TrustWalletCore/TWBase32.h b/include/TrustWalletCore/TWBase32.h new file mode 100644 index 00000000000..a8f09039ec8 --- /dev/null +++ b/include/TrustWalletCore/TWBase32.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Base32 encode / decode functions +TW_EXPORT_STRUCT +struct TWBase32; + +/// Decode a Base32 input with the given alphabet +/// +/// \param string Encoded base32 input to be decoded +/// \param alphabet Decode with the given alphabet, if nullptr ALPHABET_RFC4648 is used by default +/// \return The decoded data, can be null. +/// \note ALPHABET_RFC4648 doesn't support padding in the default alphabet +TW_EXPORT_STATIC_METHOD +TWData* _Nullable TWBase32DecodeWithAlphabet(TWString* _Nonnull string, TWString* _Nullable alphabet); + +/// Decode a Base32 input with the default alphabet (ALPHABET_RFC4648) +/// +/// \param string Encoded input to be decoded +/// \return The decoded data +/// \note Call TWBase32DecodeWithAlphabet with nullptr. +TW_EXPORT_STATIC_METHOD +TWData* _Nullable TWBase32Decode(TWString* _Nonnull string); + +/// Encode an input to Base32 with the given alphabet +/// +/// \param data Data to be encoded (raw bytes) +/// \param alphabet Encode with the given alphabet, if nullptr ALPHABET_RFC4648 is used by default +/// \return The encoded data +/// \note ALPHABET_RFC4648 doesn't support padding in the default alphabet +TW_EXPORT_STATIC_METHOD +TWString *_Nonnull TWBase32EncodeWithAlphabet(TWData *_Nonnull data, TWString* _Nullable alphabet); + +/// Encode an input to Base32 with the default alphabet (ALPHABET_RFC4648) +/// +/// \param data Data to be encoded (raw bytes) +/// \return The encoded data +/// \note Call TWBase32EncodeWithAlphabet with nullptr. +TW_EXPORT_STATIC_METHOD +TWString *_Nonnull TWBase32Encode(TWData *_Nonnull data); + +TW_EXTERN_C_END + diff --git a/include/TrustWalletCore/TWBase58.h b/include/TrustWalletCore/TWBase58.h index a37e14e06d3..c59ddd2a2a0 100644 --- a/include/TrustWalletCore/TWBase58.h +++ b/include/TrustWalletCore/TWBase58.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,22 +10,35 @@ TW_EXTERN_C_BEGIN +/// Base58 encode / decode functions TW_EXPORT_STRUCT struct TWBase58; /// Encodes data as a Base58 string, including the checksum. +/// +/// \param data The data to encode. +/// \return the encoded Base58 string with checksum. TW_EXPORT_STATIC_METHOD TWString *_Nonnull TWBase58Encode(TWData *_Nonnull data); /// Encodes data as a Base58 string, not including the checksum. +/// +/// \param data The data to encode. +/// \return then encoded Base58 string without checksum. TW_EXPORT_STATIC_METHOD TWString *_Nonnull TWBase58EncodeNoCheck(TWData *_Nonnull data); -/// Decodes a Base58 string checking the checksum. +/// Decodes a Base58 string, checking the checksum. Returns null if the string is not a valid Base58 string. +/// +/// \param string The Base58 string to decode. +/// \return the decoded data, empty if the string is not a valid Base58 string with checksum. TW_EXPORT_STATIC_METHOD TWData *_Nullable TWBase58Decode(TWString *_Nonnull string); -/// Decodes a Base58 string with no checksum. +/// Decodes a Base58 string, w/o checking the checksum. Returns null if the string is not a valid Base58 string. +/// +/// \param string The Base58 string to decode. +/// \return the decoded data, empty if the string is not a valid Base58 string without checksum. TW_EXPORT_STATIC_METHOD TWData *_Nullable TWBase58DecodeNoCheck(TWString *_Nonnull string); diff --git a/include/TrustWalletCore/TWBase64.h b/include/TrustWalletCore/TWBase64.h new file mode 100644 index 00000000000..5b3cff51a73 --- /dev/null +++ b/include/TrustWalletCore/TWBase64.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Base64 encode / decode functions +TW_EXPORT_STRUCT +struct TWBase64; + +/// Decode a Base64 input with the default alphabet (RFC4648 with '+', '/') +/// +/// \param string Encoded input to be decoded +/// \return The decoded data, empty if decoding failed. +TW_EXPORT_STATIC_METHOD +TWData* _Nullable TWBase64Decode(TWString* _Nonnull string); + +/// Decode a Base64 input with the alphabet safe for URL-s and filenames (RFC4648 with '-', '_') +/// +/// \param string Encoded base64 input to be decoded +/// \return The decoded data, empty if decoding failed. +TW_EXPORT_STATIC_METHOD +TWData* _Nullable TWBase64DecodeUrl(TWString* _Nonnull string); + +/// Encode an input to Base64 with the default alphabet (RFC4648 with '+', '/') +/// +/// \param data Data to be encoded (raw bytes) +/// \return The encoded data +TW_EXPORT_STATIC_METHOD +TWString *_Nonnull TWBase64Encode(TWData *_Nonnull data); + +/// Encode an input to Base64 with the alphabet safe for URL-s and filenames (RFC4648 with '-', '_') +/// +/// \param data Data to be encoded (raw bytes) +/// \return The encoded data +TW_EXPORT_STATIC_METHOD +TWString *_Nonnull TWBase64EncodeUrl(TWData *_Nonnull data); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWBitcoinAddress.h b/include/TrustWalletCore/TWBitcoinAddress.h index b42bdba717f..5fd4d2172a0 100644 --- a/include/TrustWalletCore/TWBitcoinAddress.h +++ b/include/TrustWalletCore/TWBitcoinAddress.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -14,46 +12,75 @@ TW_EXTERN_C_BEGIN struct TWPublicKey; -/// Represents a legacy Bitcoin address. +/// Represents a legacy Bitcoin address in C++. TW_EXPORT_CLASS struct TWBitcoinAddress; /// Compares two addresses for equality. +/// +/// \param lhs The first address to compare. +/// \param rhs The second address to compare. +/// \return bool indicating the addresses are equal. TW_EXPORT_STATIC_METHOD bool TWBitcoinAddressEqual(struct TWBitcoinAddress *_Nonnull lhs, struct TWBitcoinAddress *_Nonnull rhs); /// Determines if the data is a valid Bitcoin address. +/// +/// \param data data to validate. +/// \return bool indicating if the address data is valid. TW_EXPORT_STATIC_METHOD bool TWBitcoinAddressIsValid(TWData *_Nonnull data); /// Determines if the string is a valid Bitcoin address. +/// +/// \param string string to validate. +/// \return bool indicating if the address string is valid. TW_EXPORT_STATIC_METHOD bool TWBitcoinAddressIsValidString(TWString *_Nonnull string); -/// Initializes an address from a base58 sring representaion. +/// Initializes an address from a Base58 sring. Must be deleted with TWBitcoinAddressDelete after use. +/// +/// \param string Base58 string to initialize the address from. +/// \return TWBitcoinAddress pointer or nullptr if string is invalid. TW_EXPORT_STATIC_METHOD struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithString(TWString *_Nonnull string); /// Initializes an address from raw data. +/// +/// \param data Raw data to initialize the address from. Must be deleted with TWBitcoinAddressDelete after use. +/// \return TWBitcoinAddress pointer or nullptr if data is invalid. TW_EXPORT_STATIC_METHOD struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithData(TWData *_Nonnull data); /// Initializes an address from a public key and a prefix byte. +/// +/// \param publicKey Public key to initialize the address from. +/// \param prefix Prefix byte (p2pkh, p2sh, etc). +/// \return TWBitcoinAddress pointer or nullptr if public key is invalid. TW_EXPORT_STATIC_METHOD struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithPublicKey(struct TWPublicKey *_Nonnull publicKey, uint8_t prefix); +/// Deletes a legacy Bitcoin address. +/// +/// \param address Address to delete. TW_EXPORT_METHOD void TWBitcoinAddressDelete(struct TWBitcoinAddress *_Nonnull address); -/// Returns the address base58 string representation. +/// Returns the address in Base58 string representation. +/// +/// \param address Address to get the string representation of. TW_EXPORT_PROPERTY TWString *_Nonnull TWBitcoinAddressDescription(struct TWBitcoinAddress *_Nonnull address); /// Returns the address prefix. +/// +/// \param address Address to get the prefix of. TW_EXPORT_PROPERTY uint8_t TWBitcoinAddressPrefix(struct TWBitcoinAddress *_Nonnull address); -/// Returns the keyhash data. +/// Returns the key hash data. +/// +/// \param address Address to get the keyhash data of. TW_EXPORT_PROPERTY TWData *_Nonnull TWBitcoinAddressKeyhash(struct TWBitcoinAddress *_Nonnull address); diff --git a/include/TrustWalletCore/TWBitcoinMessageSigner.h b/include/TrustWalletCore/TWBitcoinMessageSigner.h new file mode 100644 index 00000000000..4090ff4850f --- /dev/null +++ b/include/TrustWalletCore/TWBitcoinMessageSigner.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" +#include "TWPrivateKey.h" + +TW_EXTERN_C_BEGIN + +/// Bitcoin message signing and verification. +/// +/// Bitcoin Core and some other wallets support a message signing & verification format, to create a proof (a signature) +/// that someone has access to the private keys of a specific address. +/// This feature currently works on old legacy addresses only. +TW_EXPORT_STRUCT +struct TWBitcoinMessageSigner; + +/// Sign a message. +/// +/// \param privateKey: the private key used for signing +/// \param address: the address that matches the privateKey, must be a legacy address (P2PKH) +/// \param message: A custom message which is input to the signing. +/// \note Address is derived assuming compressed public key format. +/// \returns the signature, Base64-encoded. On invalid input empty string is returned. Returned object needs to be deleteed after use. +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWBitcoinMessageSignerSignMessage(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull address, TWString* _Nonnull message); + +/// Verify signature for a message. +/// +/// \param address: address to use, only legacy is supported +/// \param message: the message signed (without prefix) +/// \param signature: in Base64-encoded form. +/// \returns false on any invalid input (does not throw). +TW_EXPORT_STATIC_METHOD +bool TWBitcoinMessageSignerVerifyMessage(TWString* _Nonnull address, TWString* _Nonnull message, TWString* _Nonnull signature); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWBitcoinPsbt.h b/include/TrustWalletCore/TWBitcoinPsbt.h new file mode 100644 index 00000000000..5860e3332b7 --- /dev/null +++ b/include/TrustWalletCore/TWBitcoinPsbt.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWBitcoinSigHashType.h" +#include "TWCoinType.h" +#include "TWData.h" +#include "TWPublicKey.h" + +TW_EXTERN_C_BEGIN + +/// Represents a signer to sign/plan PSBT for Bitcoin blockchains. +TW_EXPORT_CLASS +struct TWBitcoinPsbt; + +/// Signs a PSBT (Partially Signed Bitcoin Transaction) specified by the signing input and coin type. +/// +/// \param input The serialized data of a signing input (e.g. `TW.BitcoinV2.Proto.PsbtSigningInput`) +/// \param coin The given coin type to sign the PSBT for. +/// \return The serialized data of a `Proto.PsbtSigningOutput` proto object (e.g. `TW.BitcoinV2.Proto.PsbtSigningOutput`). +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWBitcoinPsbtSign(TWData* _Nonnull input, enum TWCoinType coin); + +/// Plans a PSBT (Partially Signed Bitcoin Transaction). +/// Can be used to get the transaction detailed decoded from PSBT. +/// +/// \param input The serialized data of a signing input (e.g. `TW.BitcoinV2.Proto.PsbtSigningInput`) +/// \param coin The given coin type to sign the PSBT for. +/// \return The serialized data of a `Proto.TransactionPlan` proto object (e.g. `TW.BitcoinV2.Proto.TransactionPlan`). +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWBitcoinPsbtPlan(TWData* _Nonnull input, enum TWCoinType coin); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWBitcoinScript.h b/include/TrustWalletCore/TWBitcoinScript.h index 701d6ad4243..b49e65ae1f8 100644 --- a/include/TrustWalletCore/TWBitcoinScript.h +++ b/include/TrustWalletCore/TWBitcoinScript.h @@ -1,125 +1,213 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "TWBase.h" +#include "TWBitcoinSigHashType.h" +#include "TWCoinType.h" #include "TWData.h" #include "TWPublicKey.h" -#include "TWCoinType.h" -#include "TWBitcoinSigHashType.h" TW_EXTERN_C_BEGIN +/// Bitcoin script manipulating functions TW_EXPORT_CLASS struct TWBitcoinScript; /// Creates an empty script. +/// +/// \return A pointer to the script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptCreate(); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptCreate(); /// Creates a script from a raw data representation. +/// +/// \param data The data buffer +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptCreateWithData(TWData *_Nonnull data); -struct TWBitcoinScript *_Nonnull TWBitcoinScriptCreateWithBytes(uint8_t *_Nonnull bytes, size_t size); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptCreateWithData(TWData* _Nonnull data); + +/// Creates a script from a raw bytes and size. +/// +/// \param bytes The buffer +/// \param size The size of the buffer +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the script +struct TWBitcoinScript* _Nonnull TWBitcoinScriptCreateWithBytes(uint8_t* _Nonnull bytes, size_t size); -/// Creates a script by copying an existring script. +/// Creates a script by copying an existing script. +/// +/// \param script Non-null pointer to a script +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptCreateCopy(const struct TWBitcoinScript *_Nonnull script); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptCreateCopy(const struct TWBitcoinScript* _Nonnull script); +/// Delete/Deallocate a given script. +/// +/// \param script Non-null pointer to a script TW_EXPORT_METHOD -void TWBitcoinScriptDelete(struct TWBitcoinScript *_Nonnull script); +void TWBitcoinScriptDelete(struct TWBitcoinScript* _Nonnull script); +/// Get size of a script +/// +/// \param script Non-null pointer to a script +/// \return size of the script TW_EXPORT_PROPERTY -size_t TWBitcoinScriptSize(const struct TWBitcoinScript *_Nonnull script); +size_t TWBitcoinScriptSize(const struct TWBitcoinScript* _Nonnull script); +/// Get data of a script +/// +/// \param script Non-null pointer to a script +/// \return data of the given script TW_EXPORT_PROPERTY -TWData *_Nonnull TWBitcoinScriptData(const struct TWBitcoinScript *_Nonnull script); +TWData* _Nonnull TWBitcoinScriptData(const struct TWBitcoinScript* _Nonnull script); +/// Return script hash of a script +/// +/// \param script Non-null pointer to a script +/// \return script hash of the given script TW_EXPORT_PROPERTY -TWData *_Nonnull TWBitcoinScriptScriptHash(const struct TWBitcoinScript *_Nonnull script); +TWData* _Nonnull TWBitcoinScriptScriptHash(const struct TWBitcoinScript* _Nonnull script); /// Determines whether this is a pay-to-script-hash (P2SH) script. +/// +/// \param script Non-null pointer to a script +/// \return true if this is a pay-to-script-hash (P2SH) script, false otherwise TW_EXPORT_PROPERTY -bool TWBitcoinScriptIsPayToScriptHash(const struct TWBitcoinScript *_Nonnull script); +bool TWBitcoinScriptIsPayToScriptHash(const struct TWBitcoinScript* _Nonnull script); /// Determines whether this is a pay-to-witness-script-hash (P2WSH) script. +/// +/// \param script Non-null pointer to a script +/// \return true if this is a pay-to-witness-script-hash (P2WSH) script, false otherwise TW_EXPORT_PROPERTY -bool TWBitcoinScriptIsPayToWitnessScriptHash(const struct TWBitcoinScript *_Nonnull script); +bool TWBitcoinScriptIsPayToWitnessScriptHash(const struct TWBitcoinScript* _Nonnull script); /// Determines whether this is a pay-to-witness-public-key-hash (P2WPKH) script. +/// +/// \param script Non-null pointer to a script +/// \return true if this is a pay-to-witness-public-key-hash (P2WPKH) script, false otherwise TW_EXPORT_PROPERTY -bool TWBitcoinScriptIsPayToWitnessPublicKeyHash(const struct TWBitcoinScript *_Nonnull script); +bool TWBitcoinScriptIsPayToWitnessPublicKeyHash(const struct TWBitcoinScript* _Nonnull script); -/// Determines whether this is a witness programm script. +/// Determines whether this is a witness program script. +/// +/// \param script Non-null pointer to a script +/// \return true if this is a witness program script, false otherwise TW_EXPORT_PROPERTY -bool TWBitcoinScriptIsWitnessProgram(const struct TWBitcoinScript *_Nonnull script); +bool TWBitcoinScriptIsWitnessProgram(const struct TWBitcoinScript* _Nonnull script); +/// Determines whether 2 scripts have the same content +/// +/// \param lhs Non-null pointer to the first script +/// \param rhs Non-null pointer to the second script +/// \return true if both script have the same content TW_EXPORT_STATIC_METHOD -bool TWBitcoinScriptEqual(const struct TWBitcoinScript *_Nonnull lhs, const struct TWBitcoinScript *_Nonnull rhs); +bool TWBitcoinScriptEqual(const struct TWBitcoinScript* _Nonnull lhs, const struct TWBitcoinScript* _Nonnull rhs); /// Matches the script to a pay-to-public-key (P2PK) script. /// -/// - Returns: the public key. +/// \param script Non-null pointer to a script +/// \return The public key. TW_EXPORT_METHOD -TWData *_Nullable TWBitcoinScriptMatchPayToPubkey(const struct TWBitcoinScript *_Nonnull script); +TWData* _Nullable TWBitcoinScriptMatchPayToPubkey(const struct TWBitcoinScript* _Nonnull script); /// Matches the script to a pay-to-public-key-hash (P2PKH). /// -/// - Returns: the key hash. +/// \param script Non-null pointer to a script +/// \return the key hash. TW_EXPORT_METHOD -TWData *_Nullable TWBitcoinScriptMatchPayToPubkeyHash(const struct TWBitcoinScript *_Nonnull script); +TWData* _Nullable TWBitcoinScriptMatchPayToPubkeyHash(const struct TWBitcoinScript* _Nonnull script); /// Matches the script to a pay-to-script-hash (P2SH). /// -/// - Returns: the script hash. +/// \param script Non-null pointer to a script +/// \return the script hash. TW_EXPORT_METHOD -TWData *_Nullable TWBitcoinScriptMatchPayToScriptHash(const struct TWBitcoinScript *_Nonnull script); +TWData* _Nullable TWBitcoinScriptMatchPayToScriptHash(const struct TWBitcoinScript* _Nonnull script); /// Matches the script to a pay-to-witness-public-key-hash (P2WPKH). /// -/// - Returns: the key hash. +/// \param script Non-null pointer to a script +/// \return the key hash. TW_EXPORT_METHOD -TWData *_Nullable TWBitcoinScriptMatchPayToWitnessPublicKeyHash(const struct TWBitcoinScript *_Nonnull script); +TWData* _Nullable TWBitcoinScriptMatchPayToWitnessPublicKeyHash(const struct TWBitcoinScript* _Nonnull script); /// Matches the script to a pay-to-witness-script-hash (P2WSH). /// -/// - Returns: the script hash, a SHA256 of the witness script. +/// \param script Non-null pointer to a script +/// \return the script hash, a SHA256 of the witness script.. TW_EXPORT_METHOD -TWData *_Nullable TWBitcoinScriptMatchPayToWitnessScriptHash(const struct TWBitcoinScript *_Nonnull script); +TWData* _Nullable TWBitcoinScriptMatchPayToWitnessScriptHash(const struct TWBitcoinScript* _Nonnull script); /// Encodes the script. +/// +/// \param script Non-null pointer to a script +/// \return The encoded script TW_EXPORT_METHOD -TWData *_Nonnull TWBitcoinScriptEncode(const struct TWBitcoinScript *_Nonnull script); +TWData* _Nonnull TWBitcoinScriptEncode(const struct TWBitcoinScript* _Nonnull script); /// Builds a standard 'pay to public key' script. +/// +/// \param pubkey Non-null pointer to a pubkey +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the built script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildPayToPublicKey(TWData *_Nonnull pubkey); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptBuildPayToPublicKey(TWData* _Nonnull pubkey); /// Builds a standard 'pay to public key hash' script. +/// +/// \param hash Non-null pointer to a PublicKey hash +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the built script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildPayToPublicKeyHash(TWData *_Nonnull hash); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptBuildPayToPublicKeyHash(TWData* _Nonnull hash); /// Builds a standard 'pay to script hash' script. +/// +/// \param scriptHash Non-null pointer to a script hash +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the built script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildPayToScriptHash(TWData *_Nonnull scriptHash); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptBuildPayToScriptHash(TWData* _Nonnull scriptHash); -/// Builds a pay-to-witness-public-key-hash (P2WPKH) script. +/// Builds a pay-to-witness-public-key-hash (P2WPKH) script.. +/// +/// \param hash Non-null pointer to a witness public key hash +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the built script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildPayToWitnessPubkeyHash(TWData *_Nonnull hash); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptBuildPayToWitnessPubkeyHash(TWData* _Nonnull hash); /// Builds a pay-to-witness-script-hash (P2WSH) script. +/// +/// \param scriptHash Non-null pointer to a script hash +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the built script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptBuildPayToWitnessScriptHash(TWData *_Nonnull scriptHash); +struct TWBitcoinScript* _Nonnull TWBitcoinScriptBuildPayToWitnessScriptHash(TWData* _Nonnull scriptHash); -/// Builds a appropriate lock script for the given address. +/// Builds a appropriate lock script for the given address.. +/// +/// \param address Non-null pointer to an address +/// \param coin coin type +/// \note Must be deleted with \TWBitcoinScriptDelete +/// \return A pointer to the built script TW_EXPORT_STATIC_METHOD -struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddress(TWString *_Nonnull address, enum TWCoinType coin); +struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddress(TWString* _Nonnull address, enum TWCoinType coin); -// Return the default HashType for the given coin, such as TWBitcoinSigHashTypeAll. +/// Builds a appropriate lock script for the given address with replay. +TW_EXPORT_STATIC_METHOD +struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddressReplay(TWString *_Nonnull address, enum TWCoinType coin, TWData *_Nonnull blockHash, int64_t blockHeight); + +/// Return the default HashType for the given coin, such as TWBitcoinSigHashTypeAll. +/// +/// \param coinType coin type +/// \return default HashType for the given coin TW_EXPORT_STATIC_METHOD uint32_t TWBitcoinScriptHashTypeForCoin(enum TWCoinType coinType); diff --git a/include/TrustWalletCore/TWBitcoinSigHashType.h b/include/TrustWalletCore/TWBitcoinSigHashType.h index 33fba1992d2..4d7e9c49a38 100644 --- a/include/TrustWalletCore/TWBitcoinSigHashType.h +++ b/include/TrustWalletCore/TWBitcoinSigHashType.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -10,6 +8,7 @@ TW_EXTERN_C_BEGIN +/// Bitcoin SIGHASH type. TW_EXPORT_ENUM(uint32_t) enum TWBitcoinSigHashType { TWBitcoinSigHashTypeAll = 0x01, @@ -20,9 +19,17 @@ enum TWBitcoinSigHashType { TWBitcoinSigHashTypeAnyoneCanPay = 0x80 }; +/// Determines if the given sig hash is single +/// +/// \param type sig hash type +/// \return true if the sigh hash type is single, false otherwise TW_EXPORT_METHOD bool TWBitcoinSigHashTypeIsSingle(enum TWBitcoinSigHashType type); +/// Determines if the given sig hash is none +/// +/// \param type sig hash type +/// \return true if the sigh hash type is none, false otherwise TW_EXPORT_METHOD bool TWBitcoinSigHashTypeIsNone(enum TWBitcoinSigHashType type); diff --git a/include/TrustWalletCore/TWBlockchain.h b/include/TrustWalletCore/TWBlockchain.h index 04f8660c5e1..faf4c575c1b 100644 --- a/include/TrustWalletCore/TWBlockchain.h +++ b/include/TrustWalletCore/TWBlockchain.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -10,6 +8,7 @@ TW_EXTERN_C_BEGIN +/// Blockchain enum type TW_EXPORT_ENUM(uint32_t) enum TWBlockchain { TWBlockchainBitcoin = 0, @@ -39,11 +38,12 @@ enum TWBlockchain { TWBlockchainHarmony = 25, TWBlockchainNEAR = 26, TWBlockchainAlgorand = 27, + TWBlockchainIOST = 28, TWBlockchainPolkadot = 29, TWBlockchainCardano = 30, TWBlockchainNEO = 31, TWBlockchainFilecoin = 32, - TWBlockchainElrondNetwork = 33, + TWBlockchainMultiversX = 33, TWBlockchainOasisNetwork = 34, TWBlockchainDecred = 35, // Bitcoin TWBlockchainZcash = 36, // Bitcoin @@ -51,6 +51,20 @@ enum TWBlockchain { TWBlockchainThorchain = 38, // Cosmos TWBlockchainRonin = 39, // Ethereum TWBlockchainKusama = 40, // Polkadot + TWBlockchainZen = 41, // Bitcoin + TWBlockchainBitcoinDiamond = 42, // Bitcoin + TWBlockchainVerge = 43, // Bitcoin + TWBlockchainNervos = 44, + TWBlockchainEverscale = 45, + TWBlockchainAptos = 46, // Aptos + TWBlockchainNebl = 47, // Bitcoin + TWBlockchainHedera = 48, // Hedera + TWBlockchainTheOpenNetwork = 49, + TWBlockchainSui = 50, + TWBlockchainGreenfield = 51, + TWBlockchainInternetComputer = 52, + TWBlockchainNativeEvmos = 53, // Cosmos + TWBlockchainNativeInjective = 54, // Cosmos }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCardano.h b/include/TrustWalletCore/TWCardano.h index 601d2edf2f9..17bd9ebb61f 100644 --- a/include/TrustWalletCore/TWCardano.h +++ b/include/TrustWalletCore/TWCardano.h @@ -1,22 +1,49 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "TWBase.h" #include "TWData.h" +#include "TWString.h" TW_EXTERN_C_BEGIN +/// Cardano helper functions TW_EXPORT_STRUCT struct TWCardano; -// The minimum ADA amount needed for a UTXO. See https://docs.cardano.org/native-tokens/minimum-ada-value-requirement -// Input is serialized TokenBundle protobuf object. +/// Calculates the minimum ADA amount needed for a UTXO. +/// +/// \deprecated consider using `TWCardanoOutputMinAdaAmount` instead. +/// \see reference https://docs.cardano.org/native-tokens/minimum-ada-value-requirement +/// \param tokenBundle serialized data of TW.Cardano.Proto.TokenBundle. +/// \return the minimum ADA amount. TW_EXPORT_STATIC_METHOD uint64_t TWCardanoMinAdaAmount(TWData *_Nonnull tokenBundle) TW_VISIBILITY_DEFAULT; +/// Calculates the minimum ADA amount needed for an output. +/// +/// \see reference https://docs.cardano.org/native-tokens/minimum-ada-value-requirement +/// \param toAddress valid destination address, as string. +/// \param tokenBundle serialized data of TW.Cardano.Proto.TokenBundle. +/// \param coinsPerUtxoByte cost per one byte of a serialized UTXO (Base-10 decimal string). +/// \return the minimum ADA amount (Base-10 decimal string). +TW_EXPORT_STATIC_METHOD +TWString *_Nullable TWCardanoOutputMinAdaAmount(TWString *_Nonnull toAddress, TWData *_Nonnull tokenBundle, TWString *_Nonnull coinsPerUtxoByte) TW_VISIBILITY_DEFAULT; + +/// Return the staking address associated to (contained in) this address. Must be a Base address. +/// Empty string is returned on error. Result must be freed. +/// \param baseAddress A valid base address, as string. +/// \return the associated staking (reward) address, as string, or empty string on error. +TW_EXPORT_STATIC_METHOD +TWString *_Nonnull TWCardanoGetStakingAddress(TWString *_Nonnull baseAddress) TW_VISIBILITY_DEFAULT; + +/// Return the legacy(byron) address. +/// \param publicKey A valid public key with TWPublicKeyTypeED25519Cardano type. +/// \return the legacy(byron) address, as string, or empty string on error. +TW_EXPORT_STATIC_METHOD +TWString *_Nonnull TWCardanoGetByronAddress(struct TWPublicKey *_Nonnull publicKey); + TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index 64d2d38f018..388bd270334 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -1,25 +1,31 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "TWBase.h" #include "TWBlockchain.h" #include "TWCurve.h" +#include "TWDerivation.h" #include "TWHDVersion.h" #include "TWHRP.h" -#include "TWPrivateKey.h" #include "TWPurpose.h" #include "TWString.h" +#include "TWDerivation.h" +#include "TWPublicKeyType.h" TW_EXTERN_C_BEGIN +/// Represents a private key. +struct TWPrivateKey; + +/// Represents a public key. +struct TWPublicKey; + /// Coin type for Level 2 of BIP44. /// -/// - SeeAlso: https://github.com/satoshilabs/slips/blob/master/slip-0044.md +/// \see https://github.com/satoshilabs/slips/blob/master/slip-0044.md TW_EXPORT_ENUM(uint32_t) enum TWCoinType { TWCoinTypeAeternity = 457, @@ -31,11 +37,13 @@ enum TWCoinType { TWCoinTypeCallisto = 820, TWCoinTypeCardano = 1815, // Note: Cardano Shelley testnet uses purpose 1852 (not 44) 1852/1815 TWCoinTypeCosmos = 118, + TWCoinTypePivx = 119, TWCoinTypeDash = 5, TWCoinTypeDecred = 42, TWCoinTypeDigiByte = 20, TWCoinTypeDogecoin = 3, TWCoinTypeEOS = 194, + TWCoinTypeWAX = 14001, TWCoinTypeEthereum = 60, TWCoinTypeEthereumClassic = 61, TWCoinTypeFIO = 235, @@ -60,9 +68,9 @@ enum TWCoinType { TWCoinTypeStellar = 148, TWCoinTypeTezos = 1729, TWCoinTypeTheta = 500, - TWCoinTypeThunderToken = 1001, + TWCoinTypeThunderCore = 1001, TWCoinTypeNEO = 888, - TWCoinTypeTomoChain = 889, + TWCoinTypeViction = 889, TWCoinTypeTron = 195, TWCoinTypeVeChain = 818, TWCoinTypeViacoin = 14, @@ -73,21 +81,24 @@ enum TWCoinType { TWCoinTypeZelcash = 19167, TWCoinTypeRavencoin = 175, TWCoinTypeWaves = 5741564, - TWCoinTypeTerra = 330, + TWCoinTypeTerra = 330, // see also TerraV2 + TWCoinTypeTerraV2 = 10000330, // see also Terra TWCoinTypeHarmony = 1023, TWCoinTypeAlgorand = 283, TWCoinTypeKusama = 434, TWCoinTypePolkadot = 354, TWCoinTypeFilecoin = 461, - TWCoinTypeElrond = 508, + TWCoinTypeMultiversX = 508, TWCoinTypeBandChain = 494, TWCoinTypeSmartChainLegacy = 10000714, TWCoinTypeSmartChain = 20000714, + TWCoinTypeTBinance = 30000714, TWCoinTypeOasis = 474, TWCoinTypePolygon = 966, TWCoinTypeTHORChain = 931, TWCoinTypeBluzelle = 483, TWCoinTypeOptimism = 10000070, + TWCoinTypeZksync = 10000324, TWCoinTypeArbitrum = 10042221, TWCoinTypeECOChain = 10000553, TWCoinTypeAvalancheCChain = 10009000, @@ -98,82 +109,222 @@ enum TWCoinType { TWCoinTypeRonin = 10002020, TWCoinTypeOsmosis = 10000118, TWCoinTypeECash = 899, + TWCoinTypeIOST = 291, TWCoinTypeCronosChain = 10000025, TWCoinTypeSmartBitcoinCash = 10000145, TWCoinTypeKuCoinCommunityChain = 10000321, + TWCoinTypeBitcoinDiamond = 999, TWCoinTypeBoba = 10000288, - TWCoinTypeMetis = 1001088, + TWCoinTypeSyscoin = 57, + TWCoinTypeVerge = 77, + TWCoinTypeZen = 121, + TWCoinTypeMetis = 10001088, TWCoinTypeAurora = 1323161554, TWCoinTypeEvmos = 10009001, TWCoinTypeNativeEvmos = 20009001, TWCoinTypeMoonriver = 10001285, TWCoinTypeMoonbeam = 10001284, - TWCoinTypeKlaytn = 10008217, + TWCoinTypeKavaEvm = 10002222, + TWCoinTypeKaia = 10008217, + TWCoinTypeMeter = 18000, + TWCoinTypeOKXChain = 996, + TWCoinTypeStratis = 105105, + TWCoinTypeKomodo = 141, + TWCoinTypeNervos = 309, + TWCoinTypeEverscale = 396, + TWCoinTypeAptos = 637, + TWCoinTypeNebl = 146, + TWCoinTypeHedera = 3030, + TWCoinTypeSecret = 529, + TWCoinTypeNativeInjective = 10000060, + TWCoinTypeAgoric = 564, + TWCoinTypeTON = 607, + TWCoinTypeSui = 784, + TWCoinTypeStargaze = 20000118, + TWCoinTypePolygonzkEVM = 10001101, + TWCoinTypeJuno = 30000118, + TWCoinTypeStride = 40000118, + TWCoinTypeAxelar = 50000118, + TWCoinTypeCrescent = 60000118, + TWCoinTypeKujira = 70000118, + TWCoinTypeIoTeXEVM = 10004689, + TWCoinTypeNativeCanto = 10007700, + TWCoinTypeComdex = 80000118, + TWCoinTypeNeutron = 90000118, + TWCoinTypeSommelier = 11000118, + TWCoinTypeFetchAI = 12000118, + TWCoinTypeMars = 13000118, + TWCoinTypeUmee = 14000118, + TWCoinTypeCoreum = 10000990, + TWCoinTypeQuasar = 15000118, + TWCoinTypePersistence = 16000118, + TWCoinTypeAkash = 17000118, + TWCoinTypeNoble = 18000118, + TWCoinTypeScroll = 534352, + TWCoinTypeRootstock = 137, + TWCoinTypeThetaFuel = 361, + TWCoinTypeConfluxeSpace = 1030, + TWCoinTypeAcala = 787, + TWCoinTypeAcalaEVM = 10000787, + TWCoinTypeOpBNB = 204, + TWCoinTypeNeon = 245022934, + TWCoinTypeBase = 8453, + TWCoinTypeSei = 19000118, + TWCoinTypeArbitrumNova = 10042170, + TWCoinTypeLinea = 59144, + TWCoinTypeGreenfield = 5600, + TWCoinTypeMantle = 5000, + TWCoinTypeZenEON = 7332, + TWCoinTypeInternetComputer = 223, + TWCoinTypeTia = 21000118, + TWCoinTypeMantaPacific = 169, + TWCoinTypeNativeZetaChain = 10007000, + TWCoinTypeZetaEVM = 20007000, + TWCoinTypeDydx = 22000118, + TWCoinTypeMerlin = 4200, + TWCoinTypeLightlink = 1890, + TWCoinTypeBlast = 81457, + TWCoinTypeBounceBit = 6001, + TWCoinTypeZkLinkNova = 810180, + // end_of_tw_coin_type_marker_do_not_modify }; /// Returns the blockchain for a coin type. +/// +/// \param coin A coin type +/// \return blockchain associated to the given coin type TW_EXPORT_PROPERTY enum TWBlockchain TWCoinTypeBlockchain(enum TWCoinType coin); /// Returns the purpose for a coin type. +/// +/// \param coin A coin type +/// \return purpose associated to the given coin type TW_EXPORT_PROPERTY enum TWPurpose TWCoinTypePurpose(enum TWCoinType coin); /// Returns the curve that should be used for a coin type. +/// +/// \param coin A coin type +/// \return curve that should be used for the given coin type TW_EXPORT_PROPERTY enum TWCurve TWCoinTypeCurve(enum TWCoinType coin); /// Returns the xpub HD version that should be used for a coin type. +/// +/// \param coin A coin type +/// \return xpub HD version that should be used for the given coin type TW_EXPORT_PROPERTY enum TWHDVersion TWCoinTypeXpubVersion(enum TWCoinType coin); /// Returns the xprv HD version that should be used for a coin type. +/// +/// \param coin A coin type +/// \return the xprv HD version that should be used for the given coin type. TW_EXPORT_PROPERTY enum TWHDVersion TWCoinTypeXprvVersion(enum TWCoinType coin); /// Validates an address string. +/// +/// \param coin A coin type +/// \param address A public address +/// \return true if the address is a valid public address of the given coin, false otherwise. TW_EXPORT_METHOD bool TWCoinTypeValidate(enum TWCoinType coin, TWString* _Nonnull address); /// Returns the default derivation path for a particular coin. +/// +/// \param coin A coin type +/// \return the default derivation path for the given coin type. TW_EXPORT_METHOD TWString* _Nonnull TWCoinTypeDerivationPath(enum TWCoinType coin); +/// Returns the derivation path for a particular coin with the explicit given derivation. +/// +/// \param coin A coin type +/// \param derivation A derivation type +/// \return the derivation path for the given coin with the explicit given derivation +TW_EXPORT_METHOD +TWString* _Nonnull TWCoinTypeDerivationPathWithDerivation(enum TWCoinType coin, enum TWDerivation derivation); + /// Derives the address for a particular coin from the private key. +/// +/// \param coin A coin type +/// \param privateKey A valid private key +/// \return Derived address for the given coin from the private key. TW_EXPORT_METHOD TWString* _Nonnull TWCoinTypeDeriveAddress(enum TWCoinType coin, struct TWPrivateKey* _Nonnull privateKey); /// Derives the address for a particular coin from the public key. +/// +/// \param coin A coin type +/// \param publicKey A valid public key +/// \return Derived address for the given coin from the public key. TW_EXPORT_METHOD TWString* _Nonnull TWCoinTypeDeriveAddressFromPublicKey(enum TWCoinType coin, struct TWPublicKey* _Nonnull publicKey); +/// Derives the address for a particular coin from the public key with the derivation. +TW_EXPORT_METHOD +TWString* _Nonnull TWCoinTypeDeriveAddressFromPublicKeyAndDerivation(enum TWCoinType coin, + struct TWPublicKey* _Nonnull publicKey, + enum TWDerivation derivation); + /// HRP for this coin type +/// +/// \param coin A coin type +/// \return HRP of the given coin type. TW_EXPORT_PROPERTY enum TWHRP TWCoinTypeHRP(enum TWCoinType coin); /// P2PKH prefix for this coin type +/// +/// \param coin A coin type +/// \return P2PKH prefix for the given coin type TW_EXPORT_PROPERTY uint8_t TWCoinTypeP2pkhPrefix(enum TWCoinType coin); /// P2SH prefix for this coin type +/// +/// \param coin A coin type +/// \return P2SH prefix for the given coin type TW_EXPORT_PROPERTY uint8_t TWCoinTypeP2shPrefix(enum TWCoinType coin); /// Static prefix for this coin type +/// +/// \param coin A coin type +/// \return Static prefix for the given coin type TW_EXPORT_PROPERTY uint8_t TWCoinTypeStaticPrefix(enum TWCoinType coin); -/// ChainID for this coin type. Caller must free return object. +/// ChainID for this coin type. +/// +/// \param coin A coin type +/// \return ChainID for the given coin type. +/// \note Caller must free returned object. TW_EXPORT_PROPERTY TWString* _Nonnull TWCoinTypeChainId(enum TWCoinType coin); /// SLIP-0044 id for this coin type +/// +/// \param coin A coin type +/// \return SLIP-0044 id for the given coin type TW_EXPORT_PROPERTY uint32_t TWCoinTypeSlip44Id(enum TWCoinType coin); +/// SS58Prefix for this coin type +/// +/// \param coin A coin type +/// \return SS58Prefix for the given coin type +TW_EXPORT_PROPERTY +uint32_t TWCoinTypeSS58Prefix(enum TWCoinType coin); + /// public key type for this coin type +/// +/// \param coin A coin type +/// \return public key type for the given coin type TW_EXPORT_PROPERTY enum TWPublicKeyType TWCoinTypePublicKeyType(enum TWCoinType coin); diff --git a/include/TrustWalletCore/TWCoinTypeConfiguration.h b/include/TrustWalletCore/TWCoinTypeConfiguration.h index 47458179ac5..ca71f4dc56e 100644 --- a/include/TrustWalletCore/TWCoinTypeConfiguration.h +++ b/include/TrustWalletCore/TWCoinTypeConfiguration.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,32 +10,54 @@ TW_EXTERN_C_BEGIN +/// CoinTypeConfiguration functions TW_EXPORT_STRUCT struct TWCoinTypeConfiguration { uint8_t unused; // C doesn't allow zero-sized struct }; /// Returns stock symbol of coin +/// +/// \param type A coin type +/// \return A non-null TWString stock symbol of coin +/// \note Caller must free returned object TW_EXPORT_STATIC_METHOD TWString *_Nonnull TWCoinTypeConfigurationGetSymbol(enum TWCoinType type); /// Returns max count decimal places for minimal coin unit +/// +/// \param type A coin type +/// \return Returns max count decimal places for minimal coin unit TW_EXPORT_STATIC_METHOD int TWCoinTypeConfigurationGetDecimals(enum TWCoinType type); /// Returns transaction url in blockchain explorer +/// +/// \param type A coin type +/// \param transactionID A transaction identifier +/// \return Returns a non-null TWString transaction url in blockchain explorer TW_EXPORT_STATIC_METHOD TWString *_Nonnull TWCoinTypeConfigurationGetTransactionURL(enum TWCoinType type, TWString *_Nonnull transactionID); /// Returns account url in blockchain explorer +/// +/// \param type A coin type +/// \param accountID an Account identifier +/// \return Returns a non-null TWString account url in blockchain explorer TW_EXPORT_STATIC_METHOD TWString *_Nonnull TWCoinTypeConfigurationGetAccountURL(enum TWCoinType type, TWString *_Nonnull accountID); /// Returns full name of coin in lower case +/// +/// \param type A coin type +/// \return Returns a non-null TWString, full name of coin in lower case TW_EXPORT_STATIC_METHOD TWString *_Nonnull TWCoinTypeConfigurationGetID(enum TWCoinType type); /// Returns full name of coin +/// +/// \param type A coin type +/// \return Returns a non-null TWString, full name of coin TW_EXPORT_STATIC_METHOD TWString *_Nonnull TWCoinTypeConfigurationGetName(enum TWCoinType type); diff --git a/include/TrustWalletCore/TWCryptoBox.h b/include/TrustWalletCore/TWCryptoBox.h new file mode 100644 index 00000000000..abd27ca8191 --- /dev/null +++ b/include/TrustWalletCore/TWCryptoBox.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWCryptoBoxPublicKey.h" +#include "TWCryptoBoxSecretKey.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// `crypto_box` encryption algorithms. +TW_EXPORT_STRUCT +struct TWCryptoBox; + +/// Encrypts message using `my_secret` and `other_pubkey`. +/// The output will have a randomly generated nonce prepended to it. +/// The output will be Overhead + 24 bytes longer than the original. +/// +/// \param mySecret *non-null* pointer to my secret key. +/// \param otherPubkey *non-null* pointer to other's public key. +/// \param message *non-null* pointer to the message to be encrypted. +/// \return *nullable* pointer to the encrypted message with randomly generated nonce prepended to it. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWCryptoBoxEncryptEasy(struct TWCryptoBoxSecretKey* _Nonnull mySecret, struct TWCryptoBoxPublicKey* _Nonnull otherPubkey, TWData* _Nonnull message); + +/// Decrypts box produced by `TWCryptoBoxEncryptEasy`. +/// We assume a 24-byte nonce is prepended to the encrypted text in box. +/// +/// \param mySecret *non-null* pointer to my secret key. +/// \param otherPubkey *non-null* pointer to other's public key. +/// \param encrypted *non-null* pointer to the encrypted message with nonce prepended to it. +/// \return *nullable* pointer to the decrypted message. +TW_EXPORT_STATIC_METHOD +TWData* _Nullable TWCryptoBoxDecryptEasy(struct TWCryptoBoxSecretKey* _Nonnull mySecret, struct TWCryptoBoxPublicKey* _Nonnull otherPubkey, TWData* _Nonnull encrypted); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCryptoBoxPublicKey.h b/include/TrustWalletCore/TWCryptoBoxPublicKey.h new file mode 100644 index 00000000000..e46ea72feae --- /dev/null +++ b/include/TrustWalletCore/TWCryptoBoxPublicKey.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Public key used in `crypto_box` cryptography. +TW_EXPORT_CLASS +struct TWCryptoBoxPublicKey; + +/// Determines if the given public key is valid or not. +/// +/// \param data *non-null* byte array. +/// \return true if the public key is valid, false otherwise. +TW_EXPORT_STATIC_METHOD +bool TWCryptoBoxPublicKeyIsValid(TWData* _Nonnull data); + +/// Create a `crypto_box` public key with the given block of data. +/// +/// \param data *non-null* byte array. Expected to have 32 bytes. +/// \note Should be deleted with \tw_crypto_box_public_key_delete. +/// \return Nullable pointer to Public Key. +TW_EXPORT_STATIC_METHOD +struct TWCryptoBoxPublicKey* _Nullable TWCryptoBoxPublicKeyCreateWithData(TWData* _Nonnull data); + +/// Delete the given public key. +/// +/// \param publicKey *non-null* pointer to public key. +TW_EXPORT_METHOD +void TWCryptoBoxPublicKeyDelete(struct TWCryptoBoxPublicKey* _Nonnull publicKey); + +/// Returns the raw data of the given public-key. +/// +/// \param publicKey *non-null* pointer to a public key. +/// \return C-compatible result with a C-compatible byte array. +TW_EXPORT_PROPERTY +TWData* _Nonnull TWCryptoBoxPublicKeyData(struct TWCryptoBoxPublicKey* _Nonnull publicKey); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCryptoBoxSecretKey.h b/include/TrustWalletCore/TWCryptoBoxSecretKey.h new file mode 100644 index 00000000000..f93ad92eb56 --- /dev/null +++ b/include/TrustWalletCore/TWCryptoBoxSecretKey.h @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWCryptoBoxPublicKey.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Secret key used in `crypto_box` cryptography. +TW_EXPORT_CLASS +struct TWCryptoBoxSecretKey; + +/// Determines if the given secret key is valid or not. +/// +/// \param data *non-null* byte array. +/// \return true if the secret key is valid, false otherwise. +TW_EXPORT_STATIC_METHOD +bool TWCryptoBoxSecretKeyIsValid(TWData* _Nonnull data); + +/// Create a random secret key. +/// +/// \note Should be deleted with \tw_crypto_box_secret_key_delete. +/// \return *non-null* pointer to Secret Key. +TW_EXPORT_STATIC_METHOD +struct TWCryptoBoxSecretKey* _Nonnull TWCryptoBoxSecretKeyCreate(); + +/// Create a `crypto_box` secret key with the given block of data. +/// +/// \param data *non-null* byte array. Expected to have 32 bytes. +/// \note Should be deleted with \tw_crypto_box_secret_key_delete. +/// \return Nullable pointer to Secret Key. +TW_EXPORT_STATIC_METHOD +struct TWCryptoBoxSecretKey* _Nullable TWCryptoBoxSecretKeyCreateWithData(TWData* _Nonnull data); + +/// Delete the given secret `key`. +/// +/// \param key *non-null* pointer to secret key. +TW_EXPORT_METHOD +void TWCryptoBoxSecretKeyDelete(struct TWCryptoBoxSecretKey* _Nonnull key); + +/// Returns the public key associated with the given `key`. +/// +/// \param key *non-null* pointer to the private key. +/// \return *non-null* pointer to the corresponding public key. +TW_EXPORT_METHOD +struct TWCryptoBoxPublicKey* _Nonnull TWCryptoBoxSecretKeyGetPublicKey(struct TWCryptoBoxSecretKey* _Nonnull key); + +/// Returns the raw data of the given secret-key. +/// +/// \param secretKey *non-null* pointer to a secret key. +/// \return C-compatible result with a C-compatible byte array. +TW_EXPORT_PROPERTY +TWData* _Nonnull TWCryptoBoxSecretKeyData(struct TWCryptoBoxSecretKey* _Nonnull secretKey); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCurve.h b/include/TrustWalletCore/TWCurve.h index 6ce6e645539..3b7f2b003bd 100644 --- a/include/TrustWalletCore/TWCurve.h +++ b/include/TrustWalletCore/TWCurve.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -18,7 +16,8 @@ enum TWCurve { TWCurveED25519Blake2bNano /* "ed25519-blake2b-nano" */, TWCurveCurve25519 /* "curve25519" */, TWCurveNIST256p1 /* "nist256p1" */, - TWCurveED25519Extended /* "ed25519-cardano-seed" */, + TWCurveED25519ExtendedCardano /* "ed25519-cardano-seed" */, + TWCurveStarkex /* "starkex" */, TWCurveNone }; diff --git a/include/TrustWalletCore/TWData.h b/include/TrustWalletCore/TWData.h index 3a4334c8f05..77dc7c625b6 100644 --- a/include/TrustWalletCore/TWData.h +++ b/include/TrustWalletCore/TWData.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -19,54 +17,111 @@ typedef const void TWString; typedef const void TWData; /// Creates a block of data from a byte array. +/// +/// \param bytes Non-null raw bytes buffer +/// \param size size of the buffer +/// \return Non-null filled block of data. TWData *_Nonnull TWDataCreateWithBytes(const uint8_t *_Nonnull bytes, size_t size) TW_VISIBILITY_DEFAULT; /// Creates an uninitialized block of data with the provided size. +/// +/// \param size size for the block of data +/// \return Non-null uninitialized block of data with the provided size TWData *_Nonnull TWDataCreateWithSize(size_t size) TW_VISIBILITY_DEFAULT; /// Creates a block of data by copying another block of data. +/// +/// \param data buffer that need to be copied +/// \return Non-null filled block of data. TWData *_Nonnull TWDataCreateWithData(TWData *_Nonnull data) TW_VISIBILITY_DEFAULT; /// Creates a block of data from a hexadecimal string. Odd length is invalid (intended grouping to bytes is not obvious). +/// +/// \param hex input hex string +/// \return Non-null filled block of data TWData *_Nullable TWDataCreateWithHexString(const TWString *_Nonnull hex) TW_VISIBILITY_DEFAULT; /// Returns the size in bytes. +/// +/// \param data A non-null valid block of data +/// \return the size of the given block of data size_t TWDataSize(TWData *_Nonnull data) TW_VISIBILITY_DEFAULT; /// Returns the raw pointer to the contents of data. +/// +/// \param data A non-null valid block of data +/// \return the raw pointer to the contents of data uint8_t *_Nonnull TWDataBytes(TWData *_Nonnull data) TW_VISIBILITY_DEFAULT; /// Returns the byte at the provided index. +/// +/// \param data A non-null valid block of data +/// \param index index of the byte that we want to fetch - index need to be < TWDataSize(data) +/// \return the byte at the provided index uint8_t TWDataGet(TWData *_Nonnull data, size_t index) TW_VISIBILITY_DEFAULT; /// Sets the byte at the provided index. +/// +/// \param data A non-null valid block of data +/// \param index index of the byte that we want to set - index need to be < TWDataSize(data) +/// \param byte Given byte to be written in data void TWDataSet(TWData *_Nonnull data, size_t index, uint8_t byte) TW_VISIBILITY_DEFAULT; /// Copies a range of bytes into the provided buffer. +/// +/// \param data A non-null valid block of data +/// \param start starting index of the range - index need to be < TWDataSize(data) +/// \param size size of the range we want to copy - size need to be < TWDataSize(data) - start +/// \param output The output buffer where we want to copy the data. void TWDataCopyBytes(TWData *_Nonnull data, size_t start, size_t size, uint8_t *_Nonnull output) TW_VISIBILITY_DEFAULT; /// Replaces a range of bytes with the contents of the provided buffer. +/// +/// \param data A non-null valid block of data +/// \param start starting index of the range - index need to be < TWDataSize(data) +/// \param size size of the range we want to replace - size need to be < TWDataSize(data) - start +/// \param bytes The buffer that will replace the range of data void TWDataReplaceBytes(TWData *_Nonnull data, size_t start, size_t size, const uint8_t *_Nonnull bytes) TW_VISIBILITY_DEFAULT; /// Appends data from a byte array. +/// +/// \param data A non-null valid block of data +/// \param bytes Non-null byte array +/// \param size The size of the byte array void TWDataAppendBytes(TWData *_Nonnull data, const uint8_t *_Nonnull bytes, size_t size) TW_VISIBILITY_DEFAULT; /// Appends a single byte. +/// +/// \param data A non-null valid block of data +/// \param byte A single byte void TWDataAppendByte(TWData *_Nonnull data, uint8_t byte) TW_VISIBILITY_DEFAULT; /// Appends a block of data. +/// +/// \param data A non-null valid block of data +/// \param append A non-null valid block of data void TWDataAppendData(TWData *_Nonnull data, TWData *_Nonnull append) TW_VISIBILITY_DEFAULT; -/// Revereses the bytes. +/// Reverse the bytes. +/// +/// \param data A non-null valid block of data void TWDataReverse(TWData *_Nonnull data) TW_VISIBILITY_DEFAULT; /// Sets all bytes to the given value. +/// +/// \param data A non-null valid block of data void TWDataReset(TWData *_Nonnull data) TW_VISIBILITY_DEFAULT; /// Deletes a block of data created with a `TWDataCreate*` method. +/// +/// \param data A non-null valid block of data void TWDataDelete(TWData *_Nonnull data) TW_VISIBILITY_DEFAULT; /// Determines whether two data blocks are equal. +/// +/// \param lhs left non null block of data to be compared +/// \param rhs right non null block of data to be compared +/// \return true if both block of data are equal, false otherwise bool TWDataEqual(TWData *_Nonnull lhs, TWData *_Nonnull rhs) TW_VISIBILITY_DEFAULT; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWDataVector.h b/include/TrustWalletCore/TWDataVector.h index 7000268757b..fbfae776ad2 100644 --- a/include/TrustWalletCore/TWDataVector.h +++ b/include/TrustWalletCore/TWDataVector.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -11,31 +9,52 @@ TW_EXTERN_C_BEGIN -// A vector of TWData byte arrays +/// A vector of TWData byte arrays TW_EXPORT_CLASS struct TWDataVector; +/// Creates a Vector of Data. +/// +/// \note Must be deleted with \TWDataVectorDelete +/// \return a non-null Vector of Data. TW_EXPORT_STATIC_METHOD -struct TWDataVector *_Nonnull TWDataVectorCreate(); +struct TWDataVector* _Nonnull TWDataVectorCreate(); -// Create with one element +/// Creates a Vector of Data with the given element +/// +/// \param data A non-null valid block of data +/// \return A Vector of data with a single given element TW_EXPORT_STATIC_METHOD -struct TWDataVector *_Nonnull TWDataVectorCreateWithData(TWData *_Nonnull data); +struct TWDataVector* _Nonnull TWDataVectorCreateWithData(TWData* _Nonnull data); -// Delete must be called at the end +/// Delete/Deallocate a Vector of Data +/// +/// \param dataVector A non-null Vector of data TW_EXPORT_METHOD -void TWDataVectorDelete(struct TWDataVector *_Nonnull dataVector); +void TWDataVectorDelete(struct TWDataVector* _Nonnull dataVector); -// Add an element to the vector. Element is cloned (will be deleted with the vector, but input parameter must be deleted on its own) +/// Add an element to a Vector of Data. Element is cloned +/// +/// \param dataVector A non-null Vector of data +/// \param data A non-null valid block of data +/// \note data input parameter must be deleted on its own TW_EXPORT_METHOD -void TWDataVectorAdd(struct TWDataVector *_Nonnull dataVector, TWData *_Nonnull data); +void TWDataVectorAdd(struct TWDataVector* _Nonnull dataVector, TWData* _Nonnull data); -// Retrieve the number of elements +/// Retrieve the number of elements +/// +/// \param dataVector A non-null Vector of data +/// \return the size of the given vector. TW_EXPORT_PROPERTY -size_t TWDataVectorSize(const struct TWDataVector *_Nonnull dataVector); - -// Retrieve the n-th element. A clone is returned (must be freed). +size_t TWDataVectorSize(const struct TWDataVector* _Nonnull dataVector); + +/// Retrieve the n-th element. +/// +/// \param dataVector A non-null Vector of data +/// \param index index element of the vector to be retrieved, need to be < TWDataVectorSize +/// \note Returned element must be freed with \TWDataDelete +/// \return A non-null block of data TW_EXPORT_METHOD -TWData *_Nullable TWDataVectorGet(const struct TWDataVector *_Nonnull dataVector, size_t index); +TWData* _Nullable TWDataVectorGet(const struct TWDataVector* _Nonnull dataVector, size_t index); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWDerivationPath.h b/include/TrustWalletCore/TWDerivationPath.h new file mode 100644 index 00000000000..ccec3051138 --- /dev/null +++ b/include/TrustWalletCore/TWDerivationPath.h @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWCoinType.h" +#include "TWPurpose.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Represents a BIP44 DerivationPath in C++. +TW_EXPORT_CLASS +struct TWDerivationPath; + +/// Creates a new DerivationPath with a purpose, coin, account, change and address. +/// Must be deleted with TWDerivationPathDelete after use. +/// +/// \param purpose The purpose of the Path. +/// \param coin The coin type of the Path. +/// \param account The derivation of the Path. +/// \param change The derivation path of the Path. +/// \param address hex encoded public key. +/// \return A new DerivationPath. +TW_EXPORT_STATIC_METHOD +struct TWDerivationPath* _Nonnull TWDerivationPathCreate(enum TWPurpose purpose, uint32_t coin, uint32_t account, uint32_t change, uint32_t address); + +/// Creates a new DerivationPath with a string +/// +/// \param string The string of the Path. +/// \return A new DerivationPath or null if string is invalid. +TW_EXPORT_STATIC_METHOD +struct TWDerivationPath* _Nullable TWDerivationPathCreateWithString(TWString* _Nonnull string); + +/// Deletes a DerivationPath. +/// +/// \param path DerivationPath to delete. +TW_EXPORT_METHOD +void TWDerivationPathDelete(struct TWDerivationPath* _Nonnull path); + +/// Returns the index component of a DerivationPath. +/// +/// \param path DerivationPath to get the index of. +/// \param index The index component of the DerivationPath. +/// \return DerivationPathIndex or null if index is invalid. +TW_EXPORT_METHOD +struct TWDerivationPathIndex* _Nullable TWDerivationPathIndexAt(struct TWDerivationPath* _Nonnull path, uint32_t index); + +/// Returns the indices count of a DerivationPath. +/// +/// \param path DerivationPath to get the indices count of. +/// \return The indices count of the DerivationPath. +TW_EXPORT_METHOD +uint32_t TWDerivationPathIndicesCount(struct TWDerivationPath* _Nonnull path); + +/// Returns the purpose enum of a DerivationPath. +/// +/// \param path DerivationPath to get the purpose of. +/// \return DerivationPathPurpose. +TW_EXPORT_PROPERTY +enum TWPurpose TWDerivationPathPurpose(struct TWDerivationPath* _Nonnull path); + +/// Returns the coin value of a derivation path. +/// +/// \param path DerivationPath to get the coin of. +/// \return The coin part of the DerivationPath. +TW_EXPORT_PROPERTY +uint32_t TWDerivationPathCoin(struct TWDerivationPath* _Nonnull path); + +/// Returns the account value of a derivation path. +/// +/// \param path DerivationPath to get the account of. +/// \return the account part of a derivation path. +TW_EXPORT_PROPERTY +uint32_t TWDerivationPathAccount(struct TWDerivationPath* _Nonnull path); + +/// Returns the change value of a derivation path. +/// +/// \param path DerivationPath to get the change of. +/// \return The change part of a derivation path. +TW_EXPORT_PROPERTY +uint32_t TWDerivationPathChange(struct TWDerivationPath* _Nonnull path); + +/// Returns the address value of a derivation path. +/// +/// \param path DerivationPath to get the address of. +/// \return The address part of the derivation path. +TW_EXPORT_PROPERTY +uint32_t TWDerivationPathAddress(struct TWDerivationPath* _Nonnull path); + +/// Returns the string description of a derivation path. +/// +/// \param path DerivationPath to get the address of. +/// \return The string description of the derivation path. +TW_EXPORT_PROPERTY +TWString* _Nonnull TWDerivationPathDescription(struct TWDerivationPath* _Nonnull path); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWDerivationPathIndex.h b/include/TrustWalletCore/TWDerivationPathIndex.h new file mode 100644 index 00000000000..a015f37b5f5 --- /dev/null +++ b/include/TrustWalletCore/TWDerivationPathIndex.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Represents a derivation path index in C++ with value and hardened flag. +TW_EXPORT_CLASS +struct TWDerivationPathIndex; + +/// Creates a new Index with a value and hardened flag. +/// Must be deleted with TWDerivationPathIndexDelete after use. +/// +/// \param value Index value +/// \param hardened Indicates if the Index is hardened. +/// \return A new Index. +TW_EXPORT_STATIC_METHOD +struct TWDerivationPathIndex* _Nonnull TWDerivationPathIndexCreate(uint32_t value, bool hardened); + +/// Deletes an Index. +/// +/// \param index Index to delete. +TW_EXPORT_METHOD +void TWDerivationPathIndexDelete(struct TWDerivationPathIndex* _Nonnull index); + +/// Returns numeric value of an Index. +/// +/// \param index Index to get the numeric value of. +TW_EXPORT_PROPERTY +uint32_t TWDerivationPathIndexValue(struct TWDerivationPathIndex* _Nonnull index); + +/// Returns hardened flag of an Index. +/// +/// \param index Index to get hardened flag. +/// \return true if hardened, false otherwise. +TW_EXPORT_PROPERTY +bool TWDerivationPathIndexHardened(struct TWDerivationPathIndex* _Nonnull index); + +/// Returns the string description of a derivation path index. +/// +/// \param path Index to get the address of. +/// \return The string description of the derivation path index. +TW_EXPORT_PROPERTY +TWString* _Nonnull TWDerivationPathIndexDescription(struct TWDerivationPathIndex* _Nonnull index); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereum.h b/include/TrustWalletCore/TWEthereum.h new file mode 100644 index 00000000000..9ff49f208b2 --- /dev/null +++ b/include/TrustWalletCore/TWEthereum.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +TW_EXPORT_STRUCT +struct TWEthereum; + +/// Generate a layer 2 eip2645 derivation path from eth address, layer, application and given index. +/// +/// \param wallet non-null TWHDWallet +/// \param ethAddress non-null Ethereum address +/// \param layer non-null layer 2 name (E.G starkex) +/// \param application non-null layer 2 application (E.G immutablex) +/// \param index non-null layer 2 index (E.G 1) +/// \return a valid eip2645 layer 2 derivation path as a string +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWEthereumEip2645GetPath(TWString* _Nonnull ethAddress, TWString* _Nonnull layer, TWString* _Nonnull application, TWString* _Nonnull index); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereumAbi.h b/include/TrustWalletCore/TWEthereumAbi.h index 0430ea07a3c..f2d23a4dec5 100644 --- a/include/TrustWalletCore/TWEthereumAbi.h +++ b/include/TrustWalletCore/TWEthereumAbi.h @@ -1,33 +1,74 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "TWBase.h" -#include "TWString.h" +#include "TWCoinType.h" #include "TWData.h" - -// Wrapper class for Ethereum ABI encoding & decoding. +#include "TWString.h" TW_EXTERN_C_BEGIN +/// Wrapper class for Ethereum ABI encoding & decoding. struct TWEthereumAbiFunction; TW_EXPORT_STRUCT struct TWEthereumAbi; +/// Decode a contract call (function input) according to an ABI json. +/// +/// \param coin EVM-compatible coin type. +/// \param input The serialized data of `TW.EthereumAbi.Proto.ContractCallDecodingInput`. +/// \return The serialized data of a `TW.EthereumAbi.Proto.ContractCallDecodingOutput` proto object. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiDecodeContractCall(enum TWCoinType coin, TWData* _Nonnull input); + +/// Decode a function input or output data according to a given ABI. +/// +/// \param coin EVM-compatible coin type. +/// \param input The serialized data of `TW.EthereumAbi.Proto.ParamsDecodingInput`. +/// \return The serialized data of a `TW.EthereumAbi.Proto.ParamsDecodingOutput` proto object. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiDecodeParams(enum TWCoinType coin, TWData* _Nonnull input); + +/// /// Decodes an Eth ABI value according to a given type. +/// +/// \param coin EVM-compatible coin type. +/// \param input The serialized data of `TW.EthereumAbi.Proto.ValueDecodingInput`. +/// \return The serialized data of a `TW.EthereumAbi.Proto.ValueDecodingOutput` proto object. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiDecodeValue(enum TWCoinType coin, TWData* _Nonnull input); + +/// Encode function to Eth ABI binary. +/// +/// \param coin EVM-compatible coin type. +/// \param input The serialized data of `TW.EthereumAbi.Proto.FunctionEncodingInput`. +/// \return The serialized data of a `TW.EthereumAbi.Proto.FunctionEncodingOutput` proto object. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumAbiEncodeFunction(enum TWCoinType coin, TWData* _Nonnull input); + /// Encode function to Eth ABI binary +/// +/// \param fn Non-null Eth abi function +/// \return Non-null encoded block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiEncode(struct TWEthereumAbiFunction* _Nonnull fn); /// Decode function output from Eth ABI binary, fill output parameters +/// +/// \param[in] fn Non-null Eth abi function +/// \param[out] encoded Non-null block of data +/// \return true if encoded have been filled correctly, false otherwise TW_EXPORT_STATIC_METHOD bool TWEthereumAbiDecodeOutput(struct TWEthereumAbiFunction* _Nonnull fn, TWData* _Nonnull encoded); /// Decode function call data to human readable json format, according to input abi json +/// +/// \param data Non-null block of data +/// \param abi Non-null string +/// \return Non-null json string function call data TW_EXPORT_STATIC_METHOD TWString* _Nullable TWEthereumAbiDecodeCall(TWData* _Nonnull data, TWString* _Nonnull abi); @@ -66,6 +107,9 @@ TWString* _Nullable TWEthereumAbiDecodeCall(TWData* _Nonnull data, TWString* _No /// })"); /// On error, empty Data is returned. /// Returned data must be deleted (hint: use WRAPD() macro). +/// +/// \param messageJson Non-null json abi input +/// \return Non-null block of data, encoded abi input TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiEncodeTyped(TWString* _Nonnull messageJson); diff --git a/include/TrustWalletCore/TWEthereumAbiFunction.h b/include/TrustWalletCore/TWEthereumAbiFunction.h index 81daa7b3e7e..cb5fbb9407c 100644 --- a/include/TrustWalletCore/TWEthereumAbiFunction.h +++ b/include/TrustWalletCore/TWEthereumAbiFunction.h @@ -1,188 +1,454 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "TWBase.h" -#include "TWString.h" #include "TWData.h" +#include "TWString.h" TW_EXTERN_C_BEGIN +/// Represents Ethereum ABI function TW_EXPORT_CLASS struct TWEthereumAbiFunction; /// Creates a function object, with the given name and empty parameter list. It must be deleted at the end. +/// +/// \param name function name +/// \return Non-null Ethereum abi function TW_EXPORT_STATIC_METHOD -struct TWEthereumAbiFunction *_Nonnull TWEthereumAbiFunctionCreateWithString(TWString *_Nonnull name); +struct TWEthereumAbiFunction* _Nonnull TWEthereumAbiFunctionCreateWithString(TWString* _Nonnull name); /// Deletes a function object created with a 'TWEthereumAbiFunctionCreateWithString' method. +/// +/// \param fn Non-null Ethereum abi function TW_EXPORT_METHOD -void TWEthereumAbiFunctionDelete(struct TWEthereumAbiFunction *_Nonnull fn); +void TWEthereumAbiFunctionDelete(struct TWEthereumAbiFunction* _Nonnull fn); /// Return the function type signature, of the form "baz(int32,uint256)" +/// +/// \param fn A Non-null eth abi function +/// \return function type signature as a Non-null string. TW_EXPORT_METHOD -TWString *_Nonnull TWEthereumAbiFunctionGetType(struct TWEthereumAbiFunction *_Nonnull fn); +TWString* _Nonnull TWEthereumAbiFunctionGetType(struct TWEthereumAbiFunction* _Nonnull fn); + +/// Methods for adding parameters of the given type (input or output). +/// For output parameters (isOutput=true) a value has to be specified, although usually not need; -/// Methods for adding parameters of the given type (input or output). -/// For output parameters (isOutput=true) a value has to be specified, although usually not needd. -/// Returns the index of the parameter (0-based). +/// Add a uint8 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt8(struct TWEthereumAbiFunction *_Nonnull fn, uint8_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt8(struct TWEthereumAbiFunction* _Nonnull fn, uint8_t val, bool isOutput); +/// Add a uint16 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt16(struct TWEthereumAbiFunction *_Nonnull fn, uint16_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt16(struct TWEthereumAbiFunction* _Nonnull fn, uint16_t val, bool isOutput); +/// Add a uint32 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt32(struct TWEthereumAbiFunction *_Nonnull fn, uint32_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt32(struct TWEthereumAbiFunction* _Nonnull fn, uint32_t val, bool isOutput); +/// Add a uint64 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt64(struct TWEthereumAbiFunction *_Nonnull fn, uint64_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt64(struct TWEthereumAbiFunction* _Nonnull fn, uint64_t val, bool isOutput); +/// Add a uint256 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUInt256(struct TWEthereumAbiFunction *_Nonnull fn, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamUInt256(struct TWEthereumAbiFunction* _Nonnull fn, TWData* _Nonnull val, bool isOutput); +/// Add a uint(bits) type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamUIntN(struct TWEthereumAbiFunction *_Nonnull fn, int bits, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamUIntN(struct TWEthereumAbiFunction* _Nonnull fn, int bits, TWData* _Nonnull val, bool isOutput); +/// Add a int8 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt8(struct TWEthereumAbiFunction *_Nonnull fn, int8_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt8(struct TWEthereumAbiFunction* _Nonnull fn, int8_t val, bool isOutput); +/// Add a int16 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt16(struct TWEthereumAbiFunction *_Nonnull fn, int16_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt16(struct TWEthereumAbiFunction* _Nonnull fn, int16_t val, bool isOutput); +/// Add a int32 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt32(struct TWEthereumAbiFunction *_Nonnull fn, int32_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt32(struct TWEthereumAbiFunction* _Nonnull fn, int32_t val, bool isOutput); +/// Add a int64 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt64(struct TWEthereumAbiFunction *_Nonnull fn, int64_t val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt64(struct TWEthereumAbiFunction* _Nonnull fn, int64_t val, bool isOutput); +/// Add a int256 type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified (stored in a block of data) +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamInt256(struct TWEthereumAbiFunction *_Nonnull fn, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamInt256(struct TWEthereumAbiFunction* _Nonnull fn, TWData* _Nonnull val, bool isOutput); +/// Add a int(bits) type parameter +/// +/// \param fn A Non-null eth abi function +/// \param bits Number of bits of the integer parameter +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamIntN(struct TWEthereumAbiFunction *_Nonnull fn, int bits, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamIntN(struct TWEthereumAbiFunction* _Nonnull fn, int bits, TWData* _Nonnull val, bool isOutput); + +/// Add a bool type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamBool(struct TWEthereumAbiFunction *_Nonnull fn, bool val, bool isOutput); +int TWEthereumAbiFunctionAddParamBool(struct TWEthereumAbiFunction* _Nonnull fn, bool val, bool isOutput); +/// Add a string type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamString(struct TWEthereumAbiFunction *_Nonnull fn, TWString *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamString(struct TWEthereumAbiFunction* _Nonnull fn, TWString* _Nonnull val, bool isOutput); +/// Add an address type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamAddress(struct TWEthereumAbiFunction *_Nonnull fn, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamAddress(struct TWEthereumAbiFunction* _Nonnull fn, TWData* _Nonnull val, bool isOutput); +/// Add a bytes type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamBytes(struct TWEthereumAbiFunction *_Nonnull fn, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamBytes(struct TWEthereumAbiFunction* _Nonnull fn, TWData* _Nonnull val, bool isOutput); +/// Add a bytes[N] type parameter +/// +/// \param fn A Non-null eth abi function +/// \param size fixed size of the bytes array parameter (val). +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamBytesFix(struct TWEthereumAbiFunction *_Nonnull fn, size_t size, TWData *_Nonnull val, bool isOutput); +int TWEthereumAbiFunctionAddParamBytesFix(struct TWEthereumAbiFunction* _Nonnull fn, size_t size, TWData* _Nonnull val, bool isOutput); +/// Add a type[] type parameter +/// +/// \param fn A Non-null eth abi function +/// \param val for output parameters, value has to be specified +/// \param isOutput determines if the parameter is an input or output +/// \return the index of the parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddParamArray(struct TWEthereumAbiFunction *_Nonnull fn, bool isOutput); +int TWEthereumAbiFunctionAddParamArray(struct TWEthereumAbiFunction* _Nonnull fn, bool isOutput); /// Methods for accessing the value of an output or input parameter, of different types. + +/// Get a uint8 type parameter at the given index +/// +/// \param fn A Non-null eth abi function +/// \param idx index for the parameter (0-based). +/// \param isOutput determines if the parameter is an input or output +/// \return the value of the parameter. TW_EXPORT_METHOD -uint8_t TWEthereumAbiFunctionGetParamUInt8(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); +uint8_t TWEthereumAbiFunctionGetParamUInt8(struct TWEthereumAbiFunction* _Nonnull fn, int idx, bool isOutput); + +/// Get a uint64 type parameter at the given index +/// +/// \param fn A Non-null eth abi function +/// \param idx index for the parameter (0-based). +/// \param isOutput determines if the parameter is an input or output +/// \return the value of the parameter. TW_EXPORT_METHOD -uint64_t TWEthereumAbiFunctionGetParamUInt64(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); +uint64_t TWEthereumAbiFunctionGetParamUInt64(struct TWEthereumAbiFunction* _Nonnull fn, int idx, bool isOutput); + +/// Get a uint256 type parameter at the given index +/// +/// \param fn A Non-null eth abi function +/// \param idx index for the parameter (0-based). +/// \param isOutput determines if the parameter is an input or output +/// \return the value of the parameter stored in a block of data. TW_EXPORT_METHOD -TWData *_Nonnull TWEthereumAbiFunctionGetParamUInt256(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); +TWData* _Nonnull TWEthereumAbiFunctionGetParamUInt256(struct TWEthereumAbiFunction* _Nonnull fn, int idx, bool isOutput); + +/// Get a bool type parameter at the given index +/// +/// \param fn A Non-null eth abi function +/// \param idx index for the parameter (0-based). +/// \param isOutput determines if the parameter is an input or output +/// \return the value of the parameter. TW_EXPORT_METHOD -bool TWEthereumAbiFunctionGetParamBool(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); +bool TWEthereumAbiFunctionGetParamBool(struct TWEthereumAbiFunction* _Nonnull fn, int idx, bool isOutput); + +/// Get a string type parameter at the given index +/// +/// \param fn A Non-null eth abi function +/// \param idx index for the parameter (0-based). +/// \param isOutput determines if the parameter is an input or output +/// \return the value of the parameter. TW_EXPORT_METHOD -TWString *_Nonnull TWEthereumAbiFunctionGetParamString(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); +TWString* _Nonnull TWEthereumAbiFunctionGetParamString(struct TWEthereumAbiFunction* _Nonnull fn, int idx, bool isOutput); + +/// Get an address type parameter at the given index +/// +/// \param fn A Non-null eth abi function +/// \param idx index for the parameter (0-based). +/// \param isOutput determines if the parameter is an input or output +/// \return the value of the parameter. TW_EXPORT_METHOD -TWData *_Nonnull TWEthereumAbiFunctionGetParamAddress(struct TWEthereumAbiFunction *_Nonnull fn, int idx, bool isOutput); +TWData* _Nonnull TWEthereumAbiFunctionGetParamAddress(struct TWEthereumAbiFunction* _Nonnull fn, int idx, bool isOutput); /// Methods for adding a parameter of the given type to a top-level input parameter array. Returns the index of the parameter (0-based). /// Note that nested ParamArrays are not possible through this API, could be done by using index paths like "1/0" + +/// Adding a uint8 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt8(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, uint8_t val); +int TWEthereumAbiFunctionAddInArrayParamUInt8(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, uint8_t val); +/// Adding a uint16 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt16(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, uint16_t val); +int TWEthereumAbiFunctionAddInArrayParamUInt16(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, uint16_t val); +/// Adding a uint32 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt32(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, uint32_t val); +int TWEthereumAbiFunctionAddInArrayParamUInt32(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, uint32_t val); +/// Adding a uint64 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt64(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, uint64_t val); +int TWEthereumAbiFunctionAddInArrayParamUInt64(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, uint64_t val); +/// Adding a uint256 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter stored in a block of data +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUInt256(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamUInt256(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, TWData* _Nonnull val); +/// Adding a uint[N] type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param bits Number of bits of the integer parameter +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter stored in a block of data +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamUIntN(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int bits, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamUIntN(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, int bits, TWData* _Nonnull val); +/// Adding a int8 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt8(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int8_t val); +int TWEthereumAbiFunctionAddInArrayParamInt8(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, int8_t val); +/// Adding a int16 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt16(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int16_t val); +int TWEthereumAbiFunctionAddInArrayParamInt16(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, int16_t val); +/// Adding a int32 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt32(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int32_t val); +int TWEthereumAbiFunctionAddInArrayParamInt32(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, int32_t val); +/// Adding a int64 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt64(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int64_t val); +int TWEthereumAbiFunctionAddInArrayParamInt64(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, int64_t val); +/// Adding a int256 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter stored in a block of data +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamInt256(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamInt256(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, TWData* _Nonnull val); +/// Adding a int[N] type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param bits Number of bits of the integer parameter +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter stored in a block of data +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamIntN(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, int bits, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamIntN(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, int bits, TWData* _Nonnull val); +/// Adding a bool type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamBool(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, bool val); +int TWEthereumAbiFunctionAddInArrayParamBool(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, bool val); +/// Adding a string type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamString(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWString *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamString(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, TWString* _Nonnull val); +/// Adding an address type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamAddress(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamAddress(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, TWData* _Nonnull val); +/// Adding a bytes type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamBytes(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamBytes(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, TWData* _Nonnull val); +/// Adding a int64 type parameter of to the top-level input parameter array +/// +/// \param fn A Non-null eth abi function +/// \param arrayIdx array index for the abi function (0-based). +/// \param size fixed size of the bytes array parameter (val). +/// \param val the value of the parameter +/// \return the index of the added parameter (0-based). TW_EXPORT_METHOD TW_METHOD_DISCARDABLE_RESULT -int TWEthereumAbiFunctionAddInArrayParamBytesFix(struct TWEthereumAbiFunction *_Nonnull fn, int arrayIdx, size_t size, TWData *_Nonnull val); +int TWEthereumAbiFunctionAddInArrayParamBytesFix(struct TWEthereumAbiFunction* _Nonnull fn, int arrayIdx, size_t size, TWData* _Nonnull val); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereumAbiValue.h b/include/TrustWalletCore/TWEthereumAbiValue.h index 58a2f7e74d5..fcbe14de839 100644 --- a/include/TrustWalletCore/TWEthereumAbiValue.h +++ b/include/TrustWalletCore/TWEthereumAbiValue.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,55 +10,93 @@ TW_EXTERN_C_BEGIN +/// Represents Ethereum ABI value TW_EXPORT_STRUCT struct TWEthereumAbiValue; -/// Returned data must be deleted (hint: use WRAPD() macro). -/// Encode a type according to Ethereum ABI, into 32 bytes. Values are padded by 0 on the left, unless specified otherwise. - +/// Encode a bool according to Ethereum ABI, into 32 bytes. Values are padded by 0 on the left, unless specified otherwise +/// +/// \param value a boolean value +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeBool(bool value); +/// Encode a int32 according to Ethereum ABI, into 32 bytes. Values are padded by 0 on the left, unless specified otherwise +/// +/// \param value a int32 value +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeInt32(int32_t value); +/// Encode a uint32 according to Ethereum ABI, into 32 bytes. Values are padded by 0 on the left, unless specified otherwise +/// +/// \param value a uint32 value +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeUInt32(uint32_t value); -/// Encode an int256. Input value is represented as a 32-byte value +/// Encode a int256 according to Ethereum ABI, into 32 bytes. Values are padded by 0 on the left, unless specified otherwise +/// +/// \param value a int256 value stored in a block of data +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeInt256(TWData* _Nonnull value); -/// Encode an uint256. Input value is represented as a 32-byte binary value +/// Encode an int256 according to Ethereum ABI, into 32 bytes. Values are padded by 0 on the left, unless specified otherwise +/// +/// \param value a int256 value stored in a block of data +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeUInt256(TWData* _Nonnull value); -/// Encode the 20 bytes of an address +/// Encode an address according to Ethereum ABI, 20 bytes of the address. +/// +/// \param value an address value stored in a block of data +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeAddress(TWData* _Nonnull value); -/// Encode a string by encoding its hash +/// Encode a string according to Ethereum ABI by encoding its hash. +/// +/// \param value a string value +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeString(TWString* _Nonnull value); /// Encode a number of bytes, up to 32 bytes, padded on the right. Longer arrays are truncated. +/// +/// \param value bunch of bytes +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeBytes(TWData* _Nonnull value); /// Encode a dynamic number of bytes by encoding its hash +/// +/// \param value bunch of bytes +/// \return Encoded value stored in a block of data TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiValueEncodeBytesDyn(TWData* _Nonnull value); - /// Decodes input data (bytes longer than 32 will be truncated) as uint256 +/// +/// \param input Data to be decoded +/// \return Non-null decoded string value TW_EXPORT_STATIC_METHOD TWString* _Nonnull TWEthereumAbiValueDecodeUInt256(TWData* _Nonnull input); /// Decode an arbitrary type, return value as string +/// +/// \param input Data to be decoded +/// \param type the underlying type that need to be decoded +/// \return Non-null decoded string value TW_EXPORT_STATIC_METHOD TWString* _Nonnull TWEthereumAbiValueDecodeValue(TWData* _Nonnull input, TWString* _Nonnull type); /// Decode an array of given simple types. Return a '\n'-separated string of elements +/// +/// \param input Data to be decoded +/// \param type the underlying type that need to be decoded +/// \return Non-null decoded string value TW_EXPORT_STATIC_METHOD TWString* _Nonnull TWEthereumAbiValueDecodeArray(TWData* _Nonnull input, TWString* _Nonnull type); diff --git a/include/TrustWalletCore/TWEthereumMessageSigner.h b/include/TrustWalletCore/TWEthereumMessageSigner.h new file mode 100644 index 00000000000..6d73c338f6c --- /dev/null +++ b/include/TrustWalletCore/TWEthereumMessageSigner.h @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" +#include "TWPrivateKey.h" +#include "TWPublicKey.h" + +TW_EXTERN_C_BEGIN + +/// Ethereum message signing and verification. +/// +/// Ethereum and some other wallets support a message signing & verification format, to create a proof (a signature) +/// that someone has access to the private keys of a specific address. +TW_EXPORT_STRUCT +struct TWEthereumMessageSigner; + +/// Sign a typed message EIP-712 V4. +/// +/// \param privateKey: the private key used for signing +/// \param messageJson: A custom typed data message in json +/// \returns the signature, Hex-encoded. On invalid input empty string is returned. Returned object needs to be deleted after use. +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWEthereumMessageSignerSignTypedMessage(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull messageJson); + +/// Sign a typed message EIP-712 V4 with EIP-155 replay attack protection. +/// +/// \param privateKey: the private key used for signing +/// \param messageJson: A custom typed data message in json +/// \param chainId: chainId for eip-155 protection +/// \returns the signature, Hex-encoded. On invalid input empty string is returned or invalid chainId error message. Returned object needs to be deleted after use. +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWEthereumMessageSignerSignTypedMessageEip155(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull messageJson, int chainId); + +/// Sign a message. +/// +/// \param privateKey: the private key used for signing +/// \param message: A custom message which is input to the signing. +/// \returns the signature, Hex-encoded. On invalid input empty string is returned. Returned object needs to be deleted after use. +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWEthereumMessageSignerSignMessage(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull message); + +/// Sign a message with Immutable X msg type. +/// +/// \param privateKey: the private key used for signing +/// \param message: A custom message which is input to the signing. +/// \returns the signature, Hex-encoded. On invalid input empty string is returned. Returned object needs to be deleted after use. +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWEthereumMessageSignerSignMessageImmutableX(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull message); + +/// Sign a message with Eip-155 msg type. +/// +/// \param privateKey: the private key used for signing +/// \param message: A custom message which is input to the signing. +/// \param chainId: chainId for eip-155 protection +/// \returns the signature, Hex-encoded. On invalid input empty string is returned. Returned object needs to be deleted after use. +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWEthereumMessageSignerSignMessageEip155(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull message, int chainId); + +/// Verify signature for a message. +/// +/// \param pubKey: pubKey that will verify and recover the message from the signature +/// \param message: the message signed (without prefix) +/// \param signature: in Hex-encoded form. +/// \returns false on any invalid input (does not throw), true if the message can be recovered from the signature +TW_EXPORT_STATIC_METHOD +bool TWEthereumMessageSignerVerifyMessage(const struct TWPublicKey* _Nonnull pubKey, TWString* _Nonnull message, TWString* _Nonnull signature); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWEthereumRlp.h b/include/TrustWalletCore/TWEthereumRlp.h new file mode 100644 index 00000000000..361ac305cbc --- /dev/null +++ b/include/TrustWalletCore/TWEthereumRlp.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWCoinType.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +TW_EXPORT_STRUCT +struct TWEthereumRlp; + +/// Encode an item or a list of items as Eth RLP binary format. +/// +/// \param coin EVM-compatible coin type. +/// \param input Non-null serialized `EthereumRlp::Proto::EncodingInput`. +/// \return serialized `EthereumRlp::Proto::EncodingOutput`. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumRlpEncode(enum TWCoinType coin, TWData* _Nonnull input); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWFIOAccount.h b/include/TrustWalletCore/TWFIOAccount.h index 8012cbf0faf..e11a65c4b3d 100644 --- a/include/TrustWalletCore/TWFIOAccount.h +++ b/include/TrustWalletCore/TWFIOAccount.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -15,13 +13,24 @@ TW_EXTERN_C_BEGIN TW_EXPORT_CLASS struct TWFIOAccount; +/// Create a FIO Account +/// +/// \param string Account name +/// \note Must be deleted with \TWFIOAccountDelete +/// \return Pointer to a nullable FIO Account TW_EXPORT_STATIC_METHOD struct TWFIOAccount *_Nullable TWFIOAccountCreateWithString(TWString *_Nonnull string); +/// Delete a FIO Account +/// +/// \param account Pointer to a non-null FIO Account TW_EXPORT_METHOD void TWFIOAccountDelete(struct TWFIOAccount *_Nonnull account); /// Returns the short account string representation. +/// +/// \param account Pointer to a non-null FIO Account +/// \return Account non-null string representation TW_EXPORT_PROPERTY TWString *_Nonnull TWFIOAccountDescription(struct TWFIOAccount *_Nonnull account); diff --git a/include/TrustWalletCore/TWFilecoinAddressConverter.h b/include/TrustWalletCore/TWFilecoinAddressConverter.h new file mode 100644 index 00000000000..b6c3689984c --- /dev/null +++ b/include/TrustWalletCore/TWFilecoinAddressConverter.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Filecoin-Ethereum address converter. +TW_EXPORT_STRUCT +struct TWFilecoinAddressConverter; + +/// Converts a Filecoin address to Ethereum. +/// +/// \param filecoinAddress: a Filecoin address. +/// \returns the Ethereum address. On invalid input empty string is returned. Returned object needs to be deleted after use. +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWFilecoinAddressConverterConvertToEthereum(TWString* _Nonnull filecoinAddress); + +/// Converts an Ethereum address to Filecoin. +/// +/// \param ethAddress: an Ethereum address. +/// \returns the Filecoin address. On invalid input empty string is returned. Returned object needs to be deleted after use. +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWFilecoinAddressConverterConvertFromEthereum(TWString* _Nonnull ethAddress); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWFilecoinAddressType.h b/include/TrustWalletCore/TWFilecoinAddressType.h new file mode 100644 index 00000000000..5bab60774c5 --- /dev/null +++ b/include/TrustWalletCore/TWFilecoinAddressType.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +/// Filecoin address type. +TW_EXPORT_ENUM(uint32_t) +enum TWFilecoinAddressType { + TWFilecoinAddressTypeDefault = 0, // default + TWFilecoinAddressTypeDelegated = 1, +}; + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWFiroAddressType.h b/include/TrustWalletCore/TWFiroAddressType.h new file mode 100644 index 00000000000..55fa3a84259 --- /dev/null +++ b/include/TrustWalletCore/TWFiroAddressType.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +/// Firo address type. +TW_EXPORT_ENUM(uint32_t) +enum TWFiroAddressType { + TWFiroAddressTypeDefault = 0, // default + TWFiroAddressTypeExchange = 1, +}; + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWGroestlcoinAddress.h b/include/TrustWalletCore/TWGroestlcoinAddress.h index 37edc0e8eb5..52114afd736 100644 --- a/include/TrustWalletCore/TWGroestlcoinAddress.h +++ b/include/TrustWalletCore/TWGroestlcoinAddress.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -19,25 +17,47 @@ TW_EXPORT_CLASS struct TWGroestlcoinAddress; /// Compares two addresses for equality. +/// +/// \param lhs left Non-null GroestlCoin address to be compared +/// \param rhs right Non-null GroestlCoin address to be compared +/// \return true if both address are equal, false otherwise TW_EXPORT_STATIC_METHOD bool TWGroestlcoinAddressEqual(struct TWGroestlcoinAddress *_Nonnull lhs, struct TWGroestlcoinAddress *_Nonnull rhs); /// Determines if the string is a valid Groestlcoin address. +/// +/// \param string Non-null string. +/// \return true if it's a valid address, false otherwise TW_EXPORT_STATIC_METHOD bool TWGroestlcoinAddressIsValidString(TWString *_Nonnull string); -/// Create an address from a base58 sring representaion. +/// Create an address from a base58 string representation. +/// +/// \param string Non-null string +/// \note Must be deleted with \TWGroestlcoinAddressDelete +/// \return Non-null GroestlcoinAddress TW_EXPORT_STATIC_METHOD struct TWGroestlcoinAddress *_Nullable TWGroestlcoinAddressCreateWithString(TWString *_Nonnull string); /// Create an address from a public key and a prefix byte. +/// +/// \param publicKey Non-null public key +/// \param prefix public key prefix +/// \note Must be deleted with \TWGroestlcoinAddressDelete +/// \return Non-null GroestlcoinAddress TW_EXPORT_STATIC_METHOD struct TWGroestlcoinAddress *_Nonnull TWGroestlcoinAddressCreateWithPublicKey(struct TWPublicKey *_Nonnull publicKey, uint8_t prefix); +/// Delete a Groestlcoin address +/// +/// \param address Non-null GroestlcoinAddress TW_EXPORT_METHOD void TWGroestlcoinAddressDelete(struct TWGroestlcoinAddress *_Nonnull address); /// Returns the address base58 string representation. +/// +/// \param address Non-null GroestlcoinAddress +/// \return Address description as a non-null string TW_EXPORT_PROPERTY TWString *_Nonnull TWGroestlcoinAddressDescription(struct TWGroestlcoinAddress *_Nonnull address); diff --git a/include/TrustWalletCore/TWHDVersion.h b/include/TrustWalletCore/TWHDVersion.h index 9e93aed3aa7..ceb7733e676 100644 --- a/include/TrustWalletCore/TWHDVersion.h +++ b/include/TrustWalletCore/TWHDVersion.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -10,9 +8,9 @@ TW_EXTERN_C_BEGIN -/// Registered HD version bytes +/// Registered HD version bytes /// -/// - SeeAlso: https://github.com/satoshilabs/slips/blob/master/slip-0132.md +/// \see https://github.com/satoshilabs/slips/blob/master/slip-0132.md TW_EXPORT_ENUM(uint32_t) enum TWHDVersion { TWHDVersionNone = 0, @@ -24,12 +22,18 @@ enum TWHDVersion { TWHDVersionYPRV = 0x049d7878, TWHDVersionZPUB = 0x04b24746, TWHDVersionZPRV = 0x04b2430c, + TWHDVersionVPUB = 0x045f1cf6, + TWHDVersionVPRV = 0x045f18bc, + TWHDVersionTPUB = 0x043587cf, + TWHDVersionTPRV = 0x04358394, // Litecoin TWHDVersionLTUB = 0x019da462, TWHDVersionLTPV = 0x019d9cfe, TWHDVersionMTUB = 0x01b26ef6, TWHDVersionMTPV = 0x01b26792, + TWHDVersionTTUB = 0x0436f6e1, + TWHDVersionTTPV = 0x0436ef7d, // Decred TWHDVersionDPUB = 0x2fda926, @@ -40,9 +44,17 @@ enum TWHDVersion { TWHDVersionDGPV = 0x02fac398, }; +/// Determine if the HD Version is public +/// +/// \param version HD version +/// \return true if the version is public, false otherwise TW_EXPORT_PROPERTY bool TWHDVersionIsPublic(enum TWHDVersion version); +/// Determine if the HD Version is private +/// +/// \param version HD version +/// \return true if the version is private, false otherwise TW_EXPORT_PROPERTY bool TWHDVersionIsPrivate(enum TWHDVersion version); diff --git a/include/TrustWalletCore/TWHDWallet.h b/include/TrustWalletCore/TWHDWallet.h index 35e4a87a069..9e902a55587 100644 --- a/include/TrustWalletCore/TWHDWallet.h +++ b/include/TrustWalletCore/TWHDWallet.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -10,7 +8,10 @@ #include "TWCoinType.h" #include "TWCurve.h" #include "TWData.h" +#include "TWDerivation.h" +#include "TWDerivationPath.h" #include "TWHDVersion.h" +#include "TWDerivation.h" #include "TWPrivateKey.h" #include "TWPublicKey.h" #include "TWPurpose.h" @@ -18,87 +19,245 @@ TW_EXTERN_C_BEGIN +/// Hierarchical Deterministic (HD) Wallet TW_EXPORT_CLASS struct TWHDWallet; -/// TWHDWalletIsValid has been deprecated; use TWMnemonicIsValid(). - /// Creates a new HDWallet with a new random mnemonic with the provided strength in bits. -/// Null is returned on invalid strength. Returned object needs to be deleted. +/// +/// \param strength strength in bits +/// \param passphrase non-null passphrase +/// \note Null is returned on invalid strength +/// \note Returned object needs to be deleted with \TWHDWalletDelete +/// \return Nullable TWHDWallet TW_EXPORT_STATIC_METHOD -struct TWHDWallet *_Nullable TWHDWalletCreate(int strength, TWString *_Nonnull passphrase); +struct TWHDWallet* _Nullable TWHDWalletCreate(int strength, TWString* _Nonnull passphrase); /// Creates an HDWallet from a valid BIP39 English mnemonic and a passphrase. -/// Null is returned on invalid mnemonic. Returned object needs to be deleted. +/// +/// \param mnemonic non-null Valid BIP39 mnemonic +/// \param passphrase non-null passphrase +/// \note Null is returned on invalid mnemonic +/// \note Returned object needs to be deleted with \TWHDWalletDelete +/// \return Nullable TWHDWallet TW_EXPORT_STATIC_METHOD -struct TWHDWallet *_Nullable TWHDWalletCreateWithMnemonic(TWString *_Nonnull mnemonic, TWString *_Nonnull passphrase); +struct TWHDWallet* _Nullable TWHDWalletCreateWithMnemonic(TWString* _Nonnull mnemonic, TWString* _Nonnull passphrase); /// Creates an HDWallet from a BIP39 mnemonic, a passphrase and validation flag. -/// Null is returned on invalid mnemonic. Returned object needs to be deleted. +/// +/// \param mnemonic non-null Valid BIP39 mnemonic +/// \param passphrase non-null passphrase +/// \param check validation flag +/// \note Null is returned on invalid mnemonic +/// \note Returned object needs to be deleted with \TWHDWalletDelete +/// \return Nullable TWHDWallet TW_EXPORT_STATIC_METHOD -struct TWHDWallet *_Nullable TWHDWalletCreateWithMnemonicCheck(TWString *_Nonnull mnemonic, TWString *_Nonnull passphrase, bool check); +struct TWHDWallet* _Nullable TWHDWalletCreateWithMnemonicCheck(TWString* _Nonnull mnemonic, TWString* _Nonnull passphrase, bool check); /// Creates an HDWallet from entropy (corresponding to a mnemonic). -/// Null is returned on invalid input. Returned object needs to be deleted. +/// +/// \param entropy Non-null entropy data (corresponding to a mnemonic) +/// \param passphrase non-null passphrase +/// \note Null is returned on invalid input +/// \note Returned object needs to be deleted with \TWHDWalletDelete +/// \return Nullable TWHDWallet TW_EXPORT_STATIC_METHOD -struct TWHDWallet *_Nullable TWHDWalletCreateWithEntropy(TWData *_Nonnull entropy, TWString *_Nonnull passphrase); +struct TWHDWallet* _Nullable TWHDWalletCreateWithEntropy(TWData* _Nonnull entropy, TWString* _Nonnull passphrase); /// Deletes a wallet. +/// +/// \param wallet non-null TWHDWallet TW_EXPORT_METHOD -void TWHDWalletDelete(struct TWHDWallet *_Nonnull wallet); +void TWHDWalletDelete(struct TWHDWallet* _Nonnull wallet); /// Wallet seed. +/// +/// \param wallet non-null TWHDWallet +/// \return The wallet seed as a Non-null block of data. TW_EXPORT_PROPERTY -TWData *_Nonnull TWHDWalletSeed(struct TWHDWallet *_Nonnull wallet); +TWData* _Nonnull TWHDWalletSeed(struct TWHDWallet* _Nonnull wallet); -// Wallet Mnemonic +/// Wallet Mnemonic +/// +/// \param wallet non-null TWHDWallet +/// \return The wallet mnemonic as a non-null TWString TW_EXPORT_PROPERTY -TWString *_Nonnull TWHDWalletMnemonic(struct TWHDWallet *_Nonnull wallet); +TWString* _Nonnull TWHDWalletMnemonic(struct TWHDWallet* _Nonnull wallet); -// Wallet entropy +/// Wallet entropy +/// +/// \param wallet non-null TWHDWallet +/// \return The wallet entropy as a non-null block of data. TW_EXPORT_PROPERTY -TWData *_Nonnull TWHDWalletEntropy(struct TWHDWallet *_Nonnull wallet); +TWData* _Nonnull TWHDWalletEntropy(struct TWHDWallet* _Nonnull wallet); + +/// Returns master key. +/// +/// \param wallet non-null TWHDWallet +/// \param curve a curve +/// \note Returned object needs to be deleted with \TWPrivateKeyDelete +/// \return Non-null corresponding private key +TW_EXPORT_METHOD +struct TWPrivateKey* _Nonnull TWHDWalletGetMasterKey(struct TWHDWallet* _Nonnull wallet, enum TWCurve curve); + +/// Generates the default private key for the specified coin, using default derivation. +/// +/// \see TWHDWalletGetKey +/// \see TWHDWalletGetKeyDerivation +/// \param wallet non-null TWHDWallet +/// \param coin a coin type +/// \note Returned object needs to be deleted with \TWPrivateKeyDelete +/// \return return the default private key for the specified coin +TW_EXPORT_METHOD +struct TWPrivateKey* _Nonnull TWHDWalletGetKeyForCoin(struct TWHDWallet* _Nonnull wallet, enum TWCoinType coin); + +/// Generates the default address for the specified coin (without exposing intermediary private key), default derivation. +/// +/// \see TWHDWalletGetAddressDerivation +/// \param wallet non-null TWHDWallet +/// \param coin a coin type +/// \return return the default address for the specified coin as a non-null TWString +TW_EXPORT_METHOD +TWString* _Nonnull TWHDWalletGetAddressForCoin(struct TWHDWallet* _Nonnull wallet, enum TWCoinType coin); -/// Returns master key. Returned object needs to be deleted. +/// Generates the default address for the specified coin and derivation (without exposing intermediary private key). +/// +/// \see TWHDWalletGetAddressForCoin +/// \param wallet non-null TWHDWallet +/// \param coin a coin type +/// \param derivation a (custom) derivation to use +/// \return return the default address for the specified coin as a non-null TWString TW_EXPORT_METHOD -struct TWPrivateKey *_Nonnull TWHDWalletGetMasterKey(struct TWHDWallet *_Nonnull wallet, enum TWCurve curve); +TWString* _Nonnull TWHDWalletGetAddressDerivation(struct TWHDWallet* _Nonnull wallet, enum TWCoinType coin, enum TWDerivation derivation); -/// Generates the default private key for the specified coin. Returned object needs to be deleted. +/// Generates the private key for the specified derivation path. +/// +/// \see TWHDWalletGetKeyForCoin +/// \see TWHDWalletGetKeyDerivation +/// \param wallet non-null TWHDWallet +/// \param coin a coin type +/// \param derivationPath a non-null derivation path +/// \note Returned object needs to be deleted with \TWPrivateKeyDelete +/// \return The private key for the specified derivation path/coin TW_EXPORT_METHOD -struct TWPrivateKey *_Nonnull TWHDWalletGetKeyForCoin(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin); +struct TWPrivateKey* _Nonnull TWHDWalletGetKey(struct TWHDWallet* _Nonnull wallet, enum TWCoinType coin, TWString* _Nonnull derivationPath); -/// Generates the default address for the specified coin (without exposing intermediary private key). +/// Generates the private key for the specified derivation. +/// +/// \see TWHDWalletGetKey +/// \see TWHDWalletGetKeyForCoin +/// \param wallet non-null TWHDWallet +/// \param coin a coin type +/// \param derivation a (custom) derivation to use +/// \note Returned object needs to be deleted with \TWPrivateKeyDelete +/// \return The private key for the specified derivation path/coin TW_EXPORT_METHOD -TWString *_Nonnull TWHDWalletGetAddressForCoin(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin); +struct TWPrivateKey* _Nonnull TWHDWalletGetKeyDerivation(struct TWHDWallet* _Nonnull wallet, enum TWCoinType coin, enum TWDerivation derivation); -/// Generates the private key for the specified derivation path. Returned object needs to be deleted. +/// Generates the private key for the specified derivation path and curve. +/// +/// \param wallet non-null TWHDWallet +/// \param curve a curve +/// \param derivationPath a non-null derivation path +/// \note Returned object needs to be deleted with \TWPrivateKeyDelete +/// \return The private key for the specified derivation path/curve TW_EXPORT_METHOD -struct TWPrivateKey *_Nonnull TWHDWalletGetKey(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin, TWString *_Nonnull derivationPath); +struct TWPrivateKey* _Nonnull TWHDWalletGetKeyByCurve(struct TWHDWallet* _Nonnull wallet, enum TWCurve curve, TWString* _Nonnull derivationPath); -/// Shortcut method to generate private key with the specified account/change/address (bip44 standard). Returned object needs to be deleted. +/// Shortcut method to generate private key with the specified account/change/address (bip44 standard). /// -/// @see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +/// \see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +/// +/// \param wallet non-null TWHDWallet +/// \param coin a coin type +/// \param account valid bip44 account +/// \param change valid bip44 change +/// \param address valid bip44 address +/// \note Returned object needs to be deleted with \TWPrivateKeyDelete +/// \return The private key for the specified bip44 parameters TW_EXPORT_METHOD -struct TWPrivateKey *_Nonnull TWHDWalletGetDerivedKey(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin, uint32_t account, uint32_t change, uint32_t address); +struct TWPrivateKey* _Nonnull TWHDWalletGetDerivedKey(struct TWHDWallet* _Nonnull wallet, enum TWCoinType coin, uint32_t account, uint32_t change, uint32_t address); -/// Returns the extended private key (for default 0 account). Returned object needs to be deleted. +/// Returns the extended private key (for default 0 account). +/// +/// \param wallet non-null TWHDWallet +/// \param purpose a purpose +/// \param coin a coin type +/// \param version hd version +/// \note Returned object needs to be deleted with \TWStringDelete +/// \return Extended private key as a non-null TWString TW_EXPORT_METHOD -TWString *_Nonnull TWHDWalletGetExtendedPrivateKey(struct TWHDWallet *_Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWHDVersion version); +TWString* _Nonnull TWHDWalletGetExtendedPrivateKey(struct TWHDWallet* _Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWHDVersion version); -/// Returns the exteded public key (for default 0 account). Returned object needs to be deleted. +/// Returns the extended public key (for default 0 account). +/// +/// \param wallet non-null TWHDWallet +/// \param purpose a purpose +/// \param coin a coin type +/// \param version hd version +/// \note Returned object needs to be deleted with \TWStringDelete +/// \return Extended public key as a non-null TWString TW_EXPORT_METHOD -TWString *_Nonnull TWHDWalletGetExtendedPublicKey(struct TWHDWallet *_Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWHDVersion version); +TWString* _Nonnull TWHDWalletGetExtendedPublicKey(struct TWHDWallet* _Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWHDVersion version); -/// Returns the extended private key, for custom account. Returned object needs to be deleted. +/// Returns the extended private key, for custom account. +/// +/// \param wallet non-null TWHDWallet +/// \param purpose a purpose +/// \param coin a coin type +/// \param derivation a derivation +/// \param version an hd version +/// \param account valid bip44 account +/// \note Returned object needs to be deleted with \TWStringDelete +/// \return Extended private key as a non-null TWString TW_EXPORT_METHOD -TWString *_Nonnull TWHDWalletGetExtendedPrivateKeyAccount(struct TWHDWallet *_Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWHDVersion version, uint32_t account); +TWString* _Nonnull TWHDWalletGetExtendedPrivateKeyAccount(struct TWHDWallet* _Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWDerivation derivation, enum TWHDVersion version, uint32_t account); -/// Returns the exteded public key, for custom account. Returned object needs to be deleted. +/// Returns the extended public key, for custom account. +/// +/// \param wallet non-null TWHDWallet +/// \param purpose a purpose +/// \param coin a coin type +/// \param derivation a derivation +/// \param version an hd version +/// \param account valid bip44 account +/// \note Returned object needs to be deleted with \TWStringDelete +/// \return Extended public key as a non-null TWString +TW_EXPORT_METHOD +TWString* _Nonnull TWHDWalletGetExtendedPublicKeyAccount(struct TWHDWallet* _Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWDerivation derivation, enum TWHDVersion version, uint32_t account); + +/// Returns the extended private key (for default 0 account with derivation). +/// +/// \param wallet non-null TWHDWallet +/// \param purpose a purpose +/// \param coin a coin type +/// \param derivation a derivation +/// \param version an hd version +/// \note Returned object needs to be deleted with \TWStringDelete +/// \return Extended private key as a non-null TWString TW_EXPORT_METHOD -TWString *_Nonnull TWHDWalletGetExtendedPublicKeyAccount(struct TWHDWallet *_Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWHDVersion version, uint32_t account); +TWString* _Nonnull TWHDWalletGetExtendedPrivateKeyDerivation(struct TWHDWallet* _Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWDerivation derivation, enum TWHDVersion version); -/// Computes the public key from an exteded public key representation. Returned object needs to be deleted. +/// Returns the extended public key (for default 0 account with derivation). +/// +/// \param wallet non-null TWHDWallet +/// \param purpose a purpose +/// \param coin a coin type +/// \param derivation a derivation +/// \param version an hd version +/// \note Returned object needs to be deleted with \TWStringDelete +/// \return Extended public key as a non-null TWString +TW_EXPORT_METHOD +TWString* _Nonnull TWHDWalletGetExtendedPublicKeyDerivation(struct TWHDWallet* _Nonnull wallet, enum TWPurpose purpose, enum TWCoinType coin, enum TWDerivation derivation, enum TWHDVersion version); + +/// Computes the public key from an extended public key representation. +/// +/// \param extended extended public key +/// \param coin a coin type +/// \param derivationPath a derivation path +/// \note Returned object needs to be deleted with \TWPublicKeyDelete +/// \return Nullable TWPublic key TW_EXPORT_STATIC_METHOD -struct TWPublicKey *_Nullable TWHDWalletGetPublicKeyFromExtended(TWString *_Nonnull extended, enum TWCoinType coin, TWString *_Nonnull derivationPath); +struct TWPublicKey* _Nullable TWHDWalletGetPublicKeyFromExtended(TWString* _Nonnull extended, enum TWCoinType coin, TWString* _Nonnull derivationPath); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWHash.h b/include/TrustWalletCore/TWHash.h index 79bdaffc7c2..06024bb4e4f 100644 --- a/include/TrustWalletCore/TWHash.h +++ b/include/TrustWalletCore/TWHash.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -11,6 +9,7 @@ TW_EXTERN_C_BEGIN +/// Hash functions TW_EXPORT_STRUCT struct TWHash { uint8_t unused; // C doesn't allow zero-sized struct @@ -22,57 +21,131 @@ static const size_t TWHashSHA512Length = 64; static const size_t TWHashRipemdLength = 20; /// Computes the SHA1 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA1 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA1(TWData *_Nonnull data); +/// Computes the SHA256 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA256 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA256(TWData *_Nonnull data); +/// Computes the SHA512 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA512 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA512(TWData *_Nonnull data); +/// Computes the SHA512_256 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA512_256 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA512_256(TWData *_Nonnull data); +/// Computes the Keccak256 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed Keccak256 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashKeccak256(TWData *_Nonnull data); +/// Computes the Keccak512 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed Keccak512 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashKeccak512(TWData *_Nonnull data); +/// Computes the SHA3_256 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA3_256 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA3_256(TWData *_Nonnull data); +/// Computes the SHA3_512 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA3_512 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA3_512(TWData *_Nonnull data); +/// Computes the RIPEMD of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed RIPEMD block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashRIPEMD(TWData *_Nonnull data); +/// Computes the Blake256 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed Blake256 block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashBlake256(TWData *_Nonnull data); +/// Computes the Blake2b of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed Blake2b block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashBlake2b(TWData *_Nonnull data, size_t size); +/// Computes the Groestl512 of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed Groestl512 block of data +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWHashBlake2bPersonal(TWData *_Nonnull data, TWData * _Nonnull personal, size_t outlen); + TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashGroestl512(TWData *_Nonnull data); +/// Computes the SHA256D of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA256D block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA256SHA256(TWData *_Nonnull data); +/// Computes the SHA256RIPEMD of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA256RIPEMD block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA256RIPEMD(TWData *_Nonnull data); +/// Computes the SHA3_256RIPEMD of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed SHA3_256RIPEMD block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashSHA3_256RIPEMD(TWData *_Nonnull data); +/// Computes the Blake256D of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed Blake256D block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashBlake256Blake256(TWData *_Nonnull data); +/// Computes the Blake256RIPEMD of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed Blake256RIPEMD block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashBlake256RIPEMD(TWData *_Nonnull data); +/// Computes the Groestl512D of a block of data. +/// +/// \param data Non-null block of data +/// \return Non-null computed Groestl512D block of data TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWHashGroestl512Groestl512(TWData *_Nonnull data); diff --git a/include/TrustWalletCore/TWLiquidStaking.h b/include/TrustWalletCore/TWLiquidStaking.h new file mode 100644 index 00000000000..a50f3e2709b --- /dev/null +++ b/include/TrustWalletCore/TWLiquidStaking.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// THORChain swap functions +TW_EXPORT_STRUCT +struct TWLiquidStaking; + +/// Builds a LiquidStaking transaction input. +/// +/// \param input The serialized data of LiquidStakingInput. +/// \return The serialized data of LiquidStakingOutput. +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWLiquidStakingBuildRequest(TWData *_Nonnull input); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWMnemonic.h b/include/TrustWalletCore/TWMnemonic.h index b8b60d46158..2cfba1dba70 100644 --- a/include/TrustWalletCore/TWMnemonic.h +++ b/include/TrustWalletCore/TWMnemonic.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -11,18 +9,28 @@ TW_EXTERN_C_BEGIN +/// Mnemonic validate / lookup functions TW_EXPORT_STRUCT struct TWMnemonic; /// Determines whether a BIP39 English mnemonic phrase is valid. +/// +/// \param mnemonic Non-null BIP39 english mnemonic +/// \return true if the mnemonic is valid, false otherwise TW_EXPORT_STATIC_METHOD bool TWMnemonicIsValid(TWString *_Nonnull mnemonic); -/// Determines whether word is a valid BIP39 English menemonic word. +/// Determines whether word is a valid BIP39 English mnemonic word. +/// +/// \param word Non-null BIP39 English mnemonic word +/// \return true if the word is a valid BIP39 English mnemonic word, false otherwise TW_EXPORT_STATIC_METHOD bool TWMnemonicIsValidWord(TWString *_Nonnull word); /// Return BIP39 English words that match the given prefix. A single string is returned, with space-separated list of words. +/// +/// \param prefix Non-null string prefix +/// \return Single non-null string, space-separated list of words containing BIP39 words that match the given prefix. TW_EXPORT_STATIC_METHOD TWString* _Nonnull TWMnemonicSuggest(TWString *_Nonnull prefix); diff --git a/include/TrustWalletCore/TWNEARAccount.h b/include/TrustWalletCore/TWNEARAccount.h index 66b4c8ee3ec..cd8fbb4b97a 100644 --- a/include/TrustWalletCore/TWNEARAccount.h +++ b/include/TrustWalletCore/TWNEARAccount.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -15,13 +13,24 @@ TW_EXTERN_C_BEGIN TW_EXPORT_CLASS struct TWNEARAccount; +/// Create a NEAR Account +/// +/// \param string Account name +/// \note Account should be deleted by calling \TWNEARAccountDelete +/// \return Pointer to a nullable NEAR Account. TW_EXPORT_STATIC_METHOD struct TWNEARAccount *_Nullable TWNEARAccountCreateWithString(TWString *_Nonnull string); +/// Delete the given Near Account +/// +/// \param account Pointer to a non-null NEAR Account TW_EXPORT_METHOD void TWNEARAccountDelete(struct TWNEARAccount *_Nonnull account); /// Returns the user friendly string representation. +/// +/// \param account Pointer to a non-null NEAR Account +/// \return Non-null string account description TW_EXPORT_PROPERTY TWString *_Nonnull TWNEARAccountDescription(struct TWNEARAccount *_Nonnull account); diff --git a/include/TrustWalletCore/TWNervosAddress.h b/include/TrustWalletCore/TWNervosAddress.h new file mode 100644 index 00000000000..0254c2bf5cf --- /dev/null +++ b/include/TrustWalletCore/TWNervosAddress.h @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// Represents a Nervos address. +TW_EXPORT_CLASS +struct TWNervosAddress; + +/// Compares two addresses for equality. +/// +/// \param lhs The first address to compare. +/// \param rhs The second address to compare. +/// \return bool indicating the addresses are equal. +TW_EXPORT_STATIC_METHOD +bool TWNervosAddressEqual(struct TWNervosAddress *_Nonnull lhs, struct TWNervosAddress *_Nonnull rhs); + +/// Determines if the string is a valid Nervos address. +/// +/// \param string string to validate. +/// \return bool indicating if the address is valid. +TW_EXPORT_STATIC_METHOD +bool TWNervosAddressIsValidString(TWString *_Nonnull string); + +/// Initializes an address from a sring representaion. +/// +/// \param string Bech32 string to initialize the address from. +/// \return TWNervosAddress pointer or nullptr if string is invalid. +TW_EXPORT_STATIC_METHOD +struct TWNervosAddress *_Nullable TWNervosAddressCreateWithString(TWString *_Nonnull string); + +/// Deletes a Nervos address. +/// +/// \param address Address to delete. +TW_EXPORT_METHOD +void TWNervosAddressDelete(struct TWNervosAddress *_Nonnull address); + +/// Returns the address string representation. +/// +/// \param address Address to get the string representation of. +TW_EXPORT_PROPERTY +TWString *_Nonnull TWNervosAddressDescription(struct TWNervosAddress *_Nonnull address); + +/// Returns the Code hash +/// +/// \param address Address to get the keyhash data of. +TW_EXPORT_PROPERTY +TWData *_Nonnull TWNervosAddressCodeHash(struct TWNervosAddress *_Nonnull address); + +/// Returns the address hash type +/// +/// \param address Address to get the hash type of. +TW_EXPORT_PROPERTY +TWString *_Nonnull TWNervosAddressHashType(struct TWNervosAddress *_Nonnull address); + +/// Returns the address args data. +/// +/// \param address Address to get the args data of. +TW_EXPORT_PROPERTY +TWData *_Nonnull TWNervosAddressArgs(struct TWNervosAddress *_Nonnull address); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWPBKDF2.h b/include/TrustWalletCore/TWPBKDF2.h index dc3bb4dcf28..86c6cca6801 100644 --- a/include/TrustWalletCore/TWPBKDF2.h +++ b/include/TrustWalletCore/TWPBKDF2.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -11,6 +9,7 @@ TW_EXTERN_C_BEGIN +/// Password-Based Key Derivation Function 2 TW_EXPORT_STRUCT struct TWPBKDF2; @@ -20,6 +19,7 @@ struct TWPBKDF2; /// \param salt is a sequence of bits, known as a cryptographic salt /// \param iterations is the number of iterations desired /// \param dkLen is the desired bit-length of the derived key +/// \return the derived key data. TW_EXPORT_STATIC_METHOD TWData *_Nullable TWPBKDF2HmacSha256(TWData *_Nonnull password, TWData *_Nonnull salt, uint32_t iterations, uint32_t dkLen); @@ -29,6 +29,7 @@ TWData *_Nullable TWPBKDF2HmacSha256(TWData *_Nonnull password, TWData *_Nonnull /// \param salt is a sequence of bits, known as a cryptographic salt /// \param iterations is the number of iterations desired /// \param dkLen is the desired bit-length of the derived key +/// \return the derived key data. TW_EXPORT_STATIC_METHOD TWData *_Nullable TWPBKDF2HmacSha512(TWData *_Nonnull password, TWData *_Nonnull salt, uint32_t iterations, uint32_t dkLen); diff --git a/include/TrustWalletCore/TWPrivateKey.h b/include/TrustWalletCore/TWPrivateKey.h index 8400050e654..5fdfc61bcde 100644 --- a/include/TrustWalletCore/TWPrivateKey.h +++ b/include/TrustWalletCore/TWPrivateKey.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -10,71 +8,142 @@ #include "TWCurve.h" #include "TWData.h" #include "TWPublicKey.h" +#include "TWCoinType.h" TW_EXTERN_C_BEGIN +/// Represents a private key. TW_EXPORT_CLASS struct TWPrivateKey; static const size_t TWPrivateKeySize = 32; +/// Create a random private key +/// +/// \note Should be deleted with \TWPrivateKeyDelete +/// \return Non-null Private key TW_EXPORT_STATIC_METHOD -struct TWPrivateKey *_Nonnull TWPrivateKeyCreate(void); +struct TWPrivateKey* _Nonnull TWPrivateKeyCreate(void); +/// Create a private key with the given block of data +/// +/// \param data a block of data +/// \note Should be deleted with \TWPrivateKeyDelete +/// \return Nullable pointer to Private Key TW_EXPORT_STATIC_METHOD -struct TWPrivateKey *_Nullable TWPrivateKeyCreateWithData(TWData *_Nonnull data); +struct TWPrivateKey* _Nullable TWPrivateKeyCreateWithData(TWData* _Nonnull data); +/// Deep copy a given private key +/// +/// \param key Non-null private key to be copied +/// \note Should be deleted with \TWPrivateKeyDelete +/// \return Deep copy, Nullable pointer to Private key TW_EXPORT_STATIC_METHOD -struct TWPrivateKey *_Nullable TWPrivateKeyCreateCopy(struct TWPrivateKey *_Nonnull key); +struct TWPrivateKey* _Nullable TWPrivateKeyCreateCopy(struct TWPrivateKey* _Nonnull key); +/// Delete the given private key +/// +/// \param pk Non-null pointer to private key TW_EXPORT_METHOD -void TWPrivateKeyDelete(struct TWPrivateKey *_Nonnull pk); +void TWPrivateKeyDelete(struct TWPrivateKey* _Nonnull pk); +/// Determines if the given private key is valid or not. +/// +/// \param data block of data (private key bytes) +/// \param curve Eliptic curve of the private key +/// \return true if the private key is valid, false otherwise TW_EXPORT_STATIC_METHOD -bool TWPrivateKeyIsValid(TWData *_Nonnull data, enum TWCurve curve); +bool TWPrivateKeyIsValid(TWData* _Nonnull data, enum TWCurve curve); +/// Convert the given private key to raw-bytes block of data +/// +/// \param pk Non-null pointer to the private key +/// \return Non-null block of data (raw bytes) of the given private key TW_EXPORT_PROPERTY -TWData *_Nonnull TWPrivateKeyData(struct TWPrivateKey *_Nonnull pk); +TWData* _Nonnull TWPrivateKeyData(struct TWPrivateKey* _Nonnull pk); -/// Returns the public key associated with this private key. +/// Returns the public key associated with the given coinType and privateKey +/// +/// \param pk Non-null pointer to the private key +/// \param coinType coinType of the given private key +/// \return Non-null pointer to the corresponding public key TW_EXPORT_METHOD -struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeySecp256k1(struct TWPrivateKey *_Nonnull pk, bool compressed); +struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKey(struct TWPrivateKey* _Nonnull pk, enum TWCoinType coinType); -/// Returns the public key associated with this private key. +/// Returns the public key associated with the given pubkeyType and privateKey +/// +/// \param pk Non-null pointer to the private key +/// \param pubkeyType pubkeyType of the given private key +/// \return Non-null pointer to the corresponding public key TW_EXPORT_METHOD -struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyNist256p1(struct TWPrivateKey *_Nonnull pk); +struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyByType(struct TWPrivateKey* _Nonnull pk, enum TWPublicKeyType pubkeyType); -/// Returns the public key associated with this private key. +/// Returns the Secp256k1 public key associated with the given private key +/// +/// \param pk Non-null pointer to the private key +/// \param compressed if the given private key is compressed or not +/// \return Non-null pointer to the corresponding public key TW_EXPORT_METHOD -struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyEd25519(struct TWPrivateKey *_Nonnull pk); +struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeySecp256k1(struct TWPrivateKey* _Nonnull pk, bool compressed); -/// Returns the public key associated with this private key. +/// Returns the Nist256p1 public key associated with the given private key +/// +/// \param pk Non-null pointer to the private key +/// \return Non-null pointer to the corresponding public key TW_EXPORT_METHOD -struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyEd25519Blake2b(struct TWPrivateKey *_Nonnull pk); +struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyNist256p1(struct TWPrivateKey* _Nonnull pk); -/// Returns the public key associated with this private key. +/// Returns the Ed25519 public key associated with the given private key +/// +/// \param pk Non-null pointer to the private key +/// \return Non-null pointer to the corresponding public key TW_EXPORT_METHOD -struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyEd25519Extended(struct TWPrivateKey *_Nonnull pk); +struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyEd25519(struct TWPrivateKey* _Nonnull pk); -/// Returns the public key associated with this private key. +/// Returns the Ed25519Blake2b public key associated with the given private key +/// +/// \param pk Non-null pointer to the private key +/// \return Non-null pointer to the corresponding public key TW_EXPORT_METHOD -struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyCurve25519(struct TWPrivateKey *_Nonnull pk); +struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyEd25519Blake2b(struct TWPrivateKey* _Nonnull pk); -/// Computes an EC Diffie-Hellman secret in constant time -/// Supported curves: secp256k1 +/// Returns the Ed25519Cardano public key associated with the given private key +/// +/// \param pk Non-null pointer to the private key +/// \return Non-null pointer to the corresponding public key TW_EXPORT_METHOD -TWData *_Nullable TWPrivateKeyGetSharedKey(const struct TWPrivateKey *_Nonnull pk, const struct TWPublicKey *_Nonnull publicKey, enum TWCurve curve); +struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyEd25519Cardano(struct TWPrivateKey* _Nonnull pk); + +/// Returns the Curve25519 public key associated with the given private key +/// +/// \param pk Non-null pointer to the private key +/// \return Non-null pointer to the corresponding public key +TW_EXPORT_METHOD +struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyCurve25519(struct TWPrivateKey* _Nonnull pk); /// Signs a digest using ECDSA and given curve. +/// +/// \param pk Non-null pointer to a Private key +/// \param digest Non-null digest block of data +/// \param curve Eliptic curve +/// \return Signature as a Non-null block of data TW_EXPORT_METHOD -TWData *_Nullable TWPrivateKeySign(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull digest, enum TWCurve curve); +TWData* _Nullable TWPrivateKeySign(struct TWPrivateKey* _Nonnull pk, TWData* _Nonnull digest, enum TWCurve curve); -/// Signs a digest using ECDSA and given curve. The result is encoded with DER. +/// Signs a digest using ECDSA. The result is encoded with DER. +/// +/// \param pk Non-null pointer to a Private key +/// \param digest Non-null digest block of data +/// \return Signature as a Non-null block of data TW_EXPORT_METHOD -TWData *_Nullable TWPrivateKeySignAsDER(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull digest, enum TWCurve curve); +TWData* _Nullable TWPrivateKeySignAsDER(struct TWPrivateKey* _Nonnull pk, TWData* _Nonnull digest); -/// Signs a digest using ECDSA and given curve, returns schnoor signature. +/// Signs a digest using ECDSA and Zilliqa schnorr signature scheme. +/// +/// \param pk Non-null pointer to a Private key +/// \param message Non-null message +/// \return Signature as a Non-null block of data TW_EXPORT_METHOD -TWData *_Nullable TWPrivateKeySignSchnorr(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull message, enum TWCurve curve); +TWData* _Nullable TWPrivateKeySignZilliqaSchnorr(struct TWPrivateKey* _Nonnull pk, TWData* _Nonnull message); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWPrivateKeyType.h b/include/TrustWalletCore/TWPrivateKeyType.h new file mode 100644 index 00000000000..ee9255e0893 --- /dev/null +++ b/include/TrustWalletCore/TWPrivateKeyType.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +/// Private key types, the vast majority of chains use the default, 32-byte key. +TW_EXPORT_ENUM(uint32_t) +enum TWPrivateKeyType { + TWPrivateKeyTypeDefault = 0, // 32 bytes long + TWPrivateKeyTypeCardano = 1, // 2 extended keys plus chainCode, 96 bytes long, used by Cardano +}; + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWPublicKey.h b/include/TrustWalletCore/TWPublicKey.h index 313dee28fb0..7187ac564af 100644 --- a/include/TrustWalletCore/TWPublicKey.h +++ b/include/TrustWalletCore/TWPublicKey.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -16,45 +14,108 @@ TW_EXTERN_C_BEGIN static const size_t TWPublicKeyCompressedSize = 33; static const size_t TWPublicKeyUncompressedSize = 65; +/// Represents a public key. TW_EXPORT_CLASS struct TWPublicKey; +/// Create a public key from a block of data +/// +/// \param data Non-null block of data representing the public key +/// \param type type of the public key +/// \note Should be deleted with \TWPublicKeyDelete +/// \return Nullable pointer to the public key TW_EXPORT_STATIC_METHOD struct TWPublicKey *_Nullable TWPublicKeyCreateWithData(TWData *_Nonnull data, enum TWPublicKeyType type); +/// Delete the given public key +/// +/// \param pk Non-null pointer to a public key TW_EXPORT_METHOD void TWPublicKeyDelete(struct TWPublicKey *_Nonnull pk); +/// Determines if the given public key is valid or not +/// +/// \param data Non-null block of data representing the public key +/// \param type type of the public key +/// \return true if the block of data is a valid public key, false otherwise TW_EXPORT_STATIC_METHOD bool TWPublicKeyIsValid(TWData *_Nonnull data, enum TWPublicKeyType type); +/// Determines if the given public key is compressed or not +/// +/// \param pk Non-null pointer to a public key +/// \return true if the public key is compressed, false otherwise TW_EXPORT_PROPERTY bool TWPublicKeyIsCompressed(struct TWPublicKey *_Nonnull pk); +/// Give the compressed public key of the given non-compressed public key +/// +/// \param from Non-null pointer to a non-compressed public key +/// \return Non-null pointer to the corresponding compressed public-key TW_EXPORT_PROPERTY struct TWPublicKey *_Nonnull TWPublicKeyCompressed(struct TWPublicKey *_Nonnull from); +/// Give the non-compressed public key of a corresponding compressed public key +/// +/// \param from Non-null pointer to the corresponding compressed public key +/// \return Non-null pointer to the corresponding non-compressed public key TW_EXPORT_PROPERTY struct TWPublicKey *_Nonnull TWPublicKeyUncompressed(struct TWPublicKey *_Nonnull from); +/// Gives the raw data of a given public-key +/// +/// \param pk Non-null pointer to a public key +/// \return Non-null pointer to the raw block of data of the given public key TW_EXPORT_PROPERTY TWData *_Nonnull TWPublicKeyData(struct TWPublicKey *_Nonnull pk); +/// Verify the validity of a signature and a message using the given public key +/// +/// \param pk Non-null pointer to a public key +/// \param signature Non-null pointer to a block of data corresponding to the signature +/// \param message Non-null pointer to a block of data corresponding to the message +/// \return true if the signature and the message belongs to the given public key, false otherwise TW_EXPORT_METHOD bool TWPublicKeyVerify(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message); +/// Verify the validity as DER of a signature and a message using the given public key +/// +/// \param pk Non-null pointer to a public key +/// \param signature Non-null pointer to a block of data corresponding to the signature +/// \param message Non-null pointer to a block of data corresponding to the message +/// \return true if the signature and the message belongs to the given public key, false otherwise TW_EXPORT_METHOD bool TWPublicKeyVerifyAsDER(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message); +/// Verify a Zilliqa schnorr signature with a signature and message. +/// +/// \param pk Non-null pointer to a public key +/// \param signature Non-null pointer to a block of data corresponding to the signature +/// \param message Non-null pointer to a block of data corresponding to the message +/// \return true if the signature and the message belongs to the given public key, false otherwise TW_EXPORT_METHOD -bool TWPublicKeyVerifySchnorr(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message); +bool TWPublicKeyVerifyZilliqaSchnorr(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message); +/// Give the public key type (eliptic) of a given public key +/// +/// \param publicKey Non-null pointer to a public key +/// \return The public key type of the given public key (eliptic) TW_EXPORT_PROPERTY enum TWPublicKeyType TWPublicKeyKeyType(struct TWPublicKey *_Nonnull publicKey); +/// Get the public key description from a given public key +/// +/// \param publicKey Non-null pointer to a public key +/// \return Non-null pointer to a string representing the description of the public key TW_EXPORT_PROPERTY TWString *_Nonnull TWPublicKeyDescription(struct TWPublicKey *_Nonnull publicKey); +/// Try to get a public key from a given signature and a message +/// +/// \param signature Non-null pointer to a block of data corresponding to the signature +/// \param message Non-null pointer to a block of data corresponding to the message +/// \return Null pointer if the public key can't be recover from the given signature and message, +/// pointer to the public key otherwise TW_EXPORT_STATIC_METHOD struct TWPublicKey *_Nullable TWPublicKeyRecover(TWData *_Nonnull signature, TWData *_Nonnull message); diff --git a/include/TrustWalletCore/TWPublicKeyType.h b/include/TrustWalletCore/TWPublicKeyType.h index 29d2b181064..f175fc8c471 100644 --- a/include/TrustWalletCore/TWPublicKeyType.h +++ b/include/TrustWalletCore/TWPublicKeyType.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -20,7 +18,8 @@ enum TWPublicKeyType { TWPublicKeyTypeED25519 = 4, TWPublicKeyTypeED25519Blake2b = 5, TWPublicKeyTypeCURVE25519 = 6, - TWPublicKeyTypeED25519Extended = 7, // used by Cardano + TWPublicKeyTypeED25519Cardano = 7, + TWPublicKeyTypeStarkex = 8, }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWPurpose.h b/include/TrustWalletCore/TWPurpose.h index ad4d9206cc7..8cfa9cd91ae 100644 --- a/include/TrustWalletCore/TWPurpose.h +++ b/include/TrustWalletCore/TWPurpose.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,9 +10,9 @@ TW_EXTERN_C_BEGIN /// HD wallet purpose /// -/// See https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki -/// See https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki -/// See https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki +/// \see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +/// \see https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki +/// \see https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki TW_EXPORT_ENUM(uint32_t) enum TWPurpose { TWPurposeBIP44 = 44, diff --git a/include/TrustWalletCore/TWRippleXAddress.h b/include/TrustWalletCore/TWRippleXAddress.h index 8b41daf1a82..3c9256d2613 100644 --- a/include/TrustWalletCore/TWRippleXAddress.h +++ b/include/TrustWalletCore/TWRippleXAddress.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -20,29 +18,54 @@ TW_EXPORT_CLASS struct TWRippleXAddress; /// Compares two addresses for equality. +/// +/// \param lhs left non-null pointer to a Ripple Address +/// \param rhs right non-null pointer to a Ripple Address +/// \return true if both address are equal, false otherwise TW_EXPORT_STATIC_METHOD bool TWRippleXAddressEqual(struct TWRippleXAddress *_Nonnull lhs, struct TWRippleXAddress *_Nonnull rhs); /// Determines if the string is a valid Ripple address. +/// +/// \param string Non-null pointer to a string that represent the Ripple Address to be checked +/// \return true if the given address is a valid Ripple address, false otherwise TW_EXPORT_STATIC_METHOD bool TWRippleXAddressIsValidString(TWString *_Nonnull string); -/// Creates an address from a string representaion. +/// Creates an address from a string representation. +/// +/// \param string Non-null pointer to a string that should be a valid ripple address +/// \note Should be deleted with \TWRippleXAddressDelete +/// \return Null pointer if the given string is an invalid ripple address, pointer to a Ripple address otherwise TW_EXPORT_STATIC_METHOD struct TWRippleXAddress *_Nullable TWRippleXAddressCreateWithString(TWString *_Nonnull string); /// Creates an address from a public key and destination tag. +/// +/// \param publicKey Non-null pointer to a public key +/// \param tag valid ripple destination tag (1-10) +/// \note Should be deleted with \TWRippleXAddressDelete +/// \return Non-null pointer to a Ripple Address TW_EXPORT_STATIC_METHOD struct TWRippleXAddress *_Nonnull TWRippleXAddressCreateWithPublicKey(struct TWPublicKey *_Nonnull publicKey, uint32_t tag); +/// Delete the given ripple address +/// +/// \param address Non-null pointer to a Ripple Address TW_EXPORT_METHOD void TWRippleXAddressDelete(struct TWRippleXAddress *_Nonnull address); /// Returns the address string representation. +/// +/// \param address Non-null pointer to a Ripple Address +/// \return Non-null pointer to the ripple address string representation TW_EXPORT_PROPERTY TWString *_Nonnull TWRippleXAddressDescription(struct TWRippleXAddress *_Nonnull address); /// Returns the destination tag. +/// +/// \param address Non-null pointer to a Ripple Address +/// \return The destination tag of the given Ripple Address (1-10) TW_EXPORT_PROPERTY uint32_t TWRippleXAddressTag(struct TWRippleXAddress *_Nonnull address); diff --git a/include/TrustWalletCore/TWSS58AddressType.h b/include/TrustWalletCore/TWSS58AddressType.h index 13ca9769e39..9f9010c670e 100644 --- a/include/TrustWalletCore/TWSS58AddressType.h +++ b/include/TrustWalletCore/TWSS58AddressType.h @@ -1,9 +1,7 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -11,9 +9,9 @@ TW_EXTERN_C_BEGIN -/// Substrate based chains Address Type +/// Substrate based chains Address Type /// -/// - See Also: https://github.com/paritytech/substrate/wiki/External-Address-Format-(SS58)#address-type +/// \see https://github.com/paritytech/substrate/wiki/External-Address-Format-(SS58)#address-type TW_EXPORT_ENUM(uint8_t) enum TWSS58AddressType { TWSS58AddressTypePolkadot = 0, diff --git a/include/TrustWalletCore/TWSegwitAddress.h b/include/TrustWalletCore/TWSegwitAddress.h index cdc7ece7297..452b4bc2ed0 100644 --- a/include/TrustWalletCore/TWSegwitAddress.h +++ b/include/TrustWalletCore/TWSegwitAddress.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -20,38 +18,69 @@ TW_EXPORT_CLASS struct TWSegwitAddress; /// Compares two addresses for equality. +/// +/// \param lhs left non-null pointer to a Bech32 Address +/// \param rhs right non-null pointer to a Bech32 Address +/// \return true if both address are equal, false otherwise TW_EXPORT_STATIC_METHOD bool TWSegwitAddressEqual(struct TWSegwitAddress *_Nonnull lhs, struct TWSegwitAddress *_Nonnull rhs); /// Determines if the string is a valid Bech32 address. +/// +/// \param string Non-null pointer to a Bech32 address as a string +/// \return true if the string is a valid Bech32 address, false otherwise. TW_EXPORT_STATIC_METHOD bool TWSegwitAddressIsValidString(TWString *_Nonnull string); -/// Creates an address from a string representaion. +/// Creates an address from a string representation. +/// +/// \param string Non-null pointer to a Bech32 address as a string +/// \note should be deleted with \TWSegwitAddressDelete +/// \return Pointer to a Bech32 address if the string is a valid Bech32 address, null pointer otherwise TW_EXPORT_STATIC_METHOD struct TWSegwitAddress *_Nullable TWSegwitAddressCreateWithString(TWString *_Nonnull string); /// Creates a segwit-version-0 address from a public key and HRP prefix. /// Taproot (v>=1) is not supported by this method. +/// +/// \param hrp HRP of the utxo coin targeted +/// \param publicKey Non-null pointer to the public key of the targeted coin +/// \note should be deleted with \TWSegwitAddressDelete +/// \return Non-null pointer to the corresponding Segwit address TW_EXPORT_STATIC_METHOD struct TWSegwitAddress *_Nonnull TWSegwitAddressCreateWithPublicKey(enum TWHRP hrp, struct TWPublicKey *_Nonnull publicKey); +/// Delete the given Segwit address +/// +/// \param address Non-null pointer to a Segwit address TW_EXPORT_METHOD void TWSegwitAddressDelete(struct TWSegwitAddress *_Nonnull address); /// Returns the address string representation. +/// +/// \param address Non-null pointer to a Segwit Address +/// \return Non-null pointer to the segwit address string representation TW_EXPORT_PROPERTY TWString *_Nonnull TWSegwitAddressDescription(struct TWSegwitAddress *_Nonnull address); /// Returns the human-readable part. +/// +/// \param address Non-null pointer to a Segwit Address +/// \return the HRP part of the given address TW_EXPORT_PROPERTY enum TWHRP TWSegwitAddressHRP(struct TWSegwitAddress *_Nonnull address); /// Returns the human-readable part. +/// +/// \param address Non-null pointer to a Segwit Address +/// \return returns the witness version of the given segwit address TW_EXPORT_PROPERTY int TWSegwitAddressWitnessVersion(struct TWSegwitAddress *_Nonnull address); /// Returns the witness program +/// +/// \param address Non-null pointer to a Segwit Address +/// \return returns the witness data of the given segwit address as a non-null pointer block of data TW_EXPORT_PROPERTY TWData *_Nonnull TWSegwitAddressWitnessProgram(struct TWSegwitAddress *_Nonnull address); diff --git a/include/TrustWalletCore/TWSolanaAddress.h b/include/TrustWalletCore/TWSolanaAddress.h index 76f7fe863e7..3a21460e759 100644 --- a/include/TrustWalletCore/TWSolanaAddress.h +++ b/include/TrustWalletCore/TWSolanaAddress.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -11,21 +9,44 @@ TW_EXTERN_C_BEGIN +/// Solana address helper functions TW_EXPORT_CLASS struct TWSolanaAddress; -/// Creates an address from a string representaion. +/// Creates an address from a string representation. +/// +/// \param string Non-null pointer to a solana address string +/// \note Should be deleted with \TWSolanaAddressDelete +/// \return Non-null pointer to a Solana address data structure TW_EXPORT_STATIC_METHOD struct TWSolanaAddress* _Nullable TWSolanaAddressCreateWithString(TWString* _Nonnull string); +/// Delete the given Solana address +/// +/// \param address Non-null pointer to a Solana Address TW_EXPORT_METHOD void TWSolanaAddressDelete(struct TWSolanaAddress* _Nonnull address); /// Derive default token address for token +/// +/// \param address Non-null pointer to a Solana Address +/// \param tokenMintAddress Non-null pointer to a token mint address as a string +/// \return Null pointer if the Default token address for a token is not found, valid pointer otherwise TW_EXPORT_METHOD TWString* _Nullable TWSolanaAddressDefaultTokenAddress(struct TWSolanaAddress* _Nonnull address, TWString* _Nonnull tokenMintAddress); +/// Derive token 2022 address for token +/// +/// \param address Non-null pointer to a Solana Address +/// \param tokenMintAddress Non-null pointer to a token mint address as a string +/// \return Null pointer if the token 2022 address for a token is not found, valid pointer otherwise +TW_EXPORT_METHOD +TWString* _Nullable TWSolanaAddressToken2022Address(struct TWSolanaAddress* _Nonnull address, TWString* _Nonnull tokenMintAddress); + /// Returns the address string representation. +/// +/// \param address Non-null pointer to a Solana Address +/// \return Non-null pointer to the Solana address string representation TW_EXPORT_PROPERTY TWString *_Nonnull TWSolanaAddressDescription(struct TWSolanaAddress *_Nonnull address); diff --git a/include/TrustWalletCore/TWSolanaTransaction.h b/include/TrustWalletCore/TWSolanaTransaction.h new file mode 100644 index 00000000000..681313ff248 --- /dev/null +++ b/include/TrustWalletCore/TWSolanaTransaction.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWDataVector.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +TW_EXPORT_STRUCT +struct TWSolanaTransaction; + +/// Decode Solana transaction, update the recent blockhash and re-sign the transaction. +/// +/// # Warning +/// +/// This is a temporary solution. It will be removed when `Solana.proto` supports +/// direct transaction signing. +/// +/// \param encodedTx base64 encoded Solana transaction. +/// \param recentBlockhash base58 encoded recent blockhash. +/// \param privateKeys list of private keys that should be used to re-sign the transaction. +/// \return serialized `Solana::Proto::SigningOutput`. +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWSolanaTransactionUpdateBlockhashAndSign(TWString *_Nonnull encodedTx, + TWString *_Nonnull recentBlockhash, + const struct TWDataVector *_Nonnull privateKeys); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWStarkExMessageSigner.h b/include/TrustWalletCore/TWStarkExMessageSigner.h new file mode 100644 index 00000000000..d5299f4f026 --- /dev/null +++ b/include/TrustWalletCore/TWStarkExMessageSigner.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" +#include "TWPrivateKey.h" + +TW_EXTERN_C_BEGIN + +/// StarkEx message signing and verification. +/// +/// StarkEx and some other wallets support a message signing & verification format, to create a proof (a signature) +/// that someone has access to the private keys of a specific address. +TW_EXPORT_STRUCT +struct TWStarkExMessageSigner; + +/// Sign a message. +/// +/// \param privateKey: the private key used for signing +/// \param message: A custom hex message which is input to the signing. +/// \returns the signature, Hex-encoded. On invalid input empty string is returned. Returned object needs to be deleted after use. +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWStarkExMessageSignerSignMessage(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull message); + +/// Verify signature for a message. +/// +/// \param pubKey: pubKey that will verify and recover the message from the signature +/// \param message: the message signed (without prefix) in hex +/// \param signature: in Hex-encoded form. +/// \returns false on any invalid input (does not throw), true if the message can be recovered from the signature +TW_EXPORT_STATIC_METHOD +bool TWStarkExMessageSignerVerifyMessage(const struct TWPublicKey* _Nonnull pubKey, TWString* _Nonnull message, TWString* _Nonnull signature); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWStarkWare.h b/include/TrustWalletCore/TWStarkWare.h new file mode 100644 index 00000000000..1ff02199f71 --- /dev/null +++ b/include/TrustWalletCore/TWStarkWare.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWPrivateKey.h" +#include "TWString.h" +#include "TWDerivationPath.h" + +TW_EXTERN_C_BEGIN + +TW_EXPORT_STRUCT +struct TWStarkWare; + +/// Generates the private stark key at the given derivation path from a valid eth signature +/// +/// \param derivationPath non-null StarkEx Derivation path +/// \param signature valid eth signature +/// \return The private key for the specified derivation path/signature +TW_EXPORT_STATIC_METHOD +struct TWPrivateKey* _Nonnull TWStarkWareGetStarkKeyFromSignature(const struct TWDerivationPath* _Nonnull derivationPath, TWString* _Nonnull signature); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWStellarMemoType.h b/include/TrustWalletCore/TWStellarMemoType.h index 45f9c629206..8f6b66eec8f 100644 --- a/include/TrustWalletCore/TWStellarMemoType.h +++ b/include/TrustWalletCore/TWStellarMemoType.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -10,6 +8,7 @@ TW_EXTERN_C_BEGIN +/// Stellar memo type. TW_EXPORT_ENUM(uint32_t) enum TWStellarMemoType { TWStellarMemoTypeNone = 0, diff --git a/include/TrustWalletCore/TWStellarPassphrase.h b/include/TrustWalletCore/TWStellarPassphrase.h index 630adf3ef83..307afb84865 100644 --- a/include/TrustWalletCore/TWStellarPassphrase.h +++ b/include/TrustWalletCore/TWStellarPassphrase.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -10,6 +8,7 @@ TW_EXTERN_C_BEGIN +/// Stellar network passphrase string. TW_EXPORT_ENUM() enum TWStellarPassphrase { TWStellarPassphraseStellar /* "Public Global Stellar Network ; September 2015" */, diff --git a/include/TrustWalletCore/TWStellarVersionByte.h b/include/TrustWalletCore/TWStellarVersionByte.h index b694c80b6bb..94f125ab94c 100644 --- a/include/TrustWalletCore/TWStellarVersionByte.h +++ b/include/TrustWalletCore/TWStellarVersionByte.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -10,11 +8,12 @@ TW_EXTERN_C_BEGIN +/// Stellar address version byte. TW_EXPORT_ENUM(uint16_t) enum TWStellarVersionByte { - TWStellarVersionByteAccountID = 0x30, // G - TWStellarVersionByteSeed = 0xc0, // S - TWStellarVersionBytePreAuthTX = 0xc8, // T + TWStellarVersionByteAccountID = 0x30, // G + TWStellarVersionByteSeed = 0xc0, // S + TWStellarVersionBytePreAuthTX = 0xc8, // T TWStellarVersionByteSHA256Hash = 0x118, // X }; diff --git a/include/TrustWalletCore/TWStoredKey.h b/include/TrustWalletCore/TWStoredKey.h index 7b23bf0ef50..58a07e521c0 100644 --- a/include/TrustWalletCore/TWStoredKey.h +++ b/include/TrustWalletCore/TWStoredKey.h @@ -1,19 +1,18 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "TWBase.h" #include "TWCoinType.h" #include "TWData.h" +#include "TWDerivation.h" #include "TWHDWallet.h" #include "TWPrivateKey.h" -#include "TWString.h" #include "TWStoredKeyEncryptionLevel.h" -#include "TWDerivation.h" +#include "TWStoredKeyEncryption.h" +#include "TWString.h" TW_EXTERN_C_BEGIN @@ -21,114 +20,295 @@ TW_EXTERN_C_BEGIN TW_EXPORT_CLASS struct TWStoredKey; -/// Loads a key from a file. Returned object needs to be deleted. +/// Loads a key from a file. +/// +/// \param path filepath to the key as a non-null string +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be load, the stored key otherwise TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyLoad(TWString* _Nonnull path); -/// Imports a private key. Returned object needs to be deleted. +/// Imports a private key. +/// +/// \param privateKey Non-null Block of data private key +/// \param name The name of the stored key to import as a non-null string +/// \param password Non-null block of data, password of the stored key +/// \param coin the coin type +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be imported, the stored key otherwise TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin); -/// Imports an HD wallet. Returned object needs to be deleted. +/// Imports a private key. +/// +/// \param privateKey Non-null Block of data private key +/// \param name The name of the stored key to import as a non-null string +/// \param password Non-null block of data, password of the stored key +/// \param coin the coin type +/// \param encryption cipher encryption mode +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be imported, the stored key otherwise +TW_EXPORT_STATIC_METHOD +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyWithEncryption(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption); + +/// Imports an HD wallet. +/// +/// \param mnemonic Non-null bip39 mnemonic +/// \param name The name of the stored key to import as a non-null string +/// \param password Non-null block of data, password of the stored key +/// \param coin the coin type +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be imported, the stored key otherwise TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin); -/// Imports a key from JSON. Returned object needs to be deleted. +/// Imports an HD wallet. +/// +/// \param mnemonic Non-null bip39 mnemonic +/// \param name The name of the stored key to import as a non-null string +/// \param password Non-null block of data, password of the stored key +/// \param coin the coin type +/// \param encryption cipher encryption mode +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be imported, the stored key otherwise +TW_EXPORT_STATIC_METHOD +struct TWStoredKey* _Nullable TWStoredKeyImportHDWalletWithEncryption(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption); + +/// Imports a key from JSON. +/// +/// \param json Json stored key import format as a non-null block of data +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be imported, the stored key otherwise TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyImportJSON(TWData* _Nonnull json); -/// Creates a new key, with given encryption strength level. Returned object needs to be deleted. +/// Creates a new key, with given encryption strength level. Returned object needs to be deleted. +/// +/// \param name The name of the key to be stored +/// \param password Non-null block of data, password of the stored key +/// \param encryptionLevel The level of encryption, see \TWStoredKeyEncryptionLevel +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return The stored key as a non-null pointer +TW_DEPRECATED_FOR("3.1.1", "TWStoredKeyCreateLevelAndEncryption") TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nonnull TWStoredKeyCreateLevel(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel); -/// DEPRECATED, use TWStoredKeyCreateLevel. Creates a new key. Returned object needs to be deleted. +/// Creates a new key, with given encryption strength level. Returned object needs to be deleted. +/// +/// \param name The name of the key to be stored +/// \param password Non-null block of data, password of the stored key +/// \param encryptionLevel The level of encryption, see \TWStoredKeyEncryptionLevel +/// \param encryption cipher encryption mode +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return The stored key as a non-null pointer TW_EXPORT_STATIC_METHOD -struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password); +struct TWStoredKey* _Nonnull TWStoredKeyCreateLevelAndEncryption(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel, enum TWStoredKeyEncryption encryption); +/// Creates a new key. +/// +/// \deprecated use TWStoredKeyCreateLevel. +/// \param name The name of the key to be stored +/// \param password Non-null block of data, password of the stored key +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return The stored key as a non-null pointer +TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password); + +/// Creates a new key. +/// +/// \deprecated use TWStoredKeyCreateLevel. +/// \param name The name of the key to be stored +/// \param password Non-null block of data, password of the stored key +/// \param encryption cipher encryption mode +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return The stored key as a non-null pointer +TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nonnull TWStoredKeyCreateEncryption(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryption encryption); + +/// Delete a stored key +/// +/// \param key The key to be deleted TW_EXPORT_METHOD void TWStoredKeyDelete(struct TWStoredKey* _Nonnull key); -/// Stored key unique identifier. Returned object needs to be deleted. +/// Stored key unique identifier. +/// +/// \param key Non-null pointer to a stored key +/// \note Returned object needs to be deleted with \TWStringDelete +/// \return The stored key unique identifier if it's found, null pointer otherwise. TW_EXPORT_PROPERTY TWString* _Nullable TWStoredKeyIdentifier(struct TWStoredKey* _Nonnull key); -/// Stored key namer. Returned object needs to be deleted. +/// Stored key namer. +/// +/// \param key Non-null pointer to a stored key +/// \note Returned object needs to be deleted with \TWStringDelete +/// \return The stored key name as a non-null string pointer. TW_EXPORT_PROPERTY TWString* _Nonnull TWStoredKeyName(struct TWStoredKey* _Nonnull key); /// Whether this key is a mnemonic phrase for a HD wallet. +/// +/// \param key Non-null pointer to a stored key +/// \return true if the given stored key is a mnemonic, false otherwise TW_EXPORT_PROPERTY bool TWStoredKeyIsMnemonic(struct TWStoredKey* _Nonnull key); /// The number of accounts. +/// +/// \param key Non-null pointer to a stored key +/// \return the number of accounts associated to the given stored key TW_EXPORT_PROPERTY size_t TWStoredKeyAccountCount(struct TWStoredKey* _Nonnull key); -/// Returns the account at a given index. Returned object needs to be deleted. +/// Returns the account at a given index. +/// +/// \param key Non-null pointer to a stored key +/// \param index the account index to be retrieved +/// \note Returned object needs to be deleted with \TWAccountDelete +/// \return Null pointer if the associated account is not found, pointer to the account otherwise. TW_EXPORT_METHOD struct TWAccount* _Nullable TWStoredKeyAccount(struct TWStoredKey* _Nonnull key, size_t index); -/// Returns the account for a specific coin, creating it if necessary. Returned object needs to be deleted. +/// Returns the account for a specific coin, creating it if necessary. +/// +/// \param key Non-null pointer to a stored key +/// \param coin The coin type +/// \param wallet The associated HD wallet, can be null. +/// \note Returned object needs to be deleted with \TWAccountDelete +/// \return Null pointer if the associated account is not found/not created, pointer to the account otherwise. TW_EXPORT_METHOD struct TWAccount* _Nullable TWStoredKeyAccountForCoin(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, struct TWHDWallet* _Nullable wallet); -/// Returns the account for a specific coin + derivation, creating it if necessary. Returned object needs to be deleted. +/// Returns the account for a specific coin + derivation, creating it if necessary. +/// +/// \param key Non-null pointer to a stored key +/// \param coin The coin type +/// \param derivation The derivation for the given coin +/// \param wallet the associated HD wallet, can be null. +/// \note Returned object needs to be deleted with \TWAccountDelete +/// \return Null pointer if the associated account is not found/not created, pointer to the account otherwise. TW_EXPORT_METHOD struct TWAccount* _Nullable TWStoredKeyAccountForCoinDerivation(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, enum TWDerivation derivation, struct TWHDWallet* _Nullable wallet); -/// Adds a new account, using given derivation (usually TWDerivationDefault) and derivation path (usually matches path from derivation, but custom possible). +/// Adds a new account, using given derivation (usually TWDerivationDefault) +/// and derivation path (usually matches path from derivation, but custom possible). +/// +/// \param key Non-null pointer to a stored key +/// \param address Non-null pointer to the address of the coin for this account +/// \param coin coin type +/// \param derivation derivation of the given coin type +/// \param derivationPath HD bip44 derivation path of the given coin +/// \param publicKey Non-null public key of the given coin/address +/// \param extendedPublicKey Non-null extended public key of the given coin/address TW_EXPORT_METHOD void TWStoredKeyAddAccountDerivation(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, enum TWCoinType coin, enum TWDerivation derivation, TWString* _Nonnull derivationPath, TWString* _Nonnull publicKey, TWString* _Nonnull extendedPublicKey); -/// [Deprecated] Use TWStoredKeyAddAccountDerivation (with TWDerivationDefault) instead. /// Adds a new account, using given derivation path. +/// +/// \deprecated Use TWStoredKeyAddAccountDerivation (with TWDerivationDefault) instead. +/// \param key Non-null pointer to a stored key +/// \param address Non-null pointer to the address of the coin for this account +/// \param coin coin type +/// \param derivationPath HD bip44 derivation path of the given coin +/// \param publicKey Non-null public key of the given coin/address +/// \param extendedPublicKey Non-null extended public key of the given coin/address TW_EXPORT_METHOD void TWStoredKeyAddAccount(struct TWStoredKey* _Nonnull key, TWString* _Nonnull address, enum TWCoinType coin, TWString* _Nonnull derivationPath, TWString* _Nonnull publicKey, TWString* _Nonnull extendedPublicKey); /// Remove the account for a specific coin +/// +/// \param key Non-null pointer to a stored key +/// \param coin Account coin type to be removed TW_EXPORT_METHOD void TWStoredKeyRemoveAccountForCoin(struct TWStoredKey* _Nonnull key, enum TWCoinType coin); /// Remove the account for a specific coin with the given derivation. +/// +/// \param key Non-null pointer to a stored key +/// \param coin Account coin type to be removed +/// \param derivation The derivation of the given coin type TW_EXPORT_METHOD void TWStoredKeyRemoveAccountForCoinDerivation(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, enum TWDerivation derivation); /// Remove the account for a specific coin with the given derivation path. +/// +/// \param key Non-null pointer to a stored key +/// \param coin Account coin type to be removed +/// \param derivationPath The derivation path (bip44) of the given coin type TW_EXPORT_METHOD void TWStoredKeyRemoveAccountForCoinDerivationPath(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, TWString* _Nonnull derivationPath); /// Saves the key to a file. +/// +/// \param key Non-null pointer to a stored key +/// \param path Non-null string filepath where the key will be saved +/// \return true if the key was successfully stored in the given filepath file, false otherwise TW_EXPORT_METHOD bool TWStoredKeyStore(struct TWStoredKey* _Nonnull key, TWString* _Nonnull path); /// Decrypts the private key. +/// +/// \param key Non-null pointer to a stored key +/// \param password Non-null block of data, password of the stored key +/// \return Decrypted private key as a block of data if success, null pointer otherwise TW_EXPORT_METHOD TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); /// Decrypts the mnemonic phrase. +/// +/// \param key Non-null pointer to a stored key +/// \param password Non-null block of data, password of the stored key +/// \return Bip39 decrypted mnemonic if success, null pointer otherwise TW_EXPORT_METHOD TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); /// Returns the private key for a specific coin. Returned object needs to be deleted. +/// +/// \param key Non-null pointer to a stored key +/// \param coin Account coin type to be queried +/// \note Returned object needs to be deleted with \TWPrivateKeyDelete +/// \return Null pointer on failure, pointer to the private key otherwise TW_EXPORT_METHOD struct TWPrivateKey* _Nullable TWStoredKeyPrivateKey(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, TWData* _Nonnull password); /// Decrypts and returns the HD Wallet for mnemonic phrase keys. Returned object needs to be deleted. +/// +/// \param key Non-null pointer to a stored key +/// \param password Non-null block of data, password of the stored key +/// \note Returned object needs to be deleted with \TWHDWalletDelete +/// \return Null pointer on failure, pointer to the HDWallet otherwise TW_EXPORT_METHOD struct TWHDWallet* _Nullable TWStoredKeyWallet(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); /// Exports the key as JSON +/// +/// \param key Non-null pointer to a stored key +/// \return Null pointer on failure, pointer to a block of data containing the json otherwise TW_EXPORT_METHOD TWData* _Nullable TWStoredKeyExportJSON(struct TWStoredKey* _Nonnull key); /// Fills in empty and invalid addresses. -/// /// This method needs the encryption password to re-derive addresses from private keys. -/// @returns `false` if the password is incorrect. +/// +/// \param key Non-null pointer to a stored key +/// \param password Non-null block of data, password of the stored key +/// \return `false` if the password is incorrect, true otherwise. TW_EXPORT_METHOD bool TWStoredKeyFixAddresses(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); +/// Re-derives address for the account(s) associated with the given coin. +/// This method can be used if address format has been changed. +/// In case of multiple accounts, all of them will be updated. +/// +/// \param key Non-null pointer to a stored key +/// \param coin Account(s) coin type to be updated +/// \return `false` if there are no accounts associated with the given coin, true otherwise +TW_EXPORT_METHOD +bool TWStoredKeyUpdateAddress(struct TWStoredKey* _Nonnull key, enum TWCoinType coin); + /// Retrieve stored key encoding parameters, as JSON string. +/// +/// \param key Non-null pointer to a stored key +/// \return Null pointer on failure, encoding parameter as a json string otherwise. TW_EXPORT_PROPERTY TWString* _Nullable TWStoredKeyEncryptionParameters(struct TWStoredKey* _Nonnull key); diff --git a/include/TrustWalletCore/TWStoredKeyEncryption.h b/include/TrustWalletCore/TWStoredKeyEncryption.h new file mode 100644 index 00000000000..ccb0d7cfcac --- /dev/null +++ b/include/TrustWalletCore/TWStoredKeyEncryption.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +/// Preset encryption kind +TW_EXPORT_ENUM(uint32_t) +enum TWStoredKeyEncryption { + TWStoredKeyEncryptionAes128Ctr = 0, + TWStoredKeyEncryptionAes128Cbc = 1, + TWStoredKeyEncryptionAes192Ctr = 2, + TWStoredKeyEncryptionAes256Ctr = 3, +}; + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWStoredKeyEncryptionLevel.h b/include/TrustWalletCore/TWStoredKeyEncryptionLevel.h index aa75da13771..6a7ceb7bcbc 100644 --- a/include/TrustWalletCore/TWStoredKeyEncryptionLevel.h +++ b/include/TrustWalletCore/TWStoredKeyEncryptionLevel.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/include/TrustWalletCore/TWString.h b/include/TrustWalletCore/TWString.h index db8468f42f4..9cfc8e77bfc 100644 --- a/include/TrustWalletCore/TWString.h +++ b/include/TrustWalletCore/TWString.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -14,33 +12,52 @@ typedef const void TWData; /// Defines a resizable string. /// -/// The implementantion of these methods should be language-specific to minimize translation overhead. For instance it -/// should be a `jstring` for Java and an `NSString` for Swift. -/// Create allocates memory, the delete call should be called at the end to release memory. +/// The implementantion of these methods should be language-specific to minimize translation +/// overhead. For instance it should be a `jstring` for Java and an `NSString` for Swift. Create +/// allocates memory, the delete call should be called at the end to release memory. typedef const void TWString; -/// Creates a string from a null-terminated UTF8 byte array. It must be deleted at the end. -TWString *_Nonnull TWStringCreateWithUTF8Bytes(const char *_Nonnull bytes) TW_VISIBILITY_DEFAULT; +/// Creates a TWString from a null-terminated UTF8 byte array. It must be deleted at the end. +/// +/// \param bytes a null-terminated UTF8 byte array. +TWString* _Nonnull TWStringCreateWithUTF8Bytes(const char* _Nonnull bytes) TW_VISIBILITY_DEFAULT; -/// Creates a string from a raw byte array and size. -TWString *_Nonnull TWStringCreateWithRawBytes(const uint8_t *_Nonnull bytes, size_t size) TW_VISIBILITY_DEFAULT; +/// Creates a string from a raw byte array and size. It must be deleted at the end. +/// +/// \param bytes a raw byte array. +/// \param size the size of the byte array. +TWString* _Nonnull TWStringCreateWithRawBytes(const uint8_t* _Nonnull bytes, size_t size) TW_VISIBILITY_DEFAULT; /// Creates a hexadecimal string from a block of data. It must be deleted at the end. -TWString *_Nonnull TWStringCreateWithHexData(TWData *_Nonnull data) TW_VISIBILITY_DEFAULT; +/// +/// \param data a block of data. +TWString* _Nonnull TWStringCreateWithHexData(TWData* _Nonnull data) TW_VISIBILITY_DEFAULT; /// Returns the string size in bytes. -size_t TWStringSize(TWString *_Nonnull string) TW_VISIBILITY_DEFAULT; +/// +/// \param string a TWString pointer. +size_t TWStringSize(TWString* _Nonnull string) TW_VISIBILITY_DEFAULT; /// Returns the byte at the provided index. -char TWStringGet(TWString *_Nonnull string, size_t index) TW_VISIBILITY_DEFAULT; +/// +/// \param string a TWString pointer. +/// \param index the index of the byte. +char TWStringGet(TWString* _Nonnull string, size_t index) TW_VISIBILITY_DEFAULT; /// Returns the raw pointer to the string's UTF8 bytes (null-terminated). -const char *_Nonnull TWStringUTF8Bytes(TWString *_Nonnull string) TW_VISIBILITY_DEFAULT; +/// +/// \param string a TWString pointer. +const char* _Nonnull TWStringUTF8Bytes(TWString* _Nonnull string) TW_VISIBILITY_DEFAULT; -/// Deletes a string created with a `TWStringCreate*` method. After delete it must not be used (can segfault)! -void TWStringDelete(TWString *_Nonnull string) TW_VISIBILITY_DEFAULT; +/// Deletes a string created with a `TWStringCreate*` method and frees the memory. +/// +/// \param string a TWString pointer. +void TWStringDelete(TWString* _Nonnull string) TW_VISIBILITY_DEFAULT; /// Determines whether two string blocks are equal. -bool TWStringEqual(TWString *_Nonnull lhs, TWString *_Nonnull rhs) TW_VISIBILITY_DEFAULT; +/// +/// \param lhs a TWString pointer. +/// \param rhs another TWString pointer. +bool TWStringEqual(TWString* _Nonnull lhs, TWString* _Nonnull rhs) TW_VISIBILITY_DEFAULT; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWTHORChainSwap.h b/include/TrustWalletCore/TWTHORChainSwap.h index 45a54b94cd2..11844425bbe 100644 --- a/include/TrustWalletCore/TWTHORChainSwap.h +++ b/include/TrustWalletCore/TWTHORChainSwap.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "TWBase.h" @@ -11,10 +9,14 @@ TW_EXTERN_C_BEGIN +/// THORChain swap functions TW_EXPORT_STRUCT struct TWTHORChainSwap; -/// Build a THORChainSwap transaction input. Input is SwapInput protobuf, return is SwapOutput. +/// Builds a THORChainSwap transaction input. +/// +/// \param input The serialized data of SwapInput. +/// \return The serialized data of SwapOutput. TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWTHORChainSwapBuildSwap(TWData *_Nonnull input); diff --git a/include/TrustWalletCore/TWTONAddressConverter.h b/include/TrustWalletCore/TWTONAddressConverter.h new file mode 100644 index 00000000000..39bb4dfed7e --- /dev/null +++ b/include/TrustWalletCore/TWTONAddressConverter.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// TON address operations. +TW_EXPORT_CLASS +struct TWTONAddressConverter; + +/// Converts a TON user address into a Bag of Cells (BoC) with a single root Cell. +/// The function is mostly used to request a Jetton user address via `get_wallet_address` RPC. +/// https://docs.ton.org/develop/dapps/asset-processing/jettons#retrieving-jetton-wallet-addresses-for-a-given-user +/// +/// \param address Address to be converted into a Bag Of Cells (BoC). +/// \return Pointer to a base64 encoded Bag Of Cells (BoC). Null if invalid address provided. +TW_EXPORT_STATIC_METHOD +TWString *_Nullable TWTONAddressConverterToBoc(TWString *_Nonnull address); + +/// Parses a TON address from a Bag of Cells (BoC) with a single root Cell. +/// The function is mostly used to parse a Jetton user address received on `get_wallet_address` RPC. +/// https://docs.ton.org/develop/dapps/asset-processing/jettons#retrieving-jetton-wallet-addresses-for-a-given-user +/// +/// \param boc Base64 encoded Bag Of Cells (BoC). +/// \return Pointer to a Jetton address. +TW_EXPORT_STATIC_METHOD +TWString *_Nullable TWTONAddressConverterFromBoc(TWString *_Nonnull boc); + +/// Converts any TON address format to user friendly with the given parameters. +/// +/// \param address raw or user-friendly address to be converted. +/// \param bounceable whether the result address should be bounceable. +/// \param testnet whether the result address should be testnet. +/// \return user-friendly address str. +TW_EXPORT_STATIC_METHOD +TWString *_Nullable TWTONAddressConverterToUserFriendly(TWString *_Nonnull address, bool bounceable, bool testnet); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWTONMessageSigner.h b/include/TrustWalletCore/TWTONMessageSigner.h new file mode 100644 index 00000000000..c9c73876ee3 --- /dev/null +++ b/include/TrustWalletCore/TWTONMessageSigner.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWPrivateKey.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// TON message signing. +TW_EXPORT_CLASS +struct TWTONMessageSigner; + +/// Signs an arbitrary message to prove ownership of an address for off-chain services. +/// https://github.com/ton-foundation/specs/blob/main/specs/wtf-0002.md +/// +/// \param privateKey: the private key used for signing +/// \param message: A custom message which is input to the signing. +/// \returns the signature, Hex-encoded. On invalid input null is returned. Returned object needs to be deleted after use. +TW_EXPORT_STATIC_METHOD +TWString *_Nullable TWTONMessageSignerSignMessage(struct TWPrivateKey *_Nonnull privateKey, TWString* _Nonnull message); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWTONWallet.h b/include/TrustWalletCore/TWTONWallet.h new file mode 100644 index 00000000000..098702faa6a --- /dev/null +++ b/include/TrustWalletCore/TWTONWallet.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWPublicKey.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +/// TON wallet operations. +TW_EXPORT_CLASS +struct TWTONWallet; + +/// Constructs a TON Wallet V4R2 stateInit encoded as BoC (BagOfCells) for the given `public_key`. +/// +/// \param publicKey wallet's public key. +/// \param workchain TON workchain to which the wallet belongs. Usually, base chain is used (0). +/// \param walletId wallet's ID allows to create multiple wallets for the same private key. +/// \return Pointer to a base64 encoded Bag Of Cells (BoC) StateInit. Null if invalid public key provided. +TW_EXPORT_STATIC_METHOD +TWString *_Nullable TWTONWalletBuildV4R2StateInit(struct TWPublicKey *_Nonnull publicKey, int32_t workchain, int32_t walletId); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWTezosMessageSigner.h b/include/TrustWalletCore/TWTezosMessageSigner.h new file mode 100644 index 00000000000..be1f586f448 --- /dev/null +++ b/include/TrustWalletCore/TWTezosMessageSigner.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" +#include "TWPrivateKey.h" +#include "TWPublicKey.h" + +TW_EXTERN_C_BEGIN + +/// Tezos message signing, verification and utilities. +TW_EXPORT_STRUCT +struct TWTezosMessageSigner; + +/// Implement format input as described in https://tezostaquito.io/docs/signing/ +/// +/// \param message message to format e.g: Hello, World +/// \param dAppUrl the app url, e.g: testUrl +/// \returns the formatted message as a string +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWTezosMessageSignerFormatMessage(TWString* _Nonnull message, TWString* _Nonnull url); + +/// Implement input to payload as described in: https://tezostaquito.io/docs/signing/ +/// +/// \param message formatted message to be turned into an hex payload +/// \return the hexpayload of the formated message as a hex string +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWTezosMessageSignerInputToPayload(TWString* _Nonnull message); + +/// Sign a message as described in https://tezostaquito.io/docs/signing/ +/// +/// \param privateKey: the private key used for signing +/// \param message: A custom message payload (hex) which is input to the signing. +/// \returns the signature, Hex-encoded. On invalid input empty string is returned. Returned object needs to be deleted after use. +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWTezosMessageSignerSignMessage(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull message); + +/// Verify signature for a message as described in https://tezostaquito.io/docs/signing/ +/// +/// \param pubKey: pubKey that will verify the message from the signature +/// \param message: the message signed as a payload (hex) +/// \param signature: in Base58-encoded form. +/// \returns false on any invalid input (does not throw), true if the message can be verified from the signature +TW_EXPORT_STATIC_METHOD +bool TWTezosMessageSignerVerifyMessage(const struct TWPublicKey* _Nonnull pubKey, TWString* _Nonnull message, TWString* _Nonnull signature); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWTransactionCompiler.h b/include/TrustWalletCore/TWTransactionCompiler.h index 519424ccade..67dfc4f0124 100644 --- a/include/TrustWalletCore/TWTransactionCompiler.h +++ b/include/TrustWalletCore/TWTransactionCompiler.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -18,25 +16,36 @@ TW_EXTERN_C_BEGIN TW_EXPORT_STRUCT struct TWTransactionCompiler; -/// Build a coin-specific SigningInput protobuf transaction input, from simple transaction parameters -/// - amount: decimal number as string -/// - asset: optional asset name, like "BNB" -/// - memo: optional memo -/// - chainId: optional chainId to override default -TW_EXPORT_STATIC_METHOD -TWData *_Nonnull TWTransactionCompilerBuildInput(enum TWCoinType coinType, TWString *_Nonnull from, TWString *_Nonnull to, TWString *_Nonnull amount, TWString *_Nonnull asset, TWString *_Nonnull memo, TWString *_Nonnull chainId); - -/// Obtain pre-signing hashes of a transaction. -/// It will return a proto object named `PreSigningOutput` which will include hash. -/// We provide a default `PreSigningOutput` in TransactionCompiler.proto. +/// Obtains pre-signing hashes of a transaction. +/// +/// We provide a default `PreSigningOutput` in TransactionCompiler.proto. /// For some special coins, such as bitcoin, we will create a custom `PreSigningOutput` object in its proto file. +/// \param coin coin type. +/// \param txInputData The serialized data of a signing input +/// \return serialized data of a proto object `PreSigningOutput` includes hash. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWTransactionCompilerPreImageHashes(enum TWCoinType coinType, + TWData* _Nonnull txInputData); + +/// Compiles a complete transation with one or more external signatures. +/// +/// Puts together from transaction input and provided public keys and signatures. The signatures must match the hashes +/// returned by TWTransactionCompilerPreImageHashes, in the same order. The publicKeyHash attached +/// to the hashes enable identifying the private key needed for signing the hash. +/// \param coin coin type. +/// \param txInputData The serialized data of a signing input. +/// \param signatures signatures to compile, using TWDataVector. +/// \param publicKeys public keys for signers to match private keys, using TWDataVector. +/// \return serialized data of a proto object `SigningOutput`. TW_EXPORT_STATIC_METHOD -TWData *_Nonnull TWTransactionCompilerPreImageHashes(enum TWCoinType coinType, TWData *_Nonnull txInputData); +TWData* _Nonnull TWTransactionCompilerCompileWithSignatures( + enum TWCoinType coinType, TWData* _Nonnull txInputData, + const struct TWDataVector* _Nonnull signatures, const struct TWDataVector* _Nonnull publicKeys); -/// Compile a complete transation with one or more external signatures, put together from transaction input and provided public keys and signatures. -/// The signatures must match the hashes returned by TWTransactionCompilerPreImageHashes, in the same order. -/// The publicKeyHash attached to the hashes enable identifying the private key needed for signing the hash. TW_EXPORT_STATIC_METHOD -TWData *_Nonnull TWTransactionCompilerCompileWithSignatures(enum TWCoinType coinType, TWData *_Nonnull txInputData, const struct TWDataVector *_Nonnull signatures, const struct TWDataVector *_Nonnull publicKeys); +TWData *_Nonnull TWTransactionCompilerCompileWithSignaturesAndPubKeyType( + enum TWCoinType coinType, TWData *_Nonnull txInputData, + const struct TWDataVector *_Nonnull signatures, const struct TWDataVector *_Nonnull publicKeys, + enum TWPublicKeyType pubKeyType); TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWTransactionDecoder.h b/include/TrustWalletCore/TWTransactionDecoder.h new file mode 100644 index 00000000000..2d1a22cffe4 --- /dev/null +++ b/include/TrustWalletCore/TWTransactionDecoder.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWCoinType.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +TW_EXPORT_STRUCT +struct TWTransactionDecoder; + +/// Decodes a transaction from a binary representation. +/// +/// \param coin coin type. +/// \param encodedTx encoded transaction data. +/// \return serialized protobuf message specific for the given coin. +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWTransactionDecoderDecode(enum TWCoinType coinType, TWData *_Nonnull encodedTx); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWTransactionUtil.h b/include/TrustWalletCore/TWTransactionUtil.h new file mode 100644 index 00000000000..55b2a811428 --- /dev/null +++ b/include/TrustWalletCore/TWTransactionUtil.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWCoinType.h" +#include "TWData.h" + +TW_EXTERN_C_BEGIN + +TW_EXPORT_STRUCT +struct TWTransactionUtil; + +/// Calculate the TX hash of a transaction. +/// +/// \param coin coin type. +/// \param encodedTx encoded transaction data. +/// \return The TX hash of a transaction, If the input is invalid or the chain is unsupported, null is returned. +TW_EXPORT_STATIC_METHOD +TWString* _Nullable TWTransactionUtilCalcTxHash(enum TWCoinType coinType, TWString* _Nonnull encodedTx); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWTronMessageSigner.h b/include/TrustWalletCore/TWTronMessageSigner.h new file mode 100644 index 00000000000..d20baba14c1 --- /dev/null +++ b/include/TrustWalletCore/TWTronMessageSigner.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWString.h" +#include "TWPrivateKey.h" +#include "TWPublicKey.h" + +TW_EXTERN_C_BEGIN + +/// Tron message signing and verification. +/// +/// Tron and some other wallets support a message signing & verification format, to create a proof (a signature) +/// that someone has access to the private keys of a specific address. +TW_EXPORT_STRUCT +struct TWTronMessageSigner; + +/// Sign a message. +/// +/// \param privateKey: the private key used for signing +/// \param message: A custom message which is input to the signing. +/// \returns the signature, Hex-encoded. On invalid input empty string is returned. Returned object needs to be deleted after use. +TW_EXPORT_STATIC_METHOD +TWString* _Nonnull TWTronMessageSignerSignMessage(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull message); + +/// Verify signature for a message. +/// +/// \param pubKey: pubKey that will verify and recover the message from the signature +/// \param message: the message signed (without prefix) +/// \param signature: in Hex-encoded form. +/// \returns false on any invalid input (does not throw), true if the message can be recovered from the signature +TW_EXPORT_STATIC_METHOD +bool TWTronMessageSignerVerifyMessage(const struct TWPublicKey* _Nonnull pubKey, TWString* _Nonnull message, TWString* _Nonnull signature); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWWalletConnectRequest.h b/include/TrustWalletCore/TWWalletConnectRequest.h new file mode 100644 index 00000000000..41ba895457c --- /dev/null +++ b/include/TrustWalletCore/TWWalletConnectRequest.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWCoinType.h" +#include "TWData.h" + +TW_EXTERN_C_BEGIN + +/// Represents a WalletConnect signing request. +TW_EXPORT_CLASS +struct TWWalletConnectRequest; + +/// Parses the WalletConnect signing request as a `SigningInput`. +/// +/// \param coin The given coin type to plan the transaction for. +/// \param input The serialized data of a `WalletConnect::Proto::ParseRequestInput` proto object. +/// \return The serialized data of `WalletConnect::Proto::ParseRequestOutput` proto object. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWWalletConnectRequestParse(enum TWCoinType coin, TWData* _Nonnull input); + +TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWWebAuthn.h b/include/TrustWalletCore/TWWebAuthn.h new file mode 100644 index 00000000000..c3f24b27a2b --- /dev/null +++ b/include/TrustWalletCore/TWWebAuthn.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +#pragma once + +#include "TWBase.h" +#include "TWData.h" +#include "TWPublicKey.h" + +TW_EXTERN_C_BEGIN + +TW_EXPORT_STRUCT +struct TWWebAuthn; + +/// Converts attestation object to the public key on P256 curve +/// +/// \param attestationObject Attestation object retrieved from webuthn.get method +/// \return Public key. +TW_EXPORT_STATIC_METHOD +struct TWPublicKey *_Nullable TWWebAuthnGetPublicKey(TWData *_Nonnull attestationObject); + +/// Uses ASN parser to extract r and s values from a webauthn signature +/// +/// \param signature ASN encoded webauthn signature: https://www.w3.org/TR/webauthn-2/#sctn-signature-attestation-types +/// \return Concatenated r and s values. +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWWebAuthnGetRSValues(TWData *_Nonnull signature); + +/// Reconstructs the original message that was signed via P256 curve. Can be used for signature validation. +/// +/// \param authenticatorData Authenticator Data: https://www.w3.org/TR/webauthn-2/#authenticator-data +/// \param clientDataJSON clientDataJSON: https://www.w3.org/TR/webauthn-2/#dom-authenticatorresponse-clientdatajson +/// \return original messages. +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWWebAuthnReconstructOriginalMessage(TWData* _Nonnull authenticatorData, TWData* _Nonnull clientDataJSON); +TW_EXTERN_C_END \ No newline at end of file diff --git a/jni/android/AnySigner.c b/jni/android/AnySigner.c new file mode 100644 index 00000000000..fd1704562f5 --- /dev/null +++ b/jni/android/AnySigner.c @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include + +#include "AnySigner.h" +#include "TWJNI.h" + +jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeSign(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin) { + TWData *inputData = TWDataCreateWithJByteArray(env, input); + TWData *outputData = TWAnySignerSign(inputData, coin); + jbyteArray resultData = TWDataJByteArray(outputData, env); + TWDataDelete(inputData); + return resultData; +} + +jboolean JNICALL Java_wallet_core_java_AnySigner_supportsJSON(JNIEnv *env, jclass thisClass, jint coin) { + return TWAnySignerSupportsJSON(coin); +} + +jstring JNICALL Java_wallet_core_java_AnySigner_signJSON(JNIEnv *env, jclass thisClass, jstring json, jbyteArray key, jint coin) { + TWString *jsonString = TWStringCreateWithJString(env, json); + TWData *keyData = TWDataCreateWithJByteArray(env, key); + TWString *result = TWAnySignerSignJSON(jsonString, keyData, coin); + TWDataDelete(keyData); + TWStringDelete(jsonString); + return TWStringJString(result, env); +} + +jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativePlan(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin) { + TWData *inputData = TWDataCreateWithJByteArray(env, input); + TWData *outputData = TWAnySignerPlan(inputData, coin); + jbyteArray resultData = TWDataJByteArray(outputData, env); + TWDataDelete(inputData); + return resultData; +} diff --git a/jni/android/AnySigner.h b/jni/android/AnySigner.h new file mode 100644 index 00000000000..566d446a7ee --- /dev/null +++ b/jni/android/AnySigner.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#ifndef JNI_TW_ANYSIGNER_H +#define JNI_TW_ANYSIGNER_H + +#include +#include + +TW_EXTERN_C_BEGIN + +JNIEXPORT +jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeSign(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin); + +JNIEXPORT +jboolean JNICALL Java_wallet_core_java_AnySigner_supportsJSON(JNIEnv *env, jclass thisClass, jint coin); + +JNIEXPORT +jbyteArray JNICALL Java_wallet_core_java_AnySigner_signJSON(JNIEnv *env, jclass thisClass, jstring json, jbyteArray key, jint coin); + +JNIEXPORT +jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativePlan(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin); + +TW_EXTERN_C_END + +#endif // JNI_TW_ANYSIGNER_H diff --git a/jni/cpp/AnySigner.c b/jni/cpp/AnySigner.c deleted file mode 100644 index d9ac054adc4..00000000000 --- a/jni/cpp/AnySigner.c +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include -#include - -#include "AnySigner.h" -#include "TWJNI.h" - -jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeSign(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin) { - TWData *inputData = TWDataCreateWithJByteArray(env, input); - TWData *outputData = TWAnySignerSign(inputData, coin); - jbyteArray resultData = TWDataJByteArray(outputData, env); - TWDataDelete(inputData); - return resultData; -} - -jboolean JNICALL Java_wallet_core_java_AnySigner_supportsJSON(JNIEnv *env, jclass thisClass, jint coin) { - return TWAnySignerSupportsJSON(coin); -} - -jstring JNICALL Java_wallet_core_java_AnySigner_signJSON(JNIEnv *env, jclass thisClass, jstring json, jbyteArray key, jint coin) { - TWString *jsonString = TWStringCreateWithJString(env, json); - TWData *keyData = TWDataCreateWithJByteArray(env, key); - TWString *result = TWAnySignerSignJSON(jsonString, keyData, coin); - TWDataDelete(keyData); - TWStringDelete(jsonString); - return TWStringJString(result, env); -} - -jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativePlan(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin) { - TWData *inputData = TWDataCreateWithJByteArray(env, input); - TWData *outputData = TWAnySignerPlan(inputData, coin); - jbyteArray resultData = TWDataJByteArray(outputData, env); - TWDataDelete(inputData); - return resultData; -} diff --git a/jni/cpp/AnySigner.h b/jni/cpp/AnySigner.h deleted file mode 100644 index b51323a6554..00000000000 --- a/jni/cpp/AnySigner.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#ifndef JNI_TW_ANYSIGNER_H -#define JNI_TW_ANYSIGNER_H - -#include -#include - -TW_EXTERN_C_BEGIN - -JNIEXPORT -jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativeSign(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin); - -JNIEXPORT -jboolean JNICALL Java_wallet_core_java_AnySigner_supportsJSON(JNIEnv *env, jclass thisClass, jint coin); - -JNIEXPORT -jbyteArray JNICALL Java_wallet_core_java_AnySigner_signJSON(JNIEnv *env, jclass thisClass, jstring json, jbyteArray key, jint coin); - -JNIEXPORT -jbyteArray JNICALL Java_wallet_core_java_AnySigner_nativePlan(JNIEnv *env, jclass thisClass, jbyteArray input, jint coin); - -TW_EXTERN_C_END - -#endif // JNI_TW_ANYSIGNER_H diff --git a/jni/cpp/Random.cpp b/jni/cpp/Random.cpp index de97baff225..216c61d46cc 100644 --- a/jni/cpp/Random.cpp +++ b/jni/cpp/Random.cpp @@ -1,11 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include +#include +#include static JavaVM* cachedJVM; @@ -26,26 +26,42 @@ uint32_t random32() { } void random_buffer(uint8_t *buf, size_t len) { - JNIEnv *env; - cachedJVM->AttachCurrentThread(&env, NULL); + if (cachedJVM) + { + JNIEnv *env; + +#if defined(__ANDROID__) || defined(ANDROID) + cachedJVM->AttachCurrentThread(&env, nullptr); +#else + cachedJVM->AttachCurrentThread((void **) &env, nullptr); +#endif - // SecureRandom random = new SecureRandom(); - jclass secureRandomClass = env->FindClass("java/security/SecureRandom"); - jmethodID constructor = env->GetMethodID(secureRandomClass, "", "()V"); - jobject random = env->NewObject(secureRandomClass, constructor); + // SecureRandom random = new SecureRandom(); + jclass secureRandomClass = env->FindClass("java/security/SecureRandom"); + jmethodID constructor = env->GetMethodID(secureRandomClass, "", "()V"); + jobject random = env->NewObject(secureRandomClass, constructor); - //byte array[] = new byte[len]; - jbyteArray array = env->NewByteArray(static_cast(len)); + //byte array[] = new byte[len]; + jbyteArray array = env->NewByteArray(static_cast(len)); - //random.nextBytes(bytes); - jmethodID nextBytes = env->GetMethodID(secureRandomClass, "nextBytes", "([B)V"); - env->CallVoidMethod(random, nextBytes, array); + //random.nextBytes(bytes); + jmethodID nextBytes = env->GetMethodID(secureRandomClass, "nextBytes", "([B)V"); + env->CallVoidMethod(random, nextBytes, array); - jbyte* bytes = env->GetByteArrayElements(array, nullptr); - memcpy(buf, bytes, len); - env->ReleaseByteArrayElements(array, bytes, JNI_ABORT); + jbyte* bytes = env->GetByteArrayElements(array, nullptr); + memcpy(buf, bytes, len); + env->ReleaseByteArrayElements(array, bytes, JNI_ABORT); - env->DeleteLocalRef(array); - env->DeleteLocalRef(random); - env->DeleteLocalRef(secureRandomClass); + env->DeleteLocalRef(array); + env->DeleteLocalRef(random); + env->DeleteLocalRef(secureRandomClass); + } + else + { + std::ifstream randomData("/dev/urandom", std::ios::in | std::ios::binary); + if (randomData.is_open()) { + randomData.read(reinterpret_cast(buf), len); + randomData.close(); + } + } } diff --git a/jni/cpp/TWJNI.h b/jni/cpp/TWJNI.h index 29bf14ba4df..86fc962ca29 100644 --- a/jni/cpp/TWJNI.h +++ b/jni/cpp/TWJNI.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/jni/cpp/TWJNIData.cpp b/jni/cpp/TWJNIData.cpp index c0577f31442..942505f593f 100644 --- a/jni/cpp/TWJNIData.cpp +++ b/jni/cpp/TWJNIData.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include @@ -10,7 +8,7 @@ #include "TWJNIData.h" jbyteArray TWDataJByteArray(TWData *_Nonnull data, JNIEnv *env) { - jsize dataSize = static_cast(TWDataSize(data)); + auto dataSize = static_cast(TWDataSize(data)); jbyteArray array = env->NewByteArray(dataSize); env->SetByteArrayRegion(array, 0, dataSize, (jbyte *) TWDataBytes(data)); TWDataDelete(data); diff --git a/jni/cpp/TWJNIData.h b/jni/cpp/TWJNIData.h index a7002bffce5..9d1c6730368 100644 --- a/jni/cpp/TWJNIData.h +++ b/jni/cpp/TWJNIData.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/jni/cpp/TWJNIString.cpp b/jni/cpp/TWJNIString.cpp index 2bb220fb6fc..996eb0c747c 100644 --- a/jni/cpp/TWJNIString.cpp +++ b/jni/cpp/TWJNIString.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include @@ -15,8 +13,8 @@ jstring _Nonnull TWStringJString(TWString *_Nonnull string, JNIEnv *env) { } TWString *_Nonnull TWStringCreateWithJString(JNIEnv *env, jstring _Nonnull string) { - auto chars = env->GetStringUTFChars(string, nullptr); - auto twstring = TWStringCreateWithUTF8Bytes(chars); + const auto *chars = env->GetStringUTFChars(string, nullptr); + const auto *twstring = TWStringCreateWithUTF8Bytes(chars); env->ReleaseStringUTFChars(string, chars); return twstring; } diff --git a/jni/cpp/TWJNIString.h b/jni/cpp/TWJNIString.h index 95f0f967898..1691fe47abb 100644 --- a/jni/cpp/TWJNIString.h +++ b/jni/cpp/TWJNIString.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/jni/java/wallet/core/java/AnySigner.java b/jni/java/wallet/core/java/AnySigner.java index 47f9b4fce9a..21caaa185d4 100644 --- a/jni/java/wallet/core/java/AnySigner.java +++ b/jni/java/wallet/core/java/AnySigner.java @@ -1,18 +1,16 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. package wallet.core.java; -import com.google.protobuf.Message; +import com.google.protobuf.MessageLite; import com.google.protobuf.Parser; import wallet.core.jni.CoinType; public class AnySigner { - public static T sign(Message input, CoinType coin, Parser parser) throws Exception { + public static T sign(MessageLite input, CoinType coin, Parser parser) throws Exception { byte[] data = input.toByteArray(); byte[] outputData = nativeSign(data, coin.value()); T output = parser.parseFrom(outputData); @@ -25,7 +23,7 @@ public static T sign(Message input, CoinType coin, Parser public static native boolean supportsJSON(int coin); - public static T plan(Message input, CoinType coin, Parser parser) throws Exception { + public static T plan(MessageLite input, CoinType coin, Parser parser) throws Exception { byte[] data = input.toByteArray(); byte[] outputData = nativePlan(data, coin.value()); T output = parser.parseFrom(outputData); diff --git a/jni/kotlin/AnySigner.c b/jni/kotlin/AnySigner.c new file mode 100644 index 00000000000..83211fb3512 --- /dev/null +++ b/jni/kotlin/AnySigner.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include + +#include "AnySigner.h" +#include "TWJNI.h" + +jbyteArray JNICALL Java_com_trustwallet_core_AnySigner_sign(JNIEnv *env, jclass thisClass, jbyteArray input, jobject coin) { + jclass coinClass = (*env)->GetObjectClass(env, coin); + jmethodID coinValueMethodID = (*env)->GetMethodID(env, coinClass, "value", "()I"); + uint32_t coinValue = (*env)->CallIntMethod(env, coin, coinValueMethodID); + + TWData *inputData = TWDataCreateWithJByteArray(env, input); + TWData *outputData = TWAnySignerSign(inputData, coinValue); + jbyteArray resultData = TWDataJByteArray(outputData, env); + TWDataDelete(inputData); + return resultData; +} + +jboolean JNICALL Java_com_trustwallet_core_AnySigner_supportsJson(JNIEnv *env, jclass thisClass, jobject coin) { + jclass coinClass = (*env)->GetObjectClass(env, coin); + jmethodID coinValueMethodID = (*env)->GetMethodID(env, coinClass, "value", "()I"); + uint32_t coinValue = (*env)->CallIntMethod(env, coin, coinValueMethodID); + return TWAnySignerSupportsJSON(coinValue); +} + +jstring JNICALL Java_com_trustwallet_core_AnySigner_signJson(JNIEnv *env, jclass thisClass, jstring json, jbyteArray key, jobject coin) { + jclass coinClass = (*env)->GetObjectClass(env, coin); + jmethodID coinValueMethodID = (*env)->GetMethodID(env, coinClass, "value", "()I"); + uint32_t coinValue = (*env)->CallIntMethod(env, coin, coinValueMethodID); + + TWString *jsonString = TWStringCreateWithJString(env, json); + TWData *keyData = TWDataCreateWithJByteArray(env, key); + TWString *result = TWAnySignerSignJSON(jsonString, keyData, coinValue); + TWDataDelete(keyData); + TWStringDelete(jsonString); + return TWStringJString(result, env); +} + +jbyteArray JNICALL Java_com_trustwallet_core_AnySigner_plan(JNIEnv *env, jclass thisClass, jbyteArray input, jobject coin) { + jclass coinClass = (*env)->GetObjectClass(env, coin); + jmethodID coinValueMethodID = (*env)->GetMethodID(env, coinClass, "value", "()I"); + uint32_t coinValue = (*env)->CallIntMethod(env, coin, coinValueMethodID); + + TWData *inputData = TWDataCreateWithJByteArray(env, input); + TWData *outputData = TWAnySignerPlan(inputData, coinValue); + jbyteArray resultData = TWDataJByteArray(outputData, env); + TWDataDelete(inputData); + return resultData; +} diff --git a/jni/kotlin/AnySigner.h b/jni/kotlin/AnySigner.h new file mode 100644 index 00000000000..eac2bd9340b --- /dev/null +++ b/jni/kotlin/AnySigner.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#ifndef JNI_TW_ANYSIGNER_H +#define JNI_TW_ANYSIGNER_H + +#include +#include + +TW_EXTERN_C_BEGIN + +JNIEXPORT +jbyteArray JNICALL Java_com_trustwallet_core_AnySigner_sign(JNIEnv *env, jclass thisClass, jbyteArray input, jobject coin); + +JNIEXPORT +jboolean JNICALL Java_com_trustwallet_core_AnySigner_supportsJson(JNIEnv *env, jclass thisClass, jobject coin); + +JNIEXPORT +jstring JNICALL Java_com_trustwallet_core_AnySigner_signJson(JNIEnv *env, jclass thisClass, jstring json, jbyteArray key, jobject coin); + +JNIEXPORT +jbyteArray JNICALL Java_com_trustwallet_core_AnySigner_plan(JNIEnv *env, jclass thisClass, jbyteArray input, jobject coin); + +TW_EXTERN_C_END + +#endif // JNI_TW_ANYSIGNER_H diff --git a/jni/proto/.gitkeep b/jni/proto/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/kotlin/.editorconfig b/kotlin/.editorconfig new file mode 100644 index 00000000000..87dad77100a --- /dev/null +++ b/kotlin/.editorconfig @@ -0,0 +1,21 @@ +root = true + +[*] +indent_style = space +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 120 + +[*.{kt,kts}] +indent_size = 4 +ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL +ij_kotlin_imports_layout = * +ij_kotlin_allow_trailing_comma = true +ij_kotlin_allow_trailing_comma_on_call_site = true +ij_kotlin_enum_constants_wrap = split_into_lines +ij_kotlin_name_count_to_use_star_import = 5 +ij_kotlin_name_count_to_use_star_import_for_members = 3 +ij_kotlin_packages_to_use_import_on_demand = * +ij_kotlin_wrap_first_method_in_call_chain = true diff --git a/kotlin/.gitignore b/kotlin/.gitignore new file mode 100644 index 00000000000..f8ed7018916 --- /dev/null +++ b/kotlin/.gitignore @@ -0,0 +1,2 @@ +.gradle +local.properties diff --git a/kotlin/README.md b/kotlin/README.md new file mode 100644 index 00000000000..d80405f6046 --- /dev/null +++ b/kotlin/README.md @@ -0,0 +1,3 @@ +### Tasks: + +- `./gradlew :wallet-core-kotlin:generateProtos` – Generates Kotlin classes for Protos diff --git a/kotlin/build-logic/build.gradle.kts b/kotlin/build-logic/build.gradle.kts new file mode 100644 index 00000000000..1f41e03775b --- /dev/null +++ b/kotlin/build-logic/build.gradle.kts @@ -0,0 +1,19 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + `kotlin-dsl` +} + +allprojects { + tasks.withType { + sourceCompatibility = "17" + targetCompatibility = "17" + } + tasks.withType { + compilerOptions { + allWarningsAsErrors.set(true) + jvmTarget.set(JvmTarget.JVM_17) + } + } +} diff --git a/kotlin/build-logic/settings.gradle.kts b/kotlin/build-logic/settings.gradle.kts new file mode 100644 index 00000000000..5cab7b66872 --- /dev/null +++ b/kotlin/build-logic/settings.gradle.kts @@ -0,0 +1,23 @@ +@file:Suppress("UnstableApiUsage") + +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } + + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} diff --git a/kotlin/build-logic/src/main/kotlin/convention.maven-publish.gradle.kts b/kotlin/build-logic/src/main/kotlin/convention.maven-publish.gradle.kts new file mode 100644 index 00000000000..37072540d89 --- /dev/null +++ b/kotlin/build-logic/src/main/kotlin/convention.maven-publish.gradle.kts @@ -0,0 +1,21 @@ +plugins { + `maven-publish` +} + +group = "com.trustwallet" +if (version == Project.DEFAULT_VERSION) { + version = "0.0.0-alpha" +} + +publishing { + repositories { + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/trustwallet/wallet-core") + credentials { + username = System.getenv("GITHUB_USER") + password = System.getenv("GITHUB_TOKEN") + } + } + } +} diff --git a/kotlin/build-logic/src/main/kotlin/convention.proto-generation.gradle.kts b/kotlin/build-logic/src/main/kotlin/convention.proto-generation.gradle.kts new file mode 100644 index 00000000000..112de933e3a --- /dev/null +++ b/kotlin/build-logic/src/main/kotlin/convention.proto-generation.gradle.kts @@ -0,0 +1,56 @@ +val libs = extensions.getByType().named("libs") + +val copyProtoTask = task("copyProtos") { + val sourceDir = rootDir.parentFile.resolve("src/proto") + val destinationDir = projectDir.resolve("build/tmp/proto") + + doFirst { + destinationDir.deleteRecursively() + } + + from(sourceDir) { + include("*.proto") + } + into(destinationDir) + + doLast { + destinationDir + .listFiles { file -> file.extension == "proto" } + .orEmpty() + .forEach { file -> + val packageName = file.nameWithoutExtension.lowercase() + file + .readText() + .replaceFirst( + oldValue = """option java_package = "wallet.core.jni.proto";""", + newValue = """option java_package = "com.trustwallet.core.$packageName";""", + ) + .let { file.writeText(it) } + } + } +} + +val wire: Configuration by configurations.creating +dependencies { + wire(libs.findLibrary("wire.compiler").get().get()) +} + +val generateProtosTask = task("generateProtos") { + dependsOn(copyProtoTask) + + val sourceDir = projectDir.resolve("build/tmp/proto") + val destinationDir = projectDir.resolve("src/commonMain/proto") + + doFirst { + destinationDir.deleteRecursively() + destinationDir.mkdirs() + } + + mainClass.set("com.squareup.wire.WireCompiler") + classpath = wire + + args( + "--proto_path=$sourceDir", + "--kotlin_out=$destinationDir", + ) +} diff --git a/kotlin/build.gradle.kts b/kotlin/build.gradle.kts new file mode 100644 index 00000000000..387b99278c9 --- /dev/null +++ b/kotlin/build.gradle.kts @@ -0,0 +1,21 @@ +// Workaround https://github.com/gradle/gradle/issues/22797 +@file:Suppress("DSL_SCOPE_VIOLATION") + +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + id("com.android.application") version libs.versions.agp.get() apply false + id("com.android.library") version libs.versions.agp.get() apply false + kotlin("android") version libs.versions.kotlin.get() apply false + kotlin("multiplatform") version libs.versions.kotlin.get() apply false +} + +allprojects { + tasks.withType { + compilerOptions { + allWarningsAsErrors.set(true) + jvmTarget.set(JvmTarget.JVM_17) + } + } +} diff --git a/kotlin/gradle.properties b/kotlin/gradle.properties new file mode 100644 index 00000000000..e77b878e37e --- /dev/null +++ b/kotlin/gradle.properties @@ -0,0 +1,11 @@ +# Gradle +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 +org.gradle.parallel=true +# Kotlin +kotlin.code.style=official +kotlin.js.compiler=ir +kotlin.mpp.androidSourceSetLayoutVersion=2 +kotlin.mpp.enableCInteropCommonization=true +# Android +android.useAndroidX=true +android.nonTransitiveRClass=true diff --git a/kotlin/gradle/libs.versions.toml b/kotlin/gradle/libs.versions.toml new file mode 100644 index 00000000000..e6e4289dd86 --- /dev/null +++ b/kotlin/gradle/libs.versions.toml @@ -0,0 +1,14 @@ +[versions] +android-sdk-tools = "33.0.2" +android-sdk-min = "24" +android-sdk-compile = "33" +android-cmake = "3.22.1" +android-ndk = "25.2.9519653" + +kotlin = "1.8.21" +agp = "8.0.0" +wire = "4.5.6" + +[libraries] +wire-runtime = { module = "com.squareup.wire:wire-runtime", version.ref = "wire" } +wire-compiler = { module = "com.squareup.wire:wire-compiler", version.ref = "wire" } diff --git a/kotlin/gradle/wrapper/gradle-wrapper.jar b/kotlin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000..c1962a79e29 Binary files /dev/null and b/kotlin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/kotlin/gradle/wrapper/gradle-wrapper.properties b/kotlin/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..8707e8b5067 --- /dev/null +++ b/kotlin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/kotlin/gradlew b/kotlin/gradlew new file mode 100755 index 00000000000..aeb74cbb43e --- /dev/null +++ b/kotlin/gradlew @@ -0,0 +1,245 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/kotlin/gradlew.bat b/kotlin/gradlew.bat new file mode 100755 index 00000000000..6689b85beec --- /dev/null +++ b/kotlin/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/kotlin/kotlin-js-store/yarn.lock b/kotlin/kotlin-js-store/yarn.lock new file mode 100644 index 00000000000..0995b488885 --- /dev/null +++ b/kotlin/kotlin-js-store/yarn.lock @@ -0,0 +1,2238 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.10.4": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== + dependencies: + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + +"@discoveryjs/json-ext@^0.5.0": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.3": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" + integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.20" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" + integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@rollup/plugin-commonjs@^21.0.1": + version "21.1.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-21.1.0.tgz#45576d7b47609af2db87f55a6d4b46e44fc3a553" + integrity sha512-6ZtHx3VHIp2ReNNDxHjuUml6ur+WcQ28N1yHgCQwsbNkQg2suhxGMDQGJOn/KuDxKtd1xuZP5xSTwBA4GQ8hbA== + dependencies: + "@rollup/pluginutils" "^3.1.0" + commondir "^1.0.1" + estree-walker "^2.0.1" + glob "^7.1.6" + is-reference "^1.2.1" + magic-string "^0.25.7" + resolve "^1.17.0" + +"@rollup/plugin-node-resolve@^13.1.3": + version "13.3.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz#da1c5c5ce8316cef96a2f823d111c1e4e498801c" + integrity sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw== + dependencies: + "@rollup/pluginutils" "^3.1.0" + "@types/resolve" "1.17.1" + deepmerge "^4.2.2" + is-builtin-module "^3.1.0" + is-module "^1.0.0" + resolve "^1.19.0" + +"@rollup/plugin-typescript@^8.3.0": + version "8.5.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.5.0.tgz#7ea11599a15b0a30fa7ea69ce3b791d41b862515" + integrity sha512-wMv1/scv0m/rXx21wD2IsBbJFba8wGF3ErJIr6IKRfRj49S85Lszbxb4DCo8iILpluTjk2GAAu9CoZt4G3ppgQ== + dependencies: + "@rollup/pluginutils" "^3.1.0" + resolve "^1.17.0" + +"@rollup/pluginutils@^3.0.9", "@rollup/pluginutils@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + +"@socket.io/component-emitter@~3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" + integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== + +"@types/cookie@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" + integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== + +"@types/cors@^2.8.12": + version "2.8.17" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.17.tgz#5d718a5e494a8166f569d986794e49c48b216b2b" + integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA== + dependencies: + "@types/node" "*" + +"@types/eslint-scope@^3.7.3": + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "8.44.9" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.9.tgz#5799663009645637bd1c45b2e1a7c8f4caf89534" + integrity sha512-6yBxcvwnnYoYT1Uk2d+jvIfsuP4mb2EdIxFnrPABj5a/838qe5bGkNLFOiipX4ULQ7XVQvTxOh7jO+BTAiqsEw== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^1.0.0": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== + +"@types/json-schema@*", "@types/json-schema@^7.0.8": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/node@*", "@types/node@>=10.0.0": + version "20.10.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.4.tgz#b246fd84d55d5b1b71bf51f964bd514409347198" + integrity sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg== + dependencies: + undici-types "~5.26.4" + +"@types/node@^12.12.14": + version "12.20.55" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" + integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== + +"@types/resolve@1.17.1": + version "1.17.1" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" + integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== + dependencies: + "@types/node" "*" + +"@ungap/promise-all-settled@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" + integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== + +"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" + integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== + +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== + +"@webassemblyjs/helper-buffer@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093" + integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA== + +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" + integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== + +"@webassemblyjs/helper-wasm-section@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577" + integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/wasm-gen" "1.11.6" + +"@webassemblyjs/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== + +"@webassemblyjs/wasm-edit@^1.11.5": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab" + integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.11.6" + "@webassemblyjs/wasm-gen" "1.11.6" + "@webassemblyjs/wasm-opt" "1.11.6" + "@webassemblyjs/wasm-parser" "1.11.6" + "@webassemblyjs/wast-printer" "1.11.6" + +"@webassemblyjs/wasm-gen@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268" + integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wasm-opt@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2" + integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/wasm-gen" "1.11.6" + "@webassemblyjs/wasm-parser" "1.11.6" + +"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1" + integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wast-printer@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20" + integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A== + dependencies: + "@webassemblyjs/ast" "1.11.6" + "@xtuc/long" "4.2.2" + +"@webpack-cli/configtest@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.2.0.tgz#7b20ce1c12533912c3b217ea68262365fa29a6f5" + integrity sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg== + +"@webpack-cli/info@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.5.0.tgz#6c78c13c5874852d6e2dd17f08a41f3fe4c261b1" + integrity sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ== + dependencies: + envinfo "^7.7.3" + +"@webpack-cli/serve@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.7.0.tgz#e1993689ac42d2b16e9194376cfb6753f6254db1" + integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q== + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +abab@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + +accepts@~1.3.4: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-import-assertions@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" + integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== + +acorn@^8.7.1, acorn@^8.8.2: + version "8.11.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" + integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== + +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-colors@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64id@2.0.0, base64id@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" + integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +body-parser@^1.19.0: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +browserslist@^4.14.5: + version "4.22.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" + integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== + dependencies: + caniuse-lite "^1.0.30001565" + electron-to-chromium "^1.4.601" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" + integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== + dependencies: + function-bind "^1.1.2" + get-intrinsic "^1.2.1" + set-function-length "^1.1.1" + +camelcase@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001565: + version "1.0.30001570" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz#b4e5c1fa786f733ab78fc70f592df6b3f23244ca" + integrity sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw== + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@3.5.3, chokidar@^3.5.1: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chrome-trace-event@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colorette@^2.0.14: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +connect@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" + integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== + dependencies: + debug "2.6.9" + finalhandler "1.1.2" + parseurl "~1.3.3" + utils-merge "1.0.1" + +content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +cookie@~0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + +cors@~2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +custom-event@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" + integrity sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg== + +date-format@^4.0.14: + version "4.0.14" + resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400" + integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@4.3.4, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + +decode-uri-component@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +define-data-property@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" + integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +di@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" + integrity sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA== + +diff@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + +dom-serialize@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" + integrity sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ== + dependencies: + custom-event "~1.0.0" + ent "~2.2.0" + extend "^3.0.0" + void-elements "^2.0.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.4.601: + version "1.4.612" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.612.tgz#350c6fd4201d677307519b931949fa64dae6a5cc" + integrity sha512-dM8BMtXtlH237ecSMnYdYuCkib2QHq0kpWfUnavjdYsyr/6OsAwg5ZGUfnQ9KD1Ga4QgB2sqXlB2NT8zy2GnVg== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +engine.io-parser@~5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.1.tgz#9f213c77512ff1a6cc0c7a86108a7ffceb16fcfb" + integrity sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ== + +engine.io@~6.5.2: + version "6.5.4" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.5.4.tgz#6822debf324e781add2254e912f8568508850cdc" + integrity sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg== + dependencies: + "@types/cookie" "^0.4.1" + "@types/cors" "^2.8.12" + "@types/node" ">=10.0.0" + accepts "~1.3.4" + base64id "2.0.0" + cookie "~0.4.1" + cors "~2.8.5" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.11.0" + +enhanced-resolve@^5.15.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +ent@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" + integrity sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA== + +envinfo@^7.7.3: + version "7.11.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.11.0.tgz#c3793f44284a55ff8c82faf1ffd91bc6478ea01f" + integrity sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg== + +es-module-lexer@^1.2.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.4.1.tgz#41ea21b43908fe6a287ffcbe4300f790555331f5" + integrity sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + +estree-walker@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fastest-levenshtein@^1.0.12: + version "1.0.16" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +find-up@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +flatted@^3.2.7: + version "3.2.9" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" + integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== + +follow-redirects@^1.0.0: + version "1.15.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" + integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + +format-util@1.0.5, format-util@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" + integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg== + +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b" + integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== + dependencies: + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.1.3, glob@^7.1.6, glob@^7.1.7: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340" + integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg== + dependencies: + get-intrinsic "^1.2.2" + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== + dependencies: + function-bind "^1.1.2" + +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +interpret@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" + integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-builtin-module@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-reference@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" + integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== + dependencies: + "@types/estree" "*" + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isbinaryfile@^4.0.8: + version "4.0.10" + resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3" + integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +jest-worker@^26.2.1: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + +karma-chrome-launcher@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz#baca9cc071b1562a1db241827257bfe5cab597ea" + integrity sha512-hsIglcq1vtboGPAN+DGCISCFOxW+ZVnIqhDQcCMqqCp+4dmJ0Qpq5QAjkbA0X2L9Mi6OBkHi2Srrbmm7pUKkzQ== + dependencies: + which "^1.2.1" + +karma-mocha@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/karma-mocha/-/karma-mocha-2.0.1.tgz#4b0254a18dfee71bdbe6188d9a6861bf86b0cd7d" + integrity sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ== + dependencies: + minimist "^1.2.3" + +karma-sourcemap-loader@0.3.8: + version "0.3.8" + resolved "https://registry.yarnpkg.com/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.8.tgz#d4bae72fb7a8397328a62b75013d2df937bdcf9c" + integrity sha512-zorxyAakYZuBcHRJE+vbrK2o2JXLFWK8VVjiT/6P+ltLBUGUvqTEkUiQ119MGdOrK7mrmxXHZF1/pfT6GgIZ6g== + dependencies: + graceful-fs "^4.1.2" + +karma-webpack@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-5.0.0.tgz#2a2c7b80163fe7ffd1010f83f5507f95ef39f840" + integrity sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA== + dependencies: + glob "^7.1.3" + minimatch "^3.0.4" + webpack-merge "^4.1.5" + +karma@6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.0.tgz#82652dfecdd853ec227b74ed718a997028a99508" + integrity sha512-s8m7z0IF5g/bS5ONT7wsOavhW4i4aFkzD4u4wgzAQWT4HGUeWI3i21cK2Yz6jndMAeHETp5XuNsRoyGJZXVd4w== + dependencies: + "@colors/colors" "1.5.0" + body-parser "^1.19.0" + braces "^3.0.2" + chokidar "^3.5.1" + connect "^3.7.0" + di "^0.0.1" + dom-serialize "^2.2.1" + glob "^7.1.7" + graceful-fs "^4.2.6" + http-proxy "^1.18.1" + isbinaryfile "^4.0.8" + lodash "^4.17.21" + log4js "^6.4.1" + mime "^2.5.2" + minimatch "^3.0.4" + mkdirp "^0.5.5" + qjobs "^1.2.0" + range-parser "^1.2.1" + rimraf "^3.0.2" + socket.io "^4.4.1" + source-map "^0.6.1" + tmp "^0.2.1" + ua-parser-js "^0.7.30" + yargs "^16.1.1" + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash@^4.17.15, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +log4js@^6.4.1: + version "6.9.1" + resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.9.1.tgz#aba5a3ff4e7872ae34f8b4c533706753709e38b6" + integrity sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g== + dependencies: + date-format "^4.0.14" + debug "^4.3.4" + flatted "^3.2.7" + rfdc "^1.3.0" + streamroller "^3.1.5" + +magic-string@^0.25.7: + version "0.25.9" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" + integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== + dependencies: + sourcemap-codec "^1.4.8" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@^2.5.2: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + +minimatch@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.3, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mkdirp@^0.5.5: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +mocha@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9" + integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA== + dependencies: + "@ungap/promise-all-settled" "1.1.2" + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.3" + debug "4.3.4" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.2.0" + he "1.2.0" + js-yaml "4.1.0" + log-symbols "4.1.0" + minimatch "5.0.1" + ms "2.1.3" + nanoid "3.3.3" + serialize-javascript "6.0.0" + strip-json-comments "3.1.1" + supports-color "8.1.1" + workerpool "6.2.1" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nanoid@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" + integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +object-assign@^4: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.9.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== + dependencies: + ee-first "1.1.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +qjobs@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" + integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +rechoir@^0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" + integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== + dependencies: + resolve "^1.9.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve@^1.17.0, resolve@^1.19.0, resolve@^1.9.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + +rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rollup-plugin-sourcemaps@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.6.3.tgz#bf93913ffe056e414419607f1d02780d7ece84ed" + integrity sha512-paFu+nT1xvuO1tPFYXGe+XnQvg4Hjqv/eIhG8i5EspfYYPBKL57X7iVbfv55aNVASg3dzWvES9dmWsL2KhfByw== + dependencies: + "@rollup/pluginutils" "^3.0.9" + source-map-resolve "^0.6.0" + +rollup-plugin-terser@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d" + integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ== + dependencies: + "@babel/code-frame" "^7.10.4" + jest-worker "^26.2.1" + serialize-javascript "^4.0.0" + terser "^5.0.0" + +rollup@^2.68.0: + version "2.79.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7" + integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== + optionalDependencies: + fsevents "~2.3.2" + +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +schema-utils@^3.1.1, schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +serialize-javascript@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + +serialize-javascript@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" + integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== + dependencies: + randombytes "^2.1.0" + +serialize-javascript@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" + integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== + dependencies: + randombytes "^2.1.0" + +set-function-length@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed" + integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ== + dependencies: + define-data-property "^1.1.1" + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +socket.io-adapter@~2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12" + integrity sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA== + dependencies: + ws "~8.11.0" + +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + +socket.io@^4.4.1: + version "4.7.2" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.7.2.tgz#22557d76c3f3ca48f82e73d68b7add36a22df002" + integrity sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw== + dependencies: + accepts "~1.3.4" + base64id "~2.0.0" + cors "~2.8.5" + debug "~4.3.2" + engine.io "~6.5.2" + socket.io-adapter "~2.5.2" + socket.io-parser "~4.2.4" + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map-loader@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-4.0.0.tgz#bdc6b118bc6c87ee4d8d851f2d4efcc5abdb2ef5" + integrity sha512-i3KVgM3+QPAHNbGavK+VBq03YoJl24m9JWNbLgsjTj8aJzXG9M61bantBTNBt7CNwY2FYf+RJRYJ3pzalKjIrw== + dependencies: + abab "^2.0.6" + iconv-lite "^0.6.3" + source-map-js "^1.0.2" + +source-map-resolve@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" + integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +streamroller@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.1.5.tgz#1263182329a45def1ffaef58d31b15d13d2ee7ff" + integrity sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw== + dependencies: + date-format "^4.0.14" + debug "^4.3.4" + fs-extra "^8.1.0" + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@8.1.1, supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +terser-webpack-plugin@^5.3.7: + version "5.3.9" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" + integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.17" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.1" + terser "^5.16.8" + +terser@^5.0.0, terser@^5.16.8: + version "5.26.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.26.0.tgz#ee9f05d929f4189a9c28a0feb889d96d50126fe1" + integrity sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tslib@^2.3.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typescript@4.7.4: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== + +typescript@^3.7.2: + version "3.9.10" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" + integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== + +ua-parser-js@^0.7.30: + version "0.7.37" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.37.tgz#e464e66dac2d33a7a1251d7d7a99d6157ec27832" + integrity sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +vary@^1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +void-elements@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" + integrity sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung== + +watchpack@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" + integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +webpack-cli@4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.10.0.tgz#37c1d69c8d85214c5a65e589378f53aec64dab31" + integrity sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w== + dependencies: + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^1.2.0" + "@webpack-cli/info" "^1.5.0" + "@webpack-cli/serve" "^1.7.0" + colorette "^2.0.14" + commander "^7.0.0" + cross-spawn "^7.0.3" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^2.2.0" + rechoir "^0.7.0" + webpack-merge "^5.7.3" + +webpack-merge@^4.1.5: + version "4.2.2" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" + integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== + dependencies: + lodash "^4.17.15" + +webpack-merge@^5.7.3: + version "5.10.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" + integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.0" + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@5.89.0: + version "5.89.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.89.0.tgz#56b8bf9a34356e93a6625770006490bf3a7f32dc" + integrity sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^1.0.0" + "@webassemblyjs/ast" "^1.11.5" + "@webassemblyjs/wasm-edit" "^1.11.5" + "@webassemblyjs/wasm-parser" "^1.11.5" + acorn "^8.7.1" + acorn-import-assertions "^1.9.0" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.15.0" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.9" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.2.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.7" + watchpack "^2.4.0" + webpack-sources "^3.2.3" + +which@^1.2.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wildcard@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== + +workerpool@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@~8.11.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" + integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-unparser@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + +yargs@16.2.0, yargs@^16.1.1: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/kotlin/settings.gradle.kts b/kotlin/settings.gradle.kts new file mode 100644 index 00000000000..481134aa1f2 --- /dev/null +++ b/kotlin/settings.gradle.kts @@ -0,0 +1,27 @@ +@file:Suppress("UnstableApiUsage") + +rootProject.name = "WalletCoreKotlin" + +pluginManagement { + repositories { + google() + mavenCentral() + } +} + +dependencyResolutionManagement { + // Uncomment after https://youtrack.jetbrains.com/issue/KT-55620/ + // repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +includeBuild( + "build-logic", +) + +include( + ":wallet-core-kotlin" +) diff --git a/kotlin/wallet-core-kotlin/.gitignore b/kotlin/wallet-core-kotlin/.gitignore new file mode 100644 index 00000000000..a9e76bfd475 --- /dev/null +++ b/kotlin/wallet-core-kotlin/.gitignore @@ -0,0 +1,2 @@ +/src/**/generated/ +/src/**/proto/ diff --git a/kotlin/wallet-core-kotlin/build.gradle.kts b/kotlin/wallet-core-kotlin/build.gradle.kts new file mode 100644 index 00000000000..63d879ca7c3 --- /dev/null +++ b/kotlin/wallet-core-kotlin/build.gradle.kts @@ -0,0 +1,147 @@ +@file:Suppress("UnstableApiUsage", "OPT_IN_USAGE") + +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackOutput + +plugins { + kotlin("multiplatform") + id("com.android.library") + id("convention.maven-publish") + id("convention.proto-generation") +} + +kotlin { + targetHierarchy.default() + + android { + publishLibraryVariants = listOf("release") + } + + jvm { + testRuns.named("test") { + executionTask.configure { + useJUnitPlatform() + } + } + } + + val nativeTargets = + listOf( + iosArm64(), + iosSimulatorArm64(), + iosX64(), + ) + + js { + browser { + webpackTask { + output.libraryTarget = KotlinWebpackOutput.Target.COMMONJS2 + } + } + useCommonJs() + } + + sourceSets { + all { + languageSettings { + optIn("kotlin.js.ExperimentalJsExport") + } + } + + val commonMain by getting { + kotlin.srcDirs( + projectDir.resolve("src/commonMain/generated"), + projectDir.resolve("src/commonMain/proto"), + ) + + dependencies { + api(libs.wire.runtime) + } + } + + getByName("commonTest") { + dependencies { + implementation(kotlin("test")) + } + } + + val androidMain by getting + val jvmMain by getting + create("commonAndroidJvmMain") { + kotlin.srcDir(projectDir.resolve("src/commonAndroidJvmMain/generated")) + + dependsOn(commonMain) + androidMain.dependsOn(this) + jvmMain.dependsOn(this) + } + + getByName("iosMain") { + kotlin.srcDir(projectDir.resolve("src/iosMain/generated")) + } + + getByName("jsMain") { + kotlin.srcDir(projectDir.resolve("src/jsMain/generated")) + + dependencies { + implementation(npm(name = "webpack", version = "5.89.0")) + } + } + } + + nativeTargets.forEach { nativeTarget -> + nativeTarget.apply { + val main by compilations.getting + main.cinterops.create("WalletCore") { + packageName = "com.trustwallet.core" + headers(rootDir.parentFile.resolve("include/TrustWalletCore").listFiles()!!) + } + } + } +} + +android { + namespace = "com.trustwallet.core" + compileSdk = libs.versions.android.sdk.compile.get().toInt() + buildToolsVersion = libs.versions.android.sdk.tools.get() + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + defaultConfig { + minSdk = libs.versions.android.sdk.min.get().toInt() + ndkVersion = libs.versions.android.ndk.get() + + consumerProguardFiles += projectDir.resolve("consumer-rules.pro") + + externalNativeBuild { + cmake { + arguments += listOf("-DCMAKE_BUILD_TYPE=Release", "-DKOTLIN=True", "-DTW_UNITY_BUILD=ON") + } + } + } + + buildFeatures { + aidl = false + compose = false + buildConfig = false + prefab = false + renderScript = false + resValues = false + shaders = false + viewBinding = false + } + + androidComponents { + beforeVariants { + it.enable = it.name == "release" + } + } + + externalNativeBuild { + cmake { + version = libs.versions.android.cmake.get() + path = rootDir.parentFile.resolve("CMakeLists.txt") + } + } +} diff --git a/kotlin/wallet-core-kotlin/consumer-rules.pro b/kotlin/wallet-core-kotlin/consumer-rules.pro new file mode 100644 index 00000000000..877985aff56 --- /dev/null +++ b/kotlin/wallet-core-kotlin/consumer-rules.pro @@ -0,0 +1,15 @@ +-keepclassmembers class com.trustwallet.core.* { + # Usage example: (*env)->GetFieldID(env, thisClass, "nativeHandle", "J"); + private long nativeHandle; + # Usage example: (*env)->GetStaticMethodID(env, class, "createFromNative", "(J)Lcom/trustwallet/core/Account;"); + private static ** createFromNative(long); +} + +-keepclassmembers enum com.trustwallet.core.* { + # Usage example: (*env)->GetFieldID(env, thisClass, "value", "I"); + private int value; + # Usage example: (*env)->GetMethodID(env, coinClass, "value", "()I"); + public int value(); + # Usage example: (*env)->GetStaticMethodID(env, class, "createFromValue", "(I)Lcom/trustwallet/core/CoinType;"); + public static ** createFromValue(int); +} diff --git a/kotlin/wallet-core-kotlin/src/androidUnitTest/kotlin/com/trustwallet/core/LibLoader.kt b/kotlin/wallet-core-kotlin/src/androidUnitTest/kotlin/com/trustwallet/core/LibLoader.kt new file mode 100644 index 00000000000..1d4f9311712 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/androidUnitTest/kotlin/com/trustwallet/core/LibLoader.kt @@ -0,0 +1,7 @@ +package com.trustwallet.core + +actual object LibLoader { + actual fun loadLibrary() { + throw NotImplementedError() + } +} diff --git a/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/AnySigner.kt b/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/AnySigner.kt new file mode 100644 index 00000000000..e6b348433c4 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/AnySigner.kt @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core + +actual object AnySigner { + + @JvmStatic + actual external fun sign(input: ByteArray, coin: CoinType): ByteArray + + @JvmStatic + actual external fun supportsJson(coin: CoinType): Boolean + + @JvmStatic + actual external fun signJson(json: String, key: ByteArray, coin: CoinType): String + + @JvmStatic + actual external fun plan(input: ByteArray, coin: CoinType): ByteArray +} diff --git a/kotlin/wallet-core-kotlin/src/commonMain/kotlin/com/trustwallet/core/AnySigner.kt b/kotlin/wallet-core-kotlin/src/commonMain/kotlin/com/trustwallet/core/AnySigner.kt new file mode 100644 index 00000000000..576aa1d32cb --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/commonMain/kotlin/com/trustwallet/core/AnySigner.kt @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core + +import com.squareup.wire.Message +import com.squareup.wire.ProtoAdapter + +expect object AnySigner { + + fun sign(input: ByteArray, coin: CoinType): ByteArray + + fun supportsJson(coin: CoinType): Boolean + + fun signJson(json: String, key: ByteArray, coin: CoinType): String + + fun plan(input: ByteArray, coin: CoinType): ByteArray +} + +fun > AnySigner.sign(input: Message<*, *>, coin: CoinType, adapter: ProtoAdapter): T = + adapter.decode(sign(input.encode(), coin)) + +fun > AnySigner.plan(input: Message<*, *>, coin: CoinType, adapter: ProtoAdapter): T = + adapter.decode(plan(input.encode(), coin)) diff --git a/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/LibLoader.kt b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/LibLoader.kt new file mode 100644 index 00000000000..5d78d0dc298 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/LibLoader.kt @@ -0,0 +1,5 @@ +package com.trustwallet.core + +expect object LibLoader { + fun loadLibrary() +} diff --git a/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt new file mode 100644 index 00000000000..c3d3dbf0527 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt @@ -0,0 +1,151 @@ +package com.trustwallet.core.test + +import com.trustwallet.core.CoinType +import com.trustwallet.core.CoinType.* +import com.trustwallet.core.HDWallet +import com.trustwallet.core.LibLoader +import kotlin.test.Test +import kotlin.test.assertEquals + +class CoinAddressDerivationTests { + + init { + LibLoader.loadLibrary() + } + + @Test + fun testDeriveAddressesFromPhrase() { + val wallet = HDWallet("shoot island position soft burden budget tooth cruel issue economy destroy above", "") + + CoinType.values().forEach { coin -> + val address = wallet.getAddressForCoin(coin) + val expectedAddress = getExpectedAddress(coin) + + assertEquals(expectedAddress, address, "Coin = $coin") + } + } + + private fun getExpectedAddress(coin: CoinType): String = when (coin) { + Binance -> "bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw" + TBinance -> "tbnb12vtaxl9952zm6rwf7v8jerq74pvaf77fkw9xhl" + Bitcoin -> "bc1quvuarfksewfeuevuc6tn0kfyptgjvwsvrprk9d" + BitcoinDiamond -> "1KaRW9xPPtCTZ9FdaTHduCPck4YvSeEWNn" + BitcoinCash -> "bitcoincash:qpzl3jxkzgvfd9flnd26leud5duv795fnv7vuaha70" + BitcoinGold -> "btg1qwz9sed0k4neu6ycrudzkca6cnqe3zweq35hvtg" + Callisto -> "0x3E6FFC80745E6669135a76F4A7ce6BCF02436e04" + Dash -> "XqHiz8EXYbTAtBEYs4pWTHh7ipEDQcNQeT" + DigiByte -> "dgb1qtjgmerfqwdffyf8ghcrkgy52cghsqptynmyswu" + + Ethereum, SmartChain, Polygon, Optimism, Zksync, Arbitrum, ArbitrumNova, ECOChain, AvalancheCChain, XDai, + Fantom, Celo, CronosChain, SmartBitcoinCash, KuCoinCommunityChain, Boba, Metis, + Aurora, Evmos, Moonriver, Moonbeam, KavaEvm, Kaia, Meter, OKXChain, PolygonzkEVM, Scroll, + ConfluxeSpace, AcalaEVM, OpBNB, Neon, Base, Linea, Greenfield, Mantle, ZenEON, MantaPacific, + ZetaEVM, Merlin, Lightlink, Blast, BounceBit, ZkLinkNova, + -> "0x8f348F300873Fd5DA36950B2aC75a26584584feE" + + Ronin -> "ronin:8f348F300873Fd5DA36950B2aC75a26584584feE" + EthereumClassic -> "0x078bA3228F3E6C08bEEac9A005de0b7e7089aD1c" + GoChain -> "0x5940ce4A14210d4Ccd0ac206CE92F21828016aC2" + Groestlcoin -> "grs1qexwmshts5pdpeqglkl39zyl6693tmfwp0cue4j" + ICON -> "hx18b380b53c23dc4ee9f6666bc20d1be02f3fe106" + Litecoin -> "ltc1qhd8fxxp2dx3vsmpac43z6ev0kllm4n53t5sk0u" + Ontology -> "AHKTnybvnWo3TeY8uvNXekvYxMrXogUjeT" + POANetwork -> "0xe8a3e8bE17E172B6926130eAfB521e9D2849aca9" + XRP -> "rPwE3gChNKtZ1mhH3Ko8YFGqKmGRWLWXV3" + Tezos -> "tz1acnY9VbMagps26Kj3RfoGRWD9nYG5qaRX" + ThunderCore -> "0x4b92b3ED6d8b24575Bf5ce4C6a86ED261DA0C8d7" + Viction -> "0xC74b6D8897cBa9A4b659d43fEF73C9cA852cE424" + Tron -> "TQ5NMqJjhpQGK7YJbESKtNCo86PJ89ujio" + VeChain -> "0x1a553275dF34195eAf23942CB7328AcF9d48c160" + Wanchain -> "0xD5ca90b928279FE5D06144136a25DeD90127aC15" + Komodo -> "RCWJLXE5CSXydxdSnwcghzPgkFswERegyb" + Zcash -> "t1YYnByMzdGhQv3W3rnjHMrJs6HH4Y231gy" + Zen -> "znUmzvod1f4P9LYsBhNxjqCDQvNSStAmYEX" + Firo -> "aEd5XFChyXobvEics2ppAqgK3Bgusjxtik" + Nimiq -> "NQ76 7AVR EHDA N05U X7SY XB14 XJU7 8ERV GM6H" + Stellar -> "GA3H6I4C5XUBYGVB66KXR27JV5KS3APSTKRUWOIXZ5MVWZKVTLXWKZ2P" + Aion -> "0xa0629f34c9ea4757ad0b275628d4d02e3db6c9009ba2ceeba76a5b55fb2ca42e" + Nano -> "nano_39gsbcishxn3n7wd17ono4otq5wazwzusqgqigztx73wbrh5jwbdbshfnumc" + Nebulas -> "n1ZVgEidtdseYv9ogmGz69Cz4mbqmHYSNqJ" + NEAR -> "0c91f6106ff835c0195d5388565a2d69e25038a7e23d26198f85caf6594117ec" + Theta, ThetaFuel -> "0x0d1fa20c218Fec2f2C55d52aB267940485fa5DA4" + Cosmos -> "cosmos142j9u5eaduzd7faumygud6ruhdwme98qsy2ekn" + Decred -> "DsidJiDGceqHTyqiejABy1ZQ3FX4SiWZkYG" + Dogecoin -> "DJRFZNg8jkUtjcpo2zJd92FUAzwRjitw6f" + Kin -> "GBL3MT2ICHHM5OJ2QJ44CAHGDK6MLPINVXBKOKLHGBWQDVRWTWQ7U2EA" + Viacoin -> "via1qnmsgjd6cvfprnszdgmyg9kewtjfgqflz67wwhc" + Verge -> "DPb3Xz4vjB6QGLKDmrbprrtv4XzNqkADc2" + Qtum -> "QhceuaTdeCZtcxmVc6yyEDEJ7Riu5gWFoF" + NULS -> "NULSd6HgU8MoRnNjBgvJpa9tqvGxYdv5ne4en" + EOS -> "EOS6hs8sRvGSzuQtq223zwJipMzqTJpXUVjyvHPvPwBSZWWrJTJkg" + WAX -> "EOS6hs8sRvGSzuQtq223zwJipMzqTJpXUVjyvHPvPwBSZWWrJTJkg" + IoTeX -> "io1qw9cccecw09q7p5kzyqtuhfhvah2mhfrc84jfk" + IoTeXEVM -> "0x038B8C633873Ca0f06961100BE5d37676EADDD23" + Zilliqa -> "zil1mk6pqphhkmaguhalq6n3cq0h38ltcehg0rfmv6" + Zelcash -> "t1UKbRPzL4WN8Rs8aZ8RNiWoD2ftCMHKGUf" + Ravencoin -> "RHoCwPc2FCQqwToYnSiAb3SrCET4zEHsbS" + Waves -> "3P63vkaHhyE9pPv9EfsjwGKqmZYcCRHys4n" + Aeternity -> "ak_QDHJSfvHG9sDHBobaWt2TAGhuhipYjEqZEH34bWugpJfJc3GN" + Terra, TerraV2 -> "terra1rh402g98t7sly8trzqw5cyracntlep6qe3smug" + Monacoin -> "M9xFZzZdZhCDxpx42cM8bQHnLwaeX1aNja" + FIO -> "FIO7MN1LuSfFgrbVHmrt9cVa2FYAs857Ppr9dzvEXoD1miKSxm3n3" + Harmony -> "one12fk20wmvgypdkn59n4hq8e3aa5899xfx4vsu09" + Solana -> "2bUBiBNZyD29gP1oV6de7nxowMLoDBtopMMTGgMvjG5m" + Algorand -> "JTJWO524JXIHVPGBDWFLJE7XUIA32ECOZOBLF2QP3V5TQBT3NKZSCG67BQ" + Acala -> "25GGezx3LWFQj6HZpYzoWoVzLsHojGtybef3vthC9nd19ms3" + Kusama -> "G9xV2EatmrjRC1FLPexc3ddqNRRzCsAdURU8RFiAAJX6ppY" + Polkadot -> "13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk" + Pivx -> "D81AqC8zKma3Cht4TbVuh4jyVVyLkZULCm" + Kava -> "kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87" + Cardano -> "addr1qyr8jjfnypp95eq74aqzn7ss687ehxclgj7mu6gratmg3mul2040vt35dypp042awzsjk5xm3zr3zm5qh7454uwdv08s84ray2" + NEO -> "AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf" + Filecoin -> "f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori" + MultiversX -> "erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8" + BandChain -> "band1624hqgend0s3d94z68fyka2y5jak6vd7u0l50r" + SmartChainLegacy -> "0x49784f90176D8D9d4A3feCDE7C1373dAAb5b13b8" + Oasis -> "oasis1qzcpavvmuw280dk0kd4lxjhtpf0u3ll27yf7sqps" + THORChain -> "thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65" + IOST -> "4av8w81EyzUgHonsVWqfs15WM4Vrpgox4BYYQWhNQDVu" + Syscoin -> "sys1qkl640se3mwpt666e3lyywnwh09e9jquvx9x8qj" + Stratis -> "strax1q0caanaw4nkf6fzwnzq2p7yum680e57pdg05zkm" + Bluzelle -> "bluzelle1xccvees6ev4wm2r49rc6ptulsdxa8x8jfpmund" + CryptoOrg -> "cro16fdf785ejm00jf9a24d23pzqzjh2h05klxjwu8" + Osmosis -> "osmo142j9u5eaduzd7faumygud6ruhdwme98qclefqp" + ECash -> "ecash:qpelrdn7a0hcucjlf9ascz3lkxv7r3rffgzn6x5377" + NativeEvmos -> "evmos13u6g7vqgw074mgmf2ze2cadzvkz9snlwstd20d" + Nervos -> "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3" + Everscale -> "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04" + TON -> "UQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUaT4" + Aptos -> "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30" + Nebl -> "NgDVaXAwNgBwb88xLiFKomfBmPkEh9F2d7" + Sui -> "0xada112cfb90b44ba889cc5d39ac2bf46281e4a91f7919c693bcd9b8323e81ed2" + Hedera -> "0.0.302a300506032b657003210049eba62f64d0d941045595d9433e65d84ecc46bcdb1421de55e05fcf2d8357d5" + Secret -> "secret1f69sk5033zcdr2p2yf3xjehn7xvgdeq09d2llh" + NativeInjective -> "inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a" + Agoric -> "agoric18zvvgk6j3eq5wd7mqxccgt20gz2w94cy88aek5" + Stargaze -> "stars142j9u5eaduzd7faumygud6ruhdwme98qycayaz" + Juno -> "juno142j9u5eaduzd7faumygud6ruhdwme98qxkfz30" + Stride -> "stride142j9u5eaduzd7faumygud6ruhdwme98qn029zl" + Axelar -> "axelar142j9u5eaduzd7faumygud6ruhdwme98q52u3aj" + Crescent -> "cre142j9u5eaduzd7faumygud6ruhdwme98q5veur7" + Kujira -> "kujira142j9u5eaduzd7faumygud6ruhdwme98qpvgpme" + NativeCanto -> "canto13u6g7vqgw074mgmf2ze2cadzvkz9snlwqua5pd" + Comdex -> "comdex142j9u5eaduzd7faumygud6ruhdwme98qhtgm0y" + Neutron -> "neutron142j9u5eaduzd7faumygud6ruhdwme98q5mrmv5" + Sommelier -> "somm142j9u5eaduzd7faumygud6ruhdwme98quc948e" + FetchAI -> "fetch142j9u5eaduzd7faumygud6ruhdwme98qrera5y" + Mars -> "mars142j9u5eaduzd7faumygud6ruhdwme98qdenqrg" + Umee -> "umee142j9u5eaduzd7faumygud6ruhdwme98qzjhxjp" + Coreum -> "core1rawf376jz2lnchgc4wzf4h9c77neg3zldc7xa8" + Quasar -> "quasar142j9u5eaduzd7faumygud6ruhdwme98q78symk" + Persistence -> "persistence142j9u5eaduzd7faumygud6ruhdwme98q7gv2ch" + Akash -> "akash142j9u5eaduzd7faumygud6ruhdwme98qal870f" + Noble -> "noble142j9u5eaduzd7faumygud6ruhdwme98qc8l3wa" + Rootstock -> "0xA2D7065F94F838a3aB9C04D67B312056846424Df" + Sei -> "sei142j9u5eaduzd7faumygud6ruhdwme98qagm0sj" + InternetComputer -> "6f8e568160a3c8362789848dc0fa52891964473c045cc25208a305fb35b7c4ab" + Tia -> "celestia142j9u5eaduzd7faumygud6ruhdwme98qpwmfv7" + NativeZetaChain -> "zeta13u6g7vqgw074mgmf2ze2cadzvkz9snlwywj304" + Dydx -> "dydx142j9u5eaduzd7faumygud6ruhdwme98qeayaky" + } +} diff --git a/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/AnySigner.kt b/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/AnySigner.kt new file mode 100644 index 00000000000..6de0b110bea --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/AnySigner.kt @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core + +actual object AnySigner { + + actual fun sign(input: ByteArray, coin: CoinType): ByteArray = + TWAnySignerSign(input.toTwData(), coin.value)!!.readTwBytes()!! + + actual fun supportsJson(coin: CoinType): Boolean = + TWAnySignerSupportsJSON(coin.value) + + actual fun signJson(json: String, key: ByteArray, coin: CoinType): String = + TWAnySignerSignJSON(json.toTwString(), key.toTwData(), coin.value).fromTwString()!! + + actual fun plan(input: ByteArray, coin: CoinType): ByteArray = + TWAnySignerPlan(input.toTwData(), coin.value)?.readTwBytes()!! +} diff --git a/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/ByteArrayExt.kt b/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/ByteArrayExt.kt new file mode 100644 index 00000000000..ec5923272a1 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/ByteArrayExt.kt @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core + +import kotlinx.cinterop.COpaquePointer +import kotlinx.cinterop.readBytes +import kotlinx.cinterop.toCValues + +internal fun COpaquePointer?.readTwBytes(): ByteArray? = + TWDataBytes(this)?.readBytes(TWDataSize(this).toInt()) + +@OptIn(ExperimentalUnsignedTypes::class) +internal fun ByteArray?.toTwData(): COpaquePointer? = + TWDataCreateWithBytes(this?.toUByteArray()?.toCValues(), this?.size?.toULong() ?: 0u) diff --git a/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/StringExt.kt b/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/StringExt.kt new file mode 100644 index 00000000000..2eb7fdd311e --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/StringExt.kt @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core + +import kotlinx.cinterop.COpaquePointer +import kotlinx.cinterop.CValuesRef +import kotlinx.cinterop.toKString + +internal fun String?.toTwString(): COpaquePointer? = + this?.let { TWStringCreateWithUTF8Bytes(it) } + +internal fun CValuesRef<*>?.fromTwString(): String? = + this?.let { TWStringUTF8Bytes(it)?.toKString() } diff --git a/kotlin/wallet-core-kotlin/src/iosTest/kotlin/com/trustwallet/core/LibLoader.kt b/kotlin/wallet-core-kotlin/src/iosTest/kotlin/com/trustwallet/core/LibLoader.kt new file mode 100644 index 00000000000..1d4f9311712 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/iosTest/kotlin/com/trustwallet/core/LibLoader.kt @@ -0,0 +1,7 @@ +package com.trustwallet.core + +actual object LibLoader { + actual fun loadLibrary() { + throw NotImplementedError() + } +} diff --git a/kotlin/wallet-core-kotlin/src/jsMain/kotlin/WalletCore.kt b/kotlin/wallet-core-kotlin/src/jsMain/kotlin/WalletCore.kt new file mode 100644 index 00000000000..a454889ea72 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/jsMain/kotlin/WalletCore.kt @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import com.trustwallet.core.* +import kotlin.js.Promise + +@JsExport +@JsName("WalletCoreKotlin") +object WalletCore { + + lateinit var Instance: JsWalletCore + private set + + fun init(): Promise = + if (::Instance.isInitialized) { + Promise.resolve(Instance) + } else { + WalletCoreExports.initWasm() + .then { walletCore -> + Instance = walletCore + walletCore + } + } +} + +@JsModule("@trustwallet/wallet-core") +@JsNonModule +private external object WalletCoreExports { + fun initWasm(): Promise +} diff --git a/kotlin/wallet-core-kotlin/src/jsMain/kotlin/com/trustwallet/core/AnySigner.kt b/kotlin/wallet-core-kotlin/src/jsMain/kotlin/com/trustwallet/core/AnySigner.kt new file mode 100644 index 00000000000..d62bf4f81c3 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/jsMain/kotlin/com/trustwallet/core/AnySigner.kt @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core + +import WalletCore + +actual object AnySigner { + + actual fun sign(input: ByteArray, coin: CoinType): ByteArray = + WalletCore.Instance.AnySigner.sign(input.asUInt8Array(), coin.jsValue).asByteArray() + + actual fun supportsJson(coin: CoinType): Boolean = + WalletCore.Instance.AnySigner.supportsJSON(coin.jsValue) + + actual fun signJson(json: String, key: ByteArray, coin: CoinType): String = + TODO() + + actual fun plan(input: ByteArray, coin: CoinType): ByteArray = + WalletCore.Instance.AnySigner.plan(input.asUInt8Array(), coin.jsValue).asByteArray() +} diff --git a/kotlin/wallet-core-kotlin/src/jsMain/kotlin/com/trustwallet/core/ByteArray.kt b/kotlin/wallet-core-kotlin/src/jsMain/kotlin/com/trustwallet/core/ByteArray.kt new file mode 100644 index 00000000000..9163d5bdca2 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/jsMain/kotlin/com/trustwallet/core/ByteArray.kt @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core + +import org.khronos.webgl.Int8Array +import org.khronos.webgl.Uint8Array + +internal typealias UInt8Array = Uint8Array + +fun Int8Array.asByteArray(): ByteArray = + unsafeCast() + +fun Uint8Array.asByteArray(): ByteArray = + Int8Array(buffer, byteOffset, length).asByteArray() + +fun ByteArray.asInt8Array(): Int8Array = + unsafeCast() + +fun ByteArray.asUInt8Array(): Uint8Array = + asInt8Array().let { Uint8Array(it.buffer, it.byteOffset, it.length) } diff --git a/kotlin/wallet-core-kotlin/src/jsMain/kotlin/com/trustwallet/core/JsAnySigner.kt b/kotlin/wallet-core-kotlin/src/jsMain/kotlin/com/trustwallet/core/JsAnySigner.kt new file mode 100644 index 00000000000..75be5fbd8d7 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/jsMain/kotlin/com/trustwallet/core/JsAnySigner.kt @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core + +@JsModule("@trustwallet/wallet-core") +@JsName("AnySigner") +external class JsAnySigner { + companion object { + fun sign(data: UInt8Array, coin: JsCoinType): UInt8Array + fun plan(data: UInt8Array, coin: JsCoinType): UInt8Array + fun supportsJSON(coin: JsCoinType): Boolean + } +} + +inline val JsWalletCore.AnySigner: JsAnySigner.Companion + get() = asDynamic().AnySigner.unsafeCast() diff --git a/kotlin/wallet-core-kotlin/src/jsMain/kotlin/com/trustwallet/core/JsWalletCore.kt b/kotlin/wallet-core-kotlin/src/jsMain/kotlin/com/trustwallet/core/JsWalletCore.kt new file mode 100644 index 00000000000..0865e64e531 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/jsMain/kotlin/com/trustwallet/core/JsWalletCore.kt @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +@file:Suppress("PropertyName") + +package com.trustwallet.core + +@JsModule("@trustwallet/wallet-core") +@JsName("WalletCore") +external interface JsWalletCore diff --git a/kotlin/wallet-core-kotlin/src/jsTest/kotlin/com/trustwallet/core/LibLoader.kt b/kotlin/wallet-core-kotlin/src/jsTest/kotlin/com/trustwallet/core/LibLoader.kt new file mode 100644 index 00000000000..1d4f9311712 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/jsTest/kotlin/com/trustwallet/core/LibLoader.kt @@ -0,0 +1,7 @@ +package com.trustwallet.core + +actual object LibLoader { + actual fun loadLibrary() { + throw NotImplementedError() + } +} diff --git a/kotlin/wallet-core-kotlin/src/jvmMain/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt b/kotlin/wallet-core-kotlin/src/jvmMain/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt new file mode 100644 index 00000000000..23c20a24897 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/jvmMain/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt @@ -0,0 +1,60 @@ +package com.trustwallet.core + +import com.trustwallet.core.WalletCoreLibLoader.Linux64Path +import com.trustwallet.core.WalletCoreLibLoader.MacArm64Path +import java.io.File +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Simple helper class that extracts proper native library for current OS/arch from .jar to temporary file and loads it. + * You can do the same by yourself, just use path constants: [MacArm64Path], [Linux64Path] + */ +object WalletCoreLibLoader { + + const val MacArm64Path = "/jni/macos-arm64/libTrustWalletCore.dylib" + const val Linux64Path = "/jni/linux-x86_64/libTrustWalletCore.so" + + private val isLoaded = AtomicBoolean(false) + + @JvmStatic + fun loadLibrary() { + if (isLoaded.compareAndSet(false, true)) { + val resLibPath = getLibResourcePath() + val resLibStream = WalletCoreLibLoader::class.java.getResourceAsStream(resLibPath) + ?: error("File not found: $resLibPath") + + val fileOut = File.createTempFile("libTrustWalletCore", null) + fileOut.deleteOnExit() + + resLibStream.copyTo(fileOut.outputStream()) + + @Suppress("UnsafeDynamicallyLoadedCode") + System.load(fileOut.absolutePath) + } + } + + private fun getLibResourcePath(): String { + val osNameOriginal = System.getProperty("os.name").lowercase() + val osName = osNameOriginal.lowercase() + val archOriginal = System.getProperty("os.arch").lowercase() + val arch = archOriginal.lowercase() + + return when { + osName.startsWith("mac") -> { + when (arch) { + "aarch64" -> MacArm64Path + else -> error("Arch is not supported: $archOriginal") + } + } + + osName.startsWith("linux") -> { + when (arch) { + "amd64", "x86_64" -> Linux64Path + else -> error("Arch is not supported: $archOriginal") + } + } + + else -> error("OS is not supported: $osNameOriginal") + } + } +} diff --git a/kotlin/wallet-core-kotlin/src/jvmMain/resources/jni/linux-x86_64/.gitignore b/kotlin/wallet-core-kotlin/src/jvmMain/resources/jni/linux-x86_64/.gitignore new file mode 100644 index 00000000000..6519c51f274 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/jvmMain/resources/jni/linux-x86_64/.gitignore @@ -0,0 +1 @@ +libTrustWalletCore.so diff --git a/kotlin/wallet-core-kotlin/src/jvmMain/resources/jni/macos-arm64/.gitignore b/kotlin/wallet-core-kotlin/src/jvmMain/resources/jni/macos-arm64/.gitignore new file mode 100644 index 00000000000..9162c6ec1f1 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/jvmMain/resources/jni/macos-arm64/.gitignore @@ -0,0 +1 @@ +libTrustWalletCore.dylib diff --git a/kotlin/wallet-core-kotlin/src/jvmTest/kotlin/com/trustwallet/core/LibLoader.kt b/kotlin/wallet-core-kotlin/src/jvmTest/kotlin/com/trustwallet/core/LibLoader.kt new file mode 100644 index 00000000000..0c153abd9e7 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/jvmTest/kotlin/com/trustwallet/core/LibLoader.kt @@ -0,0 +1,7 @@ +package com.trustwallet.core + +actual object LibLoader { + actual fun loadLibrary() { + WalletCoreLibLoader.loadLibrary() + } +} diff --git a/kotlin/wallet-core-kotlin/src/nativeInterop/cinterop/WalletCore.def b/kotlin/wallet-core-kotlin/src/nativeInterop/cinterop/WalletCore.def new file mode 100644 index 00000000000..e69de29bb2d diff --git a/protobuf-plugin/CMakeLists.txt b/protobuf-plugin/CMakeLists.txt index 45d18608f21..237789be7be 100644 --- a/protobuf-plugin/CMakeLists.txt +++ b/protobuf-plugin/CMakeLists.txt @@ -1,33 +1,26 @@ -# Copyright © 2017-2022 Trust Wallet. +# SPDX-License-Identifier: Apache-2.0 # -# This file is part of Trust. The full Trust copyright notice, including -# terms governing use, modification, and redistribution, is contained in the -# file LICENSE at the root of the source code distribution tree. +# Copyright © 2017 Trust Wallet. -cmake_minimum_required(VERSION 3.2 FATAL_ERROR) +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(TrustWalletCoreProtobufPlugin) set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version" FORCE) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -if ("$ENV{PREFIX}" STREQUAL "") +if("$ENV{PREFIX}" STREQUAL "") set(PREFIX "${CMAKE_SOURCE_DIR}/../build/local") else() set(PREFIX "$ENV{PREFIX}") endif() -include_directories(${PREFIX}/include) -link_directories(${PREFIX}/lib) +find_package(Protobuf CONFIG REQUIRED PATH ${PREFIX}/lib/pkgconfig) -find_package(Protobuf REQUIRED PATH ${PREFIX}/lib/pkgconfig) -include_directories(${Protobuf_INCLUDE_DIRS}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) +add_executable(protoc-gen-c-typedef c_typedef.cc) +target_link_libraries(protoc-gen-c-typedef protobuf::libprotobuf protobuf::libprotoc) -add_executable(protoc-gen-c-typedef c_typedef.cc ${PROTO_SRCS} ${PROTO_HDRS}) -target_link_libraries(protoc-gen-c-typedef protobuf -lprotoc -pthread) - -add_executable(protoc-gen-swift-typealias swift_typealias.cc ${PROTO_SRCS} ${PROTO_HDRS}) -target_link_libraries(protoc-gen-swift-typealias protobuf -lprotoc -pthread) +add_executable(protoc-gen-swift-typealias swift_typealias.cc) +target_link_libraries(protoc-gen-swift-typealias protobuf::libprotobuf protobuf::libprotoc) install(TARGETS protoc-gen-c-typedef protoc-gen-swift-typealias DESTINATION bin) diff --git a/protobuf-plugin/c_typedef.cc b/protobuf-plugin/c_typedef.cc index 9cd48a5e798..1352898cb73 100644 --- a/protobuf-plugin/c_typedef.cc +++ b/protobuf-plugin/c_typedef.cc @@ -14,16 +14,14 @@ class Generator : public compiler::CodeGenerator { return "TW" + proto_file.substr(0, index) + "Proto.h"; } - bool Generate(const FileDescriptor* file, const std::string& parameter, compiler::GeneratorContext* generator_context, string* error) const { + bool Generate(const FileDescriptor* file, const std::string& parameter, compiler::GeneratorContext* generator_context, std::string* error) const { std::unique_ptr output(generator_context->Open(GetOutputFilename(file->name()))); io::Printer printer(output.get(), '$'); printer.Print( - "// Copyright © 2017-2020 Trust Wallet.\n" + "// SPDX-License-Identifier: Apache-2.0\n" "//\n" - "// This file is part of Trust. The full Trust copyright notice, including\n" - "// terms governing use, modification, and redistribution, is contained in the\n" - "// file LICENSE at the root of the source code distribution tree.\n" + "// Copyright © 2017 Trust Wallet.\n" "//\n" "// This is a GENERATED FILE, changes made here WILL BE LOST.\n" "\n" diff --git a/protobuf-plugin/swift_typealias.cc b/protobuf-plugin/swift_typealias.cc index 57a169dea03..3d0983c2ca3 100644 --- a/protobuf-plugin/swift_typealias.cc +++ b/protobuf-plugin/swift_typealias.cc @@ -16,16 +16,14 @@ class Generator : public compiler::CodeGenerator { return proto_file.substr(0, index) + "+Proto.swift"; } - bool Generate(const FileDescriptor* file, const std::string& parameter, compiler::GeneratorContext* generator_context, string* error) const { + bool Generate(const FileDescriptor* file, const std::string& parameter, compiler::GeneratorContext* generator_context, std::string* error) const { std::unique_ptr output(generator_context->Open(GetOutputFilename(file->name()))); io::Printer printer(output.get(), '$'); printer.Print( - "// Copyright © 2017-2020 Trust Wallet.\n" + "// SPDX-License-Identifier: Apache-2.0\n" "//\n" - "// This file is part of Trust. The full Trust copyright notice, including\n" - "// terms governing use, modification, and redistribution, is contained in the\n" - "// file LICENSE at the root of the source code distribution tree.\n" + "// Copyright © 2017 Trust Wallet.\n" "\n" ); diff --git a/registry.json b/registry.json index bfd72132808..1bb8123783a 100644 --- a/registry.json +++ b/registry.json @@ -18,6 +18,12 @@ "path": "m/44'/0'/0'/0/0", "xpub": "xpub", "xprv": "xprv" + }, + { + "name": "testnet", + "path": "m/84'/1'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" } ], "curve": "secp256k1", @@ -28,9 +34,9 @@ "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", "explorer": { - "url": "https://blockchair.com", - "txPath": "/bitcoin/transaction/", - "accountPath": "/bitcoin/address/", + "url": "https://mempool.space", + "txPath": "/tx/", + "accountPath": "/address/", "sampleTx": "0607f62530b68cfcc91c57a1702841dd399a899d0eecda8e31ecca3f52f01df2", "sampleAccount": "17A16QmavnUfCW11DAApiJxp7ARnxN5pGX" }, @@ -309,6 +315,161 @@ "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" } }, + { + "id": "syscoin", + "name": "Syscoin", + "coinId": 57, + "symbol": "SYS", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "path": "m/84'/57'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 63, + "p2shPrefix": 5, + "hrp": "sys", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://sys1.bcfn.ca", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "19e043f76f6ffc960f5fe93ecec37bc37a58ae7525d7e9cd6ed40f71f0da60eb", + "sampleAccount": "sys1qh3gvhnzq2ch7w8g04x8zksr2mz7r90x7ksmu40" + }, + "info": { + "url": "https://syscoin.org", + "source": "https://github.com/syscoin", + "rpc": "https://sys1.bcfn.ca", + "documentation": "https://docs.syscoin.org" + } + }, + { + "id": "base", + "name": "Base", + "coinId": 8453, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "8453", + "addressHasher": "keccak256", + "explorer": { + "url": "https://basescan.org", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x4acb15506b7696a2dfac4258f3f86392b4b2b717a3f316a8aa78509b2c3b6ab4", + "sampleAccount": "0xb8ff877ed78ba520ece21b1de7843a8a57ca47cb" + }, + "info": { + "url": "https://base.mirror.xyz/", + "source": "https://github.com/base-org", + "rpc": "https://mainnet.base.org", + "documentation": "https://docs.base.org/" + } + }, + { + "id": "linea", + "name": "Linea", + "coinId": 59144, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "59144", + "addressHasher": "keccak256", + "explorer": { + "url": "https://lineascan.build", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x0c7086f96865f4fcad58d7f3449db7baab9fce2625bcb79e7ea26676aa0d3420", + "sampleAccount": "0xbf71018f716ca6c64b0b12622f87a26b3b86100f" + }, + "info": { + "url": "https://linea.build", + "source": "https://github.com/LineaLabs", + "rpc": "https://rpc.linea.build", + "documentation": "https://docs.linea.build" + } + }, + { + "id": "mantle", + "name": "Mantle", + "coinId": 5000, + "symbol": "MNT", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "5000", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.mantle.xyz", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xfae996ea23f1ff9909ac04d26ae6e52ab600a84163fab9e0e893483c685629dd", + "sampleAccount": "0xA295EEFd401C8BE1457F266d3e73cdD015e5CFbb" + }, + "info": { + "url": "https://www.mantle.xyz", + "source": "https://github.com/mantlenetworkio", + "rpc": "https://rpc.mantle.xyz", + "documentation": "https://docs.mantle.xyz/network/introduction/overview" + } + }, + { + "id": "zeneon", + "name": "Zen EON", + "coinId": 7332, + "symbol": "ZEN", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "7332", + "addressHasher": "keccak256", + "explorer": { + "url": "https://eon-explorer.horizenlabs.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xb462e3dac8eef21957d3b6cff3c184d083434367a726dd871e98a774f4d037a5", + "sampleAccount": "0x09bCfC348101B1179BCF3837aC996cF09357215f" + }, + "info": { + "url": "https://eon.horizen.io", + "source": "https://github.com/HorizenOfficial/eon", + "rpc": "https://eon-rpc.horizenlabs.io/ethv1", + "documentation": "https://eon.horizen.io/docs" + } + }, { "id": "ethereum", "name": "Ethereum", @@ -358,7 +519,9 @@ "explorer": { "url": "https://blockscout.com/etc/mainnet", "txPath": "/tx/", - "accountPath": "/address/" + "accountPath": "/address/", + "sampleTx": "0x66004165d3901819dc22e568931591d2e4287bda54995f4ce2701a12016f5997", + "sampleAccount": "0x9eab4b0fc468a7f5d46228bf5a76cb52370d068d" }, "info": { "url": "https://ethereumclassic.org", @@ -394,1440 +557,3427 @@ } }, { - "id": "cosmos", - "name": "Cosmos", - "displayName": "Cosmos Hub", - "coinId": 118, - "symbol": "ATOM", + "id": "verge", + "name": "Verge", + "coinId": 77, + "symbol": "XVG", "decimals": 6, - "blockchain": "Cosmos", + "blockchain": "Verge", "derivation": [ { - "path": "m/44'/118'/0'/0/0" + "path": "m/84'/77'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" } ], "curve": "secp256k1", "publicKeyType": "secp256k1", - "hrp": "cosmos", - "addressHasher": "sha256ripemd", + "p2pkhPrefix": 30, + "p2shPrefix": 33, + "hrp": "vg", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", "explorer": { - "url": "https://mintscan.io/cosmos", - "txPath": "/txs/", - "accountPath": "/account/" + "url": "https://verge-blockchain.info", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "8c99979a2b25a46659bff35b238aab1c3158f736f215d99526429c7c96203581", + "sampleAccount": "DFre88gd87bAZQdnS7dbBLwT6GWiGFMQB6" }, "info": { - "url": "https://cosmos.network", - "source": "https://github.com/cosmos/cosmos-sdk", - "rpc": "https://stargate.cosmos.network", - "documentation": "https://cosmos.network/rpc" + "url": "https://vergecurrency.com", + "source": "https://github.com/vergecurrency/verge", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" } }, { - "id": "zcash", - "name": "Zcash", - "coinId": 133, - "symbol": "ZEC", + "id": "pivx", + "name": "Pivx", + "coinId": 119, + "symbol": "PIVX", "decimals": 8, - "blockchain": "Zcash", + "blockchain": "Bitcoin", "derivation": [ { - "path": "m/44'/133'/0'/0/0", + "path": "m/44'/119'/0'/0/0", "xpub": "xpub", "xprv": "xprv" } ], "curve": "secp256k1", "publicKeyType": "secp256k1", - "staticPrefix": 28, - "p2pkhPrefix": 184, - "p2shPrefix": 189, + "p2pkhPrefix": 30, + "p2shPrefix": 13, "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", "explorer": { - "url": "https://blockchair.com/zcash", + "url": "https://pivx.ccore.online", "txPath": "/transaction/", "accountPath": "/address/" }, "info": { - "url": "https://z.cash", + "url": "https://pivx.org", "source": "https://github.com/trezor/blockbook", "rpc": "", "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" } }, { - "id": "firo", - "name": "Firo", - "coinId": 136, - "symbol": "FIRO", + "id": "zen", + "name": "Zen", + "coinId": 121, + "symbol": "ZEN", "decimals": 8, - "blockchain": "Bitcoin", + "blockchain": "Zen", "derivation": [ { - "path": "m/44'/136'/0'/0/0", + "path": "m/44'/121'/0'/0/0", "xpub": "xpub", "xprv": "xprv" } ], "curve": "secp256k1", "publicKeyType": "secp256k1", - "p2pkhPrefix": 82, - "p2shPrefix": 7, + "staticPrefix": 32, + "p2pkhPrefix": 137, + "p2shPrefix": 150, "publicKeyHasher": "sha256ripemd", "base58Hasher": "sha256d", "explorer": { - "url": "https://explorer.firo.org", + "url": "https://explorer.horizen.io", "txPath": "/tx/", - "accountPath": "/address/" + "accountPath": "/address/", + "sampleTx": "b7f548640766fb024247accf4e01bec37d88d49c4900357edc84d49a09ff4430", + "sampleAccount": "znRchPtvEyJJUwGbCALqyjwHJb1Gx6z4H4j" }, "info": { - "url": "https://firo.org/", - "source": "https://github.com/firoorg/firo", + "url": "https://www.horizen.io", + "source": "https://github.com/trezor/blockbook", "rpc": "", "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" } }, { - "id": "ripple", - "name": "XRP", - "coinId": 144, - "symbol": "XRP", - "decimals": 6, - "blockchain": "Ripple", + "id": "aptos", + "name": "Aptos", + "displayName": "Aptos", + "coinId": 637, + "symbol": "APT", + "decimals": 8, + "chainId": "1", + "blockchain": "Aptos", "derivation": [ { - "path": "m/44'/144'/0'/0/0" + "path": "m/44'/637'/0'/0'/0'" } ], - "curve": "secp256k1", - "publicKeyType": "secp256k1", + "curve": "ed25519", + "publicKeyType": "ed25519", "explorer": { - "url": "https://bithomp.com", - "txPath": "/explorer/", - "accountPath": "/explorer/", - "sampleTx": "E26AB8F3372D2FC02DEC1FD5674ADAB762D684BFFDBBDF5D674E9D7CF4A47054", - "sampleAccount": "rfkH7EuS1XcSkB9pocy1R6T8F4CsNYixYU" + "url": "https://explorer.aptoslabs.com", + "txPath": "/txn/", + "accountPath": "/account/", + "sampleTx": "0xedc88058e27f6c065fd6607e262cb2a83a65f74301df90c61923014c59f9d465", + "sampleAccount": "0x60ad80e8cdadb81399e8a738014bc9ec865cef842f7c2cf7d84fbf7e40d065" }, "info": { - "url": "https://ripple.com/xrp", - "source": "https://github.com/ripple/rippled", - "rpc": "https://s2.ripple.com:51234", - "documentation": "https://xrpl.org/rippled-api.html" + "url": "https://aptoslabs.com/", + "source": "https://github.com/aptos-labs/aptos-core", + "rpc": "https://fullnode.mainnet.aptoslabs.com/v1", + "documentation": "https://fullnode.mainnet.aptoslabs.com/v1/spec#/" } }, { - "id": "bitcoincash", - "name": "Bitcoin Cash", - "coinId": 145, - "symbol": "BCH", - "decimals": 8, - "blockchain": "Bitcoin", + "id": "sui", + "name": "Sui", + "coinId": 784, + "symbol": "SUI", + "decimals": 9, + "blockchain": "Sui", "derivation": [ { - "path": "m/44'/145'/0'/0/0", - "xpub": "xpub", - "xprv": "xprv" + "path": "m/44'/784'/0'/0'/0'" } ], - "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 0, - "p2shPrefix": 5, - "hrp": "bitcoincash", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", + "curve": "ed25519", + "publicKeyType": "ed25519", "explorer": { - "url": "https://blockchair.com", - "txPath": "/bitcoin-cash/transaction/", - "accountPath": "/bitcoin-cash/address/" + "url": "https://explorer.sui.io/", + "txPath": "/txblock/", + "accountPath": "/address/", + "sampleTx": "5i8fbSL6r8yw2xcKmXxwkzHu3wpiyMLsyf2htCvDH8Ao", + "sampleAccount": "0x259ff8074ab425cbb489f236e18e08f03f1a7856bdf7c7a2877bd64f738b5015" }, "info": { - "url": "https://bitcoincash.org", - "source": "https://github.com/trezor/blockbook", - "rpc": "", - "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + "url": "https://sui.io/", + "source": "https://github.com/MystenLabs/sui", + "rpc": "https://fullnode.testnet.sui.io", + "documentation": "https://docs.sui.io/" } }, { - "id": "stellar", - "name": "Stellar", - "coinId": 148, - "symbol": "XLM", - "decimals": 7, - "blockchain": "Stellar", + "id": "cosmos", + "name": "Cosmos", + "displayName": "Cosmos Hub", + "coinId": 118, + "symbol": "ATOM", + "decimals": 6, + "chainId": "cosmoshub-4", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/148'/0'" + "path": "m/44'/118'/0'/0/0" } ], - "curve": "ed25519", - "publicKeyType": "ed25519", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "cosmos", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://blockchair.com/stellar", - "txPath": "/transaction/", - "accountPath": "/account/" + "url": "https://mintscan.io/cosmos", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "541FA06FB37AC1BF61922143783DD76FECA361830F9876D0342536EE8A87A790", + "sampleAccount": "cosmos1gu6y2a0ffteesyeyeesk23082c6998xyzmt9mz" }, "info": { - "url": "https://stellar.org", - "source": "https://github.com/stellar/go", - "rpc": "https://horizon.stellar.org", - "documentation": "https://www.stellar.org/developers/horizon/reference" + "url": "https://cosmos.network", + "source": "https://github.com/cosmos/cosmos-sdk", + "rpc": "https://stargate.cosmos.network", + "documentation": "https://cosmos.network/rpc" } }, { - "id": "bitcoingold", - "name": "Bitcoin Gold", - "coinId": 156, - "symbol": "BTG", - "decimals": 8, - "blockchain": "Bitcoin", + "id": "stargaze", + "name": "Stargaze", + "displayName": "Stargaze", + "coinId": 20000118, + "symbol": "STARS", + "decimals": 6, + "chainId": "stargaze-1", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/84'/156'/0'/0/0", - "xpub": "zpub", - "xprv": "zprv" + "path": "m/44'/118'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1", - "p2pkhPrefix": 38, - "p2shPrefix": 23, - "hrp": "btg", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", + "hrp": "stars", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://explorer.bitcoingold.org/insight", - "txPath": "/tx/", - "accountPath": "/address/" + "url": "https://www.mintscan.io/stargaze", + "txPath": "/txs/", + "accountPath": "/account/" }, "info": { - "url": "https://bitcoingold.org", - "source": "https://github.com/trezor/blockbook", - "rpc": "", - "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + "url": "https://www.stargaze.zone/", + "source": "https://github.com/public-awesome/stargaze", + "rpc": "https://stargaze-rpc.polkachu.com/", + "documentation": "https://docs.stargaze.zone/guides/readme" } }, { - "id": "nano", - "name": "Nano", - "coinId": 165, - "symbol": "XNO", - "decimals": 30, - "blockchain": "Nano", + "id": "juno", + "name": "Juno", + "displayName": "Juno", + "coinId": 30000118, + "symbol": "JUNO", + "decimals": 6, + "chainId": "juno-1", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/165'/0'" + "path": "m/44'/118'/0'/0/0" } ], - "curve": "ed25519Blake2bNano", - "publicKeyType": "ed25519Blake2b", - "url": "https://nano.org", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "juno", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://nanocrawler.cc", - "txPath": "/explorer/block/", - "accountPath": "/explorer/account/", - "sampleTx": "C264DB7BF40738F0CEFF19B606746CB925B713E4B8699A055699E0DC8ABBC70F", - "sampleAccount": "nano_1wpj616kwhe1y38y1mspd8aub8i334cwybqco511iyuxm55zx8d67ptf1tsf" + "url": "https://www.mintscan.io/juno", + "txPath": "/txs/", + "accountPath": "/account/" }, "info": { - "url": "https://nano.org", - "source": "https://github.com/nanocurrency/nano-node", - "rpc": "", - "documentation": "https://docs.nano.org/commands/rpc-protocol/" + "url": "https://www.junonetwork.io/", + "source": "https://github.com/CosmosContracts/juno", + "rpc": "https://juno-rpc.polkachu.com", + "documentation": "https://docs.junonetwork.io/juno/readme" } }, { - "id": "ravencoin", - "name": "Ravencoin", - "coinId": 175, - "symbol": "RVN", - "decimals": 8, - "blockchain": "Bitcoin", + "id": "stride", + "name": "Stride", + "displayName": "Stride", + "coinId": 40000118, + "symbol": "STRD", + "decimals": 6, + "chainId": "stride-1", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/175'/0'/0/0", - "xpub": "xpub", - "xprv": "xprv" + "path": "m/44'/118'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1", - "p2pkhPrefix": 60, - "p2shPrefix": 122, - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", + "hrp": "stride", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://ravencoin.network", - "txPath": "/tx/", - "accountPath": "/address/" + "url": "https://www.mintscan.io/stride", + "txPath": "/txs/", + "accountPath": "/account/" }, "info": { - "url": "https://ravencoin.org", - "source": "https://github.com/trezor/blockbook", - "rpc": "", - "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + "url": "https://stride.zone/", + "source": "https://github.com/Stride-Labs/stride", + "rpc": "https://stride-rpc.polkachu.com/", + "documentation": "https://docs.stride.zone/docs" } }, { - "id": "poa", - "name": "POA Network", - "coinId": 178, - "symbol": "POA", - "decimals": 18, - "blockchain": "Ethereum", + "id": "axelar", + "name": "Axelar", + "displayName": "Axelar", + "coinId": 50000118, + "symbol": "AXL", + "decimals": 6, + "chainId": "axelar-dojo-1", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/178'/0'/0/0" + "path": "m/44'/118'/0'/0/0" } ], "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "chainId": "99", - "addressHasher": "keccak256", + "publicKeyType": "secp256k1", + "hrp": "axelar", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://blockscout.com", - "txPath": "/poa/core/tx/", - "accountPath": "/poa/core/address/" + "url": "https://www.mintscan.io/axelar", + "txPath": "/txs/", + "accountPath": "/account/" }, "info": { - "url": "https://poa.network", - "source": "https://github.com/poanetwork/parity-ethereum", - "rpc": "https://core.poa.network", - "documentation": "https://eth.wiki/json-rpc/API" + "url": "https://axelar.network/", + "source": "https://github.com/axelarnetwork/axelar-core", + "rpc": "https://axelar-rpc.polkachu.com", + "documentation": "https://docs.axelar.dev/" } }, { - "id": "eos", - "name": "EOS", - "coinId": 194, - "symbol": "EOS", - "decimals": 4, - "blockchain": "EOS", + "id": "crescent", + "name": "Crescent", + "displayName": "Crescent", + "coinId": 60000118, + "symbol": "CRE", + "decimals": 6, + "chainId": "crescent-1", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/194'/0'/0/0" + "path": "m/44'/118'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1", + "hrp": "cre", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://bloks.io", - "txPath": "/transaction/", + "url": "https://www.mintscan.io/crescent", + "txPath": "/txs/", "accountPath": "/account/" }, "info": { - "url": "http://eos.io", - "source": "https://github.com/eosio/eos", - "rpc": "", - "documentation": "https://developers.eos.io/eosio-nodeos/reference" + "url": "https://crescent.network/", + "source": "https://github.com/crescent-network/crescent", + "rpc": "https://crescent-rpc.polkachu.com", + "documentation": "https://docs.crescent.network/introduction/what-is-crescent" } }, { - "id": "tron", - "name": "Tron", - "coinId": 195, - "symbol": "TRX", + "id": "kujira", + "name": "Kujira", + "displayName": "Kujira", + "coinId": 70000118, + "symbol": "KUJI", "decimals": 6, - "blockchain": "Tron", + "chainId": "kaiyo-1", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/195'/0'/0/0" + "path": "m/44'/118'/0'/0/0" } ], "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", + "publicKeyType": "secp256k1", + "hrp": "kujira", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://tronscan.org", - "txPath": "/#/transaction/", - "accountPath": "/#/address/" + "url": "https://www.mintscan.io/kujira", + "txPath": "/txs/", + "accountPath": "/account/" }, "info": { - "url": "https://tron.network", - "source": "https://github.com/tronprotocol/java-tron", - "rpc": "https://api.trongrid.io", - "documentation": "https://developers.tron.network/docs/tron-wallet-rpc-api" + "url": "https://kujira.app/", + "source": "https://github.com/Team-Kujira/core", + "rpc": "https://kujira-rpc.polkachu.com", + "documentation": "https://docs.kujira.app/introduction/readme" } }, { - "id": "fio", - "name": "FIO", - "coinId": 235, - "symbol": "FIO", - "decimals": 9, - "blockchain": "FIO", + "id": "comdex", + "name": "Comdex", + "displayName": "Comdex", + "coinId": 80000118, + "symbol": "CMDX", + "decimals": 6, + "chainId": "comdex-1", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/235'/0'/0/0" + "path": "m/44'/118'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1", - "url": "https://fioprotocol.io/", + "hrp": "comdex", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://explorer.fioprotocol.io", - "txPath": "/transaction/", - "accountPath": "/account/" + "url": "https://www.mintscan.io/comdex", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "04C790D09A40EE958DBDD385B679B5EB60C10F9BC1389CC8F896DC9193A5ED6C", + "sampleAccount": "comdex1jz7av7cq45gh5hhrugtak7lkps2ga5v0u64nz6" }, "info": { - "url": "https://fioprotocol.io", - "source": "https://github.com/fioprotocol/fio", - "rpc": "https://mainnet.fioprotocol.io", - "documentation": "https://developers.fioprotocol.io" + "url": "https://comdex.one/", + "documentation": "https://docs.comdex.one/" } }, { - "id": "nimiq", - "name": "Nimiq", - "coinId": 242, - "symbol": "NIM", - "decimals": 5, - "blockchain": "Nimiq", + "id": "neutron", + "name": "Neutron", + "displayName": "Neutron", + "coinId": 90000118, + "symbol": "NTRN", + "decimals": 6, + "chainId": "neutron-1", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/242'/0'/0'" + "path": "m/44'/118'/0'/0/0" } ], - "curve": "ed25519", - "publicKeyType": "ed25519", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "neutron", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://nimiq.watch", - "txPath": "/#", - "accountPath": "/#" + "url": "https://www.mintscan.io/neutron", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "E18BA087009A05EB6A15A22FE30BA99379B909F74A74120E6F92B9882C45F0D7", + "sampleAccount": "neutron1pm4af8pcurxssdxztqw9rexx5f8zfq7uzqfmy8" }, "info": { - "url": "https://nimiq.com", - "source": "https://github.com/nimiq/core-rs", - "rpc": "", - "documentation": "https://github.com/nimiq/core-js/wiki/JSON-RPC-API" + "url": "https://neutron.org/", + "documentation": "https://docs.neutron.org/" } }, { - "id": "algorand", - "name": "Algorand", - "coinId": 283, - "symbol": "ALGO", + "id": "sommelier", + "name": "Sommelier", + "displayName": "Sommelier", + "coinId": 11000118, + "symbol": "SOMM", "decimals": 6, - "blockchain": "Algorand", + "chainId": "sommelier-3", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/283'/0'/0'/0'" + "path": "m/44'/118'/0'/0/0" } ], - "curve": "ed25519", - "publicKeyType": "ed25519", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "somm", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://algoexplorer.io", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "CR7POXFTYDLC7TV3IXHA7AZKWABUJC52BACLHJQNXAKZJGRPQY3A", - "sampleAccount": "J4AEINCSSLDA7LNBNWM4ZXFCTLTOZT5LG3F5BLMFPJYGFWVCMU37EZI2AM" + "url": "https://www.mintscan.io/sommelier", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "E73A9E5E534777DDADF7F69A5CB41972894B862D1763FA4081FE913D8D3A5E80", + "sampleAccount": "somm10d5wmqvezwtj20u5hg3wuvwucce2nhsy0tzqgn" }, "info": { - "url": "https://www.algorand.com/", - "source": "https://github.com/algorand/go-algorand", - "rpc": "https://indexer.algorand.network", - "documentation": "https://developer.algorand.org/docs/algod-rest-paths" + "url": "https://www.sommelier.finance/" } }, { - "id": "iotex", - "name": "IoTeX", - "coinId": 304, - "symbol": "IOTX", - "decimals": 18, - "blockchain": "IoTeX", + "id": "fetchai", + "name": "FetchAI", + "displayName": "Fetch AI", + "coinId": 12000118, + "symbol": "FET", + "decimals": 6, + "chainId": "fetchhub-4", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/304'/0'/0/0" + "path": "m/44'/118'/0'/0/0" } ], "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "hrp": "io", + "publicKeyType": "secp256k1", + "hrp": "fetch", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://iotexscan.io", - "txPath": "/action/", - "accountPath": "/address/" + "url": "https://www.mintscan.io/fetchai", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "7EB4F6C26809BA047F81CEFD0889775AC8522B7B8AF559B436083BE7039C5EA6", + "sampleAccount": "fetch1t3qet68dr0qkmrjtq89lrx837qa2t05265qy6s" }, "info": { - "url": "https://iotex.io", - "source": "https://github.com/iotexproject/iotex-core", - "rpc": "", - "documentation": "https://docs.iotex.io/#api" + "url": "https://fetch.ai/", + "documentation": "https://docs.fetch.ai/" } }, { - "id": "zilliqa", - "name": "Zilliqa", - "coinId": 313, - "symbol": "ZIL", - "decimals": 12, - "blockchain": "Zilliqa", + "id": "mars", + "name": "Mars", + "displayName": "Mars Hub", + "coinId": 13000118, + "symbol": "MARS", + "decimals": 6, + "chainId": "mars-1", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/313'/0'/0/0" + "path": "m/44'/118'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1", - "hrp": "zil", + "hrp": "mars", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://viewblock.io", - "txPath": "/zilliqa/tx/", - "accountPath": "/zilliqa/address/" + "url": "https://www.mintscan.io/mars-protocol", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "C12120760C71189A678739E0F1FD4EFAF2C29EA660B57A359AC728F89FAA7528", + "sampleAccount": "mars1nnjy6nct405pzfaqjm3dsyw0pf0kyw72vhw4pr" }, "info": { - "url": "https://zilliqa.com", - "source": "https://github.com/Zilliqa/Zilliqa", - "rpc": "https://api.zilliqa.com", - "documentation": "https://apidocs.zilliqa.com" + "url": "https://marsprotocol.io/", + "documentation": "https://docs.marsprotocol.io/" } }, { - "id": "terra", - "name": "Terra", - "displayName": "Terra Classic", - "coinId": 330, - "symbol": "LUNC", + "id": "umee", + "name": "Umee", + "displayName": "Umee", + "coinId": 14000118, + "symbol": "UMEE", "decimals": 6, + "chainId": "umee-1", "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/330'/0'/0/0" + "path": "m/44'/118'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1", - "hrp": "terra", + "hrp": "umee", "addressHasher": "sha256ripemd", "explorer": { - "url": "https://finder.terra.money/tx", - "txPath": "/tx/", - "accountPath": "/address/" + "url": "https://www.mintscan.io/umee", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "65B4B52C5F324F2287540847A114F645D89D544D99F793879FB3DBFF2CFEFC84", + "sampleAccount": "umee16934q0qf4akw8qruy5y8v748rvtxxjckgsecq4" }, "info": { - "url": "https://terra.money", - "source": "https://github.com/terra-project/core", - "rpc": "https://classic.fcd.terra.dev", - "documentation": "https://docs.terra.money" - }, - "deprecated": true + "url": "https://umee.cc/", + "documentation": "https://umeeversity.umee.cc/developers/" + } }, { - "id": "polkadot", - "name": "Polkadot", - "coinId": 354, - "symbol": "DOT", - "decimals": 10, - "blockchain": "Polkadot", + "id": "noble", + "name": "Noble", + "displayName": "Noble", + "coinId": 18000118, + "symbol": "USDC", + "decimals": 6, + "chainId": "noble-1", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/354'/0'/0'/0'" + "path": "m/44'/118'/0'/0/0" } ], - "curve": "ed25519", - "publicKeyType": "ed25519", - "addressHasher": "keccak256", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "noble", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://polkadot.subscan.io", - "txPath": "/extrinsic/", - "accountPath": "/account/" + "url": "https://www.mintscan.io/noble", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "EA231079975A058FEC28EF372B445763918C098DE033E868E2E035F3F98C59C7", + "sampleAccount": "noble1y2egevq0nyzm7w6a9kpxkw86eqytcvxpwsp6d9" }, "info": { - "url": "https://polkadot.network/", - "source": "https://github.com/paritytech/polkadot", - "rpc": "", - "documentation": "https://polkadot.js.org/api/substrate/rpc.html" + "url": "https://nobleassets.xyz/" } }, { - "id": "near", - "name": "NEAR", - "coinId": 397, - "symbol": "NEAR", - "decimals": 24, - "blockchain": "NEAR", + "id": "sei", + "name": "Sei", + "displayName": "Sei", + "coinId": 19000118, + "symbol": "SEI", + "decimals": 6, + "blockchain": "Cosmos", + "chainId": "pacific-1", "derivation": [ { - "path": "m/44'/397'/0'" + "path": "m/44'/118'/0'/0/0" } ], - "curve": "ed25519", - "publicKeyType": "ed25519", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "sei", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://explorer.near.org", - "txPath": "/transactions/", - "accountPath": "/accounts/" + "url": "https://www.mintscan.io/sei", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "4A2114EE45317439690F3BEA9C8B6CFA11D42CF978F9487754902D372EEB488C", + "sampleAccount": "sei155hqv2rsypqzq0zpjn72frsxx4l6tcmplw63m2" }, "info": { - "url": "https://nearprotocol.com", - "source": "https://github.com/nearprotocol/nearcore", - "rpc": "https://rpc.nearprotocol.com", - "documentation": "https://docs.nearprotocol.com" + "url": "https://sei.io/", + "documentation": "https://docs.sei.io/" } }, { - "id": "aion", - "name": "Aion", - "coinId": 425, - "symbol": "AION", - "decimals": 18, - "blockchain": "Aion", + "id": "tia", + "name": "Tia", + "displayName": "Celestia", + "coinId": 21000118, + "symbol": "TIA", + "decimals": 6, + "blockchain": "Cosmos", + "chainId": "celestia", "derivation": [ { - "path": "m/44'/425'/0'/0'/0'" + "path": "m/44'/118'/0'/0/0" } ], - "curve": "ed25519", - "publicKeyType": "ed25519", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "celestia", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://mainnet.aion.network", - "txPath": "/#/transaction/", - "accountPath": "/#/account/" + "url": "https://www.mintscan.io/celestia", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "FF370C65D8D67B8236F9D3A8D2B1256337C60C1965092CADD1FA970288FCE99B", + "sampleAccount": "celestia1tt4tv4jrs4twdtzwywxd8u65duxgk8y73wvfu2" }, "info": { - "url": "https://aion.network", - "source": "https://github.com/aionnetwork/aion", - "rpc": "", - "documentation": "https://github.com/aionnetwork/aion/wiki/JSON-RPC-API-Docs" + "url": "https://celestia.org/", + "documentation": "https://docs.celestia.org/" } }, { - "id": "kusama", - "name": "Kusama", - "coinId": 434, - "symbol": "KSM", - "decimals": 12, - "blockchain": "Kusama", + "id": "coreum", + "name": "Coreum", + "displayName": "Coreum", + "coinId": 10000990, + "symbol": "CORE", + "decimals": 6, + "chainId": "coreum-mainnet-1", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/434'/0'/0'/0'" + "path": "m/44'/990'/0'/0/0" } ], - "curve": "ed25519", - "publicKeyType": "ed25519", - "addressHasher": "keccak256", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "core", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://kusama.subscan.io", - "txPath": "/extrinsic/", + "url": "https://www.mintscan.io/coreum", + "txPath": "/txs/", "accountPath": "/account/", - "sampleTx": "0xcbe0c2e2851c1245bedaae4d52f06eaa6b4784b786bea2f0bff11af7715973dd", - "sampleAccount": "DbCNECPna3k6MXFWWNZa5jGsuWycqEE6zcUxZYkxhVofrFk" + "sampleTx": "32A4AE2AE6AAE31E75EDDADE0AB9F1499ABD5AD8D3F261ADEF2805CD46FF74E7", + "sampleAccount": "core1zmwdnfpwuymwn0fkwnj2aaje34npd5sqgjxq9v" }, "info": { - "url": "https://kusama.network", - "source": "https://github.com/paritytech/polkadot", - "rpc": "wss://kusama-rpc.polkadot.io/", - "documentation": "https://polkadot.js.org/api/substrate/rpc.html" + "url": "https://www.coreum.com/", + "documentation": "https://www.coreum.com/developers" } }, { - "id": "aeternity", - "name": "Aeternity", - "coinId": 457, - "symbol": "AE", - "decimals": 18, - "blockchain": "Aeternity", + "id": "quasar", + "name": "Quasar", + "displayName": "Quasar", + "coinId": 15000118, + "symbol": "QSR", + "decimals": 6, + "chainId": "quasar-1", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/457'/0'/0'/0'" + "path": "m/44'/118'/0'/0/0" } ], - "curve": "ed25519", - "publicKeyType": "ed25519", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "quasar", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://explorer.aepps.com", - "txPath": "/transactions/", - "accountPath": "/account/transactions/" + "url": "https://www.mintscan.io/quasar", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "2898B89C98FE1E8CF1E05A37E4EE5EE5ED83FD957B0CAEE53DE39FC82BF1A033", + "sampleAccount": "quasar1cqu6w425slheul3jsmyt6q0ec0rs0w0ugkst3k" }, "info": { - "url": "https://aeternity.com", - "source": "https://github.com/aeternity/aeternity", - "rpc": "https://sdk-mainnet.aepps.com", - "documentation": "http://aeternity.com/api-docs/" + "url": "https://www.quasar.fi/", + "documentation": "https://docs.quasar.fi/" } }, { - "id": "kava", - "name": "Kava", - "coinId": 459, - "symbol": "KAVA", + "id": "persistence", + "name": "Persistence", + "displayName": "Persistence", + "coinId": 16000118, + "symbol": "XPRT", "decimals": 6, + "chainId": "core-1", "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/459'/0'/0/0" + "path": "m/44'/118'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1", - "hrp": "kava", + "hrp": "persistence", "addressHasher": "sha256ripemd", "explorer": { - "url": "https://mintscan.io/kava", + "url": "https://www.mintscan.io/persistence", "txPath": "/txs/", "accountPath": "/account/", - "sampleTx": "2988DF83FCBFAA38179D583A96734CBD071541D6768221BB23111BC8136D5E6A", - "sampleAccount": "kava1jf9aaj9myrzsnmpdr7twecnaftzmku2mdpy2a7" + "sampleTx": "BBD9DEE03A8D7538D8E7398217467F4A2B5690D15773E8A6442E6AEEEFA21E64", + "sampleAccount": "persistence10ys69560pqr6zmqam80g8s0smtjw6p3ugzmy3u" }, "info": { - "url": "https://kava.io", - "source": "https://github.com/kava-labs/kava", - "rpc": "https://data.kava.io", - "documentation": "https://rpc.kava.io" + "url": "https://persistence.one/", + "documentation": "https://docs.persistence.one/" } }, { - "id": "filecoin", - "name": "Filecoin", - "coinId": 461, - "symbol": "FIL", - "decimals": 18, - "blockchain": "Filecoin", + "id": "akash", + "name": "Akash", + "displayName": "Akash", + "coinId": 17000118, + "symbol": "AKT", + "decimals": 6, + "chainId": "akashnet-2", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/461'/0'/0/0" + "path": "m/44'/118'/0'/0/0" } ], "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", + "publicKeyType": "secp256k1", + "hrp": "akash", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://filfox.info/en", - "txPath": "/message/", - "accountPath": "/address/", - "sampleTx": "bafy2bzacedsgjcd6xfhrrymmfrqubb44otlyhvgqkgsh533d5j5hwniiqespm", - "sampleAccount": "f1abjxfbp274xpdqcpuaykwkfb43omjotacm2p3za" + "url": "https://www.mintscan.io/akash", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "C0083856344425908D5333D4325E3E0DE9D697BA568C6D99C34303819F615D25", + "sampleAccount": "akash1f4nskxfw8ufhwnajh7xwt0wmdtxm02vwta6krg" }, "info": { - "url": "https://filecoin.io/", - "source": "https://github.com/filecoin-project/lotus", - "rpc": "", - "documentation": "https://docs.lotu.sh" + "url": "https://akash.network/", + "documentation": "https://docs.akash.network/" } }, { - "id": "bluzelle", - "name": "Bluzelle", - "coinId": 483, - "symbol": "BLZ", - "decimals": 6, - "blockchain": "Cosmos", + "id": "zcash", + "name": "Zcash", + "coinId": 133, + "symbol": "ZEC", + "decimals": 8, + "blockchain": "Zcash", "derivation": [ { - "path": "m/44'/483'/0'/0/0" + "path": "m/44'/133'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" } ], "curve": "secp256k1", "publicKeyType": "secp256k1", - "hrp": "bluzelle", - "addressHasher": "sha256ripemd", + "staticPrefix": 28, + "p2pkhPrefix": 184, + "p2shPrefix": 189, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", "explorer": { - "url": "https://bigdipper.net.bluzelle.com", - "txPath": "/transactions/", - "accountPath": "/account/", - "sampleTx": "AC026E0EC6E33A77D5EA6B9CEF9810699BC2AD8C5582E007E7857457C6D3B819", - "sampleAccount": "bluzelle1q9cryfal7u3jvnq6er5ufety20xtzw6ycx2te9" + "url": "https://blockchair.com/zcash", + "txPath": "/transaction/", + "accountPath": "/address/", + "sampleTx": "f2438a93039faf08d39bd3df1f7b5f19a2c29ffe8753127e2956ab4461adab35", + "sampleAccount": "t1Yfrf1dssDLmaMBsq2LFKWPbS5vH3nGpa2" }, "info": { - "url": "https://bluzelle.com", - "source": "https://github.com/bluzelle", - "rpc": "https://bluzelle.github.io/api/", - "documentation": "https://docs.bluzelle.com/developers/" + "url": "https://z.cash", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" } }, { - "id": "band", - "name": "BandChain", - "symbol": "BAND", - "coinId": 494, - "decimals": 6, - "blockchain": "Cosmos", + "id": "firo", + "name": "Firo", + "coinId": 136, + "symbol": "FIRO", + "decimals": 8, + "blockchain": "Bitcoin", "derivation": [ { - "path": "m/44'/494'/0'/0/0" + "path": "m/44'/136'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" } ], "curve": "secp256k1", "publicKeyType": "secp256k1", - "hrp": "band", - "addressHasher": "sha256ripemd", + "p2pkhPrefix": 82, + "p2shPrefix": 7, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", "explorer": { - "url": "https://scan-wenchang-testnet2.bandchain.org/", + "url": "https://explorer.firo.org", "txPath": "/tx/", - "accountPath": "/account/", - "sampleTx": "473264551D3063A9EC64EC251C61BE92DDDFCF6CC46D026D1E574D83D5447173", - "sampleAccount": "band12nmsm9khdsv0tywge43q3zwj8kkj3hvup9rltp" + "accountPath": "/address/", + "sampleTx": "09a60d58b3d17519a42a8eca60750c33b710ca8f3ca71994192e05c248a2a111", + "sampleAccount": "a8ULhhDgfdSiXJhSZVdhb8EuDc6R3ogsaM" }, "info": { - "url": "https://bandprotocol.com/", - "source": "https://github.com/bandprotocol/bandchain", - "rpc": "https://api-wt2-lb.bandchain.org", - "documentation": "https://docs.bandchain.org/" + "url": "https://firo.org/", + "source": "https://github.com/firoorg/firo", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" } }, { - "id": "theta", - "name": "Theta", - "coinId": 500, - "symbol": "THETA", - "decimals": 18, - "blockchain": "Theta", + "id": "komodo", + "name": "Komodo", + "coinId": 141, + "symbol": "KMD", + "decimals": 8, + "blockchain": "Zcash", "derivation": [ { - "path": "m/44'/500'/0'/0/0" + "path": "m/44'/141'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" } ], "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 60, + "p2shPrefix": 85, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", "explorer": { - "url": "https://explorer.thetatoken.org", - "txPath": "/txs/", - "accountPath": "/account/" + "url": "https://kmdexplorer.io/", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "f53bd1a5c0f5dc4b60ba9a1882742ea96faa996e1b870795812a29604dd7829e", + "sampleAccount": "RWvfkt8UjbPWXgeZEcgYmKw2vA1bbAx5t2" }, "info": { - "url": "https://www.thetatoken.org", - "source": "https://github.com/thetatoken/theta-protocol-ledger", + "url": "https://komodoplatform.com", + "source": "https://github.com/KomodoPlatform/komodo", "rpc": "", - "documentation": "https://github.com/thetatoken/theta-mainnet-integration-guide/blob/master/docs/api.md#api-reference" + "documentation": "https://developers.komodoplatform.com" } }, { - "id": "solana", - "name": "Solana", - "coinId": 501, - "symbol": "SOL", - "decimals": 9, - "blockchain": "Solana", + "id": "ripple", + "name": "XRP", + "coinId": 144, + "symbol": "XRP", + "decimals": 6, + "blockchain": "Ripple", "derivation": [ { - "path": "m/44'/501'/0'" - }, + "path": "m/44'/144'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "explorer": { + "url": "https://bithomp.com", + "txPath": "/explorer/", + "accountPath": "/explorer/", + "sampleTx": "E26AB8F3372D2FC02DEC1FD5674ADAB762D684BFFDBBDF5D674E9D7CF4A47054", + "sampleAccount": "rfkH7EuS1XcSkB9pocy1R6T8F4CsNYixYU" + }, + "info": { + "url": "https://ripple.com/xrp", + "source": "https://github.com/ripple/rippled", + "rpc": "https://s2.ripple.com:51234", + "documentation": "https://xrpl.org/rippled-api.html" + } + }, + { + "id": "bitcoincash", + "name": "Bitcoin Cash", + "coinId": 145, + "symbol": "BCH", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ { - "name": "solana", - "path": "m/44'/501'/0'/0'" + "path": "m/44'/145'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" } ], - "curve": "ed25519", - "publicKeyType": "ed25519", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 0, + "p2shPrefix": 5, + "hrp": "bitcoincash", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", "explorer": { - "url": "https://solscan.io", - "txPath": "/tx/", - "accountPath": "/account/", - "sampleTx": "5LmxrEKGchhMuYfw6Qut6CbsvE9pVfb8YvwZKvWssSesDVjHioBCmWKSJQh1WhvcM6CpemhpHNmEMA2a36rzwTa8", - "sampleAccount": "Bxp8yhH9zNwxyE4UqxP7a7hgJ5xTZfxNNft7YJJ2VRjT" + "url": "https://blockchair.com", + "txPath": "/bitcoin-cash/transaction/", + "accountPath": "/bitcoin-cash/address/" }, "info": { - "url": "https://solana.com", - "source": "https://github.com/solana-labs/solana", - "rpc": "https://api.mainnet-beta.solana.com", - "documentation": "https://docs.solana.com" + "url": "https://bitcoincash.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" } }, { - "id": "elrond", - "name": "Elrond", - "coinId": 508, - "symbol": "eGLD", - "decimals": 18, - "blockchain": "ElrondNetwork", + "id": "stellar", + "name": "Stellar", + "coinId": 148, + "symbol": "XLM", + "decimals": 7, + "blockchain": "Stellar", "derivation": [ { - "path": "m/44'/508'/0'/0'/0'" + "path": "m/44'/148'/0'" } ], "curve": "ed25519", "publicKeyType": "ed25519", - "hrp": "erd", "explorer": { - "url": "https://explorer.elrond.com", - "txPath": "/transactions/", - "accountPath": "/address/" + "url": "https://blockchair.com/stellar", + "txPath": "/transaction/", + "accountPath": "/account/", + "sampleTx": "8a7ff7261e8b3f31af7f6ed257c2e9fe7c47afcd9b1ce1be1bfc1bc5f6a3ad9e", + "sampleAccount": "GCILJZQ3CKBKBUJWW4TAM6Q37LJA5MQX6GMSFSQN75BPLWIZ33OPRG52" }, "info": { - "url": "https://elrond.com/", - "source": "https://github.com/ElrondNetwork/elrond-go", - "rpc": "https://api.elrond.com", - "documentation": "https://docs.elrond.com" + "url": "https://stellar.org", + "source": "https://github.com/stellar/go", + "rpc": "https://horizon.stellar.org", + "documentation": "https://www.stellar.org/developers/horizon/reference" } }, { - "id": "binance", - "name": "Binance", - "displayName": "BNB Beacon Chain", - "coinId": 714, - "symbol": "BNB", + "id": "bitcoingold", + "name": "Bitcoin Gold", + "coinId": 156, + "symbol": "BTG", "decimals": 8, - "blockchain": "Binance", + "blockchain": "Bitcoin", "derivation": [ { - "path": "m/44'/714'/0'/0/0" + "path": "m/84'/156'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" } ], "curve": "secp256k1", "publicKeyType": "secp256k1", - "hrp": "bnb", - "chainId": "Binance-Chain-Tigris", + "p2pkhPrefix": 38, + "p2shPrefix": 23, + "hrp": "btg", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", "explorer": { - "url": "https://explorer.binance.org", + "url": "https://explorer.bitcoingold.org/insight", "txPath": "/tx/", "accountPath": "/address/", - "sampleTx": "A93625C9F9ABEA1A8E31585B30BBB16C34FAE0D172EB5B6B2F834AF077BF06BB", - "sampleAccount": "bnb1u7jm0cll5h3224y0tapwn6gf6pr49ytewx4gsz" + "sampleTx": "2f807d7734de35d2236a1b3d8704eb12954f5f82ea66987949b10e94d9999b23", + "sampleAccount": "GJjz2Du9BoJQ3CPcoyVTHUJZSj62i1693U" }, "info": { - "url": "https://binance.org", - "source": "https://github.com/binance-chain/node-binary", - "rpc": "https://dex.binance.org", - "documentation": "https://docs.binance.org/api-reference/dex-api/paths.html" + "url": "https://bitcoingold.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" } }, { - "id": "vechain", - "name": "VeChain", - "coinId": 818, - "symbol": "VET", + "id": "nano", + "name": "Nano", + "coinId": 165, + "symbol": "XNO", + "decimals": 30, + "blockchain": "Nano", + "derivation": [ + { + "path": "m/44'/165'/0'" + } + ], + "curve": "ed25519Blake2bNano", + "publicKeyType": "ed25519Blake2b", + "url": "https://nano.org", + "explorer": { + "url": "https://www.nanolooker.com", + "txPath": "/block/", + "accountPath": "/account/", + "sampleTx": "C264DB7BF40738F0CEFF19B606746CB925B713E4B8699A055699E0DC8ABBC70F", + "sampleAccount": "nano_1wpj616kwhe1y38y1mspd8aub8i334cwybqco511iyuxm55zx8d67ptf1tsf" + }, + "info": { + "url": "https://nano.org", + "source": "https://github.com/nanocurrency/nano-node", + "rpc": "", + "documentation": "https://docs.nano.org/commands/rpc-protocol/" + } + }, + { + "id": "ravencoin", + "name": "Ravencoin", + "coinId": 175, + "symbol": "RVN", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "path": "m/44'/175'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 60, + "p2shPrefix": 122, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://blockbook.ravencoin.org", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://ravencoin.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "poa", + "name": "POA Network", + "coinId": 178, + "symbol": "POA", "decimals": 18, - "blockchain": "Vechain", + "blockchain": "Ethereum", "derivation": [ { - "path": "m/44'/818'/0'/0/0" + "path": "m/44'/178'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "74", + "chainId": "99", + "addressHasher": "keccak256", "explorer": { - "url": "https://explore.vechain.org", - "txPath": "/transactions/", - "accountPath": "/accounts/" + "url": "https://blockscout.com", + "txPath": "/poa/core/tx/", + "accountPath": "/poa/core/address/" }, "info": { - "url": "https://vechain.org", - "source": "https://github.com/vechain/thor", + "url": "https://poa.network", + "source": "https://github.com/poanetwork/parity-ethereum", + "rpc": "https://core.poa.network", + "documentation": "https://eth.wiki/json-rpc/API" + } + }, + { + "id": "eos", + "name": "EOS", + "coinId": 194, + "symbol": "EOS", + "decimals": 4, + "blockchain": "EOS", + "derivation": [ + { + "path": "m/44'/194'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "explorer": { + "url": "https://bloks.io", + "txPath": "/transaction/", + "accountPath": "/account/" + }, + "info": { + "url": "http://eos.io", + "source": "https://github.com/eosio/eos", "rpc": "", - "documentation": "https://doc.vechainworld.io/docs" + "documentation": "https://developers.eos.io/eosio-nodeos/reference" } }, { - "id": "callisto", - "name": "Callisto", - "coinId": 820, - "symbol": "CLO", + "id": "wax", + "name": "WAX", + "coinId": 14001, + "symbol": "WAXP", + "decimals": 4, + "blockchain": "EOS", + "derivation": [ + { + "path": "m/44'/194'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "explorer": { + "url": "https://wax.bloks.io", + "txPath": "/transaction/", + "accountPath": "/account/" + }, + "info": { + "url": "http://wax.io", + "source": "https://github.com/worldwide-asset-exchange/wax-blockchain", + "rpc": "https://wax.blacklusion.io", + "documentation": "https://https://developer.wax.io" + } + }, + { + "id": "tron", + "name": "Tron", + "coinId": 195, + "symbol": "TRX", + "decimals": 6, + "blockchain": "Tron", + "derivation": [ + { + "path": "m/44'/195'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://tronscan.org", + "txPath": "/#/transaction/", + "accountPath": "/#/address/" + }, + "info": { + "url": "https://tron.network", + "source": "https://github.com/tronprotocol/java-tron", + "rpc": "https://api.trongrid.io", + "documentation": "https://developers.tron.network/docs/tron-wallet-rpc-api" + } + }, + { + "id": "fio", + "name": "FIO", + "coinId": 235, + "symbol": "FIO", + "decimals": 9, + "blockchain": "FIO", + "derivation": [ + { + "path": "m/44'/235'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "url": "https://fioprotocol.io/", + "explorer": { + "url": "https://explorer.fioprotocol.io", + "txPath": "/transaction/", + "accountPath": "/account/", + "sampleTx": "930d1d3cf8988b39b5f64b64e9d61314a3e05a155d9e3505bdf863aab1adddf3", + "sampleAccount": "f5axfpgffiqz" + }, + "info": { + "url": "https://fioprotocol.io", + "source": "https://github.com/fioprotocol/fio", + "rpc": "https://mainnet.fioprotocol.io", + "documentation": "https://developers.fioprotocol.io" + } + }, + { + "id": "nimiq", + "name": "Nimiq", + "coinId": 242, + "symbol": "NIM", + "decimals": 5, + "blockchain": "Nimiq", + "derivation": [ + { + "path": "m/44'/242'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://nimiq.watch", + "txPath": "/#", + "accountPath": "/#" + }, + "info": { + "url": "https://nimiq.com", + "source": "https://github.com/nimiq/core-rs", + "rpc": "", + "documentation": "https://github.com/nimiq/core-js/wiki/JSON-RPC-API" + } + }, + { + "id": "algorand", + "name": "Algorand", + "coinId": 283, + "symbol": "ALGO", + "decimals": 6, + "blockchain": "Algorand", + "derivation": [ + { + "path": "m/44'/283'/0'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://app.dappflow.org/explorer", + "txPath": "/transaction/", + "accountPath": "/account/", + "sampleTx": "CR7POXFTYDLC7TV3IXHA7AZKWABUJC52BACLHJQNXAKZJGRPQY3A", + "sampleAccount": "J4AEINCSSLDA7LNBNWM4ZXFCTLTOZT5LG3F5BLMFPJYGFWVCMU37EZI2AM" + }, + "info": { + "url": "https://www.algorand.com/", + "source": "https://github.com/algorand/go-algorand", + "rpc": "https://indexer.algorand.network", + "documentation": "https://developer.algorand.org/docs/algod-rest-paths" + } + }, + { + "id": "iotex", + "name": "IoTeX", + "coinId": 304, + "symbol": "IOTX", + "decimals": 18, + "blockchain": "IoTeX", + "derivation": [ + { + "path": "m/44'/304'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "hrp": "io", + "explorer": { + "url": "https://iotexscan.io", + "txPath": "/action/", + "accountPath": "/address/" + }, + "info": { + "url": "https://iotex.io", + "source": "https://github.com/iotexproject/iotex-core", + "rpc": "", + "documentation": "https://docs.iotex.io/#api" + } + }, + { + "id": "iotexevm", + "name": "IoTeX EVM", + "displayName": "IoTeX EVM", + "coinId": 10004689, + "symbol": "IOTX", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/304'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "4689", + "addressHasher": "keccak256", + "explorer": { + "url": "https://iotexscan.io", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://iotex.io/", + "documentation": "https://iotex.io/developers" + } + }, + { + "id": "nervos", + "name": "Nervos", + "coinId": 309, + "symbol": "CKB", + "decimals": 8, + "blockchain": "Nervos", + "derivation": [ + { + "path": "m/44'/309'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "ckb", + "explorer": { + "url": "https://explorer.nervos.org", + "txPath": "/transaction/", + "accountPath": "/address/" + }, + "info": { + "url": "https://nervos.org", + "source": "https://github.com/nervosnetwork/ckb", + "rpc": "https://mainnet.ckb.dev/rpc", + "documentation": "https://github.com/nervosnetwork/rfcs" + } + }, + { + "id": "zilliqa", + "name": "Zilliqa", + "coinId": 313, + "symbol": "ZIL", + "decimals": 12, + "blockchain": "Zilliqa", + "derivation": [ + { + "path": "m/44'/313'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "zil", + "explorer": { + "url": "https://viewblock.io", + "txPath": "/zilliqa/tx/", + "accountPath": "/zilliqa/address/" + }, + "info": { + "url": "https://zilliqa.com", + "source": "https://github.com/Zilliqa/Zilliqa", + "rpc": "https://api.zilliqa.com", + "documentation": "https://apidocs.zilliqa.com" + } + }, + { + "id": "terra", + "name": "Terra", + "displayName": "Terra Classic", + "coinId": 330, + "symbol": "LUNC", + "decimals": 6, + "blockchain": "Cosmos", + "chainId": "columbus-5", + "derivation": [ + { + "path": "m/44'/330'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "terra", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://finder.terra.money/classic", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://terra.money", + "source": "https://github.com/terra-project/core", + "rpc": "https://columbus-fcd.terra.dev", + "documentation": "https://docs.terra.money" + } + }, + { + "id": "terrav2", + "name": "TerraV2", + "displayName": "Terra", + "coinId": 10000330, + "symbol": "LUNA", + "decimals": 6, + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/330'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "terra", + "chainId": "phoenix-1", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://finder.terra.money/mainnet", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://terra.money", + "source": "https://github.com/terra-project/core", + "rpc": "https://phoenix-lcd.terra.dev", + "documentation": "https://docs.terra.money" + } + }, + { + "id": "polkadot", + "name": "Polkadot", + "coinId": 354, + "symbol": "DOT", + "decimals": 10, + "blockchain": "Polkadot", + "derivation": [ + { + "path": "m/44'/354'/0'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "addressHasher": "keccak256", + "ss58Prefix": 0, + "explorer": { + "url": "https://polkadot.subscan.io", + "txPath": "/extrinsic/", + "accountPath": "/account/", + "sampleTx": "0xb96f97d8ee508f420e606e1a6dcc74b88844713ddec2bd7cf4e3aa6b1d6beef4", + "sampleAccount": "13hJFqnkqQbmgnGQteGntjMjTdmTBRE8Z93JqxsrpgT7Yjd2" + }, + "info": { + "url": "https://polkadot.network/", + "source": "https://github.com/paritytech/polkadot", + "rpc": "", + "documentation": "https://polkadot.js.org/api/substrate/rpc.html" + } + }, + { + "id": "everscale", + "name": "Everscale", + "coinId": 396, + "symbol": "EVER", + "decimals": 9, + "blockchain": "Everscale", + "derivation": [ + { + "path": "m/44'/396'/0'/0/0" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://everscan.io", + "txPath": "/transactions/", + "accountPath": "/accounts/", + "sampleTx": "781238b2b0d15cd4cd2e2a0a142753750cd5e1b2c8b506fcede75a90e02f1268", + "sampleAccount": "0:d2bf59964a05dee84a0dd1ddc0ad83ba44d49719cf843d689dc8b726d0fb59d8" + }, + "info": { + "url": "https://everscale.network/", + "source": "https://github.com/tonlabs/evernode-ds", + "rpc": "https://evercloud.dev", + "documentation": "https://docs.everos.dev/evernode-platform/products/evercloud/get-started" + } + }, + { + "id": "near", + "name": "NEAR", + "coinId": 397, + "symbol": "NEAR", + "decimals": 24, + "blockchain": "NEAR", + "derivation": [ + { + "path": "m/44'/397'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://nearblocks.io", + "txPath": "/txns/", + "accountPath": "/address/", + "sampleTx": "FPQAMaVnvFHNwNBJWnTttXfdJhp5FvMGGDJEesB8gvbL", + "sampleAccount": "test-trust.vlad.near" + }, + "info": { + "url": "https://nearprotocol.com", + "source": "https://github.com/nearprotocol/nearcore", + "rpc": "https://rpc.nearprotocol.com", + "documentation": "https://docs.nearprotocol.com" + } + }, + { + "id": "aion", + "name": "Aion", + "coinId": 425, + "symbol": "AION", + "decimals": 18, + "blockchain": "Aion", + "derivation": [ + { + "path": "m/44'/425'/0'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://mainnet.aion.network", + "txPath": "/#/transaction/", + "accountPath": "/#/account/" + }, + "info": { + "url": "https://aion.network", + "source": "https://github.com/aionnetwork/aion", + "rpc": "", + "documentation": "https://github.com/aionnetwork/aion/wiki/JSON-RPC-API-Docs" + } + }, + { + "id": "kusama", + "name": "Kusama", + "coinId": 434, + "symbol": "KSM", + "decimals": 12, + "blockchain": "Kusama", + "derivation": [ + { + "path": "m/44'/434'/0'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "addressHasher": "keccak256", + "ss58Prefix": 2, + "explorer": { + "url": "https://kusama.subscan.io", + "txPath": "/extrinsic/", + "accountPath": "/account/", + "sampleTx": "0xcbe0c2e2851c1245bedaae4d52f06eaa6b4784b786bea2f0bff11af7715973dd", + "sampleAccount": "DbCNECPna3k6MXFWWNZa5jGsuWycqEE6zcUxZYkxhVofrFk" + }, + "info": { + "url": "https://kusama.network", + "source": "https://github.com/paritytech/polkadot", + "rpc": "wss://kusama-rpc.polkadot.io/", + "documentation": "https://polkadot.js.org/api/substrate/rpc.html" + } + }, + { + "id": "acala", + "name": "Acala", + "coinId": 787, + "symbol": "ACA", + "decimals": 12, + "blockchain": "Polkadot", + "derivation": [ + { + "path": "m/44'/787'/0'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "addressHasher": "keccak256", + "ss58Prefix": 10, + "explorer": { + "url": "https://acala.subscan.io", + "txPath": "/extrinsic/", + "accountPath": "/account/", + "sampleTx": "0xf3d58aafb1208bc09d10ba74bbf1c7811dc55a9149c1505256b6fb5603f5047f", + "sampleAccount": "26JqMKx4HJJcmb1kXo24HYYobiK2jURGCq6zuEzFBK3hQ9Ti" + }, + "info": { + "url": "https://acala.network", + "source": "https://github.com/AcalaNetwork/Acala", + "rpc": "wss://acala-rpc.dwellir.com", + "documentation": "https://polkadot.js.org/api/substrate/rpc.html" + } + }, + { + "id": "acalaevm", + "name": "Acala EVM", + "coinId": 10000787, + "slip44": 60, + "symbol": "ACA", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "787", + "addressHasher": "keccak256", + "explorer": { + "url": "https://blockscout.acala.network", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x4b0b151dd71ed8ef3174da18565790bf14f0a903a13e4f3266c7848bc8841593", + "sampleAccount": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1" + }, + "info": { + "url": "https://acala.network", + "source": "https://github.com/AcalaNetwork/Acala", + "rpc": "https://eth-rpc-acala.aca-api.network", + "documentation": "https://polkadot.js.org/api/substrate/rpc.html" + } + }, + { + "id": "aeternity", + "name": "Aeternity", + "coinId": 457, + "symbol": "AE", + "decimals": 18, + "blockchain": "Aeternity", + "derivation": [ + { + "path": "m/44'/457'/0'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://explorer.aepps.com", + "txPath": "/transactions/", + "accountPath": "/account/transactions/" + }, + "info": { + "url": "https://aeternity.com", + "source": "https://github.com/aeternity/aeternity", + "rpc": "https://sdk-mainnet.aepps.com", + "documentation": "http://aeternity.com/api-docs/" + } + }, + { + "id": "kava", + "name": "Kava", + "coinId": 459, + "symbol": "KAVA", + "decimals": 6, + "blockchain": "Cosmos", + "chainId": "kava_2222-10", + "derivation": [ + { + "path": "m/44'/459'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "kava", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://mintscan.io/kava", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "2988DF83FCBFAA38179D583A96734CBD071541D6768221BB23111BC8136D5E6A", + "sampleAccount": "kava1xd39avn2f008jmvua0eupg39zsp2xn3wf802vn" + }, + "info": { + "url": "https://kava.io", + "source": "https://github.com/kava-labs/kava", + "rpc": "https://data.kava.io", + "documentation": "https://rpc.kava.io" + } + }, + { + "id": "filecoin", + "name": "Filecoin", + "coinId": 461, + "symbol": "FIL", + "decimals": 18, + "blockchain": "Filecoin", + "derivation": [ + { + "path": "m/44'/461'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://filfox.info/en", + "txPath": "/message/", + "accountPath": "/address/", + "sampleTx": "bafy2bzacedsgjcd6xfhrrymmfrqubb44otlyhvgqkgsh533d5j5hwniiqespm", + "sampleAccount": "f1abjxfbp274xpdqcpuaykwkfb43omjotacm2p3za" + }, + "info": { + "url": "https://filecoin.io/", + "source": "https://github.com/filecoin-project/lotus", + "rpc": "", + "documentation": "https://docs.lotu.sh" + } + }, + { + "id": "bluzelle", + "name": "Bluzelle", + "coinId": 483, + "symbol": "BLZ", + "decimals": 6, + "blockchain": "Cosmos", + "derivation": [ + { + "path": "m/44'/483'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "bluzelle", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://bigdipper.net.bluzelle.com", + "txPath": "/transactions/", + "accountPath": "/account/", + "sampleTx": "AC026E0EC6E33A77D5EA6B9CEF9810699BC2AD8C5582E007E7857457C6D3B819", + "sampleAccount": "bluzelle1q9cryfal7u3jvnq6er5ufety20xtzw6ycx2te9" + }, + "info": { + "url": "https://bluzelle.com", + "source": "https://github.com/bluzelle", + "rpc": "https://bluzelle.github.io/api/", + "documentation": "https://docs.bluzelle.com/developers/" + } + }, + { + "id": "band", + "name": "BandChain", + "symbol": "BAND", + "coinId": 494, + "decimals": 6, + "blockchain": "Cosmos", + "chainId": "laozi-mainnet", + "derivation": [ + { + "path": "m/44'/494'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "band", + "addressHasher": "sha256ripemd", + "explorer": { + "url": "https://www.mintscan.io/band", + "txPath": "/tx/", + "accountPath": "/account/", + "sampleTx": "74AF38C2183B06EB6274DA4AAC0D2334E6E283643D436852F5E088AEA2CD0B17", + "sampleAccount": "band16gpgu994g2gdrzvwp9047le3pcq9wz6mcgtd4w" + }, + "info": { + "url": "https://bandprotocol.com/", + "source": "https://github.com/bandprotocol/bandchain", + "rpc": "https://api-wt2-lb.bandchain.org", + "documentation": "https://docs.bandchain.org/" + } + }, + { + "id": "theta", + "name": "Theta", + "coinId": 500, + "symbol": "THETA", + "decimals": 18, + "blockchain": "Theta", + "derivation": [ + { + "path": "m/44'/500'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://explorer.thetatoken.org", + "txPath": "/txs/", + "accountPath": "/account/" + }, + "info": { + "url": "https://www.thetatoken.org", + "source": "https://github.com/thetatoken/theta-protocol-ledger", + "rpc": "", + "documentation": "https://github.com/thetatoken/theta-mainnet-integration-guide/blob/master/docs/api.md#api-reference" + } + }, + { + "id": "tfuelevm", + "name": "Theta Fuel", + "coinId": 361, + "symbol": "TFUEL", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/500'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "361", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.thetatoken.org", + "txPath": "/tx/", + "accountPath": "/account/", + "sampleTx": "0xdb1c1c4e06289a4fc71b98ced218242d4f4a54a09987791a6a53a5260c053555", + "sampleAccount": "0xa144e6a98b967e585b214bfa7f6692af81987e5b" + }, + "info": { + "url": "https://www.thetatoken.org", + "source": "https://github.com/thetatoken/theta-protocol-ledger", + "rpc": "https://eth-rpc-api.thetatoken.org/rpc", + "documentation": "https://github.com/thetatoken/theta-mainnet-integration-guide/blob/master/docs/api.md#api-reference" + } + }, + { + "id": "solana", + "name": "Solana", + "coinId": 501, + "symbol": "SOL", + "decimals": 9, + "blockchain": "Solana", + "derivation": [ + { + "path": "m/44'/501'/0'" + }, + { + "name": "solana", + "path": "m/44'/501'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://solscan.io", + "txPath": "/tx/", + "accountPath": "/account/", + "sampleTx": "5LmxrEKGchhMuYfw6Qut6CbsvE9pVfb8YvwZKvWssSesDVjHioBCmWKSJQh1WhvcM6CpemhpHNmEMA2a36rzwTa8", + "sampleAccount": "Bxp8yhH9zNwxyE4UqxP7a7hgJ5xTZfxNNft7YJJ2VRjT" + }, + "info": { + "url": "https://solana.com", + "source": "https://github.com/solana-labs/solana", + "rpc": "https://api.mainnet-beta.solana.com", + "documentation": "https://docs.solana.com" + } + }, + { + "id": "elrond", + "name": "MultiversX", + "coinId": 508, + "symbol": "eGLD", + "decimals": 18, + "blockchain": "MultiversX", + "derivation": [ + { + "path": "m/44'/508'/0'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "hrp": "erd", + "explorer": { + "url": "https://explorer.multiversx.com", + "txPath": "/transactions/", + "accountPath": "/accounts/" + }, + "info": { + "url": "https://multiversx.com/", + "source": "https://github.com/multiversx/mx-chain-go", + "rpc": "https://api.multiversx.com", + "documentation": "https://docs.multiversx.com" + } + }, + { + "id": "binance", + "name": "Binance", + "displayName": "BNB Beacon Chain", + "coinId": 714, + "symbol": "BNB", + "decimals": 8, + "blockchain": "Binance", + "derivation": [ + { + "path": "m/44'/714'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "addressHasher": "sha256ripemd", + "hrp": "bnb", + "chainId": "Binance-Chain-Tigris", + "explorer": { + "url": "https://explorer.binance.org", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "A93625C9F9ABEA1A8E31585B30BBB16C34FAE0D172EB5B6B2F834AF077BF06BB", + "sampleAccount": "bnb1u7jm0cll5h3224y0tapwn6gf6pr49ytewx4gsz" + }, + "info": { + "url": "https://www.bnbchain.org", + "source": "https://github.com/bnb-chain/node-binary", + "rpc": "https://dex.binance.org", + "documentation": "https://docs.bnbchain.org/docs/beaconchain/develop/api-reference/dex-api/paths" + } + }, + { + "id": "tbinance", + "name": "TBinance", + "displayName": "TBNB", + "coinId": 30000714, + "slip44": 714, + "symbol": "BNB", + "decimals": 8, + "blockchain": "Binance", + "derivation": [ + { + "path": "m/44'/714'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "addressHasher": "sha256ripemd", + "hrp": "tbnb", + "explorer": { + "url": "https://testnet-explorer.binance.org", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "92E9DA1B6D603667E2DE83C0AC0C1D9E6D65405AD642DA794421C64A82A078D0", + "sampleAccount": "tbnb1c2cxgv3cklswxlvqr9anm6mlp6536qnd36txgr" + }, + "info": { + "url": "https://www.bnbchain.org", + "source": "https://github.com/bnb-chain/node-binary", + "rpc": "https://testnet-dex.binance.org", + "documentation": "https://docs.bnbchain.org/docs/beaconchain/develop/api-reference/dex-api/paths-testnet" + } + }, + { + "id": "vechain", + "name": "VeChain", + "coinId": 818, + "symbol": "VET", + "decimals": 18, + "blockchain": "Vechain", + "derivation": [ + { + "path": "m/44'/818'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "74", + "explorer": { + "url": "https://explore.vechain.org", + "txPath": "/transactions/", + "accountPath": "/accounts/", + "sampleTx": "0xa424053be0063555aee73a595ca69968c2e4d90d36f280753e503b92b11a655d", + "sampleAccount": "0x8a0a035a33173601bfbec8b6ae7c4a6557a55103" + }, + "info": { + "url": "https://vechain.org", + "source": "https://github.com/vechain/thor", + "rpc": "", + "documentation": "https://doc.vechainworld.io/docs" + } + }, + { + "id": "callisto", + "name": "Callisto", + "coinId": 820, + "symbol": "CLO", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/820'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "820", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.callisto.network", + "txPath": "/tx/", + "accountPath": "/addr/" + }, + "info": { + "url": "https://callisto.network", + "source": "https://github.com/EthereumCommonwealth/go-callisto", + "rpc": "https://clo-geth.0xinfra.com", + "documentation": "https://eth.wiki/json-rpc/API" + } + }, + { + "id": "neo", + "name": "NEO", + "coinId": 888, + "symbol": "NEO", + "decimals": 8, + "blockchain": "NEO", + "derivation": [ + { + "path": "m/44'/888'/0'/0/0" + } + ], + "curve": "nist256p1", + "publicKeyType": "nist256p1", + "explorer": { + "url": "https://neoscan.io", + "txPath": "/transaction/", + "accountPath": "/address/", + "sampleTx": "e0ddf7c81c732df26180aca0c36d5868ad009fdbbe6e7a56ebafc14bba41cd53", + "sampleAccount": "AcxuqWhTureEQGeJgbmtSWNAtssjMLU7pb" + }, + "info": { + "url": "https://neo.org", + "source": "https://github.com/neo-project/neo", + "rpc": "http://seed1.ngd.network:10332", + "documentation": "https://neo.org/eco" + } + }, + { + "id": "viction", + "name": "Viction", + "coinId": 889, + "symbol": "VIC", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/889'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "88", + "addressHasher": "keccak256", + "explorer": { + "url": "https://www.vicscan.xyz", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x35a8d3ab06c94d5b7d27221b7c9a24ba3f1710dd0fcfd75c5d59b3a885fd709b", + "sampleAccount": "0x86cCbD9bfb371c355202086882bC644A7D0b024B" + }, + "info": { + "url": "https://www.viction.xyz/", + "source": "https://github.com/BuildOnViction/tomochain", + "rpc": "https://rpc.tomochain.com", + "documentation": "https://eth.wiki/json-rpc/API" + } + }, + { + "id": "bitcoindiamond", + "name": "Bitcoin Diamond", + "coinId": 999, + "symbol": "BCD", + "decimals": 7, + "blockchain": "BitcoinDiamond", + "derivation": [ + { + "path": "m/84'/999'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 0, + "p2shPrefix": 5, + "hrp": "bcd", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "http://explorer.btcd.io/#", + "txPath": "/tx?tx=", + "accountPath": "/address?address=", + "sampleTx": "ec564fe8993ba77f3f5c8b7f6ebb4cbc08e564a54612d6f4584cd1017cf723d4", + "sampleAccount": "1HNTyntGXNhy4WxNzWfffPqp7LHb8bGJ9R" + }, + "info": { + "url": "https://www.bitcoindiamond.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "thundertoken", + "name": "ThunderCore", + "coinId": 1001, + "symbol": "TT", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/1001'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "108", + "addressHasher": "keccak256", + "explorer": { + "url": "https://scan.thundercore.com", + "txPath": "/transactions/", + "accountPath": "/address/" + }, + "info": { + "url": "https://thundercore.com", + "source": "https://github.com/thundercore/pala", + "rpc": "https://mainnet-rpc.thundercore.com", + "documentation": "https://eth.wiki/json-rpc/API" + } + }, + { + "id": "harmony", + "name": "Harmony", + "coinId": 1023, + "symbol": "ONE", + "decimals": 18, + "blockchain": "Harmony", + "derivation": [ + { + "path": "m/44'/1023'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "hrp": "one", + "explorer": { + "url": "https://explorer.harmony.one", + "txPath": "/#/tx/", + "accountPath": "/#/address/" + }, + "info": { + "url": "https://harmony.one", + "source": "https://github.com/harmony-one/go-sdk", + "rpc": "", + "documentation": "https://docs.harmony.one/home/harmony-networks/harmony-network-overview/mainnet" + } + }, + { + "id": "oasis", + "name": "Oasis", + "coinId": 474, + "symbol": "ROSE", + "decimals": 9, + "blockchain": "OasisNetwork", + "derivation": [ + { + "path": "m/44'/474'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "hrp": "oasis", + "explorer": { + "url": "https://oasisscan.com", + "txPath": "/transactions/", + "accountPath": "/accounts/detail/", + "sampleTx": "73dc977fdd8596d4a57e6feb891b21f5da3652d26815dc94f15f7420c298e29e", + "sampleAccount": "oasis1qrx376dmwuckmruzn9vq64n49clw72lywctvxdf4" + }, + "info": { + "url": "https://oasisprotocol.org/", + "source": "https://github.com/oasisprotocol/oasis-core", + "rpc": "https://rosetta.oasis.dev/api/v1", + "documentation": "https://docs.oasis.dev/oasis-core/" + } + }, + { + "id": "ontology", + "name": "Ontology", + "coinId": 1024, + "symbol": "ONT", + "decimals": 0, + "blockchain": "Ontology", + "derivation": [ + { + "path": "m/44'/1024'/0'/0/0" + } + ], + "curve": "nist256p1", + "publicKeyType": "nist256p1", + "explorer": { + "url": "https://explorer.ont.io", + "txPath": "/transaction/", + "accountPath": "/address/" + }, + "info": { + "url": "https://ont.io", + "source": "https://github.com/ontio/ontology", + "rpc": "http://dappnode1.ont.io:20336", + "documentation": "https://github.com/ontio/ontology/blob/master/docs/specifications/rpc_api.md" + } + }, + { + "id": "tezos", + "name": "Tezos", + "coinId": 1729, + "symbol": "XTZ", + "decimals": 6, + "blockchain": "Tezos", + "derivation": [ + { + "path": "m/44'/1729'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://tzstats.com", + "txPath": "/", + "accountPath": "/", + "sampleTx": "onk3Z6V4StyfiXTPSHwZFvTKVAaws37cHmZacmULPr3VbVHpKrg", + "sampleAccount": "tz1SiPXX4MYGNJNDsRc7n8hkvUqFzg8xqF9m" + }, + "info": { + "url": "https://tezos.com", + "source": "https://gitlab.com/tezos/tezos", + "rpc": "https://rpc.tulip.tools/mainnet", + "documentation": "https://tezos.gitlab.io/tezos/api/rpc.html" + } + }, + { + "id": "cardano", + "name": "Cardano", + "coinId": 1815, + "symbol": "ADA", + "decimals": 6, + "blockchain": "Cardano", + "derivation": [ + { + "path": "m/1852'/1815'/0'/0/0" + } + ], + "curve": "ed25519ExtendedCardano", + "publicKeyType": "ed25519Cardano", + "hrp": "addr", + "explorer": { + "url": "https://cardanoscan.io", + "txPath": "/transaction/", + "accountPath": "/address/", + "sampleTx": "b7a6c5cadab0f64bdc89c77ee4a351463aba5c33f2cef6bbd6542a74a90a3af3", + "sampleAccount": "DdzFFzCqrhstpwKc8WMvPwwBb5oabcTW9zc5ykA37wJR4tYQucvsR9dXb2kEGNXkFJz2PtrpzfRiZkx8R1iNo8NYqdsukVmv7EAybFwC" + }, + "info": { + "url": "https://www.cardano.org", + "source": "https://github.com/input-output-hk/cardano-sl", + "rpc": "", + "documentation": "https://cardanodocs.com/introduction/" + } + }, + { + "id": "kin", + "name": "Kin", + "coinId": 2017, + "symbol": "KIN", + "decimals": 5, + "blockchain": "Stellar", + "derivation": [ + { + "path": "m/44'/2017'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://www.kin.org", + "txPath": "/blockchainInfoPage/?&dataType=public&header=Transaction&id=", + "accountPath": "/blockchainAccount/?&dataType=public&header=accountID&id=" + }, + "info": { + "url": "https://www.kin.org", + "source": "https://github.com/kinecosystem/go", + "rpc": "https://horizon.kinfederation.com", + "documentation": "https://www.stellar.org/developers/horizon/reference" + }, + "deprecated": true + }, + { + "id": "qtum", + "name": "Qtum", + "coinId": 2301, + "symbol": "QTUM", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "path": "m/44'/2301'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 58, + "p2shPrefix": 50, + "hrp": "qc", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://qtum.info", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://qtum.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "nebulas", + "name": "Nebulas", + "coinId": 2718, + "symbol": "NAS", + "decimals": 18, + "blockchain": "Nebulas", + "derivation": [ + { + "path": "m/44'/2718'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://explorer.nebulas.io", + "txPath": "/#/tx/", + "accountPath": "/#/address/" + }, + "info": { + "url": "https://nebulas.io", + "source": "https://github.com/nebulasio/go-nebulas", + "rpc": "https://mainnet.nebulas.io", + "documentation": "https://wiki.nebulas.io/en/latest/dapp-development/rpc/rpc.html" + } + }, + { + "id": "gochain", + "name": "GoChain", + "coinId": 6060, + "symbol": "GO", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/6060'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "60", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.gochain.io", + "txPath": "/tx/", + "accountPath": "/addr/" + }, + "info": { + "url": "https://gochain.io", + "source": "https://github.com/gochain-io/gochain", + "rpc": "https://rpc.gochain.io", + "documentation": "https://eth.wiki/json-rpc/API" + } + }, + { + "id": "nuls", + "name": "NULS", + "coinId": 8964, + "symbol": "NULS", + "decimals": 8, + "blockchain": "NULS", + "derivation": [ + { + "path": "m/44'/8964'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "explorer": { + "url": "https://nulscan.io", + "txPath": "/transaction/info?hash=", + "accountPath": "/address/info?address=", + "sampleTx": "303e0e42c28acc37ba952a1effd43daa1caec79928054f7abefb21c32e6fdc02", + "sampleAccount": "NULSd6HgdSjUZy7jKMZfvQ5QU6Z97oufGTGcF" + }, + "info": { + "url": "https://nuls.io", + "source": "https://github.com/nuls-io/nuls-v2", + "rpc": "https://public1.nuls.io/", + "documentation": "https://docs.nuls.io/" + } + }, + { + "id": "zelcash", + "name": "Zelcash", + "displayName": "Flux", + "coinId": 19167, + "symbol": "FLUX", + "decimals": 8, + "blockchain": "Zcash", + "derivation": [ + { + "path": "m/44'/19167'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "staticPrefix": 28, + "p2pkhPrefix": 184, + "p2shPrefix": 189, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://explorer.runonflux.io", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://runonflux.io", + "source": "https://github.com/trezor/blockbook", + "rpc": "https://blockbook.runonflux.io", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + } + }, + { + "id": "wanchain", + "name": "Wanchain", + "coinId": 5718350, + "symbol": "WAN", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/5718350'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "888", + "addressHasher": "keccak256", + "explorer": { + "url": "https://www.wanscan.org", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x180ea96a3218b82b9b35d796823266d8a425c182507adfe5eeffc96e6a14d856", + "sampleAccount": "0x69B492D57bb777e97aa7044D0575228434e2E8B1" + }, + "info": { + "url": "https://wanchain.org", + "source": "https://github.com/wanchain/go-wanchain", + "rpc": "", + "documentation": "https://eth.wiki/json-rpc/API" + } + }, + { + "id": "waves", + "name": "Waves", + "coinId": 5741564, + "symbol": "WAVES", + "decimals": 8, + "blockchain": "Waves", + "derivation": [ + { + "path": "m/44'/5741564'/0'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "curve25519", + "explorer": { + "url": "https://wavesexplorer.com", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://wavesplatform.com", + "source": "https://github.com/wavesplatform/Waves", + "rpc": "https://nodes.wavesnodes.com", + "documentation": "https://nodes.wavesnodes.com/api-docs/index.html" + } + }, + { + "id": "bsc", + "name": "Smart Chain Legacy", + "coinId": 10000714, + "slip44": 714, + "symbol": "BNB", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/714'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "56", + "addressHasher": "keccak256", + "explorer": { + "url": "https://bscscan.com", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e", + "sampleAccount": "0x35552c16704d214347f29Fa77f77DA6d75d7C752" + }, + "info": { + "url": "https://www.binance.org/en/smartChain", + "source": "https://github.com/binance-chain/bsc", + "rpc": "https://data-seed-prebsc-1-s1.binance.org:8545", + "documentation": "https://eth.wiki/json-rpc/API" + }, + "deprecated": true, + "testFolderName": "Binance" + }, + { + "id": "smartchain", + "name": "Smart Chain", + "displayName": "BNB Smart Chain", + "coinId": 20000714, + "slip44": 714, + "symbol": "BNB", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "56", + "addressHasher": "keccak256", + "explorer": { + "url": "https://bscscan.com", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e", + "sampleAccount": "0x35552c16704d214347f29Fa77f77DA6d75d7C752" + }, + "info": { + "url": "https://www.binance.org/en/smartChain", + "source": "https://github.com/binance-chain/bsc", + "rpc": "https://bsc-dataseed1.binance.org", + "documentation": "https://eth.wiki/json-rpc/API" + }, + "testFolderName": "Binance" + }, + { + "id": "polygon", + "name": "Polygon", + "coinId": 966, + "symbol": "POL", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "137", + "addressHasher": "keccak256", + "explorer": { + "url": "https://polygonscan.com", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xe26ed1470d5bf99a53d687843e7acdf7e4ba6620af93b4d672e714de90476e8e", + "sampleAccount": "0x720E1fa107A1Df39Db4E78A3633121ac36Bec132" + }, + "info": { + "url": "https://polygon.technology", + "source": "https://github.com/maticnetwork", + "rpc": "https://polygon-rpc.com", + "documentation": "https://docs.polygon.technology" + } + }, + { + "id": "rootstock", + "name": "Rootstock", + "coinId": 137, + "symbol": "RBTC", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/137'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "30", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.rsk.co", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xeb8fa0488a655f8dc975153bffd066800bcaae5f21cf372356365b2a1d6d2288", + "sampleAccount": "0x4e5dabc28e4a0f5e5b19fcb56b28c5a1989352c1" + }, + "info": { + "url": "https://rootstock.io", + "source": "https://github.com/rsksmart/rskj", + "rpc": "https://public-node.rsk.co", + "documentation": "https://dev.rootstock.io" + } + }, + { + "id": "thorchain", + "name": "THORChain", + "coinId": 931, + "symbol": "RUNE", + "decimals": 8, + "blockchain": "Thorchain", + "derivation": [ + { + "path": "m/44'/931'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "thor", + "addressHasher": "sha256ripemd", + "chainId": "thorchain-mainnet-v1", + "explorer": { + "url": "https://viewblock.io/thorchain", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "ADF0899E58C177E2391F22D04E9C5E1C35BB0F75B42B363A0761687907FD9476", + "sampleAccount": "thor196yf4pq80hjrmz7nnh0ar0ypqg02r0w4dq4mzu" + }, + "info": { + "url": "https://thorchain.org", + "source": "https://gitlab.com/thorchain/thornode", + "rpc": "https://seed.thorchain.info", + "documentation": "https://docs.thorchain.org" + } + }, + { + "id": "optimism", + "name": "Optimism", + "displayName": "OP Mainnet", + "coinId": 10000070, + "slip44": 60, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "10", + "addressHasher": "keccak256", + "explorer": { + "url": "https://optimistic.etherscan.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x6fd99288be9bf71eb002bb31da10a4fb0fbbb3c45ae73693b212f49c9db7df8f", + "sampleAccount": "0x1f932361e31d206b4f6b2478123a9d0f8c761031" + }, + "info": { + "url": "https://optimism.io/", + "source": "https://github.com/ethereum-optimism/optimism", + "rpc": "https://mainnet.optimism.io", + "documentation": "https://eth.wiki/json-rpc/API" + } + }, + { + "id": "polygonzkevm", + "name": "Polygon zkEVM", + "displayName": "Polygon zkEVM", + "coinId": 10001101, + "slip44": 60, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "1101", + "addressHasher": "keccak256", + "explorer": { + "url": "https://zkevm.polygonscan.com", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://www.polygon.technology/", + "source": "https://github.com/0xpolygonhermez", + "rpc": "https://zkevm-rpc.com", + "documentation": "https://wiki.polygon.technology/docs/zkEVM/introduction/" + } + }, + { + "id": "zksync", + "name": "Zksync", + "displayName": "zkSync Era", + "coinId": 10000324, + "slip44": 60, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "324", + "addressHasher": "keccak256", + "explorer": { + "url": "https://explorer.zksync.io", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://portal.zksync.io/", + "source": "https://github.com/matter-labs/zksync", + "rpc": "https://zksync2-mainnet.zksync.io", + "documentation": "https://era.zksync.io/docs" + } + }, + { + "id": "scroll", + "name": "Scroll", + "displayName": "Scroll", + "coinId": 534352, + "slip44": 60, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "534352", + "addressHasher": "keccak256", + "explorer": { + "url": "https://scrollscan.com", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xa2062a4530b194a438bb9f9e87cdce59f70775a52e8336892364445847c43ca2", + "sampleAccount": "0xf9062b8a30e0d7722960e305049fa50b86ba6253" + }, + "info": { + "url": "https://scroll.io", + "source": "https://github.com/scroll-tech", + "rpc": "https://rpc.scroll.io", + "documentation": "https://guide.scroll.io" + } + }, + { + "id": "arbitrum", + "name": "Arbitrum", + "coinId": 10042221, + "slip44": 60, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "42161", + "addressHasher": "keccak256", + "explorer": { + "url": "https://arbiscan.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xa1e319be22c08155e5904aa211fb87df5f4ade48de79c6578b1cf3dfda9e498c", + "sampleAccount": "0xecf9ffa7f51e1194f89c25ad8c484f6bfd04e1ac" + }, + "info": { + "url": "https://arbitrum.io", + "source": "https://github.com/OffchainLabs/arbitrum", + "rpc": "https://arb1.arbitrum.io/rpc", + "documentation": "https://docs.arbitrum.io/" + } + }, + { + "id": "arbitrumnova", + "name": "Arbitrum Nova", + "coinId": 10042170, + "slip44": 60, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "42170", + "addressHasher": "keccak256", + "explorer": { + "url": "https://nova.arbiscan.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xfdfee13848c2d21813c82c53afc9925f31770564c3117477960a81055702c1be", + "sampleAccount": "0x0d0707963952f2fba59dd06f2b425ace40b492fe" + }, + "info": { + "url": "https://nova.arbitrum.io", + "source": "https://github.com/OffchainLabs/arbitrum", + "rpc": "https://nova.arbitrum.io/rpc", + "documentation": "https://docs.arbitrum.io/" + } + }, + { + "id": "heco", + "name": "ECO Chain", + "displayName": "Huobi ECO Chain", + "coinId": 10000553, + "slip44": 553, + "symbol": "HT", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "128", + "addressHasher": "keccak256", + "explorer": { + "url": "https://hecoinfo.com", + "txPath": "/tx/", + "accountPath": "/address/" + }, + "info": { + "url": "https://www.hecochain.com/en-us", + "source": "https://github.com/HuobiGroup/huobi-eco-chain", + "rpc": "https://http-mainnet-node.huobichain.com", + "documentation": "https://eth.wiki/json-rpc/API" + }, + "testFolderName": "ECO" + }, + { + "id": "avalanchec", + "name": "Avalanche C-Chain", + "coinId": 10009000, + "symbol": "AVAX", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "43114", + "addressHasher": "keccak256", + "explorer": { + "url": "https://snowtrace.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x9243890b844219accefd8798271052f5a056453ec18984a56e81c92921330d54", + "sampleAccount": "0xa664325f36Ec33E66323fe2620AF3f2294b2Ef3A" + }, + "info": { + "url": "https://www.avalabs.org/", + "client": "https://github.com/ava-labs/avalanchego", + "clientPublic": "https://api.avax.network/ext/bc/C/rpc", + "clientDocs": "https://docs.avax.network/" + }, + "testFolderName": "Avalanche" + }, + { + "id": "xdai", + "name": "xDai", + "displayName": "Gnosis Chain", + "coinId": 10000100, + "symbol": "xDAI", "decimals": 18, "blockchain": "Ethereum", "derivation": [ { - "path": "m/44'/820'/0'/0/0" + "path": "m/44'/60'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "820", + "chainId": "100", "addressHasher": "keccak256", "explorer": { - "url": "https://explorer.callisto.network", + "url": "https://blockscout.com/xdai/mainnet", "txPath": "/tx/", - "accountPath": "/addr/" - }, - "info": { - "url": "https://callisto.network", - "source": "https://github.com/EthereumCommonwealth/go-callisto", - "rpc": "https://clo-geth.0xinfra.com", - "documentation": "https://eth.wiki/json-rpc/API" - } - }, - { - "id": "neo", - "name": "NEO", - "coinId": 888, - "symbol": "NEO", - "decimals": 8, - "blockchain": "NEO", - "derivation": [ - { - "path": "m/44'/888'/0'/0/0" - } - ], - "curve": "nist256p1", - "publicKeyType": "nist256p1", - "explorer": { - "url": "https://neoscan.io", - "txPath": "/transaction/", "accountPath": "/address/", - "sampleTx": "e0ddf7c81c732df26180aca0c36d5868ad009fdbbe6e7a56ebafc14bba41cd53", - "sampleAccount": "AcxuqWhTureEQGeJgbmtSWNAtssjMLU7pb" + "sampleTx": "0x936798a1ef607c9e856d7861b15999c770c06f0887c4fc1f6acbf3bef09899c1", + "sampleAccount": "0x12d61a95CF55e18D267C2F1AA67d8e42ae1368f8" }, "info": { - "url": "https://neo.org", - "source": "https://github.com/neo-project/neo", - "rpc": "http://seed1.ngd.network:10332", - "documentation": "https://neo.org/eco" + "url": "https://www.xdaichain.com", + "client": "https://github.com/openethereum/openethereum", + "clientPublic": "https://rpc.gnosischain.com", + "clientDocs": "https://eth.wiki/json-rpc/API" } }, { - "id": "tomochain", - "name": "TomoChain", - "coinId": 889, - "symbol": "TOMO", + "id": "fantom", + "name": "Fantom", + "coinId": 10000250, + "symbol": "FTM", "decimals": 18, "blockchain": "Ethereum", "derivation": [ { - "path": "m/44'/889'/0'/0/0" + "path": "m/44'/60'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "88", + "chainId": "250", "addressHasher": "keccak256", "explorer": { - "url": "https://tomoscan.io", + "url": "https://ftmscan.com", "txPath": "/tx/", - "accountPath": "/address/" + "accountPath": "/address/", + "sampleTx": "0xb0a741d882291951de1fac72e90b9baf886ddb0c9c87658a0c206490dfaa5202", + "sampleAccount": "0x9474feb9917b87da6f0d830ba66ee0035835c0d3" }, "info": { - "url": "https://tomochain.com", - "source": "https://github.com/tomochain/tomochain", - "rpc": "https://rpc.tomochain.com", - "documentation": "https://eth.wiki/json-rpc/API" + "url": "https://fantom.foundation", + "client": "https://github.com/openethereum/openethereum", + "clientPublic": "https://rpc.ftm.tools", + "clientDocs": "https://eth.wiki/json-rpc/API" } }, { - "id": "thundertoken", - "name": "Thunder Token", - "coinId": 1001, - "symbol": "TT", - "decimals": 18, - "blockchain": "Ethereum", + "id": "cryptoorg", + "name": "CryptoOrg", + "displayName": "Crypto.org", + "coinId": 394, + "symbol": "CRO", + "decimals": 8, + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/1001'/0'/0/0" + "path": "m/44'/394'/0'/0/0" } ], "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "chainId": "108", - "addressHasher": "keccak256", + "publicKeyType": "secp256k1", + "hrp": "cro", + "chainId": "crypto-org-chain-mainnet-1", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://scan.thundercore.com", - "txPath": "/transactions/", - "accountPath": "/address/" + "url": "https://crypto.org/explorer", + "txPath": "/tx/", + "accountPath": "/account/", + "sampleTx": "D87D2EB46B21466886EE149C1DEA3AE6C2E019C7D8C24FA1533A95439DDCE1E2", + "sampleAccount": "cro10wrflcdc4pys9vvgqm98yg7yv5ltj7d3xehent" }, "info": { - "url": "https://thundercore.com", - "source": "https://github.com/thundercore/pala", - "rpc": "https://mainnet-rpc.thundercore.com", - "documentation": "https://eth.wiki/json-rpc/API" + "url": "https://crypto.org/", + "client": "https://github.com/crypto-org-chain/chain-main", + "clientPublic": "https://mainnet.crypto.org:1317/", + "clientDocs": "https://crypto.org/docs/resources/chain-integration.html#api-documentation" } }, { - "id": "harmony", - "name": "Harmony", - "coinId": 1023, - "symbol": "ONE", + "id": "celo", + "name": "Celo", + "coinId": 52752, + "symbol": "CELO", "decimals": 18, - "blockchain": "Harmony", + "blockchain": "Ethereum", "derivation": [ { - "path": "m/44'/1023'/0'/0/0" + "path": "m/44'/60'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "hrp": "one", + "chainId": "42220", + "addressHasher": "keccak256", "explorer": { - "url": "https://explorer.harmony.one", - "txPath": "/#/tx/", - "accountPath": "/#/address/" + "url": "https://explorer.celo.org", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xaf41ee58afe633dc7b179c15693cca40fe0372c1d7c70351620105d59d326693", + "sampleAccount": "0xFBFf95e2Fa7e4Ff3aeA34eFB05fB60F9968a6aaD" }, "info": { - "url": "https://harmony.one", - "source": "https://github.com/harmony-one/go-sdk", - "rpc": "", - "documentation": "https://docs.harmony.one/home/harmony-networks/harmony-network-overview/mainnet" + "url": "https://celo.org", + "client": "https://github.com/celo-org/celo-blockchain", + "clientPublic": "https://forno.celo.org", + "clientDocs": "https://eth.wiki/json-rpc/API" } }, { - "id": "oasis", - "name": "Oasis", - "coinId": 474, - "symbol": "ROSE", - "decimals": 9, - "blockchain": "OasisNetwork", + "id": "ronin", + "name": "Ronin", + "coinId": 10002020, + "slip44": 60, + "symbol": "RON", + "decimals": 18, + "blockchain": "Ronin", "derivation": [ { - "path": "m/44'/474'/0'" + "path": "m/44'/60'/0'/0/0" } ], - "curve": "ed25519", - "publicKeyType": "ed25519", - "hrp": "oasis", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "2020", + "addressHasher": "keccak256", "explorer": { - "url": "https://oasisscan.com", - "txPath": "/transactions/", - "accountPath": "/accounts/detail/", - "sampleTx": "0b9bd4983f1c88a1c71bf33562b6ba02b3064e01697d15a0de4bfe1922ec74b8", - "sampleAccount": "oasis1qrx376dmwuckmruzn9vq64n49clw72lywctvxdf4" + "url": "https://explorer.roninchain.com", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xc09835aaf9c1cacea8ce322865583c791d3a4dfbd9a3b72f79539db88d3697ab", + "sampleAccount": "0xdc05ecd5fbdb64058d94f3182d66f44342b9adcb" }, "info": { - "url": "https://oasisprotocol.org/", - "source": "https://github.com/oasisprotocol/oasis-core", - "rpc": "https://rosetta.oasis.dev/api/v1", - "documentation": "https://docs.oasis.dev/oasis-core/" + "url": "https://whitepaper.axieinfinity.com/technology/ronin-ethereum-sidechain", + "client": "https://github.com/axieinfinity/ronin-smart-contracts", + "clientPublic": "https://api.roninchain.com/rpc", + "clientDocs": "https://eth.wiki/json-rpc/API" } }, { - "id": "ontology", - "name": "Ontology", - "coinId": 1024, - "symbol": "ONT", - "decimals": 0, - "blockchain": "Ontology", + "id": "secret", + "name": "Secret", + "displayName": "Secret", + "coinId": 529, + "symbol": "SCRT", + "decimals": 6, + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/1024'/0'/0/0" + "path": "m/44'/529'/0'/0/0" } ], - "curve": "nist256p1", - "publicKeyType": "nist256p1", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "secret", + "chainId": "secret-4", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://explorer.ont.io", - "txPath": "/transaction/", - "accountPath": "/address/" + "url": "https://mintscan.io/secret", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "026B4886B1D9CE836A99755DDE99D4F8A7748D27B1CE9D298A763B1CFFF62C00", + "sampleAccount": "secret167m3s89ddurjpyr82vsluvvj0t8ylzn95trrqy" }, "info": { - "url": "https://ont.io", - "source": "https://github.com/ontio/ontology", - "rpc": "http://dappnode1.ont.io:20336", - "documentation": "https://github.com/ontio/ontology/blob/master/docs/specifications/rpc_api.md" + "url": "https://scrt.network/", + "source": "https://github.com/scrtlabs/SecretNetwork", + "rpc": "https://scrt-rpc.blockpane.com/", + "documentation": "https://docs.scrt.network/" } }, { - "id": "tezos", - "name": "Tezos", - "coinId": 1729, - "symbol": "XTZ", + "id": "osmosis", + "name": "Osmosis", + "displayName": "Osmosis", + "coinId": 10000118, + "symbol": "OSMO", "decimals": 6, - "blockchain": "Tezos", + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/1729'/0'/0'" + "path": "m/44'/118'/0'/0/0" } ], - "curve": "ed25519", - "publicKeyType": "ed25519", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "hrp": "osmo", + "chainId": "osmosis-1", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://tzstats.com", - "txPath": "/", - "accountPath": "/" + "url": "https://mintscan.io/osmosis", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "5A6E50A6F2927E4B8C87BB094D5FBF15F1287429A09111806FC44B3CD86CACA8", + "sampleAccount": "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5" }, "info": { - "url": "https://tezos.com", - "source": "https://gitlab.com/tezos/tezos", - "rpc": "https://rpc.tulip.tools/mainnet", - "documentation": "https://tezos.gitlab.io/tezos/api/rpc.html" + "url": "https://osmosis.zone/", + "client": "https://github.com/osmosis-labs/osmosis", + "clientPublic": "https://rpc-osmosis.keplr.app/", + "clientDocs": "" } }, { - "id": "cardano", - "name": "Cardano", - "coinId": 1815, - "symbol": "ADA", - "decimals": 6, - "blockchain": "Cardano", + "id": "ecash", + "name": "eCash", + "coinId": 899, + "symbol": "XEC", + "decimals": 2, + "blockchain": "Bitcoin", "derivation": [ { - "path": "m/1852'/1815'/0'/0/0" + "path": "m/44'/899'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" } ], - "curve": "ed25519Extended", - "publicKeyType": "ed25519Extended", - "hrp": "addr", - "explorer": { - "url": "https://cardanoscan.io", - "txPath": "/transaction/", + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 0, + "p2shPrefix": 5, + "hrp": "ecash", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://explorer.bitcoinabc.org", + "txPath": "/tx/", "accountPath": "/address/", - "sampleTx": "b7a6c5cadab0f64bdc89c77ee4a351463aba5c33f2cef6bbd6542a74a90a3af3", - "sampleAccount": "addr1s3xuxwfetyfe7q9u3rfn6je9stlvcgmj8rezd87qjjegdtxm3y3f2mgtn87mrny9r77gm09h6ecslh3gmarrvrp9n4yzmdnecfxyu59jz29g8j" + "sampleTx": "6bc767e69cfacffd954c9e5acd178d3140bf00d094b92c6f6052b517500c553b", + "sampleAccount": "ecash:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdg2jj94l5j" }, "info": { - "url": "https://www.cardano.org", - "source": "https://github.com/input-output-hk/cardano-sl", - "rpc": "", - "documentation": "https://cardanodocs.com/introduction/" + "url": "https://e.cash", + "source": "https://github.com/trezor/blockbook", + "rpc": "https://blockbook.fabien.cash:9197", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" } }, { - "id": "kin", - "name": "Kin", - "coinId": 2017, - "symbol": "KIN", - "decimals": 5, - "blockchain": "Stellar", + "id": "iost", + "name": "IOST", + "coinId": 291, + "symbol": "IOST", + "decimals": 2, + "blockchain": "IOST", "derivation": [ { - "path": "m/44'/2017'/0'" + "path": "m/44'/899'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" } ], "curve": "ed25519", "publicKeyType": "ed25519", + "p2pkhPrefix": 0, + "p2shPrefix": 5, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", "explorer": { - "url": "https://www.kin.org", - "txPath": "/blockchainInfoPage/?&dataType=public&header=Transaction&id=", - "accountPath": "/blockchainAccount/?&dataType=public&header=accountID&id=" + "url": "https://explorer.iost.io", + "txPath": "/tx/", + "accountPath": "/account/", + "sampleTx": "7dKQzgTkPBNrZqrQ2Bqhqq132CHGPKANFDtzRsjHRCqx", + "sampleAccount": "4av8w81EyzUgHonsVWqfs15WM4Vrpgox4BYYQWhNQDVu" }, "info": { - "url": "https://www.kin.org", - "source": "https://github.com/kinecosystem/go", - "rpc": "https://horizon.kinfederation.com", - "documentation": "https://www.stellar.org/developers/horizon/reference" - }, - "deprecated": true + "url": "https://iost.io", + "source": "https://github.com/iost-official/go-iost", + "rpc": "", + "documentation": "https://developers.iost.io/docs/en/6-reference/API.html" + } }, { - "id": "qtum", - "name": "Qtum", - "coinId": 2301, - "symbol": "QTUM", - "decimals": 8, - "blockchain": "Bitcoin", + "id": "cronos", + "name": "Cronos Chain", + "coinId": 10000025, + "symbol": "CRO", + "decimals": 18, + "blockchain": "Ethereum", "derivation": [ { - "path": "m/44'/2301'/0'/0/0", - "xpub": "xpub", - "xprv": "xprv" + "path": "m/44'/60'/0'/0/0" } ], "curve": "secp256k1", - "publicKeyType": "secp256k1", - "p2pkhPrefix": 58, - "p2shPrefix": 50, - "hrp": "qc", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", + "publicKeyType": "secp256k1Extended", + "chainId": "25", + "addressHasher": "keccak256", "explorer": { - "url": "https://qtum.info", + "url": "https://cronoscan.com", "txPath": "/tx/", "accountPath": "/address/" }, "info": { - "url": "https://qtum.org", - "source": "https://github.com/trezor/blockbook", - "rpc": "", - "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" - } + "url": "https://cronos.org", + "client": "https://github.com/crypto-org-chain/cronos", + "clientPublic": "https://evm-cronos.crypto.org", + "clientDocs": "https://eth.wiki/json-rpc/API" + }, + "testFolderName": "Cronos" }, { - "id": "nebulas", - "name": "Nebulas", - "coinId": 2718, - "symbol": "NAS", + "id": "kavaevm", + "name": "KavaEvm", + "coinId": 10002222, + "symbol": "KAVA", "decimals": 18, - "blockchain": "Nebulas", + "blockchain": "Ethereum", "derivation": [ { - "path": "m/44'/2718'/0'/0/0" + "path": "m/44'/60'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", + "chainId": "2222", + "addressHasher": "keccak256", "explorer": { - "url": "https://explorer.nebulas.io", - "txPath": "/#/tx/", - "accountPath": "/#/address/" + "url": "https://explorer.kava.io", + "txPath": "/tx/", + "accountPath": "/address/" }, "info": { - "url": "https://nebulas.io", - "source": "https://github.com/nebulasio/go-nebulas", - "rpc": "https://mainnet.nebulas.io", - "documentation": "https://wiki.nebulas.io/en/latest/dapp-development/rpc/rpc.html" + "url": "https://www.kava.io/", + "client": "https://github.com/Kava-Labs/kava", + "documentation": "https://docs.kava.io/docs/ethereum/overview/", + "rpc": "https://evm.kava.io" } }, { - "id": "gochain", - "name": "GoChain", - "coinId": 6060, - "symbol": "GO", + "id": "smartbch", + "name": "Smart Bitcoin Cash", + "coinId": 10000145, + "symbol": "BCH", "decimals": 18, "blockchain": "Ethereum", "derivation": [ { - "path": "m/44'/6060'/0'/0/0" + "path": "m/44'/60'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "60", + "chainId": "10000", "addressHasher": "keccak256", "explorer": { - "url": "https://explorer.gochain.io", + "url": "https://www.smartscan.cash", "txPath": "/tx/", - "accountPath": "/addr/" + "accountPath": "/address/", + "sampleTx": "0x6413466b455b17d03c7a8ce2d7f99fec34bcd338628bdd2d0580a21e3197a4d9", + "sampleAccount": "0xFeEc227410E1DF9f3b4e6e2E284DC83051ae468F" }, "info": { - "url": "https://gochain.io", - "source": "https://github.com/gochain-io/gochain", - "rpc": "https://rpc.gochain.io", - "documentation": "https://eth.wiki/json-rpc/API" - } + "url": "https://smartbch.org/", + "source": "https://github.com/smartbch/smartbch", + "rpc": "https://smartbch.fountainhead.cash/mainnet", + "documentation": "https://github.com/smartbch/docs/blob/main/developers-guide/jsonrpc.md" + }, + "testFolderName": "Bitcoin" }, { - "id": "nuls", - "name": "NULS", - "coinId": 8964, - "symbol": "NULS", - "decimals": 8, - "blockchain": "NULS", + "id": "kcc", + "name": "KuCoin Community Chain", + "coinId": 10000321, + "symbol": "KCS", + "decimals": 18, + "blockchain": "Ethereum", "derivation": [ { - "path": "m/44'/8964'/0'/0/0" + "path": "m/44'/60'/0'/0/0" } ], "curve": "secp256k1", - "publicKeyType": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "321", + "addressHasher": "keccak256", "explorer": { - "url": "https://nulscan.io", - "txPath": "/transaction/info?hash=", - "accountPath": "/address/info?address=" + "url": "https://explorer.kcc.io/en", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x2f0d79cd289a02f3181b68b9583a64c3809fe7387810b274275985c29d02c80d", + "sampleAccount": "0x4446fc4eb47f2f6586f9faab68b3498f86c07521" }, "info": { - "url": "https://nuls.io", - "source": "https://github.com/nuls-io/nuls-v2", - "rpc": "https://public1.nuls.io/", - "documentation": "https://docs.nuls.io/" - } + "url": "https://www.kcc.io/", + "source": "https://github.com/kcc-community/kcc", + "rpc": "https://rpc-mainnet.kcc.network", + "documentation": "https://docs.kcc.io/#/en-us/" + }, + "testFolderName": "KuCoinCommunityChain" }, { - "id": "zelcash", - "name": "Zelcash", - "displayName": "Flux", - "coinId": 19167, - "symbol": "FLUX", - "decimals": 8, - "blockchain": "Zcash", + "id": "boba", + "name": "Boba", + "coinId": 10000288, + "symbol": "BOBAETH", + "decimals": 18, + "blockchain": "Ethereum", "derivation": [ { - "path": "m/44'/19167'/0'/0/0", - "xpub": "xpub", - "xprv": "xprv" + "path": "m/44'/60'/0'/0/0" } ], "curve": "secp256k1", - "publicKeyType": "secp256k1", - "staticPrefix": 28, - "p2pkhPrefix": 184, - "p2shPrefix": 189, - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", + "publicKeyType": "secp256k1Extended", + "chainId": "288", + "addressHasher": "keccak256", "explorer": { - "url": "https://explorer.runonflux.io", + "url": "https://blockexplorer.boba.network", "txPath": "/tx/", - "accountPath": "/address/" + "accountPath": "/address/", + "sampleTx": "0x31533707c3feb3b10f7deeea387ff8893f229253e65ca6b14d2400bf95b5d103", + "sampleAccount": "0x4F96F50eDB37a19216d87693E5dB241e31bD3735" }, "info": { - "url": "https://runonflux.io", - "source": "https://github.com/trezor/blockbook", - "rpc": "https://blockbook.runonflux.io", - "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + "url": "https://boba.network/", + "source": "https://github.com/bobanetwork/boba", + "rpc": "https://mainnet.boba.network", + "documentation": "https://docs.boba.network/" } }, { - "id": "wanchain", - "name": "Wanchain", - "coinId": 5718350, - "symbol": "WAN", + "id": "metis", + "name": "Metis", + "coinId": 10001088, + "symbol": "METIS", "decimals": 18, "blockchain": "Ethereum", "derivation": [ { - "path": "m/44'/5718350'/0'/0/0" + "path": "m/44'/60'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "888", + "chainId": "1088", "addressHasher": "keccak256", "explorer": { - "url": "https://www.wanscan.org", + "url": "https://andromeda-explorer.metis.io", "txPath": "/tx/", "accountPath": "/address/", - "sampleTx": "0x180ea96a3218b82b9b35d796823266d8a425c182507adfe5eeffc96e6a14d856", - "sampleAccount": "0xc6D3DBf8dF90BA3f957A9634677805eee0e43bBe" + "sampleTx": "0x422f2ebbede32d4434ad0cf0ae55d44a84e14d3d5725a760133255b42676d8ce", + "sampleAccount": "0xBe9E8Ec25866B21bA34e97b9393BCabBcB4A5C86" }, "info": { - "url": "https://wanchain.org", - "source": "https://github.com/wanchain/go-wanchain", - "rpc": "", - "documentation": "https://eth.wiki/json-rpc/API" + "url": "https://www.metis.io/", + "source": "https://github.com/MetisProtocol/mvm", + "rpc": "https://andromeda.metis.io/?owner=1088", + "documentation": "https://docs.metis.io/" } }, { - "id": "waves", - "name": "Waves", - "coinId": 5741564, - "symbol": "WAVES", - "decimals": 8, - "blockchain": "Waves", + "id": "aurora", + "name": "Aurora", + "coinId": 1323161554, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", "derivation": [ { - "path": "m/44'/5741564'/0'/0'/0'" + "path": "m/44'/60'/0'/0/0" } ], - "curve": "ed25519", - "publicKeyType": "curve25519", + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "1313161554", + "addressHasher": "keccak256", "explorer": { - "url": "https://wavesexplorer.com", + "url": "https://aurorascan.dev", "txPath": "/tx/", - "accountPath": "/address/" - }, - "info": { - "url": "https://wavesplatform.com", - "source": "https://github.com/wavesplatform/Waves", - "rpc": "https://nodes.wavesnodes.com", - "documentation": "https://nodes.wavesnodes.com/api-docs/index.html" + "accountPath": "/address/", + "sampleTx": "0x99deebdb70f8027037abb3d3d0f3c7523daee857d85e9056d2671593ff2f2f28", + "sampleAccount": "0x8707cdE20dd43E3dB1F74c28fcd509ef38B0bA51" + }, + "info": { + "url": "https://aurora.dev/", + "source": "https://github.com/aurora-is-near/aurora-engine", + "rpc": "https://mainnet.aurora.dev/", + "documentation": "https://doc.aurora.dev/" } }, { - "id": "bsc", - "name": "Smart Chain Legacy", - "coinId": 10000714, - "slip44": 714, - "symbol": "BNB", + "id": "evmos", + "name": "Evmos", + "coinId": 10009001, + "symbol": "EVMOS", "decimals": 18, "blockchain": "Ethereum", "derivation": [ { - "path": "m/44'/714'/0'/0/0" + "path": "m/44'/60'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "56", + "chainId": "9001", "addressHasher": "keccak256", "explorer": { - "url": "https://bscscan.com", + "url": "https://evm.evmos.org", "txPath": "/tx/", "accountPath": "/address/", - "sampleTx": "0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e", - "sampleAccount": "0x35552c16704d214347f29Fa77f77DA6d75d7C752" + "sampleTx": "0x24af42cf4977a96d62e3a82c3cd9b519c3e7c53dd83398b88f0cb435d867b422", + "sampleAccount": "0x30627903124Aa1e71384bc52e1cb96E4AB3252b6" }, "info": { - "url": "https://www.binance.org/en/smartChain", - "source": "https://github.com/binance-chain/bsc", - "rpc": "https://data-seed-prebsc-1-s1.binance.org:8545", - "documentation": "https://eth.wiki/json-rpc/API" - }, - "deprecated": true + "url": "https://evmos.org/", + "source": "https://github.com/tharsis/evmos", + "rpc": "https://eth.bd.evmos.org:8545", + "documentation": "https://docs.evmos.org/" + } }, { - "id": "smartchain", - "name": "Smart Chain", - "displayName": "BNB Smart Chain", - "coinId": 20000714, - "slip44": 714, - "symbol": "BNB", + "id": "nativeevmos", + "name": "NativeEvmos", + "displayName": "Native Evmos", + "coinId": 20009001, + "symbol": "EVMOS", "decimals": 18, - "blockchain": "Ethereum", + "blockchain": "NativeEvmos", + "chainId": "evmos_9001-2", "derivation": [ { "path": "m/44'/60'/0'/0/0" @@ -1835,27 +3985,27 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "56", + "hrp": "evmos", "addressHasher": "keccak256", "explorer": { - "url": "https://bscscan.com", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e", - "sampleAccount": "0x35552c16704d214347f29Fa77f77DA6d75d7C752" + "url": "https://mintscan.io/evmos", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "A16C211C83AD1E684DE46F694FAAC17D8465C864BD7385A81EC062CDE0638811", + "sampleAccount": "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34" }, "info": { - "url": "https://www.binance.org/en/smartChain", - "source": "https://github.com/binance-chain/bsc", - "rpc": "https://bsc-dataseed1.binance.org", - "documentation": "https://eth.wiki/json-rpc/API" + "url": "https://evmos.org/", + "client": "https://github.com/tharsis/evmos", + "clientPublic": "https://rest.bd.evmos.org:1317", + "clientDocs": "" } }, { - "id": "polygon", - "name": "Polygon", - "coinId": 966, - "symbol": "MATIC", + "id": "moonriver", + "name": "Moonriver", + "coinId": 10001285, + "symbol": "MOVR", "decimals": 18, "blockchain": "Ethereum", "derivation": [ @@ -1865,59 +4015,52 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "137", - "addressHasher": "keccak256", + "chainId": "1285", "explorer": { - "url": "https://polygonscan.com", + "url": "https://moonriver.moonscan.io", "txPath": "/tx/", "accountPath": "/address/", - "sampleTx": "0xe26ed1470d5bf99a53d687843e7acdf7e4ba6620af93b4d672e714de90476e8e", - "sampleAccount": "0x720E1fa107A1Df39Db4E78A3633121ac36Bec132" + "sampleTx": "0x2e2daa3943ba65d9bbb910a4f6765aa6a466a0ef8935090547ca9d30e201e032", + "sampleAccount": "0x899831D937937d011305E73EE782cce0455DF15a" }, "info": { - "url": "https://polygon.technology", - "source": "https://github.com/maticnetwork/contracts", - "rpc": "https://rpc-mainnet.matic.network", - "documentation": "https://eth.wiki/json-rpc/API" + "url": "https://moonbeam.network/networks/moonriver", + "rpc": "https://moonriver.public.blastapi.io" } }, { - "id": "thorchain", - "name": "THORChain", - "coinId": 931, - "symbol": "RUNE", - "decimals": 8, - "blockchain": "Thorchain", + "id": "moonbeam", + "name": "Moonbeam", + "coinId": 10001284, + "symbol": "GLMR", + "decimals": 18, + "blockchain": "Ethereum", "derivation": [ { - "path": "m/44'/931'/0'/0/0" + "path": "m/44'/60'/0'/0/0" } ], "curve": "secp256k1", - "publicKeyType": "secp256k1", - "hrp": "thor", - "chainId": "thorchain-mainnet-v1", + "publicKeyType": "secp256k1Extended", + "chainId": "1284", "explorer": { - "url": "https://viewblock.io/thorchain", + "url": "https://moonscan.io", "txPath": "/tx/", "accountPath": "/address/", - "sampleTx": "ADF0899E58C177E2391F22D04E9C5E1C35BB0F75B42B363A0761687907FD9476", - "sampleAccount": "thor196yf4pq80hjrmz7nnh0ar0ypqg02r0w4dq4mzu" + "sampleTx": "0xb22a146c933e6e51affbfa5f712a266b5f5e92ae453cd2f252bcc3c36ff035a6", + "sampleAccount": "0x201bb4f276C765dF7225e5A4153E17edD23a67eC" }, "info": { - "url": "https://thorchain.org", - "source": "https://gitlab.com/thorchain/thornode", - "rpc": "https://seed.thorchain.info", - "documentation": "https://docs.thorchain.org" + "url": "https://moonbeam.network", + "rpc": "https://rpc.api.moonbeam.network", + "documentation": "https://docs.moonbeam.network" } }, { - "id": "optimism", - "name": "Optimism", - "displayName": "Optimistic Ethereum", - "coinId": 10000070, - "slip44": 60, - "symbol": "ETH", + "id": "kaia", + "name": "Kaia", + "coinId": 10008217, + "symbol": "KLAY", "decimals": 18, "blockchain": "Ethereum", "derivation": [ @@ -1927,26 +4070,26 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "10", - "addressHasher": "keccak256", + "chainId": "8217", "explorer": { - "url": "https://optimistic.etherscan.io", + "url": "https://kaiascan.io", "txPath": "/tx/", - "accountPath": "/address/" + "accountPath": "/account/", + "sampleTx": "0x93ea92687845fe7bb6cacd69c76a16a2a3c2bbb85a8a93ff0e032d0098d583d7", + "sampleAccount": "0x2ad9656bf5b82caf10847b431012e28e301e83ba" }, "info": { - "url": "https://optimism.io/", - "source": "https://github.com/ethereum-optimism/optimism", - "rpc": "https://mainnet.optimism.io", - "documentation": "https://eth.wiki/json-rpc/API" + "url": "https://kaia.io", + "rpc": "https://public-en.node.kaia.io", + "documentation": "https://docs.kaia.io" } }, { - "id": "arbitrum", - "name": "Arbitrum", - "coinId": 10042221, - "slip44": 60, - "symbol": "ETH", + "id": "meter", + "name": "Meter", + "coinId": 18000, + "chainId": "82", + "symbol": "MTR", "decimals": 18, "blockchain": "Ethereum", "derivation": [ @@ -1956,27 +4099,26 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "42161", - "addressHasher": "keccak256", "explorer": { - "url": "https://arbiscan.io", + "url": "https://scan.meter.io", "txPath": "/tx/", - "accountPath": "/address/" + "accountPath": "/address/", + "sampleTx": "0x8ea268d5dbb40217c763b800a75fc063cf28b56f40f2bc69dc043f5c4bbdc144", + "sampleAccount": "0xe5a273954d24eddf9ae9ea4cef2347d584cfa3dd" }, "info": { - "url": "https://arbitrum.io", - "source": "https://github.com/OffchainLabs/arbitrum", - "rpc": "https://node.offchainlabs.com:8547", - "documentation": "https://developer.offchainlabs.com" + "url": "https://meter.io/", + "source": "https://github.com/meterio/meter-pov", + "rpc": "https://rpc.meter.io", + "documentation": "https://docs.meter.io/" } }, { - "id": "heco", - "name": "ECO Chain", - "displayName": "Huobi ECO Chain", - "coinId": 10000553, - "slip44": 553, - "symbol": "HT", + "id": "okc", + "name": "OKX Chain", + "coinId": 996, + "chainId": "66", + "symbol": "OKT", "decimals": 18, "blockchain": "Ethereum", "derivation": [ @@ -1986,25 +4128,27 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "128", "addressHasher": "keccak256", "explorer": { - "url": "https://hecoinfo.com", + "url": "https://www.oklink.com/oktc", "txPath": "/tx/", - "accountPath": "/address/" + "accountPath": "/address/", + "sampleTx": "0x46C3A947E8248570FBD28E4FE456CC8F80DFD90716533878FB67857B95FA3D37", + "sampleAccount": "0x074faafd0b20fad2efa115b8ed7e75993e580b85" }, "info": { - "url": "https://www.hecochain.com/en-us", - "source": "https://github.com/HuobiGroup/huobi-eco-chain", - "rpc": "https://http-mainnet-node.huobichain.com", - "documentation": "https://eth.wiki/json-rpc/API" + "url": "https://www.okx.com/okc", + "source": "https://github.com/okex/exchain", + "rpc": "https://exchainrpc.okex.org", + "documentation": "https://okc-docs.readthedocs.io/en/latest" } }, { - "id": "avalanchec", - "name": "Avalanche C-Chain", - "coinId": 10009000, - "symbol": "AVAX", + "id": "cfxevm", + "name": "Conflux eSpace", + "coinId": 1030, + "chainId": "1030", + "symbol": "CFX", "decimals": 18, "blockchain": "Ethereum", "derivation": [ @@ -2014,58 +4158,58 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "43114", "addressHasher": "keccak256", "explorer": { - "url": "https://cchain.explorer.avax.network", + "url": "https://evm.confluxscan.net", "txPath": "/tx/", "accountPath": "/address/", - "sampleTx": "0x9243890b844219accefd8798271052f5a056453ec18984a56e81c92921330d54", - "sampleAccount": "0xa664325f36Ec33E66323fe2620AF3f2294b2Ef3A" + "sampleTx": "0x920efefb3213b2d6a3b84149cb50b61a813d085443a20e1b0eab74120e41ff72", + "sampleAccount": "0x337a087daef75c72871de592fbbba570829a936a" }, "info": { - "url": "https://www.avalabs.org/", - "client": "https://github.com/ava-labs/avalanchego", - "clientPublic": "https://api.avax.network/ext/bc/C/rpc", - "clientDocs": "https://docs.avax.network/" + "url": "https://confluxnetwork.org", + "source": "https://github.com/conflux-chain", + "rpc": "https://evm.confluxrpc.com", + "documentation": "https://doc.confluxnetwork.org/docs/espace" } }, { - "id": "xdai", - "name": "xDai", - "displayName": "Gnosis Chain", - "coinId": 10000100, - "symbol": "xDAI", + "id": "greenfield", + "name": "Greenfield", + "displayName": "BNB Greenfield", + "coinId": 5600, + "symbol": "BNB", "decimals": 18, - "blockchain": "Ethereum", + "chainId": "1017", + "blockchain": "Greenfield", "derivation": [ { "path": "m/44'/60'/0'/0/0" } ], "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "chainId": "100", + "publicKeyType": "secp256k1", "addressHasher": "keccak256", "explorer": { - "url": "https://blockscout.com/xdai/mainnet", + "url": "https://greenfieldscan.com", "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "0x936798a1ef607c9e856d7861b15999c770c06f0887c4fc1f6acbf3bef09899c1", - "sampleAccount": "0x12d61a95CF55e18D267C2F1AA67d8e42ae1368f8" + "accountPath": "/account/", + "sampleTx": "0x150eac42070957115fd538b1f348fadd78d710fb641c248120efcf35d1e7e4f3", + "sampleAccount": "0xcf0f6b88ed72653b00fdebbffc90b98072cb3285" }, "info": { - "url": "https://www.xdaichain.com", - "client": "https://github.com/openethereum/openethereum", - "clientPublic": "https://rpc.gnosischain.com", - "clientDocs": "https://eth.wiki/json-rpc/API" + "url": "https://greenfield.bnbchain.org", + "source": "https://github.com/bnb-chain/greenfield", + "rpc": "https://gnfd-testnet-fullnode-tendermint-us.bnbchain.org", + "documentation": "https://docs.bnbchain.org/greenfield-docs" } }, { - "id": "fantom", - "name": "Fantom", - "coinId": 10000250, - "symbol": "FTM", + "id": "opbnb", + "name": "OpBNB", + "coinId": 204, + "chainId": "204", + "symbol": "BNB", "decimals": 18, "blockchain": "Ethereum", "derivation": [ @@ -2075,187 +4219,189 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "250", "addressHasher": "keccak256", "explorer": { - "url": "https://ftmscan.com", + "url": "https://opbnbscan.com", "txPath": "/tx/", "accountPath": "/address/", - "sampleTx": "0xb0a741d882291951de1fac72e90b9baf886ddb0c9c87658a0c206490dfaa5202", - "sampleAccount": "0x9474feb9917b87da6f0d830ba66ee0035835c0d3" + "sampleTx": "0x788ea8fb4a82dae957f1d3b18af3cd0bbde55a276e66bd17af8c869f24c03a0f", + "sampleAccount": "0x4eaf936c172b5e5511959167e8ab4f7031113ca3" }, "info": { - "url": "https://fantom.foundation", - "client": "https://github.com/openethereum/openethereum", - "clientPublic": "https://rpc.ftm.tools", - "clientDocs": "https://eth.wiki/json-rpc/API" + "url": "https://opbnb.bnbchain.org/en", + "source": "https://github.com/bnb-chain/opbnb", + "rpc": "https://opbnb-mainnet-rpc.bnbchain.org", + "documentation": "https://docs.bnbchain.org/opbnb-docs" } }, { - "id": "cryptoorg", - "name": "CryptoOrg", - "displayName": "Crypto.org", - "coinId": 394, - "symbol": "CRO", + "id": "stratis", + "name": "Stratis", + "coinId": 105105, + "symbol": "STRAX", "decimals": 8, - "blockchain": "Cosmos", + "blockchain": "Bitcoin", "derivation": [ { - "path": "m/44'/394'/0'/0/0" + "name": "segwit", + "path": "m/44'/105105'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" } ], "curve": "secp256k1", "publicKeyType": "secp256k1", - "hrp": "cro", - "chainId": "crypto-org-chain-mainnet-1", - "addressHasher": "sha256ripemd", + "hrp": "strax", + "p2pkhPrefix": 75, + "p2shPrefix": 140, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", "explorer": { - "url": "https://crypto.org/explorer", - "txPath": "/tx/", - "accountPath": "/account/", - "sampleTx": "D87D2EB46B21466886EE149C1DEA3AE6C2E019C7D8C24FA1533A95439DDCE1E2", - "sampleAccount": "cro10wrflcdc4pys9vvgqm98yg7yv5ltj7d3xehent" + "url": "https://explorer.rutanio.com/strax/explorer", + "txPath": "/transaction/", + "accountPath": "/address/", + "sampleTx": "3923df87e83859dec8b87a414cbb1529113788c512a4d0c283e1394c274f0bfb", + "sampleAccount": "XWqnSWzQA5kDAS727UTYtXkdcbKc8mEvyN" }, "info": { - "url": "https://crypto.org/", - "client": "https://github.com/crypto-org-chain/chain-main", - "clientPublic": "https://mainnet.crypto.org:1317/", - "clientDocs": "https://crypto.org/docs/resources/chain-integration.html#api-documentation" + "url": "https://www.stratisplatform.com/", + "source": "https://github.com/stratisproject", + "rpc": "", + "documentation": "https://academy.stratisplatform.com/index.html" } }, { - "id": "celo", - "name": "Celo", - "coinId": 52752, - "symbol": "CELO", - "decimals": 18, - "blockchain": "Ethereum", + "id": "Nebl", + "name": "Nebl", + "coinId": 146, + "symbol": "NEBL", + "decimals": 8, + "blockchain": "Verge", "derivation": [ { - "path": "m/44'/60'/0'/0/0" + "path": "m/44'/146'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" } ], "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "chainId": "42220", - "addressHasher": "keccak256", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 53, + "p2shPrefix": 112, + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", "explorer": { - "url": "https://explorer.celo.org", + "url": "https://explorer.nebl.io", "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "0xaf41ee58afe633dc7b179c15693cca40fe0372c1d7c70351620105d59d326693", - "sampleAccount": "0xFBFf95e2Fa7e4Ff3aeA34eFB05fB60F9968a6aaD" + "accountPath": "/address/", + "sampleTx": "1e56c745ab87d702c98eddc6bc2475b2eb292ed4af92d170b2362c8a089272a4", + "sampleAccount": "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc" }, "info": { - "url": "https://celo.org", - "client": "https://github.com/celo-org/celo-blockchain", - "clientPublic": "https://forno.celo.org", - "clientDocs": "https://eth.wiki/json-rpc/API" + "url": "https://nebl.io", + "source": "https://github.com/NeblioTeam", + "rpc": "", + "documentation": "https://github.com/NeblioTeam" } }, { - "id": "ronin", - "name": "Ronin", - "coinId": 10002020, - "slip44": 60, - "symbol": "RON", - "decimals": 18, - "blockchain": "Ronin", + "id": "hedera", + "name": "Hedera", + "coinId": 3030, + "symbol": "HBAR", + "decimals": 8, + "blockchain": "Hedera", "derivation": [ { - "path": "m/44'/60'/0'/0/0" + "path": "m/44'/3030'/0'/0'/0" } ], - "curve": "secp256k1", - "publicKeyType": "secp256k1Extended", - "chainId": "2020", - "addressHasher": "keccak256", + "curve": "ed25519", + "publicKeyType": "ed25519", "explorer": { - "url": "https://explorer.roninchain.com", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "0xc09835aaf9c1cacea8ce322865583c791d3a4dfbd9a3b72f79539db88d3697ab", - "sampleAccount": "0xdc05ecd5fbdb64058d94f3182d66f44342b9adcb" + "url": "https://hashscan.io/mainnet", + "txPath": "/transaction/", + "accountPath": "/account/", + "sampleTx": "0.0.19790-1666769504-858851231", + "sampleAccount": "0.0.19790" }, "info": { - "url": "https://whitepaper.axieinfinity.com/technology/ronin-ethereum-sidechain", - "client": "https://github.com/axieinfinity/ronin-smart-contracts", - "clientPublic": "https://api.roninchain.com/rpc", - "clientDocs": "https://eth.wiki/json-rpc/API" + "url": "https://hedera.com/", + "source": "https://github.com/hashgraph", + "documentation": "https://docs.hedera.com" } }, { - "id": "osmosis", - "name": "Osmosis", - "displayName": "Osmosis", - "coinId": 10000118, - "symbol": "OSMO", + "id": "agoric", + "name": "Agoric", + "coinId": 564, + "symbol": "BLD", "decimals": 6, "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/118'/0'/0/0" + "path": "m/44'/564'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1", - "hrp": "osmo", - "chainId": "osmosis-1", + "hrp": "agoric", + "chainId": "agoric-3", "addressHasher": "sha256ripemd", "explorer": { - "url": "https://mintscan.io/osmosis", - "txPath": "/txs/", - "accountPath": "/account/", - "sampleTx": "5A6E50A6F2927E4B8C87BB094D5FBF15F1287429A09111806FC44B3CD86CACA8", - "sampleAccount": "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5" + "url": "https://atomscan.com/agoric", + "txPath": "/transactions/", + "accountPath": "/accounts/", + "sampleTx": "576224B1A0F3D56BA23C5350C2A0E3CEA86F40005B828793E5ACBC2F4813152E", + "sampleAccount": "agoric1cqvwa8jr6pmt45jndanc8lqmdsxjkhw0yertc0" }, "info": { - "url": "https://osmosis.zone/", - "client": "https://github.com/osmosis-labs/osmosis", - "clientPublic": "https://rpc-osmosis.keplr.app/", - "clientDocs": "" + "url": "https://agoric.com", + "source": "https://github.com/Agoric/agoric-sdk", + "rpc": "https://agoric-rpc.polkachu.com", + "documentation": "https://docs.agoric.com" } }, { - "id": "ecash", - "name": "eCash", - "coinId": 899, - "symbol": "XEC", - "decimals": 2, - "blockchain": "Bitcoin", + "id": "dydx", + "name": "Dydx", + "displayName": "dYdX", + "coinId": 22000118, + "symbol": "DYDX", + "decimals": 18, + "blockchain": "Cosmos", "derivation": [ { - "path": "m/44'/899'/0'/0/0", - "xpub": "xpub", - "xprv": "xprv" + "path": "m/44'/118'/0'/0/0" } ], "curve": "secp256k1", "publicKeyType": "secp256k1", - "p2pkhPrefix": 0, - "p2shPrefix": 5, - "hrp": "ecash", - "publicKeyHasher": "sha256ripemd", - "base58Hasher": "sha256d", + "hrp": "dydx", + "chainId": "dydx-mainnet-1", + "addressHasher": "sha256ripemd", "explorer": { - "url": "https://explorer.bitcoinabc.org", + "url": "https://www.mintscan.io/dydx", "txPath": "/tx/", - "accountPath": "/address/" + "accountPath": "/address/", + "sampleTx": "F236222E4F7C92FA84711FD6451ED22DD56CBDFA319BFDAFB99A21E4E9B9EC2F", + "sampleAccount": "dydx1adl7usw7z2dnysyn7wvrghu0u0q6gr7jqs4gtt" }, "info": { - "url": "https://e.cash", - "source": "https://github.com/trezor/blockbook", - "rpc": "https://blockbook.fabien.cash:9197", - "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + "url": "https://dydx.exchange", + "source": "https://github.com/dydxprotocol", + "rpc": "https://dydx-dao-api.polkachu.com", + "documentation": "https://docs.dydx.exchange" } }, { - "id": "cronos", - "name": "Cronos Chain", - "coinId": 10000025, - "symbol": "CRO", + "id": "nativeinjective", + "name": "NativeInjective", + "displayName": "Native Injective", + "coinId": 10000060, + "symbol": "INJ", "decimals": 18, - "blockchain": "Ethereum", + "blockchain": "NativeInjective", "derivation": [ { "path": "m/44'/60'/0'/0/0" @@ -2263,27 +4409,30 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "25", + "hrp": "inj", "addressHasher": "keccak256", + "chainId": "injective-1", "explorer": { - "url": "https://cronoscan.com", - "txPath": "/tx/", - "accountPath": "/address/" + "url": "https://www.mintscan.io/injective", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "C5F6A4FF9DF1AE9FF543D2CEBD8E3E9B04290B2445F9D91D7707EDBF4B7EE16B", + "sampleAccount": "inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd" }, "info": { - "url": "https://cronos.org", - "client": "https://github.com/crypto-org-chain/cronos", - "clientPublic": "https://evm-cronos.crypto.org", - "clientDocs": "https://eth.wiki/json-rpc/API" + "url": "https://injective.com", + "documentation": "https://docs.injective.network" } }, { - "id": "smartbch", - "name": "Smart Bitcoin Cash", - "coinId": 10000145, - "symbol": "BCH", + "id": "nativecanto", + "name": "NativeCanto", + "displayName": "NativeCanto", + "coinId": 10007700, + "symbol": "CANTO", "decimals": 18, - "blockchain": "Ethereum", + "blockchain": "Cosmos", + "chainId": "canto_7700-1", "derivation": [ { "path": "m/44'/60'/0'/0/0" @@ -2291,29 +4440,29 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "10000", + "hrp": "canto", "addressHasher": "keccak256", "explorer": { - "url": "https://www.smartscan.cash", - "txPath": "/tx/", - "accountPath": "/address/", - "sampleTx": "0x6413466b455b17d03c7a8ce2d7f99fec34bcd338628bdd2d0580a21e3197a4d9", - "sampleAccount": "0xFeEc227410E1DF9f3b4e6e2E284DC83051ae468F" + "url": "https://mintscan.io/canto", + "txPath": "/txs/", + "accountPath": "/account/", + "sampleTx": "7A7830270097AA9AC8B819EFBB8E0B56579F20ECB7615ECD37E19ABEEFB8DB83", + "sampleAccount": "canto17xpfvakm2amg962yls6f84z3kell8c5lz0zsl4" }, "info": { - "url": "https://smartbch.org/", - "source": "https://github.com/smartbch/smartbch", - "rpc": "https://smartbch.fountainhead.cash/mainnet", - "documentation": "https://github.com/smartbch/docs/blob/main/developers-guide/jsonrpc.md" + "url": "https://canto.io/", + "documentation": "https://docs.canto.io/" } }, { - "id": "kcc", - "name": "KuCoin Community Chain", - "coinId": 10000321, - "symbol": "KCS", + "id": "zetachain", + "name": "NativeZetaChain", + "displayName": "NativeZetaChain", + "coinId": 10007000, + "symbol": "ZETA", "decimals": 18, - "blockchain": "Ethereum", + "blockchain": "Cosmos", + "chainId": "zetachain_7000-1", "derivation": [ { "path": "m/44'/60'/0'/0/0" @@ -2321,27 +4470,25 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "321", + "hrp": "zeta", "addressHasher": "keccak256", "explorer": { - "url": "https://explorer.kcc.io/en", - "txPath": "/tx/", + "url": "https://explorer.zetachain.com", + "txPath": "/cosmos/tx/", "accountPath": "/address/", - "sampleTx": "0x2f0d79cd289a02f3181b68b9583a64c3809fe7387810b274275985c29d02c80d", - "sampleAccount": "0x4446fc4eb47f2f6586f9faab68b3498f86c07521" + "sampleTx": "2DBB071DDD47985F4470A21E5943CE95D371AE4BDE2267E201D3553FB2BDCFDE", + "sampleAccount": "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne" }, "info": { - "url": "https://www.kcc.io/", - "source": "https://github.com/kcc-community/kcc", - "rpc": "https://rpc-mainnet.kcc.network", - "documentation": "https://docs.kcc.io/#/en-us/" + "url": "https://www.zetachain.com/", + "documentation": "https://www.zetachain.com/docs/" } }, { - "id": "boba", - "name": "Boba", - "coinId": 10000288, - "symbol": "BOBAETH", + "id": "zetaevm", + "name": "Zeta EVM", + "coinId": 20007000, + "symbol": "ZETA", "decimals": 18, "blockchain": "Ethereum", "derivation": [ @@ -2351,27 +4498,53 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "288", + "chainId": "7000", "addressHasher": "keccak256", "explorer": { - "url": "https://blockexplorer.boba.network", - "txPath": "/tx/", + "url": "https://explorer.zetachain.com", + "txPath": "/evm/tx/", "accountPath": "/address/", - "sampleTx": "0x31533707c3feb3b10f7deeea387ff8893f229253e65ca6b14d2400bf95b5d103", - "sampleAccount": "0x4F96F50eDB37a19216d87693E5dB241e31bD3735" + "sampleTx": "0x04cb1201857de29af97b755e51c888454fb96c1f3bb3c1329bb94d5353d5c19e", + "sampleAccount": "0x85539A58F9c88DdDccBaBBfc660968323Fd1e167" }, "info": { - "url": "https://boba.network/", - "source": "https://github.com/bobanetwork/boba", - "rpc": "https://mainnet.boba.network", - "documentation": "https://docs.boba.network/" + "url": "https://www.zetachain.com/", + "documentation": "https://www.zetachain.com/docs/" } }, { - "id": "metis", - "name": "Metis", - "coinId": 10001088, - "symbol": "METIS", + "id": "ton", + "name": "TON", + "coinId": 607, + "symbol": "TON", + "decimals": 9, + "blockchain": "TheOpenNetwork", + "derivation": [ + { + "path": "m/44'/607'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "explorer": { + "url": "https://tonviewer.com", + "txPath": "/transaction/", + "accountPath": "/", + "sampleTx": "fJXfn0EVhV09HFuEgUHu4Cchb24nUQtIMwSzmzk2tLs=", + "sampleAccount": "EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N" + }, + "info": { + "url": "https://ton.org", + "source": "https://github.com/ton-blockchain", + "rpc": "https://toncenter.com/api/v2/jsonRPC", + "documentation": "https://ton.org/docs" + } + }, + { + "id": "neon", + "name": "Neon", + "coinId": 245022934, + "symbol": "NEON", "decimals": 18, "blockchain": "Ethereum", "derivation": [ @@ -2381,26 +4554,56 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "1088", + "chainId": "245022934", "addressHasher": "keccak256", "explorer": { - "url": "https://andromeda-explorer.metis.io", + "url": "https://neonscan.org", "txPath": "/tx/", "accountPath": "/address/", - "sampleTx": "0x422f2ebbede32d4434ad0cf0ae55d44a84e14d3d5725a760133255b42676d8ce", - "sampleAccount": "0xBe9E8Ec25866B21bA34e97b9393BCabBcB4A5C86" + "sampleTx": "0x77d86af2c6f02f14ef13ca52bf54864d92fcc4b32d8e884e225061c006738ed6", + "sampleAccount": "0xfa4a8650e7bebb918859c280a86f9661bed29877" }, "info": { - "url": "https://www.metis.io/", - "source": "https://github.com/MetisProtocol/mvm", - "rpc": "https://andromeda.metis.io/?owner=1088", - "documentation": "https://docs.metis.io/" + "url": "https://neonevm.org", + "source": "https://github.com/neonevm/neon-evm", + "rpc": "https://neon-proxy-mainnet.solana.p2p.org/", + "documentation": "https://docs.neonfoundation.io/docs/quick_start" } }, { - "id": "aurora", - "name": "Aurora", - "coinId": 1323161554, + "id": "internet_computer", + "name": "Internet Computer", + "coinId": 223, + "symbol": "ICP", + "decimals": 8, + "blockchain": "InternetComputer", + "derivation": [ + { + "path": "m/44'/223'/0'/0/0", + "xpub": "xpub", + "xpriv": "xpriv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://dashboard.internetcomputer.org", + "txPath": "/transaction/", + "accountPath": "/account/", + "sampleTx": "9e32c54975adf84a1d98f19df41bbc34a752a899c32cc9c0000200b2c4308f85", + "sampleAccount": "529ea51c22e8d66e8302eabd9297b100fdb369109822248bb86939a671fbc55b" + }, + "info": { + "url": "https://internetcomputer.org", + "source": "https://github.com/dfinity/ic", + "rpc": "", + "documentation": "https://internetcomputer.org/docs" + } + }, + { + "id": "manta", + "name": "Manta Pacific", + "coinId": 169, "symbol": "ETH", "decimals": 18, "blockchain": "Ethereum", @@ -2411,27 +4614,27 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "1313161554", + "chainId": "169", "addressHasher": "keccak256", "explorer": { - "url": "https://aurorascan.dev", + "url": "https://pacific-explorer.manta.network", "txPath": "/tx/", "accountPath": "/address/", - "sampleTx": "0x99deebdb70f8027037abb3d3d0f3c7523daee857d85e9056d2671593ff2f2f28", - "sampleAccount": "0x8707cdE20dd43E3dB1F74c28fcd509ef38B0bA51" + "sampleTx": "0x2bbd5d85b0ed05d1416e30ce1197a6f0c27d10ce02593a2719e2baf486d2e8c2", + "sampleAccount": "0xF122a1aC569a36a5Cf6d0F828A22254c8A9afF84" }, "info": { - "url": "https://aurora.dev/", - "source": "https://github.com/aurora-is-near/aurora-engine", - "rpc": "https://mainnet.aurora.dev/", - "documentation": "https://doc.aurora.dev/" + "url": "https://pacific.manta.network", + "source": "https://github.com/manta-network", + "rpc": "https://pacific-rpc.manta.network/http", + "documentation": "https://docs.manta.network/docs/Introduction" } }, { - "id": "evmos", - "name": "Evmos", - "coinId": 10009001, - "symbol": "EVMOS", + "id": "merlin", + "name": "Merlin", + "coinId": 4200, + "symbol": "BTC", "decimals": 18, "blockchain": "Ethereum", "derivation": [ @@ -2441,30 +4644,30 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "9001", + "chainId": "4200", "addressHasher": "keccak256", "explorer": { - "url": "https://evm.evmos.org", + "url": "https://scan.merlinchain.io", "txPath": "/tx/", "accountPath": "/address/", - "sampleTx": "0x24af42cf4977a96d62e3a82c3cd9b519c3e7c53dd83398b88f0cb435d867b422", - "sampleAccount": "0x30627903124Aa1e71384bc52e1cb96E4AB3252b6" + "sampleTx": "0xca6f2891959b669237738dd0bc2c1051d966778c9de90b94165032ce58364212", + "sampleAccount": "0xf7e017b3f61bD3410A3b03D7DAD7699FD6780584" }, "info": { - "url": "https://evmos.org/", - "source": "https://github.com/tharsis/evmos", - "rpc": "https://eth.bd.evmos.org:8545", - "documentation": "https://docs.evmos.org/" + "url": "https://merlinchain.io", + "source": "https://merlinchain.io", + "rpc": "https://rpc.merlinchain.io", + "documentation": "https://docs.merlinchain.io/merlin-docs" } }, { - "id": "nativeevmos", - "name": "NativeEvmos", - "displayName": "Native Evmos", - "coinId": 20009001, - "symbol": "EVMOS", + "id": "lightlink", + "name": "Lightlink", + "displayName": "Lightlink Phoenix", + "coinId": 1890, + "symbol": "ETH", "decimals": 18, - "blockchain": "Cosmos", + "blockchain": "Ethereum", "derivation": [ { "path": "m/44'/60'/0'/0/0" @@ -2472,27 +4675,27 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "hrp": "evmos", + "chainId": "1890", "addressHasher": "keccak256", "explorer": { - "url": "https://mintscan.io/evmos", - "txPath": "/txs/", - "accountPath": "/account/", - "sampleTx": "A16C211C83AD1E684DE46F694FAAC17D8465C864BD7385A81EC062CDE0638811", - "sampleAccount": "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34" + "url": "https://phoenix.lightlink.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0xc65f82445aefc883fd4ffe09149c8ce48f61b670c0734528a49d4a8bd8309bb0", + "sampleAccount": "0x4566ED6c7a7fFc90E2C7cfF7eB9156262afD2fDe" }, "info": { - "url": "https://evmos.org/", - "client": "https://github.com/tharsis/evmos", - "clientPublic": "https://rest.bd.evmos.org:1317", - "clientDocs": "" + "url": "https://lightlink.io", + "source": "https://github.com/lightlink-network", + "rpc": "https://endpoints.omniatech.io/v1/lightlink/phoenix/public", + "documentation": "https://docs.lightlink.io/lightlink-protocol" } }, { - "id": "moonriver", - "name": "Moonriver", - "coinId": 10001285, - "symbol": "MOVR", + "id": "blast", + "name": "Blast", + "coinId": 81457, + "symbol": "ETH", "decimals": 18, "blockchain": "Ethereum", "derivation": [ @@ -2502,24 +4705,27 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "1285", + "chainId": "81457", + "addressHasher": "keccak256", "explorer": { - "url": "https://moonriver.moonscan.io", + "url": "https://blastscan.io", "txPath": "/tx/", "accountPath": "/address/", - "sampleTx": "0x2e2daa3943ba65d9bbb910a4f6765aa6a466a0ef8935090547ca9d30e201e032", - "sampleAccount": "0x899831D937937d011305E73EE782cce0455DF15a" + "sampleTx": "0x511fc00e8329343b9e953bf1f75e9b0a7b3cc2eb3a8f049d5be41adf4fbd6cac", + "sampleAccount": "0x0d11f2f0ff55c4fcfc3ff86bdc8e78ffa7df99fd" }, "info": { - "url": "https://moonbeam.network/networks/moonriver", - "rpc": "https://moonriver.public.blastapi.io" + "url": "https://blast.io", + "source": "https://github.com/blast-io", + "rpc": "https://rpc.blast.io", + "documentation": "https://docs.blast.io" } }, { - "id": "moonbeam", - "name": "Moonbeam", - "coinId": 10001284, - "symbol": "GLMR", + "id": "bouncebit", + "name": "BounceBit", + "coinId": 6001, + "symbol": "BB", "decimals": 18, "blockchain": "Ethereum", "derivation": [ @@ -2529,25 +4735,28 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "1284", + "chainId": "6001", + "addressHasher": "keccak256", "explorer": { - "url": "https://moonscan.io", + "url": "https://bbscan.io", "txPath": "/tx/", "accountPath": "/address/", - "sampleTx": "0xb22a146c933e6e51affbfa5f712a266b5f5e92ae453cd2f252bcc3c36ff035a6", - "sampleAccount": "0x201bb4f276C765dF7225e5A4153E17edD23a67eC" + "sampleTx": "0x52558f4143d058d942e3b73414090266ae3ffce1fe8c25fe86896e2c8e5ef932", + "sampleAccount": "0xf4aa7349a9ccca4609943955b5ddc7bd9278c223" }, "info": { - "url": "https://moonbeam.network", - "rpc": "https://rpc.api.moonbeam.network", - "documentation": "https://docs.moonbeam.network" + "url": "https://bouncebit.io", + "source": "https://github.com/BounceBit-Labs", + "rpc": "https://fullnode-mainnet.bouncebitapi.com", + "documentation": "https://docs.bouncebit.io" } }, { - "id": "klaytn", - "name": "Klaytn", - "coinId": 10008217, - "symbol": "KLAY", + "id": "zklinknova", + "name": "ZkLinkNova", + "displayName": "zkLink Nova Mainnet", + "coinId": 810180, + "symbol": "ETH", "decimals": 18, "blockchain": "Ethereum", "derivation": [ @@ -2557,18 +4766,20 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1Extended", - "chainId": "8217", + "chainId": "810180", + "addressHasher": "keccak256", "explorer": { - "url": "https://scope.klaytn.com", + "url": "https://explorer.zklink.io", "txPath": "/tx/", - "accountPath": "/account/", - "sampleTx": "0x93ea92687845fe7bb6cacd69c76a16a2a3c2bbb85a8a93ff0e032d0098d583d7", - "sampleAccount": "0x2ad9656bf5b82caf10847b431012e28e301e83ba" + "accountPath": "/address/", + "sampleTx": "0xeb5eb8710369c89115a83f3e744c15c9d388030cfce2fd3a653dbd18f2947400", + "sampleAccount": "0xF95115BaD9a4585B3C5e2bfB50579f17163A45aA" }, "info": { - "url": "https://klaytn.foundation", - "rpc": "https://public-node-api.klaytnapi.com/v1/cypress", - "documentation": "https://docs.klaytn.foundation" + "url": "https://zklink.io", + "source": "https://github.com/zkLinkProtocol", + "rpc": "https://rpc.zklink.io", + "documentation": "https://docs.zklink.io" } } -] \ No newline at end of file +] diff --git a/rust/.gitignore b/rust/.gitignore new file mode 100644 index 00000000000..bca6f9b85aa --- /dev/null +++ b/rust/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/rust/Cargo.lock b/rust/Cargo.lock new file mode 100644 index 00000000000..108bb84e1e0 --- /dev/null +++ b/rust/Cargo.lock @@ -0,0 +1,2436 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "arbitrary" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db55d72333851e17d572bec876e390cd3b11eb1ef53ae821dd9f3b653d2b4569" + +[[package]] +name = "arbitrary" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.6", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.107", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.107", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std", + "digest 0.10.6", + "num-bigint", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bcs" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b6598a2f5d564fb7855dc6b06fd1c38cff5a72bd8b863a4d021938497b440a" +dependencies = [ + "serde", + "thiserror", +] + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bigdecimal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e99ff7289b20a7385f66a0feda78af2fc119d28fb56aea8886a9cd0a4abdd75" +dependencies = [ + "bech32", + "bitcoin-private", + "bitcoin_hashes", + "hex_lit", + "secp256k1", +] + +[[package]] +name = "bitcoin-private" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" + +[[package]] +name = "bitcoin_hashes" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501" +dependencies = [ + "bitcoin-private", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitreader" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd859c9d97f7c468252795b35aeccc412bdbb1e90ee6969c4fa6328272eaeff" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "bitstream-io" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcde5f311c85b8ca30c2e4198d4326bc342c76541590106f5fa4a50946ea499" + +[[package]] +name = "bitvec" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7774144344a4faa177370406a7ff5f1da24303817368584c6206c8303eb07848" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d1988118c887f61418940e322d574e8a2dd67165f1f1556eaae22e4019c6af" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "ppv-lite86", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.6", +] + +[[package]] +name = "blake2b-ref" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "294d17c72e0ba59fad763caa112368d0672083779cdebbb97164f4bb4c1e339a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58b559fd6448c6e2fd0adb5720cd98a2506594cafa4737ff98c396f3e82f667" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aadb5b6ccbd078890f6d7003694e33816e6b784358f18e15e7e6d9f065a57cd" +dependencies = [ + "once_cell", + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.37", + "syn_derive", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "const-oid" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2538c4e68e52548bacb3e83ac549f903d44f011ac9d5abb5e132e67d0808f7" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "crypto_box" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16182b4f39a82ec8a6851155cc4c0cda3065bb1db33651726a29e1951de0f009" +dependencies = [ + "aead", + "crypto_secretbox", + "curve25519-dalek", + "salsa20", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto_secretbox" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" +dependencies = [ + "aead", + "cipher", + "generic-array", + "poly1305", + "salsa20", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.6", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + +[[package]] +name = "der" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b10af9f9f9f2134a42d3f8aa74658660f2e0234b0eb81bd171df8aa32779ed" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.107", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cdeb9ec472d588e539a818b2dee436825730da08ad0017c4b1a17676bdc8b7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.107", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer 0.10.3", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a48e5d537b8a30c0b023116d981b16334be1485af7ca68db3a2b7024cbc957fd" +dependencies = [ + "der", + "digest 0.10.6", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "elliptic-curve" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c71eaa367f2e5d556414a8eea812bc62985c879748d6403edabd9cb03f16e7" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.6", + "ff", + "generic-array", + "group", + "hkdf", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" + +[[package]] +name = "ethnum" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8ff382b2fa527fb7fb06eeebfc5bbb3f17e3cc6b9d70b006c41daa8824adac" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" + +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "arbitrary 0.4.7", + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "groestl" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343cfc165f92a988fd60292f7a0bfde4352a5a0beff9fbec29251ca4e9676e4d" +dependencies = [ + "digest 0.10.6", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.6", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "impl-codec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161ebdfec3c8e3b52bf61c4f3550a1eea4f9579d10dc1b936f3171ebdcd6c443" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.107", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "move-core-types" +version = "0.0.4" +source = "git+https://github.com/move-language/move?rev=ea70797099baea64f05194a918cebd69ed02b285#ea70797099baea64f05194a918cebd69ed02b285" +dependencies = [ + "anyhow", + "bcs", + "ethnum", + "hex", + "num", + "once_cell", + "primitive-types", + "rand", + "ref-cast", + "serde", + "serde_bytes", + "uint", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "parity-scale-codec" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373b1a4c1338d9cd3d1fa53b3a11bdab5ab6bd80a20f7f7becd76953ae2be909" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.107", +] + +[[package]] +name = "paste" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" + +[[package]] +name = "pb-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "354a34df9c65b596152598001c0fe3393379ec2db03ae30b9985659422e2607e" +dependencies = [ + "clap", + "env_logger", + "log", + "nom", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primeorder" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8d3875361e28f7753baefef104386e7aa47642c93023356d97fdef4003bfb5" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "primitive-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-serde", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.11", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ref-cast" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c78fb8c9293bcd48ef6fce7b4ca950ceaf21210de6e105a883ee280c0f7b9ed" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f9c0c92af03644e4806106281fe2e068ac5bc0ae74a707266d06ea27bccee5f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.107", +] + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.6", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "sec1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48518a2b5775ba8ca5b46596aae011caa431e6ce7e4a67ead66d92f08884220e" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +dependencies = [ + "bitcoin_hashes", + "rand", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +dependencies = [ + "cc", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.6", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.6", +] + +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.6", + "keccak", +] + +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.6", + "rand_core", +] + +[[package]] +name = "spki" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a5be806ab6f127c3da44b7378837ebf01dadca8510a0e572460216b228bd0e" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "starknet-crypto" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693e6362f150f9276e429a910481fb7f3bcb8d6aa643743f587cfece0b374874" +dependencies = [ + "crypto-bigint", + "hex", + "hmac", + "num-bigint", + "num-integer", + "num-traits", + "rfc6979", + "sha2", + "starknet-crypto-codegen", + "starknet-curve", + "starknet-ff", + "zeroize", +] + +[[package]] +name = "starknet-crypto-codegen" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6dc88f1f470d9de1001ffbb90d2344c9dd1a615f5467daf0574e2975dfd9ebd" +dependencies = [ + "starknet-curve", + "starknet-ff", + "syn 2.0.37", +] + +[[package]] +name = "starknet-curve" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252610baff59e4c4332ce3569f7469c5d3f9b415a2240d698fb238b2b4fc0942" +dependencies = [ + "starknet-ff", +] + +[[package]] +name = "starknet-ff" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcdf692e13247ec111718e219caaa44ea1a687e9c36bf6083e1cd1b98374a2ad" +dependencies = [ + "ark-ff", + "bigdecimal", + "crypto-bigint", + "getrandom", + "hex", + "serde", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.37", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.107", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.19.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.4.7", +] + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "tw_any_coin" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_coin_registry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_proto", +] + +[[package]] +name = "tw_aptos" +version = "0.1.0" +dependencies = [ + "move-core-types", + "serde", + "serde_bytes", + "serde_json", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_number", + "tw_proto", +] + +[[package]] +name = "tw_base58_address" +version = "0.1.0" +dependencies = [ + "serde", + "tw_coin_entry", + "tw_encoding", + "tw_hash", +] + +[[package]] +name = "tw_bech32_address" +version = "0.1.0" +dependencies = [ + "serde", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", +] + +[[package]] +name = "tw_binance" +version = "0.1.0" +dependencies = [ + "quick-protobuf", + "serde", + "serde_json", + "serde_repr", + "strum_macros", + "tw_bech32_address", + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_encoding", + "tw_evm", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_proto", +] + +[[package]] +name = "tw_bitcoin" +version = "0.1.0" +dependencies = [ + "bitcoin", + "secp256k1", + "serde", + "serde_json", + "tw_base58_address", + "tw_bech32_address", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_proto", + "tw_utxo", +] + +[[package]] +name = "tw_coin_entry" +version = "0.1.0" +dependencies = [ + "derivation-path", + "serde", + "serde_json", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", +] + +[[package]] +name = "tw_coin_registry" +version = "0.1.0" +dependencies = [ + "itertools", + "lazy_static", + "serde", + "serde_json", + "strum", + "strum_macros", + "tw_aptos", + "tw_binance", + "tw_bitcoin", + "tw_coin_entry", + "tw_cosmos", + "tw_ethereum", + "tw_evm", + "tw_greenfield", + "tw_hash", + "tw_internet_computer", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_native_evmos", + "tw_native_injective", + "tw_ronin", + "tw_solana", + "tw_sui", + "tw_thorchain", + "tw_ton", + "tw_utxo", +] + +[[package]] +name = "tw_cosmos" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_keypair", + "tw_proto", +] + +[[package]] +name = "tw_cosmos_sdk" +version = "0.1.0" +dependencies = [ + "pb-rs", + "quick-protobuf", + "serde", + "serde_json", + "tw_bech32_address", + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", +] + +[[package]] +name = "tw_encoding" +version = "0.1.0" +dependencies = [ + "arbitrary 1.3.0", + "bcs", + "bech32", + "bs58", + "ciborium", + "data-encoding", + "hex", + "serde", + "serde_bytes", + "tw_memory", +] + +[[package]] +name = "tw_ethereum" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_encoding", + "tw_evm", + "tw_keypair", + "tw_number", + "tw_proto", +] + +[[package]] +name = "tw_evm" +version = "0.1.0" +dependencies = [ + "itertools", + "lazy_static", + "rlp", + "serde", + "serde_json", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", +] + +[[package]] +name = "tw_greenfield" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_encoding", + "tw_evm", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", +] + +[[package]] +name = "tw_hash" +version = "0.1.0" +dependencies = [ + "arbitrary 1.3.0", + "blake-hash", + "blake2b-ref", + "digest 0.10.6", + "groestl", + "hmac", + "ripemd", + "serde", + "serde_json", + "sha1", + "sha2", + "sha3", + "tw_encoding", + "tw_memory", + "zeroize", +] + +[[package]] +name = "tw_internet_computer" +version = "0.1.0" +dependencies = [ + "pb-rs", + "quick-protobuf", + "serde", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_proto", +] + +[[package]] +name = "tw_keypair" +version = "0.1.0" +dependencies = [ + "arbitrary 1.3.0", + "bitcoin", + "blake2", + "crypto_box", + "curve25519-dalek", + "der", + "digest 0.10.6", + "ecdsa", + "k256", + "lazy_static", + "p256", + "pkcs8", + "rand_core", + "rfc6979", + "secp256k1", + "serde", + "serde_json", + "sha2", + "starknet-crypto", + "starknet-ff", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "zeroize", +] + +[[package]] +name = "tw_memory" +version = "0.1.0" + +[[package]] +name = "tw_misc" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "zeroize", +] + +[[package]] +name = "tw_native_evmos" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_proto", +] + +[[package]] +name = "tw_native_injective" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_proto", +] + +[[package]] +name = "tw_number" +version = "0.1.0" +dependencies = [ + "arbitrary 1.3.0", + "lazy_static", + "primitive-types", + "serde", + "tw_encoding", + "tw_hash", + "tw_memory", +] + +[[package]] +name = "tw_proto" +version = "0.1.0" +dependencies = [ + "arbitrary 1.3.0", + "pb-rs", + "quick-protobuf", +] + +[[package]] +name = "tw_ronin" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_encoding", + "tw_evm", + "tw_keypair", + "tw_memory", + "tw_number", + "tw_proto", +] + +[[package]] +name = "tw_solana" +version = "0.1.0" +dependencies = [ + "bincode", + "borsh", + "lazy_static", + "serde", + "serde_json", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_proto", +] + +[[package]] +name = "tw_sui" +version = "0.1.0" +dependencies = [ + "indexmap", + "move-core-types", + "serde", + "serde_repr", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_proto", +] + +[[package]] +name = "tw_tests" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tw_any_coin", + "tw_coin_entry", + "tw_coin_registry", + "tw_cosmos_sdk", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", + "tw_solana", + "tw_ton", + "tw_ton_sdk", + "tw_utxo", + "wallet-core-rs", +] + +[[package]] +name = "tw_thorchain" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_keypair", + "tw_memory", + "tw_proto", +] + +[[package]] +name = "tw_ton" +version = "0.1.0" +dependencies = [ + "lazy_static", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", + "tw_ton_sdk", +] + +[[package]] +name = "tw_ton_sdk" +version = "0.1.0" +dependencies = [ + "bitreader", + "bitstream-io", + "crc", + "lazy_static", + "num-bigint", + "serde", + "serde_json", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_number", +] + +[[package]] +name = "tw_utxo" +version = "0.1.0" +dependencies = [ + "bech32", + "bitcoin", + "byteorder", + "itertools", + "secp256k1", + "strum_macros", + "tw_base58_address", + "tw_bech32_address", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_proto", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "arbitrary 1.3.0", + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +dependencies = [ + "getrandom", +] + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wallet-core-rs" +version = "0.1.0" +dependencies = [ + "bitreader", + "tw_any_coin", + "tw_bitcoin", + "tw_coin_registry", + "tw_encoding", + "tw_ethereum", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", + "tw_solana", + "tw_ton", + "uuid", +] + +[[package]] +name = "wallet_core_bin" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tw_coin_registry", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.107", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.107", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winnow" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 00000000000..ca41fb6099d --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,52 @@ +[workspace] +members = [ + "chains/tw_aptos", + "chains/tw_binance", + "chains/tw_bitcoin", + "chains/tw_cosmos", + "chains/tw_ethereum", + "chains/tw_greenfield", + "chains/tw_internet_computer", + "chains/tw_native_evmos", + "chains/tw_native_injective", + "chains/tw_ronin", + "chains/tw_solana", + "chains/tw_sui", + "chains/tw_thorchain", + "chains/tw_ton", + "frameworks/tw_ton_sdk", + "frameworks/tw_utxo", + "tw_any_coin", + "tw_base58_address", + "tw_bech32_address", + "tw_coin_entry", + "tw_coin_registry", + "tw_cosmos_sdk", + "tw_encoding", + "tw_evm", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", + "tw_tests", + "wallet_core_bin", + "wallet_core_rs", +] + +[profile.release] +strip = true +codegen-units = 1 +panic = "abort" + +[profile.wasm-test] +inherits = "release" +# Fixes an incredibly slow compilation of `curve25519-dalek` package. +opt-level = 1 +debug = true +debug-assertions = true +overflow-checks = true + +[profile.release.package.curve25519-dalek] +opt-level = 2 diff --git a/rust/chains/tw_aptos/Cargo.toml b/rust/chains/tw_aptos/Cargo.toml new file mode 100644 index 00000000000..d78b0e3c644 --- /dev/null +++ b/rust/chains/tw_aptos/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "tw_aptos" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde_json = "1.0" +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_encoding = { path = "../../tw_encoding" } +tw_keypair = { path = "../../tw_keypair" } +tw_proto = { path = "../../tw_proto" } +tw_number = { path = "../../tw_number" } +tw_hash = { path = "../../tw_hash" } +tw_memory = { path = "../../tw_memory" } +move-core-types = { git = "https://github.com/move-language/move", rev = "ea70797099baea64f05194a918cebd69ed02b285", features = ["address32"] } +serde = { version = "1.0", features = ["derive"] } +serde_bytes = "0.11.12" + +[dev-dependencies] +tw_coin_entry = { path = "../../tw_coin_entry", features = ["test-utils"] } +tw_encoding = { path = "../../tw_encoding" } +tw_number = { path = "../../tw_number", features = ["helpers"] } diff --git a/rust/chains/tw_aptos/fuzz/.gitignore b/rust/chains/tw_aptos/fuzz/.gitignore new file mode 100644 index 00000000000..5c404b9583f --- /dev/null +++ b/rust/chains/tw_aptos/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +Cargo.lock diff --git a/rust/chains/tw_aptos/fuzz/Cargo.toml b/rust/chains/tw_aptos/fuzz/Cargo.toml new file mode 100644 index 00000000000..721a84b3ab9 --- /dev/null +++ b/rust/chains/tw_aptos/fuzz/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "tw_aptos-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +tw_proto = { path = "../../tw_proto", features = ["fuzz"] } + +[dependencies.tw_aptos] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "sign" +path = "fuzz_targets/sign.rs" +test = false +doc = false diff --git a/rust/chains/tw_aptos/fuzz/fuzz_targets/sign.rs b/rust/chains/tw_aptos/fuzz/fuzz_targets/sign.rs new file mode 100644 index 00000000000..5d55e467d14 --- /dev/null +++ b/rust/chains/tw_aptos/fuzz/fuzz_targets/sign.rs @@ -0,0 +1,9 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_aptos::signer::Signer; +use tw_proto::Aptos::Proto; + +fuzz_target!(|input: Proto::SigningInput<'_>| { + let _ = Signer::sign_proto(input); +}); diff --git a/rust/chains/tw_aptos/src/address.rs b/rust/chains/tw_aptos/src/address.rs new file mode 100644 index 00000000000..3915750c625 --- /dev/null +++ b/rust/chains/tw_aptos/src/address.rs @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use move_core_types::account_address::{AccountAddress, AccountAddressParseError}; +use std::fmt::{Display, Formatter}; +use std::str::FromStr; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::*; +use tw_hash::sha3::sha3_256; +use tw_keypair::ed25519; +use tw_memory::Data; + +#[repr(u8)] +pub enum Scheme { + Ed25519 = 0, +} + +#[derive(Clone)] +pub struct Address { + addr: AccountAddress, +} + +impl Address { + pub const LENGTH: usize = AccountAddress::LENGTH; + + /// Initializes an address with a `ed25519` public key. + pub fn with_ed25519_pubkey( + pubkey: &ed25519::sha512::PublicKey, + ) -> Result { + let mut to_hash = pubkey.as_slice().to_vec(); + to_hash.push(Scheme::Ed25519 as u8); + let hashed = sha3_256(to_hash.as_slice()); + let addr = AccountAddress::from_bytes(hashed).map_err(from_account_error)?; + Ok(Address { addr }) + } + + pub fn inner(&self) -> AccountAddress { + self.addr + } +} + +impl Display for Address { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.addr.to_hex_literal()) + } +} + +impl CoinAddress for Address { + #[inline] + fn data(&self) -> Data { + self.addr.to_vec() + } +} + +#[inline] +pub fn from_account_error(_err: AccountAddressParseError) -> AddressError { + AddressError::InvalidInput +} + +impl FromStr for Address { + type Err = AddressError; + + // https://github.com/aptos-labs/aptos-core/blob/261019cbdafe1c514c60c2b74357ea2c77d25e67/types/src/account_address.rs#L44 + fn from_str(s: &str) -> Result { + const NUM_CHARS: usize = AccountAddress::LENGTH * 2; + let mut has_0x = false; + let mut working = s.trim(); + + // Checks if it has a 0x at the beginning, which is okay + if working.starts_with("0x") { + has_0x = true; + working = &working[2..]; + } + + if working.len() > NUM_CHARS || (!has_0x && working.len() < NUM_CHARS) { + return Err(AddressError::InvalidInput); + } + + if !working.chars().all(|c| char::is_ascii_hexdigit(&c)) { + return Err(AddressError::InvalidInput); + } + + let addr = if has_0x { + AccountAddress::from_hex_literal(s.trim()) + } else { + AccountAddress::from_str(s.trim()) + } + .map_err(from_account_error)?; + + Ok(Address { addr }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_keypair::ed25519::sha512::PrivateKey; + + #[test] + fn test_from_public_key() { + let private = PrivateKey::try_from( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ) + .unwrap(); + let public = private.public(); + let addr = Address::with_ed25519_pubkey(&public); + assert_eq!( + addr.as_ref().unwrap().to_string(), + "0x9006fa46f038224e8004bdda97f2e7a60c2c3d135bce7cb15541e5c0aae907a4" + ); + assert_eq!(addr.unwrap().data().len(), Address::LENGTH); + } + + #[test] + fn test_from_account_error() { + assert_eq!( + from_account_error(AccountAddressParseError {}), + AddressError::InvalidInput + ); + } +} diff --git a/rust/chains/tw_aptos/src/aptos_move_packages.rs b/rust/chains/tw_aptos/src/aptos_move_packages.rs new file mode 100644 index 00000000000..e90e6bf2a4b --- /dev/null +++ b/rust/chains/tw_aptos/src/aptos_move_packages.rs @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::transaction_payload::{EntryFunction, TransactionPayload}; +use move_core_types::account_address::AccountAddress; +use move_core_types::ident_str; +use move_core_types::language_storage::{ModuleId, TypeTag}; +use serde_json::json; +use tw_coin_entry::error::prelude::*; +use tw_encoding::bcs; + +pub fn aptos_account_transfer( + to: AccountAddress, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("aptos_account").to_owned(), + ), + ident_str!("transfer").to_owned(), + vec![], + vec![bcs::encode(&to)?, bcs::encode(&amount)?], + json!([to.to_hex_literal(), amount.to_string()]), + ))) +} + +pub fn aptos_account_create_account(auth_key: AccountAddress) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("aptos_account").to_owned(), + ), + ident_str!("create_account").to_owned(), + vec![], + vec![bcs::encode(&auth_key)?], + json!([auth_key.to_hex_literal()]), + ))) +} + +pub fn coin_transfer( + coin_type: TypeTag, + to: AccountAddress, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("coin").to_owned(), + ), + ident_str!("transfer").to_owned(), + vec![coin_type], + vec![bcs::encode(&to)?, bcs::encode(&amount)?], + json!([to.to_hex_literal(), amount.to_string()]), + ))) +} + +pub fn aptos_account_transfer_coins( + coin_type: TypeTag, + to: AccountAddress, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("aptos_account").to_owned(), + ), + ident_str!("transfer_coins").to_owned(), + vec![coin_type], + vec![bcs::encode(&to)?, bcs::encode(&amount)?], + json!([to.to_hex_literal(), amount.to_string()]), + ))) +} + +pub fn token_transfers_offer_script( + receiver: AccountAddress, + creator: AccountAddress, + collection: Vec, + name: Vec, + property_version: u64, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, + ]), + ident_str!("token_transfers").to_owned(), + ), + ident_str!("offer_script").to_owned(), + vec![], + vec![ + bcs::encode(&receiver)?, + bcs::encode(&creator)?, + bcs::encode(&collection)?, + bcs::encode(&name)?, + bcs::encode(&property_version)?, + bcs::encode(&amount)?, + ], + json!([ + receiver.to_hex_literal(), + creator.to_hex_literal(), + String::from_utf8_lossy(&collection), + String::from_utf8_lossy(&name), + property_version.to_string(), + amount.to_string() + ]), + ))) +} + +pub fn token_transfers_cancel_offer_script( + receiver: AccountAddress, + creator: AccountAddress, + collection: Vec, + name: Vec, + property_version: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, + ]), + ident_str!("token_transfers").to_owned(), + ), + ident_str!("cancel_offer_script").to_owned(), + vec![], + vec![ + bcs::encode(&receiver)?, + bcs::encode(&creator)?, + bcs::encode(&collection)?, + bcs::encode(&name)?, + bcs::encode(&property_version)?, + ], + json!([ + receiver.to_hex_literal(), + creator.to_hex_literal(), + String::from_utf8_lossy(&collection), + String::from_utf8_lossy(&name), + property_version.to_string() + ]), + ))) +} + +pub fn token_transfers_claim_script( + sender: AccountAddress, + creator: AccountAddress, + collection: Vec, + name: Vec, + property_version: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, + ]), + ident_str!("token_transfers").to_owned(), + ), + ident_str!("claim_script").to_owned(), + vec![], + vec![ + bcs::encode(&sender)?, + bcs::encode(&creator)?, + bcs::encode(&collection)?, + bcs::encode(&name)?, + bcs::encode(&property_version)?, + ], + json!([ + sender.to_hex_literal(), + creator.to_hex_literal(), + String::from_utf8_lossy(&collection), + String::from_utf8_lossy(&name), + property_version.to_string() + ]), + ))) +} + +pub fn managed_coin_register(coin_type: TypeTag) -> TransactionPayload { + TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("managed_coin").to_owned(), + ), + ident_str!("register").to_owned(), + vec![coin_type], + vec![], + json!([]), + )) +} diff --git a/rust/chains/tw_aptos/src/compiler.rs b/rust/chains/tw_aptos/src/compiler.rs new file mode 100644 index 00000000000..53b9df11bd4 --- /dev/null +++ b/rust/chains/tw_aptos/src/compiler.rs @@ -0,0 +1,76 @@ +use crate::address::Address; +use crate::transaction_builder; +use std::str::FromStr; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_proto::Aptos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct Compiler; + +impl Compiler { + #[inline] + pub fn preimage_hashes( + input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + Self::preimage_hashes_impl(input) + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + fn preimage_hashes_impl( + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let builder = transaction_builder::TransactionFactory::new_from_protobuf(input.clone())?; + let sender = Address::from_str(&input.sender) + .into_tw() + .context("Invalid sender address")?; + let signed_tx = builder + .sender(sender.inner()) + .sequence_number(input.sequence_number as u64) + .build()? + .pre_image()?; + Ok(CompilerProto::PreSigningOutput { + data: signed_tx.into(), + ..CompilerProto::PreSigningOutput::default() + }) + } + + #[inline] + pub fn compile( + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + Self::compile_impl(input, signatures, public_keys) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn compile_impl( + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> SigningResult> { + let builder = transaction_builder::TransactionFactory::new_from_protobuf(input.clone())?; + let sender = Address::from_str(&input.sender)?; + let signature = signatures + .first() + .or_tw_err(SigningErrorType::Error_signatures_count)?; + let public_key = public_keys + .first() + .or_tw_err(SigningErrorType::Error_signatures_count)?; + + let signed_tx = builder + .sender(sender.inner()) + .sequence_number(input.sequence_number as u64) + .build()? + .compile(signature.to_vec(), public_key.to_vec())?; + Ok(Proto::SigningOutput { + raw_txn: signed_tx.raw_txn_bytes().clone().into(), + encoded: signed_tx.encoded().clone().into(), + authenticator: Some((*signed_tx.authenticator()).clone().into()), + json: signed_tx.to_json().to_string().into(), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/chains/tw_aptos/src/constants.rs b/rust/chains/tw_aptos/src/constants.rs new file mode 100644 index 00000000000..714c1a2f782 --- /dev/null +++ b/rust/chains/tw_aptos/src/constants.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub const GAS_UNIT_PRICE: u64 = 100; +pub const MAX_GAS_AMOUNT: u64 = 100_000_000; +pub const APTOS_SALT: &[u8] = b"APTOS::RawTransaction"; diff --git a/rust/chains/tw_aptos/src/entry.rs b/rust/chains/tw_aptos/src/entry.rs new file mode 100644 index 00000000000..f9a46c55049 --- /dev/null +++ b/rust/chains/tw_aptos/src/entry.rs @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::Address; +use crate::compiler::Compiler; +use crate::modules::transaction_util::AptosTransactionUtil; +use crate::signer::Signer; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::wallet_connector::NoWalletConnector; +use tw_coin_entry::prefix::NoPrefix; +use tw_keypair::tw::PublicKey; +use tw_proto::Aptos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct AptosEntry; + +impl CoinEntry for AptosEntry { + type AddressPrefix = NoPrefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + type WalletConnector = NoWalletConnector; + type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = AptosTransactionUtil; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + fn derive_address( + &self, + _coin: &dyn CoinContext, + public_key: PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + let public_key = public_key + .to_ed25519() + .ok_or(AddressError::PublicKeyTypeMismatch)?; + Address::with_ed25519_pubkey(public_key) + } + + #[inline] + fn sign(&self, _coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + Signer::sign_proto(input) + } + + #[inline] + fn preimage_hashes( + &self, + _coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + Compiler::preimage_hashes(input) + } + + #[inline] + fn compile( + &self, + _coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + Compiler::compile(input, signatures, public_keys) + } + + #[inline] + fn json_signer(&self) -> Option { + None + } + + #[inline] + fn message_signer(&self) -> Option { + None + } + + #[inline] + fn transaction_util(&self) -> Option { + Some(AptosTransactionUtil) + } +} diff --git a/rust/chains/tw_aptos/src/lib.rs b/rust/chains/tw_aptos/src/lib.rs new file mode 100644 index 00000000000..5388e03f4e7 --- /dev/null +++ b/rust/chains/tw_aptos/src/lib.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod address; +pub mod aptos_move_packages; +pub mod constants; +pub mod entry; +mod serde_helper; + +pub mod nft; + +pub mod compiler; +pub mod liquid_staking; +pub mod modules; +pub mod signer; +pub mod transaction; +pub mod transaction_builder; +pub mod transaction_payload; diff --git a/rust/chains/tw_aptos/src/liquid_staking.rs b/rust/chains/tw_aptos/src/liquid_staking.rs new file mode 100644 index 00000000000..2ce311e4350 --- /dev/null +++ b/rust/chains/tw_aptos/src/liquid_staking.rs @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::from_account_error; +use crate::transaction_payload::{EntryFunction, TransactionPayload}; +use move_core_types::{account_address::AccountAddress, ident_str, language_storage::ModuleId}; +use serde_json::json; +use std::str::FromStr; +use tw_coin_entry::error::prelude::*; +use tw_encoding::bcs; +use tw_proto::{ + Aptos::Proto::mod_LiquidStaking::OneOfliquid_stake_transaction_payload, + Aptos::Proto::{LiquidStaking, TortugaClaim, TortugaStake, TortugaUnstake}, +}; + +pub fn tortuga_stake( + smart_contract_address: AccountAddress, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + smart_contract_address, + ident_str!("stake_router").to_owned(), + ), + ident_str!("stake").to_owned(), + vec![], + vec![bcs::encode(&amount)?], + json!([amount.to_string()]), + ))) +} + +pub fn tortuga_unstake( + smart_contract_address: AccountAddress, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + smart_contract_address, + ident_str!("stake_router").to_owned(), + ), + ident_str!("unstake").to_owned(), + vec![], + vec![bcs::encode(&amount)?], + json!([amount.to_string()]), + ))) +} + +pub fn tortuga_claim( + smart_contract_address: AccountAddress, + idx: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + smart_contract_address, + ident_str!("stake_router").to_owned(), + ), + ident_str!("claim").to_owned(), + vec![], + vec![bcs::encode(&idx)?], + json!([idx.to_string()]), + ))) +} + +pub struct Stake { + pub amount: u64, + pub smart_contract_address: AccountAddress, +} + +pub struct Unstake { + pub amount: u64, + pub smart_contract_address: AccountAddress, +} + +pub struct Claim { + pub idx: u64, + pub smart_contract_address: AccountAddress, +} + +pub enum LiquidStakingOperation { + Stake(Stake), + Unstake(Unstake), + Claim(Claim), +} + +impl TryFrom> for LiquidStakingOperation { + type Error = SigningError; + + fn try_from(value: LiquidStaking) -> SigningResult { + match value.liquid_stake_transaction_payload { + OneOfliquid_stake_transaction_payload::stake(stake_msg) => { + let smart_contract_address = + AccountAddress::from_str(&value.smart_contract_address) + .map_err(from_account_error) + .into_tw() + .context("Invalid Smart Contract address")?; + Ok(LiquidStakingOperation::Stake(Stake { + amount: stake_msg.amount, + smart_contract_address, + })) + }, + OneOfliquid_stake_transaction_payload::unstake(unstake_msg) => { + let smart_contract_address = + AccountAddress::from_str(&value.smart_contract_address) + .map_err(from_account_error) + .into_tw() + .context("Invalid Smart Contract address")?; + Ok(LiquidStakingOperation::Unstake(Unstake { + amount: unstake_msg.amount, + smart_contract_address, + })) + }, + OneOfliquid_stake_transaction_payload::claim(claim) => { + let smart_contract_address = + AccountAddress::from_str(&value.smart_contract_address) + .map_err(from_account_error) + .into_tw() + .context("Invalid Smart Contract address")?; + Ok(LiquidStakingOperation::Claim(Claim { + idx: claim.idx, + smart_contract_address, + })) + }, + OneOfliquid_stake_transaction_payload::None => { + SigningError::err(SigningErrorType::Error_invalid_params) + }, + } + } +} + +impl From for LiquidStaking<'_> { + fn from(value: LiquidStakingOperation) -> Self { + match value { + LiquidStakingOperation::Stake(stake) => LiquidStaking { + smart_contract_address: stake.smart_contract_address.to_hex_literal().into(), + liquid_stake_transaction_payload: OneOfliquid_stake_transaction_payload::stake( + TortugaStake { + amount: stake.amount, + }, + ), + }, + LiquidStakingOperation::Unstake(unstake) => LiquidStaking { + smart_contract_address: unstake.smart_contract_address.to_hex_literal().into(), + liquid_stake_transaction_payload: OneOfliquid_stake_transaction_payload::unstake( + TortugaUnstake { + amount: unstake.amount, + }, + ), + }, + LiquidStakingOperation::Claim(claim) => LiquidStaking { + smart_contract_address: claim.smart_contract_address.to_hex_literal().into(), + liquid_stake_transaction_payload: OneOfliquid_stake_transaction_payload::claim( + TortugaClaim { idx: claim.idx }, + ), + }, + } + } +} diff --git a/rust/chains/tw_aptos/src/modules/mod.rs b/rust/chains/tw_aptos/src/modules/mod.rs new file mode 100644 index 00000000000..c083bb0102e --- /dev/null +++ b/rust/chains/tw_aptos/src/modules/mod.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod transaction_util; diff --git a/rust/chains/tw_aptos/src/modules/transaction_util.rs b/rust/chains/tw_aptos/src/modules/transaction_util.rs new file mode 100644 index 00000000000..6b7034477f8 --- /dev/null +++ b/rust/chains/tw_aptos/src/modules/transaction_util.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::transaction_util::TransactionUtil; +use tw_encoding::hex; +use tw_hash::sha3::sha3_256; + +pub struct AptosTransactionUtil; + +impl TransactionUtil for AptosTransactionUtil { + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + Self::calc_tx_hash_impl(coin, encoded_tx) + } +} + +impl AptosTransactionUtil { + fn calc_tx_hash_impl(_coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + let txn_bytes = hex::decode(encoded_tx).map_err(|_| SigningErrorType::Error_input_parse)?; + + // See: https://github.com/aptos-labs/aptos-ts-sdk/blob/f54cac824a41e41dea09c7a6916858a8604dc901/src/api/transaction.ts#L118 + let prefix = sha3_256("APTOS::Transaction".as_bytes()); + + let mut hash_message = Vec::new(); + hash_message.extend_from_slice(&prefix); + // 0 is the index of the enum `Transaction`, see: https://github.com/aptos-labs/aptos-core/blob/6a130c1cca274a5cfdb4a65b441cd5fe61b6c15b/types/src/transaction/mod.rs#L1939 + hash_message.push(0); + hash_message.extend_from_slice(&txn_bytes); + + let tx_hash = sha3_256(&hash_message); + Ok(hex::encode(tx_hash, true)) + } +} diff --git a/rust/chains/tw_aptos/src/nft.rs b/rust/chains/tw_aptos/src/nft.rs new file mode 100644 index 00000000000..172308b4ee2 --- /dev/null +++ b/rust/chains/tw_aptos/src/nft.rs @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::from_account_error; +use move_core_types::account_address::AccountAddress; +use std::str::FromStr; +use tw_coin_entry::error::prelude::*; +use tw_proto::Aptos::Proto::mod_NftMessage::OneOfnft_transaction_payload; +use tw_proto::Aptos::Proto::{CancelOfferNftMessage, ClaimNftMessage, NftMessage, OfferNftMessage}; + +pub struct Offer { + pub receiver: AccountAddress, + pub creator: AccountAddress, + pub collection: Vec, + pub name: Vec, + pub property_version: u64, + pub amount: u64, +} + +pub struct Claim { + pub sender: AccountAddress, + pub creator: AccountAddress, + pub collection: Vec, + pub name: Vec, + pub property_version: u64, +} + +pub enum NftOperation { + Claim(Claim), + Offer(Offer), + Cancel(Offer), +} + +impl TryFrom> for NftOperation { + type Error = SigningError; + + fn try_from(value: NftMessage) -> SigningResult { + match value.nft_transaction_payload { + OneOfnft_transaction_payload::offer_nft(msg) => { + Ok(NftOperation::Offer(Offer::try_from(msg)?)) + }, + OneOfnft_transaction_payload::cancel_offer_nft(msg) => { + Ok(NftOperation::Cancel(Offer::try_from(msg)?)) + }, + OneOfnft_transaction_payload::claim_nft(msg) => { + Ok(NftOperation::Claim(Claim::try_from(msg)?)) + }, + OneOfnft_transaction_payload::None => { + SigningError::err(SigningErrorType::Error_invalid_params) + .context("No transaction payload provided") + }, + } + } +} + +impl From for NftMessage<'_> { + fn from(value: NftOperation) -> Self { + match value { + NftOperation::Claim(claim) => NftMessage { + nft_transaction_payload: OneOfnft_transaction_payload::claim_nft(claim.into()), + }, + NftOperation::Offer(offer) => NftMessage { + nft_transaction_payload: OneOfnft_transaction_payload::offer_nft(offer.into()), + }, + NftOperation::Cancel(cancel) => NftMessage { + nft_transaction_payload: OneOfnft_transaction_payload::cancel_offer_nft( + cancel.into(), + ), + }, + } + } +} + +impl TryFrom> for Offer { + type Error = SigningError; + + fn try_from(value: OfferNftMessage) -> SigningResult { + Ok(Offer { + receiver: AccountAddress::from_str(&value.receiver).map_err(from_account_error)?, + creator: AccountAddress::from_str(&value.creator).map_err(from_account_error)?, + collection: value.collectionName.as_bytes().to_vec(), + name: value.name.as_bytes().to_vec(), + property_version: value.property_version, + amount: value.amount, + }) + } +} + +impl From for OfferNftMessage<'_> { + fn from(value: Offer) -> Self { + OfferNftMessage { + receiver: value.receiver.to_hex_literal().into(), + creator: value.creator.to_hex_literal().into(), + collectionName: String::from_utf8_lossy(value.collection.as_slice()) + .to_string() + .into(), + name: String::from_utf8_lossy(&value.name).to_string().into(), + property_version: value.property_version, + amount: value.amount, + } + } +} + +impl TryFrom> for Offer { + type Error = SigningError; + + fn try_from(value: CancelOfferNftMessage) -> SigningResult { + Ok(Offer { + receiver: AccountAddress::from_str(&value.receiver) + .map_err(from_account_error) + .into_tw() + .context("Invalid receiver address")?, + creator: AccountAddress::from_str(&value.creator) + .map_err(from_account_error) + .into_tw() + .context("Invalid creator address")?, + collection: value.collectionName.as_bytes().to_vec(), + name: value.name.as_bytes().to_vec(), + property_version: value.property_version, + amount: 0, + }) + } +} + +impl From for CancelOfferNftMessage<'_> { + fn from(value: Offer) -> Self { + CancelOfferNftMessage { + receiver: value.receiver.to_hex_literal().into(), + creator: value.creator.to_hex_literal().into(), + collectionName: String::from_utf8_lossy(value.collection.as_slice()) + .to_string() + .into(), + name: String::from_utf8_lossy(value.name.as_slice()) + .to_string() + .into(), + property_version: value.property_version, + } + } +} + +impl TryFrom> for Claim { + type Error = SigningError; + + fn try_from(value: ClaimNftMessage) -> SigningResult { + Ok(Claim { + sender: AccountAddress::from_str(&value.sender) + .map_err(from_account_error) + .into_tw() + .context("Invalid sender address")?, + creator: AccountAddress::from_str(&value.creator) + .map_err(from_account_error) + .into_tw() + .context("Invalid creator address")?, + collection: value.collectionName.as_bytes().to_vec(), + name: value.name.as_bytes().to_vec(), + property_version: value.property_version, + }) + } +} + +impl From for ClaimNftMessage<'_> { + fn from(value: Claim) -> Self { + ClaimNftMessage { + sender: value.sender.to_hex_literal().into(), + creator: value.creator.to_hex_literal().into(), + collectionName: String::from_utf8_lossy(value.collection.as_slice()) + .to_string() + .into(), + name: String::from_utf8_lossy(value.name.as_slice()) + .to_string() + .into(), + property_version: value.property_version, + } + } +} diff --git a/rust/chains/tw_aptos/src/serde_helper/mod.rs b/rust/chains/tw_aptos/src/serde_helper/mod.rs new file mode 100644 index 00000000000..876f018225e --- /dev/null +++ b/rust/chains/tw_aptos/src/serde_helper/mod.rs @@ -0,0 +1,5 @@ +// Copyright © Aptos Foundation +// Parts of the project are originally copyright © Meta Platforms, Inc. +// SPDX-License-Identifier: Apache-2.0 + +pub mod vec_bytes; diff --git a/rust/chains/tw_aptos/src/serde_helper/vec_bytes.rs b/rust/chains/tw_aptos/src/serde_helper/vec_bytes.rs new file mode 100644 index 00000000000..f57311d19dd --- /dev/null +++ b/rust/chains/tw_aptos/src/serde_helper/vec_bytes.rs @@ -0,0 +1,30 @@ +// Copyright © Aptos Foundation +// Parts of the project are originally copyright © Meta Platforms, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use serde::{ + de::Deserializer, + ser::{SerializeSeq, Serializer}, + Deserialize, +}; + +pub fn serialize(data: &[Vec], serializer: S) -> Result +where + S: Serializer, +{ + let mut seq = serializer.serialize_seq(Some(data.len()))?; + for e in data { + seq.serialize_element(serde_bytes::Bytes::new(e.as_slice()))?; + } + seq.end() +} + +pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + Ok(>::deserialize(deserializer)? + .into_iter() + .map(serde_bytes::ByteBuf::into_vec) + .collect()) +} diff --git a/rust/chains/tw_aptos/src/signer.rs b/rust/chains/tw_aptos/src/signer.rs new file mode 100644 index 00000000000..8937e821081 --- /dev/null +++ b/rust/chains/tw_aptos/src/signer.rs @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::Address; +use crate::transaction_builder; +use std::str::FromStr; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_keypair::ed25519; +use tw_proto::Aptos::Proto; + +pub struct Signer; + +impl Signer { + #[inline] + pub fn sign_proto(input: Proto::SigningInput<'_>) -> Proto::SigningOutput<'static> { + Self::sign_proto_impl(input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_proto_impl( + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let key_pair = ed25519::sha512::KeyPair::try_from(input.private_key.as_ref())?; + let builder = transaction_builder::TransactionFactory::new_from_protobuf(input.clone())?; + let sender = Address::from_str(&input.sender) + .into_tw() + .context("Invalid sender address")?; + let signed_tx = builder + .sender(sender.inner()) + .sequence_number(input.sequence_number as u64) + .build()? + .sign(key_pair)?; + Ok(Proto::SigningOutput { + raw_txn: signed_tx.raw_txn_bytes().clone().into(), + encoded: signed_tx.encoded().clone().into(), + authenticator: Some((*signed_tx.authenticator()).clone().into()), + json: signed_tx.to_json().to_string().into(), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/chains/tw_aptos/src/transaction.rs b/rust/chains/tw_aptos/src/transaction.rs new file mode 100644 index 00000000000..f4ca4a2c151 --- /dev/null +++ b/rust/chains/tw_aptos/src/transaction.rs @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::constants::APTOS_SALT; +use crate::transaction_payload::TransactionPayload; +use move_core_types::account_address::AccountAddress; +use serde::Serialize; +use serde_json::{json, Value}; +use std::borrow::Cow; +use tw_coin_entry::error::prelude::*; +use tw_encoding::hex::encode; +use tw_encoding::{bcs, EncodingResult}; +use tw_keypair::ed25519::sha512::KeyPair; +use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait}; +use tw_memory::Data; +use tw_proto::Aptos::Proto; + +#[derive(Clone, Serialize)] +pub enum TransactionAuthenticator { + /// Single Ed25519 signature + Ed25519 { + public_key: Vec, + signature: Vec, + }, +} + +impl From for Proto::TransactionAuthenticator<'_> { + fn from(from: TransactionAuthenticator) -> Self { + Proto::TransactionAuthenticator { + signature: Cow::from(from.get_signature()), + public_key: Cow::from(from.get_public_key()), + } + } +} + +impl TransactionAuthenticator { + pub fn get_signature(&self) -> Vec { + match self { + TransactionAuthenticator::Ed25519 { + public_key: _public_key, + signature, + } => signature.clone(), + } + } + + pub fn get_public_key(&self) -> Vec { + match self { + TransactionAuthenticator::Ed25519 { + public_key, + signature: _signature, + } => public_key.clone(), + } + } + + pub fn to_json(&self) -> Value { + match self { + TransactionAuthenticator::Ed25519 { + public_key, + signature, + } => { + json!({"public_key": encode(public_key, true), + "signature": encode(signature, true), + "type": "ed25519_signature"}) + }, + } + } +} + +/// RawTransaction is the portion of a transaction that a client signs. +#[derive(Clone, Serialize)] +pub struct RawTransaction { + /// Sender's address. + sender: AccountAddress, + + /// Sequence number of this transaction. This must match the sequence number + /// stored in the sender's account at the time the transaction executes. + sequence_number: u64, + + /// The transaction payload, e.g., a script to execute. + payload: TransactionPayload, + + /// Maximal total gas to spend for this transaction. + max_gas_amount: u64, + + /// Price to be paid per gas unit. + gas_unit_price: u64, + + /// Expiration timestamp for this transaction, represented + /// as seconds from the Unix Epoch. If the current blockchain timestamp + /// is greater than or equal to this time, then the transaction has + /// expired and will be discarded. This can be set to a large value far + /// in the future to indicate that a transaction does not expire. + expiration_timestamp_secs: u64, + + /// Chain ID of the Aptos network this transaction is intended for. + chain_id: u8, +} + +impl RawTransaction { + /// Create a new `RawTransaction` with a payload. + /// + /// It can be either to publish a module, to execute a script, or to issue a writeset + /// transaction. + pub fn new( + sender: AccountAddress, + sequence_number: u64, + payload: TransactionPayload, + max_gas_amount: u64, + gas_unit_price: u64, + expiration_timestamp_secs: u64, + chain_id: u8, + ) -> Self { + RawTransaction { + sender, + sequence_number, + payload, + max_gas_amount, + gas_unit_price, + expiration_timestamp_secs, + chain_id, + } + } + + /// Create a new `RawTransaction` with an entry function + fn serialize(&self) -> EncodingResult { + bcs::encode(&self) + } + + fn msg_to_sign(&self) -> SigningResult { + let serialized = self + .serialize() + .into_tw() + .context("Error serializing RawTransaction")?; + let mut preimage = tw_hash::sha3::sha3_256(APTOS_SALT); + preimage.extend_from_slice(serialized.as_slice()); + Ok(preimage) + } + + pub fn pre_image(&self) -> SigningResult> { + self.msg_to_sign() + } + + pub fn compile( + &self, + signature: Vec, + public_key: Vec, + ) -> SigningResult { + let serialized = self.serialize()?; + let auth = TransactionAuthenticator::Ed25519 { + public_key, + signature, + }; + let mut encoded = serialized.clone(); + encoded.extend_from_slice(bcs::encode(&auth)?.as_slice()); + Ok(SignedTransaction { + raw_txn: self.clone(), + authenticator: auth, + raw_txn_bytes: serialized.to_vec(), + encoded, + }) + } + + pub fn sign(self, key_pair: KeyPair) -> SigningResult { + let to_sign = self.pre_image()?; + let signature = key_pair.private().sign(to_sign)?.to_bytes().into_vec(); + let pubkey = key_pair.public().as_slice().to_vec(); + self.compile(signature, pubkey) + } + + pub fn to_json(&self) -> Value { + json!({ + "expiration_timestamp_secs": self.expiration_timestamp_secs.to_string(), + "gas_unit_price": self.gas_unit_price.to_string(), + "max_gas_amount": self.max_gas_amount.to_string(), + "payload": self.payload.to_json(), + "sender": self.sender.to_hex_literal(), + "sequence_number": self.sequence_number.to_string() + }) + } +} + +/// A transaction that has been signed. +/// +/// A `SignedTransaction` is a single transaction that can be atomically executed. Clients submit +/// these to validator nodes, and the validator and executor submits these to the VM. +/// +#[derive(Clone, Serialize)] +pub struct SignedTransaction { + /// The raw transaction + raw_txn: RawTransaction, + + /// Public key and signature to authenticate + authenticator: TransactionAuthenticator, + + #[serde(skip_serializing)] + /// Raw txs bytes + raw_txn_bytes: Vec, + + #[serde(skip_serializing)] + /// Encoded bytes to be broadcast + encoded: Vec, +} + +impl SignedTransaction { + pub fn authenticator(&self) -> &TransactionAuthenticator { + &self.authenticator + } + pub fn raw_txn_bytes(&self) -> &Vec { + &self.raw_txn_bytes + } + pub fn encoded(&self) -> &Vec { + &self.encoded + } + + pub fn to_json(&self) -> Value { + let mut json_value = self.raw_txn.to_json(); + json_value["signature"] = self.authenticator.to_json(); + json_value + } +} diff --git a/rust/chains/tw_aptos/src/transaction_builder.rs b/rust/chains/tw_aptos/src/transaction_builder.rs new file mode 100644 index 00000000000..deae84c8008 --- /dev/null +++ b/rust/chains/tw_aptos/src/transaction_builder.rs @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::from_account_error; +use crate::aptos_move_packages::{ + aptos_account_create_account, aptos_account_transfer, aptos_account_transfer_coins, + coin_transfer, managed_coin_register, token_transfers_cancel_offer_script, + token_transfers_claim_script, token_transfers_offer_script, +}; +use crate::constants::{GAS_UNIT_PRICE, MAX_GAS_AMOUNT}; +use crate::liquid_staking::{ + tortuga_claim, tortuga_stake, tortuga_unstake, LiquidStakingOperation, +}; +use crate::nft::NftOperation; +use crate::transaction::RawTransaction; +use crate::transaction_payload::{ + convert_proto_struct_tag_to_type_tag, EntryFunction, TransactionPayload, +}; +use move_core_types::account_address::AccountAddress; +use move_core_types::language_storage::TypeTag; +use serde_json::Value; +use std::str::FromStr; +use tw_coin_entry::error::prelude::*; +use tw_proto::Aptos::Proto::mod_SigningInput::OneOftransaction_payload; +use tw_proto::Aptos::Proto::SigningInput; + +pub struct TransactionBuilder { + sender: Option, + sequence_number: Option, + payload: TransactionPayload, + max_gas_amount: u64, + gas_unit_price: u64, + expiration_timestamp_secs: u64, + chain_id: u8, +} + +impl TransactionBuilder { + pub fn sender(mut self, sender: AccountAddress) -> Self { + self.sender = Some(sender); + self + } + + pub fn sequence_number(mut self, sequence_number: u64) -> Self { + self.sequence_number = Some(sequence_number); + self + } + + pub fn build(self) -> SigningResult { + let sender = self + .sender + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("Invalid sender address")?; + let sequence_number = self + .sequence_number + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("Invalid sequence number")?; + Ok(RawTransaction::new( + sender, + sequence_number, + self.payload, + self.max_gas_amount, + self.gas_unit_price, + self.expiration_timestamp_secs, + self.chain_id, + )) + } +} + +#[derive(Clone, Debug)] +pub struct TransactionFactory { + max_gas_amount: u64, + gas_unit_price: u64, + transaction_expiration_time: u64, + chain_id: u8, +} + +impl TransactionFactory { + pub fn new(chain_id: u8) -> Self { + Self { + max_gas_amount: MAX_GAS_AMOUNT, + gas_unit_price: GAS_UNIT_PRICE, + transaction_expiration_time: 30, + chain_id, + } + } + + pub fn new_from_protobuf(input: SigningInput) -> SigningResult { + let factory = TransactionFactory::new(input.chain_id as u8) + .with_gas_unit_price(input.gas_unit_price) + .with_max_gas_amount(input.max_gas_amount) + .with_transaction_expiration_time(input.expiration_timestamp_secs); + match input.transaction_payload { + OneOftransaction_payload::transfer(transfer) => factory + .implicitly_create_user_account_and_transfer( + AccountAddress::from_str(&transfer.to) + .map_err(from_account_error) + .into_tw() + .context("Invalid destination address")?, + transfer.amount, + ), + OneOftransaction_payload::token_transfer(token_transfer) => { + let func = token_transfer + .function + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("'TokenTransferMessage::function' is not set")?; + factory.coins_transfer( + AccountAddress::from_str(&token_transfer.to) + .map_err(from_account_error) + .into_tw() + .context("Invalid destination address")?, + token_transfer.amount, + convert_proto_struct_tag_to_type_tag(func)?, + ) + }, + OneOftransaction_payload::create_account(create_account) => { + let address = AccountAddress::from_str(&create_account.auth_key) + .map_err(from_account_error) + .into_tw() + .context("Invalid 'auth_key' address")?; + factory.create_user_account(address) + }, + OneOftransaction_payload::nft_message(nft_message) => { + factory.nft_ops(NftOperation::try_from(nft_message)?) + }, + OneOftransaction_payload::register_token(register_token) => { + let function = register_token + .function + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("'ManagedTokensRegisterMessage::function' is not set")?; + Ok(factory.register_token(convert_proto_struct_tag_to_type_tag(function)?)) + }, + OneOftransaction_payload::liquid_staking_message(msg) => { + factory.liquid_staking_ops(LiquidStakingOperation::try_from(msg)?) + }, + OneOftransaction_payload::token_transfer_coins(token_transfer_coins) => { + let func = token_transfer_coins + .function + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("'TokenTransferCoinsMessage::function' is not set")?; + factory.implicitly_create_user_and_coins_transfer( + AccountAddress::from_str(&token_transfer_coins.to) + .map_err(from_account_error) + .into_tw() + .context("Invalid destination address")?, + token_transfer_coins.amount, + convert_proto_struct_tag_to_type_tag(func)?, + ) + }, + OneOftransaction_payload::None => { + let is_blind_sign = !input.any_encoded.is_empty(); + let v = serde_json::from_str::(&input.any_encoded) + .into_tw() + .context("Error decoding 'SigningInput::any_encoded' as JSON")?; + if is_blind_sign { + let entry_function = EntryFunction::try_from(v)?; + Ok(factory.payload(TransactionPayload::EntryFunction(entry_function))) + } else { + SigningError::err(SigningErrorType::Error_input_parse) + } + }, + } + } + + pub fn with_max_gas_amount(mut self, max_gas_amount: u64) -> Self { + self.max_gas_amount = max_gas_amount; + self + } + + pub fn with_gas_unit_price(mut self, gas_unit_price: u64) -> Self { + self.gas_unit_price = gas_unit_price; + self + } + + pub fn with_transaction_expiration_time(mut self, transaction_expiration_time: u64) -> Self { + self.transaction_expiration_time = transaction_expiration_time; + self + } + + pub fn payload(&self, payload: TransactionPayload) -> TransactionBuilder { + self.transaction_builder(payload) + } + + pub fn create_user_account(&self, to: AccountAddress) -> SigningResult { + Ok(self.payload(aptos_account_create_account(to)?)) + } + + pub fn register_token(&self, coin_type: TypeTag) -> TransactionBuilder { + self.payload(managed_coin_register(coin_type)) + } + + pub fn nft_ops(&self, operation: NftOperation) -> SigningResult { + match operation { + NftOperation::Claim(claim) => Ok(self.payload(token_transfers_claim_script( + claim.sender, + claim.creator, + claim.collection, + claim.name, + claim.property_version, + )?)), + NftOperation::Cancel(offer) => Ok(self.payload(token_transfers_cancel_offer_script( + offer.receiver, + offer.creator, + offer.collection, + offer.name, + offer.property_version, + )?)), + NftOperation::Offer(offer) => Ok(self.payload(token_transfers_offer_script( + offer.receiver, + offer.creator, + offer.collection, + offer.name, + offer.property_version, + offer.amount, + )?)), + } + } + + pub fn liquid_staking_ops( + &self, + operation: LiquidStakingOperation, + ) -> SigningResult { + match operation { + LiquidStakingOperation::Stake(stake) => { + Ok(self.payload(tortuga_stake(stake.smart_contract_address, stake.amount)?)) + }, + LiquidStakingOperation::Unstake(unstake) => Ok(self.payload(tortuga_unstake( + unstake.smart_contract_address, + unstake.amount, + )?)), + LiquidStakingOperation::Claim(claim) => { + Ok(self.payload(tortuga_claim(claim.smart_contract_address, claim.idx)?)) + }, + } + } + + pub fn implicitly_create_user_account_and_transfer( + &self, + to: AccountAddress, + amount: u64, + ) -> SigningResult { + Ok(self.payload(aptos_account_transfer(to, amount)?)) + } + + pub fn coins_transfer( + &self, + to: AccountAddress, + amount: u64, + coin_type: TypeTag, + ) -> SigningResult { + Ok(self.payload(coin_transfer(coin_type, to, amount)?)) + } + + pub fn implicitly_create_user_and_coins_transfer( + &self, + to: AccountAddress, + amount: u64, + coin_type: TypeTag, + ) -> SigningResult { + Ok(self.payload(aptos_account_transfer_coins(coin_type, to, amount)?)) + } + + fn transaction_builder(&self, payload: TransactionPayload) -> TransactionBuilder { + TransactionBuilder { + sender: None, + sequence_number: None, + payload, + max_gas_amount: self.max_gas_amount, + gas_unit_price: self.gas_unit_price, + expiration_timestamp_secs: self.expiration_timestamp(), + chain_id: self.chain_id, + } + } + + fn expiration_timestamp(&self) -> u64 { + self.transaction_expiration_time + } +} diff --git a/rust/chains/tw_aptos/src/transaction_payload.rs b/rust/chains/tw_aptos/src/transaction_payload.rs new file mode 100644 index 00000000000..aa1c74d02da --- /dev/null +++ b/rust/chains/tw_aptos/src/transaction_payload.rs @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::serde_helper::vec_bytes; +use move_core_types::identifier::Identifier; +use move_core_types::language_storage::{ModuleId, StructTag, TypeTag}; +use move_core_types::parser::parse_transaction_argument; +use move_core_types::transaction_argument::TransactionArgument; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::default::Default; +use std::str::FromStr; +use tw_coin_entry::error::prelude::*; +use tw_encoding::{bcs, EncodingError, EncodingResult}; +use tw_memory::Data; +use tw_proto::Aptos; + +pub type EntryFunctionResult = Result; + +#[derive(Debug)] +pub enum EntryFunctionError { + MissingFunctionName, + InvalidFunctionName, + MissingArguments, + InvalidArguments, + EncodingError, + MissingTypeArguments, + InvalidTypeArguments, +} + +impl From for EntryFunctionError { + fn from(_error: EncodingError) -> Self { + EntryFunctionError::EncodingError + } +} + +impl From for SigningError { + fn from(e: EntryFunctionError) -> Self { + SigningError::new(SigningErrorType::Error_invalid_params) + .context(format!("Error decoding EntryFunction: {e:?}")) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct EntryFunction { + module: ModuleId, + function: Identifier, + ty_args: Vec, + #[serde(with = "vec_bytes")] + args: Vec>, + #[serde(skip_serializing)] + json_args: Value, +} + +impl TryFrom for EntryFunction { + type Error = EntryFunctionError; + + fn try_from(value: Value) -> EntryFunctionResult { + let function_str = value["function"] + .as_str() + .ok_or(EntryFunctionError::MissingFunctionName)?; + let tag = StructTag::from_str(function_str) + .map_err(|_| EntryFunctionError::InvalidFunctionName)?; + + let args = value["arguments"] + .as_array() + .ok_or(EntryFunctionError::MissingArguments)? + .iter() + .map(|element| { + let arg_str = element.to_string(); + let arg = parse_transaction_argument( + arg_str.trim_start_matches('"').trim_end_matches('"'), + ) + .map_err(|_| EntryFunctionError::InvalidArguments)?; + serialize_argument(&arg).map_err(EntryFunctionError::from) + }) + .collect::>>()?; + + let ty_args = value["type_arguments"] + .as_array() + .ok_or(EntryFunctionError::MissingTypeArguments)? + .iter() + .map(|element| { + let ty_arg_str = element + .as_str() + .ok_or(EntryFunctionError::InvalidTypeArguments)?; + TypeTag::from_str(ty_arg_str).map_err(|_| EntryFunctionError::InvalidTypeArguments) + }) + .collect::>>()?; + + Ok(EntryFunction { + module: tag.module_id(), + function: tag.name, + ty_args, + args, + json_args: value["arguments"].clone(), + }) + } +} + +fn serialize_argument(arg: &TransactionArgument) -> EncodingResult { + match arg { + TransactionArgument::U8(v) => bcs::encode(v), + TransactionArgument::U16(v) => bcs::encode(v), + TransactionArgument::U32(v) => bcs::encode(v), + TransactionArgument::U64(v) => bcs::encode(v), + TransactionArgument::U128(v) => bcs::encode(v), + TransactionArgument::U256(v) => bcs::encode(v), + TransactionArgument::U8Vector(v) => bcs::encode(v), + TransactionArgument::Bool(v) => bcs::encode(v), + TransactionArgument::Address(v) => { + let serialized_v = bcs::encode(v)?; + bcs::encode(&serialized_v) + }, + } +} + +pub fn convert_proto_struct_tag_to_type_tag( + struct_tag: Aptos::Proto::StructTag, +) -> SigningResult { + TypeTag::from_str(&format!( + "{}::{}::{}", + struct_tag.account_address, struct_tag.module, struct_tag.name + )) + .tw_err(|_| SigningErrorType::Error_invalid_params) +} + +pub fn convert_type_tag_to_struct_tag(type_tag: TypeTag) -> Aptos::Proto::StructTag<'static> { + if let TypeTag::Struct(st) = type_tag { + Aptos::Proto::StructTag { + account_address: st.address.to_hex_literal().into(), + module: st.module.to_string().into(), + name: st.name.to_string().into(), + } + } else { + Aptos::Proto::StructTag::default() + } +} + +impl EntryFunction { + fn to_json(&self) -> Value { + // Create a JSON array from the `ty_args` field by filtering and mapping + // the items that match `TypeTag::Struct` to their string representation. + let type_arguments: Value = self + .ty_args + .iter() + .map(|item| Some(json!(item.to_string()))) + .collect(); + + // Construct the final JSON value + json!({ + "type": "entry_function_payload", + "function": format!("{}::{}", self.module.short_str_lossless(), self.function.clone().into_string()), + "arguments": self.json_args, + "type_arguments": type_arguments + }) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub enum TransactionPayload { + Script, + ModuleBundle, + /// A transaction that executes an existing entry function published on-chain. + EntryFunction(EntryFunction), +} + +impl TransactionPayload { + pub fn to_json(&self) -> Value { + match self { + TransactionPayload::Script => Value::default(), + TransactionPayload::ModuleBundle => Value::default(), + TransactionPayload::EntryFunction(entry) => entry.to_json(), + } + } +} + +impl EntryFunction { + pub fn new( + module: ModuleId, + function: Identifier, + ty_args: Vec, + args: Vec>, + json_args: Value, + ) -> Self { + EntryFunction { + module, + function, + ty_args, + args, + json_args, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::address::Address; + use move_core_types::account_address::AccountAddress; + use move_core_types::identifier::Identifier; + use move_core_types::language_storage::{ModuleId, TypeTag}; + use serde_json::{json, Value}; + use std::str::FromStr; + use tw_encoding::hex; + + #[test] + fn test_payload_from_json() { + let payload_value: Value = json!({ + "arguments": ["0xc95db29a67a848940829b3df6119b5e67b788ff0248676e4484c7c6f29c0f5e6"], + "function": "0xc23c3b70956ce8d88fb18ad9ed3b463fe873cb045db3f6d2e2fb15b9aab71d50::IFO::release", + "type": "entry_function_payload", + "type_arguments": [ + "0x48e0e3958d42b8d452c9199d4a221d0d1b15d14655787453dbe77208ced90517::coins::BUSD", + "0x48e0e3958d42b8d452c9199d4a221d0d1b15d14655787453dbe77208ced90517::coins::DAI", + "0x9936836587ca33240d3d3f91844651b16cb07802faf5e34514ed6f78580deb0a::uints::U1" + ] + }); + + let v = EntryFunction::try_from(payload_value.clone()).unwrap(); + assert_eq!(payload_value, v.to_json()); + } + + #[test] + fn test_payload_from_json_with_arg_non_str() { + let payload_value: Value = json!({ + "type":"entry_function_payload", + "function":"0xd11107bdf0d6d7040c6c0bfbdecb6545191fdf13e8d8d259952f53e1713f61b5::ditto_staking::stake_aptos", + "type_arguments":[], + "arguments": [1000000] + }); + + let v = EntryFunction::try_from(payload_value.clone()).unwrap(); + assert_eq!(payload_value, v.to_json()); + } + + #[test] + fn test_basic_payload() { + let addr = + Address::from_str("0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b") + .unwrap() + .inner(); + let amount: i64 = 1000; + let args = vec![bcs::encode(&addr).unwrap(), bcs::encode(&amount).unwrap()]; + let module = ModuleId::new(AccountAddress::ONE, Identifier::from_str("coin").unwrap()); + let function = Identifier::from_str("transfer").unwrap(); + let type_tag = vec![TypeTag::from_str("0x1::aptos_coin::AptosCoin").unwrap()]; + let entry = EntryFunction::new( + module, + function, + type_tag, + args, + json!([addr.to_hex_literal(), amount.to_string()]), + ); + let tp = TransactionPayload::EntryFunction(entry); + let serialized = bcs::encode(&tp).unwrap(); + assert_eq!(hex::encode(serialized, false), "02000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e000220eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b08e803000000000000"); + let payload_value: Value = json!({ + "function": "0x1::coin::transfer", + "type": "entry_function_payload", + "arguments": ["0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", "1000"], + "type_arguments": ["0x1::aptos_coin::AptosCoin"] + }); + assert_eq!(tp.to_json(), payload_value); + } +} diff --git a/rust/chains/tw_aptos/tests/signer.rs b/rust/chains/tw_aptos/tests/signer.rs new file mode 100644 index 00000000000..e69c6894839 --- /dev/null +++ b/rust/chains/tw_aptos/tests/signer.rs @@ -0,0 +1,871 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use move_core_types::account_address::AccountAddress; +use move_core_types::language_storage::TypeTag; +use std::str::FromStr; +use tw_aptos::liquid_staking; +use tw_aptos::liquid_staking::{LiquidStakingOperation, Stake, Unstake}; +use tw_aptos::nft::{Claim, NftOperation, Offer}; +use tw_aptos::signer::Signer; +use tw_aptos::transaction_payload::convert_type_tag_to_struct_tag; +use tw_coin_entry::error::prelude::*; +use tw_encoding::hex; +use tw_proto::Aptos::Proto; +use tw_proto::Aptos::Proto::{SigningInput, SigningOutput}; + +pub struct AccountCreation { + to: String, +} + +pub struct Transfer { + to: String, + amount: u64, +} + +pub struct TokenTransfer { + transfer: Transfer, + tag: TypeTag, +} + +pub struct RegisterToken { + coin_type: TypeTag, +} + +pub enum OpsDetails { + RegisterToken(RegisterToken), + LiquidStakingOps(LiquidStakingOperation), + AccountCreation(AccountCreation), + Transfer(Transfer), + TokenTransfer(TokenTransfer), + ImplicitTokenTransfer(TokenTransfer), + NftOps(NftOperation), +} + +fn setup_proto_transaction<'a>( + sender: &'a str, + keypair_str: &'a str, + transaction_type: &'a str, + sequence_number: i64, + chain_id: u32, + max_gas_amount: u64, + timestamp: u64, + gas_unit_price: u64, + any_encoded: &'a str, + ops_details: Option, +) -> SigningInput<'a> { + let private = hex::decode(keypair_str).unwrap(); + + let payload: Proto::mod_SigningInput::OneOftransaction_payload = match transaction_type { + "transfer" => { + if let OpsDetails::Transfer(transfer) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::transfer( + Proto::TransferMessage { + to: transfer.to.into(), + amount: transfer.amount, + }, + ) + } else { + panic!("Unsupported arguments") + } + }, + "create_account" => { + if let OpsDetails::AccountCreation(account) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::create_account( + Proto::CreateAccountMessage { + auth_key: account.to.into(), + }, + ) + } else { + panic!("Unsupported arguments") + } + }, + "coin_transfer" => { + if let OpsDetails::TokenTransfer(token_transfer) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::token_transfer( + Proto::TokenTransferMessage { + to: token_transfer.transfer.to.into(), + amount: token_transfer.transfer.amount, + function: Some(convert_type_tag_to_struct_tag(token_transfer.tag)), + }, + ) + } else { + panic!("Unsupported arguments") + } + }, + "implicit_coin_transfer" => { + if let OpsDetails::ImplicitTokenTransfer(token_transfer) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::token_transfer_coins( + Proto::TokenTransferCoinsMessage { + to: token_transfer.transfer.to.into(), + amount: token_transfer.transfer.amount, + function: Some(convert_type_tag_to_struct_tag(token_transfer.tag)), + }, + ) + } else { + panic!("Unsupported arguments") + } + }, + "nft_ops" => { + if let OpsDetails::NftOps(nft) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::nft_message(nft.into()) + } else { + panic!("Unsupported arguments") + } + }, + "register_token" => { + if let OpsDetails::RegisterToken(register_token) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::register_token( + Proto::ManagedTokensRegisterMessage { + function: Some(convert_type_tag_to_struct_tag(register_token.coin_type)), + }, + ) + } else { + panic!("Unsupported arguments") + } + }, + "liquid_staking_ops" => { + if let OpsDetails::LiquidStakingOps(liquid_staking_ops) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::liquid_staking_message( + liquid_staking_ops.into(), + ) + } else { + panic!("Unsupported arguments") + } + }, + "blind_sign_json" => Proto::mod_SigningInput::OneOftransaction_payload::None, + _ => Proto::mod_SigningInput::OneOftransaction_payload::None, + }; + + let input = SigningInput { + chain_id, + sender: sender.into(), + sequence_number, + max_gas_amount, + gas_unit_price, + expiration_timestamp_secs: timestamp, + private_key: private.into(), + any_encoded: any_encoded.into(), + transaction_payload: payload, + }; + + input +} + +fn test_tx_result( + output: SigningOutput, + expected_raw_txn_bytes_str: &str, + expected_signature_str: &str, + expected_encoded_txn_str: &str, + json_literal: &str, +) { + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + assert_eq!( + hex::encode(output.raw_txn.to_vec(), false), + expected_raw_txn_bytes_str + ); + assert_eq!( + hex::encode(output.authenticator.unwrap().signature.to_vec(), false), + expected_signature_str + ); + assert_eq!( + hex::encode(output.encoded.to_vec(), false), + expected_encoded_txn_str + ); + + let json_value_expected: serde_json::Value = serde_json::from_str(json_literal).unwrap(); + let json_value: serde_json::Value = serde_json::from_str(output.json.as_ref()).unwrap(); + assert_eq!(json_value, json_value_expected); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467?network=devnet +#[test] +fn test_aptos_sign_transaction_transfer() { + let input = setup_proto_transaction( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", + "transfer", + 99, + 33, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::Transfer(Transfer { + to: "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30".to_string(), + amount: 1000, + })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021", + "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], + "function": "0x1::aptos_account::transfer", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "99", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x477141736de6b0936a6c3734e4d6fd018c7d21f1f28f99028ef0bc6881168602?network=Devnet +#[test] +fn test_aptos_sign_create_account() { + let input = setup_proto_transaction( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "create_account", + 0, // Sequence number + 33, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::AccountCreation(AccountCreation { + to: "0x3aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30e".to_string(), + })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3000000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e6372656174655f6163636f756e740001203aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30efe4d3200000000006400000000000000c2276ada0000000021", // Expected raw transaction bytes + "fcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3000000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e6372656174655f6163636f756e740001203aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30efe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40fcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x3aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30e"], + "function": "0x1::aptos_account::create_account", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "0", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xfcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb5b383a5c7f99b2edb3bed9533f8169a89051b149d65876a82f4c0b9bf78a15b?network=Devnet +#[test] +fn test_aptos_sign_coin_transfer() { + let input = setup_proto_transaction( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "coin_transfer", + 24, // Sequence number + 32, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::TokenTransfer(TokenTransfer { + transfer: Transfer { + to: "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30" + .to_string(), + amount: 100000, + }, + tag: TypeTag::from_str( + "0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9::coins::BTC", + ) + .unwrap(), + })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30180000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010743417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b905636f696e730342544300022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008a086010000000000fe4d3200000000006400000000000000c2276ada0000000020", // Expected raw transaction bytes + "7643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30180000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010743417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b905636f696e730342544300022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008a086010000000000fe4d3200000000006400000000000000c2276ada00000000200020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c407643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","100000"], + "function": "0x1::coin::transfer", + "type": "entry_function_payload", + "type_arguments": ["0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9::coins::BTC"] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "24", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x7643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0x197d40ea12e2bfc65a0a913b9f4ca3b0b0208fe0c1514d3d55cef3d5bcf25211?network=mainnet +#[test] +fn test_implicit_aptos_sign_coin_transfer() { + let input = setup_proto_transaction("0x1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf25", // Sender's address + "e7f56c77189e03699a75d8ec5c090e41f3d9d4783bc49c33df8a93d915e10de8", // Keypair + "implicit_coin_transfer", + 2, // Sequence number + 1, + 2000, + 3664390082, + 100, + "", + Some(OpsDetails::ImplicitTokenTransfer(TokenTransfer { transfer: Transfer { to: "0xb7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c".to_string(), amount: 10000 }, tag: TypeTag::from_str("0xe9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9::mee_coin::MeeCoin").unwrap() })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf2502000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e7472616e736665725f636f696e730107e9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9086d65655f636f696e074d6565436f696e000220b7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c081027000000000000d0070000000000006400000000000000c2276ada0000000001", // Expected raw transaction bytes + "30ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d", // Expected signature + "1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf2502000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e7472616e736665725f636f696e730107e9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9086d65655f636f696e074d6565436f696e000220b7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c081027000000000000d0070000000000006400000000000000c2276ada0000000001002062e7a6a486553b56a53e89dfae3f780693e537e5b0a7ed33290780e581ca83694030ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "2000", + "payload": { + "arguments": ["0xb7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c","10000"], + "function": "0x1::aptos_account::transfer_coins", + "type": "entry_function_payload", + "type_arguments": ["0xe9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9::mee_coin::MeeCoin"] + }, + "sender": "0x1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf25", + "sequence_number": "2", + "signature": { + "public_key": "0x62e7a6a486553b56a53e89dfae3f780693e537e5b0a7ed33290780e581ca8369", + "signature": "0x30ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0x514e473618bd3cb89a2b110b7c473db9a2e10532f98eb42d02d86fb31c00525d?network=testnet +#[test] +fn test_aptos_nft_offer() { + let input = setup_proto_transaction( + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", // Sender's address + "7bebb6d543d17f6fe4e685cfab239fa37896edd594ff859f1df32f244fb707e2", // Keypair + "nft_ops", + 1, // Sequence number + 2, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::NftOps(NftOperation::Offer(Offer { + receiver: AccountAddress::from_str( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + ) + .unwrap(), + creator: AccountAddress::from_str( + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + ) + .unwrap(), + collection: "Topaz Troopers".as_bytes().to_vec(), + name: "Topaz Trooper #20068".as_bytes().to_vec(), + property_version: 0, + amount: 1, + }))), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee01000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c6f666665725f73637269707400062007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000080100000000000000fe4d3200000000006400000000000000c2276ada0000000002", // Expected raw transaction bytes + "af5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f", // Expected signature + "783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee01000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c6f666665725f73637269707400062007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000080100000000000000fe4d3200000000006400000000000000c2276ada00000000020020d1d99b67e37b483161a0fa369c46f34a3be4863c20e20fc7cdc669c0826a411340af5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": [ + "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + "Topaz Troopers", "Topaz Trooper #20068", "0", "1"], + "function": "0x3::token_transfers::offer_script", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + "sequence_number": "1", + "signature": { + "public_key": "0xd1d99b67e37b483161a0fa369c46f34a3be4863c20e20fc7cdc669c0826a4113", + "signature": "0xaf5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0x0b8c64e6847c368e4c6bd2cce0e9eab378971b0ef2e3bc40cbd292910a80201d?network=testnet +#[test] +fn test_aptos_cancel_nft_offer() { + let input = setup_proto_transaction( + "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "nft_ops", + 21, // Sequence number + 2, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::NftOps(NftOperation::Cancel(Offer { + receiver: AccountAddress::from_str( + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + ) + .unwrap(), + creator: AccountAddress::from_str( + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + ) + .unwrap(), + collection: "Topaz Troopers".as_bytes().to_vec(), + name: "Topaz Trooper #20068".as_bytes().to_vec(), + property_version: 0, + amount: 0, + }))), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3015000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572731363616e63656c5f6f666665725f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada0000000002", // Expected raw transaction bytes + "826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3015000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572731363616e63656c5f6f666665725f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": [ + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + "Topaz Troopers", "Topaz Trooper #20068", "0"], + "function": "0x3::token_transfers::cancel_offer_script", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "21", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x60b51e15140ec0b7650334e948fb447ce3cb13ae63492260461ebfa9d02e85c4?network=testnet +#[test] +fn test_aptos_nft_claim() { + let input = setup_proto_transaction( + "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "nft_ops", + 19, // Sequence number + 2, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::NftOps(NftOperation::Claim(Claim { + sender: AccountAddress::from_str( + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + ) + .unwrap(), + creator: AccountAddress::from_str( + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + ) + .unwrap(), + collection: "Topaz Troopers".as_bytes().to_vec(), + name: "Topaz Trooper #20068".as_bytes().to_vec(), + property_version: 0, + }))), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3013000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c636c61696d5f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada0000000002", // Expected raw transaction bytes + "ede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3013000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c636c61696d5f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40ede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": [ + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + "Topaz Troopers", "Topaz Trooper #20068", "0"], + "function": "0x3::token_transfers::claim_script", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "19", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0xe591252daed785641bfbbcf72a5d17864568cf32e04c0cc9129f3a13834d0e8e?network=testnet +#[test] +fn test_aptos_register_token() { + let input = setup_proto_transaction("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "register_token", + 23, // Sequence number + 2, + 2000000, + 3664390082, + 100, + "", + Some(OpsDetails::RegisterToken(RegisterToken { coin_type: TypeTag::from_str("0xe4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379::move_coin::MoveCoin").unwrap() })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3017000000000000000200000000000000000000000000000000000000000000000000000000000000010c6d616e616765645f636f696e0872656769737465720107e4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379096d6f76655f636f696e084d6f7665436f696e000080841e00000000006400000000000000c2276ada0000000002", // Expected raw transaction bytes + "e230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3017000000000000000200000000000000000000000000000000000000000000000000000000000000010c6d616e616765645f636f696e0872656769737465720107e4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379096d6f76655f636f696e084d6f7665436f696e000080841e00000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40e230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "2000000", + "payload": { + "arguments": [], + "function": "0x1::managed_coin::register", + "type": "entry_function_payload", + "type_arguments": ["0xe4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379::move_coin::MoveCoin"] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "23", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xe230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x25dca849cb4ebacbff223139f7ad5d24c37c225d9506b8b12a925de70429e685/userTxnOverview?network=mainnet +#[test] +fn test_aptos_tortuga_stake() { + let input = setup_proto_transaction( + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", // Sender's address + "786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05", // Keypair + "liquid_staking_ops", + 19, // Sequence number + 1, + 5554, + 1670240203, + 100, + "", + Some(OpsDetails::LiquidStakingOps(LiquidStakingOperation::Stake( + Stake { + amount: 100000000, + smart_contract_address: AccountAddress::from_str( + "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f", + ) + .unwrap(), + }, + ))), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1300000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000b2150000000000006400000000000000cbd78d630000000001", // Expected raw transaction bytes + "22d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d", // Expected signature + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1300000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000b2150000000000006400000000000000cbd78d630000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc44022d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d", // Expected encoded transaction + r#"{ + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "19", + "max_gas_amount": "5554", + "gas_unit_price": "100", + "expiration_timestamp_secs": "1670240203", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::stake", + "type_arguments": [], + "arguments": [ + "100000000" + ], + "type": "entry_function_payload" + }, + "signature": { + "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", + "signature": "0x22d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x92edb4f756fe86118e34a0e64746c70260ee02c2ae2cf402b3e39f6a282ce968?network=mainnet +#[test] +fn test_aptos_tortuga_unstake() { + let input = setup_proto_transaction( + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", // Sender's address + "786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05", // Keypair + "liquid_staking_ops", + 20, // Sequence number + 1, + 2371, + 1670304949, + 120, + "", + Some(OpsDetails::LiquidStakingOps( + LiquidStakingOperation::Unstake(Unstake { + amount: 99178100, + smart_contract_address: AccountAddress::from_str( + "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f", + ) + .unwrap(), + }), + )), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1400000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e9050000000043090000000000007800000000000000b5d48e630000000001", // Expected raw transaction bytes + "6994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c", // Expected signature + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1400000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e9050000000043090000000000007800000000000000b5d48e630000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4406994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c", // Expected encoded transaction + r#"{ + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "20", + "max_gas_amount": "2371", + "gas_unit_price": "120", + "expiration_timestamp_secs": "1670304949", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::unstake", + "type_arguments": [], + "arguments": [ + "99178100" + ], + "type": "entry_function_payload" + }, + "signature": { + "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", + "signature": "0x6994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c", + "type": "ed25519_signature" + } + }"#); +} + +// // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x9fc874de7a7d3e813d9a1658d896023de270a0096a5e258c196005656ace7d54?network=mainnet +#[test] +fn test_aptos_tortuga_claim() { + let input = setup_proto_transaction( + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", // Sender's address + "786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05", // Keypair + "liquid_staking_ops", + 28, // Sequence number + 1, + 10, + 1682066783, + 148, + "", + Some(OpsDetails::LiquidStakingOps(LiquidStakingOperation::Claim( + liquid_staking::Claim { + idx: 0, + smart_contract_address: AccountAddress::from_str( + "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f", + ) + .unwrap(), + }, + ))), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657205636c61696d00010800000000000000000a0000000000000094000000000000005f4d42640000000001", // Expected raw transaction bytes + "c936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03", // Expected signature + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657205636c61696d00010800000000000000000a0000000000000094000000000000005f4d42640000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc440c936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03", // Expected encoded transaction + r#"{ + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "28", + "max_gas_amount": "10", + "gas_unit_price": "148", + "expiration_timestamp_secs": "1682066783", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::claim", + "type_arguments": [], + "arguments": [ + "0" + ], + "type": "entry_function_payload" + }, + "signature": { + "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", + "signature": "0xc936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x7efd69e7f9462774b932ce500ab51c0d0dcc004cf272e09f8ffd5804c2a84e33?network=mainnet +#[test] +fn test_aptos_blind_sign() { + let input = setup_proto_transaction( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "blind_sign_json", + 42, // Sequence number + 1, + 100011, + 3664390082, + 100, + r#"{ + "function": "0x16fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c::AnimeSwapPoolV1::swap_exact_coins_for_coins_3_pair_entry", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin", + "0x881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f4::coin::MOJO", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC" + ], + "arguments": [ + "1000000", + "49329" + ], + "type": "entry_function_payload" + }"#, + None, + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada0000000001", // Expected raw transaction bytes + "42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c4042cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "100011", + "payload": { + "function": "0x16fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c::AnimeSwapPoolV1::swap_exact_coins_for_coins_3_pair_entry", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin", + "0x881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f4::coin::MOJO", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC" + ], + "arguments": [ + "1000000", + "49329" + ], + "type": "entry_function_payload" + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "42", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x25dca849cb4ebacbff223139f7ad5d24c37c225d9506b8b12a925de70429e685/payload +#[test] +fn test_aptos_blind_sign_staking() { + let input = setup_proto_transaction( + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "blind_sign_json", + 43, // Sequence number + 1, + 100011, + 3664390082, + 100, + r#"{ + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::stake", + "type_arguments": [], + "arguments": [ + "100000000" + ], + "type": "entry_function_payload" + }"#, + None, + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2b00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000ab860100000000006400000000000000c2276ada0000000001", // Expected raw transaction bytes + "a41b7440a50f36e8491319508734acb55488abc6d88fbc9cb2b37ba23210f01f5d08c856cb7abf18c414cf9302ee144450bd99495a7e21e61f624764db91eb0b", // Expected signature + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2b00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40a41b7440a50f36e8491319508734acb55488abc6d88fbc9cb2b37ba23210f01f5d08c856cb7abf18c414cf9302ee144450bd99495a7e21e61f624764db91eb0b", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "100011", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::stake", + "type_arguments": [], + "arguments": [ + "100000000" + ], + "type": "entry_function_payload" + }, + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "43", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xa41b7440a50f36e8491319508734acb55488abc6d88fbc9cb2b37ba23210f01f5d08c856cb7abf18c414cf9302ee144450bd99495a7e21e61f624764db91eb0b", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x92edb4f756fe86118e34a0e64746c70260ee02c2ae2cf402b3e39f6a282ce968/payload +#[test] +fn test_aptos_blind_sign_unstaking() { + let input = setup_proto_transaction( + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "blind_sign_json", + 44, // Sequence number + 1, + 100011, + 3664390082, + 100, + r#"{ + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::unstake", + "type_arguments": [], + "arguments": [ + "99178100" + ], + "type": "entry_function_payload" + }"#, + None, + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e90500000000ab860100000000006400000000000000c2276ada0000000001", // Expected raw transaction bytes + "a58ad5e3331beb8c0212a18a1f932207cb664b78f5aad3cb1fe7435e0e0e053247ce49b38fd67b064bed34ed643eb6a03165d77c681d7d73ac3161ab984a960a", // Expected signature + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e90500000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40a58ad5e3331beb8c0212a18a1f932207cb664b78f5aad3cb1fe7435e0e0e053247ce49b38fd67b064bed34ed643eb6a03165d77c681d7d73ac3161ab984a960a", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "100011", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::unstake", + "type_arguments": [], + "arguments": [ + "99178100" + ], + "type": "entry_function_payload" + }, + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "44", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xa58ad5e3331beb8c0212a18a1f932207cb664b78f5aad3cb1fe7435e0e0e053247ce49b38fd67b064bed34ed643eb6a03165d77c681d7d73ac3161ab984a960a", + "type": "ed25519_signature" + } + }"#); +} diff --git a/rust/chains/tw_binance/Cargo.toml b/rust/chains/tw_binance/Cargo.toml new file mode 100644 index 00000000000..e5c9a10aa2c --- /dev/null +++ b/rust/chains/tw_binance/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "tw_binance" +version = "0.1.0" +edition = "2021" + +[dependencies] +quick-protobuf = "0.8.1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_repr = "0.1" +strum_macros = "0.25" +tw_bech32_address = { path = "../../tw_bech32_address" } +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_encoding = { path = "../../tw_encoding" } +tw_evm = { path = "../../tw_evm" } +tw_hash = { path = "../../tw_hash" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_misc = { path = "../../tw_misc", features = ["serde"] } +tw_proto = { path = "../../tw_proto" } diff --git a/rust/chains/tw_binance/fuzz/.gitignore b/rust/chains/tw_binance/fuzz/.gitignore new file mode 100644 index 00000000000..5c404b9583f --- /dev/null +++ b/rust/chains/tw_binance/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +Cargo.lock diff --git a/rust/chains/tw_binance/fuzz/Cargo.toml b/rust/chains/tw_binance/fuzz/Cargo.toml new file mode 100644 index 00000000000..179f9d77239 --- /dev/null +++ b/rust/chains/tw_binance/fuzz/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "tw_binance-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +tw_any_coin = { path = "../../../tw_any_coin", features = ["test-utils"] } +tw_coin_registry = { path = "../../../tw_coin_registry" } +tw_proto = { path = "../../../tw_proto", features = ["fuzz"] } + +[dependencies.tw_binance] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "sign" +path = "fuzz_targets/sign.rs" +test = false +doc = false diff --git a/rust/chains/tw_binance/fuzz/fuzz_targets/sign.rs b/rust/chains/tw_binance/fuzz/fuzz_targets/sign.rs new file mode 100644 index 00000000000..482fd1f5844 --- /dev/null +++ b/rust/chains/tw_binance/fuzz/fuzz_targets/sign.rs @@ -0,0 +1,11 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_any_coin::test_utils::sign_utils::AnySignerHelper; +use tw_coin_registry::coin_type::CoinType; +use tw_proto::Binance::Proto; + +fuzz_target!(|input: Proto::SigningInput<'_>| { + let mut signer = AnySignerHelper::::default(); + let _ = signer.sign(CoinType::Binance, input); +}); diff --git a/rust/chains/tw_binance/src/address.rs b/rust/chains/tw_binance/src/address.rs new file mode 100644 index 00000000000..45b00e26bb4 --- /dev/null +++ b/rust/chains/tw_binance/src/address.rs @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::str::FromStr; +use tw_bech32_address::bech32_prefix::Bech32Prefix; +use tw_bech32_address::Bech32Address; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::*; +use tw_cosmos_sdk::address::CosmosAddress; +use tw_keypair::tw::PublicKey; +use tw_memory::Data; + +/// The list of known BNB hrps. +const BNB_KNOWN_HRPS: [&str; 2] = [ + BinanceAddress::VALIDATOR_HRP, // BNB Validator HRP. + "bca", +]; + +#[derive(Deserialize, PartialEq, Serialize)] +pub struct BinanceAddress(Bech32Address); + +impl CoinAddress for BinanceAddress { + #[inline] + fn data(&self) -> Data { + self.0.data() + } +} + +impl CosmosAddress for BinanceAddress {} + +impl BinanceAddress { + pub const VALIDATOR_HRP: &'static str = "bva"; + + pub fn new_validator_addr(key_hash: Data) -> AddressResult { + Bech32Address::new(Self::VALIDATOR_HRP.to_string(), key_hash).map(BinanceAddress) + } + + /// Creates a Binance address with the only `prefix` + pub fn from_str_with_coin_and_prefix( + coin: &dyn CoinContext, + address_str: String, + prefix: Option, + ) -> AddressResult + where + Self: Sized, + { + let possible_hrps = match prefix { + Some(Bech32Prefix { hrp }) => vec![hrp], + None => { + let coin_hrp = coin.hrp().ok_or(AddressError::InvalidHrp)?; + let other_hrps = BNB_KNOWN_HRPS + .iter() + .map(|another_hrp| another_hrp.to_string()); + std::iter::once(coin_hrp).chain(other_hrps).collect() + }, + }; + Bech32Address::from_str_checked(possible_hrps, address_str).map(BinanceAddress) + } + + pub fn with_public_key_coin_context( + coin: &dyn CoinContext, + public_key: &PublicKey, + prefix: Option, + ) -> AddressResult { + Bech32Address::with_public_key_coin_context(coin, public_key, prefix).map(BinanceAddress) + } + + pub fn from_key_hash_with_coin( + coin: &dyn CoinContext, + key_hash: Data, + ) -> AddressResult { + Bech32Address::from_key_hash_with_coin(coin, key_hash).map(BinanceAddress) + } +} + +impl FromStr for BinanceAddress { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + Bech32Address::from_str(s).map(BinanceAddress) + } +} + +impl fmt::Display for BinanceAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} diff --git a/rust/chains/tw_binance/src/amino.rs b/rust/chains/tw_binance/src/amino.rs new file mode 100644 index 00000000000..6bcc017dd48 --- /dev/null +++ b/rust/chains/tw_binance/src/amino.rs @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use quick_protobuf::MessageWrite; +use tw_encoding::{EncodingError, EncodingResult}; +use tw_memory::Data; +use tw_proto::serialize; + +pub struct AminoEncoder { + /// The Amino content starts with a prefix. + content: Data, +} + +impl AminoEncoder { + pub fn new(prefix: &[u8]) -> AminoEncoder { + AminoEncoder { + content: prefix.to_vec(), + } + } + + pub fn extend_content(mut self, content: &[u8]) -> AminoEncoder { + self.content.extend_from_slice(content); + self + } + + pub fn extend_with_msg(mut self, msg: &M) -> EncodingResult { + let msg_data = serialize(msg).map_err(|_| EncodingError::Internal)?; + self.content.extend_from_slice(&msg_data); + Ok(self) + } + + pub fn encode(self) -> Data { + self.content + } + + pub fn encode_size_prefixed(self) -> EncodingResult { + const CONTENT_SIZE_CAPACITY: usize = 10; + + let content_len = self.content.len(); + let capacity = content_len + CONTENT_SIZE_CAPACITY; + + let mut buffer = Vec::with_capacity(capacity); + + Self::write_varint(&mut buffer, content_len as u64)?; + buffer.extend_from_slice(&self.content); + + Ok(buffer) + } + + /// This method takes `&mut Data` instead of `&mut [u8]` because the given `buffer` can be extended (become longer). + fn write_varint(buffer: &mut Data, num: u64) -> EncodingResult<()> { + let mut writer = quick_protobuf::Writer::new(buffer); + writer + .write_varint(num) + .map_err(|_| EncodingError::Internal) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_encoding::hex::DecodeHex; + + struct TestInput { + prefix: &'static str, + content: &'static str, + content_size_prefixed: bool, + expected: &'static str, + } + + fn amino_encode_impl(input: TestInput) { + let prefix = input.prefix.decode_hex().unwrap(); + let content = input.content.decode_hex().unwrap(); + + let encoder = AminoEncoder::new(&prefix).extend_content(&content); + + let actual = if input.content_size_prefixed { + encoder + .encode_size_prefixed() + .expect("Error on Amino encoding with content size prefix") + } else { + encoder.encode() + }; + + let expected = input.expected.decode_hex().unwrap(); + assert_eq!(actual, expected); + } + + #[test] + fn test_amino_encode() { + let content_size_prefixed = false; + + amino_encode_impl(TestInput { + prefix: "0b0c0d0e", + content: "0102030405060708", + content_size_prefixed, + expected: "0b0c0d0e0102030405060708", + }); + amino_encode_impl(TestInput { + prefix: "0b0c0d0e", + content: "01020304050607080102030405060708010203040506070801020304050607080102030405060708", + content_size_prefixed, + expected: "0b0c0d0e01020304050607080102030405060708010203040506070801020304050607080102030405060708", + }); + } + + #[test] + fn test_amino_encode_with_content_size_prefix() { + let content_size_prefixed = true; + + amino_encode_impl(TestInput { + prefix: "0b0c0d0e", + content: "0102030405060708", + content_size_prefixed, + expected: "0c0b0c0d0e0102030405060708", + }); + amino_encode_impl(TestInput { + prefix: "0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e", + content: "0102030405060708", + content_size_prefixed, + expected: "dc020b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0102030405060708", + }); + } +} diff --git a/rust/chains/tw_binance/src/compiler.rs b/rust/chains/tw_binance/src/compiler.rs new file mode 100644 index 00000000000..400c37e36ec --- /dev/null +++ b/rust/chains/tw_binance/src/compiler.rs @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::context::BinanceContext; +use crate::modules::preimager::{JsonPreimager, JsonTxPreimage}; +use crate::modules::serializer::BinanceAminoSerializer; +use crate::modules::tx_builder::TxBuilder; +use crate::signature::BinanceSignature; +use crate::transaction::SignerInfo; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::common::compile_input::SingleSignaturePubkey; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_cosmos_sdk::modules::serializer::json_serializer::JsonSerializer; +use tw_cosmos_sdk::public_key::secp256k1::Secp256PublicKey; +use tw_cosmos_sdk::public_key::CosmosPublicKey; +use tw_misc::traits::ToBytesVec; +use tw_proto::Binance::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct BinanceCompiler; + +impl BinanceCompiler { + #[inline] + pub fn preimage_hashes( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + Self::preimage_hashes_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + fn preimage_hashes_impl( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let unsigned_tx = TxBuilder::unsigned_tx_from_proto(coin, &input)?; + let JsonTxPreimage { + tx_hash, + encoded_tx, + } = JsonPreimager::preimage_hash(&unsigned_tx)?; + + Ok(CompilerProto::PreSigningOutput { + data_hash: tx_hash.to_vec().into(), + data: encoded_tx.as_bytes().to_vec().into(), + ..CompilerProto::PreSigningOutput::default() + }) + } + + #[inline] + pub fn compile( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + Self::compile_impl(coin, input, signatures, public_keys) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn compile_impl( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> SigningResult> { + let SingleSignaturePubkey { + signature, + public_key, + } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; + let signature = BinanceSignature::try_from(signature.as_slice())?; + let public_key_params = None; + let public_key = + Secp256PublicKey::from_bytes(coin, public_key.as_slice(), public_key_params)?; + + let signature_bytes = signature.to_vec(); + let signature_json = JsonSerializer::::serialize_signature( + &public_key, + signature_bytes.clone(), + ); + + let unsigned_tx = TxBuilder::unsigned_tx_from_proto(coin, &input)?; + let signed_tx = unsigned_tx.into_signed(SignerInfo { + public_key, + signature, + }); + + let encoded_tx = BinanceAminoSerializer::serialize_signed_tx(&signed_tx)?; + + let signature_json = + serde_json::to_string(&signature_json).tw_err(|_| SigningErrorType::Error_internal)?; + Ok(Proto::SigningOutput { + encoded: encoded_tx.into(), + signature: signature_bytes.into(), + signature_json: signature_json.into(), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/chains/tw_binance/src/context.rs b/rust/chains/tw_binance/src/context.rs new file mode 100644 index 00000000000..58952365b63 --- /dev/null +++ b/rust/chains/tw_binance/src/context.rs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::BinanceAddress; +use tw_cosmos_sdk::context::CosmosContext; +use tw_cosmos_sdk::private_key::secp256k1::Secp256PrivateKey; +use tw_cosmos_sdk::public_key::secp256k1::Secp256PublicKey; +use tw_cosmos_sdk::signature::secp256k1::Secp256k1Signature; +use tw_hash::hasher::Hasher; + +pub struct BinanceContext; + +impl CosmosContext for BinanceContext { + type Address = BinanceAddress; + type PrivateKey = Secp256PrivateKey; + type PublicKey = Secp256PublicKey; + type Signature = Secp256k1Signature; + + /// Binance Beacon chain uses `sha256` hash. + fn default_tx_hasher() -> Hasher { + Hasher::Sha256 + } +} diff --git a/rust/chains/tw_binance/src/entry.rs b/rust/chains/tw_binance/src/entry.rs new file mode 100644 index 00000000000..55930473d53 --- /dev/null +++ b/rust/chains/tw_binance/src/entry.rs @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::BinanceAddress; +use crate::compiler::BinanceCompiler; +use crate::modules::wallet_connect::connector::BinanceWalletConnector; +use crate::signer::BinanceSigner; +use std::str::FromStr; +use tw_bech32_address::bech32_prefix::Bech32Prefix; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; +use tw_keypair::tw::PublicKey; +use tw_proto::Binance::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct BinanceEntry; + +impl CoinEntry for BinanceEntry { + type AddressPrefix = Bech32Prefix; + type Address = BinanceAddress; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + type WalletConnector = BinanceWalletConnector; + type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; + + #[inline] + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + prefix: Option, + ) -> AddressResult { + BinanceAddress::from_str_with_coin_and_prefix(coin, address.to_string(), prefix) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + BinanceAddress::from_str(address) + } + + #[inline] + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: PublicKey, + _derivation: Derivation, + prefix: Option, + ) -> AddressResult { + BinanceAddress::with_public_key_coin_context(coin, &public_key, prefix) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + BinanceSigner::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + BinanceCompiler::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + BinanceCompiler::compile(coin, input, signatures, public_keys) + } + + #[inline] + fn wallet_connector(&self) -> Option { + Some(BinanceWalletConnector) + } +} diff --git a/rust/chains/tw_binance/src/lib.rs b/rust/chains/tw_binance/src/lib.rs new file mode 100644 index 00000000000..6df844a203d --- /dev/null +++ b/rust/chains/tw_binance/src/lib.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod address; +pub mod amino; +pub mod compiler; +pub mod context; +pub mod entry; +pub mod modules; +pub mod signature; +pub mod signer; +pub mod transaction; diff --git a/rust/chains/tw_binance/src/modules/mod.rs b/rust/chains/tw_binance/src/modules/mod.rs new file mode 100644 index 00000000000..943cd0f39ce --- /dev/null +++ b/rust/chains/tw_binance/src/modules/mod.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod preimager; +pub mod serializer; +pub mod tx_builder; +pub mod wallet_connect; diff --git a/rust/chains/tw_binance/src/modules/preimager.rs b/rust/chains/tw_binance/src/modules/preimager.rs new file mode 100644 index 00000000000..45e2dacdf53 --- /dev/null +++ b/rust/chains/tw_binance/src/modules/preimager.rs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::transaction::UnsignedTransaction; +use tw_coin_entry::error::prelude::*; +use tw_hash::{sha2, H256}; + +pub struct JsonTxPreimage { + pub encoded_tx: String, + pub tx_hash: H256, +} + +pub struct JsonPreimager; + +impl JsonPreimager { + pub fn preimage_hash(unsigned: &UnsignedTransaction) -> SigningResult { + let encoded_tx = + serde_json::to_string(unsigned).tw_err(|_| SigningErrorType::Error_internal)?; + let tx_hash = sha2::sha256(encoded_tx.as_bytes()); + let tx_hash = H256::try_from(tx_hash.as_slice()).expect("sha256 must return 32 bytes"); + Ok(JsonTxPreimage { + encoded_tx, + tx_hash, + }) + } +} diff --git a/rust/chains/tw_binance/src/modules/serializer.rs b/rust/chains/tw_binance/src/modules/serializer.rs new file mode 100644 index 00000000000..88ec0ad22f3 --- /dev/null +++ b/rust/chains/tw_binance/src/modules/serializer.rs @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::amino::AminoEncoder; +use crate::transaction::SignedTransaction; +use std::borrow::Cow; +use tw_coin_entry::error::prelude::*; +use tw_cosmos_sdk::public_key::CosmosPublicKey; +use tw_memory::Data; +use tw_misc::traits::ToBytesVec; +use tw_proto::serialize; +use tw_proto::Binance::Proto; + +/// cbindgen:ignore +pub const TRANSACTION_AMINO_PREFIX: [u8; 4] = [0xF0, 0x62, 0x5D, 0xEE]; +/// cbindgen:ignore +pub const PUBLIC_KEY_PREFIX: [u8; 4] = [0xEB, 0x5A, 0xE9, 0x87]; + +pub struct BinanceAminoSerializer; + +impl BinanceAminoSerializer { + pub fn serialize_signed_tx(tx: &SignedTransaction) -> SigningResult { + let msgs = tx + .unsigned + .msgs + .iter() + .map(|msg| msg.as_ref().to_amino_protobuf().map(Cow::from)) + .collect::>>()?; + + let signature = Self::serialize_signature(tx)?; + let tx = Proto::Transaction { + msgs, + signatures: vec![signature.into()], + memo: tx.unsigned.memo.clone().into(), + source: tx.unsigned.source, + data: tx.unsigned.data.clone().unwrap_or_default().into(), + }; + Ok(AminoEncoder::new(&TRANSACTION_AMINO_PREFIX) + .extend_with_msg(&tx)? + .encode_size_prefixed()?) + } + + pub fn serialize_public_key(public_key: Data) -> Data { + let public_key_len = public_key.len() as u8; + AminoEncoder::new(&PUBLIC_KEY_PREFIX) + // Push the length of the public key. + .extend_content(&[public_key_len]) + .extend_content(public_key.as_slice()) + .encode() + } + + pub fn serialize_signature(signed: &SignedTransaction) -> SigningResult { + let sign_msg = Proto::Signature { + pub_key: Self::serialize_public_key(signed.signer.public_key.to_bytes()).into(), + signature: signed.signer.signature.to_vec().into(), + account_number: signed.unsigned.account_number, + sequence: signed.unsigned.sequence, + }; + // There is no need to use Amino encoding here as the prefix is empty. + serialize(&sign_msg).tw_err(|_| SigningErrorType::Error_internal) + } +} diff --git a/rust/chains/tw_binance/src/modules/tx_builder.rs b/rust/chains/tw_binance/src/modules/tx_builder.rs new file mode 100644 index 00000000000..ce1340d63c0 --- /dev/null +++ b/rust/chains/tw_binance/src/modules/tx_builder.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::transaction::message::{BinanceMessageEnum, TWBinanceProto}; +use crate::transaction::UnsignedTransaction; +use std::borrow::Cow; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_proto::Binance::Proto; + +pub struct TxBuilder; + +impl TxBuilder { + pub fn unsigned_tx_from_proto( + coin: &dyn CoinContext, + input: &Proto::SigningInput<'_>, + ) -> SigningResult { + let msg = BinanceMessageEnum::from_tw_proto(coin, &input.order_oneof)?; + Ok(UnsignedTransaction { + account_number: input.account_number, + chain_id: input.chain_id.to_string(), + data: None, + memo: input.memo.to_string(), + msgs: vec![msg], + sequence: input.sequence, + source: input.source, + }) + } + + pub fn unsigned_tx_to_proto( + unsigned: &UnsignedTransaction, + ) -> SigningResult> { + if unsigned.msgs.len() != 1 { + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("Expected exactly one Transaction Message"); + } + let msg = unsigned + .msgs + .first() + .expect("There should be exactly one message") + .to_tw_proto(); + + Ok(Proto::SigningInput { + chain_id: unsigned.chain_id.clone().into(), + account_number: unsigned.account_number, + sequence: unsigned.sequence, + source: unsigned.source, + memo: unsigned.memo.clone().into(), + private_key: Cow::default(), + order_oneof: msg, + }) + } +} diff --git a/rust/chains/tw_binance/src/modules/wallet_connect/connector.rs b/rust/chains/tw_binance/src/modules/wallet_connect/connector.rs new file mode 100644 index 00000000000..e66b4688a2f --- /dev/null +++ b/rust/chains/tw_binance/src/modules/wallet_connect/connector.rs @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::tx_builder::TxBuilder; +use crate::modules::wallet_connect::types::SignAminoRequest; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::wallet_connector::WalletConnector; +use tw_coin_entry::signing_output_error; +use tw_proto::WalletConnect::Proto::{ + self as WCProto, mod_ParseRequestOutput::OneOfsigning_input_oneof as SigningInputEnum, +}; + +pub struct BinanceWalletConnector; + +impl WalletConnector for BinanceWalletConnector { + fn parse_request( + &self, + coin: &dyn CoinContext, + request: WCProto::ParseRequestInput<'_>, + ) -> WCProto::ParseRequestOutput<'static> { + Self::parse_request_impl(coin, request) + .unwrap_or_else(|e| signing_output_error!(WCProto::ParseRequestOutput, e)) + } +} + +impl BinanceWalletConnector { + fn parse_request_impl( + coin: &dyn CoinContext, + request: WCProto::ParseRequestInput<'_>, + ) -> SigningResult> { + match request.method { + WCProto::Method::CosmosSignAmino => Self::parse_sign_amino_request(coin, request), + _ => SigningError::err(SigningErrorType::Error_not_supported) + .context("Unknown WalletConnect method"), + } + } + + pub fn parse_sign_amino_request( + _coin: &dyn CoinContext, + request: WCProto::ParseRequestInput<'_>, + ) -> SigningResult> { + let amino_req: SignAminoRequest = serde_json::from_str(&request.payload) + .tw_err(|_| SigningErrorType::Error_input_parse) + .context("Error deserializing WalletConnect signAmino request as JSON")?; + + // Parse a `SigningInput` from the given `signDoc`. + let signing_input = TxBuilder::unsigned_tx_to_proto(&amino_req.sign_doc)?; + + Ok(WCProto::ParseRequestOutput { + signing_input_oneof: SigningInputEnum::binance(signing_input), + ..WCProto::ParseRequestOutput::default() + }) + } +} diff --git a/rust/chains/tw_binance/src/modules/wallet_connect/mod.rs b/rust/chains/tw_binance/src/modules/wallet_connect/mod.rs new file mode 100644 index 00000000000..1a90ae8a9cf --- /dev/null +++ b/rust/chains/tw_binance/src/modules/wallet_connect/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod connector; +pub mod types; diff --git a/rust/chains/tw_binance/src/modules/wallet_connect/types.rs b/rust/chains/tw_binance/src/modules/wallet_connect/types.rs new file mode 100644 index 00000000000..f5ac124e47e --- /dev/null +++ b/rust/chains/tw_binance/src/modules/wallet_connect/types.rs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::transaction::UnsignedTransaction; +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct SignAminoRequest { + #[serde(rename = "signDoc")] + pub sign_doc: UnsignedTransaction, +} diff --git a/rust/chains/tw_binance/src/signature.rs b/rust/chains/tw_binance/src/signature.rs new file mode 100644 index 00000000000..45e1b5c6fca --- /dev/null +++ b/rust/chains/tw_binance/src/signature.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_keypair::ecdsa::secp256k1; + +pub type BinanceSignature = secp256k1::VerifySignature; diff --git a/rust/chains/tw_binance/src/signer.rs b/rust/chains/tw_binance/src/signer.rs new file mode 100644 index 00000000000..86cb5da5b4b --- /dev/null +++ b/rust/chains/tw_binance/src/signer.rs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::context::BinanceContext; +use crate::modules::preimager::{JsonPreimager, JsonTxPreimage}; +use crate::modules::serializer::BinanceAminoSerializer; +use crate::modules::tx_builder::TxBuilder; +use crate::signature::BinanceSignature; +use crate::transaction::SignerInfo; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_cosmos_sdk::modules::serializer::json_serializer::JsonSerializer; +use tw_cosmos_sdk::public_key::secp256k1::Secp256PublicKey; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait}; +use tw_misc::traits::ToBytesVec; +use tw_proto::Binance::Proto; + +pub struct BinanceSigner; + +impl BinanceSigner { + pub fn sign( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> Proto::SigningOutput<'static> { + Self::sign_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_impl( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let unsigned_tx = TxBuilder::unsigned_tx_from_proto(coin, &input)?; + let JsonTxPreimage { tx_hash, .. } = JsonPreimager::preimage_hash(&unsigned_tx)?; + + let key_pair = secp256k1::KeyPair::try_from(input.private_key.as_ref())?; + + let signature = BinanceSignature::from(key_pair.sign(tx_hash)?); + let public_key = + Secp256PublicKey::from_secp256k1_public_key(coin.public_key_type(), key_pair.public())?; + + let signature_bytes = signature.to_vec(); + let signature_json = JsonSerializer::::serialize_signature( + &public_key, + signature_bytes.clone(), + ); + + let signed_tx = unsigned_tx.into_signed(SignerInfo { + public_key, + signature, + }); + let encoded_tx = BinanceAminoSerializer::serialize_signed_tx(&signed_tx)?; + + let signature_json = + serde_json::to_string(&signature_json).tw_err(|_| SigningErrorType::Error_internal)?; + Ok(Proto::SigningOutput { + encoded: encoded_tx.into(), + signature: signature_bytes.into(), + signature_json: signature_json.into(), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/chains/tw_binance/src/transaction/message/htlt_order.rs b/rust/chains/tw_binance/src/transaction/message/htlt_order.rs new file mode 100644 index 00000000000..902ae019e82 --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/htlt_order.rs @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::BinanceAddress; +use crate::amino::AminoEncoder; +use crate::transaction::message::{BinanceMessage, TWBinanceProto, Token}; +use serde::{Deserialize, Serialize}; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::*; +use tw_encoding::hex::as_hex; +use tw_memory::Data; +use tw_proto::Binance::Proto; + +#[derive(Deserialize, Serialize)] +pub struct HTLTOrder { + pub amount: Vec, + pub cross_chain: bool, + pub expected_income: String, + pub from: BinanceAddress, + pub height_span: i64, + #[serde(with = "as_hex")] + pub random_number_hash: Data, + pub recipient_other_chain: String, + pub sender_other_chain: String, + pub timestamp: i64, + pub to: BinanceAddress, +} + +impl HTLTOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0xB3, 0x3F, 0x9A, 0x24]; +} + +impl BinanceMessage for HTLTOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for HTLTOrder { + type Proto<'a> = Proto::HTLTOrder<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let from = BinanceAddress::from_key_hash_with_coin(coin, msg.from.to_vec())?; + let to = BinanceAddress::from_key_hash_with_coin(coin, msg.to.to_vec())?; + + let amount = msg.amount.iter().map(Token::from_tw_proto).collect(); + + Ok(HTLTOrder { + from, + to, + recipient_other_chain: msg.recipient_other_chain.to_string(), + sender_other_chain: msg.sender_other_chain.to_string(), + random_number_hash: msg.random_number_hash.to_vec(), + timestamp: msg.timestamp, + amount, + expected_income: msg.expected_income.to_string(), + height_span: msg.height_span, + cross_chain: msg.cross_chain, + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::HTLTOrder { + from: self.from.data().into(), + to: self.to.data().into(), + recipient_other_chain: self.recipient_other_chain.clone().into(), + sender_other_chain: self.sender_other_chain.clone().into(), + random_number_hash: self.random_number_hash.clone().into(), + timestamp: self.timestamp, + amount: self.amount.iter().map(Token::to_tw_proto).collect(), + expected_income: self.expected_income.clone().into(), + height_span: self.height_span, + cross_chain: self.cross_chain, + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct DepositHTLTOrder { + pub amount: Vec, + pub from: BinanceAddress, + #[serde(with = "as_hex")] + pub swap_id: Data, +} + +impl DepositHTLTOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x63, 0x98, 0x64, 0x96]; +} + +impl BinanceMessage for DepositHTLTOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for DepositHTLTOrder { + type Proto<'a> = Proto::DepositHTLTOrder<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let from = BinanceAddress::from_key_hash_with_coin(coin, msg.from.to_vec())?; + let amount = msg.amount.iter().map(Token::from_tw_proto).collect(); + + Ok(DepositHTLTOrder { + from, + amount, + swap_id: msg.swap_id.to_vec(), + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::DepositHTLTOrder { + from: self.from.data().into(), + amount: self.amount.iter().map(Token::to_tw_proto).collect(), + swap_id: self.swap_id.clone().into(), + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct ClaimHTLTOrder { + pub from: BinanceAddress, + #[serde(with = "as_hex")] + pub random_number: Data, + #[serde(with = "as_hex")] + pub swap_id: Data, +} + +impl ClaimHTLTOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0xC1, 0x66, 0x53, 0x00]; +} + +impl BinanceMessage for ClaimHTLTOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for ClaimHTLTOrder { + type Proto<'a> = Proto::ClaimHTLOrder<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let from = BinanceAddress::from_key_hash_with_coin(coin, msg.from.to_vec())?; + + Ok(ClaimHTLTOrder { + from, + swap_id: msg.swap_id.to_vec(), + random_number: msg.random_number.to_vec(), + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::ClaimHTLOrder { + from: self.from.data().into(), + swap_id: self.swap_id.clone().into(), + random_number: self.random_number.clone().into(), + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct RefundHTLTOrder { + pub from: BinanceAddress, + #[serde(with = "as_hex")] + pub swap_id: Data, +} + +impl RefundHTLTOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x34, 0x54, 0xA2, 0x7C]; +} + +impl TWBinanceProto for RefundHTLTOrder { + type Proto<'a> = Proto::RefundHTLTOrder<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let from = BinanceAddress::from_key_hash_with_coin(coin, msg.from.to_vec())?; + let swap_id = msg.swap_id.to_vec(); + + Ok(RefundHTLTOrder { from, swap_id }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::RefundHTLTOrder { + from: self.from.data().into(), + swap_id: self.swap_id.clone().into(), + } + } +} + +impl BinanceMessage for RefundHTLTOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} diff --git a/rust/chains/tw_binance/src/transaction/message/mod.rs b/rust/chains/tw_binance/src/transaction/message/mod.rs new file mode 100644 index 00000000000..0879bcf5b3e --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/mod.rs @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use serde::{Deserialize, Serialize, Serializer}; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_memory::Data; +use tw_proto::Binance::Proto::{self, mod_SigningInput::OneOforder_oneof as BinanceMessageProto}; + +pub mod htlt_order; +pub mod send_order; +pub mod side_chain_delegate; +pub mod time_lock_order; +pub mod token_order; +pub mod trade_order; +pub mod tranfer_out_order; + +pub trait BinanceMessage { + fn to_amino_protobuf(&self) -> SigningResult; +} + +/// A Binance message represented as a Trust Wallet Core Protobuf message. +pub trait TWBinanceProto: Sized { + type Proto<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult; + + fn to_tw_proto(&self) -> Self::Proto<'static>; +} + +/// Please note that some of the fields are typped such as `SideDelegateOrder`. +#[derive(Deserialize, Serialize)] +#[serde(untagged)] +pub enum BinanceMessageEnum { + HTLTOrder(htlt_order::HTLTOrder), + DepositHTLTOrder(htlt_order::DepositHTLTOrder), + ClaimHTLTOrder(htlt_order::ClaimHTLTOrder), + RefundHTLTOrder(htlt_order::RefundHTLTOrder), + SendOrder(send_order::SendOrder), + SideDelegateOrder(side_chain_delegate::SideDelegateOrder), + SideRedelegateOrder(side_chain_delegate::SideRedelegateOrder), + SideUndelegateOrder(side_chain_delegate::SideUndelegateOrder), + StakeMigrationOrder(side_chain_delegate::StakeMigrationOrder), + TimeLockOrder(time_lock_order::TimeLockOrder), + TimeRelockOrder(time_lock_order::TimeRelockOrder), + TimeUnlockOrder(time_lock_order::TimeUnlockOrder), + TokenFreezeOrder(token_order::TokenFreezeOrder), + TokenUnfreezeOrder(token_order::TokenUnfreezeOrder), + TokenIssueOrder(token_order::TokenIssueOrder), + TokenMintOrder(token_order::TokenMintOrder), + TokenBurnOrder(token_order::TokenBurnOrder), + NewTradeOrder(trade_order::NewTradeOrder), + CancelTradeOrder(trade_order::CancelTradeOrder), + TransferOutOrder(tranfer_out_order::TransferOutOrder), +} + +impl TWBinanceProto for BinanceMessageEnum { + type Proto<'a> = BinanceMessageProto<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + match msg { + BinanceMessageProto::trade_order(ref order) => { + trade_order::NewTradeOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::NewTradeOrder) + }, + BinanceMessageProto::cancel_trade_order(ref order) => { + trade_order::CancelTradeOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::CancelTradeOrder) + }, + BinanceMessageProto::send_order(ref order) => { + send_order::SendOrder::from_tw_proto(coin, order).map(BinanceMessageEnum::SendOrder) + }, + BinanceMessageProto::freeze_order(ref order) => { + token_order::TokenFreezeOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::TokenFreezeOrder) + }, + BinanceMessageProto::unfreeze_order(ref order) => { + token_order::TokenUnfreezeOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::TokenUnfreezeOrder) + }, + BinanceMessageProto::htlt_order(ref order) => { + htlt_order::HTLTOrder::from_tw_proto(coin, order).map(BinanceMessageEnum::HTLTOrder) + }, + BinanceMessageProto::depositHTLT_order(ref order) => { + htlt_order::DepositHTLTOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::DepositHTLTOrder) + }, + BinanceMessageProto::claimHTLT_order(ref order) => { + htlt_order::ClaimHTLTOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::ClaimHTLTOrder) + }, + BinanceMessageProto::refundHTLT_order(ref order) => { + htlt_order::RefundHTLTOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::RefundHTLTOrder) + }, + BinanceMessageProto::issue_order(ref order) => { + token_order::TokenIssueOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::TokenIssueOrder) + }, + BinanceMessageProto::mint_order(ref order) => { + token_order::TokenMintOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::TokenMintOrder) + }, + BinanceMessageProto::burn_order(ref order) => { + token_order::TokenBurnOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::TokenBurnOrder) + }, + BinanceMessageProto::transfer_out_order(ref order) => { + tranfer_out_order::TransferOutOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::TransferOutOrder) + }, + BinanceMessageProto::side_delegate_order(ref order) => { + side_chain_delegate::SideDelegateOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::SideDelegateOrder) + }, + BinanceMessageProto::side_redelegate_order(ref order) => { + side_chain_delegate::SideRedelegateOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::SideRedelegateOrder) + }, + BinanceMessageProto::side_undelegate_order(ref order) => { + side_chain_delegate::SideUndelegateOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::SideUndelegateOrder) + }, + BinanceMessageProto::time_lock_order(ref order) => { + time_lock_order::TimeLockOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::TimeLockOrder) + }, + BinanceMessageProto::time_relock_order(ref order) => { + time_lock_order::TimeRelockOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::TimeRelockOrder) + }, + BinanceMessageProto::time_unlock_order(ref order) => { + time_lock_order::TimeUnlockOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::TimeUnlockOrder) + }, + BinanceMessageProto::side_stake_migration_order(ref order) => { + side_chain_delegate::StakeMigrationOrder::from_tw_proto(coin, order) + .map(BinanceMessageEnum::StakeMigrationOrder) + }, + BinanceMessageProto::None => SigningError::err(SigningErrorType::Error_invalid_params), + } + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + match self { + BinanceMessageEnum::HTLTOrder(m) => BinanceMessageProto::htlt_order(m.to_tw_proto()), + BinanceMessageEnum::DepositHTLTOrder(m) => { + BinanceMessageProto::depositHTLT_order(m.to_tw_proto()) + }, + BinanceMessageEnum::ClaimHTLTOrder(m) => { + BinanceMessageProto::claimHTLT_order(m.to_tw_proto()) + }, + BinanceMessageEnum::RefundHTLTOrder(m) => { + BinanceMessageProto::refundHTLT_order(m.to_tw_proto()) + }, + BinanceMessageEnum::SendOrder(m) => BinanceMessageProto::send_order(m.to_tw_proto()), + BinanceMessageEnum::SideDelegateOrder(m) => { + BinanceMessageProto::side_delegate_order(m.to_tw_proto()) + }, + BinanceMessageEnum::SideRedelegateOrder(m) => { + BinanceMessageProto::side_redelegate_order(m.to_tw_proto()) + }, + BinanceMessageEnum::SideUndelegateOrder(m) => { + BinanceMessageProto::side_undelegate_order(m.to_tw_proto()) + }, + BinanceMessageEnum::StakeMigrationOrder(m) => { + BinanceMessageProto::side_stake_migration_order(m.to_tw_proto()) + }, + BinanceMessageEnum::TimeLockOrder(m) => { + BinanceMessageProto::time_lock_order(m.to_tw_proto()) + }, + BinanceMessageEnum::TimeRelockOrder(m) => { + BinanceMessageProto::time_relock_order(m.to_tw_proto()) + }, + BinanceMessageEnum::TimeUnlockOrder(m) => { + BinanceMessageProto::time_unlock_order(m.to_tw_proto()) + }, + BinanceMessageEnum::TokenFreezeOrder(m) => { + BinanceMessageProto::freeze_order(m.to_tw_proto()) + }, + BinanceMessageEnum::TokenUnfreezeOrder(m) => { + BinanceMessageProto::unfreeze_order(m.to_tw_proto()) + }, + BinanceMessageEnum::TokenIssueOrder(m) => { + BinanceMessageProto::issue_order(m.to_tw_proto()) + }, + BinanceMessageEnum::TokenMintOrder(m) => { + BinanceMessageProto::mint_order(m.to_tw_proto()) + }, + BinanceMessageEnum::TokenBurnOrder(m) => { + BinanceMessageProto::burn_order(m.to_tw_proto()) + }, + BinanceMessageEnum::NewTradeOrder(m) => { + BinanceMessageProto::trade_order(m.to_tw_proto()) + }, + BinanceMessageEnum::CancelTradeOrder(m) => { + BinanceMessageProto::cancel_trade_order(m.to_tw_proto()) + }, + BinanceMessageEnum::TransferOutOrder(m) => { + BinanceMessageProto::transfer_out_order(m.to_tw_proto()) + }, + } + } +} + +impl<'a> AsRef for BinanceMessageEnum { + fn as_ref(&self) -> &(dyn BinanceMessage + 'a) { + match self { + BinanceMessageEnum::HTLTOrder(m) => m, + BinanceMessageEnum::DepositHTLTOrder(m) => m, + BinanceMessageEnum::ClaimHTLTOrder(m) => m, + BinanceMessageEnum::RefundHTLTOrder(m) => m, + BinanceMessageEnum::SendOrder(m) => m, + BinanceMessageEnum::SideDelegateOrder(m) => m, + BinanceMessageEnum::SideRedelegateOrder(m) => m, + BinanceMessageEnum::SideUndelegateOrder(m) => m, + BinanceMessageEnum::StakeMigrationOrder(m) => m, + BinanceMessageEnum::TimeLockOrder(m) => m, + BinanceMessageEnum::TimeRelockOrder(m) => m, + BinanceMessageEnum::TimeUnlockOrder(m) => m, + BinanceMessageEnum::TokenFreezeOrder(m) => m, + BinanceMessageEnum::TokenUnfreezeOrder(m) => m, + BinanceMessageEnum::TokenIssueOrder(m) => m, + BinanceMessageEnum::TokenMintOrder(m) => m, + BinanceMessageEnum::TokenBurnOrder(m) => m, + BinanceMessageEnum::NewTradeOrder(m) => m, + BinanceMessageEnum::CancelTradeOrder(m) => m, + BinanceMessageEnum::TransferOutOrder(m) => m, + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct Token { + /// Amount. + pub amount: i64, + /// Token ID. + pub denom: String, +} + +impl Token { + pub fn serialize_with_string_amount(&self, serializer: S) -> Result + where + S: Serializer, + { + #[derive(Serialize)] + struct TokenWithStringAmount<'a> { + amount: String, + denom: &'a str, + } + + TokenWithStringAmount { + amount: self.amount.to_string(), + denom: &self.denom, + } + .serialize(serializer) + } + + fn from_tw_proto(msg: &Proto::mod_SendOrder::Token<'_>) -> Self { + Token { + denom: msg.denom.to_string(), + amount: msg.amount, + } + } + + fn to_tw_proto(&self) -> Proto::mod_SendOrder::Token<'static> { + Proto::mod_SendOrder::Token { + denom: self.denom.clone().into(), + amount: self.amount, + } + } +} diff --git a/rust/chains/tw_binance/src/transaction/message/send_order.rs b/rust/chains/tw_binance/src/transaction/message/send_order.rs new file mode 100644 index 00000000000..6ffd7d69be2 --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/send_order.rs @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::BinanceAddress; +use crate::amino::AminoEncoder; +use crate::transaction::message::{BinanceMessage, TWBinanceProto, Token}; +use serde::{Deserialize, Serialize}; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::*; +use tw_memory::Data; +use tw_proto::Binance::Proto; + +/// Either an input or output of a `SendOrder`. +#[derive(Deserialize, Serialize)] +pub struct InOut { + /// Source address. + pub address: BinanceAddress, + + /// Input coin amounts. + pub coins: Vec, +} + +impl InOut { + pub fn to_input_proto(&self) -> Proto::mod_SendOrder::Input<'static> { + Proto::mod_SendOrder::Input { + address: self.address.data().into(), + coins: self.coins.iter().map(Token::to_tw_proto).collect(), + } + } + + pub fn to_output_proto(&self) -> Proto::mod_SendOrder::Output<'static> { + Proto::mod_SendOrder::Output { + address: self.address.data().into(), + coins: self.coins.iter().map(Token::to_tw_proto).collect(), + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct SendOrder { + pub inputs: Vec, + pub outputs: Vec, +} + +impl SendOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x2A, 0x2C, 0x87, 0xFA]; +} + +impl BinanceMessage for SendOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for SendOrder { + type Proto<'a> = Proto::SendOrder<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + fn in_out_from_proto( + coin: &dyn CoinContext, + address_key_hash: &[u8], + coins: &[Proto::mod_SendOrder::Token], + ) -> SigningResult { + let address = BinanceAddress::from_key_hash_with_coin(coin, address_key_hash.to_vec())?; + let coins = coins.iter().map(Token::from_tw_proto).collect(); + + Ok(InOut { address, coins }) + } + + let inputs = msg + .inputs + .iter() + .map(|input| in_out_from_proto(coin, &input.address, &input.coins)) + .collect::>>()?; + + let outputs = msg + .outputs + .iter() + .map(|output| in_out_from_proto(coin, &output.address, &output.coins)) + .collect::>>()?; + + Ok(SendOrder { inputs, outputs }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::SendOrder { + inputs: self.inputs.iter().map(InOut::to_input_proto).collect(), + outputs: self.outputs.iter().map(InOut::to_output_proto).collect(), + } + } +} diff --git a/rust/chains/tw_binance/src/transaction/message/side_chain_delegate.rs b/rust/chains/tw_binance/src/transaction/message/side_chain_delegate.rs new file mode 100644 index 00000000000..4684056cede --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/side_chain_delegate.rs @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::BinanceAddress; +use crate::amino::AminoEncoder; +use crate::transaction::message::{BinanceMessage, TWBinanceProto, Token}; +use serde::{Deserialize, Serialize}; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::*; +use tw_evm::address::Address as EthereumAddress; +use tw_memory::Data; +use tw_misc::serde::Typed; +use tw_proto::Binance::Proto; + +pub type SideDelegateOrder = Typed; + +/// cosmos-sdk/MsgSideChainDelegate +#[derive(Deserialize, Serialize)] +pub struct SideDelegateOrderValue { + #[serde(serialize_with = "Token::serialize_with_string_amount")] + pub delegation: Token, + pub delegator_addr: BinanceAddress, + pub side_chain_id: String, + pub validator_addr: BinanceAddress, +} + +impl SideDelegateOrderValue { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0xE3, 0xA0, 0x7F, 0xD2]; + /// cbindgen:ignore + pub const MESSAGE_TYPE: &'static str = "cosmos-sdk/MsgSideChainDelegate"; +} + +impl BinanceMessage for SideDelegateOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&SideDelegateOrderValue::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for SideDelegateOrder { + type Proto<'a> = Proto::SideChainDelegate<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let delegator_addr = + BinanceAddress::from_key_hash_with_coin(coin, msg.delegator_addr.to_vec())?; + let validator_addr = BinanceAddress::new_validator_addr(msg.validator_addr.to_vec())?; + + let delegation = msg + .delegation + .as_ref() + .or_tw_err(SigningErrorType::Error_invalid_params)?; + + let value = SideDelegateOrderValue { + delegator_addr, + validator_addr, + delegation: Token::from_tw_proto(delegation), + side_chain_id: msg.chain_id.to_string(), + }; + Ok(Typed { + ty: SideDelegateOrderValue::MESSAGE_TYPE.to_string(), + value, + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::SideChainDelegate { + delegator_addr: self.value.delegator_addr.data().into(), + validator_addr: self.value.validator_addr.data().into(), + delegation: Some(self.value.delegation.to_tw_proto()), + chain_id: self.value.side_chain_id.clone().into(), + } + } +} + +pub type SideRedelegateOrder = Typed; + +/// cosmos-sdk/MsgSideChainRedelegate +#[derive(Deserialize, Serialize)] +pub struct SideRedelegateOrderValue { + #[serde(serialize_with = "Token::serialize_with_string_amount")] + pub amount: Token, + pub delegator_addr: BinanceAddress, + pub side_chain_id: String, + pub validator_dst_addr: BinanceAddress, + pub validator_src_addr: BinanceAddress, +} + +impl SideRedelegateOrderValue { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0xE3, 0xCE, 0xD3, 0x64]; + /// cbindgen:ignore + pub const MESSAGE_TYPE: &'static str = "cosmos-sdk/MsgSideChainRedelegate"; +} + +impl BinanceMessage for SideRedelegateOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&SideRedelegateOrderValue::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for SideRedelegateOrder { + type Proto<'a> = Proto::SideChainRedelegate<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let delegator_addr = + BinanceAddress::from_key_hash_with_coin(coin, msg.delegator_addr.to_vec())?; + let validator_src_addr = + BinanceAddress::new_validator_addr(msg.validator_src_addr.to_vec())?; + let validator_dst_addr = + BinanceAddress::new_validator_addr(msg.validator_dst_addr.to_vec())?; + + let amount = msg + .amount + .as_ref() + .or_tw_err(SigningErrorType::Error_invalid_params)?; + + let value = SideRedelegateOrderValue { + delegator_addr, + validator_src_addr, + validator_dst_addr, + amount: Token::from_tw_proto(amount), + side_chain_id: msg.chain_id.to_string(), + }; + Ok(Typed { + ty: SideRedelegateOrderValue::MESSAGE_TYPE.to_string(), + value, + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::SideChainRedelegate { + delegator_addr: self.value.delegator_addr.data().into(), + validator_src_addr: self.value.validator_src_addr.data().into(), + validator_dst_addr: self.value.validator_dst_addr.data().into(), + amount: Some(self.value.amount.to_tw_proto()), + chain_id: self.value.side_chain_id.clone().into(), + } + } +} + +pub type SideUndelegateOrder = Typed; + +/// cosmos-sdk/MsgSideChainUndelegate +#[derive(Deserialize, Serialize)] +pub struct SideUndelegateOrderValue { + #[serde(serialize_with = "Token::serialize_with_string_amount")] + pub amount: Token, + pub delegator_addr: BinanceAddress, + pub side_chain_id: String, + pub validator_addr: BinanceAddress, +} + +impl SideUndelegateOrderValue { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x51, 0x4F, 0x7E, 0x0E]; + /// cbindgen:ignore + pub const MESSAGE_TYPE: &'static str = "cosmos-sdk/MsgSideChainUndelegate"; +} + +impl BinanceMessage for SideUndelegateOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&SideUndelegateOrderValue::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for SideUndelegateOrder { + type Proto<'a> = Proto::SideChainUndelegate<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let delegator_addr = + BinanceAddress::from_key_hash_with_coin(coin, msg.delegator_addr.to_vec())?; + let validator_addr = BinanceAddress::new_validator_addr(msg.validator_addr.to_vec())?; + + let amount = msg + .amount + .as_ref() + .or_tw_err(SigningErrorType::Error_invalid_params)?; + + let value = SideUndelegateOrderValue { + delegator_addr, + validator_addr, + amount: Token::from_tw_proto(amount), + side_chain_id: msg.chain_id.to_string(), + }; + Ok(Typed { + ty: SideUndelegateOrderValue::MESSAGE_TYPE.to_string(), + value, + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::SideChainUndelegate { + delegator_addr: self.value.delegator_addr.data().into(), + validator_addr: self.value.validator_addr.data().into(), + amount: Some(self.value.amount.to_tw_proto()), + chain_id: self.value.side_chain_id.clone().into(), + } + } +} + +pub type StakeMigrationOrder = Typed; + +/// https://github.com/bnb-chain/bnc-cosmos-sdk/blob/cf3ab19af300ccd6a6381287c3fae6bf6ac12f5e/x/stake/types/stake_migration.go#L29-L35 +#[derive(Deserialize, Serialize)] +pub struct StakeMigrationOrderValue { + #[serde(serialize_with = "Token::serialize_with_string_amount")] + pub amount: Token, + pub delegator_addr: EthereumAddress, + pub refund_addr: BinanceAddress, + pub validator_dst_addr: EthereumAddress, + pub validator_src_addr: BinanceAddress, +} + +impl StakeMigrationOrderValue { + /// cbindgen:ignore + /// https://github.com/bnb-chain/javascript-sdk/blob/442286ac2923fdfd7cb4fb2299f722ec263c714c/src/types/tx/stdTx.ts#L68 + pub const PREFIX: [u8; 4] = [0x38, 0x58, 0x91, 0x96]; + /// cbindgen:ignore + pub const MESSAGE_TYPE: &'static str = "cosmos-sdk/MsgSideChainStakeMigration"; +} + +impl BinanceMessage for StakeMigrationOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&StakeMigrationOrderValue::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for StakeMigrationOrder { + type Proto<'a> = Proto::SideChainStakeMigration<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let delegator_addr = EthereumAddress::try_from(msg.delegator_addr.as_ref())?; + let refund_addr = BinanceAddress::from_key_hash_with_coin(coin, msg.refund_addr.to_vec())?; + let validator_dst_addr = EthereumAddress::try_from(msg.validator_dst_addr.as_ref())?; + let validator_src_addr = + BinanceAddress::new_validator_addr(msg.validator_src_addr.to_vec())?; + + let amount = msg + .amount + .as_ref() + .or_tw_err(SigningErrorType::Error_invalid_params)?; + + let value = StakeMigrationOrderValue { + amount: Token::from_tw_proto(amount), + delegator_addr, + refund_addr, + validator_dst_addr, + validator_src_addr, + }; + Ok(Typed { + ty: StakeMigrationOrderValue::MESSAGE_TYPE.to_string(), + value, + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::SideChainStakeMigration { + delegator_addr: self.value.delegator_addr.data().into(), + validator_src_addr: self.value.validator_src_addr.data().into(), + validator_dst_addr: self.value.validator_dst_addr.data().into(), + refund_addr: self.value.refund_addr.data().into(), + amount: Some(self.value.amount.to_tw_proto()), + } + } +} diff --git a/rust/chains/tw_binance/src/transaction/message/time_lock_order.rs b/rust/chains/tw_binance/src/transaction/message/time_lock_order.rs new file mode 100644 index 00000000000..9e2e4e21e8c --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/time_lock_order.rs @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::BinanceAddress; +use crate::amino::AminoEncoder; +use crate::transaction::message::{BinanceMessage, TWBinanceProto, Token}; +use serde::{Deserialize, Serialize}; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::*; +use tw_memory::Data; +use tw_proto::Binance::Proto; + +#[derive(Deserialize, Serialize)] +pub struct TimeLockOrder { + pub amount: Vec, + pub description: String, + pub from: BinanceAddress, + pub lock_time: i64, +} + +impl TimeLockOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x07, 0x92, 0x15, 0x31]; +} + +impl BinanceMessage for TimeLockOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for TimeLockOrder { + type Proto<'a> = Proto::TimeLockOrder<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let from = BinanceAddress::from_key_hash_with_coin(coin, msg.from_address.to_vec())?; + let amount = msg.amount.iter().map(Token::from_tw_proto).collect(); + + Ok(TimeLockOrder { + from, + description: msg.description.to_string(), + amount, + lock_time: msg.lock_time, + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::TimeLockOrder { + from_address: self.from.data().into(), + description: self.description.clone().into(), + amount: self.amount.iter().map(Token::to_tw_proto).collect(), + lock_time: self.lock_time, + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct TimeRelockOrder { + /// If the amount is empty or omitted, set null to avoid signature verification error. + pub amount: Option>, + pub description: String, + pub from: BinanceAddress, + pub lock_time: i64, + pub time_lock_id: i64, +} + +impl TimeRelockOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x50, 0x47, 0x11, 0xDA]; +} + +impl BinanceMessage for TimeRelockOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for TimeRelockOrder { + type Proto<'a> = Proto::TimeRelockOrder<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let from = BinanceAddress::from_key_hash_with_coin(coin, msg.from_address.to_vec())?; + + let amount = if msg.amount.is_empty() { + None + } else { + Some(msg.amount.iter().map(Token::from_tw_proto).collect()) + }; + + Ok(TimeRelockOrder { + from, + time_lock_id: msg.id, + description: msg.description.to_string(), + amount, + lock_time: msg.lock_time, + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + let amount = match self.amount { + Some(ref tokens) => tokens.iter().map(Token::to_tw_proto).collect(), + None => Vec::default(), + }; + + Proto::TimeRelockOrder { + from_address: self.from.data().into(), + id: self.time_lock_id, + description: self.description.clone().into(), + amount, + lock_time: self.lock_time, + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct TimeUnlockOrder { + pub from: BinanceAddress, + pub time_lock_id: i64, +} + +impl TimeUnlockOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0xC4, 0x05, 0x0C, 0x6C]; +} + +impl BinanceMessage for TimeUnlockOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for TimeUnlockOrder { + type Proto<'a> = Proto::TimeUnlockOrder<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let from = BinanceAddress::from_key_hash_with_coin(coin, msg.from_address.to_vec())?; + Ok(TimeUnlockOrder { + from, + time_lock_id: msg.id, + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::TimeUnlockOrder { + from_address: self.from.data().into(), + id: self.time_lock_id, + } + } +} diff --git a/rust/chains/tw_binance/src/transaction/message/token_order.rs b/rust/chains/tw_binance/src/transaction/message/token_order.rs new file mode 100644 index 00000000000..ec88ba88e26 --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/token_order.rs @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::BinanceAddress; +use crate::amino::AminoEncoder; +use crate::transaction::message::{BinanceMessage, TWBinanceProto}; +use serde::{Deserialize, Serialize}; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::*; +use tw_memory::Data; +use tw_proto::Binance::Proto; + +#[derive(Deserialize, Serialize)] +pub struct TokenFreezeOrder { + pub amount: i64, + pub from: BinanceAddress, + pub symbol: String, +} + +impl TokenFreezeOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0xE7, 0x74, 0xB3, 0x2D]; +} + +impl BinanceMessage for TokenFreezeOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for TokenFreezeOrder { + type Proto<'a> = Proto::TokenFreezeOrder<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let from = BinanceAddress::from_key_hash_with_coin(coin, msg.from.to_vec())?; + Ok(TokenFreezeOrder { + from, + symbol: msg.symbol.to_string(), + amount: msg.amount, + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::TokenFreezeOrder { + from: self.from.data().into(), + symbol: self.symbol.clone().into(), + amount: self.amount, + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct TokenUnfreezeOrder { + pub amount: i64, + pub from: BinanceAddress, + pub symbol: String, +} + +impl TokenUnfreezeOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x65, 0x15, 0xFF, 0x0D]; +} + +impl BinanceMessage for TokenUnfreezeOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for TokenUnfreezeOrder { + type Proto<'a> = Proto::TokenUnfreezeOrder<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let from = BinanceAddress::from_key_hash_with_coin(coin, msg.from.to_vec())?; + Ok(TokenUnfreezeOrder { + from, + symbol: msg.symbol.to_string(), + amount: msg.amount, + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::TokenUnfreezeOrder { + from: self.from.data().into(), + symbol: self.symbol.clone().into(), + amount: self.amount, + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct TokenIssueOrder { + pub from: BinanceAddress, + pub mintable: bool, + pub name: String, + pub symbol: String, + pub total_supply: i64, +} + +impl TokenIssueOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x17, 0xEF, 0xAB, 0x80]; +} + +impl BinanceMessage for TokenIssueOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for TokenIssueOrder { + type Proto<'a> = Proto::TokenIssueOrder<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let from = BinanceAddress::from_key_hash_with_coin(coin, msg.from.to_vec())?; + Ok(TokenIssueOrder { + from, + name: msg.name.to_string(), + symbol: msg.symbol.to_string(), + total_supply: msg.total_supply, + mintable: msg.mintable, + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::TokenIssueOrder { + from: self.from.data().into(), + name: self.name.clone().into(), + symbol: self.symbol.clone().into(), + total_supply: self.total_supply, + mintable: self.mintable, + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct TokenMintOrder { + pub amount: i64, + pub from: BinanceAddress, + pub symbol: String, +} + +impl TokenMintOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x46, 0x7E, 0x08, 0x29]; +} + +impl BinanceMessage for TokenMintOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for TokenMintOrder { + type Proto<'a> = Proto::TokenMintOrder<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let from = BinanceAddress::from_key_hash_with_coin(coin, msg.from.to_vec())?; + Ok(TokenMintOrder { + from, + symbol: msg.symbol.to_string(), + amount: msg.amount, + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::TokenMintOrder { + from: self.from.data().into(), + symbol: self.symbol.clone().into(), + amount: self.amount, + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct TokenBurnOrder { + pub amount: i64, + pub from: BinanceAddress, + pub symbol: String, +} + +impl TokenBurnOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x7E, 0xD2, 0xD2, 0xA0]; +} + +impl BinanceMessage for TokenBurnOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for TokenBurnOrder { + type Proto<'a> = Proto::TokenBurnOrder<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let from = BinanceAddress::from_key_hash_with_coin(coin, msg.from.to_vec())?; + Ok(TokenBurnOrder { + from, + symbol: msg.symbol.to_string(), + amount: msg.amount, + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::TokenBurnOrder { + from: self.from.data().into(), + symbol: self.symbol.clone().into(), + amount: self.amount, + } + } +} diff --git a/rust/chains/tw_binance/src/transaction/message/trade_order.rs b/rust/chains/tw_binance/src/transaction/message/trade_order.rs new file mode 100644 index 00000000000..0add4dcd87b --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/trade_order.rs @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::BinanceAddress; +use crate::amino::AminoEncoder; +use crate::transaction::message::{BinanceMessage, TWBinanceProto}; +use serde::{Deserialize, Serialize}; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::*; +use tw_memory::Data; +use tw_proto::Binance::Proto; + +#[repr(i64)] +#[derive( + Clone, Copy, serde_repr::Deserialize_repr, serde_repr::Serialize_repr, strum_macros::FromRepr, +)] +pub enum OrderType { + /// https://github.com/bnb-chain/python-sdk/blob/0f6b8a6077f486b26eda3e448f3e84ef35bfff75/binance_chain/constants.py#L62 + Limit = 2, +} + +#[derive(Deserialize, Serialize)] +pub struct NewTradeOrder { + /// Order id, optional. + pub id: String, + /// Order type. + #[serde(rename = "ordertype")] + pub order_type: OrderType, + /// Price of the order, which is the real price multiplied by 1e8 (10^8) and rounded to integer. + pub price: i64, + /// Quantity of the order, which is the real price multiplied by 1e8 (10^8) and rounded to integer. + pub quantity: i64, + /// Originating address. + pub sender: BinanceAddress, + /// 1 for buy and 2 for sell. + pub side: i64, + /// Symbol for trading pair in full name of the tokens. + pub symbol: String, + /// 1 for Good Till Expire(GTE) order and 3 for Immediate Or Cancel (IOC). + #[serde(rename = "timeinforce")] + pub time_in_force: i64, +} + +impl NewTradeOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0xCE, 0x6D, 0xC0, 0x43]; +} + +impl BinanceMessage for NewTradeOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for NewTradeOrder { + type Proto<'a> = Proto::TradeOrder<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let order_type = OrderType::from_repr(msg.ordertype) + .or_tw_err(SigningErrorType::Error_invalid_params)?; + let sender = BinanceAddress::from_key_hash_with_coin(coin, msg.sender.to_vec())?; + + Ok(NewTradeOrder { + id: msg.id.to_string(), + order_type, + price: msg.price, + quantity: msg.quantity, + sender, + side: msg.side, + symbol: msg.symbol.to_string(), + time_in_force: msg.timeinforce, + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::TradeOrder { + id: self.id.clone().into(), + ordertype: self.order_type as i64, + price: self.price, + quantity: self.quantity, + sender: self.sender.data().into(), + side: self.side, + symbol: self.symbol.clone().into(), + timeinforce: self.time_in_force, + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct CancelTradeOrder { + /// Order id to cancel. + pub refid: String, + /// Originating address. + pub sender: BinanceAddress, + /// Symbol for trading pair in full name of the tokens. + pub symbol: String, +} + +impl CancelTradeOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x16, 0x6E, 0x68, 0x1B]; +} + +impl BinanceMessage for CancelTradeOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for CancelTradeOrder { + type Proto<'a> = Proto::CancelTradeOrder<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let sender = BinanceAddress::from_key_hash_with_coin(coin, msg.sender.to_vec())?; + Ok(CancelTradeOrder { + sender, + symbol: msg.symbol.to_string(), + refid: msg.refid.to_string(), + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::CancelTradeOrder { + sender: self.sender.data().into(), + symbol: self.symbol.clone().into(), + refid: self.refid.clone().into(), + } + } +} diff --git a/rust/chains/tw_binance/src/transaction/message/tranfer_out_order.rs b/rust/chains/tw_binance/src/transaction/message/tranfer_out_order.rs new file mode 100644 index 00000000000..30dfef73f56 --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/tranfer_out_order.rs @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::BinanceAddress; +use crate::amino::AminoEncoder; +use crate::transaction::message::{BinanceMessage, TWBinanceProto, Token}; +use serde::{Deserialize, Serialize}; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::*; +use tw_evm::address::Address as EthereumAddress; +use tw_hash::H160; +use tw_memory::Data; +use tw_proto::Binance::Proto; + +#[derive(Deserialize, Serialize)] +pub struct TransferOutOrder { + pub amount: Token, + pub expire_time: i64, + pub from: BinanceAddress, + pub to: EthereumAddress, +} + +impl TransferOutOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x80, 0x08, 0x19, 0xC0]; +} + +impl BinanceMessage for TransferOutOrder { + fn to_amino_protobuf(&self) -> SigningResult { + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&self.to_tw_proto())? + .encode()) + } +} + +impl TWBinanceProto for TransferOutOrder { + type Proto<'a> = Proto::TransferOut<'a>; + + fn from_tw_proto(coin: &dyn CoinContext, msg: &Self::Proto<'_>) -> SigningResult { + let from = BinanceAddress::from_key_hash_with_coin(coin, msg.from.to_vec())?; + + let to_bytes = + H160::try_from(msg.to.as_ref()).tw_err(|_| SigningErrorType::Error_invalid_address)?; + let to = EthereumAddress::from_bytes(to_bytes); + + let amount_proto = msg + .amount + .as_ref() + .or_tw_err(SigningErrorType::Error_invalid_params)?; + + Ok(TransferOutOrder { + from, + to, + amount: Token::from_tw_proto(amount_proto), + expire_time: msg.expire_time, + }) + } + + fn to_tw_proto(&self) -> Self::Proto<'static> { + Proto::TransferOut { + from: self.from.data().into(), + to: self.to.data().into(), + amount: Some(self.amount.to_tw_proto()), + expire_time: self.expire_time, + } + } +} diff --git a/rust/chains/tw_binance/src/transaction/mod.rs b/rust/chains/tw_binance/src/transaction/mod.rs new file mode 100644 index 00000000000..fbd24f33ebe --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/mod.rs @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::signature::BinanceSignature; +use crate::transaction::message::BinanceMessageEnum; +use serde::{Deserialize, Serialize}; +use tw_cosmos_sdk::public_key::secp256k1::Secp256PublicKey; +use tw_memory::Data; +use tw_misc::serde::as_string; + +pub mod message; + +#[derive(Deserialize, Serialize)] +pub struct UnsignedTransaction { + #[serde(with = "as_string")] + pub account_number: i64, + pub chain_id: String, + pub data: Option, + pub memo: String, + pub msgs: Vec, + #[serde(with = "as_string")] + pub sequence: i64, + #[serde(with = "as_string")] + pub source: i64, +} + +impl UnsignedTransaction { + pub fn into_signed(self, signer: SignerInfo) -> SignedTransaction { + SignedTransaction { + unsigned: self, + signer, + } + } +} + +pub struct SignerInfo { + pub public_key: Secp256PublicKey, + pub signature: BinanceSignature, +} + +pub struct SignedTransaction { + pub unsigned: UnsignedTransaction, + pub signer: SignerInfo, +} diff --git a/rust/chains/tw_bitcoin/Cargo.toml b/rust/chains/tw_bitcoin/Cargo.toml new file mode 100644 index 00000000000..1d5571814b0 --- /dev/null +++ b/rust/chains/tw_bitcoin/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "tw_bitcoin" +version = "0.1.0" +edition = "2021" + +[dependencies] +bitcoin = { version = "0.30.0", features = ["rand-std"] } +secp256k1 = { version = "0.27.0", features = ["global-context", "rand-std"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tw_bech32_address = { path = "../../tw_bech32_address" } +tw_base58_address = { path = "../../tw_base58_address" } +tw_coin_entry = { path = "../../tw_coin_entry", features = ["test-utils"] } +tw_encoding = { path = "../../tw_encoding" } +tw_hash = { path = "../../tw_hash" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_misc = { path = "../../tw_misc" } +tw_proto = { path = "../../tw_proto" } +tw_utxo = { path = "../../frameworks/tw_utxo" } diff --git a/rust/chains/tw_bitcoin/src/entry.rs b/rust/chains/tw_bitcoin/src/entry.rs new file mode 100644 index 00000000000..67d597814b4 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/entry.rs @@ -0,0 +1,125 @@ +use crate::modules::compiler::BitcoinCompiler; +use crate::modules::planner::BitcoinPlanner; +use crate::modules::psbt_planner::PsbtPlanner; +use crate::modules::signer::BitcoinSigner; +use crate::modules::transaction_util::BitcoinTransactionUtil; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::wallet_connector::NoWalletConnector; +use tw_keypair::tw::PublicKey; +use tw_proto::BitcoinV2::Proto; +use tw_utxo::address::standard_bitcoin::{StandardBitcoinAddress, StandardBitcoinPrefix}; +use tw_utxo::utxo_entry::UtxoEntry; + +pub struct BitcoinEntry; + +impl CoinEntry for BitcoinEntry { + type AddressPrefix = StandardBitcoinPrefix; + type Address = StandardBitcoinAddress; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = Proto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = BitcoinPlanner; + type MessageSigner = NoMessageSigner; + type WalletConnector = NoWalletConnector; + type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = BitcoinTransactionUtil; + + #[inline] + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + prefix: Option, + ) -> AddressResult { + StandardBitcoinAddress::from_str_with_coin_and_prefix(coin, address, prefix) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + StandardBitcoinAddress::from_str(address) + } + + #[inline] + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: PublicKey, + derivation: Derivation, + prefix: Option, + ) -> AddressResult { + StandardBitcoinAddress::derive_as_tw(coin, &public_key, derivation, prefix) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, proto: Self::SigningInput<'_>) -> Self::SigningOutput { + BitcoinSigner::sign(coin, &proto) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + proto: Proto::SigningInput<'_>, + ) -> Self::PreSigningOutput { + BitcoinCompiler::preimage_hashes(coin, proto) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + proto: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + BitcoinCompiler::compile(coin, proto, signatures, public_keys) + } + + #[inline] + fn plan_builder(&self) -> Option { + Some(BitcoinPlanner) + } + + #[inline] + fn transaction_util(&self) -> Option { + Some(BitcoinTransactionUtil) + } +} + +impl UtxoEntry for BitcoinEntry { + type PsbtSigningInput<'a> = Proto::PsbtSigningInput<'a>; + type PsbtSigningOutput = Proto::PsbtSigningOutput<'static>; + type PsbtTransactionPlan = Proto::TransactionPlan<'static>; + + #[inline] + fn sign_psbt( + &self, + coin: &dyn CoinContext, + input: Self::PsbtSigningInput<'_>, + ) -> Self::PsbtSigningOutput { + BitcoinSigner::sign_psbt(coin, &input) + } + + #[inline] + fn plan_psbt( + &self, + coin: &dyn CoinContext, + input: Self::PsbtSigningInput<'_>, + ) -> Self::PsbtTransactionPlan { + PsbtPlanner::plan_psbt(coin, &input) + } +} diff --git a/rust/chains/tw_bitcoin/src/lib.rs b/rust/chains/tw_bitcoin/src/lib.rs new file mode 100644 index 00000000000..1b6998ea2c6 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/lib.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod entry; +pub mod modules; diff --git a/rust/chains/tw_bitcoin/src/modules/compiler.rs b/rust/chains/tw_bitcoin/src/modules/compiler.rs new file mode 100644 index 00000000000..ca4a4e745b8 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/compiler.rs @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::protobuf_builder::ProtobufBuilder; +use crate::modules::signing_request::SigningRequestBuilder; +use std::borrow::Cow; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_proto::BitcoinV2::Proto; +use tw_proto::BitcoinV2::Proto::mod_PreSigningOutput::{ + SigningMethod as ProtoSigningMethod, TaprootTweak as ProtoTaprootTweak, +}; +use tw_utxo::modules::sighash_computer::{SighashComputer, TaprootTweak, TxPreimage}; +use tw_utxo::modules::sighash_verifier::SighashVerifier; +use tw_utxo::modules::tx_compiler::TxCompiler; +use tw_utxo::modules::tx_planner::TxPlanner; +use tw_utxo::modules::utxo_selector::SelectResult; +use tw_utxo::signing_mode::SigningMethod; +use tw_utxo::transaction::transaction_interface::TransactionInterface; + +pub struct BitcoinCompiler; + +impl BitcoinCompiler { + /// Please note that [`Proto::SigningInput::public_key`] must be set. + /// If the public key should be derived from a private key, please do it before this method is called. + #[inline] + pub fn preimage_hashes( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> Proto::PreSigningOutput<'static> { + Self::preimage_hashes_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(Proto::PreSigningOutput, e)) + } + + fn preimage_hashes_impl( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let request = SigningRequestBuilder::build(coin, &input)?; + let SelectResult { unsigned_tx, .. } = TxPlanner::plan(request)?; + + let TxPreimage { sighashes } = SighashComputer::preimage_tx(&unsigned_tx)?; + + let sighashes: Vec<_> = sighashes + .into_iter() + .map(|sighash| Proto::mod_PreSigningOutput::Sighash { + public_key: Cow::from(sighash.signer_pubkey), + sighash: Cow::from(sighash.sighash.to_vec()), + signing_method: signing_method(sighash.signing_method), + tweak: taproot_tweak(sighash.taproot_tweak), + }) + .collect(); + + Ok(Proto::PreSigningOutput { + sighashes, + ..Proto::PreSigningOutput::default() + }) + } + + #[inline] + pub fn compile( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + Self::compile_impl(coin, input, signatures, public_keys) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn compile_impl( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + _public_keys: Vec, + ) -> SigningResult> { + let request = SigningRequestBuilder::build(coin, &input)?; + let SelectResult { unsigned_tx, plan } = TxPlanner::plan(request)?; + + SighashVerifier::verify_signatures(&unsigned_tx, &signatures)?; + let signed_tx = TxCompiler::compile(unsigned_tx, &signatures)?; + let tx_proto = ProtobufBuilder::tx_to_proto(&signed_tx); + + Ok(Proto::SigningOutput { + transaction: Some(tx_proto), + encoded: Cow::from(signed_tx.encode_out()), + txid: Cow::from(signed_tx.txid()), + // `vsize` could have been changed after the transaction being signed. + vsize: signed_tx.vsize() as u64, + weight: signed_tx.weight() as u64, + // `fee` should haven't been changed since it's a difference between `sum(inputs)` and `sum(outputs)`. + fee: plan.fee_estimate, + ..Proto::SigningOutput::default() + }) + } +} + +pub fn signing_method(s: SigningMethod) -> ProtoSigningMethod { + match s { + SigningMethod::Legacy => ProtoSigningMethod::Legacy, + SigningMethod::Segwit => ProtoSigningMethod::Segwit, + SigningMethod::Taproot => ProtoSigningMethod::Taproot, + } +} + +pub fn taproot_tweak(tweak: Option) -> Option> { + tweak.map(|tweak| { + let merkle_root = match tweak.merkle_root { + Some(root) => root.to_vec(), + None => Vec::default(), + }; + ProtoTaprootTweak { + merkle_root: Cow::from(merkle_root), + } + }) +} diff --git a/rust/chains/tw_bitcoin/src/modules/mod.rs b/rust/chains/tw_bitcoin/src/modules/mod.rs new file mode 100644 index 00000000000..d275e69e9de --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/mod.rs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod compiler; +pub mod planner; +pub mod protobuf_builder; +pub mod psbt; +pub mod psbt_planner; +pub mod psbt_request; +pub mod signer; +pub mod signing_request; +pub mod transaction_util; +pub mod tx_builder; diff --git a/rust/chains/tw_bitcoin/src/modules/planner.rs b/rust/chains/tw_bitcoin/src/modules/planner.rs new file mode 100644 index 00000000000..a109ccd958f --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/planner.rs @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::signing_request::SigningRequestBuilder; +use crate::modules::tx_builder::utxo_protobuf::parse_out_point; +use std::borrow::Cow; +use std::collections::HashMap; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::plan_builder::PlanBuilder; +use tw_coin_entry::signing_output_error; +use tw_proto::BitcoinV2::Proto; +use tw_utxo::modules::tx_planner::TxPlanner; +use tw_utxo::modules::utxo_selector::SelectResult; + +pub struct BitcoinPlanner; + +impl BitcoinPlanner { + pub fn plan_impl<'a>( + coin: &dyn CoinContext, + input: &Proto::SigningInput<'a>, + ) -> SigningResult> { + let request = SigningRequestBuilder::build(coin, input)?; + let SelectResult { unsigned_tx, plan } = TxPlanner::plan(request)?; + + // Prepare a map of source Inputs Proto `{ OutPoint -> Input }`. + // It will be used to find a Input Proto by its `OutPoint`. + let mut inputs_map = HashMap::with_capacity(input.inputs.len()); + for utxo in input.inputs.iter() { + let key = parse_out_point(&utxo.out_point)?; + if inputs_map.insert(key, utxo).is_some() { + // Found a duplicate UTXO. Return an error. + return SigningError::err(SigningErrorType::Error_invalid_utxo) + .context("Provided duplicate UTXOs with the same OutPoint"); + } + } + + // Fill out the selected Inputs Proto. + let mut selected_inputs_proto = Vec::with_capacity(unsigned_tx.inputs().len()); + for selected_utxo in unsigned_tx.inputs() { + let utxo_proto = inputs_map + .get(&selected_utxo.previous_output) + .or_tw_err(SigningErrorType::Error_internal) + .context("Planned transaction contains an unknown UTXO")?; + selected_inputs_proto.push((*utxo_proto).clone()); + } + + // Fill out the Output Proto. + let mut outputs_proto = Vec::with_capacity(unsigned_tx.transaction().outputs.len()); + for selected_output in unsigned_tx.transaction().outputs.iter() { + // For now, just provide a scriptPubkey as is. + // Later it's probably worth to return the same output builders as in `SigningInput`. + let to_recipient = Proto::mod_Output::OneOfto_recipient::custom_script_pubkey( + Cow::from(selected_output.script_pubkey.to_vec()), + ); + + outputs_proto.push(Proto::Output { + value: selected_output.value, + to_recipient, + }) + } + + Ok(Proto::TransactionPlan { + inputs: selected_inputs_proto, + outputs: outputs_proto, + available_amount: plan.total_spend, + send_amount: plan.total_send, + vsize_estimate: plan.vsize_estimate as u64, + fee_estimate: plan.fee_estimate, + change: plan.change, + ..Proto::TransactionPlan::default() + }) + } +} + +impl PlanBuilder for BitcoinPlanner { + type SigningInput<'a> = Proto::SigningInput<'a>; + type Plan<'a> = Proto::TransactionPlan<'a>; + + fn plan<'a>(&self, coin: &dyn CoinContext, input: &Self::SigningInput<'a>) -> Self::Plan<'a> { + Self::plan_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(Proto::TransactionPlan, e)) + } +} diff --git a/rust/chains/tw_bitcoin/src/modules/protobuf_builder/mod.rs b/rust/chains/tw_bitcoin/src/modules/protobuf_builder/mod.rs new file mode 100644 index 00000000000..dc25bc975e5 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/protobuf_builder/mod.rs @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::borrow::Cow; +use tw_proto::BitcoinV2::Proto; +use tw_utxo::script::{Script, Witness}; +use tw_utxo::transaction::standard_transaction::{ + Transaction, TransactionInput, TransactionOutput, +}; +use tw_utxo::transaction::transaction_interface::TransactionInterface; + +pub struct ProtobufBuilder; + +impl ProtobufBuilder { + pub fn tx_to_proto(tx: &Transaction) -> Proto::Transaction<'static> { + let inputs = tx.inputs().iter().map(Self::tx_input_to_proto).collect(); + let outputs = tx.outputs().iter().map(Self::tx_output_to_proto).collect(); + + Proto::Transaction { + version: tx.version(), + lock_time: tx.locktime, + inputs, + outputs, + } + } + + fn tx_input_to_proto( + input: &TransactionInput, + ) -> Proto::mod_Transaction::TransactionInput<'static> { + Proto::mod_Transaction::TransactionInput { + out_point: Some(Proto::OutPoint { + hash: Cow::from(input.previous_output.hash.to_vec()), + vout: input.previous_output.index, + }), + sequence: input.sequence, + script_sig: Self::script_data(&input.script_sig), + witness_items: Self::witness_to_proto(&input.witness), + } + } + + fn tx_output_to_proto( + output: &TransactionOutput, + ) -> Proto::mod_Transaction::TransactionOutput<'static> { + Proto::mod_Transaction::TransactionOutput { + script_pubkey: Self::script_data(&output.script_pubkey), + value: output.value, + } + } + + fn witness_to_proto(witness: &Witness) -> Vec> { + witness.as_items().iter().map(Self::script_data).collect() + } + + fn script_data(script: &Script) -> Cow<'static, [u8]> { + Cow::from(script.to_vec()) + } +} diff --git a/rust/chains/tw_bitcoin/src/modules/psbt.rs b/rust/chains/tw_bitcoin/src/modules/psbt.rs new file mode 100644 index 00000000000..58bb5d54a58 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/psbt.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use bitcoin::psbt::Psbt; +use tw_utxo::transaction::standard_transaction::Transaction; + +/// Finalizes the [Partially Signed Bitcoin Transaction](Psbt) +/// by updating the final `script_sig` and/or `witness`. +pub fn update_psbt_signed(psbt: &mut Psbt, signed_tx: &Transaction) { + for (signed_txin, utxo_psbt) in signed_tx.inputs.iter().zip(psbt.inputs.iter_mut()) { + if !signed_txin.script_sig.is_empty() { + utxo_psbt.final_script_sig = Some(bitcoin::ScriptBuf::from_bytes( + signed_txin.script_sig.to_vec(), + )); + } + + if !signed_txin.witness.is_empty() { + let mut final_witness = bitcoin::Witness::new(); + for witness_item in signed_txin.witness.as_items() { + final_witness.push(bitcoin::ScriptBuf::from_bytes(witness_item.to_vec())); + } + utxo_psbt.final_script_witness = Some(final_witness); + } + } +} diff --git a/rust/chains/tw_bitcoin/src/modules/psbt_planner.rs b/rust/chains/tw_bitcoin/src/modules/psbt_planner.rs new file mode 100644 index 00000000000..a57945daeef --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/psbt_planner.rs @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::psbt_request::PsbtRequest; +use crate::modules::signing_request::SigningRequestBuilder; +use crate::modules::tx_builder::script_parser::StandardScriptParser; +use crate::modules::tx_builder::BitcoinChainInfo; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_proto::BitcoinV2::Proto; +use tw_proto::BitcoinV2::Proto::mod_Input::OneOfclaiming_script as ClaimingScriptProto; +use tw_proto::BitcoinV2::Proto::mod_Output::OneOfto_recipient as ToRecipientProto; +use tw_utxo::transaction::standard_transaction::{TransactionInput, TransactionOutput}; +use tw_utxo::transaction::transaction_interface::TransactionInterface; +use tw_utxo::transaction::UtxoToSign; + +pub struct PsbtPlanner; + +impl PsbtPlanner { + pub fn plan_psbt( + coin: &dyn CoinContext, + input: &Proto::PsbtSigningInput, + ) -> Proto::TransactionPlan<'static> { + Self::plan_psbt_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(Proto::TransactionPlan, e)) + } + + pub fn plan_psbt_impl( + coin: &dyn CoinContext, + input: &Proto::PsbtSigningInput, + ) -> SigningResult> { + let chain_info = SigningRequestBuilder::chain_info(coin, &input.chain_info)?; + let PsbtRequest { unsigned_tx, .. } = PsbtRequest::build(input)?; + + let total_input = unsigned_tx.total_input()?; + let total_output = unsigned_tx.total_output()?; + let fee_estimate = total_input + .checked_sub(total_output) + .or_tw_err(SigningErrorType::Error_not_enough_utxos) + .context("PSBT sum(input) < sum(output)")?; + + let vsize_estimate = unsigned_tx.estimate_transaction().vsize() as u64; + + let inputs: Vec<_> = unsigned_tx + .input_args() + .iter() + .zip(unsigned_tx.inputs()) + .map(|(unsigned_txin, txin)| Self::utxo_to_proto(unsigned_txin, txin, &chain_info)) + .collect::>()?; + + let outputs: Vec<_> = unsigned_tx + .outputs() + .iter() + .map(|txout| Self::output_to_proto(txout, &chain_info)) + .collect::>()?; + + Ok(Proto::TransactionPlan { + inputs, + outputs, + available_amount: total_input, + send_amount: total_input, + vsize_estimate, + fee_estimate, + change: 0, + ..Proto::TransactionPlan::default() + }) + } + + pub fn utxo_to_proto( + unsigned_txin: &UtxoToSign, + txin: &TransactionInput, + chain_info: &BitcoinChainInfo, + ) -> SigningResult> { + let out_point = Proto::OutPoint { + hash: txin.previous_output.hash.to_vec().into(), + vout: txin.previous_output.index, + }; + let sequence = Proto::mod_Input::Sequence { + sequence: txin.sequence, + }; + + let from_address = StandardScriptParser + .parse(&unsigned_txin.prevout_script_pubkey)? + .try_to_address(chain_info)? + .or_tw_err(SigningErrorType::Error_invalid_utxo) + .context("Unexpected UTXO scriptPubkey")? + .to_string(); + + Ok(Proto::Input { + out_point: Some(out_point), + value: unsigned_txin.amount, + sighash_type: unsigned_txin.sighash_ty.raw_sighash(), + sequence: Some(sequence), + claiming_script: ClaimingScriptProto::receiver_address(from_address.into()), + }) + } + + pub fn output_to_proto( + output: &TransactionOutput, + chain_info: &BitcoinChainInfo, + ) -> SigningResult> { + let to_recipient = match StandardScriptParser + .parse(&output.script_pubkey)? + .try_to_address(chain_info)? + { + Some(to_addr) => ToRecipientProto::to_address(to_addr.to_string().into()), + // Cannot convert the output scriptPubkey into an address. Return it as is. + None => ToRecipientProto::custom_script_pubkey(output.script_pubkey.to_vec().into()), + }; + + Ok(Proto::Output { + value: output.value, + to_recipient, + }) + } +} diff --git a/rust/chains/tw_bitcoin/src/modules/psbt_request/mod.rs b/rust/chains/tw_bitcoin/src/modules/psbt_request/mod.rs new file mode 100644 index 00000000000..dcf92cbe366 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/psbt_request/mod.rs @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::psbt_request::output_psbt::OutputPsbt; +use crate::modules::psbt_request::utxo_psbt::UtxoPsbt; +use crate::modules::tx_builder::public_keys::PublicKeys; +use bitcoin::psbt::Psbt; +use tw_coin_entry::error::prelude::*; +use tw_proto::BitcoinV2::Proto; +use tw_utxo::transaction::standard_transaction::builder::TransactionBuilder; +use tw_utxo::transaction::standard_transaction::Transaction; +use tw_utxo::transaction::unsigned_transaction::UnsignedTransaction; + +pub mod output_psbt; +pub mod utxo_psbt; + +pub struct PsbtRequest { + pub psbt: Psbt, + pub unsigned_tx: UnsignedTransaction, +} + +impl PsbtRequest { + pub fn build(input: &Proto::PsbtSigningInput) -> SigningResult { + let psbt = Psbt::deserialize(input.psbt.as_ref()) + .tw_err(|_| SigningErrorType::Error_input_parse) + .context("Error deserializing PSBT")?; + + let version = psbt + .unsigned_tx + .version + .try_into() + .tw_err(|_| SigningErrorType::Error_invalid_params) + .context("Invalid PSBT transaction version")?; + let lock_time = psbt.unsigned_tx.lock_time.to_consensus_u32(); + + let public_keys = Self::get_public_keys(input)?; + + let mut builder = TransactionBuilder::default(); + builder.version(version).lock_time(lock_time); + + // Add all UTXOs to the unsigned transaction builder. + for (txin, txin_psbt) in psbt.unsigned_tx.input.iter().zip(psbt.inputs.iter()) { + let utxo_builder = UtxoPsbt::new(txin, txin_psbt, &public_keys); + + let (utxo, utxo_args) = utxo_builder + .build() + .context("Error creating UTXO from PSBT")?; + builder.push_input(utxo, utxo_args); + } + + // Add all outputs to the unsigned transaction builder. + for txout in psbt.unsigned_tx.output.iter() { + let output = OutputPsbt::new(txout) + .build() + .context("Error creating Output from PSBT")?; + builder.push_output(output); + } + + let unsigned_tx = builder.build()?; + Ok(PsbtRequest { psbt, unsigned_tx }) + } + + fn get_public_keys(input: &Proto::PsbtSigningInput) -> SigningResult { + let mut public_keys = PublicKeys::default(); + + if input.private_keys.is_empty() { + for public in input.public_keys.iter() { + public_keys.add_public_key(public.to_vec()); + } + } else { + for private in input.private_keys.iter() { + public_keys.add_public_with_ecdsa_private(private)?; + } + } + + Ok(public_keys) + } +} diff --git a/rust/chains/tw_bitcoin/src/modules/psbt_request/output_psbt.rs b/rust/chains/tw_bitcoin/src/modules/psbt_request/output_psbt.rs new file mode 100644 index 00000000000..c691744230d --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/psbt_request/output_psbt.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::error::prelude::*; +use tw_utxo::script::Script; +use tw_utxo::transaction::standard_transaction::TransactionOutput; + +/// Currently, we rely on `bitcoin` crate to build our own [`TransactionOutput`]. +pub struct OutputPsbt<'a> { + output: &'a bitcoin::TxOut, +} + +impl<'a> OutputPsbt<'a> { + pub fn new(output: &'a bitcoin::TxOut) -> Self { + OutputPsbt { output } + } + + pub fn build(self) -> SigningResult { + let value = self + .output + .value + .try_into() + .tw_err(|_| SigningErrorType::Error_invalid_utxo_amount) + .context("PSBT Output amount is too large")?; + let script_pubkey = Script::from(self.output.script_pubkey.to_bytes()); + Ok(TransactionOutput { + value, + script_pubkey, + }) + } +} diff --git a/rust/chains/tw_bitcoin/src/modules/psbt_request/utxo_psbt.rs b/rust/chains/tw_bitcoin/src/modules/psbt_request/utxo_psbt.rs new file mode 100644 index 00000000000..58d144de377 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/psbt_request/utxo_psbt.rs @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::tx_builder::public_keys::PublicKeys; +use crate::modules::tx_builder::script_parser::{StandardScript, StandardScriptParser}; +use secp256k1::ThirtyTwoByteHash; +use tw_coin_entry::error::prelude::*; +use tw_hash::H256; +use tw_utxo::script::Script; +use tw_utxo::sighash::SighashType; +use tw_utxo::transaction::standard_transaction::builder::UtxoBuilder; +use tw_utxo::transaction::standard_transaction::TransactionInput; +use tw_utxo::transaction::UtxoToSign; + +/// Currently, we rely on `bitcoin` crate to build our own [`UtxoToSign`]. +pub struct UtxoPsbt<'a> { + utxo: &'a bitcoin::TxIn, + utxo_psbt: &'a bitcoin::psbt::Input, + public_keys: &'a PublicKeys, +} + +impl<'a> UtxoPsbt<'a> { + pub fn new( + utxo: &'a bitcoin::TxIn, + utxo_psbt: &'a bitcoin::psbt::Input, + public_keys: &'a PublicKeys, + ) -> Self { + UtxoPsbt { + utxo, + utxo_psbt, + public_keys, + } + } + + pub fn build(self) -> SigningResult<(TransactionInput, UtxoToSign)> { + if let Some(ref non_witness_utxo) = self.utxo_psbt.non_witness_utxo { + self.build_non_witness_utxo(non_witness_utxo) + } else if let Some(ref witness_utxo) = self.utxo_psbt.witness_utxo { + self.build_witness_utxo(witness_utxo) + } else { + SigningError::err(SigningErrorType::Error_invalid_params) + .context("Neither 'witness_utxo' nor 'non_witness_utxo' are set in the PSBT") + } + } + + pub fn build_non_witness_utxo( + &self, + non_witness_utxo: &bitcoin::Transaction, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let prev_out_idx = self.utxo.previous_output.vout as usize; + let prev_out = non_witness_utxo + .output + .get(prev_out_idx) + .or_tw_err(SigningErrorType::Error_invalid_utxo) + .with_context(|| { + format!("'Psbt::non_witness_utxo' does not contain '{prev_out_idx}' output") + })?; + + let script = Script::from(prev_out.script_pubkey.to_bytes()); + let builder = self.prepare_builder(prev_out.value)?; + + self.build_utxo_with_script(builder, &script) + } + + pub fn build_witness_utxo( + &self, + witness_utxo: &bitcoin::TxOut, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let script = Script::from(witness_utxo.script_pubkey.to_bytes()); + let builder = self.prepare_builder(witness_utxo.value)?; + self.build_utxo_with_script(builder, &script) + } + + fn build_utxo_with_script( + &self, + builder: UtxoBuilder, + script: &Script, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + match StandardScriptParser.parse(script)? { + StandardScript::P2PK(pubkey) => builder.p2pk(&pubkey), + StandardScript::P2PKH(pubkey_hash) => { + let pubkey = self.public_keys.get_ecdsa_public_key(&pubkey_hash)?; + builder.p2pkh(&pubkey) + }, + StandardScript::P2WPKH(pubkey_hash) => { + let pubkey = self.public_keys.get_ecdsa_public_key(&pubkey_hash)?; + builder.p2wpkh(&pubkey) + }, + StandardScript::P2TR(tweaked_pubkey) => { + if self.has_tap_scripts() { + return SigningError::err(SigningErrorType::Error_not_supported) + .context("P2TR script path is not supported for PSBT at the moment"); + } + builder.p2tr_key_path_with_tweaked_pubkey(&tweaked_pubkey) + }, + StandardScript::P2SH(_) | StandardScript::P2WSH(_) => { + SigningError::err(SigningErrorType::Error_not_supported) + .context("P2SH and P2WSH scriptPubkey's are not supported yet") + }, + StandardScript::OpReturn(_) => SigningError::err(SigningErrorType::Error_invalid_utxo) + .context("Cannot spend an OP_RETURN output"), + } + } + + fn prepare_builder(&self, amount: u64) -> SigningResult { + let prevout_hash = H256::from(self.utxo.previous_output.txid.to_raw_hash().into_32()); + let prevout_index = self.utxo.previous_output.vout; + let sequence = self.utxo.sequence.0; + + let sighash_ty = match self.utxo_psbt.sighash_type { + Some(psbt_ty) => SighashType::from_u32(psbt_ty.to_u32())?, + None => SighashType::default(), + }; + + let amount = amount + .try_into() + .tw_err(|_| SigningErrorType::Error_invalid_utxo_amount) + .context("PSBT UTXO amount is too large")?; + + Ok(UtxoBuilder::default() + .prev_txid(prevout_hash) + .prev_index(prevout_index) + .sequence(sequence) + .sighash_type(sighash_ty) + .amount(amount)) + } + + fn has_tap_scripts(&self) -> bool { + !self.utxo_psbt.tap_scripts.is_empty() + } +} diff --git a/rust/chains/tw_bitcoin/src/modules/signer.rs b/rust/chains/tw_bitcoin/src/modules/signer.rs new file mode 100644 index 00000000000..7cd2e7d4749 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/signer.rs @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::protobuf_builder::ProtobufBuilder; +use crate::modules::psbt::update_psbt_signed; +use crate::modules::psbt_request::PsbtRequest; +use crate::modules::signing_request::SigningRequestBuilder; +use std::borrow::Cow; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_keypair::{ecdsa, schnorr}; +use tw_proto::BitcoinV2::Proto; +use tw_utxo::modules::keys_manager::KeysManager; +use tw_utxo::modules::tx_planner::TxPlanner; +use tw_utxo::modules::tx_signer::TxSigner; +use tw_utxo::modules::utxo_selector::SelectResult; +use tw_utxo::signing_mode::SigningMethod; +use tw_utxo::transaction::standard_transaction::Transaction; +use tw_utxo::transaction::transaction_interface::TransactionInterface; +use tw_utxo::transaction::unsigned_transaction::UnsignedTransaction; + +pub struct BitcoinSigner; + +impl BitcoinSigner { + pub fn sign( + coin: &dyn CoinContext, + input: &Proto::SigningInput<'_>, + ) -> Proto::SigningOutput<'static> { + Self::sign_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + pub fn sign_impl( + coin: &dyn CoinContext, + input: &Proto::SigningInput<'_>, + ) -> SigningResult> { + let request = SigningRequestBuilder::build(coin, input)?; + let SelectResult { unsigned_tx, plan } = TxPlanner::plan(request)?; + + let keys_manager = Self::keys_manager_for_tx( + &input.private_keys, + &unsigned_tx, + input.dangerous_use_fixed_schnorr_rng, + )?; + + let signed_tx = + TxSigner::sign_tx(unsigned_tx, &keys_manager).context("Error signing transaction")?; + + Ok(Proto::SigningOutput { + transaction: Some(ProtobufBuilder::tx_to_proto(&signed_tx)), + encoded: Cow::from(signed_tx.encode_out()), + txid: Cow::from(signed_tx.txid()), + // `vsize` could have been changed after the transaction being signed. + vsize: signed_tx.vsize() as u64, + // `fee` should haven't been changed since it's a difference between `sum(inputs)` and `sum(outputs)`. + fee: plan.fee_estimate, + weight: signed_tx.weight() as u64, + ..Proto::SigningOutput::default() + }) + } + + pub fn sign_psbt( + coin: &dyn CoinContext, + input: &Proto::PsbtSigningInput<'_>, + ) -> Proto::PsbtSigningOutput<'static> { + Self::sign_psbt_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(Proto::PsbtSigningOutput, e)) + } + + pub fn sign_psbt_impl( + _coin: &dyn CoinContext, + input: &Proto::PsbtSigningInput<'_>, + ) -> SigningResult> { + let PsbtRequest { + mut psbt, + unsigned_tx, + } = PsbtRequest::build(input)?; + + let fee = unsigned_tx + .total_input()? + .checked_sub(unsigned_tx.total_output()?) + .or_tw_err(SigningErrorType::Error_not_enough_utxos) + .context("PSBT sum(input) < sum(output)")?; + + let keys_manager = Self::keys_manager_for_tx( + &input.private_keys, + &unsigned_tx, + input.dangerous_use_fixed_schnorr_rng, + )?; + + let signed_tx = + TxSigner::sign_tx(unsigned_tx, &keys_manager).context("Error signing transaction")?; + + update_psbt_signed(&mut psbt, &signed_tx); + + Ok(Proto::PsbtSigningOutput { + transaction: Some(ProtobufBuilder::tx_to_proto(&signed_tx)), + encoded: Cow::from(signed_tx.encode_out()), + txid: Cow::from(signed_tx.txid()), + // `vsize` could have been changed after the transaction being signed. + vsize: signed_tx.vsize() as u64, + fee, + weight: signed_tx.weight() as u64, + psbt: Cow::from(psbt.serialize()), + ..Proto::PsbtSigningOutput::default() + }) + } + + fn keys_manager_for_tx

( + private_keys: &[P], + unsigned_tx: &UnsignedTransaction, + dangerous_use_fixed_schnorr_rng: bool, + ) -> SigningResult + where + P: AsRef<[u8]>, + { + let has_taproot = unsigned_tx + .input_args() + .iter() + .any(|utxo_args| utxo_args.signing_method == SigningMethod::Taproot); + + let mut keys_manager = KeysManager::default(); + + // Parse private keys and put them to the keys manager. + for private in private_keys.iter() { + let ecdsa_private = ecdsa::secp256k1::PrivateKey::try_from(private.as_ref()) + .into_tw() + .context("Invalid ecdsa secp256k1 private key")?; + keys_manager.add_ecdsa_private(ecdsa_private); + + if has_taproot { + let schnorr_private = schnorr::PrivateKey::try_from(private.as_ref()) + .into_tw() + .context("Invalid schnorr private key")?; + + if dangerous_use_fixed_schnorr_rng { + keys_manager.add_schnorr_private(schnorr_private.no_aux_rand()); + } else { + keys_manager.add_schnorr_private(schnorr_private); + } + } + } + + Ok(keys_manager) + } +} diff --git a/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs b/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs new file mode 100644 index 00000000000..81548a754fb --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::tx_builder::output_protobuf::OutputProtobuf; +use crate::modules::tx_builder::public_keys::PublicKeys; +use crate::modules::tx_builder::utxo_protobuf::UtxoProtobuf; +use crate::modules::tx_builder::BitcoinChainInfo; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_misc::traits::OptionalEmpty; +use tw_proto::BitcoinV2::Proto; +use tw_utxo::dust::DustPolicy; +use tw_utxo::modules::tx_planner::{PlanRequest, RequestType}; +use tw_utxo::modules::utxo_selector::InputSelector; +use tw_utxo::transaction::standard_transaction::builder::TransactionBuilder; +use tw_utxo::transaction::standard_transaction::Transaction; +use Proto::mod_SigningInput::OneOfdust_policy as ProtoDustPolicy; + +const DEFAULT_TX_VERSION: u32 = 1; + +pub type StandardSigningRequest = PlanRequest; + +pub struct SigningRequestBuilder; + +impl SigningRequestBuilder { + pub fn build( + coin: &dyn CoinContext, + input: &Proto::SigningInput, + ) -> SigningResult { + let chain_info = Self::chain_info(coin, &input.chain_info)?; + let dust_policy = Self::dust_policy(&input.dust_policy)?; + let fee_per_vbyte = input.fee_per_vb; + let version = Self::transaction_version(&input.version); + + let public_keys = Self::get_public_keys(input)?; + + let mut builder = TransactionBuilder::default(); + builder.version(version).lock_time(input.lock_time); + + // Parse all UTXOs. + for utxo_proto in input.inputs.iter() { + let utxo_builder = UtxoProtobuf::new(&chain_info, utxo_proto, &public_keys); + + let (utxo, utxo_args) = utxo_builder + .utxo_from_proto() + .context("Error creating UTXO from Protobuf")?; + builder.push_input(utxo, utxo_args); + } + + // If `max_amount_output` is set, construct a transaction with only one output. + if let Some(max_output_proto) = input.max_amount_output.as_ref() { + let output_builder = OutputProtobuf::new(&chain_info, max_output_proto); + + let max_output = output_builder + .output_from_proto() + .context("Error creating Max Output from Protobuf")?; + builder.push_output(max_output); + + let unsigned_tx = builder.build()?; + return Ok(StandardSigningRequest { + ty: RequestType::SendMax { unsigned_tx }, + dust_policy, + fee_per_vbyte, + }); + } + + // `max_amount_output` isn't set, parse all Outputs. + for output_proto in input.outputs.iter() { + let output = OutputProtobuf::new(&chain_info, output_proto) + .output_from_proto() + .context("Error creating Output from Proto")?; + builder.push_output(output); + } + + // Parse change output if it was provided. + let change_output = input + .change_output + .as_ref() + .map(|change_output_proto| { + OutputProtobuf::new(&chain_info, change_output_proto) + .output_from_proto() + .context("Error creating Change Output from Proto") + }) + .transpose()?; + + let input_selector = Self::input_selector(&input.input_selector); + + let unsigned_tx = builder.build()?; + Ok(StandardSigningRequest { + ty: RequestType::SendExact { + unsigned_tx, + change_output, + input_selector, + }, + dust_policy, + fee_per_vbyte, + }) + } + + fn get_public_keys(input: &Proto::SigningInput) -> SigningResult { + let mut public_keys = PublicKeys::default(); + + if input.private_keys.is_empty() { + for public in input.public_keys.iter() { + public_keys.add_public_key(public.to_vec()); + } + } else { + for private in input.private_keys.iter() { + public_keys.add_public_with_ecdsa_private(private)?; + } + } + + Ok(public_keys) + } + + fn input_selector(selector: &Proto::InputSelector) -> InputSelector { + match selector { + Proto::InputSelector::SelectAscending => InputSelector::Ascending, + Proto::InputSelector::SelectInOrder => InputSelector::InOrder, + Proto::InputSelector::SelectDescending => InputSelector::Descending, + Proto::InputSelector::UseAll => InputSelector::UseAll, + } + } + + fn dust_policy(proto: &ProtoDustPolicy) -> SigningResult { + match proto { + ProtoDustPolicy::fixed_dust_threshold(fixed) => Ok(DustPolicy::FixedAmount(*fixed)), + ProtoDustPolicy::None => SigningError::err(SigningErrorType::Error_invalid_params) + .context("No dust policy provided"), + } + } + + fn transaction_version(proto: &Proto::TransactionVersion) -> u32 { + match proto { + Proto::TransactionVersion::UseDefault => DEFAULT_TX_VERSION, + Proto::TransactionVersion::V1 => 1, + Proto::TransactionVersion::V2 => 2, + } + } + + pub fn chain_info( + coin: &dyn CoinContext, + chain_info: &Option, + ) -> SigningResult { + fn prefix_to_u8(prefix: u32, prefix_name: &str) -> SigningResult { + prefix + .try_into() + .tw_err(|_| SigningErrorType::Error_invalid_params) + .with_context(|| format!("Invalid {prefix_name} prefix. It must fit uint8")) + } + + if let Some(info) = chain_info { + let hrp = info.hrp.to_string().empty_or_some(); + return Ok(BitcoinChainInfo { + p2pkh_prefix: prefix_to_u8(info.p2pkh_prefix, "p2pkh")?, + p2sh_prefix: prefix_to_u8(info.p2sh_prefix, "p2sh")?, + hrp, + }); + } + + // Try to get the chain info from the context. + // Note that not all Bitcoin forks support HRP (segwit addresses). + let hrp = coin.hrp(); + match (coin.p2pkh_prefix(), coin.p2sh_prefix()) { + (Some(p2pkh_prefix), Some(p2sh_prefix)) => Ok(BitcoinChainInfo { + p2pkh_prefix, + p2sh_prefix, + hrp, + }), + _ => SigningError::err(SigningErrorType::Error_invalid_params) + .context("Neither 'SigningInput.chain_info' nor p2pkh/p2sh prefixes specified in the registry.json") + } + } +} diff --git a/rust/chains/tw_bitcoin/src/modules/transaction_util.rs b/rust/chains/tw_bitcoin/src/modules/transaction_util.rs new file mode 100644 index 00000000000..19862095334 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/transaction_util.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use bitcoin::consensus::deserialize; +use bitcoin::Transaction; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::transaction_util::TransactionUtil; +use tw_encoding::hex::decode; + +pub struct BitcoinTransactionUtil; + +impl TransactionUtil for BitcoinTransactionUtil { + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + Self::calc_tx_hash_impl(coin, encoded_tx) + } +} + +impl BitcoinTransactionUtil { + fn calc_tx_hash_impl(_coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + let tx = decode(encoded_tx).map_err(|_| SigningErrorType::Error_input_parse)?; + + // Deserialize the transaction + let tx: Transaction = deserialize(&tx).map_err(|_| SigningErrorType::Error_input_parse)?; + + // Calculate the transaction ID + let txid = tx.txid(); + + // Note: to_string() returns the reversed byte order, which is the RPC format + Ok(txid.to_string()) + } +} diff --git a/rust/chains/tw_bitcoin/src/modules/tx_builder/mod.rs b/rust/chains/tw_bitcoin/src/modules/tx_builder/mod.rs new file mode 100644 index 00000000000..37c99c33e53 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/tx_builder/mod.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod output_protobuf; +pub mod public_keys; +pub mod script_parser; +pub mod utxo_protobuf; + +pub struct BitcoinChainInfo { + pub p2pkh_prefix: u8, + pub p2sh_prefix: u8, + /// Note that not all Bitcoin forks support HRP (segwit addresses). + pub hrp: Option, +} diff --git a/rust/chains/tw_bitcoin/src/modules/tx_builder/output_protobuf.rs b/rust/chains/tw_bitcoin/src/modules/tx_builder/output_protobuf.rs new file mode 100644 index 00000000000..0e1a6e374b0 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/tx_builder/output_protobuf.rs @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::tx_builder::BitcoinChainInfo; +use std::str::FromStr; +use tw_coin_entry::error::prelude::*; +use tw_hash::hasher::sha256_ripemd; +use tw_hash::sha2::sha256; +use tw_hash::{Hash, H160, H256}; +use tw_keypair::{ecdsa, schnorr}; +use tw_memory::Data; +use tw_proto::BitcoinV2::Proto; +use tw_utxo::address::legacy::LegacyAddress; +use tw_utxo::address::segwit::SegwitAddress; +use tw_utxo::address::standard_bitcoin::StandardBitcoinAddress; +use tw_utxo::address::taproot::TaprootAddress; +use tw_utxo::script::Script; +use tw_utxo::transaction::standard_transaction::builder::OutputBuilder; +use tw_utxo::transaction::standard_transaction::TransactionOutput; + +pub struct OutputProtobuf<'a> { + chain_info: &'a BitcoinChainInfo, + output: &'a Proto::Output<'a>, +} + +impl<'a> OutputProtobuf<'a> { + pub fn new(chain_info: &'a BitcoinChainInfo, output: &'a Proto::Output<'a>) -> Self { + OutputProtobuf { chain_info, output } + } + + pub fn output_from_proto(self) -> SigningResult { + use Proto::mod_Output::mod_OutputBuilder::OneOfvariant as BuilderType; + use Proto::mod_Output::OneOfto_recipient as RecipientType; + + match self.output.to_recipient { + RecipientType::builder(ref builder) => match builder.variant { + BuilderType::p2sh(ref redeem) => self.p2sh(redeem), + BuilderType::p2pk(ref pubkey) => self.p2pk(pubkey), + BuilderType::p2pkh(ref pubkey_or_hash) => self.p2pkh(pubkey_or_hash), + BuilderType::p2wsh(ref redeem) => self.p2wsh(redeem), + BuilderType::p2wpkh(ref pubkey_or_hash) => self.p2wpkh(pubkey_or_hash), + BuilderType::p2tr_key_path(ref key_path) => self.p2tr_key_path(key_path), + BuilderType::p2tr_script_path(ref script) => self.p2tr_script_path(script), + BuilderType::p2tr_dangerous_assume_tweaked(ref pubkey) => { + self.p2tr_dangerous_assume_tweaked(pubkey) + }, + BuilderType::brc20_inscribe(ref inscription) => self.brc20_inscribe(inscription), + BuilderType::op_return(ref data) => self.op_return(data), + BuilderType::None => SigningError::err(SigningErrorType::Error_invalid_params) + .context("No Output Builder type provided"), + }, + RecipientType::custom_script_pubkey(ref script) => self.custom_script(script.to_vec()), + RecipientType::to_address(ref address) => self.recipient_address(address), + RecipientType::None => SigningError::err(SigningErrorType::Error_invalid_params) + .context("No Output recipient type provided"), + } + } + + pub fn p2sh( + &self, + redeem: &Proto::mod_Output::RedeemScriptOrHash, + ) -> SigningResult { + let redeem_hash = + Self::redeem_hash_from_proto(redeem, sha256_ripemd).context("P2SH builder")?; + Ok(self.prepare_builder()?.p2sh_from_hash(&redeem_hash)) + } + + pub fn p2pk(&self, pubkey: &[u8]) -> SigningResult { + let pubkey = ecdsa::secp256k1::PublicKey::try_from(pubkey) + .into_tw() + .context("Invalid P2PK public key")?; + Ok(self.prepare_builder()?.p2pk(&pubkey)) + } + + pub fn p2pkh( + &self, + pubkey_or_hash: &Proto::PublicKeyOrHash, + ) -> SigningResult { + let pubkey_hash = Self::pubkey_hash_from_proto(pubkey_or_hash).context("P2PKH builder")?; + Ok(self.prepare_builder()?.p2pkh_from_hash(&pubkey_hash)) + } + + pub fn p2wsh( + &self, + redeem: &Proto::mod_Output::RedeemScriptOrHash, + ) -> SigningResult { + let redeem_hash = Self::redeem_hash_from_proto(redeem, sha256).context("P2WSH builder")?; + Ok(self.prepare_builder()?.p2wsh_from_hash(&redeem_hash)) + } + + pub fn p2wpkh( + &self, + pubkey_or_hash: &Proto::PublicKeyOrHash, + ) -> SigningResult { + let pubkey_hash = Self::pubkey_hash_from_proto(pubkey_or_hash).context("P2WPKH builder")?; + Ok(self.prepare_builder()?.p2wpkh_from_hash(&pubkey_hash)) + } + + pub fn p2tr_key_path(&self, taproot_pubkey: &[u8]) -> SigningResult { + let public_key = schnorr::PublicKey::try_from(taproot_pubkey) + .into_tw() + .context("Invalid P2TR key path. Must be a schnorr public key")?; + Ok(self.prepare_builder()?.p2tr_key_path(&public_key)) + } + + pub fn p2tr_dangerous_assume_tweaked( + &self, + tweaked_pubkey: &[u8], + ) -> SigningResult { + let tweaked_x_only = H256::try_from(tweaked_pubkey) + .tw_err(|_| SigningErrorType::Error_invalid_params) + .context("Invalid P2TR tweaked public key. Expected 32 bytes x-only public key")?; + Ok(self + .prepare_builder()? + .p2tr_dangerous_assume_tweaked(&tweaked_x_only)) + } + + pub fn p2tr_script_path( + &self, + taproot_script_path: &Proto::mod_Output::OutputTaprootScriptPath, + ) -> SigningResult { + let public_key = schnorr::PublicKey::try_from(taproot_script_path.internal_key.as_ref()) + .into_tw() + .context( + "Invalid OutputTaprootScriptPath.internal_key. Must be a schnorr public key", + )?; + + let merkle_root = H256::try_from(taproot_script_path.merkle_root.as_ref()) + .tw_err(|_| SigningErrorType::Error_invalid_params) + .context("Invalid OutputTaprootScriptPath.merkle_root. Must be a 32 byte array")?; + + Ok(self + .prepare_builder()? + .p2tr_script_path(&public_key, merkle_root)) + } + + pub fn brc20_inscribe( + &self, + inscription: &Proto::mod_Output::OutputBrc20Inscription, + ) -> SigningResult { + let public_key = schnorr::PublicKey::try_from(inscription.inscribe_to.as_ref())?; + self.prepare_builder()?.brc20_transfer( + &public_key, + inscription.ticker.to_string(), + inscription.transfer_amount.to_string(), + ) + } + + pub fn custom_script(&self, script_data: Data) -> SigningResult { + let script = Script::from(script_data); + Ok(self.prepare_builder()?.custom_script_pubkey(script)) + } + + pub fn recipient_address(&self, addr: &str) -> SigningResult { + let addr = StandardBitcoinAddress::from_str(addr) + .into_tw() + .context("Invalid recipient address")?; + + match addr { + StandardBitcoinAddress::Legacy(ref legacy) => self.recipient_legacy_address(legacy), + StandardBitcoinAddress::Segwit(ref segwit) => self.recipient_segwit_address(segwit), + StandardBitcoinAddress::Taproot(ref taproot) => self.recipient_taproot_address(taproot), + } + } + + pub fn op_return(&self, op_return_data: &[u8]) -> SigningResult { + self.prepare_builder()?.op_return(op_return_data) + } + + pub fn recipient_legacy_address( + &self, + addr: &LegacyAddress, + ) -> SigningResult { + let p2pkh_prefix = self.chain_info.p2pkh_prefix; + let p2sh_prefix = self.chain_info.p2sh_prefix; + + if addr.prefix() == p2pkh_prefix { + Ok(self.prepare_builder()?.p2pkh_from_hash(&addr.payload())) + } else if addr.prefix() == p2sh_prefix { + Ok(self.prepare_builder()?.p2sh_from_hash(&addr.payload())) + } else { + // Unknown + SigningError::err(SigningErrorType::Error_invalid_address).context(format!( + "The given '{addr}' address has unexpected prefix. Expected p2pkh={p2pkh_prefix} p2sh={p2sh_prefix}", + )) + } + } + + pub fn recipient_segwit_address( + &self, + addr: &SegwitAddress, + ) -> SigningResult { + if let Ok(pubkey_hash) = H160::try_from(addr.witness_program()) { + Ok(self.prepare_builder()?.p2wpkh_from_hash(&pubkey_hash)) + } else if let Ok(script_hash) = H256::try_from(addr.witness_program()) { + Ok(self.prepare_builder()?.p2wsh_from_hash(&script_hash)) + } else { + return SigningError::err(SigningErrorType::Error_invalid_address) + .context(format!("The given '{addr}' Segwit address has unexpected witness program. Expected either 20 or 32 bytes")); + } + } + + pub fn recipient_taproot_address( + &self, + addr: &TaprootAddress, + ) -> SigningResult { + let pubkey_x_only = H256::try_from(addr.witness_program()) + .tw_err(|_| SigningErrorType::Error_invalid_address) + .context(format!("The given '{addr}' Taproot address has unexpected witness program. Expected 32 bytes public key"))?; + + Ok(self + .prepare_builder()? + .p2tr_dangerous_assume_tweaked(&pubkey_x_only)) + } + + /// Tries to convert [`Proto::RedeemScriptOrHash`] to [`Hash`] using a specific `hasher` function. + /// Please note `P2SH` and `P2WSH` use different hashing functions. + pub fn redeem_hash_from_proto( + input: &Proto::mod_Output::RedeemScriptOrHash, + hasher: F, + ) -> SigningResult> + where + F: FnOnce(&[u8]) -> Data, + { + use Proto::mod_Output::mod_RedeemScriptOrHash::OneOfvariant as RedeemOrHashType; + + let hash_data = match input.variant { + RedeemOrHashType::redeem_script(ref redeem) => hasher(redeem.as_ref()), + RedeemOrHashType::hash(ref hash) => hash.to_vec(), + RedeemOrHashType::None => { + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("Expected a redeem script or its hash") + }, + }; + Hash::::try_from(hash_data.as_slice()) + .tw_err(|_| SigningErrorType::Error_invalid_params) + .with_context(|| format!("Expected exactly {N} bytes redeem script hash")) + } + + pub fn prepare_builder(&self) -> SigningResult { + if self.output.value < 0 { + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("Transaction Output amount cannot be negative"); + } + Ok(OutputBuilder::new(self.output.value)) + } + + /// Tries to convert [`Proto::PublicKeyOrHash`] to [`Hash`]. + /// Please note `P2PKH` and `P2WPKH` use the same `ripemd(sha256(x))` hash function. + pub fn pubkey_hash_from_proto( + input: &Proto::PublicKeyOrHash, + ) -> SigningResult> { + use Proto::mod_PublicKeyOrHash::OneOfvariant as PublicKeyOrHashType; + + let hash_data = match input.variant { + PublicKeyOrHashType::pubkey(ref pubkey) => sha256_ripemd(pubkey.as_ref()), + PublicKeyOrHashType::hash(ref hash) => hash.to_vec(), + PublicKeyOrHashType::None => { + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("Expected a public key or its hash") + }, + }; + Hash::::try_from(hash_data.as_slice()) + .tw_err(|_| SigningErrorType::Error_invalid_params) + .with_context(|| format!("Expected exactly {N} bytes public key hash")) + } +} diff --git a/rust/chains/tw_bitcoin/src/modules/tx_builder/public_keys.rs b/rust/chains/tw_bitcoin/src/modules/tx_builder/public_keys.rs new file mode 100644 index 00000000000..6c822c686b6 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/tx_builder/public_keys.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::collections::HashMap; +use tw_coin_entry::error::prelude::*; +use tw_hash::hasher::sha256_ripemd; +use tw_hash::H160; +use tw_keypair::ecdsa; +use tw_memory::Data; + +/// A transaction builder helper that allows to easily get an access to a public key by using its `ripemd(sha256)` hash. +#[derive(Default)] +pub struct PublicKeys { + /// The map of public keys by their hash, i.e `{ ripemd(sha256(public_key)) -> public_key }`. + public_key_hash_map: HashMap, +} + +impl PublicKeys { + /// Adds a public key to the keys manager. + /// Simply ignore duplicate keys. + pub fn add_public_key(&mut self, pubkey: Data) -> &mut Self { + let pubkey_hash = H160::try_from(sha256_ripemd(&pubkey).as_slice()) + .expect("sha256_ripemd must return exactly 20 bytes"); + self.public_key_hash_map.insert(pubkey_hash, pubkey); + self + } + + /// Adds a public key derived from the ecdsa secp256k1 `private`. + pub fn add_public_with_ecdsa_private(&mut self, private: &[u8]) -> SigningResult<&mut Self> { + let private = ecdsa::secp256k1::PrivateKey::try_from(private) + .into_tw() + .context("Given an invalid ecdsa secp256k1 private key")?; + Ok(self.add_public_key(private.public().compressed().to_vec())) + } + + pub fn get_public_key(&self, pubkey_hash: &H160) -> SigningResult<&[u8]> { + self.public_key_hash_map + .get(pubkey_hash) + .map(|pubkey_bytes|pubkey_bytes.as_slice()) + .or_tw_err(SigningErrorType::Error_missing_private_key) + .with_context(|| format!("Missing either a private or public key corresponding to the pubkey hash: {pubkey_hash}")) + } + + pub fn get_ecdsa_public_key( + &self, + pubkey_hash: &H160, + ) -> SigningResult { + let pubkey_data = self.get_public_key(pubkey_hash)?; + ecdsa::secp256k1::PublicKey::try_from(pubkey_data) + .into_tw() + .context("Expected a valid ecdsa secp256k1 public key") + } +} diff --git a/rust/chains/tw_bitcoin/src/modules/tx_builder/script_parser.rs b/rust/chains/tw_bitcoin/src/modules/tx_builder/script_parser.rs new file mode 100644 index 00000000000..26d8a70e009 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/tx_builder/script_parser.rs @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::tx_builder::BitcoinChainInfo; +use tw_coin_entry::error::prelude::*; +use tw_hash::{H160, H256}; +use tw_keypair::{ecdsa, schnorr}; +use tw_memory::Data; +use tw_utxo::address::legacy::LegacyAddress; +use tw_utxo::address::segwit::SegwitAddress; +use tw_utxo::address::standard_bitcoin::StandardBitcoinAddress; +use tw_utxo::address::taproot::TaprootAddress; +use tw_utxo::script::standard_script::conditions; +use tw_utxo::script::Script; + +pub enum StandardScript { + /// Compressed or uncompressed public key bytes. + P2PK(ecdsa::secp256k1::PublicKey), + /// Public key hash. + P2PKH(H160), + /// Script hash. + P2SH(H160), + /// Public key hash. + P2WPKH(H160), + /// Script hash. + P2WSH(H256), + /// Tweaked public key. + /// The public key can be tweaked as either key-path or script-path, + P2TR(schnorr::XOnlyPublicKey), + /// OP_RETURN payload. + OpReturn(Data), +} + +impl StandardScript { + pub fn try_to_address( + &self, + chain_info: &BitcoinChainInfo, + ) -> AddressResult> { + let try_hrp = || chain_info.hrp.clone().ok_or(AddressError::MissingPrefix); + + match self { + StandardScript::P2PK(pubkey) => { + // Display P2PK input as P2PKH. + LegacyAddress::p2pkh_with_public_key(chain_info.p2pkh_prefix, pubkey) + .map(StandardBitcoinAddress::Legacy) + .map(Some) + }, + StandardScript::P2PKH(pubkey_hash) => { + LegacyAddress::new(chain_info.p2pkh_prefix, pubkey_hash.as_slice()) + .map(StandardBitcoinAddress::Legacy) + .map(Some) + }, + StandardScript::P2SH(script_hash) => { + LegacyAddress::new(chain_info.p2sh_prefix, script_hash.as_slice()) + .map(StandardBitcoinAddress::Legacy) + .map(Some) + }, + StandardScript::P2WPKH(pubkey_hash) => { + SegwitAddress::new(try_hrp()?, pubkey_hash.to_vec()) + .map(StandardBitcoinAddress::Segwit) + .map(Some) + }, + StandardScript::P2WSH(script_hash) => { + SegwitAddress::new(try_hrp()?, script_hash.to_vec()) + .map(StandardBitcoinAddress::Segwit) + .map(Some) + }, + StandardScript::P2TR(tweaked_pubkey) => { + TaprootAddress::new(try_hrp()?, tweaked_pubkey.bytes().to_vec()) + .map(StandardBitcoinAddress::Taproot) + .map(Some) + }, + StandardScript::OpReturn(_) => Ok(None), + } + } +} + +pub struct StandardScriptParser; + +impl StandardScriptParser { + /// Later, this method can be moved to a trait. + pub fn parse(&self, script: &Script) -> SigningResult { + if let Some(pubkey) = conditions::match_p2pk(script) { + // P2PK + let pubkey = ecdsa::secp256k1::PublicKey::try_from(pubkey) + .into_tw() + .context("P2PK scriptPubkey must contain a valid ecdsa secp256k1 public key")?; + Ok(StandardScript::P2PK(pubkey)) + } else if let Some(pubkey_hash) = conditions::match_p2pkh(script) { + // P2PKH + Ok(StandardScript::P2PKH(pubkey_hash)) + } else if let Some(script_hash) = conditions::match_p2sh(script) { + // P2SH + Ok(StandardScript::P2SH(script_hash)) + } else if let Some(pubkey_hash) = conditions::match_p2wpkh(script) { + // P2WPKH + Ok(StandardScript::P2WPKH(pubkey_hash)) + } else if let Some(script_hash) = conditions::match_p2wsh(script) { + // P2WSH + Ok(StandardScript::P2WSH(script_hash)) + } else if let Some(tweaked_pubkey) = conditions::match_p2tr(script) { + // P2TR + let tweaked_pubkey_x_only = + schnorr::XOnlyPublicKey::try_from(tweaked_pubkey.as_slice()) + .into_tw() + .context("P2TR scriptPubkey must contain a valid tweaked schnorr public key")?; + Ok(StandardScript::P2TR(tweaked_pubkey_x_only)) + } else if let Some(payload) = conditions::match_op_return(script) { + // OP_RETURN + Ok(StandardScript::OpReturn(payload)) + } else { + // Unknown + SigningError::err(SigningErrorType::Error_script_output).context( + "The given custom scriptPubkey is not supported. Consider using a proper Input/Output builder", + ) + } + } +} diff --git a/rust/chains/tw_bitcoin/src/modules/tx_builder/utxo_protobuf.rs b/rust/chains/tw_bitcoin/src/modules/tx_builder/utxo_protobuf.rs new file mode 100644 index 00000000000..d536915a3c8 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/modules/tx_builder/utxo_protobuf.rs @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::tx_builder::public_keys::PublicKeys; +use crate::modules::tx_builder::script_parser::{StandardScript, StandardScriptParser}; +use crate::modules::tx_builder::BitcoinChainInfo; +use std::str::FromStr; +use tw_coin_entry::error::prelude::*; +use tw_hash::{H160, H256}; +use tw_keypair::{ecdsa, schnorr}; +use tw_memory::Data; +use tw_misc::traits::ToBytesVec; +use tw_proto::BitcoinV2::Proto; +use tw_utxo::address::legacy::LegacyAddress; +use tw_utxo::address::segwit::SegwitAddress; +use tw_utxo::address::standard_bitcoin::StandardBitcoinAddress; +use tw_utxo::address::taproot::TaprootAddress; +use tw_utxo::script::Script; +use tw_utxo::sighash::SighashType; +use tw_utxo::transaction::standard_transaction::builder::UtxoBuilder; +use tw_utxo::transaction::standard_transaction::TransactionInput; +use tw_utxo::transaction::transaction_parts::OutPoint; +use tw_utxo::transaction::UtxoToSign; + +pub struct UtxoProtobuf<'a> { + chain_info: &'a BitcoinChainInfo, + input: &'a Proto::Input<'a>, + public_keys: &'a PublicKeys, +} + +impl<'a> UtxoProtobuf<'a> { + pub fn new( + chain_info: &'a BitcoinChainInfo, + input: &'a Proto::Input<'a>, + public_keys: &'a PublicKeys, + ) -> Self { + UtxoProtobuf { + chain_info, + input, + public_keys, + } + } + + pub fn utxo_from_proto(self) -> SigningResult<(TransactionInput, UtxoToSign)> { + use Proto::mod_Input::mod_InputBuilder::OneOfvariant as BuilderType; + use Proto::mod_Input::OneOfclaiming_script as ScriptType; + + match self.input.claiming_script { + ScriptType::script_builder(ref builder) => match builder.variant { + // BuilderType::p2sh(ref redeem_script) => self.p2sh(redeem_script.to_vec()), + BuilderType::p2pk(ref pubkey) => self.p2pk(pubkey), + BuilderType::p2pkh(ref pubkey_or_hash) => self.p2pkh(pubkey_or_hash), + // BuilderType::p2wsh(ref redeem_script) => self.p2wsh(redeem_script.to_vec()), + BuilderType::p2wpkh(ref pubkey_or_hash) => self.p2wpkh(pubkey_or_hash), + BuilderType::p2tr_key_path(ref key_path) => self.p2tr_key_path(key_path), + // BuilderType::p2tr_script_path(ref script) => self.p2tr_script_path(script), + BuilderType::brc20_inscribe(ref inscription) => self.brc20_inscribe(inscription), + BuilderType::None => SigningError::err(SigningErrorType::Error_invalid_params) + .context("No Input Builder type provided"), + }, + ScriptType::script_data(ref script) => self.custom_script(script.to_vec()), + ScriptType::receiver_address(ref address) => self.recipient_address(address), + ScriptType::None => SigningError::err(SigningErrorType::Error_invalid_params) + .context("No Input claiming script provided"), + } + } + + // TODO next iteration + // pub fn p2sh(&self, redeem_script: Data) -> SigningResult<(TransactionInput, UtxoToSign)> { + // let redeem_script = Script::from(redeem_script); + // self.prepare_builder()?.p2sh(redeem_script) + // } + + pub fn p2pk(&self, pubkey: &[u8]) -> SigningResult<(TransactionInput, UtxoToSign)> { + let pubkey = ecdsa::secp256k1::PublicKey::try_from(pubkey) + .into_tw() + .context("Invalid P2PK public key")?; + + self.prepare_builder()?.p2pk(&pubkey) + } + + pub fn p2pkh( + &self, + pubkey_or_hash: &Proto::PublicKeyOrHash, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let pubkey_hash = self.get_ecdsa_pubkey_from_proto(pubkey_or_hash)?; + self.prepare_builder()?.p2pkh(&pubkey_hash) + } + + // TODO next iteration + // pub fn p2wsh(&self, redeem_script: Data) -> SigningResult<(TransactionInput, UtxoToSign)> { + // let script = Script::from(redeem_script); + // self.prepare_builder()?.p2wsh(script) + // } + + pub fn p2wpkh( + &self, + pubkey_or_hash: &Proto::PublicKeyOrHash, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let pubkey = self.get_ecdsa_pubkey_from_proto(pubkey_or_hash)?; + self.prepare_builder()?.p2wpkh(&pubkey) + } + + pub fn p2tr_key_path(&self, pubkey: &[u8]) -> SigningResult<(TransactionInput, UtxoToSign)> { + let public_key = schnorr::PublicKey::try_from(pubkey)?; + self.prepare_builder()?.p2tr_key_path(&public_key) + } + + // TODO next iteration + // pub fn p2tr_script_path( + // &self, + // taproot_script_path: &Proto::mod_Input::InputTaprootScriptPath, + // ) -> SigningResult<(TransactionInput, UtxoToSign)> { + // let payload = Script::from(taproot_script_path.payload.to_vec()); + // // let x = taproot_script_path. + // self.prepare_builder()? + // .p2tr_script_path(payload, taproot_script_path.control_block.to_vec()) + // } + + pub fn brc20_inscribe( + &self, + inscription: &Proto::mod_Input::InputBrc20Inscription, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let public_key = schnorr::PublicKey::try_from(inscription.inscribe_to.as_ref())?; + self.prepare_builder()?.brc20_transfer( + &public_key, + inscription.ticker.to_string(), + inscription.transfer_amount.to_string(), + ) + } + + pub fn custom_script( + &self, + script_data: Data, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let script = Script::from(script_data); + let builder = self.prepare_builder()?; + + match StandardScriptParser.parse(&script)? { + StandardScript::P2PK(pk) => builder.p2pk(&pk), + StandardScript::P2PKH(pubkey_hash) => { + let pubkey = self.public_keys.get_ecdsa_public_key(&pubkey_hash)?; + builder.p2pkh(&pubkey) + }, + StandardScript::P2WPKH(pubkey_hash) => { + let pubkey = self.public_keys.get_ecdsa_public_key(&pubkey_hash)?; + builder.p2wpkh(&pubkey) + }, + StandardScript::P2TR(tweaked_pubkey) => { + builder.p2tr_key_path_with_tweaked_pubkey(&tweaked_pubkey) + }, + StandardScript::P2SH(_) | StandardScript::P2WSH(_) => { + SigningError::err(SigningErrorType::Error_not_supported) + .context("P2SH and P2WSH scriptPubkey's are not supported yet") + }, + StandardScript::OpReturn(_) => SigningError::err(SigningErrorType::Error_invalid_utxo) + .context("Cannot spend an OP_RETURN output"), + } + } + + pub fn recipient_address(&self, addr: &str) -> SigningResult<(TransactionInput, UtxoToSign)> { + let addr = StandardBitcoinAddress::from_str(addr) + .into_tw() + .context("Invalid claiming script recipient address")?; + + match addr { + StandardBitcoinAddress::Legacy(ref legacy) => self.recipient_legacy_address(legacy), + StandardBitcoinAddress::Segwit(ref segwit) => self.recipient_segwit_address(segwit), + StandardBitcoinAddress::Taproot(ref taproot) => self.recipient_taproot_address(taproot), + } + .with_context(|| format!("Error handling {addr} input recipient")) + } + + pub fn recipient_legacy_address( + &self, + addr: &LegacyAddress, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let p2pkh_prefix = self.chain_info.p2pkh_prefix; + let p2sh_prefix = self.chain_info.p2sh_prefix; + + if p2pkh_prefix == addr.prefix() { + // P2PKH + let pubkey = self.public_keys.get_ecdsa_public_key(&addr.payload())?; + self.prepare_builder()?.p2pkh(&pubkey) + } else if p2sh_prefix == addr.prefix() { + // P2SH + SigningError::err(SigningErrorType::Error_script_redeem).context( + "pay-to-script-hash can only be used via 'Input.InputBuilder.p2sh'.\ + That is because P2SH address does not provide redeem script but its hash", + ) + } else { + // Unknown + SigningError::err(SigningErrorType::Error_invalid_address).context(format!( + "The given '{addr}' address has unexpected prefix. Expected p2pkh={p2pkh_prefix} p2sh={p2sh_prefix}", + )) + } + } + + pub fn recipient_segwit_address( + &self, + addr: &SegwitAddress, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let witness_program = addr.witness_program(); + match witness_program.len() { + // P2WPKH + H160::LEN => { + let pubkey_hash = H160::try_from(witness_program) + .expect("'witness_program' length must be checked already"); + let pubkey = self.public_keys.get_ecdsa_public_key(&pubkey_hash)?; + self.prepare_builder()?.p2wpkh(&pubkey) + }, + // P2WSH + H256::LEN => { + SigningError::err(SigningErrorType::Error_script_redeem).context( + "pay-to-witness-script-hash can only be used via 'Input.InputBuilder.p2wsh'.\ + That is because P2SH address does not provide redeem script but its hash" + ) + }, + // Unknown + _ => SigningError::err(SigningErrorType::Error_invalid_address) + .context(format!("The given '{addr}' segwit address should have 20 or 32 byte length witness program")), + } + } + + pub fn recipient_taproot_address( + &self, + addr: &TaprootAddress, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let tweaked_pubkey = schnorr::XOnlyPublicKey::try_from(addr.witness_program()) + .tw_err(|_| SigningErrorType::Error_invalid_address) + .with_context(|| { + format!("The given '{addr}' taproot address witness program should be a valid tweaked schnorr public key") + })?; + + self.prepare_builder()? + .p2tr_key_path_with_tweaked_pubkey(&tweaked_pubkey) + } + + pub fn prepare_builder(&self) -> SigningResult { + let OutPoint { hash, index } = parse_out_point(&self.input.out_point)?; + let sighash_ty = SighashType::from_u32(self.input.sighash_type)?; + + if self.input.value < 0 { + return SigningError::err(SigningErrorType::Error_invalid_utxo_amount) + .context("UTXO amount cannot be negative"); + } + + let sequence = self + .input + .sequence + .clone() + .map(|seq| seq.sequence) + // Use the default 0xFFFFFFFF sequence value if not specified. + .unwrap_or(u32::MAX); + + Ok(UtxoBuilder::default() + .prev_txid(hash) + .prev_index(index) + .sequence(sequence) + .amount(self.input.value) + .sighash_type(sighash_ty)) + } + + /// Tries to convert [`Proto::PublicKeyOrHash`] to [`Hash`]. + /// Please note `P2PKH` and `P2WPKH` use the same `ripemd(sha256(x))` hash function. + fn get_ecdsa_pubkey_from_proto( + &self, + input: &Proto::PublicKeyOrHash, + ) -> SigningResult { + use Proto::mod_PublicKeyOrHash::OneOfvariant as PublicKeyOrHashType; + + let pubkey_data = match input.variant { + PublicKeyOrHashType::pubkey(ref pubkey) => pubkey.as_ref(), + PublicKeyOrHashType::hash(ref hash) => { + let hash = H160::try_from(hash.as_ref()) + .tw_err(|_| SigningErrorType::Error_invalid_params) + .context("Expected 20 bytes public key hash")?; + self.public_keys.get_public_key(&hash)? + }, + PublicKeyOrHashType::None => { + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("Expected a public key or its hash") + }, + }; + + ecdsa::secp256k1::PublicKey::try_from(pubkey_data) + .into_tw() + .context("Expected a valid ecdsa secp256k1 public key") + } +} + +pub fn parse_out_point(maybe_out_point: &Option) -> SigningResult { + let out_point = maybe_out_point + .as_ref() + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("No OutPoint provided for a UTXO")?; + + let hash = H256::try_from(out_point.hash.as_ref()) + .tw_err(|_| SigningErrorType::Error_invalid_params) + .context("Invalid previous txid")?; + + Ok(OutPoint { + hash, + index: out_point.vout, + }) +} diff --git a/rust/chains/tw_cosmos/Cargo.toml b/rust/chains/tw_cosmos/Cargo.toml new file mode 100644 index 00000000000..cbeae311ec3 --- /dev/null +++ b/rust/chains/tw_cosmos/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "tw_cosmos" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_keypair = { path = "../../tw_keypair" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk", features = ["test-utils"] } diff --git a/rust/chains/tw_cosmos/src/entry.rs b/rust/chains/tw_cosmos/src/entry.rs new file mode 100644 index 00000000000..dc2e9de681b --- /dev/null +++ b/rust/chains/tw_cosmos/src/entry.rs @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::wallet_connector::NoWalletConnector; +use tw_cosmos_sdk::address::{Address, Bech32Prefix}; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; +use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; +use tw_cosmos_sdk::modules::transaction_util::CosmosTransactionUtil; +use tw_keypair::tw; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct CosmosEntry; + +impl CoinEntry for CosmosEntry { + type AddressPrefix = Bech32Prefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + type WalletConnector = NoWalletConnector; + type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = CosmosTransactionUtil; + + #[inline] + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + prefix: Option, + ) -> AddressResult { + Address::from_str_with_coin_and_prefix(coin, address.to_string(), prefix) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: tw::PublicKey, + _derivation: Derivation, + prefix: Option, + ) -> AddressResult { + Address::with_public_key_coin_context(coin, &public_key, prefix) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + TWSigner::::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + TWTransactionCompiler::::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + TWTransactionCompiler::::compile( + coin, + input, + signatures, + public_keys, + ) + } + + #[inline] + fn transaction_util(&self) -> Option { + Some(CosmosTransactionUtil::::default()) + } +} diff --git a/rust/chains/tw_cosmos/src/lib.rs b/rust/chains/tw_cosmos/src/lib.rs new file mode 100644 index 00000000000..c080f3da3cf --- /dev/null +++ b/rust/chains/tw_cosmos/src/lib.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod entry; diff --git a/rust/chains/tw_ethereum/Cargo.toml b/rust/chains/tw_ethereum/Cargo.toml new file mode 100644 index 00000000000..6a3cbe0ebd8 --- /dev/null +++ b/rust/chains/tw_ethereum/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "tw_ethereum" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_evm = { path = "../../tw_evm" } +tw_keypair = { path = "../../tw_keypair" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_coin_entry = { path = "../../tw_coin_entry", features = ["test-utils"] } +tw_encoding = { path = "../../tw_encoding" } +tw_number = { path = "../../tw_number", features = ["helpers"] } diff --git a/rust/chains/tw_ethereum/src/entry.rs b/rust/chains/tw_ethereum/src/entry.rs new file mode 100644 index 00000000000..aad3d6f90b5 --- /dev/null +++ b/rust/chains/tw_ethereum/src/entry.rs @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::wallet_connector::NoWalletConnector; +use tw_coin_entry::prefix::NoPrefix; +use tw_evm::address::Address; +use tw_evm::evm_context::StandardEvmContext; +use tw_evm::evm_entry::EvmEntry; +use tw_evm::modules::compiler::Compiler; +use tw_evm::modules::message_signer::EthMessageSigner; +use tw_evm::modules::signer::Signer; +use tw_evm::modules::transaction_util::EvmTransactionUtil; +use tw_keypair::tw::PublicKey; +use tw_proto::Ethereum::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct EthereumEntry; + +impl CoinEntry for EthereumEntry { + type AddressPrefix = NoPrefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = EthMessageSigner; + type WalletConnector = NoWalletConnector; + type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = EvmTransactionUtil; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + _coin: &dyn CoinContext, + public_key: PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + let public_key = public_key + .to_secp256k1() + .ok_or(AddressError::PublicKeyTypeMismatch)?; + Ok(Address::with_secp256k1_pubkey(public_key)) + } + + #[inline] + fn sign(&self, _coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + Signer::::sign_proto(input) + } + + #[inline] + fn preimage_hashes( + &self, + _coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + Compiler::::preimage_hashes(input) + } + + #[inline] + fn compile( + &self, + _coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + Compiler::::compile(input, signatures, public_keys) + } + + #[inline] + fn message_signer(&self) -> Option { + Some(EthMessageSigner) + } + + #[inline] + fn transaction_util(&self) -> Option { + Some(EvmTransactionUtil) + } +} + +impl EvmEntry for EthereumEntry { + type Context = StandardEvmContext; +} diff --git a/rust/chains/tw_ethereum/src/lib.rs b/rust/chains/tw_ethereum/src/lib.rs new file mode 100644 index 00000000000..c080f3da3cf --- /dev/null +++ b/rust/chains/tw_ethereum/src/lib.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod entry; diff --git a/rust/chains/tw_ethereum/tests/compiler.rs b/rust/chains/tw_ethereum/tests/compiler.rs new file mode 100644 index 00000000000..7d88e7279e2 --- /dev/null +++ b/rust/chains/tw_ethereum/tests/compiler.rs @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::borrow::Cow; +use tw_coin_entry::coin_entry_ext::CoinEntryExt; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_encoding::hex; +use tw_ethereum::entry::EthereumEntry; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::tw; +use tw_number::U256; +use tw_proto::Ethereum::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_proto::{deserialize, serialize}; + +#[test] +fn test_external_signature_sign() { + let coin = TestCoinContext::default(); + + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(1_000_000_000_000_000_000), + data: Cow::default(), + }; + let input = Proto::SigningInput { + nonce: U256::encode_be_compact(11), + chain_id: U256::encode_be_compact(1), + gas_price: U256::encode_be_compact(20_000_000_000), + gas_limit: U256::encode_be_compact(21_000), + to_address: "0x3535353535353535353535353535353535353535".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), + }), + ..Proto::SigningInput::default() + }; + + // Step 1: Obtain preimage hash + let input_data = serialize(&input).unwrap(); + let preimage_data = EthereumEntry + .preimage_hashes(&coin, &input_data) + .expect("!preimage_hashes"); + let preimage: CompilerProto::PreSigningOutput = + deserialize(&preimage_data).expect("Coin entry returned an invalid output"); + + assert_eq!(preimage.error, SigningErrorType::OK); + assert!(preimage.error_message.is_empty()); + assert_eq!( + hex::encode(&preimage.data_hash, false), + "15e180a6274b2f6a572b9b51823fce25ef39576d10188ecdcd7de44526c47217" + ); + + // Simulate signature, normally obtained from signature server + let public_key = secp256k1::PublicKey::try_from("044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad7119ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a").unwrap(); + let public_key = tw::PublicKey::Secp256k1Extended(public_key); + let signature = hex::decode("360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c9796677900").unwrap(); + + // Verify signature (pubkey & hash & signature) + assert!(public_key.verify(&signature, &preimage.data_hash)); + + // Step 2: Compile transaction info + let input_data = serialize(&input).unwrap(); + let output_data = EthereumEntry + .compile( + &coin, + &input_data, + vec![signature], + vec![public_key.to_bytes()], + ) + .expect("!compile"); + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + let expected_encoded = "f86c0b8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07ba053bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c97966779"; + assert_eq!(hex::encode(output.encoded, false), expected_encoded); + + // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + let input = Proto::SigningInput { + private_key: Cow::from( + hex::decode("4646464646464646464646464646464646464646464646464646464646464646") + .unwrap(), + ), + ..input + }; + + let input_data = serialize(&input).unwrap(); + let output_data = EthereumEntry + .sign(&coin, &input_data) + .expect("!output_data"); + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + assert_eq!(hex::encode(output.encoded, false), expected_encoded); +} diff --git a/rust/chains/tw_ethereum/tests/signer.rs b/rust/chains/tw_ethereum/tests/signer.rs new file mode 100644 index 00000000000..06b6af14507 --- /dev/null +++ b/rust/chains/tw_ethereum/tests/signer.rs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_entry_ext::CoinEntryExt; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_encoding::hex::DecodeHex; +use tw_ethereum::entry::EthereumEntry; + +#[test] +fn test_sign_json() { + let coin = TestCoinContext::default(); + + let input_json = r#"{"chainId":"AQ==","gasPrice":"1pOkAA==","gasLimit":"Ugg=","toAddress":"0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1","transaction":{"transfer":{"amount":"A0i8paFgAA=="}}})"#; + let private_key = "17209af590a86462395d5881e60d11c7fa7d482cfb02b5a01b93c2eeef243543" + .decode_hex() + .unwrap(); + + EthereumEntry + .sign_json(&coin, input_json, private_key) + .expect_err("'EthEntry::sign_json' is not supported yet"); + + // Expected result - "f86a8084d693a400825208947d8bf18c7ce84b3e175b339c4ca93aed1dd166f1870348bca5a160008025a0fe5802b49e04c6b1705088310e133605ed8b549811a18968ad409ea02ad79f21a05bf845646fb1e1b9365f63a7fd5eb5e984094e3ed35c3bed7361aebbcbf41f10" +} diff --git a/rust/chains/tw_greenfield/Cargo.toml b/rust/chains/tw_greenfield/Cargo.toml new file mode 100644 index 00000000000..ab4ee6f224f --- /dev/null +++ b/rust/chains/tw_greenfield/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "tw_greenfield" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_encoding = { path = "../../tw_encoding" } +tw_evm = { path = "../../tw_evm" } +tw_hash = { path = "../../tw_hash" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_misc = { path = "../../tw_misc" } +tw_number = { path = "../../tw_number" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_coin_entry = { path = "../../tw_coin_entry", features = ["test-utils"] } +tw_misc = { path = "../../tw_misc", features = ["test-utils"] } diff --git a/rust/chains/tw_greenfield/fuzz/.gitignore b/rust/chains/tw_greenfield/fuzz/.gitignore new file mode 100644 index 00000000000..5c404b9583f --- /dev/null +++ b/rust/chains/tw_greenfield/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +Cargo.lock diff --git a/rust/chains/tw_greenfield/fuzz/Cargo.toml b/rust/chains/tw_greenfield/fuzz/Cargo.toml new file mode 100644 index 00000000000..fbc0f5790cc --- /dev/null +++ b/rust/chains/tw_greenfield/fuzz/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "tw_greenfield-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +tw_any_coin = { path = "../../../tw_any_coin", features = ["test-utils"] } +tw_coin_registry = { path = "../../../tw_coin_registry" } +tw_proto = { path = "../../../tw_proto", features = ["fuzz"] } + +[dependencies.tw_greenfield] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "sign" +path = "fuzz_targets/sign.rs" +test = false +doc = false diff --git a/rust/chains/tw_greenfield/fuzz/fuzz_targets/sign.rs b/rust/chains/tw_greenfield/fuzz/fuzz_targets/sign.rs new file mode 100644 index 00000000000..f27808e5e42 --- /dev/null +++ b/rust/chains/tw_greenfield/fuzz/fuzz_targets/sign.rs @@ -0,0 +1,11 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_any_coin::test_utils::sign_utils::AnySignerHelper; +use tw_coin_registry::coin_type::CoinType; +use tw_proto::Greenfield::Proto; + +fuzz_target!(|input: Proto::SigningInput<'_>| { + let mut signer = AnySignerHelper::::default(); + let _ = signer.sign(CoinType::Greenfield, input); +}); diff --git a/rust/chains/tw_greenfield/src/address.rs b/rust/chains/tw_greenfield/src/address.rs new file mode 100644 index 00000000000..94b9565b17a --- /dev/null +++ b/rust/chains/tw_greenfield/src/address.rs @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use serde::Serialize; +use std::fmt; +use std::str::FromStr; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::*; +use tw_cosmos_sdk::address::CosmosAddress; +use tw_evm::address::Address as EthereumAddress; +use tw_keypair::ecdsa::secp256k1; +use tw_memory::Data; + +#[derive(Clone, Serialize)] +pub struct GreenfieldAddress(EthereumAddress); + +impl GreenfieldAddress { + /// Initializes an address with a `secp256k1` public key. + pub fn with_secp256k1_pubkey(pubkey: &secp256k1::PublicKey) -> GreenfieldAddress { + GreenfieldAddress(EthereumAddress::with_secp256k1_pubkey(pubkey)) + } +} + +impl CosmosAddress for GreenfieldAddress {} + +impl CoinAddress for GreenfieldAddress { + #[inline] + fn data(&self) -> Data { + self.0.data() + } +} + +impl FromStr for GreenfieldAddress { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + EthereumAddress::from_str(s).map(GreenfieldAddress) + } +} + +impl fmt::Display for GreenfieldAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/rust/chains/tw_greenfield/src/compiler.rs b/rust/chains/tw_greenfield/src/compiler.rs new file mode 100644 index 00000000000..e9db4725199 --- /dev/null +++ b/rust/chains/tw_greenfield/src/compiler.rs @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::context::GreenfieldContext; +use crate::modules::eip712_signer::{Eip712Signer, Eip712TxPreimage}; +use crate::modules::tx_builder::TxBuilder; +use crate::public_key::GreenfieldPublicKey; +use crate::signature::GreenfieldSignature; +use std::borrow::Cow; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::common::compile_input::SingleSignaturePubkey; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_cosmos_sdk::modules::broadcast_msg::{BroadcastMode, BroadcastMsg}; +use tw_cosmos_sdk::modules::serializer::json_serializer::JsonSerializer; +use tw_cosmos_sdk::modules::serializer::protobuf_serializer::ProtobufSerializer; +use tw_cosmos_sdk::public_key::CosmosPublicKey; +use tw_misc::traits::ToBytesVec; +use tw_proto::Greenfield::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct GreenfieldCompiler; + +impl GreenfieldCompiler { + /// Please note that [`Proto::SigningInput::public_key`] must be set. + /// If the public key should be derived from a private key, please do it before this method is called. + #[inline] + pub fn preimage_hashes( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + Self::preimage_hashes_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + fn preimage_hashes_impl( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let unsigned = TxBuilder::unsigned_tx_from_proto(coin, &input)?; + let Eip712TxPreimage { eip712_tx, tx_hash } = Eip712Signer::preimage_hash(&unsigned)?; + + Ok(CompilerProto::PreSigningOutput { + data: Cow::from(eip712_tx.to_vec()), + data_hash: Cow::from(tx_hash.to_vec()), + ..CompilerProto::PreSigningOutput::default() + }) + } + + #[inline] + pub fn compile( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + Self::compile_impl(coin, input, signatures, public_keys) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + pub(crate) fn compile_impl( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> SigningResult> { + let SingleSignaturePubkey { + signature: raw_signature, + public_key, + } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; + + let public_key_params = None; + let public_key = GreenfieldPublicKey::from_bytes(coin, &public_key, public_key_params) + .into_tw() + .context("Invalid provided public key")?; + let signature = GreenfieldSignature::try_from(raw_signature.as_slice()) + .into_tw() + .context("Invalid provided signature")?; + let signature_bytes = signature.to_vec(); + + // Set the public key. It will be used to construct a signer info. + input.public_key = Cow::from(public_key.to_bytes()); + let unsigned = TxBuilder::unsigned_tx_from_proto(coin, &input)?; + + let signed_tx = unsigned.into_signed(signature); + let signed_tx_raw = ProtobufSerializer::::build_signed_tx(&signed_tx)?; + + let broadcast_mode = Self::broadcast_mode(input.mode); + let broadcast_tx = BroadcastMsg::raw(broadcast_mode, &signed_tx_raw).to_json_string(); + + let signature_json = JsonSerializer::::serialize_signature( + &public_key, + signature_bytes.clone(), + ); + let signature_json = serde_json::to_string(&[signature_json]) + .tw_err(|_| SigningErrorType::Error_internal) + .context("Error serializing signatures as JSON")?; + + Ok(Proto::SigningOutput { + signature: Cow::from(signature_bytes), + signature_json: Cow::from(signature_json), + serialized: Cow::from(broadcast_tx), + ..Proto::SigningOutput::default() + }) + } + + fn broadcast_mode(input: Proto::BroadcastMode) -> BroadcastMode { + match input { + Proto::BroadcastMode::SYNC => BroadcastMode::Sync, + Proto::BroadcastMode::ASYNC => BroadcastMode::Async, + } + } +} diff --git a/rust/chains/tw_greenfield/src/context.rs b/rust/chains/tw_greenfield/src/context.rs new file mode 100644 index 00000000000..888147cde48 --- /dev/null +++ b/rust/chains/tw_greenfield/src/context.rs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::GreenfieldAddress; +use crate::public_key::GreenfieldPublicKey; +use tw_cosmos_sdk::context::CosmosContext; +use tw_cosmos_sdk::private_key::secp256k1::Secp256PrivateKey; +use tw_cosmos_sdk::signature::secp256k1::Secp256k1Signature; +use tw_hash::hasher::Hasher; + +pub struct GreenfieldContext; + +impl CosmosContext for GreenfieldContext { + type Address = GreenfieldAddress; + type PrivateKey = Secp256PrivateKey; + type PublicKey = GreenfieldPublicKey; + type Signature = Secp256k1Signature; + + /// Greenfield uses EIP712 message signing algorithm built upon `keccak256` hash. + fn default_tx_hasher() -> Hasher { + Hasher::Keccak256 + } +} diff --git a/rust/chains/tw_greenfield/src/eip712_types.rs b/rust/chains/tw_greenfield/src/eip712_types.rs new file mode 100644 index 00000000000..3bf8f103d52 --- /dev/null +++ b/rust/chains/tw_greenfield/src/eip712_types.rs @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::transaction::GreenfieldFee; +use core::fmt; +use serde::{Serialize, Serializer}; +use serde_json::Value as Json; +use std::collections::BTreeMap; +use tw_cosmos_sdk::transaction::message::JsonMessage; +use tw_cosmos_sdk::transaction::Coin; +use tw_evm::abi::param_type::constructor::TypeConstructor; +use tw_evm::message::eip712::message_types::MessageTypesBuilder; +use tw_evm::message::eip712::property::PropertyType; +use tw_number::U256; + +const DOMAIN_NAME: &str = "Greenfield Tx"; +const DOMAIN_VERSION: &str = "1.0.0"; +const DOMAIN_VERIFY_CONTRACT: &str = "greenfield"; +const DOMAIN_SALT: &str = "0"; + +#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct MsgPropertyName(pub usize); + +impl fmt::Display for MsgPropertyName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "msg{}", self.0) + } +} + +pub struct MsgPropertyType(pub usize); + +impl fmt::Display for MsgPropertyType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Msg{}", self.0) + } +} + +impl Serialize for MsgPropertyName { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.to_string().serialize(serializer) + } +} + +/// # EIP712 type +/// +/// ```json +/// { +/// "EIP712Domain": [ +/// { +/// "name": "chainId", +/// "type": "uint256" +/// }, +/// { +/// "name": "name", +/// "type": "string" +/// }, +/// { +/// "name": "salt", +/// "type": "string" +/// }, +/// { +/// "name": "verifyingContract", +/// "type": "string" +/// }, +/// { +/// "name": "version", +/// "type": "string" +/// } +/// ] +/// } +/// ``` +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Eip712Domain { + pub name: String, + pub version: String, + #[serde(serialize_with = "U256::as_decimal_str")] + pub chain_id: U256, + pub verifying_contract: String, + pub salt: String, +} + +impl Eip712Domain { + const TYPE_NAME: &'static str = "EIP712Domain"; + + /// Returns a Serializable data of the `EIP712Domain` type. + /// https://github.com/bnb-chain/greenfield-cosmos-sdk/blob/b48770f5e210b28536f92734b6228913666d4da1/x/auth/tx/eip712.go#L35-L40 + pub fn new(chain_id: U256) -> Eip712Domain { + Eip712Domain { + name: DOMAIN_NAME.to_string(), + version: DOMAIN_VERSION.to_string(), + chain_id, + verifying_contract: DOMAIN_VERIFY_CONTRACT.to_string(), + salt: DOMAIN_SALT.to_string(), + } + } + + pub fn declare_eip712_types(&self, builder: &mut MessageTypesBuilder) { + if let Some(mut domain_builder) = builder.add_custom_type(Self::TYPE_NAME.to_string()) { + domain_builder + .add_property("chainId", PropertyType::Uint) + .add_property("name", PropertyType::String) + .add_property("salt", PropertyType::String) + .add_property("verifyingContract", PropertyType::String) + .add_property("version", PropertyType::String); + } + } +} + +/// # EIP712 type +/// +/// ```json +/// { +/// "Coin": [ +/// { +/// "name": "amount", +/// "type": "uint256" +/// }, +/// { +/// "name": "denom", +/// "type": "string" +/// } +/// ] +/// } +/// ``` +pub struct Eip712Coin; + +impl Eip712Coin { + const TYPE_NAME: &'static str = "Coin"; + + pub fn declare_eip712_types(builder: &mut MessageTypesBuilder) { + if let Some(mut coin_builder) = builder.add_custom_type(Self::TYPE_NAME.to_string()) { + coin_builder + .add_property("amount", PropertyType::Uint) + .add_property("denom", PropertyType::String); + } + } +} + +/// # EIP712 type +/// +/// ```json +/// { +/// "Fee": [ +/// { +/// "name": "amount", +/// "type": "Coin[]" +/// }, +/// { +/// "name": "gas_limit", +/// "type": "uint256" +/// }, +/// { +/// "name": "granter", +/// "type": "string" +/// }, +/// { +/// "name": "payer", +/// "type": "string" +/// } +/// ] +/// } +/// ``` +#[derive(Serialize)] +pub struct Eip712Fee { + pub amount: Vec, + #[serde(serialize_with = "U256::as_decimal_str")] + pub gas_limit: U256, + pub payer: String, + pub granter: String, +} + +impl Eip712Fee { + const TYPE_NAME: &'static str = "Fee"; + + pub fn declare_eip712_types(builder: &mut MessageTypesBuilder) { + // `Tx` depends on `Coin` and `Fee` custom types. + Eip712Coin::declare_eip712_types(builder); + + if let Some(mut fee_builder) = builder.add_custom_type(Self::TYPE_NAME.to_string()) { + let amount_type = PropertyType::Custom(Eip712Coin::TYPE_NAME.to_string()); + fee_builder + .add_property("amount", PropertyType::array(amount_type)) + .add_property("gas_limit", PropertyType::Uint) + .add_property("granter", PropertyType::String) + .add_property("payer", PropertyType::String); + } + } +} + +impl From for Eip712Fee { + fn from(fee: GreenfieldFee) -> Self { + let payer = fee + .payer + .as_ref() + .map(|addr| addr.to_string()) + .unwrap_or_default(); + let granter = fee + .granter + .as_ref() + .map(|addr| addr.to_string()) + .unwrap_or_default(); + + Eip712Fee { + amount: fee.amounts.clone(), + gas_limit: U256::from(fee.gas_limit), + payer, + granter, + } + } +} + +#[derive(Clone, Serialize)] +pub struct Eip712TypedMsg { + #[serde(rename = "type")] + pub msg_type: String, + #[serde(flatten)] + pub value: Json, +} + +impl From for Eip712TypedMsg { + fn from(msg: JsonMessage) -> Self { + Eip712TypedMsg { + msg_type: msg.msg_type, + value: msg.value, + } + } +} + +/// # EIP712 type +/// +/// ```json +/// { +/// "Tx": [ +/// { +/// "name": "account_number", +/// "type": "uint256" +/// }, +/// { +/// "name": "chain_id", +/// "type": "uint256" +/// }, +/// { +/// "name": "fee", +/// "type": "Fee" +/// }, +/// { +/// "name": "memo", +/// "type": "string" +/// }, +/// { +/// "name": "msg1", +/// "type": "Msg1" +/// }, +/// { +/// "name": "sequence", +/// "type": "uint256" +/// }, +/// { +/// "name": "timeout_height", +/// "type": "uint256" +/// } +/// ] +/// } +/// ``` +#[derive(Serialize)] +pub struct Eip712Transaction { + #[serde(serialize_with = "U256::as_decimal_str")] + pub account_number: U256, + #[serde(serialize_with = "U256::as_decimal_str")] + pub chain_id: U256, + pub fee: Eip712Fee, + pub memo: String, + /// Will be flatten as `"msg1": { ... }, "msg2": { ... }`. + #[serde(flatten)] + pub msgs: BTreeMap, + #[serde(serialize_with = "U256::as_decimal_str")] + pub sequence: U256, + #[serde(serialize_with = "U256::as_decimal_str")] + pub timeout_height: U256, +} + +impl Eip712Transaction { + /// cbindgen::ignore + pub const TYPE_NAME: &'static str = "Tx"; + + pub fn declare_eip712_types(&self, builder: &mut MessageTypesBuilder) { + Eip712Fee::declare_eip712_types(builder); + + let Some(mut tx_builder) = builder.add_custom_type(Self::TYPE_NAME.to_string()) else { + return; + }; + + tx_builder + .add_property("account_number", PropertyType::Uint) + .add_property("chain_id", PropertyType::Uint) + .add_property( + "fee", + PropertyType::Custom(Eip712Fee::TYPE_NAME.to_string()), + ) + .add_property("memo", PropertyType::String) + .add_property("sequence", PropertyType::Uint) + .add_property("timeout_height", PropertyType::Uint); + + for (msg_property_name, _msg) in self.msgs.iter() { + let msg_property_type = MsgPropertyType(msg_property_name.0).to_string(); + let msg_property_type = PropertyType::Custom(msg_property_type.to_string()); + + let msg_property_name = msg_property_name.to_string(); + tx_builder.add_property(&msg_property_name, msg_property_type); + } + + tx_builder.sort_by_names(); + } +} diff --git a/rust/chains/tw_greenfield/src/entry.rs b/rust/chains/tw_greenfield/src/entry.rs new file mode 100644 index 00000000000..448cbe54f15 --- /dev/null +++ b/rust/chains/tw_greenfield/src/entry.rs @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::GreenfieldAddress; +use crate::compiler::GreenfieldCompiler; +use crate::signer::GreenfieldSigner; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; +use tw_coin_entry::modules::wallet_connector::NoWalletConnector; +use tw_coin_entry::prefix::NoPrefix; +use tw_keypair::tw::PublicKey; +use tw_proto::Greenfield::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct GreenfieldEntry; + +impl CoinEntry for GreenfieldEntry { + type AddressPrefix = NoPrefix; + type Address = GreenfieldAddress; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + type WalletConnector = NoWalletConnector; + type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + GreenfieldAddress::from_str(address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + GreenfieldAddress::from_str(address) + } + + #[inline] + fn derive_address( + &self, + _coin: &dyn CoinContext, + public_key: PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + let public_key = public_key + .to_secp256k1() + .ok_or(AddressError::PublicKeyTypeMismatch)?; + Ok(GreenfieldAddress::with_secp256k1_pubkey(public_key)) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + GreenfieldSigner::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + GreenfieldCompiler::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + GreenfieldCompiler::compile(coin, input, signatures, public_keys) + } +} diff --git a/rust/chains/tw_greenfield/src/lib.rs b/rust/chains/tw_greenfield/src/lib.rs new file mode 100644 index 00000000000..02e2d565253 --- /dev/null +++ b/rust/chains/tw_greenfield/src/lib.rs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod address; +pub mod compiler; +pub mod context; +pub mod eip712_types; +pub mod entry; +pub mod modules; +pub mod public_key; +pub mod signature; +pub mod signer; +pub mod transaction; diff --git a/rust/chains/tw_greenfield/src/modules/eip712_signer.rs b/rust/chains/tw_greenfield/src/modules/eip712_signer.rs new file mode 100644 index 00000000000..c5ead1f19de --- /dev/null +++ b/rust/chains/tw_greenfield/src/modules/eip712_signer.rs @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::eip712_types::{ + Eip712Domain, Eip712Fee, Eip712Transaction, Eip712TypedMsg, MsgPropertyName, +}; +use crate::transaction::GreenfieldUnsignedTransaction; +use std::collections::BTreeMap; +use tw_coin_entry::error::prelude::*; +use tw_evm::message::eip712::eip712_message::Eip712Message; +use tw_evm::message::eip712::message_types::MessageTypesBuilder; +use tw_evm::message::{to_signing, EthMessage}; +use tw_hash::H256; +use tw_number::U256; + +pub struct Eip712TxPreimage { + pub eip712_tx: String, + pub tx_hash: H256, +} + +pub struct Eip712Signer; + +impl Eip712Signer { + pub fn preimage_hash( + unsigned: &GreenfieldUnsignedTransaction, + ) -> SigningResult { + // `types_builder` will be used to declare all custom types like `Tx`, `Fee`, `Msg1` etc. + let mut types_builder = MessageTypesBuilder::default(); + + // Step 1: Convert all [`TxBody::messages`] to a map of `msg1: GreenfieldTypedMsg`, `msg2: GreenfieldTypedMsg`. + // at the same time, declare the message custom types. + let mut msgs = BTreeMap::new(); + for (msg_idx, msg) in unsigned.tx_body.messages.iter().enumerate() { + // Index of the transaction messages starts from 1. + let msg_idx = msg_idx + 1; + + let property_name = MsgPropertyName(msg_idx); + let property_value = Eip712TypedMsg::from(msg.to_json()?); + + msgs.insert(property_name, property_value); + + // Declare message custom types like `Msg1`, `TypeMsg1Amount`, etc. + msg.declare_eip712_type(msg_idx, &mut types_builder); + } + + // Step 2: Generate `Tx` and `Domain` objects - the main parts of the EIP712 message. + let tx_to_sign = Eip712Transaction { + account_number: U256::from(unsigned.account_number), + chain_id: unsigned.eth_chain_id, + fee: Eip712Fee::from(unsigned.fee.clone()), + memo: unsigned.tx_body.memo.clone(), + msgs, + sequence: U256::from(unsigned.signer.sequence), + timeout_height: U256::zero(), + }; + let domain = Eip712Domain::new(unsigned.eth_chain_id); + + // Step 3: Declare `Tx`, `Domain` and all types they depend on. + tx_to_sign.declare_eip712_types(&mut types_builder); + domain.declare_eip712_types(&mut types_builder); + + // Step 4: Generate EIP712 message with all declared custom types, `Domain` and `Tx`, + // and compute the EIP712 message hash. + let msg_to_sign = Eip712Message { + types: types_builder.build(), + domain: serde_json::to_value(domain) + .tw_err(|_| SigningErrorType::Error_internal) + .context("Error serializing EIP712Domain as JSON")?, + primary_type: Eip712Transaction::TYPE_NAME.to_string(), + message: serde_json::to_value(tx_to_sign) + .tw_err(|_| SigningErrorType::Error_internal) + .context("Error serializing EIP712 message payload as JSON")?, + }; + + let tx_hash = msg_to_sign.hash().map_err(to_signing)?; + let eip712_tx = + serde_json::to_string(&msg_to_sign).tw_err(|_| SigningErrorType::Error_internal)?; + + Ok(Eip712TxPreimage { eip712_tx, tx_hash }) + } +} diff --git a/rust/chains/tw_greenfield/src/modules/mod.rs b/rust/chains/tw_greenfield/src/modules/mod.rs new file mode 100644 index 00000000000..a9eed00576a --- /dev/null +++ b/rust/chains/tw_greenfield/src/modules/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod eip712_signer; +pub mod tx_builder; diff --git a/rust/chains/tw_greenfield/src/modules/tx_builder.rs b/rust/chains/tw_greenfield/src/modules/tx_builder.rs new file mode 100644 index 00000000000..6a12bb2aa6e --- /dev/null +++ b/rust/chains/tw_greenfield/src/modules/tx_builder.rs @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::GreenfieldAddress; +use crate::public_key::GreenfieldPublicKey; +use crate::transaction::message::GreenfieldMessageBox; +use crate::transaction::{ + GreenfieldFee, GreenfieldSignMode, GreenfieldSignerInfo, GreenfieldTxBody, + GreenfieldUnsignedTransaction, +}; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_cosmos_sdk::public_key::CosmosPublicKey; +use tw_cosmos_sdk::transaction::{Coin, SignerInfo}; +use tw_misc::traits::OptionalEmpty; +use tw_number::U256; +use tw_proto::Greenfield::Proto; + +const DEFAULT_TIMEOUT_HEIGHT: u64 = 0; + +/// [`TxBuilder`] is used to build `UnsignedTransaction` +/// from the `TW::Greenfield::Proto::SigningInput` protobuf message. +pub struct TxBuilder; + +impl TxBuilder { + /// Please note that [`Proto::SigningInput::public_key`] must be set. + /// If the public key should be derived from a private key, please do it before this method is called. + pub fn unsigned_tx_from_proto( + coin: &dyn CoinContext, + input: &Proto::SigningInput<'_>, + ) -> SigningResult { + let signer = Self::signer_info_from_proto(coin, input)?; + + let fee = input + .fee + .as_ref() + .or_tw_err(SigningErrorType::Error_wrong_fee) + .context("No 'fee' specified")?; + let fee = Self::fee_from_proto(fee, &signer)?; + + let eth_chain_id = U256::from_str(&input.eth_chain_id) + .tw_err(|_| SigningErrorType::Error_invalid_params) + .context("Invalid ETH chain ID")?; + + Ok(GreenfieldUnsignedTransaction { + signer, + fee, + cosmos_chain_id: input.cosmos_chain_id.to_string(), + eth_chain_id, + account_number: input.account_number, + tx_body: Self::tx_body_from_proto(input)?, + }) + } + + pub fn signer_info_from_proto( + coin: &dyn CoinContext, + input: &Proto::SigningInput, + ) -> SigningResult { + let public_key_params = None; + let public_key = + GreenfieldPublicKey::from_bytes(coin, &input.public_key, public_key_params)?; + let sign_mode = match input.signing_mode { + Proto::SigningMode::Eip712 => GreenfieldSignMode::Eip712, + }; + Ok(SignerInfo { + public_key, + sequence: input.sequence, + sign_mode: sign_mode.into(), + }) + } + + fn fee_from_proto( + input: &Proto::Fee, + signer: &GreenfieldSignerInfo, + ) -> SigningResult { + let payer = GreenfieldAddress::with_secp256k1_pubkey(&signer.public_key.0); + + let amounts = input + .amounts + .iter() + .map(Self::coin_from_proto) + .collect::>()?; + Ok(GreenfieldFee { + amounts, + gas_limit: input.gas, + payer: Some(payer), + granter: None, + }) + } + + fn coin_from_proto(input: &Proto::Amount<'_>) -> SigningResult { + let amount = U256::from_str(&input.amount) + .into_tw() + .context("Invalid amount: expected uint256 decimal-string")?; + Ok(Coin { + amount, + denom: input.denom.to_string(), + }) + } + + fn tx_body_from_proto(input: &Proto::SigningInput<'_>) -> SigningResult { + if input.messages.is_empty() { + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("No transaction messages provided"); + } + + let messages = input + .messages + .iter() + .map(Self::tx_message_from_proto) + .collect::>()?; + + Ok(GreenfieldTxBody { + messages, + memo: input.memo.to_string(), + timeout_height: DEFAULT_TIMEOUT_HEIGHT, + }) + } + + pub fn tx_message_from_proto(input: &Proto::Message) -> SigningResult { + use Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + + match input.message_oneof { + MessageEnum::send_coins_message(ref send) => Self::send_msg_from_proto(send), + MessageEnum::bridge_transfer_out(ref transfer_out) => { + Self::bridge_transfer_out_from_proto(transfer_out) + }, + MessageEnum::None => SigningError::err(SigningErrorType::Error_invalid_params) + .context("No message type provided"), + } + } + + pub fn send_msg_from_proto( + send: &Proto::mod_Message::Send<'_>, + ) -> SigningResult { + use crate::transaction::message::send_order::GreenfieldSendMessage; + use tw_cosmos_sdk::transaction::message::cosmos_bank_message::SendMessage; + + let amounts = send + .amounts + .iter() + .map(Self::coin_from_proto) + .collect::>()?; + let msg = SendMessage { + custom_type_prefix: send.type_prefix.to_string().empty_or_some(), + from_address: GreenfieldAddress::from_str(&send.from_address) + .into_tw() + .context("Invalid sender address")?, + to_address: GreenfieldAddress::from_str(&send.to_address) + .into_tw() + .context("Invalid recipient address")?, + amount: amounts, + }; + Ok(Box::new(GreenfieldSendMessage(msg))) + } + + pub fn bridge_transfer_out_from_proto( + transfer_out: &Proto::mod_Message::BridgeTransferOut<'_>, + ) -> SigningResult { + use crate::transaction::message::transfer_out::GreenfieldTransferOut; + + let amount = transfer_out + .amount + .as_ref() + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("No transfer amount specified")?; + + let msg = GreenfieldTransferOut { + custom_type_prefix: transfer_out.type_prefix.to_string().empty_or_some(), + amount: Self::coin_from_proto(amount)?, + from: GreenfieldAddress::from_str(&transfer_out.from_address) + .into_tw() + .context("Invalid sender address")?, + to: GreenfieldAddress::from_str(&transfer_out.to_address) + .into_tw() + .context("Invalid recipient address")?, + }; + Ok(Box::new(msg)) + } +} diff --git a/rust/chains/tw_greenfield/src/public_key.rs b/rust/chains/tw_greenfield/src/public_key.rs new file mode 100644 index 00000000000..41f4a3cced6 --- /dev/null +++ b/rust/chains/tw_greenfield/src/public_key.rs @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_cosmos_sdk::public_key::{ + CosmosPublicKey, JsonPublicKey, ProtobufPublicKey, PublicKeyParams, +}; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::{tw, KeyPairError, KeyPairResult}; +use tw_memory::Data; +use tw_proto::{google, to_any}; + +pub struct GreenfieldPublicKey(pub secp256k1::PublicKey); + +impl JsonPublicKey for GreenfieldPublicKey { + fn public_key_type(&self) -> String { + "/cosmos.crypto.eth.ethsecp256k1.PubKey".to_string() + } +} + +impl ProtobufPublicKey for GreenfieldPublicKey { + fn to_proto(&self) -> google::protobuf::Any { + let proto = tw_cosmos_sdk::proto::cosmos::crypto::eth::ethsecp256k1::PubKey { + key: self.0.compressed().to_vec(), + }; + to_any(&proto) + } +} + +impl CosmosPublicKey for GreenfieldPublicKey { + fn from_private_key( + _coin: &dyn CoinContext, + private_key: &tw::PrivateKey, + // Ignore custom public key parameters. + _params: Option, + ) -> KeyPairResult { + let public_key = private_key + .get_public_key_by_type(tw::PublicKeyType::Secp256k1)? + .to_secp256k1() + .ok_or(KeyPairError::InvalidPublicKey)? + .clone(); + Ok(GreenfieldPublicKey(public_key)) + } + + fn from_bytes( + _coin: &dyn CoinContext, + public_key_bytes: &[u8], + // Ignore custom public key parameters. + _params: Option, + ) -> KeyPairResult { + secp256k1::PublicKey::try_from(public_key_bytes).map(GreenfieldPublicKey) + } + + fn to_bytes(&self) -> Data { + self.0.compressed().to_vec() + } +} diff --git a/rust/chains/tw_greenfield/src/signature.rs b/rust/chains/tw_greenfield/src/signature.rs new file mode 100644 index 00000000000..bc2094edb67 --- /dev/null +++ b/rust/chains/tw_greenfield/src/signature.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_cosmos_sdk::signature::CosmosSignature; +use tw_evm::message::signature::{MessageSignature, SignatureType}; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::KeyPairError; +use tw_misc::traits::ToBytesVec; + +/// secp256k1 signature with the legacy ETH replay protection. +#[derive(Clone)] +pub struct GreenfieldSignature(MessageSignature); + +impl TryFrom for GreenfieldSignature { + type Error = KeyPairError; + + fn try_from(sign: secp256k1::Signature) -> Result { + MessageSignature::prepared(sign, SignatureType::Legacy).map(GreenfieldSignature) + } +} + +impl CosmosSignature for GreenfieldSignature {} + +/// [`GreenfieldSignature::try_from`] tries to parse a standard secp256k1 signature from the given bytes, +/// and applies the legacy ETH replay protection. +impl<'a> TryFrom<&'a [u8]> for GreenfieldSignature { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + let standard_sign = secp256k1::Signature::try_from(bytes)?; + GreenfieldSignature::try_from(standard_sign) + } +} + +impl ToBytesVec for GreenfieldSignature { + fn to_vec(&self) -> Vec { + self.0.to_bytes().to_vec() + } +} diff --git a/rust/chains/tw_greenfield/src/signer.rs b/rust/chains/tw_greenfield/src/signer.rs new file mode 100644 index 00000000000..afde1ed2747 --- /dev/null +++ b/rust/chains/tw_greenfield/src/signer.rs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::compiler::GreenfieldCompiler; +use crate::modules::eip712_signer::{Eip712Signer, Eip712TxPreimage}; +use crate::modules::tx_builder::TxBuilder; +use std::borrow::Cow; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait}; +use tw_misc::traits::ToBytesVec; +use tw_proto::Greenfield::Proto; + +pub struct GreenfieldSigner; + +impl GreenfieldSigner { + pub fn sign( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> Proto::SigningOutput<'static> { + Self::sign_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_impl( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let key_pair = secp256k1::KeyPair::try_from(input.private_key.as_ref())?; + let public_key = key_pair.public().compressed().to_vec(); + + // Set the public key. It will be used to construct a signer info. + input.public_key = Cow::from(public_key.clone()); + let unsigned_tx = TxBuilder::unsigned_tx_from_proto(coin, &input)?; + + let Eip712TxPreimage { tx_hash, .. } = Eip712Signer::preimage_hash(&unsigned_tx)?; + // Get the standard secp256k1 signature. It will be EIP155 protected at the `GreenfieldCompiler::compile_impl`. + let signature = key_pair.sign(tx_hash)?; + + let signatures = vec![signature.to_vec()]; + let public_keys = vec![public_key]; + + GreenfieldCompiler::compile_impl(coin, input, signatures, public_keys) + } +} diff --git a/rust/chains/tw_greenfield/src/transaction/message/mod.rs b/rust/chains/tw_greenfield/src/transaction/message/mod.rs new file mode 100644 index 00000000000..7921931c696 --- /dev/null +++ b/rust/chains/tw_greenfield/src/transaction/message/mod.rs @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::eip712_types::MsgPropertyType; +use tw_cosmos_sdk::transaction::message::{CosmosMessage, CosmosMessageBox}; +use tw_evm::message::eip712::message_types::MessageTypesBuilder; + +pub mod send_order; +pub mod transfer_out; +pub mod type_msg_amount; + +pub type GreenfieldMessageBox = Box; + +pub trait GreenfieldMessage: CosmosMessage { + fn eip712_type(&self, msg_idx: usize) -> String { + MsgPropertyType(msg_idx).to_string() + } + + fn declare_eip712_type(&self, msg_idx: usize, message_types: &mut MessageTypesBuilder); + + fn to_cosmos_message(&self) -> CosmosMessageBox; +} diff --git a/rust/chains/tw_greenfield/src/transaction/message/send_order.rs b/rust/chains/tw_greenfield/src/transaction/message/send_order.rs new file mode 100644 index 00000000000..7733629ba9d --- /dev/null +++ b/rust/chains/tw_greenfield/src/transaction/message/send_order.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::GreenfieldAddress; +use crate::transaction::message::type_msg_amount::TypeMsgAmount; +use crate::transaction::message::GreenfieldMessage; +use tw_coin_entry::error::prelude::*; +use tw_cosmos_sdk::proto::cosmos as CosmosProto; +use tw_cosmos_sdk::transaction::message::cosmos_bank_message::SendMessage; +use tw_cosmos_sdk::transaction::message::{ + message_to_json, CosmosMessage, CosmosMessageBox, JsonMessage, ProtobufMessage, +}; +use tw_evm::abi::param_type::constructor::TypeConstructor; +use tw_evm::message::eip712::message_types::MessageTypesBuilder; +use tw_evm::message::eip712::property::PropertyType; +use tw_proto::type_url; + +/// cosmos.bank.v1beta1.MsgSend +/// +/// # EIP712 custom types +/// +/// ```json +/// { +/// "TypeMsgAmount": [ +/// { +/// "name": "amount", +/// "type": "string" +/// }, +/// { +/// "name": "denom", +/// "type": "string" +/// } +/// ], +/// "Msg": [ +/// { +/// "name": "amount", +/// "type": "TypeMsgAmount[]" +/// }, +/// { +/// "name": "from_address", +/// "type": "string" +/// }, +/// { +/// "name": "to_address", +/// "type": "string" +/// }, +/// { +/// "name": "type", +/// "type": "string" +/// } +/// ] +/// } +/// ``` +#[derive(Clone)] +pub struct GreenfieldSendMessage(pub SendMessage); + +impl CosmosMessage for GreenfieldSendMessage { + fn to_proto(&self) -> SigningResult { + self.0.to_proto() + } + + /// [`GreenfieldSendMessage::to_json`] implementation differs from the original [`SendMessage::to_json`]: + /// * [`JsonMessage::msg_type`] should be "cosmos.bank.v1beta1.MsgSend" if other is not specified. + /// * [`JsonMessage::value`] is the same. + fn to_json(&self) -> SigningResult { + let msg_type = self + .0 + .custom_type_prefix + .clone() + .unwrap_or_else(type_url::); + message_to_json(&msg_type, &self.0) + } +} + +impl GreenfieldMessage for GreenfieldSendMessage { + fn declare_eip712_type(&self, msg_idx: usize, message_types: &mut MessageTypesBuilder) { + let this_msg_type_name = self.eip712_type(msg_idx); + + TypeMsgAmount::declare_eip712_type(msg_idx, message_types); + + if let Some(mut builder) = message_types.add_custom_type(this_msg_type_name) { + let amount_msg_type = PropertyType::Custom(TypeMsgAmount::eip712_type(msg_idx)); + builder + .add_property("amount", PropertyType::array(amount_msg_type)) + .add_property("from_address", PropertyType::String) + .add_property("to_address", PropertyType::String) + .add_property("type", PropertyType::String); + } + } + + fn to_cosmos_message(&self) -> CosmosMessageBox { + Box::new(self.clone()) + } +} diff --git a/rust/chains/tw_greenfield/src/transaction/message/transfer_out.rs b/rust/chains/tw_greenfield/src/transaction/message/transfer_out.rs new file mode 100644 index 00000000000..00a01116db5 --- /dev/null +++ b/rust/chains/tw_greenfield/src/transaction/message/transfer_out.rs @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::GreenfieldAddress; +use crate::transaction::message::type_msg_amount::TypeMsgAmount; +use crate::transaction::message::GreenfieldMessage; +use serde::Serialize; +use tw_coin_entry::error::prelude::*; +use tw_cosmos_sdk::modules::serializer::protobuf_serializer::build_coin; +use tw_cosmos_sdk::proto::greenfield as GreenfieldProto; +use tw_cosmos_sdk::transaction::message::{ + message_to_json, CosmosMessage, CosmosMessageBox, JsonMessage, ProtobufMessage, +}; +use tw_cosmos_sdk::transaction::Coin; +use tw_evm::message::eip712::message_types::MessageTypesBuilder; +use tw_evm::message::eip712::property::PropertyType; +use tw_proto::{to_any, type_url}; + +/// greenfield.bridge.MsgTransferOut +/// +/// # EIP712 custom types +/// +/// ```json +/// { +/// "TypeMsgAmount": [ +/// { +/// "name": "amount", +/// "type": "string" +/// }, +/// { +/// "name": "denom", +/// "type": "string" +/// } +/// ], +/// "Msg": [ +/// { +/// "name": "amount", +/// "type": "TypeMsgAmount" +/// }, +/// { +/// "name": "from", +/// "type": "string" +/// }, +/// { +/// "name": "to", +/// "type": "string" +/// }, +/// { +/// "name": "type", +/// "type": "string" +/// } +/// ] +/// } +/// ``` +#[derive(Clone, Serialize)] +pub struct GreenfieldTransferOut { + #[serde(skip)] + pub custom_type_prefix: Option, + pub amount: Coin, + pub from: GreenfieldAddress, + pub to: GreenfieldAddress, +} + +impl CosmosMessage for GreenfieldTransferOut { + fn to_proto(&self) -> SigningResult { + let msg = GreenfieldProto::bridge::MsgTransferOut { + from: self.from.to_string(), + to: self.to.to_string(), + amount: Some(build_coin(&self.amount)), + }; + Ok(to_any(&msg)) + } + + fn to_json(&self) -> SigningResult { + let msg_type = self + .custom_type_prefix + .clone() + .unwrap_or_else(type_url::); + message_to_json(&msg_type, self) + } +} + +impl GreenfieldMessage for GreenfieldTransferOut { + fn declare_eip712_type(&self, msg_idx: usize, message_types: &mut MessageTypesBuilder) { + let this_msg_type_name = self.eip712_type(msg_idx); + + TypeMsgAmount::declare_eip712_type(msg_idx, message_types); + + if let Some(mut builder) = message_types.add_custom_type(this_msg_type_name) { + let amount_msg_type = PropertyType::Custom(TypeMsgAmount::eip712_type(msg_idx)); + builder + .add_property("amount", amount_msg_type) + .add_property("from", PropertyType::String) + .add_property("to", PropertyType::String) + .add_property("type", PropertyType::String); + } + } + + fn to_cosmos_message(&self) -> CosmosMessageBox { + Box::new(self.clone()) + } +} diff --git a/rust/chains/tw_greenfield/src/transaction/message/type_msg_amount.rs b/rust/chains/tw_greenfield/src/transaction/message/type_msg_amount.rs new file mode 100644 index 00000000000..e635367220b --- /dev/null +++ b/rust/chains/tw_greenfield/src/transaction/message/type_msg_amount.rs @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_evm::message::eip712::message_types::MessageTypesBuilder; +use tw_evm::message::eip712::property::PropertyType; + +/// Represents an amount type that belongs to a particular order. +pub struct TypeMsgAmount; + +impl TypeMsgAmount { + pub fn eip712_type(msg_idx: usize) -> String { + format!("TypeMsg{msg_idx}Amount") + } + + pub fn declare_eip712_type(msg_idx: usize, message_types: &mut MessageTypesBuilder) { + let type_msg_amount_type = Self::eip712_type(msg_idx); + if let Some(mut builder) = message_types.add_custom_type(type_msg_amount_type) { + builder.add_property("amount", PropertyType::String); + builder.add_property("denom", PropertyType::String); + } + } +} diff --git a/rust/chains/tw_greenfield/src/transaction/mod.rs b/rust/chains/tw_greenfield/src/transaction/mod.rs new file mode 100644 index 00000000000..80f08f72da3 --- /dev/null +++ b/rust/chains/tw_greenfield/src/transaction/mod.rs @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::GreenfieldAddress; +use crate::context::GreenfieldContext; +use crate::public_key::GreenfieldPublicKey; +use crate::signature::GreenfieldSignature; +use crate::transaction::message::GreenfieldMessageBox; +use tw_cosmos_sdk::transaction::{ + Fee, SignMode, SignedTransaction, SignerInfo, TxBody, UnsignedTransaction, +}; +use tw_misc::traits::ToBytesVec; +use tw_number::U256; + +pub type GreenfieldSignerInfo = SignerInfo; +pub type GreenfieldFee = Fee; +pub type GreenfieldSignedTransaction = SignedTransaction; + +pub mod message; + +pub enum GreenfieldSignMode { + Eip712, +} + +impl From for SignMode { + fn from(mode: GreenfieldSignMode) -> Self { + use tw_cosmos_sdk::proto::cosmos::signing::v1beta1 as signing_proto; + + match mode { + GreenfieldSignMode::Eip712 => { + SignMode::Other(signing_proto::SignMode::SIGN_MODE_EIP_712 as i32) + }, + } + } +} + +pub struct GreenfieldTxBody { + pub messages: Vec, + pub memo: String, + pub timeout_height: u64, +} + +impl GreenfieldTxBody { + fn into_cosmos_tx_body(self) -> TxBody { + TxBody { + messages: self + .messages + .into_iter() + .map(|greenfield_msg| greenfield_msg.to_cosmos_message()) + .collect(), + memo: self.memo, + timeout_height: self.timeout_height, + } + } +} + +pub struct GreenfieldUnsignedTransaction { + pub signer: GreenfieldSignerInfo, + pub fee: GreenfieldFee, + pub cosmos_chain_id: String, + pub eth_chain_id: U256, + pub account_number: u64, + pub tx_body: GreenfieldTxBody, +} + +impl GreenfieldUnsignedTransaction { + pub fn into_signed(self, signature: GreenfieldSignature) -> GreenfieldSignedTransaction { + self.into_cosmos_unsigned().into_signed(signature.to_vec()) + } + + fn into_cosmos_unsigned(self) -> UnsignedTransaction { + UnsignedTransaction { + signer: self.signer, + fee: self.fee, + chain_id: self.cosmos_chain_id, + account_number: self.account_number, + tx_body: self.tx_body.into_cosmos_tx_body(), + } + } +} diff --git a/rust/chains/tw_greenfield/tests/data/send_order_eip712.json b/rust/chains/tw_greenfield/tests/data/send_order_eip712.json new file mode 100644 index 00000000000..01f861be82b --- /dev/null +++ b/rust/chains/tw_greenfield/tests/data/send_order_eip712.json @@ -0,0 +1,149 @@ +{ + "types": { + "Coin": [ + { + "name": "amount", + "type": "uint256" + }, + { + "name": "denom", + "type": "string" + } + ], + "EIP712Domain": [ + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "salt", + "type": "string" + }, + { + "name": "verifyingContract", + "type": "string" + }, + { + "name": "version", + "type": "string" + } + ], + "Fee": [ + { + "name": "amount", + "type": "Coin[]" + }, + { + "name": "gas_limit", + "type": "uint256" + }, + { + "name": "granter", + "type": "string" + }, + { + "name": "payer", + "type": "string" + } + ], + "Msg1": [ + { + "name": "amount", + "type": "TypeMsg1Amount[]" + }, + { + "name": "from_address", + "type": "string" + }, + { + "name": "to_address", + "type": "string" + }, + { + "name": "type", + "type": "string" + } + ], + "Tx": [ + { + "name": "account_number", + "type": "uint256" + }, + { + "name": "chain_id", + "type": "uint256" + }, + { + "name": "fee", + "type": "Fee" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "msg1", + "type": "Msg1" + }, + { + "name": "sequence", + "type": "uint256" + }, + { + "name": "timeout_height", + "type": "uint256" + } + ], + "TypeMsg1Amount": [ + { + "name": "amount", + "type": "string" + }, + { + "name": "denom", + "type": "string" + } + ] + }, + "primaryType": "Tx", + "domain": { + "name": "Greenfield Tx", + "version": "1.0.0", + "chainId": "5600", + "verifyingContract": "greenfield", + "salt": "0" + }, + "message": { + "account_number": "15560", + "chain_id": "5600", + "fee": { + "amount": [ + { + "amount": "2000000000000000", + "denom": "BNB" + } + ], + "gas_limit": "200000", + "granter": "", + "payer": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1" + }, + "memo": "", + "msg1": { + "amount": [ + { + "amount": "1000000000000000", + "denom": "BNB" + } + ], + "from_address": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1", + "to_address": "0x280b27f3676db1C4475EE10F75D510Eb527fd155", + "type": "/cosmos.bank.v1beta1.MsgSend" + }, + "sequence": "2", + "timeout_height": "0" + } +} diff --git a/rust/chains/tw_greenfield/tests/data/transfer_out_eip712.json b/rust/chains/tw_greenfield/tests/data/transfer_out_eip712.json new file mode 100644 index 00000000000..c17ecd1bea2 --- /dev/null +++ b/rust/chains/tw_greenfield/tests/data/transfer_out_eip712.json @@ -0,0 +1,147 @@ +{ + "types": { + "Coin": [ + { + "name": "amount", + "type": "uint256" + }, + { + "name": "denom", + "type": "string" + } + ], + "EIP712Domain": [ + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "salt", + "type": "string" + }, + { + "name": "verifyingContract", + "type": "string" + }, + { + "name": "version", + "type": "string" + } + ], + "Fee": [ + { + "name": "amount", + "type": "Coin[]" + }, + { + "name": "gas_limit", + "type": "uint256" + }, + { + "name": "granter", + "type": "string" + }, + { + "name": "payer", + "type": "string" + } + ], + "Msg1": [ + { + "name": "amount", + "type": "TypeMsg1Amount" + }, + { + "name": "from", + "type": "string" + }, + { + "name": "to", + "type": "string" + }, + { + "name": "type", + "type": "string" + } + ], + "Tx": [ + { + "name": "account_number", + "type": "uint256" + }, + { + "name": "chain_id", + "type": "uint256" + }, + { + "name": "fee", + "type": "Fee" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "msg1", + "type": "Msg1" + }, + { + "name": "sequence", + "type": "uint256" + }, + { + "name": "timeout_height", + "type": "uint256" + } + ], + "TypeMsg1Amount": [ + { + "name": "amount", + "type": "string" + }, + { + "name": "denom", + "type": "string" + } + ] + }, + "primaryType": "Tx", + "domain": { + "name": "Greenfield Tx", + "version": "1.0.0", + "chainId": "5600", + "verifyingContract": "greenfield", + "salt": "0" + }, + "message": { + "account_number": "15560", + "chain_id": "5600", + "fee": { + "amount": [ + { + "amount": "2000000000000000", + "denom": "BNB" + } + ], + "gas_limit": "200000", + "granter": "", + "payer": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1" + }, + "memo": "", + "msg1": { + "amount": { + "amount": "1000000000000000", + "denom": "BNB" + }, + "from": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1", + "to": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1", + "type": "/greenfield.bridge.MsgTransferOut" + }, + "sequence": "2", + "timeout_height": "0" + } +} \ No newline at end of file diff --git a/rust/chains/tw_greenfield/tests/eip712_signer.rs b/rust/chains/tw_greenfield/tests/eip712_signer.rs new file mode 100644 index 00000000000..e20ab3b48fb --- /dev/null +++ b/rust/chains/tw_greenfield/tests/eip712_signer.rs @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_greenfield::modules::eip712_signer::{Eip712Signer, Eip712TxPreimage}; +use tw_greenfield::modules::tx_builder::TxBuilder; +use tw_misc::assert_eq_json; +use tw_proto::Greenfield::Proto; +use Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +const SEND_ORDER_EIP712: &str = include_str!("data/send_order_eip712.json"); +const TRANSFER_OUT_EIP712: &str = include_str!("data/transfer_out_eip712.json"); + +const PUBLIC_KEY_15560: &str = "0279ef34064da10db0463c70480616ba020703ec3a45026def7bebd2082f5d6fc8"; + +fn make_amount<'a>(denom: &'a str, amount: &'a str) -> Proto::Amount<'a> { + Proto::Amount { + denom: denom.into(), + amount: amount.into(), + } +} + +/// Testnet transaction: +/// https://greenfieldscan.com/tx/0x9f895cf2dd64fb1f428cefcf2a6585a813c3540fc9fe1ef42db1da2cb1df55ab +#[test] +fn test_eip712_signer_encode_send() { + let coin = TestCoinContext::default(); + + let send_order = Proto::mod_Message::Send { + from_address: "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1".into(), + to_address: "0x280b27f3676db1C4475EE10F75D510Eb527fd155".into(), + amounts: vec![make_amount("BNB", "1000000000000000")], + ..Proto::mod_Message::Send::default() + }; + + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Eip712, + account_number: 15560, + eth_chain_id: "5600".into(), + cosmos_chain_id: "greenfield_5600-1".into(), + fee: Some(Proto::Fee { + amounts: vec![make_amount("BNB", "2000000000000000")], + gas: 200000, + }), + sequence: 2, + messages: vec![Proto::Message { + message_oneof: MessageEnum::send_coins_message(send_order), + }], + public_key: PUBLIC_KEY_15560.decode_hex().unwrap().into(), + ..Proto::SigningInput::default() + }; + + let unsigned_tx = TxBuilder::unsigned_tx_from_proto(&coin, &input) + .expect("Error on generating an unsigned transaction"); + + let Eip712TxPreimage { eip712_tx, tx_hash } = + Eip712Signer::preimage_hash(&unsigned_tx).expect("Error on TX preimage"); + + assert_eq_json!(eip712_tx, SEND_ORDER_EIP712); + assert_eq!( + tx_hash.to_hex(), + "b8c62654582ca96b37ca94966199682bf70ed934e740d2f874ff54675a0ac344" + ); +} + +#[test] +fn test_eip712_signer_encode_transfer_out() { + let coin = TestCoinContext::default(); + + let send_order = Proto::mod_Message::BridgeTransferOut { + from_address: "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1".into(), + to_address: "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1".into(), + amount: Some(make_amount("BNB", "1000000000000000")), + ..Proto::mod_Message::BridgeTransferOut::default() + }; + + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Eip712, + account_number: 15560, + eth_chain_id: "5600".into(), + cosmos_chain_id: "greenfield_5600-1".into(), + fee: Some(Proto::Fee { + amounts: vec![make_amount("BNB", "2000000000000000")], + gas: 200000, + }), + sequence: 2, + messages: vec![Proto::Message { + message_oneof: MessageEnum::bridge_transfer_out(send_order), + }], + public_key: PUBLIC_KEY_15560.decode_hex().unwrap().into(), + ..Proto::SigningInput::default() + }; + + let unsigned_tx = TxBuilder::unsigned_tx_from_proto(&coin, &input) + .expect("Error on generating an unsigned transaction"); + + let Eip712TxPreimage { eip712_tx, tx_hash } = + Eip712Signer::preimage_hash(&unsigned_tx).expect("Error on TX preimage"); + + assert_eq_json!(eip712_tx, TRANSFER_OUT_EIP712); + assert_eq!( + tx_hash.to_hex(), + "ea7731461041f5f652ab424bb767c670e484cfe1f4a85179deba8a6596873af4" + ); +} diff --git a/rust/chains/tw_internet_computer/Cargo.toml b/rust/chains/tw_internet_computer/Cargo.toml new file mode 100644 index 00000000000..fda55e71f69 --- /dev/null +++ b/rust/chains/tw_internet_computer/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tw_internet_computer" +version = "0.1.0" +edition = "2021" + +[dependencies] +quick-protobuf = "0.8.1" +serde = { version = "1.0", features = ["derive"] } +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_encoding = { path = "../../tw_encoding" } +tw_hash = { path = "../../tw_hash" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } + +[build-dependencies] +pb-rs = "0.10.0" diff --git a/rust/chains/tw_internet_computer/build.rs b/rust/chains/tw_internet_computer/build.rs new file mode 100644 index 00000000000..1b8ee954b23 --- /dev/null +++ b/rust/chains/tw_internet_computer/build.rs @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use pb_rs::types::FileDescriptor; +use pb_rs::ConfigBuilder; +use std::path::{Path, PathBuf}; +use std::{env, fs}; + +fn main() { + let proto_ext = Some(Path::new("proto").as_os_str()); + + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()).join("proto"); + + let proto_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("src") + .join("transactions") + .join("proto"); + let proto_dir_str = proto_dir.to_str().expect("Invalid proto directory path"); + // Re-run this build.rs if the `proto` directory has been changed (i.e. a new file is added). + println!("cargo:rerun-if-changed={}", proto_dir_str); + + let protos: Vec<_> = fs::read_dir(&proto_dir) + .expect("Expected a valid directory with proto files") + .filter_map(|file| { + let file = file.ok()?; + if file.path().extension() != proto_ext { + return None; + } + + let path = file.path(); + let path_str = path.to_str().expect("Invalid Proto file name"); + println!("cargo:rerun-if-changed={}", path_str); + Some(path) + }) + .collect(); + + // Delete all old generated files before re-generating new ones + if out_dir.exists() { + fs::remove_dir_all(&out_dir).expect("Error removing out directory"); + } + fs::DirBuilder::new() + .create(&out_dir) + .expect("Error creating out directory"); + + let out_protos = ConfigBuilder::new(&protos, None, Some(&out_dir), &[proto_dir]) + .expect("Error configuring pb-rs builder") + .dont_use_cow(true) + .owned(true) + .build(); + FileDescriptor::run(&out_protos).expect("Error generating proto files"); +} diff --git a/rust/chains/tw_internet_computer/fuzz/.gitignore b/rust/chains/tw_internet_computer/fuzz/.gitignore new file mode 100644 index 00000000000..1a45eee7760 --- /dev/null +++ b/rust/chains/tw_internet_computer/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/rust/chains/tw_internet_computer/fuzz/Cargo.lock b/rust/chains/tw_internet_computer/fuzz/Cargo.lock new file mode 100644 index 00000000000..887d622e7ea --- /dev/null +++ b/rust/chains/tw_internet_computer/fuzz/Cargo.lock @@ -0,0 +1,1595 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "arbitrary" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bigdecimal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d1988118c887f61418940e322d574e8a2dd67165f1f1556eaae22e4019c6af" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "ppv-lite86", +] + +[[package]] +name = "blake2" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" +dependencies = [ + "crypto-mac", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "blake2b-ref" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "294d17c72e0ba59fad763caa112368d0672083779cdebbb97164f4bb4c1e339a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "hkdf", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "groestl" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343cfc165f92a988fd60292f7a0bfde4352a5a0beff9fbec29251ca4e9676e4d" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.7", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.7", +] + +[[package]] +name = "parity-scale-codec" +version = "3.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d640b40e81625f53b59f9c5812d3fb9e7ce11971d3fb34268eb7a83adf98051" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ac464815d51fff2f64d690bf6ea4442d365e53495d50737685cfecfa3dd3f62" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pb-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "354a34df9c65b596152598001c0fe3393379ec2db03ae30b9985659422e2607e" +dependencies = [ + "clap", + "env_logger", + "log", + "nom", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primeorder" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "primitive-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.10", +] + +[[package]] +name = "regex" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", +] + +[[package]] +name = "serde_json" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "starknet-crypto" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693e6362f150f9276e429a910481fb7f3bcb8d6aa643743f587cfece0b374874" +dependencies = [ + "crypto-bigint", + "hex", + "hmac", + "num-bigint", + "num-integer", + "num-traits", + "rfc6979", + "sha2 0.10.7", + "starknet-crypto-codegen", + "starknet-curve 0.3.0", + "starknet-ff", + "zeroize", +] + +[[package]] +name = "starknet-crypto-codegen" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af6527b845423542c8a16e060ea1bc43f67229848e7cd4c4d80be994a84220ce" +dependencies = [ + "starknet-curve 0.4.0", + "starknet-ff", + "syn 2.0.31", +] + +[[package]] +name = "starknet-curve" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252610baff59e4c4332ce3569f7469c5d3f9b415a2240d698fb238b2b4fc0942" +dependencies = [ + "starknet-ff", +] + +[[package]] +name = "starknet-curve" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68a0d87ae56572abf83ddbfd44259a7c90dbeeee1629a1ffe223e7f9a8f3052" +dependencies = [ + "starknet-ff", +] + +[[package]] +name = "starknet-ff" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2cb1d9c0a50380cddab99cb202c6bfb3332728a2769bd0ca2ee80b0b390dd4" +dependencies = [ + "ark-ff", + "bigdecimal", + "crypto-bigint", + "getrandom 0.2.10", + "hex", + "serde", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tw_coin_entry" +version = "0.1.0" +dependencies = [ + "serde_json", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", +] + +[[package]] +name = "tw_encoding" +version = "0.1.0" +dependencies = [ + "bs58", + "ciborium", + "data-encoding", + "hex", + "serde", + "tw_memory", +] + +[[package]] +name = "tw_hash" +version = "0.1.0" +dependencies = [ + "blake-hash", + "blake2b-ref", + "digest 0.10.7", + "groestl", + "hmac", + "ripemd", + "serde", + "sha1", + "sha2 0.10.7", + "sha3", + "tw_encoding", + "tw_memory", + "zeroize", +] + +[[package]] +name = "tw_internet_computer" +version = "0.1.0" +dependencies = [ + "pb-rs", + "quick-protobuf", + "serde", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_proto", +] + +[[package]] +name = "tw_internet_computer-fuzz" +version = "0.0.0" +dependencies = [ + "libfuzzer-sys", + "tw_internet_computer", + "tw_keypair", +] + +[[package]] +name = "tw_keypair" +version = "0.1.0" +dependencies = [ + "blake2", + "curve25519-dalek", + "der", + "digest 0.9.0", + "ecdsa", + "k256", + "lazy_static", + "p256", + "pkcs8", + "rfc6979", + "serde", + "sha2 0.9.9", + "starknet-crypto", + "starknet-ff", + "tw_encoding", + "tw_hash", + "tw_memory", + "tw_misc", + "zeroize", +] + +[[package]] +name = "tw_memory" +version = "0.1.0" + +[[package]] +name = "tw_misc" +version = "0.1.0" +dependencies = [ + "zeroize", +] + +[[package]] +name = "tw_number" +version = "0.1.0" +dependencies = [ + "primitive-types", + "tw_hash", + "tw_memory", +] + +[[package]] +name = "tw_proto" +version = "0.1.0" +dependencies = [ + "pb-rs", + "quick-protobuf", + "tw_encoding", + "tw_memory", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.31", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", +] diff --git a/rust/chains/tw_internet_computer/fuzz/Cargo.toml b/rust/chains/tw_internet_computer/fuzz/Cargo.toml new file mode 100644 index 00000000000..a31848778a8 --- /dev/null +++ b/rust/chains/tw_internet_computer/fuzz/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "tw_internet_computer-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } + +[dependencies.tw_internet_computer] +path = ".." + + +[dependencies.tw_keypair] +path = "../../tw_keypair" + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "tw_internet_computer_transfer" +path = "fuzz_targets/tw_internet_computer_transfer.rs" +test = false +doc = false diff --git a/rust/chains/tw_internet_computer/fuzz/fuzz_targets/tw_internet_computer_transfer.rs b/rust/chains/tw_internet_computer/fuzz/fuzz_targets/tw_internet_computer_transfer.rs new file mode 100644 index 00000000000..568b15610b2 --- /dev/null +++ b/rust/chains/tw_internet_computer/fuzz/fuzz_targets/tw_internet_computer_transfer.rs @@ -0,0 +1,71 @@ +#![no_main] + +use libfuzzer_sys::{arbitrary, fuzz_target}; +use tw_internet_computer::{ + address::AccountIdentifier, + protocol::principal::Principal, + transactions::transfer::{transfer, TransferArgs}, +}; +use tw_keypair::ecdsa::secp256k1; + +#[derive(Debug, arbitrary::Arbitrary)] +struct ArbitraryTransferArgs { + memo: u64, + amount: u64, + max_fee: Option, + #[arbitrary(with = arbitrary_to_field)] + to: String, + current_timestamp_nanos: u64, + permitted_drift: Option, +} + +fn arbitrary_to_field(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + let mut buf = [0; 29]; + u.fill_buffer(&mut buf)?; + let principal = Principal::from_slice(&buf); + let account_identifier = AccountIdentifier::new(&principal); + Ok(account_identifier.to_hex()) +} + +impl From<&ArbitraryTransferArgs> for TransferArgs { + fn from(prev: &ArbitraryTransferArgs) -> TransferArgs { + TransferArgs { + memo: prev.memo, + amount: prev.amount, + max_fee: prev.max_fee, + to: prev.to.clone(), + current_timestamp_nanos: prev.current_timestamp_nanos, + } + } +} + +#[derive(Debug, arbitrary::Arbitrary)] +struct TWInternetComputerTransactionsTransferInput { + #[arbitrary(with = arbitrary_private_key)] + private: Vec, + #[arbitrary(with = arbitrary_canister_id)] + canister_id: Principal, + args: ArbitraryTransferArgs, +} + +fn arbitrary_private_key(u: &mut arbitrary::Unstructured) -> arbitrary::Result> { + let mut buf = [0; 32]; + u.fill_buffer(&mut buf)?; + Ok(Vec::from(buf.as_slice())) +} + +fn arbitrary_canister_id(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + let mut buf = [0; 29]; + u.fill_buffer(&mut buf)?; + let principal = Principal::from_slice(&buf); + Ok(principal) +} + +fuzz_target!(|input: TWInternetComputerTransactionsTransferInput| { + let Ok(private_key) = secp256k1::PrivateKey::try_from(input.private.as_slice()) else { + return; + }; + + let args = TransferArgs::from(&input.args); + transfer(private_key, input.canister_id, args).ok(); +}); diff --git a/rust/chains/tw_internet_computer/src/address.rs b/rust/chains/tw_internet_computer/src/address.rs new file mode 100644 index 00000000000..e439b573a9d --- /dev/null +++ b/rust/chains/tw_internet_computer/src/address.rs @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::{coin_entry::CoinAddress, error::prelude::*}; +use tw_encoding::hex; +use tw_hash::{crc32::crc32, sha2::sha224, H256}; +use tw_keypair::ecdsa::secp256k1::PublicKey; + +use crate::protocol::principal::Principal; + +pub trait IcpAddress: std::str::FromStr + Into { + fn from_str_optional(s: &str) -> AddressResult> { + if s.is_empty() { + return Ok(None); + } + + Self::from_str(s).map(Some) + } +} + +/// The ICP ledger keeps track of accounts using account identifiers. +/// An account identifier is created by `SHA-224` hashing: +/// * \x0Aaccount-id +/// * the owner's principal ID +/// * subaccount (32-bytes) +/// +/// Then, +/// * CRC32 the SHA-224 hash +/// * Prepend the CRC32 to the SHA-224. +/// +/// https://internetcomputer.org/docs/current/references/ledger/#_accounts +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AccountIdentifier { + bytes: H256, +} + +impl AccountIdentifier { + /// Create a default account identifier from the given principal owner. + pub fn new(owner: &Principal) -> Self { + let mut input = vec![]; + input.extend_from_slice(b"\x0Aaccount-id"); + input.extend_from_slice(owner.as_slice()); + input.extend_from_slice(&H256::new()[..]); + + let hash = sha224(&input); + let crc32_bytes = crc32(&hash).to_be_bytes(); + + let mut bytes = H256::new(); + bytes[0..4].copy_from_slice(&crc32_bytes); + bytes[4..32].copy_from_slice(&hash); + Self { bytes } + } + + /// Return the textual-encoded account identifier. + pub fn to_hex(&self) -> String { + hex::encode(self.bytes, false) + } + + /// Instantiate an account identifier from a hex-encoded string. + pub fn from_hex(hex_str: &str) -> AddressResult { + if hex_str.len() != 64 { + return Err(AddressError::FromHexError); + } + + let hex = H256::try_from( + hex::decode(hex_str) + .map_err(|_| AddressError::FromHexError)? + .as_slice(), + ) + .map_err(|_| AddressError::FromHexError)?; + + if !is_check_sum_valid(hex) { + return Err(AddressError::FromHexError); + } + + Ok(Self { bytes: hex }) + } +} + +impl From<&PublicKey> for AccountIdentifier { + /// Takes a Secp256k1 public key, DER-encodes the public key, + /// and creates a principal from the encoding. + fn from(public_key: &PublicKey) -> Self { + let principal = Principal::from(public_key); + AccountIdentifier::new(&principal) + } +} + +impl std::str::FromStr for AccountIdentifier { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + AccountIdentifier::from_hex(s).map_err(|_| AddressError::FromHexError) + } +} + +impl std::fmt::Display for AccountIdentifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_hex()) + } +} + +impl CoinAddress for AccountIdentifier { + fn data(&self) -> tw_memory::Data { + self.bytes.into_vec() + } +} + +impl IcpAddress for AccountIdentifier {} + +impl AsRef<[u8]> for AccountIdentifier { + fn as_ref(&self) -> &[u8] { + self.bytes.as_slice() + } +} + +fn is_check_sum_valid(hash: H256) -> bool { + let found_checksum = &hash[0..4]; + let expected_checksum = crc32(&hash[4..]).to_be_bytes(); + found_checksum == expected_checksum +} + +#[cfg(test)] +mod test { + use tw_keypair::ecdsa::secp256k1::PublicKey; + + use super::*; + + const VALID_ADDRESSES: [&str; 10] = [ + "fb257577279ecac604d4780214af95aa6adc3a814f6f3d6d7ac844d1deca500a", + "e90c48d54847f4758f1d6b589a1db2500757a49a6722d4f775e050107b4b752d", + "a7c5baf393aed527ef6fb3869fbf84dd4e562edf9b04bd8f9bfbd6b8e6a22776", + "4cb2ca5cfcfa1d952f8cd7f0ec46c96e1023ab057b83a2c7ce236b9e71ccca0b", + "a93fff2708a6305e8946a0a06cbf559da01a758da58a615d404037b08ea96181", + "6e66c78a45cec01bcd0efd6dd142a82dc63b1a591c4ccb3c5877cd4d667747b4", + "c4ca697b46bb89ebf19eef3ad7b5e7bfa73315c0433a68a12a67f60fe017b9ad", + "7c513ec0de7347555b75cfefe29e56689de36636321fb0c8addf24a4f934ff0b", + "f61e15cdcaf0325bbaeb9a23a9f49d5447b33e6feee9763c2fdfe3a986142912", + "bb3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278", + ]; + + const TOO_SHORT_ADDRESS: &str = + "3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278b"; + const TOO_LONG_ADDRESS: &str = + "3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278bce"; + const INVALID_CHECKSUM_ADDRESS: &str = + "553357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278"; + + const PUBLIC_KEY_HEX: &str = "048542e6fb4b17d6dfcac3948fe412c00d626728815ee7cc70509603f1bc92128a6e7548f3432d6248bc49ff44a1e50f6389238468d17f7d7024de5be9b181dbc8"; + + #[test] + fn from_hex() { + assert!(VALID_ADDRESSES + .iter() + .all(|s| AccountIdentifier::from_hex(s).is_ok())); + + assert!( + AccountIdentifier::from_hex(TOO_SHORT_ADDRESS).is_err(), + "Address is too short" + ); + assert!( + AccountIdentifier::from_hex(TOO_LONG_ADDRESS).is_err(), + "Address is too long" + ); + assert!( + AccountIdentifier::from_hex(INVALID_CHECKSUM_ADDRESS).is_err(), + "Invalid checksum" + ); + } + + #[test] + fn from_public_key() { + let public_key = PublicKey::try_from(PUBLIC_KEY_HEX).expect("Failed to populate key"); + let address = AccountIdentifier::from(&public_key); + assert_eq!( + address.to_hex(), + "2f25874478d06cf68b9833524a6390d0ba69c566b02f46626979a3d6a4153211" + ); + } +} diff --git a/rust/chains/tw_internet_computer/src/context.rs b/rust/chains/tw_internet_computer/src/context.rs new file mode 100644 index 00000000000..41cee32fb86 --- /dev/null +++ b/rust/chains/tw_internet_computer/src/context.rs @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::{ + address::{AccountIdentifier, IcpAddress}, + protocol::principal::Principal, +}; + +pub trait InternetComputerContext { + type Address: IcpAddress; + + fn get_canister_id() -> Principal; +} + +#[derive(Default)] +pub struct StandardInternetComputerContext; + +impl InternetComputerContext for StandardInternetComputerContext { + type Address = AccountIdentifier; + + fn get_canister_id() -> Principal { + // ICP Ledger Canister + Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap() + } +} + +#[cfg(test)] +mod test { + + use std::marker::PhantomData; + + use tw_coin_entry::error::prelude::*; + + use super::*; + + const TEST_OWNER_PRINCIPAL_ID: &str = + "t4u4z-y3dur-j63pk-nw4rv-yxdbt-agtt6-nygn7-ywh6y-zm2f4-sdzle-3qe"; + const TEST_TEXTUAL_ICP_ADDRESS: &str = + "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a"; + const TEXTUAL_ICP_LEDGER_CANISTER_ID: &str = "ryjl3-tyaaa-aaaaa-aaaba-cai"; + + pub struct ContextTest { + _phantom: PhantomData, + } + + impl ContextTest { + fn get_canister_id() -> Principal { + Context::get_canister_id() + } + + fn account_identifier_optional(s: &str) -> AddressResult> { + Context::Address::from_str_optional(s) + } + } + + #[test] + fn standard_internet_computer_context_canister_address() { + let owner = Principal::from_text(TEST_OWNER_PRINCIPAL_ID).unwrap(); + let expected_account_identifier = AccountIdentifier::new(&owner); + let account_identifier = + ContextTest::::account_identifier_optional( + TEST_TEXTUAL_ICP_ADDRESS, + ) + .unwrap() + .unwrap(); + assert_eq!(expected_account_identifier, account_identifier); + } + + #[test] + fn standard_internet_computer_context_canister_type() { + let ledger_canister_id = ContextTest::::get_canister_id(); + assert_eq!(ledger_canister_id.to_text(), TEXTUAL_ICP_LEDGER_CANISTER_ID); + } +} diff --git a/rust/chains/tw_internet_computer/src/entry.rs b/rust/chains/tw_internet_computer/src/entry.rs new file mode 100644 index 00000000000..3e9be8f75cb --- /dev/null +++ b/rust/chains/tw_internet_computer/src/entry.rs @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::str::FromStr; + +use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; +use tw_coin_entry::{ + coin_context::CoinContext, + coin_entry::CoinEntry, + error::prelude::*, + modules::{ + json_signer::NoJsonSigner, message_signer::NoMessageSigner, plan_builder::NoPlanBuilder, + wallet_connector::NoWalletConnector, + }, + prefix::NoPrefix, + signing_output_error, +}; +use tw_proto::{ + Common::Proto::SigningError as CommonError, InternetComputer::Proto, + TxCompiler::Proto as CompilerProto, +}; + +use crate::{address::AccountIdentifier, context::StandardInternetComputerContext, signer::Signer}; + +pub struct InternetComputerEntry; + +impl CoinEntry for InternetComputerEntry { + type AddressPrefix = NoPrefix; + type Address = AccountIdentifier; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + type WalletConnector = NoWalletConnector; + type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + Self::Address::from_str(address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Self::Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + _coin: &dyn tw_coin_entry::coin_context::CoinContext, + public_key: tw_keypair::tw::PublicKey, + _derivation: tw_coin_entry::derivation::Derivation, + _prefix: Option, + ) -> AddressResult { + let secp256k1_public_key = public_key + .to_secp256k1() + .ok_or(AddressError::PublicKeyTypeMismatch)?; + Ok(Self::Address::from(secp256k1_public_key)) + } + + #[inline] + fn sign( + &self, + _coin: &dyn tw_coin_entry::coin_context::CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::SigningOutput { + Signer::::sign_proto(input) + } + + fn preimage_hashes( + &self, + _coin: &dyn tw_coin_entry::coin_context::CoinContext, + _input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + signing_output_error!( + CompilerProto::PreSigningOutput, + SigningError::new(CommonError::Error_not_supported) + ) + } + + fn compile( + &self, + _coin: &dyn tw_coin_entry::coin_context::CoinContext, + _input: Self::SigningInput<'_>, + _signatures: Vec, + _public_keys: Vec, + ) -> Self::SigningOutput { + signing_output_error!( + Proto::SigningOutput, + SigningError::new(CommonError::Error_not_supported) + ) + } +} diff --git a/rust/chains/tw_internet_computer/src/lib.rs b/rust/chains/tw_internet_computer/src/lib.rs new file mode 100644 index 00000000000..acef6a64209 --- /dev/null +++ b/rust/chains/tw_internet_computer/src/lib.rs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod address; +pub mod context; +pub mod entry; +pub mod protocol; +pub mod signer; +pub mod transactions; diff --git a/rust/chains/tw_internet_computer/src/protocol/envelope.rs b/rust/chains/tw_internet_computer/src/protocol/envelope.rs new file mode 100644 index 00000000000..c9627d53e1b --- /dev/null +++ b/rust/chains/tw_internet_computer/src/protocol/envelope.rs @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::collections::BTreeMap; + +use serde::{Serialize, Serializer}; + +use super::{ + principal::Principal, + request_id::{hash_of_map, RawHttpRequestVal, RequestId}, +}; + +pub trait RepresentationHashable { + fn request_id(&self) -> RequestId; +} + +#[derive(Debug, Clone, Serialize)] +pub struct Envelope { + pub content: C, + #[serde(skip_serializing_if = "Option::is_none")] + pub sender_pubkey: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub sender_sig: Option>, +} + +/// A replicated call to a canister method, whether update or query. +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "request_type", rename = "call")] +pub struct EnvelopeCallContent { + /// A random series of bytes to uniquely identify this message. + #[serde( + default, + skip_serializing_if = "Option::is_none", + serialize_with = "serialize_nonce" + )] + pub nonce: Option>, + /// A nanosecond timestamp after which this request is no longer valid. + pub ingress_expiry: u64, + /// The principal that is sending this request. + pub sender: Principal, + /// The ID of the canister to be called. + pub canister_id: Principal, + /// The name of the canister method to be called. + pub method_name: String, + /// The argument to pass to the canister method. + #[serde(serialize_with = "serialize_arg")] + pub arg: Vec, +} + +impl RepresentationHashable for EnvelopeCallContent { + fn request_id(&self) -> RequestId { + let mut map = vec![ + ( + "request_type".to_string(), + RawHttpRequestVal::String("call".to_string()), + ), + ( + "canister_id".to_string(), + RawHttpRequestVal::Bytes(self.canister_id.as_slice().to_vec()), + ), + ( + "method_name".to_string(), + RawHttpRequestVal::String(self.method_name.to_string()), + ), + ( + "arg".to_string(), + RawHttpRequestVal::Bytes(self.arg.clone()), + ), + ( + "ingress_expiry".to_string(), + RawHttpRequestVal::U64(self.ingress_expiry), + ), + ( + "sender".to_string(), + RawHttpRequestVal::Bytes(self.sender.as_slice().to_vec()), + ), + ] + .into_iter() + .collect::>(); + + if let Some(some_nonce) = &self.nonce { + map.insert( + "nonce".to_string(), + RawHttpRequestVal::Bytes(some_nonce.clone()), + ); + } + RequestId(hash_of_map(&map)) + } +} + +/// A request for information from the [IC state tree](https://internetcomputer.org/docs/current/references/ic-interface-spec#state-tree). +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "request_type", rename = "read_state")] +pub struct EnvelopeReadStateContent { + /// A nanosecond timestamp after which this request is no longer valid. + pub ingress_expiry: u64, + /// The principal that is sending this request. + pub sender: Principal, + /// A list of paths within the state tree to fetch. + pub paths: Vec>, +} + +impl RepresentationHashable for EnvelopeReadStateContent { + fn request_id(&self) -> RequestId { + let map = vec![ + ( + "request_type".to_string(), + RawHttpRequestVal::String("read_state".to_string()), + ), + ( + "ingress_expiry".to_string(), + RawHttpRequestVal::U64(self.ingress_expiry), + ), + ( + "paths".to_string(), + RawHttpRequestVal::Array( + self.paths + .iter() + .map(|p| { + RawHttpRequestVal::Array( + p.iter() + .map(|b| RawHttpRequestVal::Bytes(b.as_slice().to_vec())) + .collect(), + ) + }) + .collect(), + ), + ), + ( + "sender".to_string(), + RawHttpRequestVal::Bytes(self.sender.as_slice().to_vec()), + ), + ] + .into_iter() + .collect::>(); + + RequestId(hash_of_map(&map)) + } +} + +fn serialize_arg(arg: &[u8], s: S) -> Result +where + S: Serializer, +{ + s.serialize_bytes(arg) +} + +fn serialize_nonce(nonce: &Option>, s: S) -> Result +where + S: Serializer, +{ + match nonce { + Some(nonce) => s.serialize_bytes(nonce), + None => s.serialize_none(), + } +} + +#[derive(Debug, Clone)] +pub struct Label(Vec); + +impl Label { + #[inline] + pub fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } +} + +impl From<&str> for Label { + fn from(value: &str) -> Self { + Label(value.as_bytes().to_vec()) + } +} + +impl From for Label { + fn from(value: RequestId) -> Self { + Label(value.0.as_slice().to_vec()) + } +} + +// Serialization +impl serde::Serialize for Label { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_bytes(self.0.as_slice()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn representation_independent_hash_call_or_query() { + let content = EnvelopeCallContent { + ingress_expiry: 1685570400000000000, + sender: Principal::anonymous(), + canister_id: Principal::from_slice(&[0, 0, 0, 0, 0, 0, 4, 210]), + method_name: "hello".to_string(), + arg: b"DIDL\x00\xFD*".to_vec(), + nonce: None, + }; + + assert_eq!( + tw_encoding::hex::encode(content.request_id().0, false), + "1d1091364d6bb8a6c16b203ee75467d59ead468f523eb058880ae8ec80e2b101" + ); + } + + #[test] + fn representation_independent_hash_read_state() { + let content = EnvelopeCallContent { + ingress_expiry: 1685570400000000000, + sender: Principal::anonymous(), + canister_id: Principal::from_slice(&[0, 0, 0, 0, 0, 0, 4, 210]), + method_name: "hello".to_string(), + arg: b"DIDL\x00\xFD*".to_vec(), + nonce: None, + }; + let update_request_id = content.request_id(); + + let content = EnvelopeReadStateContent { + ingress_expiry: 1685570400000000000, + sender: Principal::anonymous(), + paths: vec![vec![ + Label::from("request_status"), + Label::from(update_request_id), + ]], + }; + let request_id = content.request_id(); + assert_eq!( + tw_encoding::hex::encode(request_id.0, false), + "3cde0f14a953c3afbe1335f22e861bb62389f1449beca02707ab197e6829c2a3" + ); + } +} diff --git a/rust/chains/tw_internet_computer/src/protocol/identity.rs b/rust/chains/tw_internet_computer/src/protocol/identity.rs new file mode 100644 index 00000000000..b97e0308ec2 --- /dev/null +++ b/rust/chains/tw_internet_computer/src/protocol/identity.rs @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_hash::H256; +use tw_keypair::{ecdsa::secp256k1::PrivateKey, traits::SigningKeyTrait, KeyPairError}; + +use super::principal::Principal; + +#[derive(Debug)] +pub enum SigningError { + Failed(KeyPairError), +} + +/// Contains the signature and the associated DER-encoded public key from a call to +/// [Identity::sign]. +pub struct IdentitySignature { + pub signature: Vec, + pub public_key: Vec, +} + +/// An identity is a simple way to abstract away signing for envelopes. +/// When creating a request to the IC, the sender and signature are required +/// for authentication purposes. The sender is derived from a DER-encode public key +/// and the signature is created using the private key. +pub struct Identity { + private_key: PrivateKey, + der_encoded_public_key: Vec, +} + +impl Identity { + /// Gets the public key and DER-encodes it and returns an Identity. + pub fn new(private_key: PrivateKey) -> Self { + let public_key = private_key.public(); + let der_encoded_public_key = public_key.der_encoded(); + + Self { + private_key, + der_encoded_public_key, + } + } + + /// Returns the principal of the private key. + /// Sender represents who is sending the request. + pub fn sender(&self) -> Principal { + Principal::self_authenticating(&self.der_encoded_public_key) + } + + /// Signs the given content with the private key. + /// The signatures are encoded as the concatenation of the 32-byte big endian + /// encodings of the two values r and s. + /// + /// See: https://internetcomputer.org/docs/current/references/ic-interface-spec#ecdsa + pub fn sign(&self, content: H256) -> Result { + let signature = self + .private_key + .sign(content) + .map_err(SigningError::Failed)?; + + let r = signature.r(); + let s = signature.s(); + let mut bytes = [0u8; 64]; + bytes[..32].clone_from_slice(r.as_slice()); + bytes[32..].clone_from_slice(s.as_slice()); + + let signature = IdentitySignature { + public_key: self.der_encoded_public_key.clone(), + signature: bytes.to_vec(), //Signature bytes + }; + + Ok(signature) + } +} + +#[cfg(test)] +mod test { + + use tw_encoding::hex; + + use super::*; + + /// Test that the sender is derived from the private key. + #[test] + fn sender() { + let private_key = PrivateKey::try_from( + "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be", + ) + .unwrap(); + let identity = Identity::new(private_key); + let sender = identity.sender(); + assert_eq!( + sender.to_text(), + "hpikg-6exdt-jn33w-ndty3-fc7jc-tl2lr-buih3-cs3y7-tftkp-sfp62-gqe" + ) + } + + /// Test signing with the identity. + #[test] + fn sign() { + let private_key = PrivateKey::try_from( + "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be", + ) + .unwrap(); + let der_encoded_public_key = private_key.public().der_encoded(); + let identity = Identity::new(private_key); + let content = H256::new(); + let signature = identity.sign(content).unwrap(); + assert_eq!( + hex::encode(signature.signature, false), + "17c0974ee2ae621099389a5e4d0f960925d2e0e23658df03069308fb8edcb7bb120a338ada3e2ede7f41f6ed2f424a8a4f2c8fb68260f27d4f1bf96d19094b9f" + ); + assert_eq!(der_encoded_public_key, signature.public_key); + } +} diff --git a/rust/chains/tw_internet_computer/src/protocol/mod.rs b/rust/chains/tw_internet_computer/src/protocol/mod.rs new file mode 100644 index 00000000000..5d1f0380c38 --- /dev/null +++ b/rust/chains/tw_internet_computer/src/protocol/mod.rs @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod envelope; +pub mod identity; +pub mod principal; +pub mod request_id; +pub mod rosetta; + +use std::time::Duration; + +/// This constant defines the maximum amount of time an ingress message can wait +/// to start executing after submission before it is expired. Hence, if an +/// ingress message is submitted at time `t` and it has not been scheduled for +/// execution till time `t+MAX_INGRESS_TTL`, it will be expired. +/// +/// At the time of writing, this constant is also used to control how long the +/// status of a completed ingress message (IngressStatus ∈ [Completed, Failed]) +/// is maintained by the IC before it is deleted from the ingress history. +const MAX_INGRESS_TTL: Duration = Duration::from_secs(5 * 60); + +/// An upper limit on the validity of the request, expressed in nanoseconds since 1970-01-01. +pub fn get_ingress_expiry( + current_timestamp_duration: Duration, + permitted_drift_in_seconds: Option, +) -> u64 { + let permitted_drift = permitted_drift_in_seconds + .map(Duration::from_secs) + .unwrap_or(Duration::from_secs(60)); + + current_timestamp_duration + .saturating_add(MAX_INGRESS_TTL) + .saturating_sub(permitted_drift) + .as_nanos() as u64 +} diff --git a/rust/chains/tw_internet_computer/src/protocol/principal.rs b/rust/chains/tw_internet_computer/src/protocol/principal.rs new file mode 100644 index 00000000000..905739a86b8 --- /dev/null +++ b/rust/chains/tw_internet_computer/src/protocol/principal.rs @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +/// Taken from candid crate and modified to rely upon built-in crc32 and SHA224 functionality. +use std::fmt::Write; + +use tw_hash::{crc32::crc32, sha2::sha224}; +use tw_keypair::ecdsa::secp256k1::PublicKey; + +/// An error happened while encoding, decoding or serializing a [`Principal`]. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum PrincipalError { + BytesTooLong, + InvalidBase32, + TextTooShort, + TextTooLong, + CheckSequenceNotMatch, + AbnormalGrouped(Principal), +} + +/// Generic ID on Internet Computer. +/// +/// Principals are generic identifiers for canisters, users +/// and possibly other concepts in the future. +/// As far as most uses of the IC are concerned they are +/// opaque binary blobs with a length between 0 and 29 bytes, +/// and there is intentionally no mechanism to tell canister ids and user ids apart. +/// +/// Note a principal is not necessarily tied with a public key-pair, +/// yet we need at least a key-pair of a related principal to sign +/// requests. +/// +/// A Principal can be serialized to a byte array ([`Vec`]) or a text +/// representation, but the inner structure of the byte representation +/// is kept private. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Principal { + /// Length. + len: u8, + + /// The content buffer. When returning slices this should always be sized according to + /// `len`. + bytes: [u8; Self::MAX_LENGTH_IN_BYTES], +} + +impl Principal { + const MAX_LENGTH_IN_BYTES: usize = 29; + const CRC_LENGTH_IN_BYTES: usize = 4; + + const SELF_AUTHENTICATING_TAG: u8 = 2; + const ANONYMOUS_TAG: u8 = 4; + + /// Construct a [`Principal`] of the IC management canister + pub const fn management_canister() -> Self { + Self { + len: 0, + bytes: [0; Self::MAX_LENGTH_IN_BYTES], + } + } + + /// Construct a self-authenticating ID from public key + pub fn self_authenticating>(public_key: P) -> Self { + let public_key = public_key.as_ref(); + let hash = sha224(public_key); + let mut bytes = [0; Self::MAX_LENGTH_IN_BYTES]; + bytes[..Self::MAX_LENGTH_IN_BYTES - 1].copy_from_slice(hash.as_slice()); + bytes[Self::MAX_LENGTH_IN_BYTES - 1] = Self::SELF_AUTHENTICATING_TAG; + + Self { + len: Self::MAX_LENGTH_IN_BYTES as u8, + bytes, + } + } + + /// Construct an anonymous ID. + pub const fn anonymous() -> Self { + let mut bytes = [0; Self::MAX_LENGTH_IN_BYTES]; + bytes[0] = Self::ANONYMOUS_TAG; + Self { len: 1, bytes } + } + + /// Construct a [`Principal`] from a slice of bytes. + /// + /// # Panics + /// + /// Panics if the slice is longer than 29 bytes. + pub fn from_slice(slice: &[u8]) -> Self { + match Self::try_from_slice(slice) { + Ok(v) => v, + _ => panic!("slice length exceeds capacity"), + } + } + + /// Construct a [`Principal`] from a slice of bytes. + pub fn try_from_slice(slice: &[u8]) -> Result { + const MAX_LENGTH_IN_BYTES: usize = Principal::MAX_LENGTH_IN_BYTES; + if slice.len() > MAX_LENGTH_IN_BYTES { + return Err(PrincipalError::BytesTooLong); + } + + let mut bytes = [0; MAX_LENGTH_IN_BYTES]; + bytes[0..slice.len()].copy_from_slice(slice); + + Ok(Self { + len: slice.len() as u8, + bytes, + }) + } + + /// Parse a [`Principal`] from text representation. + pub fn from_text>(text: S) -> Result { + // Strategy: Parse very liberally, then pretty-print and compare output + // This is both simpler and yields better error messages + + let mut s = text.as_ref().to_string(); + s.make_ascii_uppercase(); + s.retain(|c| c != '-'); + + let bytes = tw_encoding::base32::decode(&s, None, false) + .map_err(|_| PrincipalError::InvalidBase32)?; + + if bytes.len() < Self::CRC_LENGTH_IN_BYTES { + return Err(PrincipalError::TextTooShort); + } + + let crc_bytes = &bytes[..Self::CRC_LENGTH_IN_BYTES]; + let data_bytes = &bytes[Self::CRC_LENGTH_IN_BYTES..]; + if data_bytes.len() > Self::MAX_LENGTH_IN_BYTES { + return Err(PrincipalError::TextTooLong); + } + + if crc32(data_bytes).to_be_bytes() != crc_bytes { + return Err(PrincipalError::CheckSequenceNotMatch); + } + + // Already checked data_bytes.len() <= MAX_LENGTH_IN_BYTES + // safe to unwrap here + let result = Self::try_from_slice(data_bytes).unwrap(); + let expected = format!("{result}"); + + // In the Spec: + // The textual representation is conventionally printed with lower case letters, + // but parsed case-insensitively. + if text.as_ref().to_ascii_lowercase() != expected { + return Err(PrincipalError::AbnormalGrouped(result)); + } + Ok(result) + } + + /// Convert [`Principal`] to text representation. + pub fn to_text(&self) -> String { + format!("{self}") + } + + /// Return the [`Principal`]'s underlying slice of bytes. + #[inline] + pub fn as_slice(&self) -> &[u8] { + &self.bytes[..self.len as usize] + } +} + +impl std::str::FromStr for Principal { + type Err = PrincipalError; + + fn from_str(s: &str) -> Result { + Principal::from_text(s) + } +} + +impl From<&PublicKey> for Principal { + /// Takes a Secp256k1 public key, DER-encodes the public key, + /// and creates a principal from the encoding. + fn from(public_key: &PublicKey) -> Self { + Self::self_authenticating(public_key.der_encoded()) + } +} + +impl std::fmt::Display for Principal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let blob: &[u8] = self.as_slice(); + + // calc checksum + let checksum = crc32(blob); + + // combine blobs + let mut bytes = vec![]; + bytes.extend_from_slice(&checksum.to_be_bytes()); + bytes.extend_from_slice(blob); + + // base32 + let mut s = + tw_encoding::base32::encode(&bytes, None, false).map_err(|_| std::fmt::Error)?; + s.make_ascii_lowercase(); + + // write out string with dashes + let mut s = s.as_str(); + while s.len() > 5 { + f.write_str(&s[..5])?; + f.write_char('-')?; + s = &s[5..]; + } + f.write_str(s) + } +} + +impl TryFrom<&str> for Principal { + type Error = PrincipalError; + + fn try_from(s: &str) -> Result { + Principal::from_text(s) + } +} + +impl TryFrom> for Principal { + type Error = PrincipalError; + + fn try_from(bytes: Vec) -> Result { + Self::try_from(bytes.as_slice()) + } +} + +impl TryFrom<&Vec> for Principal { + type Error = PrincipalError; + + fn try_from(bytes: &Vec) -> Result { + Self::try_from(bytes.as_slice()) + } +} + +impl TryFrom<&[u8]> for Principal { + type Error = PrincipalError; + + fn try_from(bytes: &[u8]) -> Result { + Self::try_from_slice(bytes) + } +} + +impl AsRef<[u8]> for Principal { + fn as_ref(&self) -> &[u8] { + self.as_slice() + } +} + +// Serialization +impl serde::Serialize for Principal { + fn serialize(&self, serializer: S) -> Result { + if serializer.is_human_readable() { + self.to_text().serialize(serializer) + } else { + serializer.serialize_bytes(self.as_slice()) + } + } +} diff --git a/rust/chains/tw_internet_computer/src/protocol/request_id.rs b/rust/chains/tw_internet_computer/src/protocol/request_id.rs new file mode 100644 index 00000000000..ecc48a5a17f --- /dev/null +++ b/rust/chains/tw_internet_computer/src/protocol/request_id.rs @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; +use tw_hash::{sha2::sha256, H256}; + +const DOMAIN_IC_REQUEST: &[u8; 11] = b"\x0Aic-request"; + +/// When signing requests or querying the status of a request +/// (see Request status) in the state tree, the user identifies +/// the request using a request id, which is the +/// representation-independent hash of the content map of the +/// original request. A request id must have length of 32 bytes. +pub struct RequestId(pub(crate) H256); + +impl RequestId { + /// Create the prehash from the request ID. + /// See: https://internetcomputer.org/docs/current/references/ic-interface-spec#envelope-authentication + pub fn sig_data(&self) -> H256 { + let mut sig_data = vec![]; + sig_data.extend_from_slice(DOMAIN_IC_REQUEST); + sig_data.extend_from_slice(self.0.as_slice()); + H256::try_from(sha256(&sig_data).as_slice()).unwrap_or_else(|_| H256::new()) + } +} + +/// The different types of values supported in `RawHttpRequest`. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum RawHttpRequestVal { + Bytes(#[serde(serialize_with = "serialize_bytes")] Vec), + String(String), + U64(u64), + Array(Vec), +} + +fn serialize_bytes(bytes: &[u8], s: S) -> Result +where + S: serde::Serializer, +{ + s.serialize_bytes(bytes) +} + +fn hash_string(value: String) -> Vec { + sha256(value.as_bytes()) +} + +fn hash_bytes(value: Vec) -> Vec { + sha256(value.as_slice()) +} + +fn hash_u64(value: u64) -> Vec { + // We need at most ⌈ 64 / 7 ⌉ = 10 bytes to encode a 64 bit + // integer in LEB128. + let mut buf = [0u8; 10]; + let mut n = value; + let mut i = 0; + + loop { + let byte = (n & 0x7f) as u8; + n >>= 7; + + if n == 0 { + buf[i] = byte; + break; + } else { + buf[i] = byte | 0x80; + i += 1; + } + } + + hash_bytes(buf[..=i].to_vec()) +} + +// arrays, encoded as the concatenation of the hashes of the encodings of the +// array elements. +fn hash_array(elements: Vec) -> Vec { + let mut buffer = vec![]; + elements + .into_iter() + // Hash the encoding of all the array elements. + .for_each(|e| { + let mut hashed_val = hash_val(e); + buffer.append(&mut hashed_val); + }); + sha256(&buffer) +} + +fn hash_val(val: RawHttpRequestVal) -> Vec { + match val { + RawHttpRequestVal::String(string) => hash_string(string), + RawHttpRequestVal::Bytes(bytes) => hash_bytes(bytes), + RawHttpRequestVal::U64(integer) => hash_u64(integer), + RawHttpRequestVal::Array(elements) => hash_array(elements), + } +} + +fn hash_key_val(key: String, val: RawHttpRequestVal) -> Vec { + let mut key_hash = hash_string(key); + let mut val_hash = hash_val(val); + key_hash.append(&mut val_hash); + key_hash +} + +/// Describes `hash_of_map` as specified in the public spec. +/// See: https://internetcomputer.org/docs/current/references/ic-interface-spec#hash-of-map +pub fn hash_of_map(map: &BTreeMap) -> H256 { + let mut hashes: Vec> = Vec::new(); + for (key, val) in map.iter() { + hashes.push(hash_key_val(key.to_string(), val.clone())); + } + + // Computes hash by first sorting by "field name" hash, which is the + // same as sorting by concatenation of H(field name) · H(field value) + // (although in practice it's actually more stable in the presence of + // duplicated field names). Then concatenate all the hashes. + hashes.sort(); + + let buffer = hashes.into_iter().flatten().collect::>(); + let hash = sha256(&buffer); + + H256::try_from(hash.as_slice()).unwrap_or_else(|_| H256::new()) +} diff --git a/rust/chains/tw_internet_computer/src/protocol/rosetta.rs b/rust/chains/tw_internet_computer/src/protocol/rosetta.rs new file mode 100644 index 00000000000..a929a4f5f15 --- /dev/null +++ b/rust/chains/tw_internet_computer/src/protocol/rosetta.rs @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use super::envelope::{Envelope, EnvelopeCallContent, EnvelopeReadStateContent}; +use serde::Serialize; + +/// The types of requests that are available from the Rosetta node. +/// This enum is truncated to include support only for the +/// operations that this crate can currently perform. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub enum RequestType { + // Aliases for backwards compatibility + #[serde(rename = "TRANSACTION")] + #[serde(alias = "Send")] + Send, +} + +/// The type (encoded as CBOR) returned by the Rosetta node's +/// /construction/combine endpoint. It contains the +/// IC calls to submit the transaction and to check the result. +pub type SignedTransaction = Vec; + +/// A vector of update/read-state calls for different ingress windows +/// of the same call. +pub type Request = (RequestType, Vec); + +#[derive(Debug, Clone)] +pub enum EnvelopePairError { + InvalidUpdateEnvelope, + InvalidReadStateEnvelope, +} + +/// A signed IC update call and the corresponding read-state call for +/// a particular ingress window. +#[derive(Debug, Clone, Serialize)] +pub struct EnvelopePair { + update: Envelope, + read_state: Envelope, +} + +impl EnvelopePair { + pub fn new( + update_envelope: Envelope, + read_state_envelope: Envelope, + ) -> Result { + Ok(Self { + update: update_envelope, + read_state: read_state_envelope, + }) + } +} diff --git a/rust/chains/tw_internet_computer/src/signer.rs b/rust/chains/tw_internet_computer/src/signer.rs new file mode 100644 index 00000000000..ee4a39a76de --- /dev/null +++ b/rust/chains/tw_internet_computer/src/signer.rs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::marker::PhantomData; + +use tw_coin_entry::{error::prelude::*, signing_output_error}; +use tw_keypair::ecdsa::secp256k1; +use tw_proto::{Common::Proto::SigningError as CommonError, InternetComputer::Proto}; + +use crate::{ + context::InternetComputerContext, + protocol::identity, + transactions::{self, sign_transaction}, +}; + +impl From for SigningError { + fn from(error: transactions::SignTransactionError) -> Self { + match error { + transactions::SignTransactionError::InvalidArguments => { + SigningError::new(CommonError::Error_invalid_params) + }, + transactions::SignTransactionError::Identity(identity_error) => match identity_error { + identity::SigningError::Failed(_) => SigningError::new(CommonError::Error_signing), + }, + transactions::SignTransactionError::InvalidEnvelopePair + | transactions::SignTransactionError::EncodingArgsFailed => { + SigningError::new(CommonError::Error_internal) + }, + transactions::SignTransactionError::InvalidToAccountIdentifier => { + SigningError::new(CommonError::Error_invalid_address) + }, + transactions::SignTransactionError::InvalidAmount => { + SigningError::new(CommonError::Error_invalid_requested_token_amount) + }, + } + } +} + +pub struct Signer { + _phantom: PhantomData, +} + +impl Signer { + #[inline] + pub fn sign_proto(input: Proto::SigningInput<'_>) -> Proto::SigningOutput<'static> { + Self::sign_proto_impl(input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_proto_impl( + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let private_key = secp256k1::PrivateKey::try_from(input.private_key.as_ref())?; + + let Some(ref transaction) = input.transaction else { + return SigningError::err(CommonError::Error_invalid_params); + }; + + let canister_id = Context::get_canister_id(); + let signed_transaction = + sign_transaction(private_key, canister_id, &transaction.transaction_oneof)?; + + let cbor_encoded_signed_transaction = tw_encoding::cbor::encode(&signed_transaction) + .tw_err(|_| CommonError::Error_internal)?; + + Ok(Proto::SigningOutput { + signed_transaction: cbor_encoded_signed_transaction.into(), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/chains/tw_internet_computer/src/transactions/mod.rs b/rust/chains/tw_internet_computer/src/transactions/mod.rs new file mode 100644 index 00000000000..e0c21697c1d --- /dev/null +++ b/rust/chains/tw_internet_computer/src/transactions/mod.rs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod transfer; + +pub mod proto { + include!(concat!(env!("OUT_DIR"), "/proto/mod.rs")); +} + +use tw_keypair::ecdsa::secp256k1::PrivateKey; +use tw_proto::InternetComputer::Proto::mod_Transaction::OneOftransaction_oneof as Tx; + +use crate::protocol::{identity, principal::Principal, rosetta}; + +#[derive(Debug)] +pub enum SignTransactionError { + InvalidAmount, + InvalidArguments, + Identity(identity::SigningError), + EncodingArgsFailed, + InvalidToAccountIdentifier, + InvalidEnvelopePair, +} + +pub fn sign_transaction( + private_key: PrivateKey, + canister_id: Principal, + transaction: &Tx, +) -> Result { + match transaction { + Tx::transfer(transfer_args) => transfer::transfer( + private_key, + canister_id, + transfer::TransferArgs { + memo: transfer_args.memo, + amount: transfer_args.amount, + max_fee: None, + to: transfer_args.to_account_identifier.to_string(), + current_timestamp_nanos: transfer_args.current_timestamp_nanos, + permitted_drift: if transfer_args.permitted_drift > 0 { + Some(transfer_args.permitted_drift) + } else { + None + }, + }, + ), + Tx::None => Err(SignTransactionError::InvalidArguments), + } +} diff --git a/rust/chains/tw_internet_computer/src/transactions/proto/ledger.proto b/rust/chains/tw_internet_computer/src/transactions/proto/ledger.proto new file mode 100644 index 00000000000..59858bbf2ea --- /dev/null +++ b/rust/chains/tw_internet_computer/src/transactions/proto/ledger.proto @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +// -*- c-basic-offset: 2 -*- +// Source: https://github.com/dfinity/ic/blob/master/rs/rosetta-api/icp_ledger/proto/ic_ledger/pb/v1/types.proto +// Commit Hash: 703eb96fea44ad2c82740e4360a526a2a127a960 +// Striped annotations related to the use of hardware wallets. + +syntax = "proto3"; + +package ic_ledger.pb.v1; + +import "./types.proto"; + +// Annotations related to the use of hardware wallets. The annotated messages are +// parsed on hardware wallets and marked fields are displayed in a trusted user +// interface (TUI). We must not, for instance, add fields that would change the +// semantics of the message such that old hardware wallets would not display +// appropriate information to users. + +// ** LEDGER CANISTER ENDPOINTS + +// Initialise the ledger canister +message LedgerInit { + AccountIdentifier minting_account = 1; + repeated Account initial_values = 2; + ic_base_types.pb.v1.PrincipalId archive_canister = 3; + uint32 max_message_size_bytes = 4; +} + +// The format of values serialized to/from the stable memory during and upgrade +message LedgerUpgrade {} + +// Make a payment +message SendRequest { + Memo memo = 1; + Payment payment = 2; + Tokens max_fee = 3; + Subaccount from_subaccount = 4; + AccountIdentifier to = 5; + BlockIndex created_at = 6; + TimeStamp created_at_time = 7; +} + +message SendResponse { + BlockIndex resulting_height = 1; +} + +// Notify a canister that it has received a payment +message NotifyRequest { + BlockIndex block_height = 1; + Tokens max_fee = 2; + Subaccount from_subaccount = 3; + ic_base_types.pb.v1.PrincipalId to_canister = 4; + Subaccount to_subaccount = 5; +} + +message NotifyResponse {} + +message TransactionNotificationRequest { + ic_base_types.pb.v1.PrincipalId from = 1; + Subaccount from_subaccount = 2; + ic_base_types.pb.v1.PrincipalId to = 3; + Subaccount to_subaccount = 4; + BlockIndex block_height = 5; + Tokens amount = 6; + Memo memo = 7; +} + +message TransactionNotificationResponse { + bytes response = 1; +} + +message CyclesNotificationResponse { + oneof response { + ic_base_types.pb.v1.PrincipalId created_canister_id = 1; + Refund refund = 2; + ToppedUp topped_up = 3; + } +} + +// Get the balance of an account +message AccountBalanceRequest { + AccountIdentifier account = 1; +} + +message AccountBalanceResponse { + Tokens balance = 1; +} + +// Get the length of the chain with a certification +message TipOfChainRequest {} + +message TipOfChainResponse { + Certification certification = 1; + BlockIndex chain_length = 2; +} + +// How many Tokens are there not in the minting account +message TotalSupplyRequest {} + +message TotalSupplyResponse { + Tokens total_supply = 1; +} + +// Archive any blocks older than this +message LedgerArchiveRequest { + TimeStamp timestamp = 1; +} + +// * Shared Endpoints * + +// Get a single block +message BlockRequest { + uint64 block_height = 1; +} + +message EncodedBlock { + bytes block = 1; +} + +message BlockResponse { + oneof block_content { + EncodedBlock block = 1; + ic_base_types.pb.v1.PrincipalId canister_id = 2; + } +} + +// Get a set of blocks +message GetBlocksRequest { + uint64 start = 1; + uint64 length = 2; +} + +message Refund { + BlockIndex refund = 2; + string error = 3; +} + +message ToppedUp {} + +message EncodedBlocks { + repeated EncodedBlock blocks = 1; +} + +message GetBlocksResponse { + oneof get_blocks_content { + EncodedBlocks blocks = 1; + string error = 2; + } +} + +// Iterate through blocks +message IterBlocksRequest { + uint64 start = 1; + uint64 length = 2; +} + +message IterBlocksResponse { + repeated EncodedBlock blocks = 1; +} + +message ArchiveIndexEntry { + uint64 height_from = 1; + uint64 height_to = 2; + ic_base_types.pb.v1.PrincipalId canister_id = 3; +} + +message ArchiveIndexResponse { + repeated ArchiveIndexEntry entries = 1; +} + +// ** ARCHIVE CANISTER ENDPOINTS ** + +// * Archive canister * +// Init the archive canister +message ArchiveInit { + uint32 node_max_memory_size_bytes = 1; + uint32 max_message_size_bytes = 2; +} + +// Add blocks to the archive canister +message ArchiveAddRequest { + repeated Block blocks = 1; +} + +message ArchiveAddResponse {} + +// Fetch a list of all of the archive nodes +message GetNodesRequest {} + +message GetNodesResponse { + repeated ic_base_types.pb.v1.PrincipalId nodes = 1; +} + +// ** BASIC TYPES ** +message Tokens { + uint64 e8s = 1; +} + +message Payment { + Tokens receiver_gets = 1; +} + +message BlockIndex { + uint64 height = 1; +} + +// This is the +message Block { + Hash parent_hash = 1; + TimeStamp timestamp = 2; + Transaction transaction = 3; +} + +message Hash { + bytes hash = 1; +} + +message Account { + AccountIdentifier identifier = 1; + Tokens balance = 2; +} + +message Transaction { + oneof transfer { + Burn burn = 1; + Mint mint = 2; + Send send = 3; + } + Memo memo = 4; + Icrc1Memo icrc1_memo = 7; + BlockIndex created_at = 5; // obsolete + TimeStamp created_at_time = 6; +} + +message Send { + // The meaning of the [from] field depends on the transaction type: + // - Transfer: [from] is the source account. + // - TransferFrom: [from] is the approver. + // - Approve: [from] is the approver. + AccountIdentifier from = 1; + // The meaning of the [to] field depends on the transaction type: + // - Transfer: [to] is the destination account. + // - TransferFrom: [to] is the destination account. + // - Approve: [to] is the default account id of the approved principal. + AccountIdentifier to = 2; + // If the transaction type is Approve, the amount must be zero. + Tokens amount = 3; + Tokens max_fee = 4; + + // We represent metadata of new operation types as submessages for + // backward compatibility with old clients. + oneof extension { + Approve approve = 5; + TransferFrom transfer_from = 6; + } +} + +message TransferFrom { + // The default account id of the principal who sent the transaction. + AccountIdentifier spender = 1; +} + +message Approve { + Tokens allowance = 1; + TimeStamp expires_at = 2; + Tokens expected_allowance = 3; +} + +message Mint { + AccountIdentifier to = 2; + Tokens amount = 3; +} + +message Burn { + AccountIdentifier from = 1; + Tokens amount = 3; +} + +message AccountIdentifier { + // Can contain either: + // * the 32 byte identifier (4 byte checksum + 28 byte hash) + // * the 28 byte hash + bytes hash = 1; +} + +message Subaccount { + bytes sub_account = 1; +} + +message Memo { + uint64 memo = 1; +} + +message Icrc1Memo { + bytes memo = 1; +} + +message TimeStamp { + uint64 timestamp_nanos = 1; +} + +message Certification { + bytes certification = 1; +} + +message TransferFeeRequest {} + +message TransferFeeResponse { + Tokens transfer_fee = 1; +} \ No newline at end of file diff --git a/rust/chains/tw_internet_computer/src/transactions/proto/types.proto b/rust/chains/tw_internet_computer/src/transactions/proto/types.proto new file mode 100644 index 00000000000..f869c11ccc3 --- /dev/null +++ b/rust/chains/tw_internet_computer/src/transactions/proto/types.proto @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +// Source: https://github.com/dfinity/ic/blob/master/rs/rosetta-api/icp_ledger/proto/ic_ledger/pb/v1/types.proto +// Commit Hash: 703eb96fea44ad2c82740e4360a526a2a127a960 +// Striped annotations related to the use of hardware wallets. + +syntax = "proto3"; + +package ic_base_types.pb.v1; + +// A PB container for a PrincipalId, which uniquely identifies +// a principal. +message PrincipalId { + bytes serialized_id = 1; +} \ No newline at end of file diff --git a/rust/chains/tw_internet_computer/src/transactions/transfer.rs b/rust/chains/tw_internet_computer/src/transactions/transfer.rs new file mode 100644 index 00000000000..7cc846f3b53 --- /dev/null +++ b/rust/chains/tw_internet_computer/src/transactions/transfer.rs @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::time::Duration; + +use tw_keypair::ecdsa::secp256k1::PrivateKey; + +use crate::{ + address::AccountIdentifier, + protocol::{ + envelope::{ + Envelope, EnvelopeCallContent, EnvelopeReadStateContent, Label, RepresentationHashable, + }, + get_ingress_expiry, + identity::Identity, + principal::Principal, + request_id::RequestId, + rosetta, + }, + transactions::proto::ic_ledger::pb::v1::{ + AccountIdentifier as ProtoAccountIdentifier, Memo, Payment, SendRequest, TimeStamp, Tokens, + }, +}; + +use super::SignTransactionError; + +/// Arguments to be used with [transfer] to create a signed transaction enveloper pair. +#[derive(Clone, Debug)] +pub struct TransferArgs { + /// The memo field is used as a method to help identify the transaction. + pub memo: u64, + /// The amount of ICP to send as e8s. + pub amount: u64, + /// The maximum fee will to be paid to complete the transfer. + /// If not provided, the minimum fee will be applied to the transaction. Currently 10_000 e8s (0.00010000 ICP). + pub max_fee: Option, + /// The address to send the amount to. + pub to: String, + /// The current timestamp in nanoseconds. + pub current_timestamp_nanos: u64, + /// The duration to tune up ingress expiry in seconds. + pub permitted_drift: Option, +} + +impl TryFrom for SendRequest { + type Error = SignTransactionError; + + fn try_from(args: TransferArgs) -> Result { + let current_timestamp_duration = Duration::from_nanos(args.current_timestamp_nanos); + let timestamp_nanos = current_timestamp_duration.as_nanos() as u64; + + let to_account_identifier = AccountIdentifier::from_hex(&args.to) + .map_err(|_| SignTransactionError::InvalidToAccountIdentifier)?; + let to_hash = to_account_identifier.as_ref().to_vec(); + + let request = Self { + memo: Some(Memo { memo: args.memo }), + payment: Some(Payment { + receiver_gets: Some(Tokens { e8s: args.amount }), + }), + max_fee: args.max_fee.map(|fee| Tokens { e8s: fee }), + from_subaccount: None, + to: Some(ProtoAccountIdentifier { hash: to_hash }), + created_at: None, + created_at_time: Some(TimeStamp { timestamp_nanos }), + }; + Ok(request) + } +} + +/// The endpoint on the ledger canister that is used to make transfers. +const METHOD_NAME: &str = "send_pb"; + +/// Given a secp256k1 private key, the canister ID of an ICP-based ledger canister, and the actual transfer args, +/// this function creates a signed transaction to be sent to a Rosetta API node. +pub fn transfer( + private_key: PrivateKey, + canister_id: Principal, + args: TransferArgs, +) -> Result { + if args.amount < 1 { + return Err(SignTransactionError::InvalidAmount); + } + + let current_timestamp_duration = Duration::from_nanos(args.current_timestamp_nanos); + let ingress_expiry = get_ingress_expiry(current_timestamp_duration, args.permitted_drift); + let identity = Identity::new(private_key); + + // Encode the arguments for the ledger `send_pb` endpoint. + let send_request = SendRequest::try_from(args)?; + let arg = + tw_proto::serialize(&send_request).map_err(|_| SignTransactionError::EncodingArgsFailed)?; + // Create the update envelope. + let (request_id, update_envelope) = + create_update_envelope(&identity, canister_id, arg, ingress_expiry)?; + + // Create the read state envelope. + let (_, read_state_envelope) = + create_read_state_envelope(&identity, request_id, ingress_expiry)?; + + // Create a new EnvelopePair with the update call and read_state envelopes. + let envelope_pair = rosetta::EnvelopePair::new(update_envelope, read_state_envelope) + .map_err(|_| SignTransactionError::InvalidEnvelopePair)?; + + // Create a signed transaction containing the envelope pair. + let request: rosetta::Request = (rosetta::RequestType::Send, vec![envelope_pair]); + Ok(vec![request]) +} + +#[inline] +fn create_update_envelope( + identity: &Identity, + canister_id: Principal, + arg: Vec, + ingress_expiry: u64, +) -> Result<(RequestId, Envelope), SignTransactionError> { + let sender = identity.sender(); + let content = EnvelopeCallContent { + nonce: None, + ingress_expiry, + sender, + canister_id, + method_name: METHOD_NAME.to_string(), + arg, + }; + + let request_id = content.request_id(); + let signature = identity + .sign(request_id.sig_data()) + .map_err(SignTransactionError::Identity)?; + + let env = Envelope { + content, + sender_pubkey: Some(signature.public_key), + sender_sig: Some(signature.signature), + }; + Ok((request_id, env)) +} + +#[inline] +fn create_read_state_envelope( + identity: &Identity, + update_request_id: RequestId, + ingress_expiry: u64, +) -> Result<(RequestId, Envelope), SignTransactionError> { + let sender = identity.sender(); + + let content = EnvelopeReadStateContent { + ingress_expiry, + sender, + paths: vec![vec![ + Label::from("request_status"), + Label::from(update_request_id), + ]], + }; + + let request_id = content.request_id(); + let signature = identity + .sign(request_id.sig_data()) + .map_err(SignTransactionError::Identity)?; + + let env = Envelope { + content, + sender_pubkey: Some(signature.public_key), + sender_sig: Some(signature.signature), + }; + Ok((request_id, env)) +} + +#[cfg(test)] +mod test { + use tw_encoding::hex; + + use crate::address::AccountIdentifier; + + use super::*; + + pub const SIGNED_TRANSACTION: &str = "81826b5452414e53414354494f4e81a266757064617465a367636f6e74656e74a66c726571756573745f747970656463616c6c6e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d026b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f706263617267583b0a0012070a050880c2d72f2a220a20943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a3a0a088090caa5a3a78abd176d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f736967984013186f18b9181c189818b318a8186518b2186118d418971618b1187d18eb185818e01826182f1873183b185018cb185d18ef18d81839186418b3183218da1824182f184e18a01880182718c0189018c918a018fd18c418d9189e189818b318ef1874183b185118e118a51864185918e718ed18c71889186c1822182318ca6a726561645f7374617465a367636f6e74656e74a46c726571756573745f747970656a726561645f73746174656e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d0265706174687381824e726571756573745f7374617475735820e8fbc2d5b0bf837b3a369249143e50d4476faafb2dd620e4e982586a51ebcf1e6d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f7369679840182d182718201888188618ce187f0c182a187a18d718e818df18fb18d318d41118a5186a184b18341842185318d718e618e8187a1828186c186a183618461418f3183318bd18a618a718bc18d918c818b7189d186e1865188418ff18fd18e418e9187f181b18d705184818b21872187818d6181c161833184318a2"; + + fn make_transfer_args() -> TransferArgs { + let current_timestamp_nanos = Duration::from_secs(1_691_709_940).as_nanos() as u64; + let owner = + Principal::from_text("t4u4z-y3dur-j63pk-nw4rv-yxdbt-agtt6-nygn7-ywh6y-zm2f4-sdzle-3qe") + .unwrap(); + let to_account_identifier = AccountIdentifier::new(&owner); + + TransferArgs { + memo: 0, + amount: 100_000_000, + max_fee: None, + to: to_account_identifier.to_hex(), + current_timestamp_nanos, + permitted_drift: None, + } + } + + #[test] + fn transfer_successful() { + let private_key = PrivateKey::try_from( + "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be", + ) + .unwrap(); + let canister_id = Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap(); + let transfer_args = make_transfer_args(); + + let signed_transaction = transfer(private_key, canister_id, transfer_args).unwrap(); + // Encode the signed transaction. + let cbor_encoded_signed_transaction = + tw_encoding::cbor::encode(&signed_transaction).unwrap(); + let hex_encoded_signed_transaction = hex::encode(&cbor_encoded_signed_transaction, false); + assert_eq!(hex_encoded_signed_transaction, SIGNED_TRANSACTION); + } + + #[test] + fn transfer_invalid_amount() { + let private_key = PrivateKey::try_from( + "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be", + ) + .unwrap(); + let canister_id = Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap(); + let mut transfer_args = make_transfer_args(); + transfer_args.amount = 0; + + let signed_transaction = transfer(private_key, canister_id, transfer_args); + assert!(matches!( + signed_transaction, + Err(SignTransactionError::InvalidAmount) + )); + } + + #[test] + fn transfer_invalid_to_account_identifier() { + let private_key = PrivateKey::try_from( + "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be", + ) + .unwrap(); + let canister_id = Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap(); + let mut transfer_args = make_transfer_args(); + transfer_args.to = "invalid".to_string(); + + let signed_transaction = transfer(private_key, canister_id, transfer_args); + assert!(matches!( + signed_transaction, + Err(SignTransactionError::InvalidToAccountIdentifier) + )); + } +} diff --git a/rust/chains/tw_native_evmos/Cargo.toml b/rust/chains/tw_native_evmos/Cargo.toml new file mode 100644 index 00000000000..87137a252cc --- /dev/null +++ b/rust/chains/tw_native_evmos/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "tw_native_evmos" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_hash = { path = "../../tw_hash" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk", features = ["test-utils"] } diff --git a/rust/chains/tw_native_evmos/src/context.rs b/rust/chains/tw_native_evmos/src/context.rs new file mode 100644 index 00000000000..abebb7cd8f9 --- /dev/null +++ b/rust/chains/tw_native_evmos/src/context.rs @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::ethermint_public_key::EthermintEthSecp256PublicKey; +use tw_cosmos_sdk::address::Address; +use tw_cosmos_sdk::context::CosmosContext; +use tw_cosmos_sdk::private_key::secp256k1::Secp256PrivateKey; +use tw_cosmos_sdk::signature::secp256k1::Secp256k1Signature; +use tw_hash::hasher::Hasher; + +pub struct NativeEvmosContext; + +impl CosmosContext for NativeEvmosContext { + type Address = Address; + type PrivateKey = Secp256PrivateKey; + type PublicKey = EthermintEthSecp256PublicKey; + type Signature = Secp256k1Signature; + + fn default_tx_hasher() -> Hasher { + Hasher::Keccak256 + } +} diff --git a/rust/chains/tw_native_evmos/src/entry.rs b/rust/chains/tw_native_evmos/src/entry.rs new file mode 100644 index 00000000000..e3964e6454c --- /dev/null +++ b/rust/chains/tw_native_evmos/src/entry.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::context::NativeEvmosContext; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; +use tw_coin_entry::modules::wallet_connector::NoWalletConnector; +use tw_cosmos_sdk::address::{Address, Bech32Prefix}; +use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; +use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; +use tw_keypair::tw; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct NativeEvmosEntry; + +impl CoinEntry for NativeEvmosEntry { + type AddressPrefix = Bech32Prefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + type WalletConnector = NoWalletConnector; + type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; + + #[inline] + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + prefix: Option, + ) -> AddressResult { + Address::from_str_with_coin_and_prefix(coin, address.to_string(), prefix) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: tw::PublicKey, + _derivation: Derivation, + prefix: Option, + ) -> AddressResult { + Address::with_public_key_coin_context(coin, &public_key, prefix) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + TWSigner::::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + TWTransactionCompiler::::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + TWTransactionCompiler::::compile(coin, input, signatures, public_keys) + } +} diff --git a/rust/chains/tw_native_evmos/src/ethermint_public_key.rs b/rust/chains/tw_native_evmos/src/ethermint_public_key.rs new file mode 100644 index 00000000000..c2bf2d8f0ad --- /dev/null +++ b/rust/chains/tw_native_evmos/src/ethermint_public_key.rs @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_cosmos_sdk::proto::ethermint; +use tw_cosmos_sdk::public_key::secp256k1::Secp256PublicKey; +use tw_cosmos_sdk::public_key::{ + CosmosPublicKey, JsonPublicKey, ProtobufPublicKey, PublicKeyParams, +}; +use tw_keypair::{tw, KeyPairResult}; +use tw_memory::Data; +use tw_proto::{google, type_url}; + +const ETHERMINT_SECP256K1_PUBLIC_KEY_TYPE: &str = "ethermint/PubKeyEthSecp256k1"; + +pub struct EthermintEthSecp256PublicKey(Secp256PublicKey); + +impl EthermintEthSecp256PublicKey { + fn default_public_key_params() -> PublicKeyParams { + PublicKeyParams { + // `NativeEvmos` requires the public key to be compressed, + // however the uncompressed public key is used to generate an address. + public_key_type: tw::PublicKeyType::Secp256k1, + json_type: ETHERMINT_SECP256K1_PUBLIC_KEY_TYPE.to_string(), + protobuf_type_url: type_url::(), + } + } +} + +impl CosmosPublicKey for EthermintEthSecp256PublicKey { + fn from_bytes( + coin: &dyn CoinContext, + public_key_bytes: &[u8], + maybe_params: Option, + ) -> KeyPairResult { + // Use default Ethermint public key parameters if otherwise is not specified, + // however the uncompressed public key is used to generate an address. + let params = maybe_params.unwrap_or_else(Self::default_public_key_params); + Secp256PublicKey::from_bytes(coin, public_key_bytes, Some(params)) + .map(EthermintEthSecp256PublicKey) + } + + fn to_bytes(&self) -> Data { + self.0.to_bytes() + } +} + +impl JsonPublicKey for EthermintEthSecp256PublicKey { + fn public_key_type(&self) -> String { + self.0.public_key_type() + } +} + +impl ProtobufPublicKey for EthermintEthSecp256PublicKey { + fn to_proto(&self) -> google::protobuf::Any { + self.0.to_proto() + } +} diff --git a/rust/chains/tw_native_evmos/src/lib.rs b/rust/chains/tw_native_evmos/src/lib.rs new file mode 100644 index 00000000000..0c7068cba6f --- /dev/null +++ b/rust/chains/tw_native_evmos/src/lib.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod context; +pub mod entry; +pub mod ethermint_public_key; diff --git a/rust/chains/tw_native_injective/Cargo.toml b/rust/chains/tw_native_injective/Cargo.toml new file mode 100644 index 00000000000..b8d8a0bcb50 --- /dev/null +++ b/rust/chains/tw_native_injective/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "tw_native_injective" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_hash = { path = "../../tw_hash" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk", features = ["test-utils"] } diff --git a/rust/chains/tw_native_injective/src/context.rs b/rust/chains/tw_native_injective/src/context.rs new file mode 100644 index 00000000000..c50dff8c44a --- /dev/null +++ b/rust/chains/tw_native_injective/src/context.rs @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::injective_public_key::InjectiveEthSecp256PublicKey; +use tw_cosmos_sdk::address::Address; +use tw_cosmos_sdk::context::CosmosContext; +use tw_cosmos_sdk::private_key::secp256k1::Secp256PrivateKey; +use tw_cosmos_sdk::signature::secp256k1::Secp256k1Signature; +use tw_hash::hasher::Hasher; + +pub struct NativeInjectiveContext; + +impl CosmosContext for NativeInjectiveContext { + type Address = Address; + type PrivateKey = Secp256PrivateKey; + type PublicKey = InjectiveEthSecp256PublicKey; + type Signature = Secp256k1Signature; + + fn default_tx_hasher() -> Hasher { + Hasher::Keccak256 + } +} diff --git a/rust/chains/tw_native_injective/src/entry.rs b/rust/chains/tw_native_injective/src/entry.rs new file mode 100644 index 00000000000..bca5b9759dd --- /dev/null +++ b/rust/chains/tw_native_injective/src/entry.rs @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::context::NativeInjectiveContext; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; +use tw_coin_entry::modules::wallet_connector::NoWalletConnector; +use tw_cosmos_sdk::address::{Address, Bech32Prefix}; +use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; +use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; +use tw_keypair::tw; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct NativeInjectiveEntry; + +impl CoinEntry for NativeInjectiveEntry { + type AddressPrefix = Bech32Prefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + type WalletConnector = NoWalletConnector; + type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; + + #[inline] + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + prefix: Option, + ) -> AddressResult { + Address::from_str_with_coin_and_prefix(coin, address.to_string(), prefix) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: tw::PublicKey, + _derivation: Derivation, + prefix: Option, + ) -> AddressResult { + Address::with_public_key_coin_context(coin, &public_key, prefix) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + TWSigner::::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + TWTransactionCompiler::::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + TWTransactionCompiler::::compile( + coin, + input, + signatures, + public_keys, + ) + } +} diff --git a/rust/chains/tw_native_injective/src/injective_public_key.rs b/rust/chains/tw_native_injective/src/injective_public_key.rs new file mode 100644 index 00000000000..c405867f6e8 --- /dev/null +++ b/rust/chains/tw_native_injective/src/injective_public_key.rs @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_cosmos_sdk::proto::injective; +use tw_cosmos_sdk::public_key::secp256k1::Secp256PublicKey; +use tw_cosmos_sdk::public_key::{ + CosmosPublicKey, JsonPublicKey, ProtobufPublicKey, PublicKeyParams, +}; +use tw_keypair::KeyPairResult; +use tw_memory::Data; +use tw_proto::{google, type_url}; + +/// https://github.com/cosmostation/cosmostation-chrome-extension/blob/e2fd27d71a17993f8eef07ce30f7a04a32e52788/src/constants/cosmos.ts#L4 +const INJECTIVE_SECP256K1_PUBLIC_KEY_TYPE: &str = "injective/PubKeyEthSecp256k1"; + +pub struct InjectiveEthSecp256PublicKey(Secp256PublicKey); + +impl InjectiveEthSecp256PublicKey { + fn default_public_key_params(coin: &dyn CoinContext) -> PublicKeyParams { + PublicKeyParams { + // `NativeInjective` uses the same public key type as specified in `registry.json`. + public_key_type: coin.public_key_type(), + json_type: INJECTIVE_SECP256K1_PUBLIC_KEY_TYPE.to_string(), + protobuf_type_url: type_url::(), + } + } +} + +impl CosmosPublicKey for InjectiveEthSecp256PublicKey { + fn from_bytes( + coin: &dyn CoinContext, + public_key_bytes: &[u8], + maybe_params: Option, + ) -> KeyPairResult { + // Use default Ethermint public key parameters if otherwise is not specified, + // however the uncompressed public key is used to generate an address. + let params = maybe_params.unwrap_or_else(|| Self::default_public_key_params(coin)); + Secp256PublicKey::from_bytes(coin, public_key_bytes, Some(params)) + .map(InjectiveEthSecp256PublicKey) + } + + fn to_bytes(&self) -> Data { + self.0.to_bytes() + } +} + +impl JsonPublicKey for InjectiveEthSecp256PublicKey { + fn public_key_type(&self) -> String { + self.0.public_key_type() + } +} + +impl ProtobufPublicKey for InjectiveEthSecp256PublicKey { + fn to_proto(&self) -> google::protobuf::Any { + self.0.to_proto() + } +} diff --git a/rust/chains/tw_native_injective/src/lib.rs b/rust/chains/tw_native_injective/src/lib.rs new file mode 100644 index 00000000000..fb550eba23b --- /dev/null +++ b/rust/chains/tw_native_injective/src/lib.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod context; +pub mod entry; +pub mod injective_public_key; diff --git a/rust/chains/tw_ronin/Cargo.toml b/rust/chains/tw_ronin/Cargo.toml new file mode 100644 index 00000000000..fadcf48ebab --- /dev/null +++ b/rust/chains/tw_ronin/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "tw_ronin" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_evm = { path = "../../tw_evm" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_coin_entry = { path = "../../tw_coin_entry", features = ["test-utils"] } +tw_encoding = { path = "../../tw_encoding" } +tw_number = { path = "../../tw_number", features = ["helpers"] } diff --git a/rust/chains/tw_ronin/src/address.rs b/rust/chains/tw_ronin/src/address.rs new file mode 100644 index 00000000000..73bdaf91624 --- /dev/null +++ b/rust/chains/tw_ronin/src/address.rs @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::fmt; +use std::str::FromStr; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::*; +use tw_evm::address::{Address as EthAddress, EvmAddress}; +use tw_keypair::ecdsa::secp256k1; +use tw_memory::Data; + +/// cbindgen:ignore +const RONIN_PREFIX: &str = "ronin:"; + +#[derive(Debug)] +pub struct Address(EthAddress); + +impl Address { + /// Initializes an address with a `secp256k1` public key. + #[inline] + pub fn with_secp256k1_pubkey(pubkey: &secp256k1::PublicKey) -> Address { + Address(EthAddress::with_secp256k1_pubkey(pubkey)) + } +} + +impl EvmAddress for Address {} + +impl From

for EthAddress { + #[inline] + fn from(addr: Address) -> Self { + addr.0 + } +} + +impl FromStr for Address { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + // Accept both Ronin and standard addresses. + let standard = match s.strip_prefix(RONIN_PREFIX) { + Some(ronin_no_prefix) => format!("0x{ronin_no_prefix}"), + None => s.to_string(), + }; + EthAddress::from_str(&standard).map(Address) + } +} + +impl fmt::Display for Address { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let standard_prefixed = self.0.to_string(); + // Strip the `0x` prefix. + let standard_no_prefix = standard_prefixed + .strip_prefix("0x") + .unwrap_or(&standard_prefixed); + write!(f, "{RONIN_PREFIX}{standard_no_prefix}") + } +} + +impl CoinAddress for Address { + #[inline] + fn data(&self) -> Data { + self.0.data() + } +} diff --git a/rust/chains/tw_ronin/src/entry.rs b/rust/chains/tw_ronin/src/entry.rs new file mode 100644 index 00000000000..49a21c71c0c --- /dev/null +++ b/rust/chains/tw_ronin/src/entry.rs @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::Address; +use crate::ronin_context::RoninContext; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; +use tw_coin_entry::modules::wallet_connector::NoWalletConnector; +use tw_coin_entry::prefix::NoPrefix; +use tw_evm::evm_entry::EvmEntry; +use tw_evm::modules::compiler::Compiler; +use tw_evm::modules::message_signer::EthMessageSigner; +use tw_evm::modules::signer::Signer; +use tw_keypair::tw::PublicKey; +use tw_proto::Ethereum::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct RoninEntry; + +impl CoinEntry for RoninEntry { + type AddressPrefix = NoPrefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = EthMessageSigner; + type WalletConnector = NoWalletConnector; + type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + fn derive_address( + &self, + _coin: &dyn CoinContext, + public_key: PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + let public_key = public_key + .to_secp256k1() + .ok_or(AddressError::PublicKeyTypeMismatch)?; + Ok(Address::with_secp256k1_pubkey(public_key)) + } + + #[inline] + fn sign(&self, _coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + Signer::::sign_proto(input) + } + + #[inline] + fn preimage_hashes( + &self, + _coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + Compiler::::preimage_hashes(input) + } + + #[inline] + fn compile( + &self, + _coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + Compiler::::compile(input, signatures, public_keys) + } + + #[inline] + fn message_signer(&self) -> Option { + Some(EthMessageSigner) + } +} + +impl EvmEntry for RoninEntry { + type Context = RoninContext; +} diff --git a/rust/chains/tw_ronin/src/lib.rs b/rust/chains/tw_ronin/src/lib.rs new file mode 100644 index 00000000000..615f3566671 --- /dev/null +++ b/rust/chains/tw_ronin/src/lib.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod address; +pub mod entry; +pub mod ronin_context; diff --git a/rust/chains/tw_ronin/src/ronin_context.rs b/rust/chains/tw_ronin/src/ronin_context.rs new file mode 100644 index 00000000000..e30475a9f1f --- /dev/null +++ b/rust/chains/tw_ronin/src/ronin_context.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::Address; +use tw_evm::evm_context::EvmContext; + +#[derive(Default)] +pub struct RoninContext; + +impl EvmContext for RoninContext { + type Address = Address; +} diff --git a/rust/chains/tw_ronin/tests/address.rs b/rust/chains/tw_ronin/tests/address.rs new file mode 100644 index 00000000000..4a97ee560cc --- /dev/null +++ b/rust/chains/tw_ronin/tests/address.rs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::str::FromStr; +use tw_ronin::address::Address; + +#[test] +fn test_ronin_address_valid() { + let normalized = "ronin:EC49280228b0D05Aa8e8b756503254e1eE7835ab"; + let valid = [ + normalized, + "ronin:ec49280228b0d05aa8e8b756503254e1ee7835ab", + "0xEC49280228b0D05Aa8e8b756503254e1eE7835ab", + "ronin:0xEC49280228b0D05Aa8e8b756503254e1eE7835ab", + ]; + + for test in valid { + let addr = Address::from_str(test).unwrap(); + assert_eq!(addr.to_string(), normalized); + } +} + +#[test] +fn test_ronin_address_invalid() { + let invalid = [ + "EC49280228b0D05Aa8e8b756503254e1eE7835ab", // no prefix + "ec49280228b0d05aa8e8b756503254e1ee7835ab", // no prefix + "roni:EC49280228b0D05Aa8e8b756503254e1eE7835ab", // wrong prefix + "ronin=EC49280228b0D05Aa8e8b756503254e1eE7835ab", // wrong prefix + "0xronin:EC49280228b0D05Aa8e8b756503254e1eE7835ab", // wrong prefix + "EC49280228b0D05Aa8e8b756503254e1eE7835", // too short + "ronin:EC49280228b0D05Aa8e8b756503254e1eE7835", // too short + "ronin:ec49280228b0d05aa8e8b756503254e1ee7835", // too short + "", // empty + ]; + + for test in invalid { + Address::from_str(test).unwrap_err(); + } +} diff --git a/rust/chains/tw_ronin/tests/compiler.rs b/rust/chains/tw_ronin/tests/compiler.rs new file mode 100644 index 00000000000..18d55a3fd92 --- /dev/null +++ b/rust/chains/tw_ronin/tests/compiler.rs @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::borrow::Cow; +use tw_coin_entry::coin_entry_ext::CoinEntryExt; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::tw; +use tw_number::U256; +use tw_proto::Ethereum::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_proto::{deserialize, serialize}; +use tw_ronin::entry::RoninEntry; + +#[test] +fn test_ronin_preimage_hashes_and_compile() { + let coin = TestCoinContext::default(); + + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(1_000_000_000_000_000_000), + data: Cow::default(), + }; + let input = Proto::SigningInput { + nonce: U256::encode_be_compact(11), + chain_id: U256::encode_be_compact(1), + gas_price: U256::encode_be_compact(20_000_000_000), + gas_limit: U256::encode_be_compact(21_000), + to_address: "ronin:3535353535353535353535353535353535353535".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), + }), + ..Proto::SigningInput::default() + }; + + let input_data = serialize(&input).unwrap(); + + let res = RoninEntry + .preimage_hashes(&coin, &input_data) + .expect("!preimage_hashes"); + let preimage: CompilerProto::PreSigningOutput = + deserialize(res.as_slice()).expect("Coin entry returned an invalid output"); + + assert_eq!(preimage.error, SigningErrorType::OK); + assert!(preimage.error_message.is_empty()); + assert_eq!( + preimage.data_hash.to_hex(), + "15e180a6274b2f6a572b9b51823fce25ef39576d10188ecdcd7de44526c47217" + ); + + // Simulate signature, normally obtained from signature server + let public_key = secp256k1::PublicKey::try_from("044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad7119ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a").unwrap(); + let public_key = tw::PublicKey::Secp256k1Extended(public_key); + let signature = "360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c9796677900".decode_hex().unwrap(); + + // Verify signature (pubkey & hash & signature) + assert!(public_key.verify(&signature, &preimage.data_hash)); + + // Step 3: Compile transaction info + let output_data = RoninEntry + .compile( + &coin, + &input_data, + vec![signature], + vec![public_key.to_bytes()], + ) + .expect("!compile"); + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + let expected_encoded = "f86c0b8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07ba053bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c97966779"; + assert_eq!(output.encoded.to_hex(), expected_encoded); +} diff --git a/rust/chains/tw_ronin/tests/rlp.rs b/rust/chains/tw_ronin/tests/rlp.rs new file mode 100644 index 00000000000..d3d9548fd7b --- /dev/null +++ b/rust/chains/tw_ronin/tests/rlp.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::borrow::Cow; +use tw_coin_entry::error::prelude::*; +use tw_encoding::hex::ToHex; +use tw_evm::evm_entry::EvmEntryExt; +use tw_proto::EthereumRlp::Proto as RlpProto; +use tw_proto::{deserialize, serialize}; +use tw_ronin::entry::RoninEntry; +use RlpProto::mod_RlpItem::OneOfitem as Item; + +#[test] +fn test_rlp_encode_ronin_address() { + let ronin_addr = "ronin:6b175474e89094c44da98b954eedeac495271d0f"; + let input = RlpProto::EncodingInput { + item: Some(RlpProto::RlpItem { + item: Item::address(Cow::from(ronin_addr)), + }), + }; + let input_data = serialize(&input).unwrap(); + let output_data = RoninEntry.encode_rlp(&input_data).unwrap(); + let output: RlpProto::EncodingOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + assert_eq!( + output.encoded.to_hex(), + "946b175474e89094c44da98b954eedeac495271d0f" + ); +} diff --git a/rust/chains/tw_ronin/tests/signer.rs b/rust/chains/tw_ronin/tests/signer.rs new file mode 100644 index 00000000000..309592e9278 --- /dev/null +++ b/rust/chains/tw_ronin/tests/signer.rs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::borrow::Cow; +use tw_coin_entry::coin_entry_ext::CoinEntryExt; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::test_utils::test_context::TestCoinContext; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_number::U256; +use tw_proto::Ethereum::Proto; +use tw_proto::{deserialize, serialize}; +use tw_ronin::entry::RoninEntry; + +/// https://explorer.roninchain.com/tx/0xf13a2c4421700f8782ca73eaf16bb8baf82bcf093e23570a1ff062cdd8dbf6c3 +#[test] +fn test_ronin_signing() { + let coin = TestCoinContext::default(); + + let private = "0x4646464646464646464646464646464646464646464646464646464646464646" + .decode_hex() + .unwrap(); + + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(276_447), + data: Cow::default(), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(2020), + nonce: U256::encode_be_compact(0), + gas_price: U256::encode_be_compact(1_000_000_000), + gas_limit: U256::encode_be_compact(21_000), + to_address: "ronin:c36edf48e21cf395b206352a1819de658fd7f988".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + let input_data = serialize(&input).unwrap(); + + let output_data = RoninEntry.sign(&coin, &input_data).expect("!sign"); + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = "f86880843b9aca0082520894c36edf48e21cf395b206352a1819de658fd7f988830437df80820feca0442aa06b0d0465bfecf84b28e2ce614a32a1ccc12735dc03a5799517d6659d7aa004e1bf2efa30743f1b6d49dbec2671e9fb5ead1e7da15e352ca1df6fb86a8ba7"; + assert_eq!(output.encoded.to_hex(), expected); +} + +#[test] +fn test_sign_json() { + let coin = TestCoinContext::default(); + + let input_json = r#"{"chainId":"B+Q=","nonce":"AA==","gasPrice":"O5rKAA==","gasLimit":"Ugg=","toAddress":"ronin:c36edf48e21cf395b206352a1819de658fd7f988","privateKey":"RkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkY=","transaction":{"transfer":{"amount":"BDff"}}}"#; + let private_key = "0x4646464646464646464646464646464646464646464646464646464646464646" + .decode_hex() + .unwrap(); + + RoninEntry + .sign_json(&coin, input_json, private_key) + .expect_err("'EthEntry::sign_json' is not supported yet"); + + // Expected result - "f86880843b9aca0082520894c36edf48e21cf395b206352a1819de658fd7f988830437df80820feca0442aa06b0d0465bfecf84b28e2ce614a32a1ccc12735dc03a5799517d6659d7aa004e1bf2efa30743f1b6d49dbec2671e9fb5ead1e7da15e352ca1df6fb86a8ba7" +} diff --git a/rust/chains/tw_solana/Cargo.toml b/rust/chains/tw_solana/Cargo.toml new file mode 100644 index 00000000000..99c893b081e --- /dev/null +++ b/rust/chains/tw_solana/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tw_solana" +version = "0.1.0" +edition = "2021" + +[dependencies] +bincode = "1.3.3" +borsh = { version = "1.3.1", features = ["derive"] } +lazy_static = "1.4.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_encoding = { path = "../../tw_encoding" } +tw_hash = { path = "../../tw_hash" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } diff --git a/rust/chains/tw_solana/src/address.rs b/rust/chains/tw_solana/src/address.rs new file mode 100644 index 00000000000..cb2051be84c --- /dev/null +++ b/rust/chains/tw_solana/src/address.rs @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::SOLANA_ALPHABET; +use serde::de::Error as DeError; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt; +use std::str::FromStr; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::*; +use tw_encoding::base58; +use tw_hash::{as_byte_sequence, sha2, H256}; +use tw_keypair::{ed25519, tw}; +use tw_memory::Data; + +pub const MAX_SEEDS: usize = 16; +pub const MAX_SEED_LEN: usize = H256::LEN; +const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress"; + +#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct SolanaAddress { + bytes: H256, +} + +impl SolanaAddress { + pub fn with_public_key(public_key: &tw::PublicKey) -> AddressResult { + let bytes = public_key + .to_ed25519() + .ok_or(AddressError::PublicKeyTypeMismatch)? + .to_bytes(); + Ok(SolanaAddress { bytes }) + } + + pub fn with_public_key_ed25519(public_key: &ed25519::sha512::PublicKey) -> SolanaAddress { + SolanaAddress { + bytes: public_key.to_bytes(), + } + } + + pub fn with_public_key_bytes(bytes: H256) -> SolanaAddress { + SolanaAddress { bytes } + } + + pub fn bytes(&self) -> H256 { + self.bytes + } + + /// Find a valid [program derived address][pda] and its corresponding bump seed. + /// + /// [pda]: https://solana.com/docs/core/cpi#program-derived-addresses + /// + /// Program derived addresses (PDAs) are account keys that only the program, + /// `program_id`, has the authority to sign. The address is of the same form + /// as a Solana `Pubkey`, except they are ensured to not be on the ed25519 + /// curve and thus have no associated private key. When performing + /// cross-program invocations the program can "sign" for the key by calling + /// [`invoke_signed`] and passing the same seeds used to generate the + /// address, along with the calculated _bump seed_, which this function + /// returns as the second tuple element. The runtime will verify that the + /// program associated with this address is the caller and thus authorized + /// to be the signer. + pub fn find_program_address( + seeds: &[&[u8]], + program_id: SolanaAddress, + ) -> Option { + let mut bump_seed = [u8::MAX]; + for _ in 0..u8::MAX { + let mut seeds_with_bump = seeds.to_vec(); + seeds_with_bump.push(&bump_seed); + match Self::create_program_address(&seeds_with_bump, program_id) { + Ok(Some(address)) => return Some(address), + // Try to re-compute the program address with a different seed. + Ok(None) => (), + Err(_) => return None, + } + // Try to re-compute the program address with a different seed. + bump_seed[0] -= 1; + } + None + } + + /// Create a valid [program derived address][pda] without searching for a bump seed. + /// + /// [pda]: https://solana.com/docs/core/cpi#program-derived-addresses + /// + /// Because this function does not create a bump seed, it may unpredictably + /// return an error for any given set of seeds and is not generally suitable + /// for creating program derived addresses. + /// + /// However, it can be used for efficiently verifying that a set of seeds plus + /// bump seed generated by [`find_program_address`] derives a particular + /// address as expected. See the example for details. + /// + /// See the documentation for [`find_program_address`] for a full description + /// of program derived addresses and bump seeds. + /// + /// [`find_program_address`]: Pubkey::find_program_address + pub fn create_program_address( + seeds: &[&[u8]], + program_id: SolanaAddress, + ) -> AddressResult> { + if seeds.len() > MAX_SEEDS { + return Err(AddressError::Internal); + } + if seeds.iter().any(|seed| seed.len() > MAX_SEED_LEN) { + return Err(AddressError::Internal); + } + + let mut data_to_hash = Vec::new(); + + // concatenate seeds + for seed in seeds { + data_to_hash.extend_from_slice(seed); + } + // Append `program_id`. + data_to_hash.extend_from_slice(program_id.bytes.as_slice()); + data_to_hash.extend_from_slice(PDA_MARKER); + + let hash = H256::try_from(sha2::sha256(&data_to_hash).as_slice()) + .expect("sha256 must return 32 bytes"); + + // The given hash (aka new public key) must not be on the ed25519 elliptic curve. + match ed25519::sha512::PublicKey::try_from(hash.as_slice()) { + Ok(_) => Ok(None), + Err(_) => Ok(Some(SolanaAddress::with_public_key_bytes(hash))), + } + } +} + +impl CoinAddress for SolanaAddress { + #[inline] + fn data(&self) -> Data { + self.bytes.to_vec() + } +} + +impl FromStr for SolanaAddress { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + let bytes = + base58::decode(s, SOLANA_ALPHABET).map_err(|_| AddressError::FromBase58Error)?; + let bytes = H256::try_from(bytes.as_slice()).map_err(|_| AddressError::InvalidInput)?; + Ok(SolanaAddress { bytes }) + } +} + +impl From<&'static str> for SolanaAddress { + fn from(s: &'static str) -> Self { + SolanaAddress::from_str(s).unwrap() + } +} + +impl fmt::Debug for SolanaAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self) + } +} + +impl fmt::Display for SolanaAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let encoded = base58::encode(self.bytes.as_slice(), SOLANA_ALPHABET); + write!(f, "{}", encoded) + } +} + +impl Serialize for SolanaAddress { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if serializer.is_human_readable() { + return self.to_string().serialize(serializer); + } + as_byte_sequence::serialize(&self.bytes(), serializer) + } +} + +impl<'de> Deserialize<'de> for SolanaAddress { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + let addr_str = String::deserialize(deserializer)?; + return SolanaAddress::from_str(&addr_str) + .map_err(|e| DeError::custom(format!("{e:?}"))); + } + let bytes = as_byte_sequence::deserialize(deserializer)?; + Ok(SolanaAddress { bytes }) + } +} diff --git a/rust/chains/tw_solana/src/blockhash.rs b/rust/chains/tw_solana/src/blockhash.rs new file mode 100644 index 00000000000..1cfe0a92fa5 --- /dev/null +++ b/rust/chains/tw_solana/src/blockhash.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::SOLANA_ALPHABET; +use serde::Deserialize; +use std::fmt; +use std::str::FromStr; +use tw_encoding::base58::{self, as_base58_bitcoin}; +use tw_encoding::EncodingError; +use tw_hash::H256; + +#[derive(Clone, Copy, Default, Deserialize)] +pub struct Blockhash(#[serde(with = "as_base58_bitcoin")] H256); + +impl Blockhash { + pub fn with_bytes(bytes: H256) -> Blockhash { + Blockhash(bytes) + } + + pub fn to_bytes(&self) -> H256 { + self.0 + } +} + +impl fmt::Display for Blockhash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", base58::encode(self.0.as_slice(), SOLANA_ALPHABET)) + } +} + +impl FromStr for Blockhash { + type Err = EncodingError; + + fn from_str(s: &str) -> Result { + let bytes = base58::decode(s, SOLANA_ALPHABET)?; + let bytes = H256::try_from(bytes.as_slice()).map_err(|_| EncodingError::InvalidInput)?; + Ok(Blockhash(bytes)) + } +} diff --git a/rust/chains/tw_solana/src/compiler.rs b/rust/chains/tw_solana/src/compiler.rs new file mode 100644 index 00000000000..818f04a8ce9 --- /dev/null +++ b/rust/chains/tw_solana/src/compiler.rs @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SolanaAddress; +use crate::modules::message_builder::MessageBuilder; +use crate::modules::proto_builder::ProtoBuilder; +use crate::modules::tx_signer::TxSigner; +use crate::SOLANA_ALPHABET; +use std::borrow::Cow; +use std::collections::HashMap; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_encoding::base58; +use tw_encoding::base64::{self, STANDARD}; +use tw_keypair::ed25519; +use tw_keypair::traits::VerifyingKeyTrait; +use tw_proto::Solana::Proto; + +pub struct SolanaCompiler; + +impl SolanaCompiler { + #[inline] + pub fn preimage_hashes( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> Proto::PreSigningOutput<'static> { + Self::preimage_hashes_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(Proto::PreSigningOutput, e)) + } + + fn preimage_hashes_impl( + _coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let builder = MessageBuilder::new(input); + let unsigned_msg = builder.build()?; + let data_to_sign = TxSigner::preimage_versioned(&unsigned_msg)?; + + let signers: Vec<_> = unsigned_msg + .signers() + .map(|addr| Cow::from(addr.to_string().into_bytes())) + .collect(); + + Ok(Proto::PreSigningOutput { + signers, + data: Cow::from(data_to_sign), + ..Proto::PreSigningOutput::default() + }) + } + + #[inline] + pub fn compile( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + Self::compile_impl(coin, input, signatures, public_keys) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn compile_impl( + _coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> SigningResult> { + let encode = move |data| match input.tx_encoding { + Proto::Encoding::Base58 => base58::encode(data, SOLANA_ALPHABET), + Proto::Encoding::Base64 => base64::encode(data, STANDARD), + }; + + if signatures.len() != public_keys.len() { + return SigningError::err(SigningErrorType::Error_signatures_count) + .context("Expected the same number of signatures and public keys"); + } + + let builder = MessageBuilder::new(input); + let unsigned_msg = builder.build()?; + let data_to_sign = TxSigner::preimage_versioned(&unsigned_msg)?; + + // Verify the given signatures and collect the key-signature map. + let mut key_signs = HashMap::default(); + for (sign, pubkey) in signatures.iter().zip(public_keys.iter()) { + let signature = ed25519::Signature::try_from(sign.as_slice())?; + let pubkey = ed25519::sha512::PublicKey::try_from(pubkey.as_slice())?; + + if !pubkey.verify(signature.clone(), data_to_sign.clone()) + && !signature.to_bytes().is_zero() + { + return SigningError::err(SigningErrorType::Error_signing) + .context("Error verifying the given signature"); + } + + key_signs.insert(SolanaAddress::with_public_key_ed25519(&pubkey), signature); + } + + let signed_tx = TxSigner::compile_versioned(unsigned_msg, key_signs)?; + + let signed_encoded = bincode::serialize(&signed_tx) + .tw_err(|_| SigningErrorType::Error_internal) + .context("Error serializing signed transaction")?; + let signed_encoded = encode(&signed_encoded); + let unsigned_encoded = encode(&data_to_sign); + + Ok(Proto::SigningOutput { + encoded: Cow::from(signed_encoded), + unsigned_tx: Cow::from(unsigned_encoded), + signatures: ProtoBuilder::build_signatures(&signed_tx), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/chains/tw_solana/src/defined_addresses.rs b/rust/chains/tw_solana/src/defined_addresses.rs new file mode 100644 index 00000000000..f7e663ef1f4 --- /dev/null +++ b/rust/chains/tw_solana/src/defined_addresses.rs @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SolanaAddress; +use lazy_static::lazy_static; + +macro_rules! define { + ($name:ident = $s:literal) => { + lazy_static! { + pub static ref $name: SolanaAddress = SolanaAddress::from($s); + } + }; +} + +define!(SYSTEM_PROGRAM_ID_ADDRESS = "11111111111111111111111111111111"); +define!(STAKE_PROGRAM_ID_ADDRESS = "Stake11111111111111111111111111111111111111"); +define!(TOKEN_PROGRAM_ID_ADDRESS = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); +define!(TOKEN_2022_PROGRAM_ID_ADDRESS = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"); +define!(ASSOCIATED_TOKEN_PROGRAM_ID_ADDRESS = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); +define!(SYSVAR_RENT_ID_ADDRESS = "SysvarRent111111111111111111111111111111111"); +define!(SYSVAR_CLOCK_ID_ADDRESS = "SysvarC1ock11111111111111111111111111111111"); +define!(STAKE_CONFIG_ID_ADDRESS = "StakeConfig11111111111111111111111111111111"); +define!(NULL_ID_ADDRESS = "11111111111111111111111111111111"); +define!(SYSVAR_STAKE_HISTORY_ID_ADDRESS = "SysvarStakeHistory1111111111111111111111111"); +define!(MEMO_PROGRAM_ID_ADDRESS = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"); +define!(SYSVAR_RECENT_BLOCKHASHS_ADDRESS = "SysvarRecentB1ockHashes11111111111111111111"); +define!(COMPUTE_BUDGET_ADDRESS = "ComputeBudget111111111111111111111111111111"); diff --git a/rust/chains/tw_solana/src/entry.rs b/rust/chains/tw_solana/src/entry.rs new file mode 100644 index 00000000000..5fd330739fe --- /dev/null +++ b/rust/chains/tw_solana/src/entry.rs @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SolanaAddress; +use crate::compiler::SolanaCompiler; +use crate::modules::transaction_decoder::SolanaTransactionDecoder; +use crate::modules::transaction_util::SolanaTransactionUtil; +use crate::modules::wallet_connect::connector::SolanaWalletConnector; +use crate::signer::SolanaSigner; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::prefix::NoPrefix; +use tw_keypair::tw::PublicKey; +use tw_proto::Solana::Proto; + +pub struct SolanaEntry; + +impl CoinEntry for SolanaEntry { + type AddressPrefix = NoPrefix; + type Address = SolanaAddress; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = Proto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + type WalletConnector = SolanaWalletConnector; + type TransactionDecoder = SolanaTransactionDecoder; + type TransactionUtil = SolanaTransactionUtil; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + SolanaAddress::from_str(address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + SolanaAddress::from_str(address) + } + + #[inline] + fn derive_address( + &self, + _coin: &dyn CoinContext, + public_key: PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + SolanaAddress::with_public_key(&public_key) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + SolanaSigner::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + SolanaCompiler::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + SolanaCompiler::compile(coin, input, signatures, public_keys) + } + + #[inline] + fn wallet_connector(&self) -> Option { + Some(SolanaWalletConnector) + } + + #[inline] + fn transaction_decoder(&self) -> Option { + Some(SolanaTransactionDecoder) + } + + #[inline] + fn transaction_util(&self) -> Option { + Some(SolanaTransactionUtil) + } +} diff --git a/rust/chains/tw_solana/src/instruction.rs b/rust/chains/tw_solana/src/instruction.rs new file mode 100644 index 00000000000..fee1928163b --- /dev/null +++ b/rust/chains/tw_solana/src/instruction.rs @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SolanaAddress; +use borsh::BorshSerialize; +use serde::{Deserialize, Serialize}; +use tw_memory::Data; + +pub struct Instruction { + /// Pubkey of the program that executes this instruction. + pub program_id: SolanaAddress, + /// Metadata describing accounts that should be passed to the program. + pub accounts: Vec, + /// Opaque data passed to the program for its own interpretation. + pub data: Data, +} + +impl Instruction { + /// Create a new instruction from a value, encoded with [`bincode`]. + /// + /// [`bincode`]: https://docs.rs/bincode/latest/bincode/ + /// + /// `program_id` is the address of the program that will execute the instruction. + /// `accounts` contains a description of all accounts that may be accessed by the program. + pub fn new_with_bincode( + program_id: SolanaAddress, + data: T, + accounts: Vec, + ) -> Self { + let data = bincode::serialize(&data).expect("Error serializing bincode"); + Self { + program_id, + accounts, + data, + } + } + + /// Create a new instruction from a value, encoded with [`borsh`]. + /// + /// [`borsh`]: https://docs.rs/borsh/latest/borsh/ + /// + /// `program_id` is the address of the program that will execute the instruction. + /// `accounts` contains a description of all accounts that may be accessed by the program. + pub fn new_with_borsh( + program_id: SolanaAddress, + data: &T, + accounts: Vec, + ) -> Self { + let data = borsh::to_vec(data).expect("Error serializing borsh"); + Self { + program_id, + accounts, + data, + } + } + + pub fn new(program_id: SolanaAddress, data: Data, accounts: Vec) -> Self { + Self { + program_id, + accounts, + data, + } + } + + pub fn with_references>(mut self, references: I) -> Self { + for reference in references { + self.accounts.push(AccountMeta::readonly(reference, false)); + } + self + } +} + +#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct AccountMeta { + /// An account's public key. + pub pubkey: SolanaAddress, + /// True if an `Instruction` requires a `Transaction` signature matching `pubkey`. + pub is_signer: bool, + /// True if the account data or metadata may be mutated during program execution. + pub is_writable: bool, +} + +impl AccountMeta { + /// Construct metadata for a writable account. + pub fn new(pubkey: SolanaAddress, is_signer: bool) -> Self { + Self { + pubkey, + is_signer, + is_writable: true, + } + } + + /// Construct metadata for a read-only account. + pub fn readonly(pubkey: SolanaAddress, is_signer: bool) -> Self { + Self { + pubkey, + is_signer, + is_writable: false, + } + } +} diff --git a/rust/chains/tw_solana/src/lib.rs b/rust/chains/tw_solana/src/lib.rs new file mode 100644 index 00000000000..5bf3b779386 --- /dev/null +++ b/rust/chains/tw_solana/src/lib.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_encoding::base58::Alphabet; + +pub mod address; +pub mod blockhash; +pub mod compiler; +pub mod defined_addresses; +pub mod entry; +pub mod instruction; +pub mod modules; +pub mod program; +pub mod signer; +pub mod transaction; + +// cbindgen:ignore +pub const SOLANA_ALPHABET: Alphabet = Alphabet::Bitcoin; diff --git a/rust/chains/tw_solana/src/modules/compiled_instructions.rs b/rust/chains/tw_solana/src/modules/compiled_instructions.rs new file mode 100644 index 00000000000..28b0f30cd6b --- /dev/null +++ b/rust/chains/tw_solana/src/modules/compiled_instructions.rs @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SolanaAddress; +use crate::instruction::Instruction; +use crate::transaction::CompiledInstruction; +use tw_coin_entry::error::prelude::*; + +pub fn compile_instructions( + ixs: &[Instruction], + keys: &[SolanaAddress], +) -> SigningResult> { + ixs.iter().map(|ix| compile_instruction(ix, keys)).collect() +} + +fn position(keys: &[SolanaAddress], key: &SolanaAddress) -> SigningResult { + keys.iter() + .position(|k| k == key) + .map(|k| k as u8) + .or_tw_err(SigningErrorType::Error_internal) +} + +/// https://github.com/solana-labs/solana/blob/4b65cc8eef6ef79cb9b9cbc534a99b4900e58cf7/sdk/program/src/message/legacy.rs#L72-L84 +pub(crate) fn compile_instruction( + ix: &Instruction, + keys: &[SolanaAddress], +) -> SigningResult { + let accounts = ix + .accounts + .iter() + .map(|account_meta| position(keys, &account_meta.pubkey)) + .collect::>>() + .context("Cannot build account metas")?; + + Ok(CompiledInstruction { + program_id_index: position(keys, &ix.program_id) + .context("Program ID account is not provided")?, + data: ix.data.clone(), + accounts, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::instruction::AccountMeta; + use crate::SOLANA_ALPHABET; + use std::str::FromStr; + use tw_encoding::base58; + use tw_keypair::ed25519; + + #[test] + fn test_compile_instruction() { + let public_0 = base58::decode( + "GymAh18wHuFTytfSJWi8eYTA9x5S3sNb9CJSGBWoPRE3", + SOLANA_ALPHABET, + ) + .unwrap(); + let public_0 = ed25519::sha512::PublicKey::try_from(public_0.as_slice()).unwrap(); + let address_0 = SolanaAddress::with_public_key_ed25519(&public_0); + + let public_1 = base58::decode( + "2oKoYSAHgveX91917v4DUEuN8BNKXDg8KJWpaGyEay9V", + SOLANA_ALPHABET, + ) + .unwrap(); + let public_1 = ed25519::sha512::PublicKey::try_from(public_1.as_slice()).unwrap(); + let address_1 = SolanaAddress::with_public_key_ed25519(&public_1); + + let program_id = SolanaAddress::from_str("11111111111111111111111111111111").unwrap(); + + let addresses = vec![address_0, address_1, program_id]; + let ixs_addresses = vec![ + AccountMeta::new(address_1, false), + AccountMeta::new(address_0, false), + AccountMeta::new(program_id, false), + AccountMeta::new(address_1, false), + AccountMeta::new(address_0, false), + ]; + let data = vec![0_u8, 1, 2, 4]; + let instruction = Instruction::new(program_id, data.clone(), ixs_addresses); + + let compiled_ix = compile_instruction(&instruction, &addresses).unwrap(); + let expected = CompiledInstruction { + program_id_index: 2, + accounts: vec![1, 0, 2, 1, 0], + data, + }; + assert_eq!(compiled_ix, expected); + } +} diff --git a/rust/chains/tw_solana/src/modules/compiled_keys.rs b/rust/chains/tw_solana/src/modules/compiled_keys.rs new file mode 100644 index 00000000000..e138c1d9b29 --- /dev/null +++ b/rust/chains/tw_solana/src/modules/compiled_keys.rs @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +//! Original source code: https://github.com/solana-labs/solana/blob/4b65cc8eef6ef79cb9b9cbc534a99b4900e58cf7/sdk/program/src/message/compiled_keys.rs + +use crate::address::SolanaAddress; +use crate::instruction::Instruction; +use crate::transaction::MessageHeader; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use tw_coin_entry::error::prelude::*; + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +struct CompiledKeyMeta { + is_signer: bool, + is_writable: bool, +} + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub(crate) struct CompiledKeys { + ordered_keys: Vec, + key_meta_map: HashMap, +} + +impl CompiledKeys { + pub fn with_fee_payer(fee_payer: SolanaAddress) -> Self { + let mut selfi = Self::default(); + + selfi.key_meta_map.insert( + fee_payer, + CompiledKeyMeta { + is_signer: true, + is_writable: true, + }, + ); + // Fee payer must be the first account in the keys list. + selfi.ordered_keys.push(fee_payer); + + selfi + } + + pub fn compile(mut self, instructions: &[Instruction]) -> Self { + for ix in instructions { + for account_meta in &ix.accounts { + let meta_entry = self.key_meta_map.entry(account_meta.pubkey); + if matches!(meta_entry, Entry::Vacant(_)) { + self.ordered_keys.push(account_meta.pubkey); + } + + let meta = meta_entry.or_default(); + meta.is_signer |= account_meta.is_signer; + meta.is_writable |= account_meta.is_writable; + } + } + + // add programIds (read-only, at end) + for ix in instructions { + let meta_entry = self.key_meta_map.entry(ix.program_id); + if matches!(meta_entry, Entry::Vacant(_)) { + self.ordered_keys.push(ix.program_id); + } + meta_entry.or_default(); + } + + self + } + + pub fn try_into_message_components(self) -> SigningResult<(MessageHeader, Vec)> { + let try_into_u8 = |num: usize| -> SigningResult { + u8::try_from(num).tw_err(|_| SigningErrorType::Error_tx_too_big) + }; + + let Self { + ordered_keys, + key_meta_map, + } = self; + + let filter = |account, is_signer: bool, is_writable: bool| -> Option { + let meta = key_meta_map.get(account).copied().unwrap_or_default(); + (meta.is_signer == is_signer && meta.is_writable == is_writable).then_some(*account) + }; + + let writable_signer_keys: Vec = ordered_keys + .iter() + .filter_map(|key| filter(key, true, true)) + .collect(); + let readonly_signer_keys: Vec = ordered_keys + .iter() + .filter_map(|key| filter(key, true, false)) + .collect(); + let writable_non_signer_keys: Vec = ordered_keys + .iter() + .filter_map(|key| filter(key, false, true)) + .collect(); + let readonly_non_signer_keys: Vec = ordered_keys + .iter() + .filter_map(|key| filter(key, false, false)) + .collect(); + + let signers_len = writable_signer_keys + .len() + .saturating_add(readonly_signer_keys.len()); + + let header = MessageHeader { + num_required_signatures: try_into_u8(signers_len) + .context("Too many signatures required")?, + num_readonly_signed_accounts: try_into_u8(readonly_signer_keys.len()) + .context("Too many accounts in the transaction")?, + num_readonly_unsigned_accounts: try_into_u8(readonly_non_signer_keys.len()) + .context("Too many accounts in the transaction")?, + }; + + let static_account_keys: Vec<_> = std::iter::empty() + .chain(writable_signer_keys) + .chain(readonly_signer_keys) + .chain(writable_non_signer_keys) + .chain(readonly_non_signer_keys) + .collect(); + + Ok((header, static_account_keys)) + } +} diff --git a/rust/chains/tw_solana/src/modules/instruction_builder/compute_budget_instruction.rs b/rust/chains/tw_solana/src/modules/instruction_builder/compute_budget_instruction.rs new file mode 100644 index 00000000000..a95218e4365 --- /dev/null +++ b/rust/chains/tw_solana/src/modules/instruction_builder/compute_budget_instruction.rs @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::defined_addresses::COMPUTE_BUDGET_ADDRESS; +use crate::instruction::Instruction; +use borsh::{BorshDeserialize, BorshSerialize}; + +pub type UnitLimit = u32; +pub type UnitPrice = u64; + +#[derive(BorshDeserialize, BorshSerialize)] +pub enum ComputeBudgetInstruction { + Unused, + RequestHeapFrame(u32), + SetComputeUnitLimit(UnitLimit), + SetComputeUnitPrice(UnitPrice), + SetLoadedAccountsDataSizeLimit(u32), +} + +pub struct ComputeBudgetInstructionBuilder; + +impl ComputeBudgetInstructionBuilder { + /// Create a `ComputeBudgetInstruction::SetComputeUnitLimit` `Instruction` + pub fn set_compute_unit_limit(units: UnitLimit) -> Instruction { + let account_metas = vec![]; + Instruction::new_with_borsh( + *COMPUTE_BUDGET_ADDRESS, + &ComputeBudgetInstruction::SetComputeUnitLimit(units), + account_metas, + ) + } + + /// Create a `ComputeBudgetInstruction::SetComputeUnitPrice` `Instruction` + pub fn set_compute_unit_price(micro_lamports: UnitPrice) -> Instruction { + let account_metas = vec![]; + Instruction::new_with_borsh( + *COMPUTE_BUDGET_ADDRESS, + &ComputeBudgetInstruction::SetComputeUnitPrice(micro_lamports), + account_metas, + ) + } +} diff --git a/rust/chains/tw_solana/src/modules/instruction_builder/mod.rs b/rust/chains/tw_solana/src/modules/instruction_builder/mod.rs new file mode 100644 index 00000000000..9a4da04d11d --- /dev/null +++ b/rust/chains/tw_solana/src/modules/instruction_builder/mod.rs @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SolanaAddress; +use crate::instruction::Instruction; + +pub mod compute_budget_instruction; +pub mod stake_instruction; +pub mod system_instruction; +pub mod token_instruction; + +use compute_budget_instruction::ComputeBudgetInstructionBuilder; +use system_instruction::SystemInstructionBuilder; + +#[derive(Default)] +pub struct InstructionBuilder { + instructions: Vec, +} + +impl InstructionBuilder { + /// Adds an `advance_nonce` instruction if the nonce account provided. + pub fn maybe_advance_nonce( + &mut self, + nonce_account: Option, + authorized_account: SolanaAddress, + ) -> &mut Self { + if let Some(nonce_account) = nonce_account { + self.instructions + .push(SystemInstructionBuilder::advance_nonce_account( + nonce_account, + authorized_account, + )); + } + self + } + + /// Adds a `memo` instruction if it is not empty. + pub fn maybe_memo(&mut self, memo: &str) -> &mut Self { + if !memo.is_empty() { + self.instructions.push(SystemInstructionBuilder::memo(memo)); + } + self + } + + /// Adds a `priority_fee` instruction if it is provided. + pub fn maybe_priority_fee_price(&mut self, micro_lamports: Option) -> &mut Self { + if let Some(micro_lamports) = micro_lamports { + let ix = ComputeBudgetInstructionBuilder::set_compute_unit_price(micro_lamports); + self.instructions.push(ix); + } + self + } + + /// Adds a `fee_limit` instruction if it is provided. + pub fn maybe_priority_fee_limit(&mut self, units: Option) -> &mut Self { + if let Some(units) = units { + let ix = ComputeBudgetInstructionBuilder::set_compute_unit_limit(units); + self.instructions.push(ix); + } + self + } + + pub fn add_instruction(&mut self, instruction: Instruction) -> &mut Self { + self.instructions.push(instruction); + self + } + + pub fn add_instructions(&mut self, instructions: I) -> &mut Self + where + I: IntoIterator, + { + self.instructions.extend(instructions); + self + } + + pub fn output(self) -> Vec { + self.instructions + } +} diff --git a/rust/chains/tw_solana/src/modules/instruction_builder/stake_instruction.rs b/rust/chains/tw_solana/src/modules/instruction_builder/stake_instruction.rs new file mode 100644 index 00000000000..ad02414ec72 --- /dev/null +++ b/rust/chains/tw_solana/src/modules/instruction_builder/stake_instruction.rs @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SolanaAddress; +use crate::blockhash::Blockhash; +use crate::defined_addresses::*; +use crate::instruction::{AccountMeta, Instruction}; +use crate::modules::instruction_builder::system_instruction::SystemInstructionBuilder; +use crate::program::stake_program::StakeProgram; +use serde::{Deserialize, Serialize}; + +type UnixTimestamp = i64; +type Epoch = u64; + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct Authorized { + pub staker: SolanaAddress, + pub withdrawer: SolanaAddress, +} + +#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq, Clone)] +pub struct Lockup { + /// UnixTimestamp at which this stake will allow withdrawal, unless the + /// transaction is signed by the custodian + pub unix_timestamp: UnixTimestamp, + /// epoch height at which this stake will allow withdrawal, unless the + /// transaction is signed by the custodian + pub epoch: Epoch, + /// custodian signature on a transaction exempts the operation from + /// lockup constraints + pub custodian: SolanaAddress, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub enum StakeAuthorize { + Staker, + Withdrawer, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct LockupArgs { + pub unix_timestamp: Option, + pub epoch: Option, + pub custodian: Option, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub enum StakeInstruction { + /// Initialize a stake with lockup and authorization information + /// + /// # Account references + /// 0. `[WRITE]` Uninitialized stake account + /// 1. `[]` Rent sysvar + /// + /// Authorized carries pubkeys that must sign staker transactions + /// and withdrawer transactions. + /// Lockup carries information about withdrawal restrictions + Initialize(Authorized, Lockup), + + /// Authorize a key to manage stake or withdrawal + /// + /// # Account references + /// 0. `[WRITE]` Stake account to be updated + /// 1. `[]` Clock sysvar + /// 2. `[SIGNER]` The stake or withdraw authority + /// 3. Optional: `[SIGNER]` Lockup authority, if updating StakeAuthorize::Withdrawer before + /// lockup expiration + Authorize(SolanaAddress, StakeAuthorize), + + /// Delegate a stake to a particular vote account + /// + /// # Account references + /// 0. `[WRITE]` Initialized stake account to be delegated + /// 1. `[]` Vote account to which this stake will be delegated + /// 2. `[]` Clock sysvar + /// 3. `[]` Stake history sysvar that carries stake warmup/cooldown history + /// 4. `[]` Address of config account that carries stake config + /// 5. `[SIGNER]` Stake authority + /// + /// The entire balance of the staking account is staked. DelegateStake + /// can be called multiple times, but re-delegation is delayed + /// by one epoch + DelegateStake, + + /// Split u64 tokens and stake off a stake account into another stake account. + /// + /// # Account references + /// 0. `[WRITE]` Stake account to be split; must be in the Initialized or Stake state + /// 1. `[WRITE]` Uninitialized stake account that will take the split-off amount + /// 2. `[SIGNER]` Stake authority + Split(u64), + + /// Withdraw unstaked lamports from the stake account + /// + /// # Account references + /// 0. `[WRITE]` Stake account from which to withdraw + /// 1. `[WRITE]` Recipient account + /// 2. `[]` Clock sysvar + /// 3. `[]` Stake history sysvar that carries stake warmup/cooldown history + /// 4. `[SIGNER]` Withdraw authority + /// 5. Optional: `[SIGNER]` Lockup authority, if before lockup expiration + /// + /// The u64 is the portion of the stake account balance to be withdrawn, + /// must be `<= StakeAccount.lamports - staked_lamports`. + Withdraw(u64), + + /// Deactivates the stake in the account + /// + /// # Account references + /// 0. `[WRITE]` Delegated stake account + /// 1. `[]` Clock sysvar + /// 2. `[SIGNER]` Stake authority + Deactivate, + + /// Set stake lockup + /// + /// If a lockup is not active, the withdraw authority may set a new lockup + /// If a lockup is active, the lockup custodian may update the lockup parameters + /// + /// # Account references + /// 0. `[WRITE]` Initialized stake account + /// 1. `[SIGNER]` Lockup authority or withdraw authority + SetLockup { + unix_timestamp: Option, + epoch: Option, + custodian: Option, + }, + + /// Merge two stake accounts. + /// + /// Both accounts must have identical lockup and authority keys. A merge + /// is possible between two stakes in the following states with no additional + /// conditions: + /// + /// * two deactivated stakes + /// * an inactive stake into an activating stake during its activation epoch + /// + /// For the following cases, the voter pubkey and vote credits observed must match: + /// + /// * two activated stakes + /// * two activating accounts that share an activation epoch, during the activation epoch + /// + /// All other combinations of stake states will fail to merge, including all + /// "transient" states, where a stake is activating or deactivating with a + /// non-zero effective stake. + /// + /// # Account references + /// 0. `[WRITE]` Destination stake account for the merge + /// 1. `[WRITE]` Source stake account for to merge. This account will be drained + /// 2. `[]` Clock sysvar + /// 3. `[]` Stake history sysvar that carries stake warmup/cooldown history + /// 4. `[SIGNER]` Stake authority + Merge, + + /// Authorize a key to manage stake or withdrawal with a derived key + /// + /// # Account references + /// 0. `[WRITE]` Stake account to be updated + /// 1. `[SIGNER]` Base key of stake or withdraw authority + /// 2. `[]` Clock sysvar + /// 3. Optional: `[SIGNER]` Lockup authority, if updating StakeAuthorize::Withdrawer before + /// lockup expiration + AuthorizeWithSeed { + new_authorized_pubkey: SolanaAddress, + stake_authorize: StakeAuthorize, + authority_seed: String, + authority_owner: SolanaAddress, + }, + + /// Initialize a stake with authorization information + /// + /// This instruction is similar to `Initialize` except that the withdraw authority + /// must be a signer, and no lockup is applied to the account. + /// + /// # Account references + /// 0. `[WRITE]` Uninitialized stake account + /// 1. `[]` Rent sysvar + /// 2. `[]` The stake authority + /// 3. `[SIGNER]` The withdraw authority + /// + InitializeChecked, + + /// Authorize a key to manage stake or withdrawal + /// + /// This instruction behaves like `Authorize` with the additional requirement that the new + /// stake or withdraw authority must also be a signer. + /// + /// # Account references + /// 0. `[WRITE]` Stake account to be updated + /// 1. `[]` Clock sysvar + /// 2. `[SIGNER]` The stake or withdraw authority + /// 3. `[SIGNER]` The new stake or withdraw authority + /// 4. Optional: `[SIGNER]` Lockup authority, if updating StakeAuthorize::Withdrawer before + /// lockup expiration + AuthorizeChecked(StakeAuthorize), + + /// Authorize a key to manage stake or withdrawal with a derived key + /// + /// This instruction behaves like `AuthorizeWithSeed` with the additional requirement that + /// the new stake or withdraw authority must also be a signer. + /// + /// # Account references + /// 0. `[WRITE]` Stake account to be updated + /// 1. `[SIGNER]` Base key of stake or withdraw authority + /// 2. `[]` Clock sysvar + /// 3. `[SIGNER]` The new stake or withdraw authority + /// 4. Optional: `[SIGNER]` Lockup authority, if updating StakeAuthorize::Withdrawer before + /// lockup expiration + AuthorizeCheckedWithSeed { + stake_authorize: StakeAuthorize, + authority_seed: String, + authority_owner: SolanaAddress, + }, + + /// Set stake lockup + /// + /// This instruction behaves like `SetLockup` with the additional requirement that + /// the new lockup authority also be a signer. + /// + /// If a lockup is not active, the withdraw authority may set a new lockup + /// If a lockup is active, the lockup custodian may update the lockup parameters + /// + /// # Account references + /// 0. `[WRITE]` Initialized stake account + /// 1. `[SIGNER]` Lockup authority or withdraw authority + /// 2. Optional: `[SIGNER]` New lockup authority + SetLockupChecked { + unix_timestamp: Option, + epoch: Option, + }, + + /// Get the minimum stake delegation, in lamports + /// + /// # Account references + /// None + /// + /// Returns the minimum delegation as a little-endian encoded u64 value. + /// Programs can use the [`get_minimum_delegation()`] helper function to invoke and + /// retrieve the return value for this instruction. + /// + /// [`get_minimum_delegation()`]: super::tools::get_minimum_delegation + GetMinimumDelegation, + + /// Deactivate stake delegated to a vote account that has been delinquent for at least + /// `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION` epochs. + /// + /// No signer is required for this instruction as it is a common good to deactivate abandoned + /// stake. + /// + /// # Account references + /// 0. `[WRITE]` Delegated stake account + /// 1. `[]` Delinquent vote account for the delegated stake account + /// 2. `[]` Reference vote account that has voted at least once in the last + /// `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION` epochs + DeactivateDelinquent, + + /// Redelegate activated stake to another vote account. + /// + /// Upon success: + /// * the balance of the delegated stake account will be reduced to the undelegated amount in + /// the account (rent exempt minimum and any additional lamports not part of the delegation), + /// and scheduled for deactivation. + /// * the provided uninitialized stake account will receive the original balance of the + /// delegated stake account, minus the rent exempt minimum, and scheduled for activation to + /// the provided vote account. Any existing lamports in the uninitialized stake account + /// will also be included in the re-delegation. + /// + /// # Account references + /// 0. `[WRITE]` Delegated stake account to be redelegated. The account must be fully + /// activated and carry a balance greater than or equal to the minimum delegation amount + /// plus rent exempt minimum + /// 1. `[WRITE]` Uninitialized stake account that will hold the redelegated stake + /// 2. `[]` Vote account to which this stake will be re-delegated + /// 3. `[]` Address of config account that carries stake config + /// 4. `[SIGNER]` Stake authority + /// + Redelegate, +} + +pub struct DepositStakeArgs { + pub sender: SolanaAddress, + pub validator: SolanaAddress, + pub stake_account: Option, + pub recent_blockhash: Blockhash, + pub lamports: u64, + pub space: u64, +} + +pub struct StakeInstructionBuilder; + +impl StakeInstructionBuilder { + /// Creates an Initialize Stake instruction. + pub fn stake_initialize( + stake_pubkey: SolanaAddress, + authorized: Authorized, + lockup: Lockup, + ) -> Instruction { + Instruction::new_with_bincode( + *STAKE_PROGRAM_ID_ADDRESS, + StakeInstruction::Initialize(authorized, lockup), + vec![ + AccountMeta::new(stake_pubkey, false), + AccountMeta::readonly(*SYSVAR_RENT_ID_ADDRESS, false), + ], + ) + } + + pub fn withdraw( + stake_pubkey: SolanaAddress, + withdrawer_pubkey: SolanaAddress, + to_pubkey: SolanaAddress, + lamports: u64, + custodian_pubkey: Option, + ) -> Instruction { + let mut account_metas = vec![ + AccountMeta::new(stake_pubkey, false), + AccountMeta::new(to_pubkey, false), + AccountMeta::readonly(*SYSVAR_CLOCK_ID_ADDRESS, false), + AccountMeta::readonly(*SYSVAR_STAKE_HISTORY_ID_ADDRESS, false), + AccountMeta::readonly(withdrawer_pubkey, true), + ]; + + if let Some(custodian_pubkey) = custodian_pubkey { + account_metas.push(AccountMeta::readonly(custodian_pubkey, true)); + } + + Instruction::new_with_bincode( + *STAKE_PROGRAM_ID_ADDRESS, + StakeInstruction::Withdraw(lamports), + account_metas, + ) + } + + pub fn delegate( + stake_pubkey: SolanaAddress, + vote_pubkey: SolanaAddress, + authorized_pubkey: SolanaAddress, + ) -> Instruction { + let account_metas = vec![ + AccountMeta::new(stake_pubkey, false), + AccountMeta::readonly(vote_pubkey, false), + AccountMeta::readonly(*SYSVAR_CLOCK_ID_ADDRESS, false), + AccountMeta::readonly(*SYSVAR_STAKE_HISTORY_ID_ADDRESS, false), + AccountMeta::readonly(*STAKE_CONFIG_ID_ADDRESS, false), + AccountMeta::readonly(authorized_pubkey, true), + ]; + Instruction::new_with_bincode( + *STAKE_PROGRAM_ID_ADDRESS, + StakeInstruction::DelegateStake, + account_metas, + ) + } + + /// 0. `[WRITE]` Delegated stake account + /// 1. `[]` Clock sysvar + /// 2. `[SIGNER]` Stake authority + pub fn deactivate( + stake_pubkey: SolanaAddress, + authorized_pubkey: SolanaAddress, + ) -> Instruction { + let account_metas = vec![ + AccountMeta::new(stake_pubkey, false), + AccountMeta::readonly(*SYSVAR_CLOCK_ID_ADDRESS, false), + AccountMeta::new(authorized_pubkey, true), + ]; + Instruction::new_with_bincode( + *STAKE_PROGRAM_ID_ADDRESS, + StakeInstruction::Deactivate, + account_metas, + ) + } + + /// The function represents "stake delegation" operation that consists of several small instructions. + pub fn deposit_stake(args: DepositStakeArgs) -> Vec { + let stake_addr = args.stake_account.unwrap_or_else(|| { + // no stake address specified, generate a new unique + StakeProgram::address_from_recent_blockhash(&args.sender, &args.recent_blockhash) + }); + let seed = StakeProgram::recent_blockhash_as_seed(&args.recent_blockhash); + + let authorized = Authorized { + staker: args.sender, + withdrawer: args.sender, + }; + let lockup = Lockup::default(); + + vec![ + SystemInstructionBuilder::create_account_with_seed( + args.sender, + stake_addr, + args.sender, + seed, + args.lamports, + args.space, + ), + StakeInstructionBuilder::stake_initialize(stake_addr, authorized, lockup), + StakeInstructionBuilder::delegate(stake_addr, args.validator, args.sender), + ] + } +} diff --git a/rust/chains/tw_solana/src/modules/instruction_builder/system_instruction.rs b/rust/chains/tw_solana/src/modules/instruction_builder/system_instruction.rs new file mode 100644 index 00000000000..ee7e0ab3f59 --- /dev/null +++ b/rust/chains/tw_solana/src/modules/instruction_builder/system_instruction.rs @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SolanaAddress; +use crate::defined_addresses::*; +use crate::instruction::{AccountMeta, Instruction}; +use serde::{Deserialize, Serialize}; + +/// An instruction to the system program. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub enum SystemInstruction { + /// Create a new account + /// + /// # Account references + /// 0. `[WRITE, SIGNER]` Funding account + /// 1. `[WRITE, SIGNER]` New account + CreateAccount { + /// Number of lamports to transfer to the new account + lamports: u64, + + /// Number of bytes of memory to allocate + space: u64, + + /// Address of program that will own the new account + owner: SolanaAddress, + }, + + /// Assign account to a program + /// + /// # Account references + /// 0. `[WRITE, SIGNER]` Assigned account public key + Assign { + /// Owner program account + owner: SolanaAddress, + }, + + /// Transfer lamports + /// + /// # Account references + /// 0. `[WRITE, SIGNER]` Funding account + /// 1. `[WRITE]` Recipient account + Transfer { lamports: u64 }, + + /// Create a new account at an address derived from a base pubkey and a seed + /// + /// # Account references + /// 0. `[WRITE, SIGNER]` Funding account + /// 1. `[WRITE]` Created account + /// 2. `[SIGNER]` (optional) Base account; the account matching the base SolanaAddress below must be + /// provided as a signer, but may be the same as the funding account + /// and provided as account 0 + CreateAccountWithSeed { + /// Base public key + base: SolanaAddress, + + /// String of ASCII chars, no longer than `SolanaAddress::MAX_SEED_LEN` + seed: String, + + /// Number of lamports to transfer to the new account + lamports: u64, + + /// Number of bytes of memory to allocate + space: u64, + + /// Owner program account address + owner: SolanaAddress, + }, + + /// Consumes a stored nonce, replacing it with a successor + /// + /// # Account references + /// 0. `[WRITE]` Nonce account + /// 1. `[]` RecentBlockhashes sysvar + /// 2. `[SIGNER]` Nonce authority + AdvanceNonceAccount, + + /// Withdraw funds from a nonce account + /// + /// # Account references + /// 0. `[WRITE]` Nonce account + /// 1. `[WRITE]` Recipient account + /// 2. `[]` RecentBlockhashes sysvar + /// 3. `[]` Rent sysvar + /// 4. `[SIGNER]` Nonce authority + /// + /// The `u64` parameter is the lamports to withdraw, which must leave the + /// account balance above the rent exempt reserve or at zero. + WithdrawNonceAccount(u64), + + /// Drive state of Uninitialized nonce account to Initialized, setting the nonce value + /// + /// # Account references + /// 0. `[WRITE]` Nonce account + /// 1. `[]` RecentBlockhashes sysvar + /// 2. `[]` Rent sysvar + /// + /// The `SolanaAddress` parameter specifies the entity authorized to execute nonce + /// instruction on the account + /// + /// No signatures are required to execute this instruction, enabling derived + /// nonce account addresses + InitializeNonceAccount(SolanaAddress), + + /// Change the entity authorized to execute nonce instructions on the account + /// + /// # Account references + /// 0. `[WRITE]` Nonce account + /// 1. `[SIGNER]` Nonce authority + /// + /// The `SolanaAddress` parameter identifies the entity to authorize + AuthorizeNonceAccount(SolanaAddress), + + /// Allocate space in a (possibly new) account without funding + /// + /// # Account references + /// 0. `[WRITE, SIGNER]` New account + Allocate { + /// Number of bytes of memory to allocate + space: u64, + }, + + /// Allocate space for and assign an account at an address + /// derived from a base public key and a seed + /// + /// # Account references + /// 0. `[WRITE]` Allocated account + /// 1. `[SIGNER]` Base account + AllocateWithSeed { + /// Base public key + base: SolanaAddress, + + /// String of ASCII chars, no longer than `pubkey::MAX_SEED_LEN` + seed: String, + + /// Number of bytes of memory to allocate + space: u64, + + /// Owner program account + owner: SolanaAddress, + }, + + /// Assign account to a program based on a seed + /// + /// # Account references + /// 0. `[WRITE]` Assigned account + /// 1. `[SIGNER]` Base account + AssignWithSeed { + /// Base public key + base: SolanaAddress, + + /// String of ASCII chars, no longer than `pubkey::MAX_SEED_LEN` + seed: String, + + /// Owner program account + owner: SolanaAddress, + }, + + /// Transfer lamports from a derived address + /// + /// # Account references + /// 0. `[WRITE]` Funding account + /// 1. `[SIGNER]` Base for funding account + /// 2. `[WRITE]` Recipient account + TransferWithSeed { + /// Amount to transfer + lamports: u64, + + /// Seed to use to derive the funding account address + from_seed: String, + + /// Owner to use to derive the funding account address + from_owner: SolanaAddress, + }, + + /// One-time idempotent upgrade of legacy nonce versions in order to bump + /// them out of chain blockhash domain. + /// + /// # Account references + /// 0. `[WRITE]` Nonce account + UpgradeNonceAccount, +} + +pub struct SystemInstructionBuilder; + +impl SystemInstructionBuilder { + pub fn memo(memo: &str) -> Instruction { + let account_metas = Vec::default(); + let data = memo.as_bytes().to_vec(); + Instruction::new(*MEMO_PROGRAM_ID_ADDRESS, data, account_metas) + } + + pub fn create_account( + from_pubkey: SolanaAddress, + to_pubkey: SolanaAddress, + lamports: u64, + space: u64, + owner: SolanaAddress, + ) -> Instruction { + let account_metas = vec![ + AccountMeta::new(from_pubkey, true), + AccountMeta::new(to_pubkey, true), + ]; + Instruction::new_with_bincode( + *SYSTEM_PROGRAM_ID_ADDRESS, + SystemInstruction::CreateAccount { + lamports, + space, + owner, + }, + account_metas, + ) + } + + pub fn transfer( + from_pubkey: SolanaAddress, + to_pubkey: SolanaAddress, + lamports: u64, + ) -> Instruction { + let account_metas = vec![ + AccountMeta::new(from_pubkey, true), + AccountMeta::new(to_pubkey, false), + ]; + Instruction::new_with_bincode( + *SYSTEM_PROGRAM_ID_ADDRESS, + SystemInstruction::Transfer { lamports }, + account_metas, + ) + } + + /// Please note `to_pubkey` must match `create_with_seed(base, seed, owner)`. + pub fn create_account_with_seed( + from_pubkey: SolanaAddress, + to_pubkey: SolanaAddress, + base: SolanaAddress, + seed: String, + lamports: u64, + space: u64, + ) -> Instruction { + let account_metas = vec![ + AccountMeta::new(from_pubkey, true), + AccountMeta::new(to_pubkey, false), + AccountMeta::readonly(base, true), + ]; + + Instruction::new_with_bincode( + *SYSTEM_PROGRAM_ID_ADDRESS, + SystemInstruction::CreateAccountWithSeed { + base, + seed, + lamports, + space, + owner: *STAKE_PROGRAM_ID_ADDRESS, + }, + account_metas, + ) + } + + /// Advance the value of a durable transaction nonce. + /// + /// # Required signers + /// + /// The `authorized_pubkey` signer must sign the transaction. + pub fn advance_nonce_account( + nonce_pubkey: SolanaAddress, + authorized_pubkey: SolanaAddress, + ) -> Instruction { + let account_metas = vec![ + AccountMeta::new(nonce_pubkey, false), + AccountMeta::readonly(*SYSVAR_RECENT_BLOCKHASHS_ADDRESS, false), + AccountMeta::readonly(authorized_pubkey, true), + ]; + Instruction::new_with_bincode( + *SYSTEM_PROGRAM_ID_ADDRESS, + SystemInstruction::AdvanceNonceAccount, + account_metas, + ) + } + + /// Create an account containing a durable transaction nonce. + pub fn init_nonce_account( + nonce_pubkey: SolanaAddress, + authority: SolanaAddress, + ) -> Instruction { + let account_metas = vec![ + AccountMeta::new(nonce_pubkey, false), + AccountMeta::readonly(*SYSVAR_RECENT_BLOCKHASHS_ADDRESS, false), + AccountMeta::readonly(*SYSVAR_RENT_ID_ADDRESS, false), + ]; + Instruction::new_with_bincode( + *SYSTEM_PROGRAM_ID_ADDRESS, + SystemInstruction::InitializeNonceAccount(authority), + account_metas, + ) + } + + /// Withdraw lamports from a durable transaction nonce account. + pub fn withdraw_nonce_account( + nonce_pubkey: SolanaAddress, + authorized_pubkey: SolanaAddress, + to_pubkey: SolanaAddress, + lamports: u64, + ) -> Instruction { + let account_metas = vec![ + AccountMeta::new(nonce_pubkey, false), + AccountMeta::new(to_pubkey, false), + AccountMeta::readonly(*SYSVAR_RECENT_BLOCKHASHS_ADDRESS, false), + AccountMeta::readonly(*SYSVAR_RENT_ID_ADDRESS, false), + AccountMeta::readonly(authorized_pubkey, true), + ]; + Instruction::new_with_bincode( + *SYSTEM_PROGRAM_ID_ADDRESS, + SystemInstruction::WithdrawNonceAccount(lamports), + account_metas, + ) + } + + /// The function represents "create nonce account" operation that consists of several small instructions. + pub fn create_nonce_account( + signer: SolanaAddress, + new_nonce_account: SolanaAddress, + rent: u64, + space: u64, + ) -> Vec { + let program_id = *SYSTEM_PROGRAM_ID_ADDRESS; + vec![ + SystemInstructionBuilder::create_account( + signer, + new_nonce_account, + rent, + space, + program_id, + ), + SystemInstructionBuilder::init_nonce_account(new_nonce_account, signer), + ] + } +} diff --git a/rust/chains/tw_solana/src/modules/instruction_builder/token_instruction.rs b/rust/chains/tw_solana/src/modules/instruction_builder/token_instruction.rs new file mode 100644 index 00000000000..cc5a47957e3 --- /dev/null +++ b/rust/chains/tw_solana/src/modules/instruction_builder/token_instruction.rs @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SolanaAddress; +use crate::defined_addresses::*; +use crate::instruction::{AccountMeta, Instruction}; +use std::mem::size_of; +use tw_memory::Data; + +/// Instructions supported by the token program. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TokenInstruction { + /// Transfers tokens from one account to another either directly or via a + /// delegate. If this account is associated with the native mint then equal + /// amounts of SOL and Tokens will be transferred to the destination + /// account. + /// + /// This instruction differs from Transfer in that the token mint and + /// decimals value is checked by the caller. This may be useful when + /// creating transactions offline or within a hardware wallet. + /// + /// Accounts expected by this instruction: + /// + /// * Single owner/delegate + /// 0. `[writable]` The source account. + /// 1. `[]` The token mint. + /// 2. `[writable]` The destination account. + /// 3. `[signer]` The source account's owner/delegate. + /// + /// * Multisignature owner/delegate + /// 0. `[writable]` The source account. + /// 1. `[]` The token mint. + /// 2. `[writable]` The destination account. + /// 3. `[]` The source account's multisignature owner/delegate. + /// 4. ..4+M `[signer]` M signer accounts. + TransferChecked { + /// The amount of tokens to transfer. + amount: u64, + /// Expected number of base 10 digits to the right of the decimal place. + decimals: u8, + }, +} + +impl TokenInstruction { + /// Packs a [TokenInstruction](enum.TokenInstruction.html) into a byte + /// buffer. + /// + /// # Important + /// + /// This serialization method differs from [`bincode::serialize`]: + /// It needs to serialize enum `type` as u8 (1 byte), but [`bincode::serialize`] serializes the type as u32 (4 bytes). + pub fn pack(&self) -> Vec { + let mut buf = Vec::with_capacity(size_of::()); + match self { + &Self::TransferChecked { amount, decimals } => { + // https://github.com/solana-labs/solana-program-library/blob/5418cf9b90d5c9ff5bff9f55fd17651f66c98902/token/program-2022/src/instruction.rs#L334-L339 + // https://github.com/trustwallet/wallet-core/blob/cd5a27481d2181e63362cb57e2b2160506cce163/src/Solana/Instruction.h#L37 + buf.push(12); + buf.extend_from_slice(&amount.to_le_bytes()); + buf.push(decimals); + }, + }; + buf + } +} + +pub struct TokenInstructionBuilder; + +impl TokenInstructionBuilder { + // `create_associated_token_account()` solana-program-library/associated-token-account/program/src/lib.rs + pub fn create_account( + funding_pubkey: SolanaAddress, + other_main_pubkey: SolanaAddress, + token_mint_pubkey: SolanaAddress, + token_pubkey: SolanaAddress, + token_program_id: SolanaAddress, + ) -> Instruction { + let account_metas = vec![ + AccountMeta::new(funding_pubkey, true), + AccountMeta::new(token_pubkey, false), + AccountMeta::readonly(other_main_pubkey, false), + AccountMeta::readonly(token_mint_pubkey, false), + AccountMeta::readonly(*SYSTEM_PROGRAM_ID_ADDRESS, false), + AccountMeta::readonly(token_program_id, false), + AccountMeta::readonly(*SYSVAR_RENT_ID_ADDRESS, false), + ]; + let data = Data::default(); + Instruction::new(*ASSOCIATED_TOKEN_PROGRAM_ID_ADDRESS, data, account_metas) + } + + /// transfer_checked() solana-program-library/token/program/src/instruction.rs + pub fn transfer_checked( + sender_token_pubkey: SolanaAddress, + token_mint_pubkey: SolanaAddress, + recipient_token_pubkey: SolanaAddress, + signer: SolanaAddress, + amount: u64, + decimals: u8, + token_program_id: SolanaAddress, + ) -> Instruction { + let account_metas = vec![ + AccountMeta::new(sender_token_pubkey, false), + AccountMeta::readonly(token_mint_pubkey, false), + AccountMeta::new(recipient_token_pubkey, false), + AccountMeta::new(signer, true), + ]; + + let data = TokenInstruction::TransferChecked { amount, decimals }.pack(); + Instruction::new(token_program_id, data, account_metas) + } +} diff --git a/rust/chains/tw_solana/src/modules/message_builder.rs b/rust/chains/tw_solana/src/modules/message_builder.rs new file mode 100644 index 00000000000..c4852154b1f --- /dev/null +++ b/rust/chains/tw_solana/src/modules/message_builder.rs @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SolanaAddress; +use crate::blockhash::Blockhash; +use crate::defined_addresses::{TOKEN_2022_PROGRAM_ID_ADDRESS, TOKEN_PROGRAM_ID_ADDRESS}; +use crate::instruction::Instruction; +use crate::modules::compiled_instructions::compile_instructions; +use crate::modules::compiled_keys::CompiledKeys; +use crate::modules::instruction_builder::compute_budget_instruction::{UnitLimit, UnitPrice}; +use crate::modules::instruction_builder::stake_instruction::{ + DepositStakeArgs, StakeInstructionBuilder, +}; +use crate::modules::instruction_builder::system_instruction::SystemInstructionBuilder; +use crate::modules::instruction_builder::token_instruction::TokenInstructionBuilder; +use crate::modules::instruction_builder::InstructionBuilder; +use crate::modules::PubkeySignatureMap; +use crate::transaction::versioned::VersionedMessage; +use crate::transaction::{legacy, v0, CompiledInstruction, MessageHeader, Signature}; +use std::borrow::Cow; +use std::str::FromStr; +use tw_coin_entry::error::prelude::*; +use tw_keypair::ed25519; +use tw_keypair::traits::KeyPairTrait; +use tw_proto::Solana::Proto; +use Proto::mod_SigningInput::OneOftransaction_type as ProtoTransactionType; + +const DEFAULT_SPACE: u64 = 200; +const DEFAULT_CREATE_NONCE_SPACE: u64 = 80; + +pub struct MessageBuilder<'a> { + input: Proto::SigningInput<'a>, +} + +impl<'a> MessageBuilder<'a> { + pub fn new(input: Proto::SigningInput<'a>) -> Self { + MessageBuilder { input } + } + + pub fn signing_keys(&self) -> SigningResult> { + let mut signing_keys = Vec::default(); + if !self.input.fee_payer_private_key.is_empty() { + let fee_payer_private_key = + ed25519::sha512::KeyPair::try_from(self.input.fee_payer_private_key.as_ref()) + .into_tw() + .context("Invalid fee payer private key")?; + signing_keys.push(fee_payer_private_key); + } + + signing_keys.push(self.signer_key()?); + + // Consider matching other transaction types if they may contain other private keys. + if let ProtoTransactionType::create_nonce_account(ref nonce) = self.input.transaction_type { + let nonce_private_key = + ed25519::sha512::KeyPair::try_from(nonce.nonce_account_private_key.as_ref()) + .into_tw() + .context("Invalid nonce account private key")?; + signing_keys.push(nonce_private_key); + } + + Ok(signing_keys) + } + + pub fn external_signatures(&self) -> SigningResult { + match self.input.raw_message { + Some(ref raw_message) => RawMessageBuilder::external_signatures(raw_message), + None => Ok(PubkeySignatureMap::default()), + } + } + + pub fn build(self) -> SigningResult { + if let Some(ref raw_message) = self.input.raw_message { + return RawMessageBuilder::build(raw_message); + } + + let instructions = self.build_instructions()?; + + // Please note the fee payer can be different from the actual signer. + let (message_header, account_keys) = CompiledKeys::with_fee_payer(self.fee_payer()?) + .compile(&instructions) + .try_into_message_components()?; + + let compiled_instructions = compile_instructions(&instructions, &account_keys)?; + + if self.input.v0_msg { + Ok(VersionedMessage::V0(v0::Message { + header: message_header, + account_keys, + recent_blockhash: self.recent_blockhash()?.to_bytes(), + instructions: compiled_instructions, + address_table_lookups: Vec::default(), + })) + } else { + Ok(VersionedMessage::Legacy(legacy::Message { + header: message_header, + account_keys, + recent_blockhash: self.recent_blockhash()?.to_bytes(), + instructions: compiled_instructions, + })) + } + } + + /// Please note that this method can add [`MessageBuilder::signer_keys`] if necessary. + fn build_instructions(&self) -> SigningResult> { + match self.input.transaction_type { + ProtoTransactionType::transfer_transaction(ref transfer) => { + self.transfer_from_proto(transfer) + }, + ProtoTransactionType::delegate_stake_transaction(ref delegate) => { + self.delegate_stake_from_proto(delegate) + }, + ProtoTransactionType::deactivate_stake_transaction(ref deactivate) => { + self.deactivate_stake_from_proto(deactivate) + }, + ProtoTransactionType::deactivate_all_stake_transaction(ref deactivate_all) => { + self.deactivate_all_stake_from_proto(deactivate_all) + }, + ProtoTransactionType::withdraw_transaction(ref withdraw) => { + self.withdraw_from_proto(withdraw) + }, + ProtoTransactionType::withdraw_all_transaction(ref withdraw_all) => { + self.withdraw_all_from_proto(withdraw_all) + }, + ProtoTransactionType::create_token_account_transaction(ref create_token_acc) => { + self.create_token_account_from_proto(create_token_acc) + }, + ProtoTransactionType::token_transfer_transaction(ref token_transfer) => { + self.token_transfer_from_proto(token_transfer) + }, + ProtoTransactionType::create_and_transfer_token_transaction( + ref create_and_transfer, + ) => self.create_and_transfer_token_from_proto(create_and_transfer), + ProtoTransactionType::create_nonce_account(ref create_nonce) => { + self.create_nonce_account_from_proto(create_nonce) + }, + ProtoTransactionType::withdraw_nonce_account(ref withdraw_nonce) => { + self.withdraw_nonce_from_proto(withdraw_nonce) + }, + ProtoTransactionType::advance_nonce_account(ref advance_nonce) => { + self.advance_nonce_from_proto(advance_nonce) + }, + ProtoTransactionType::None => SigningError::err(SigningErrorType::Error_invalid_params) + .context("No transaction type specified"), + } + } + + fn transfer_from_proto( + &self, + transfer: &Proto::Transfer<'_>, + ) -> SigningResult> { + let from = self.signer_address()?; + let to = SolanaAddress::from_str(&transfer.recipient) + .into_tw() + .context("Invalid recipient address")?; + + let references = Self::parse_references(&transfer.references)?; + + let transfer_ix = SystemInstructionBuilder::transfer(from, to, transfer.value) + .with_references(references); + + let mut builder = InstructionBuilder::default(); + builder + .maybe_advance_nonce(self.nonce_account()?, from) + .maybe_priority_fee_price(self.priority_fee_price()) + .maybe_priority_fee_limit(self.priority_fee_limit()) + .maybe_memo(transfer.memo.as_ref()) + .add_instruction(transfer_ix); + Ok(builder.output()) + } + + fn delegate_stake_from_proto( + &self, + delegate: &Proto::DelegateStake, + ) -> SigningResult> { + let sender = self.signer_address()?; + let validator = SolanaAddress::from_str(delegate.validator_pubkey.as_ref()) + .into_tw() + .context("Invalid validator address")?; + + let stake_account = if delegate.stake_account.is_empty() { + None + } else { + let stake_account = SolanaAddress::from_str(&delegate.stake_account) + .into_tw() + .context("Invalid stake account")?; + Some(stake_account) + }; + + let deposit_ixs = StakeInstructionBuilder::deposit_stake(DepositStakeArgs { + sender, + validator, + stake_account, + recent_blockhash: self.recent_blockhash()?, + lamports: delegate.value, + space: DEFAULT_SPACE, + }); + + let mut builder = InstructionBuilder::default(); + builder + .maybe_advance_nonce(self.nonce_account()?, sender) + .maybe_priority_fee_price(self.priority_fee_price()) + .maybe_priority_fee_limit(self.priority_fee_limit()) + .add_instructions(deposit_ixs); + Ok(builder.output()) + } + + fn deactivate_stake_from_proto( + &self, + deactivate: &Proto::DeactivateStake, + ) -> SigningResult> { + let sender = self.signer_address()?; + let stake_account = SolanaAddress::from_str(&deactivate.stake_account) + .into_tw() + .context("Invalid stake account")?; + + let deactivate_ix = StakeInstructionBuilder::deactivate(stake_account, sender); + + let mut builder = InstructionBuilder::default(); + builder + .maybe_advance_nonce(self.nonce_account()?, sender) + .maybe_priority_fee_price(self.priority_fee_price()) + .maybe_priority_fee_limit(self.priority_fee_limit()) + .add_instruction(deactivate_ix); + Ok(builder.output()) + } + + fn deactivate_all_stake_from_proto( + &self, + deactivate_all: &Proto::DeactivateAllStake, + ) -> SigningResult> { + let sender = self.signer_address()?; + let deactivate_ixs = deactivate_all + .stake_accounts + .iter() + .map(|stake_account| { + let stake_account = SolanaAddress::from_str(stake_account.as_ref())?; + Ok(StakeInstructionBuilder::deactivate(stake_account, sender)) + }) + .collect::>>() + .context("Invalid stake account(s)")?; + + let mut builder = InstructionBuilder::default(); + builder + .maybe_advance_nonce(self.nonce_account()?, sender) + .maybe_priority_fee_price(self.priority_fee_price()) + .maybe_priority_fee_limit(self.priority_fee_limit()) + .add_instructions(deactivate_ixs); + Ok(builder.output()) + } + + fn withdraw_from_proto( + &self, + withdraw: &Proto::WithdrawStake, + ) -> SigningResult> { + let sender = self.signer_address()?; + let stake_account = SolanaAddress::from_str(withdraw.stake_account.as_ref()) + .into_tw() + .context("Invalid stake account")?; + + let custodian_account = None; + + let withdraw_ix = StakeInstructionBuilder::withdraw( + stake_account, + sender, + sender, + withdraw.value, + custodian_account, + ); + + let mut builder = InstructionBuilder::default(); + builder + .maybe_advance_nonce(self.nonce_account()?, sender) + .maybe_priority_fee_price(self.priority_fee_price()) + .maybe_priority_fee_limit(self.priority_fee_limit()) + .add_instruction(withdraw_ix); + Ok(builder.output()) + } + + fn withdraw_all_from_proto( + &self, + withdraw_all: &Proto::WithdrawAllStake, + ) -> SigningResult> { + let sender = self.signer_address()?; + + let withdraw_ixs = withdraw_all + .stake_accounts + .iter() + .map(|withdraw| { + let stake_account = SolanaAddress::from_str(withdraw.stake_account.as_ref()) + .into_tw() + .context("Invalid stake account")?; + + let custodian_account = None; + Ok(StakeInstructionBuilder::withdraw( + stake_account, + sender, + sender, + withdraw.value, + custodian_account, + )) + }) + .collect::>>()?; + + let mut builder = InstructionBuilder::default(); + builder + .maybe_advance_nonce(self.nonce_account()?, sender) + .maybe_priority_fee_price(self.priority_fee_price()) + .maybe_priority_fee_limit(self.priority_fee_limit()) + .add_instructions(withdraw_ixs); + Ok(builder.output()) + } + + fn create_token_account_from_proto( + &self, + create_token_acc: &Proto::CreateTokenAccount, + ) -> SigningResult> { + let funding_account = self.signer_address()?; + let other_main_address = SolanaAddress::from_str(create_token_acc.main_address.as_ref()) + .into_tw() + .context("Invalid main address")?; + + let token_mint_address = + SolanaAddress::from_str(create_token_acc.token_mint_address.as_ref()) + .into_tw() + .context("Invalid token mint address")?; + + let token_address = SolanaAddress::from_str(create_token_acc.token_address.as_ref()) + .into_tw() + .context("Invalid token address to be created")?; + + let instruction = TokenInstructionBuilder::create_account( + funding_account, + other_main_address, + token_mint_address, + token_address, + match_program_id(create_token_acc.token_program_id), + ); + let mut builder = InstructionBuilder::default(); + builder + .maybe_advance_nonce(self.nonce_account()?, funding_account) + .maybe_priority_fee_price(self.priority_fee_price()) + .maybe_priority_fee_limit(self.priority_fee_limit()) + .add_instruction(instruction); + Ok(builder.output()) + } + + fn token_transfer_from_proto( + &self, + token_transfer: &Proto::TokenTransfer, + ) -> SigningResult> { + let signer = self.signer_address()?; + let sender_token_address = + SolanaAddress::from_str(token_transfer.sender_token_address.as_ref()) + .into_tw() + .context("Invalid sender token address")?; + + let token_mint_address = + SolanaAddress::from_str(token_transfer.token_mint_address.as_ref()) + .into_tw() + .context("Invalid token mint address")?; + + let recipient_token_address = + SolanaAddress::from_str(token_transfer.recipient_token_address.as_ref()) + .into_tw() + .context("Invalid recipient token address")?; + + let decimals = token_transfer + .decimals + .try_into() + .tw_err(|_| SigningErrorType::Error_invalid_params) + .context("Invalid token decimals. Expected lower than 256")?; + + let references = Self::parse_references(&token_transfer.references)?; + let transfer_instruction = TokenInstructionBuilder::transfer_checked( + sender_token_address, + token_mint_address, + recipient_token_address, + signer, + token_transfer.amount, + decimals, + match_program_id(token_transfer.token_program_id), + ) + .with_references(references); + + let mut builder = InstructionBuilder::default(); + builder + .maybe_advance_nonce(self.nonce_account()?, signer) + .maybe_priority_fee_price(self.priority_fee_price()) + .maybe_priority_fee_limit(self.priority_fee_limit()) + .maybe_memo(token_transfer.memo.as_ref()) + .add_instruction(transfer_instruction); + Ok(builder.output()) + } + + fn create_and_transfer_token_from_proto( + &self, + create_and_transfer: &Proto::CreateAndTransferToken, + ) -> SigningResult> { + let signer = self.signer_address()?; + let fee_payer = self.fee_payer()?; + + let sender_token_address = + SolanaAddress::from_str(create_and_transfer.sender_token_address.as_ref()) + .into_tw() + .context("Invalid sender token address")?; + + let token_mint_address = + SolanaAddress::from_str(create_and_transfer.token_mint_address.as_ref()) + .into_tw() + .context("Invalid token mint address")?; + + let recipient_main_address = + SolanaAddress::from_str(create_and_transfer.recipient_main_address.as_ref()) + .into_tw() + .context("Invalid recipient main address")?; + + let recipient_token_address = + SolanaAddress::from_str(create_and_transfer.recipient_token_address.as_ref()) + .into_tw() + .context("Invalid recipient token address")?; + + let references = Self::parse_references(&create_and_transfer.references)?; + + let decimals = create_and_transfer + .decimals + .try_into() + .tw_err(|_| SigningErrorType::Error_invalid_params) + .context("Invalid token decimals. Expected lower than 256")?; + + let create_account_instruction = TokenInstructionBuilder::create_account( + // Can be different from the actual signer. + fee_payer, + recipient_main_address, + token_mint_address, + recipient_token_address, + match_program_id(create_and_transfer.token_program_id), + ); + let transfer_instruction = TokenInstructionBuilder::transfer_checked( + sender_token_address, + token_mint_address, + recipient_token_address, + signer, + create_and_transfer.amount, + decimals, + match_program_id(create_and_transfer.token_program_id), + ) + .with_references(references); + + let mut builder = InstructionBuilder::default(); + builder + .maybe_advance_nonce(self.nonce_account()?, signer) + .maybe_priority_fee_price(self.priority_fee_price()) + .maybe_priority_fee_limit(self.priority_fee_limit()) + .add_instruction(create_account_instruction) + // Optional memo. Order: before transfer, as per documentation. + .maybe_memo(create_and_transfer.memo.as_ref()) + .add_instruction(transfer_instruction); + Ok(builder.output()) + } + + fn create_nonce_account_from_proto( + &self, + create_nonce: &Proto::CreateNonceAccount, + ) -> SigningResult> { + let signer = self.signer_address()?; + let prev_nonce_account = self.nonce_account()?; + + let new_nonce_account = if create_nonce.nonce_account.is_empty() { + let nonce_key = + ed25519::sha512::KeyPair::try_from(create_nonce.nonce_account_private_key.as_ref()) + .into_tw() + .context("Invalid nonce account private key")?; + SolanaAddress::with_public_key_ed25519(nonce_key.public()) + } else { + SolanaAddress::from_str(create_nonce.nonce_account.as_ref()) + .into_tw() + .context("Invalid nonce account")? + }; + + let mut builder = InstructionBuilder::default(); + builder + .maybe_advance_nonce(prev_nonce_account, signer) + .maybe_priority_fee_price(self.priority_fee_price()) + .maybe_priority_fee_limit(self.priority_fee_limit()) + .add_instructions(SystemInstructionBuilder::create_nonce_account( + signer, + new_nonce_account, + create_nonce.rent, + DEFAULT_CREATE_NONCE_SPACE, + )); + Ok(builder.output()) + } + + fn withdraw_nonce_from_proto( + &self, + withdraw_nonce: &Proto::WithdrawNonceAccount, + ) -> SigningResult> { + let signer = self.signer_address()?; + let withdraw_from_nonce = SolanaAddress::from_str(withdraw_nonce.nonce_account.as_ref()) + .into_tw() + .context("Invalid nonce account")?; + + let recipient = SolanaAddress::from_str(withdraw_nonce.recipient.as_ref()) + .into_tw() + .context("Invalid recipient")?; + + let mut builder = InstructionBuilder::default(); + builder + .maybe_advance_nonce(self.nonce_account()?, signer) + .maybe_priority_fee_price(self.priority_fee_price()) + .maybe_priority_fee_limit(self.priority_fee_limit()) + .add_instruction(SystemInstructionBuilder::withdraw_nonce_account( + withdraw_from_nonce, + signer, + recipient, + withdraw_nonce.value, + )); + Ok(builder.output()) + } + + fn advance_nonce_from_proto( + &self, + advance_nonce: &Proto::AdvanceNonceAccount, + ) -> SigningResult> { + let signer = self.signer_address()?; + let nonce_account = SolanaAddress::from_str(advance_nonce.nonce_account.as_ref()) + .into_tw() + .context("Invalid nonce account")?; + + let mut builder = InstructionBuilder::default(); + builder + .maybe_advance_nonce(Some(nonce_account), signer) + .maybe_priority_fee_price(self.priority_fee_price()) + .maybe_priority_fee_limit(self.priority_fee_limit()); + Ok(builder.output()) + } + + fn nonce_account(&self) -> SigningResult> { + if self.input.nonce_account.is_empty() { + Ok(None) + } else { + SolanaAddress::from_str(&self.input.nonce_account) + .map(Some) + .map_err(SigningError::from) + .context("Invalid nonce account") + } + } + + fn signer_address(&self) -> SigningResult { + if self.input.private_key.is_empty() { + SolanaAddress::from_str(&self.input.sender) + .map_err(SigningError::from) + .context("Sender address is either not set or invalid") + } else { + Ok(SolanaAddress::with_public_key_ed25519( + self.signer_key()?.public(), + )) + } + } + + /// Returns a fee payer of the transaction. + /// Please note it can be different the transaction signer if [`Proto::SigningInput::fee_payer`] is set. + pub fn fee_payer(&self) -> SigningResult { + if !self.input.fee_payer_private_key.is_empty() { + let fee_payer_key = + ed25519::sha512::KeyPair::try_from(self.input.fee_payer_private_key.as_ref()) + .into_tw() + .context("Invalid fee payer private key")?; + return Ok(SolanaAddress::with_public_key_ed25519( + fee_payer_key.public(), + )); + } + if !self.input.fee_payer.is_empty() { + return SolanaAddress::from_str(self.input.fee_payer.as_ref()) + .map_err(SigningError::from) + .context("Invalid fee payer address"); + } + self.signer_address() + } + + fn recent_blockhash(&self) -> SigningResult { + Blockhash::from_str(&self.input.recent_blockhash) + .map_err(SigningError::from) + .context("Invalid recent blockhash") + } + + fn signer_key(&self) -> SigningResult { + ed25519::sha512::KeyPair::try_from(self.input.private_key.as_ref()) + .map_err(SigningError::from) + .context("Invalid signer key") + } + + fn priority_fee_price(&self) -> Option { + self.input + .priority_fee_price + .as_ref() + .map(|proto| proto.price) + } + + fn priority_fee_limit(&self) -> Option { + self.input + .priority_fee_limit + .as_ref() + .map(|proto| proto.limit) + } + + fn parse_references(refs: &[Cow<'_, str>]) -> SigningResult> { + refs.iter() + .map(|addr| SolanaAddress::from_str(addr).map_err(SigningError::from)) + .collect::>>() + .context("Invalid transaction reference(s)") + } +} + +pub struct RawMessageBuilder; + +impl RawMessageBuilder { + pub fn build(raw_message: &Proto::RawMessage) -> SigningResult { + use Proto::mod_RawMessage::OneOfmessage as RawMessageType; + + match raw_message.message { + RawMessageType::legacy(ref legacy) => Self::build_legacy(legacy), + RawMessageType::v0(ref v0) => Self::build_v0(v0), + RawMessageType::None => SigningError::err(SigningErrorType::Error_invalid_params), + } + } + + pub fn external_signatures( + raw_message: &Proto::RawMessage, + ) -> SigningResult { + let mut key_signs = PubkeySignatureMap::with_capacity(raw_message.signatures.len()); + for entry in raw_message.signatures.iter() { + let pubkey = SolanaAddress::from_str(entry.pubkey.as_ref()) + .into_tw() + .context("Invalid 'PubkeySignature::public'")?; + + let signature = Signature::from_str(entry.signature.as_ref()) + .context("Invalid 'PubkeySignature::signature'")?; + + let ed25519_signature = ed25519::Signature::try_from(signature.0.as_slice()) + .into_tw() + .context("Invalid 'PubkeySignature::signature'")?; + + key_signs.insert(pubkey, ed25519_signature); + } + Ok(key_signs) + } + + fn build_legacy( + legacy: &Proto::mod_RawMessage::MessageLegacy, + ) -> SigningResult { + let header = Self::build_message_header(&legacy.header)?; + let account_keys = Self::build_account_keys(&legacy.account_keys)?; + let recent_blockhash = Blockhash::from_str(legacy.recent_blockhash.as_ref()) + .into_tw() + .context("Invalid recent blockhash")?; + + let instructions: Vec<_> = Self::build_instructions(&legacy.instructions)?; + + Ok(VersionedMessage::Legacy(legacy::Message { + header, + account_keys, + recent_blockhash: recent_blockhash.to_bytes(), + instructions, + })) + } + + fn build_v0(v0: &Proto::mod_RawMessage::MessageV0) -> SigningResult { + let header = Self::build_message_header(&v0.header)?; + let account_keys = Self::build_account_keys(&v0.account_keys)?; + let recent_blockhash = Blockhash::from_str(v0.recent_blockhash.as_ref()) + .into_tw() + .context("Invalid recent blockhash")?; + + let instructions: Vec<_> = Self::build_instructions(&v0.instructions)?; + let address_table_lookups = v0 + .address_table_lookups + .iter() + .map(Self::build_address_lookup_table) + .collect::>>()?; + + Ok(VersionedMessage::V0(v0::Message { + header, + account_keys, + recent_blockhash: recent_blockhash.to_bytes(), + instructions, + address_table_lookups, + })) + } + + fn build_message_header( + raw_header: &Option, + ) -> SigningResult { + let raw_header = raw_header + .as_ref() + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("No message header provided")?; + + Ok(MessageHeader { + num_required_signatures: try_into_u8(raw_header.num_required_signatures)?, + num_readonly_signed_accounts: try_into_u8(raw_header.num_readonly_signed_accounts)?, + num_readonly_unsigned_accounts: try_into_u8(raw_header.num_readonly_unsigned_accounts)?, + }) + } + + fn build_account_keys(raw_keys: &[Cow<'_, str>]) -> SigningResult> { + raw_keys + .iter() + .map(|s| SolanaAddress::from_str(s.as_ref())) + .collect::>>() + .map_err(SigningError::from) + .context("Cannot build account keys") + } + + fn build_instructions( + ixs: &[Proto::mod_RawMessage::Instruction], + ) -> SigningResult> { + ixs.iter().map(Self::build_instruction).collect() + } + + fn build_instruction( + ix: &Proto::mod_RawMessage::Instruction, + ) -> SigningResult { + let accounts = ix + .accounts + .iter() + .map(|idx| try_into_u8(*idx)) + .collect::>>() + .context("Cannot build account metas")?; + + Ok(CompiledInstruction { + program_id_index: try_into_u8(ix.program_id).context("Invalid program ID")?, + accounts, + data: ix.program_data.to_vec(), + }) + } + + fn build_address_lookup_table( + lookup: &Proto::mod_RawMessage::MessageAddressTableLookup, + ) -> SigningResult { + let account_key = SolanaAddress::from_str(lookup.account_key.as_ref()) + .into_tw() + .context("Invalid lookup's account key")?; + + let writable_indexes = lookup + .writable_indexes + .iter() + .copied() + .map(try_into_u8) + .collect::>>() + .context("Invalid writable index(s)")?; + + let readonly_indexes = lookup + .readonly_indexes + .iter() + .copied() + .map(try_into_u8) + .collect::>>() + .context("Invalid readonly index(s)")?; + + Ok(v0::MessageAddressTableLookup { + account_key, + writable_indexes, + readonly_indexes, + }) + } +} + +fn try_into_u8(num: T) -> SigningResult +where + u8: TryFrom, +{ + u8::try_from(num).tw_err(|_| SigningErrorType::Error_tx_too_big) +} + +fn match_program_id(program_id: Proto::TokenProgramId) -> SolanaAddress { + match program_id { + Proto::TokenProgramId::TokenProgram => *TOKEN_PROGRAM_ID_ADDRESS, + Proto::TokenProgramId::Token2022Program => *TOKEN_2022_PROGRAM_ID_ADDRESS, + } +} diff --git a/rust/chains/tw_solana/src/modules/mod.rs b/rust/chains/tw_solana/src/modules/mod.rs new file mode 100644 index 00000000000..074e5cbc8a7 --- /dev/null +++ b/rust/chains/tw_solana/src/modules/mod.rs @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SolanaAddress; +use std::collections::HashMap; +use tw_keypair::ed25519; + +pub mod compiled_instructions; +pub mod compiled_keys; +pub mod instruction_builder; +pub mod message_builder; +pub mod proto_builder; +pub mod transaction_decoder; +pub mod transaction_util; +pub mod tx_signer; +pub mod utils; +pub mod wallet_connect; + +pub type PubkeySignatureMap = HashMap; diff --git a/rust/chains/tw_solana/src/modules/proto_builder.rs b/rust/chains/tw_solana/src/modules/proto_builder.rs new file mode 100644 index 00000000000..d40ff7ca664 --- /dev/null +++ b/rust/chains/tw_solana/src/modules/proto_builder.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::transaction::v0; +use crate::transaction::versioned::{VersionedMessage, VersionedTransaction}; +use std::borrow::Cow; +use tw_proto::Solana::Proto::{self, mod_RawMessage::OneOfmessage as ProtoMessageType}; + +pub struct ProtoBuilder; + +impl ProtoBuilder { + pub fn build_from_tx(tx: &VersionedTransaction) -> Proto::RawMessage<'static> { + let message_header = Proto::mod_RawMessage::MessageHeader { + num_required_signatures: tx.message.header().num_required_signatures as u32, + num_readonly_signed_accounts: tx.message.header().num_readonly_signed_accounts as u32, + num_readonly_unsigned_accounts: tx.message.header().num_readonly_unsigned_accounts + as u32, + }; + + let account_keys: Vec<_> = tx + .message + .account_keys() + .iter() + .map(|acc| Cow::from(acc.to_string())) + .collect(); + + let recent_blockhash = Cow::from(tx.message.recent_blockhash().to_string()); + + let instructions = tx + .message + .instructions() + .iter() + .map(|ix| Proto::mod_RawMessage::Instruction { + program_id: ix.program_id_index as u32, + accounts: vec_u8_to_u32(&ix.accounts), + program_data: Cow::from(ix.data.clone()), + }) + .collect(); + + let message = match tx.message { + VersionedMessage::Legacy(_) => { + ProtoMessageType::legacy(Proto::mod_RawMessage::MessageLegacy { + header: Some(message_header), + account_keys, + recent_blockhash, + instructions, + }) + }, + VersionedMessage::V0(ref v0) => { + let address_table_lookups = + Self::build_address_table_lookups(&v0.address_table_lookups); + ProtoMessageType::v0(Proto::mod_RawMessage::MessageV0 { + header: Some(message_header), + account_keys, + recent_blockhash, + instructions, + address_table_lookups, + }) + }, + }; + + Proto::RawMessage { + signatures: Self::build_signatures(tx), + message, + } + } + + fn build_address_table_lookups( + lookups: &[v0::MessageAddressTableLookup], + ) -> Vec> { + lookups + .iter() + .map(|lookup| Proto::mod_RawMessage::MessageAddressTableLookup { + account_key: Cow::from(lookup.account_key.to_string()), + writable_indexes: vec_u8_to_u32(&lookup.writable_indexes), + readonly_indexes: vec_u8_to_u32(&lookup.readonly_indexes), + }) + .collect() + } + + pub fn build_signatures(tx: &VersionedTransaction) -> Vec> { + tx.message + .account_keys() + .iter() + .zip(tx.signatures.iter()) + .map(|(pubkey, signature)| Proto::PubkeySignature { + pubkey: Cow::from(pubkey.to_string()), + signature: Cow::from(signature.to_string()), + }) + .collect() + } +} + +fn vec_u8_to_u32(v: &[u8]) -> Vec { + v.iter().map(|num| *num as u32).collect() +} diff --git a/rust/chains/tw_solana/src/modules/transaction_decoder.rs b/rust/chains/tw_solana/src/modules/transaction_decoder.rs new file mode 100644 index 00000000000..02a441a464a --- /dev/null +++ b/rust/chains/tw_solana/src/modules/transaction_decoder.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::proto_builder::ProtoBuilder; +use crate::transaction::versioned::VersionedTransaction; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::transaction_decoder::TransactionDecoder; +use tw_coin_entry::signing_output_error; +use tw_proto::Solana::Proto; + +pub struct SolanaTransactionDecoder; + +impl TransactionDecoder for SolanaTransactionDecoder { + type Output = Proto::DecodingTransactionOutput<'static>; + + fn decode_transaction(&self, coin: &dyn CoinContext, tx: &[u8]) -> Self::Output { + Self::decode_transaction_impl(coin, tx) + .unwrap_or_else(|e| signing_output_error!(Proto::DecodingTransactionOutput, e)) + } +} + +impl SolanaTransactionDecoder { + pub(crate) fn decode_transaction_impl( + _coin: &dyn CoinContext, + tx: &[u8], + ) -> SigningResult> { + let decoded_tx: VersionedTransaction = bincode::deserialize(tx) + .tw_err(|_| SigningErrorType::Error_input_parse) + .context("Error decoding transaction as 'bincode'")?; + let transaction = ProtoBuilder::build_from_tx(&decoded_tx); + + Ok(Proto::DecodingTransactionOutput { + transaction: Some(transaction), + ..Proto::DecodingTransactionOutput::default() + }) + } +} diff --git a/rust/chains/tw_solana/src/modules/transaction_util.rs b/rust/chains/tw_solana/src/modules/transaction_util.rs new file mode 100644 index 00000000000..fdde5d6952c --- /dev/null +++ b/rust/chains/tw_solana/src/modules/transaction_util.rs @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::transaction_decoder::SolanaTransactionDecoder; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::transaction_util::TransactionUtil; +use tw_encoding::base64; +use tw_encoding::base64::STANDARD; + +pub struct SolanaTransactionUtil; + +impl TransactionUtil for SolanaTransactionUtil { + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + Self::calc_tx_hash_impl(coin, encoded_tx) + } +} + +impl SolanaTransactionUtil { + fn calc_tx_hash_impl(coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + // Solana signed transactions can be encoded in either base64 or base58. For more information, see: https://solana.com/docs/rpc/http/sendtransaction + // Currently, this function only accepts base64 encoding. + let tx_bytes = base64::decode(encoded_tx, STANDARD)?; + let decoded_tx_output = SolanaTransactionDecoder::decode_transaction_impl(coin, &tx_bytes)?; + + let first_sig = decoded_tx_output + .transaction + .as_ref() + .and_then(|tx| tx.signatures.first()) + .or_tw_err(SigningErrorType::Error_input_parse) + .context("There is no transaction signatures. Looks like it hasn't been signed yet")?; + + // Tx hash is the first signature + Ok(first_sig.signature.to_string()) + } +} diff --git a/rust/chains/tw_solana/src/modules/tx_signer.rs b/rust/chains/tw_solana/src/modules/tx_signer.rs new file mode 100644 index 00000000000..5fb421e6876 --- /dev/null +++ b/rust/chains/tw_solana/src/modules/tx_signer.rs @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SolanaAddress; +use crate::modules::PubkeySignatureMap; +use crate::transaction::{versioned, Signature}; +use std::collections::HashMap; +use tw_coin_entry::error::prelude::*; +use tw_keypair::ed25519; +use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait}; +use tw_memory::Data; + +pub struct TxSigner; + +impl TxSigner { + pub fn sign_versioned( + unsigned_msg: versioned::VersionedMessage, + keys: &[ed25519::sha512::KeyPair], + external_signatures: &PubkeySignatureMap, + ) -> SigningResult { + let mut key_signs = HashMap::default(); + + let message_encoded = Self::preimage_versioned(&unsigned_msg)?; + + // Add external signatures first, so they can be overriden if corresponding private keys are specified. + key_signs.extend(external_signatures.clone()); + + // Sign the message with all given private keys. + for private_key in keys { + let signing_pubkey = + SolanaAddress::with_public_key_bytes(private_key.public().to_bytes()); + let ed25519_signature = private_key.sign(message_encoded.clone())?; + + key_signs.insert(signing_pubkey, ed25519_signature); + } + + Self::compile_versioned(unsigned_msg, key_signs) + } + + pub fn compile_versioned( + unsigned_msg: versioned::VersionedMessage, + key_signs: HashMap, + ) -> SigningResult { + let mut tx = versioned::VersionedTransaction::unsigned(unsigned_msg); + + let actual_signatures = key_signs.len(); + let expected_signatures = tx.message.num_required_signatures(); + if actual_signatures != expected_signatures { + return SigningError::err(SigningErrorType::Error_signatures_count) + .with_context(|| format!("Expected '{expected_signatures}' signatures, provided '{actual_signatures}'")); + } + + for (signing_pubkey, ed25519_signature) in key_signs { + // Find an index of the corresponding account. + let account_index = tx + .message + .get_account_index(signing_pubkey) + .or_tw_err(SigningErrorType::Error_missing_private_key) + .with_context(|| { + format!("Provided a signature for an unexpected account: {signing_pubkey}") + })?; + let signature_to_reassign = tx + .signatures + .get_mut(account_index) + .or_tw_err(SigningErrorType::Error_signatures_count) + .context("Internal error: invalid number of Tx.signatures[]")?; + *signature_to_reassign = Signature(ed25519_signature.to_bytes()); + } + + Ok(tx) + } + + pub fn preimage_versioned(msg: &versioned::VersionedMessage) -> SigningResult { + bincode::serialize(&msg) + .tw_err(|_| SigningErrorType::Error_internal) + .context("Error serializing Solana Message as 'bincode'") + } +} diff --git a/rust/chains/tw_solana/src/modules/utils.rs b/rust/chains/tw_solana/src/modules/utils.rs new file mode 100644 index 00000000000..85b662784b8 --- /dev/null +++ b/rust/chains/tw_solana/src/modules/utils.rs @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::proto_builder::ProtoBuilder; +use crate::modules::tx_signer::TxSigner; +use crate::modules::PubkeySignatureMap; +use crate::transaction::versioned::VersionedTransaction; +use crate::SOLANA_ALPHABET; +use std::borrow::Cow; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_encoding::base58; +use tw_encoding::base64::{self, STANDARD}; +use tw_hash::H256; +use tw_keypair::{ed25519, KeyPairResult}; +use tw_memory::Data; +use tw_proto::Solana::Proto; + +pub struct SolanaTransaction; + +impl SolanaTransaction { + pub fn update_blockhash_and_sign( + encoded_tx: &str, + recent_blockhash: &str, + private_keys: &[Data], + ) -> Proto::SigningOutput<'static> { + Self::update_blockhash_and_sign_impl(encoded_tx, recent_blockhash, private_keys) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn update_blockhash_and_sign_impl( + encoded_tx: &str, + recent_blockhash: &str, + private_keys: &[Data], + ) -> SigningResult> { + let tx_bytes = base64::decode(encoded_tx, STANDARD)?; + + let tx_to_sign: VersionedTransaction = + bincode::deserialize(&tx_bytes).map_err(|_| SigningErrorType::Error_input_parse)?; + let mut msg_to_sign = tx_to_sign.message; + + let new_blockchain_hash = base58::decode(recent_blockhash, SOLANA_ALPHABET)?; + let new_blockchain_hash = H256::try_from(new_blockchain_hash.as_slice()) + .tw_err(|_| SigningErrorType::Error_invalid_params)?; + + // Update the transaction's blockhash and re-sign it. + msg_to_sign.set_recent_blockhash(new_blockchain_hash); + + let unsigned_encoded = TxSigner::preimage_versioned(&msg_to_sign)?; + + // Do not sign the transaction if there is no private keys, but set zeroed signatures. + // It's needed to estimate the transaction fee with an updated blockhash without using real private keys. + let signed_tx = if private_keys.is_empty() { + VersionedTransaction::unsigned(msg_to_sign) + } else { + let private_keys = private_keys + .iter() + .map(|pk| ed25519::sha512::KeyPair::try_from(pk.as_slice())) + .collect::>>()?; + + let external_signatures = PubkeySignatureMap::default(); + TxSigner::sign_versioned(msg_to_sign, &private_keys, &external_signatures)? + }; + + let unsigned_encoded = base64::encode(&unsigned_encoded, STANDARD); + let signed_encoded = + bincode::serialize(&signed_tx).tw_err(|_| SigningErrorType::Error_internal)?; + let signed_encoded = base64::encode(&signed_encoded, STANDARD); + + Ok(Proto::SigningOutput { + encoded: Cow::from(signed_encoded), + unsigned_tx: Cow::from(unsigned_encoded), + signatures: ProtoBuilder::build_signatures(&signed_tx), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/chains/tw_solana/src/modules/wallet_connect/connector.rs b/rust/chains/tw_solana/src/modules/wallet_connect/connector.rs new file mode 100644 index 00000000000..041d50012ac --- /dev/null +++ b/rust/chains/tw_solana/src/modules/wallet_connect/connector.rs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::proto_builder::ProtoBuilder; +use crate::modules::wallet_connect::request::SignTransactionRequest; +use crate::transaction::versioned::VersionedTransaction; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::wallet_connector::WalletConnector; +use tw_coin_entry::signing_output_error; +use tw_proto::Solana::Proto; +use tw_proto::WalletConnect::Proto::{ + self as WCProto, mod_ParseRequestOutput::OneOfsigning_input_oneof as SigningInputEnum, +}; + +pub struct SolanaWalletConnector; + +impl WalletConnector for SolanaWalletConnector { + fn parse_request( + &self, + coin: &dyn CoinContext, + request: WCProto::ParseRequestInput<'_>, + ) -> WCProto::ParseRequestOutput<'static> { + Self::parse_request_impl(coin, request) + .unwrap_or_else(|e| signing_output_error!(WCProto::ParseRequestOutput, e)) + } +} + +impl SolanaWalletConnector { + fn parse_request_impl( + coin: &dyn CoinContext, + request: WCProto::ParseRequestInput<'_>, + ) -> SigningResult> { + match request.method { + WCProto::Method::SolanaSignTransaction => { + Self::parse_sign_transaction_request(coin, request) + }, + _ => SigningError::err(SigningErrorType::Error_not_supported) + .context("Unknown WalletConnect method"), + } + } + + pub fn parse_sign_transaction_request( + _coin: &dyn CoinContext, + request: WCProto::ParseRequestInput<'_>, + ) -> SigningResult> { + let sign_req: SignTransactionRequest = serde_json::from_str(&request.payload) + .tw_err(|_| SigningErrorType::Error_input_parse) + .context("Error parsing WalletConnect signing request as JSON")?; + + let transaction: VersionedTransaction = bincode::deserialize(&sign_req.transaction.0) + .tw_err(|_| SigningErrorType::Error_input_parse) + .context("Error deserializing Solana transaction as 'bincode'")?; + + let signing_input = Proto::SigningInput { + raw_message: Some(ProtoBuilder::build_from_tx(&transaction)), + ..Proto::SigningInput::default() + }; + + Ok(WCProto::ParseRequestOutput { + signing_input_oneof: SigningInputEnum::solana(signing_input), + ..WCProto::ParseRequestOutput::default() + }) + } +} diff --git a/rust/chains/tw_solana/src/modules/wallet_connect/mod.rs b/rust/chains/tw_solana/src/modules/wallet_connect/mod.rs new file mode 100644 index 00000000000..6a72d9528b8 --- /dev/null +++ b/rust/chains/tw_solana/src/modules/wallet_connect/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod connector; +pub mod request; diff --git a/rust/chains/tw_solana/src/modules/wallet_connect/request.rs b/rust/chains/tw_solana/src/modules/wallet_connect/request.rs new file mode 100644 index 00000000000..603de8e1045 --- /dev/null +++ b/rust/chains/tw_solana/src/modules/wallet_connect/request.rs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use serde::Deserialize; +use tw_encoding::base64::Base64Encoded; + +/// `solana_signTransaction` request payload without legacy fields. +/// https://docs.walletconnect.com/advanced/rpc-reference/solana-rpc#solana_signtransaction +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SignTransactionRequest { + pub transaction: Base64Encoded, +} diff --git a/rust/chains/tw_solana/src/program/mod.rs b/rust/chains/tw_solana/src/program/mod.rs new file mode 100644 index 00000000000..bd0451b533b --- /dev/null +++ b/rust/chains/tw_solana/src/program/mod.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod stake_program; diff --git a/rust/chains/tw_solana/src/program/stake_program.rs b/rust/chains/tw_solana/src/program/stake_program.rs new file mode 100644 index 00000000000..e70a9a2fe3b --- /dev/null +++ b/rust/chains/tw_solana/src/program/stake_program.rs @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SolanaAddress; +use crate::blockhash::Blockhash; +use crate::defined_addresses::*; +use tw_coin_entry::error::prelude::*; +use tw_hash::sha2::sha256; +use tw_hash::H256; + +pub const MAX_SEED_LEN: usize = 32; + +pub struct StakeProgram; + +impl StakeProgram { + pub fn recent_blockhash_as_seed(recent_blockhash: &Blockhash) -> String { + recent_blockhash + .to_string() + .chars() + .take(MAX_SEED_LEN) + .collect() + } + + pub fn address_from_recent_blockhash( + from: &SolanaAddress, + recent_blockhash: &Blockhash, + ) -> SolanaAddress { + let mut seed = from.bytes().to_vec(); + seed.extend_from_slice(Self::recent_blockhash_as_seed(recent_blockhash).as_bytes()); + seed.extend_from_slice(STAKE_PROGRAM_ID_ADDRESS.bytes().as_slice()); + let bytes = + H256::try_from(sha256(&seed).as_slice()).expect("sha256 expected to return 32 bytes"); + SolanaAddress::with_public_key_bytes(bytes) + } + + /// https://github.com/solana-labs/solana-program-library/blob/master/associated-token-account/program/src/lib.rs#L35 + pub fn get_associated_token_address( + main_address: SolanaAddress, + token_program_id: SolanaAddress, + token_mint_address: SolanaAddress, + ) -> AddressResult { + SolanaAddress::find_program_address( + &[ + main_address.bytes().as_slice(), + token_program_id.bytes().as_slice(), + token_mint_address.bytes().as_slice(), + ], + *ASSOCIATED_TOKEN_PROGRAM_ID_ADDRESS, + ) + .ok_or(AddressError::InvalidInput) + } +} diff --git a/rust/chains/tw_solana/src/signer.rs b/rust/chains/tw_solana/src/signer.rs new file mode 100644 index 00000000000..caa552aa4da --- /dev/null +++ b/rust/chains/tw_solana/src/signer.rs @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::message_builder::MessageBuilder; +use crate::modules::proto_builder::ProtoBuilder; +use crate::modules::tx_signer::TxSigner; +use crate::SOLANA_ALPHABET; +use std::borrow::Cow; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_encoding::base58; +use tw_encoding::base64::{self, STANDARD}; +use tw_proto::Solana::Proto; + +pub struct SolanaSigner; + +impl SolanaSigner { + pub fn sign( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> Proto::SigningOutput<'static> { + Self::sign_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_impl( + _coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let encode = move |data| match input.tx_encoding { + Proto::Encoding::Base58 => base58::encode(data, SOLANA_ALPHABET), + Proto::Encoding::Base64 => base64::encode(data, STANDARD), + }; + + let builder = MessageBuilder::new(input); + let signing_keys = builder.signing_keys()?; + let external_signatures = builder.external_signatures()?; + let unsigned_msg = builder.build()?; + + let encoded_unsigned = bincode::serialize(&unsigned_msg) + .tw_err(|_| SigningErrorType::Error_internal) + .context("Error serializing Solana Message as 'bincode'")?; + let encoded_unsigned = encode(&encoded_unsigned); + + let signed_tx = + TxSigner::sign_versioned(unsigned_msg, &signing_keys, &external_signatures)?; + + let encoded_tx = bincode::serialize(&signed_tx) + .tw_err(|_| SigningErrorType::Error_internal) + .context("Error serializing Solana Transaction as 'bincode'")?; + let encoded_tx = encode(&encoded_tx); + + Ok(Proto::SigningOutput { + encoded: Cow::from(encoded_tx), + unsigned_tx: Cow::from(encoded_unsigned), + signatures: ProtoBuilder::build_signatures(&signed_tx), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/chains/tw_solana/src/transaction/legacy.rs b/rust/chains/tw_solana/src/transaction/legacy.rs new file mode 100644 index 00000000000..dae3bf13949 --- /dev/null +++ b/rust/chains/tw_solana/src/transaction/legacy.rs @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SolanaAddress; +use crate::transaction::{short_vec, CompiledInstruction, MessageHeader, Signature}; +use serde::{Deserialize, Serialize}; +use tw_hash::{as_byte_sequence, H256}; + +#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Message { + /// The message header, identifying signed and read-only `account_keys`. + // NOTE: Serialization-related changes must be paired with the direct read at sigverify. + pub header: MessageHeader, + + /// All the account keys used by this transaction. + #[serde(with = "short_vec")] + pub account_keys: Vec, + + /// The id of a recent ledger entry. + #[serde(with = "as_byte_sequence")] + pub recent_blockhash: H256, + + /// Programs that will be executed in sequence and committed in one atomic transaction if all + /// succeed. + #[serde(with = "short_vec")] + pub instructions: Vec, +} + +#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)] +pub struct Transaction { + /// A set of signatures of a serialized [`Message`], signed by the first + /// keys of the `Message`'s [`account_keys`], where the number of signatures + /// is equal to [`num_required_signatures`] of the `Message`'s + /// [`MessageHeader`]. + /// + /// [`account_keys`]: Message::account_keys + /// [`MessageHeader`]: crate::message::MessageHeader + /// [`num_required_signatures`]: crate::message::MessageHeader::num_required_signatures + // NOTE: Serialization-related changes must be paired with the direct read at sigverify. + #[serde(with = "short_vec")] + pub signatures: Vec, + + /// The message to sign. + pub message: Message, +} diff --git a/rust/chains/tw_solana/src/transaction/mod.rs b/rust/chains/tw_solana/src/transaction/mod.rs new file mode 100644 index 00000000000..a37cdc75963 --- /dev/null +++ b/rust/chains/tw_solana/src/transaction/mod.rs @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::SOLANA_ALPHABET; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::str::FromStr; +use tw_coin_entry::error::prelude::*; +use tw_encoding::base58; +use tw_hash::{as_byte_sequence, H512}; + +pub mod legacy; +pub mod short_vec; +pub mod v0; +pub mod versioned; + +#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, Copy)] +#[serde(rename_all = "camelCase")] +pub struct MessageHeader { + /// The number of signatures required for this message to be considered + /// valid. The signers of those signatures must match the first + /// `num_required_signatures` of [`Message::account_keys`]. + // NOTE: Serialization-related changes must be paired with the direct read at sigverify. + pub num_required_signatures: u8, + + /// The last `num_readonly_signed_accounts` of the signed keys are read-only + /// accounts. + pub num_readonly_signed_accounts: u8, + + /// The last `num_readonly_unsigned_accounts` of the unsigned keys are + /// read-only accounts. + pub num_readonly_unsigned_accounts: u8, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct CompiledInstruction { + /// Index into the transaction keys array indicating the program account that executes this instruction. + pub program_id_index: u8, + /// Ordered indices into the transaction keys array indicating which accounts to pass to the program. + #[serde(with = "short_vec")] + pub accounts: Vec, + /// The program input data. + #[serde(with = "short_vec")] + pub data: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy, Eq, PartialEq, Hash)] +pub struct Signature(#[serde(with = "as_byte_sequence")] pub H512); + +impl FromStr for Signature { + type Err = SigningError; + + fn from_str(s: &str) -> Result { + let data = base58::decode(s, SOLANA_ALPHABET) + .tw_err(|_| SigningErrorType::Error_input_parse) + .context("Error decoding Solana Signature from base58")?; + H512::try_from(data.as_slice()) + .map(Signature) + .tw_err(|_| SigningErrorType::Error_input_parse) + .context("Solana Signature must be 64 byte length") + } +} + +impl fmt::Display for Signature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", base58::encode(self.0.as_slice(), SOLANA_ALPHABET)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::address::SolanaAddress; + use crate::transaction::v0::MessageAddressTableLookup; + use crate::transaction::versioned::{VersionedMessage, VersionedTransaction}; + use crate::SOLANA_ALPHABET; + use tw_encoding::base58; + use tw_encoding::base64::{self, STANDARD}; + use tw_encoding::hex::ToHex; + use tw_hash::H256; + use tw_memory::Data; + + fn base58_decode(s: &'static str) -> Data { + base58::decode(s, SOLANA_ALPHABET).unwrap() + } + + fn base58_decode_h256(s: &'static str) -> H256 { + let bytes = base58::decode(s, SOLANA_ALPHABET).unwrap(); + H256::try_from(bytes.as_slice()).unwrap() + } + + #[test] + fn test_rango_transaction_ser_de() { + let serialized = base64::decode("AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAHEIoR5xuWyrvjIW4xU7CWlPOfyFAiy8B295hGo6tNjBmRCgUkQaFYTleMcAX2p74eBXQZd1dwDyQZAPJfSv2KGc5kcFLJj5qd2BVMaSNGVPfVBm74GbLwUq5/U1Ccdqc2gokZQxRDpMq7aeToP3nRaWIP4RXMxN+LJetccXMPq/QumgOqt7kkqk07cyPCKgYoQ4fQtOqqZn5sEqjWHYj3CDS5ha48uggePWu090s1ff4yoCjAvULeZ+cqYFn+Adk5Teyfw71W3u/F6VTnLQEPW96gJr5Kcm3bGi08n224JyF++PTko52VL0CIM2xtl0WkvNslD6Wawxr7yd9HYllN4Lz8lFwXilWGgyJdOq1qqBuZbE49glHeCO/sJHNnIHC0BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAEedVb8jHAbu50xW7OaBUH/bGy3qP0jlECsc2iVrwTjwbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpjJclj04kifG7PRApFI4NgwtaE5na/xCEBI572Nvp+Fm0P/on9df2SnTAmx8pWHneSwmrNt/J3VFLMhqns4zl6OL4d+g9rsaIj0Orta57MRu3jDSWCJf85ae4LBbiD/GXvOojZjsHekJrpRUuPggLJr943hDVD5UareeEucjCvaoHCgAFAsBcFQAKAAkDBBcBAAAAAAANBgAGACMJDAEBCQIABgwCAAAAAMqaOwAAAAAMAQYBEQs1DA8ABgEFAiMhCwsOCx0MDxoBGQcYBAgDJBscDB4PBwUQEhEfFR8UFwcFISITHw8MDCAfFgstwSCbM0HWnIEAAwAAABEBZAABCh0BAyZHAQMAypo7AAAAAJaWFAYAAAAAMgAADAMGAAABCQPZoILFk7gfE2y5bt3AC+g/4OwNzdiHKBhIbdeYvYFEjQPKyMkExMUkx0R25UNa/g5KsG0vfUwdUJ8e8HecK/Jkd3qm9XefBOB0BaD1+J+dBJz09vfyGuRYZH09HfdE/kL8v6Ql+H03+tO+9lMmmVg8O1c6gAN6eX0Cbn4=", STANDARD).unwrap(); + let actual: VersionedTransaction = bincode::deserialize(&serialized).unwrap(); + + let expected = VersionedTransaction { + signatures: vec![Signature(H512::default())], + message: VersionedMessage::V0(v0::Message { + header: MessageHeader { + num_required_signatures: 1, + num_readonly_signed_accounts: 0, + num_readonly_unsigned_accounts: 7, + }, + account_keys: vec![ + SolanaAddress::from("AHy6YZA8BsHgQfVkk7MbwpAN94iyN7Nf1zN4nPqUN32Q"), + SolanaAddress::from("g7dD1FHSemkUQrX1Eak37wzvDjscgBW2pFCENwjLdMX"), + SolanaAddress::from("7m57LBTxtzhWn6WdFxKtnoJLBQXyNERLYebebXLVaKy3"), + SolanaAddress::from("AEBCPtV8FFkWFAKxrz7mbYvobpkZuWaRWQCyJVRaheUD"), + SolanaAddress::from("BND2ehwWVeHVA5EtMm2b7Vu51AT8f2PNWusS9KQX5moy"), + SolanaAddress::from("DVCeozFGbe6ew3eWTnZByjHeYqTq1cvbrB7JJhkLxaRJ"), + SolanaAddress::from("GvgWmk8iPACw1AEMt47WzkuTkKoSGbn4Xk3aLM8vdbJD"), + SolanaAddress::from("HkphEpUqnFBxBuCPEq5j1HA9L8EwmsmRT6UcFKziptM1"), + SolanaAddress::from("Hzxx6b5a7dmmJeDXLQzr4dTrc2HGK9ar5YRakZgr3ZZ7"), + SolanaAddress::from("11111111111111111111111111111111"), + SolanaAddress::from("ComputeBudget111111111111111111111111111111"), + SolanaAddress::from("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"), + SolanaAddress::from("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), + SolanaAddress::from("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"), + SolanaAddress::from("D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf"), + SolanaAddress::from("GGztQqQ6pCPaJQnNpXBgELr5cs3WwDakRbh1iEMzjgSJ"), + ], + recent_blockhash: base58_decode_h256( + "DiSimxK2z1cRa6yD4goqte3rDMmghJAD8WDUZEab2CzD", + ), + instructions: vec![ + CompiledInstruction { + program_id_index: 10, + accounts: vec![], + data: base58_decode("K1FDJ7"), + }, + CompiledInstruction { + program_id_index: 10, + accounts: vec![], + data: base58_decode("3E9ErJ5MrzbZ"), + }, + CompiledInstruction { + program_id_index: 13, + accounts: vec![0, 6, 0, 35, 9, 12], + data: base58_decode("2"), + }, + CompiledInstruction { + program_id_index: 9, + accounts: vec![0, 6], + data: base58_decode("3Bxs3zzLZLuLQEYX"), + }, + CompiledInstruction { + program_id_index: 12, + accounts: vec![6], + data: base58_decode("J"), + }, + CompiledInstruction { + program_id_index: 11, + accounts: vec![ + 12, 15, 0, 6, 1, 5, 2, 35, 33, 11, 11, 14, 11, 29, 12, 15, 26, 1, 25, + 7, 24, 4, 8, 3, 36, 27, 28, 12, 30, 15, 7, 5, 16, 18, 17, 31, 21, 31, + 20, 23, 7, 5, 33, 34, 19, 31, 15, 12, 12, 32, 31, 22, 11, + ], + data: base58_decode( + "5n9zLuyvSGkuf4iDD6PfDvzvzehUkDghmApUkZSXSx57jF9RGSH5Y23tzFJDG3", + ), + }, + CompiledInstruction { + program_id_index: 12, + accounts: vec![6, 0, 0], + data: base58_decode("A"), + }, + ], + address_table_lookups: vec![ + MessageAddressTableLookup { + account_key: SolanaAddress::from( + "FeXRmSWmwChZbB2EC7Qjw9XKk28yBrPj3k3nzT1DKfak", + ), + writable_indexes: vec![202, 200, 201], + readonly_indexes: vec![196, 197, 36, 199], + }, + MessageAddressTableLookup { + account_key: SolanaAddress::from( + "5cFsmTCEfmvpBUBHqsWZnf9n5vTWLYH2LT8X7HdShwxP", + ), + writable_indexes: vec![160, 245, 248, 159, 157], + readonly_indexes: vec![156, 244, 246, 247], + }, + MessageAddressTableLookup { + account_key: SolanaAddress::from( + "HJ5StCvsDU4JsvK39VcsHjaoTRTtQU749MQ9qUsJaG1m", + ), + writable_indexes: vec![122, 121, 125], + readonly_indexes: vec![110, 126], + }, + ], + }), + }; + + assert_eq!(actual, expected); + + let serialized_again = bincode::serialize(&actual).unwrap(); + assert_eq!(serialized_again.to_hex(), serialized.to_hex()); + } +} diff --git a/rust/chains/tw_solana/src/transaction/short_vec.rs b/rust/chains/tw_solana/src/transaction/short_vec.rs new file mode 100644 index 00000000000..5001a942902 --- /dev/null +++ b/rust/chains/tw_solana/src/transaction/short_vec.rs @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +//! Compact serde-encoding of vectors with small length. +//! Source code: https://github.com/solana-labs/solana/blob/a16f982169eb197fad0eb8c58c307fb069f69d8f/sdk/program/src/short_vec.rs + +#![allow(clippy::arithmetic_side_effects)] +use serde::{ + de::{self, Deserializer, SeqAccess, Visitor}, + ser::{self, SerializeTuple, Serializer}, + Deserialize, Serialize, +}; +use std::{convert::TryFrom, fmt, marker::PhantomData}; + +/// Same as u16, but serialized with 1 to 3 bytes. If the value is above +/// 0x7f, the top bit is set and the remaining value is stored in the next +/// bytes. Each byte follows the same pattern until the 3rd byte. The 3rd +/// byte, if needed, uses all 8 bits to store the last byte of the original +/// value. +pub struct ShortU16(pub u16); + +impl Serialize for ShortU16 { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // Pass a non-zero value to serialize_tuple() so that serde_json will + // generate an open bracket. + let mut seq = serializer.serialize_tuple(1)?; + + let mut rem_val = self.0; + loop { + let mut elem = (rem_val & 0x7f) as u8; + rem_val >>= 7; + if rem_val == 0 { + seq.serialize_element(&elem)?; + break; + } else { + elem |= 0x80; + seq.serialize_element(&elem)?; + } + } + seq.end() + } +} + +enum VisitStatus { + Done(u16), + More(u16), +} + +#[derive(Debug)] +enum VisitError { + TooLong(usize), + TooShort(usize), + Overflow(u32), + Alias, + ByteThreeContinues, +} + +impl VisitError { + fn into_de_error<'de, A>(self) -> A::Error + where + A: SeqAccess<'de>, + { + match self { + VisitError::TooLong(len) => de::Error::invalid_length(len, &"three or fewer bytes"), + VisitError::TooShort(len) => de::Error::invalid_length(len, &"more bytes"), + VisitError::Overflow(val) => de::Error::invalid_value( + de::Unexpected::Unsigned(val as u64), + &"a value in the range [0, 65535]", + ), + VisitError::Alias => de::Error::invalid_value( + de::Unexpected::Other("alias encoding"), + &"strict form encoding", + ), + VisitError::ByteThreeContinues => de::Error::invalid_value( + de::Unexpected::Other("continue signal on byte-three"), + &"a terminal signal on or before byte-three", + ), + } + } +} + +type VisitResult = Result; + +const MAX_ENCODING_LENGTH: usize = 3; +fn visit_byte(elem: u8, val: u16, nth_byte: usize) -> VisitResult { + if elem == 0 && nth_byte != 0 { + return Err(VisitError::Alias); + } + + let val = u32::from(val); + let elem = u32::from(elem); + let elem_val = elem & 0x7f; + let elem_done = (elem & 0x80) == 0; + + if nth_byte >= MAX_ENCODING_LENGTH { + return Err(VisitError::TooLong(nth_byte.saturating_add(1))); + } else if nth_byte == MAX_ENCODING_LENGTH.saturating_sub(1) && !elem_done { + return Err(VisitError::ByteThreeContinues); + } + + let shift = u32::try_from(nth_byte) + .unwrap_or(u32::MAX) + .saturating_mul(7); + let elem_val = elem_val.checked_shl(shift).unwrap_or(u32::MAX); + + let new_val = val | elem_val; + let val = u16::try_from(new_val).map_err(|_| VisitError::Overflow(new_val))?; + + if elem_done { + Ok(VisitStatus::Done(val)) + } else { + Ok(VisitStatus::More(val)) + } +} + +struct ShortU16Visitor; + +impl<'de> Visitor<'de> for ShortU16Visitor { + type Value = ShortU16; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a ShortU16") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + // Decodes an unsigned 16 bit integer one-to-one encoded as follows: + // 1 byte : 0xxxxxxx => 00000000 0xxxxxxx : 0 - 127 + // 2 bytes : 1xxxxxxx 0yyyyyyy => 00yyyyyy yxxxxxxx : 128 - 16,383 + // 3 bytes : 1xxxxxxx 1yyyyyyy 000000zz => zzyyyyyy yxxxxxxx : 16,384 - 65,535 + let mut val: u16 = 0; + for nth_byte in 0..MAX_ENCODING_LENGTH { + let elem: u8 = seq.next_element()?.ok_or_else(|| { + VisitError::TooShort(nth_byte.saturating_add(1)).into_de_error::() + })?; + match visit_byte(elem, val, nth_byte).map_err(|e| e.into_de_error::())? { + VisitStatus::Done(new_val) => return Ok(ShortU16(new_val)), + VisitStatus::More(new_val) => val = new_val, + } + } + + Err(VisitError::ByteThreeContinues.into_de_error::()) + } +} + +impl<'de> Deserialize<'de> for ShortU16 { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_tuple(3, ShortU16Visitor) + } +} + +/// If you don't want to use the ShortVec newtype, you can do ShortVec +/// serialization on an ordinary vector with the following field annotation: +/// +/// #[serde(with = "short_vec")] +/// +pub fn serialize( + elements: &[T], + serializer: S, +) -> Result { + // Pass a non-zero value to serialize_tuple() so that serde_json will + // generate an open bracket. + let mut seq = serializer.serialize_tuple(1)?; + + let len = elements.len(); + if len > u16::MAX as usize { + return Err(ser::Error::custom("length larger than u16")); + } + let short_len = ShortU16(len as u16); + seq.serialize_element(&short_len)?; + + for element in elements { + seq.serialize_element(element)?; + } + seq.end() +} + +struct ShortVecVisitor { + _t: PhantomData, +} + +impl<'de, T> Visitor<'de> for ShortVecVisitor +where + T: Deserialize<'de>, +{ + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a Vec with a multi-byte length") + } + + fn visit_seq(self, mut seq: A) -> Result, A::Error> + where + A: SeqAccess<'de>, + { + let short_len: ShortU16 = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(0, &self))?; + let len = short_len.0 as usize; + + let mut result = Vec::with_capacity(len); + for i in 0..len { + let elem = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(i, &self))?; + result.push(elem); + } + Ok(result) + } +} + +/// If you don't want to use the ShortVec newtype, you can do ShortVec +/// deserialization on an ordinary vector with the following field annotation: +/// +/// #[serde(with = "short_vec")] +/// +pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: Deserialize<'de>, +{ + let visitor = ShortVecVisitor { _t: PhantomData }; + deserializer.deserialize_tuple(usize::MAX, visitor) +} + +pub struct ShortVec(pub Vec); + +impl Serialize for ShortVec { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serialize(&self.0, serializer) + } +} + +impl<'de, T: Deserialize<'de>> Deserialize<'de> for ShortVec { + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + deserialize(deserializer).map(ShortVec) + } +} + +/// Return the decoded value and how many bytes it consumed. +#[allow(clippy::result_unit_err)] +pub fn decode_shortu16_len(bytes: &[u8]) -> Result<(usize, usize), ()> { + let mut val = 0; + for (nth_byte, byte) in bytes.iter().take(MAX_ENCODING_LENGTH).enumerate() { + match visit_byte(*byte, val, nth_byte).map_err(|_| ())? { + VisitStatus::More(new_val) => val = new_val, + VisitStatus::Done(new_val) => { + return Ok((usize::from(new_val), nth_byte.saturating_add(1))); + }, + } + } + Err(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use bincode::{deserialize, serialize}; + + /// Return the serialized length. + fn encode_len(len: u16) -> Vec { + bincode::serialize(&ShortU16(len)).unwrap() + } + + fn assert_len_encoding(len: u16, bytes: &[u8]) { + assert_eq!(encode_len(len), bytes, "unexpected usize encoding"); + assert_eq!( + decode_shortu16_len(bytes).unwrap(), + (usize::from(len), bytes.len()), + "unexpected usize decoding" + ); + } + + #[test] + fn test_short_vec_encode_len() { + assert_len_encoding(0x0, &[0x0]); + assert_len_encoding(0x7f, &[0x7f]); + assert_len_encoding(0x80, &[0x80, 0x01]); + assert_len_encoding(0xff, &[0xff, 0x01]); + assert_len_encoding(0x100, &[0x80, 0x02]); + assert_len_encoding(0x7fff, &[0xff, 0xff, 0x01]); + assert_len_encoding(0xffff, &[0xff, 0xff, 0x03]); + } + + fn assert_good_deserialized_value(value: u16, bytes: &[u8]) { + assert_eq!(value, deserialize::(bytes).unwrap().0); + } + + fn assert_bad_deserialized_value(bytes: &[u8]) { + assert!(deserialize::(bytes).is_err()); + } + + #[test] + fn test_deserialize() { + assert_good_deserialized_value(0x0000, &[0x00]); + assert_good_deserialized_value(0x007f, &[0x7f]); + assert_good_deserialized_value(0x0080, &[0x80, 0x01]); + assert_good_deserialized_value(0x00ff, &[0xff, 0x01]); + assert_good_deserialized_value(0x0100, &[0x80, 0x02]); + assert_good_deserialized_value(0x07ff, &[0xff, 0x0f]); + assert_good_deserialized_value(0x3fff, &[0xff, 0x7f]); + assert_good_deserialized_value(0x4000, &[0x80, 0x80, 0x01]); + assert_good_deserialized_value(0xffff, &[0xff, 0xff, 0x03]); + + // aliases + // 0x0000 + assert_bad_deserialized_value(&[0x80, 0x00]); + assert_bad_deserialized_value(&[0x80, 0x80, 0x00]); + // 0x007f + assert_bad_deserialized_value(&[0xff, 0x00]); + assert_bad_deserialized_value(&[0xff, 0x80, 0x00]); + // 0x0080 + assert_bad_deserialized_value(&[0x80, 0x81, 0x00]); + // 0x00ff + assert_bad_deserialized_value(&[0xff, 0x81, 0x00]); + // 0x0100 + assert_bad_deserialized_value(&[0x80, 0x82, 0x00]); + // 0x07ff + assert_bad_deserialized_value(&[0xff, 0x8f, 0x00]); + // 0x3fff + assert_bad_deserialized_value(&[0xff, 0xff, 0x00]); + + // too short + assert_bad_deserialized_value(&[]); + assert_bad_deserialized_value(&[0x80]); + + // too long + assert_bad_deserialized_value(&[0x80, 0x80, 0x80, 0x00]); + + // too large + // 0x0001_0000 + assert_bad_deserialized_value(&[0x80, 0x80, 0x04]); + // 0x0001_8000 + assert_bad_deserialized_value(&[0x80, 0x80, 0x06]); + } + + #[test] + fn test_short_vec_u8() { + let vec = ShortVec(vec![4u8; 32]); + let bytes = serialize(&vec).unwrap(); + assert_eq!(bytes.len(), vec.0.len() + 1); + + let vec1: ShortVec = deserialize(&bytes).unwrap(); + assert_eq!(vec.0, vec1.0); + } + + #[test] + fn test_short_vec_u8_too_long() { + let vec = ShortVec(vec![4u8; u16::MAX as usize]); + assert!(matches!(serialize(&vec), Ok(_))); + + let vec = ShortVec(vec![4u8; u16::MAX as usize + 1]); + assert!(matches!(serialize(&vec), Err(_))); + } + + #[test] + fn test_short_vec_json() { + let vec = ShortVec(vec![0, 1, 2]); + let s = serde_json::to_string(&vec).unwrap(); + assert_eq!(s, "[[3],0,1,2]"); + } + + #[test] + fn test_short_vec_aliased_length() { + let bytes = [ + 0x81, 0x80, 0x00, // 3-byte alias of 1 + 0x00, + ]; + assert!(deserialize::>(&bytes).is_err()); + } +} diff --git a/rust/chains/tw_solana/src/transaction/v0.rs b/rust/chains/tw_solana/src/transaction/v0.rs new file mode 100644 index 00000000000..8b1a67d0cc9 --- /dev/null +++ b/rust/chains/tw_solana/src/transaction/v0.rs @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SolanaAddress; +use crate::transaction::{short_vec, CompiledInstruction, MessageHeader}; +use serde::{Deserialize, Serialize}; +use tw_hash::{as_byte_sequence, H256}; + +#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct MessageAddressTableLookup { + /// Address lookup table account key + pub account_key: SolanaAddress, + /// List of indexes used to load writable account addresses + #[serde(with = "short_vec")] + pub writable_indexes: Vec, + /// List of indexes used to load readonly account addresses + #[serde(with = "short_vec")] + pub readonly_indexes: Vec, +} + +#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Message { + /// The message header, identifying signed and read-only `account_keys`. + /// Header values only describe static `account_keys`, they do not describe + /// any additional account keys loaded via address table lookups. + pub header: MessageHeader, + + /// List of accounts loaded by this transaction. + #[serde(with = "short_vec")] + pub account_keys: Vec, + + /// The blockhash of a recent block. + #[serde(with = "as_byte_sequence")] + pub recent_blockhash: H256, + + /// Instructions that invoke a designated program, are executed in sequence, + /// and committed in one atomic transaction if all succeed. + /// + /// # Notes + /// + /// Program indexes must index into the list of message `account_keys` because + /// program id's cannot be dynamically loaded from a lookup table. + /// + /// Account indexes must index into the list of addresses + /// constructed from the concatenation of three key lists: + /// 1) message `account_keys` + /// 2) ordered list of keys loaded from `writable` lookup table indexes + /// 3) ordered list of keys loaded from `readable` lookup table indexes + #[serde(with = "short_vec")] + pub instructions: Vec, + + /// List of address table lookups used to load additional accounts + /// for this transaction. + #[serde(with = "short_vec")] + pub address_table_lookups: Vec, +} diff --git a/rust/chains/tw_solana/src/transaction/versioned.rs b/rust/chains/tw_solana/src/transaction/versioned.rs new file mode 100644 index 00000000000..ade097c4b7d --- /dev/null +++ b/rust/chains/tw_solana/src/transaction/versioned.rs @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +//! Source code: https://github.com/solana-labs/solana/blob/a16f982169eb197fad0eb8c58c307fb069f69d8f/sdk/program/src/message/versions/mod.rs + +use crate::address::SolanaAddress; +use crate::blockhash::Blockhash; +use crate::transaction::{legacy, short_vec, v0, CompiledInstruction, MessageHeader, Signature}; +use serde::de::{SeqAccess, Unexpected, Visitor}; +use serde::ser::SerializeTuple; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt; +use tw_hash::{as_byte_sequence, H256}; + +/// Bit mask that indicates whether a serialized message is versioned. +pub const MESSAGE_VERSION_PREFIX: u8 = 0x80; + +// NOTE: Serialization-related changes must be paired with the direct read at sigverify. +/// An atomic transaction +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct VersionedTransaction { + /// List of signatures + #[serde(with = "short_vec")] + pub signatures: Vec, + /// Message to sign. + pub message: VersionedMessage, +} + +impl VersionedTransaction { + pub fn unsigned(message: VersionedMessage) -> VersionedTransaction { + VersionedTransaction { + signatures: vec![Signature::default(); message.num_required_signatures()], + message, + } + } +} + +/// Either a legacy message or a v0 message. +/// +/// # Serialization +/// +/// If the first bit is set, the remaining 7 bits will be used to determine +/// which message version is serialized starting from version `0`. If the first +/// is bit is not set, all bytes are used to encode the legacy `Message` +/// format. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum VersionedMessage { + Legacy(legacy::Message), + V0(v0::Message), +} + +impl VersionedMessage { + pub fn header(&self) -> &MessageHeader { + match self { + VersionedMessage::Legacy(legacy) => &legacy.header, + VersionedMessage::V0(v0) => &v0.header, + } + } + + pub fn account_keys(&self) -> &[SolanaAddress] { + match self { + VersionedMessage::Legacy(legacy) => &legacy.account_keys, + VersionedMessage::V0(v0) => &v0.account_keys, + } + } + + pub fn signers(&self) -> impl Iterator { + let signatures_count = self.num_required_signatures(); + match self { + VersionedMessage::Legacy(legacy) => &legacy.account_keys, + VersionedMessage::V0(v0) => &v0.account_keys, + } + .iter() + .take(signatures_count) + } + + pub fn recent_blockhash(&self) -> Blockhash { + match self { + VersionedMessage::Legacy(legacy) => Blockhash::with_bytes(legacy.recent_blockhash), + VersionedMessage::V0(v0) => Blockhash::with_bytes(v0.recent_blockhash), + } + } + + pub fn instructions(&self) -> &[CompiledInstruction] { + match self { + VersionedMessage::Legacy(legacy) => &legacy.instructions, + VersionedMessage::V0(v0) => &v0.instructions, + } + } + + pub fn num_required_signatures(&self) -> usize { + match self { + VersionedMessage::Legacy(legacy) => legacy.header.num_required_signatures as usize, + VersionedMessage::V0(v0) => v0.header.num_required_signatures as usize, + } + } + + pub fn get_account_index(&self, account: SolanaAddress) -> Option { + let account_keys = match self { + VersionedMessage::Legacy(legacy) => &legacy.account_keys, + VersionedMessage::V0(v0) => &v0.account_keys, + }; + account_keys.iter().position(|pk| *pk == account) + } + + pub fn set_recent_blockhash(&mut self, recent_blockhash: H256) { + match self { + VersionedMessage::Legacy(legacy) => legacy.recent_blockhash = recent_blockhash, + VersionedMessage::V0(v0) => v0.recent_blockhash = recent_blockhash, + } + } +} + +impl Serialize for VersionedMessage { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Self::Legacy(message) => { + let mut seq = serializer.serialize_tuple(1)?; + seq.serialize_element(message)?; + seq.end() + }, + Self::V0(message) => { + let mut seq = serializer.serialize_tuple(2)?; + seq.serialize_element(&MESSAGE_VERSION_PREFIX)?; + seq.serialize_element(message)?; + seq.end() + }, + } + } +} + +enum MessagePrefix { + Legacy(u8), + Versioned(u8), +} + +impl<'de> Deserialize<'de> for MessagePrefix { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct PrefixVisitor; + + impl<'de> Visitor<'de> for PrefixVisitor { + type Value = MessagePrefix; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("message prefix byte") + } + + // Serde's integer visitors bubble up to u64 so check the prefix + // with this function instead of visit_u8. This approach is + // necessary because serde_json directly calls visit_u64 for + // unsigned integers. + fn visit_u64(self, value: u64) -> Result { + if value > u8::MAX as u64 { + Err(de::Error::invalid_type(Unexpected::Unsigned(value), &self))?; + } + + let byte = value as u8; + if byte & MESSAGE_VERSION_PREFIX != 0 { + Ok(MessagePrefix::Versioned(byte & !MESSAGE_VERSION_PREFIX)) + } else { + Ok(MessagePrefix::Legacy(byte)) + } + } + } + + deserializer.deserialize_u8(PrefixVisitor) + } +} + +impl<'de> Deserialize<'de> for VersionedMessage { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct MessageVisitor; + + impl<'de> Visitor<'de> for MessageVisitor { + type Value = VersionedMessage; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("message bytes") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let prefix: MessagePrefix = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(0, &self))?; + + match prefix { + MessagePrefix::Legacy(num_required_signatures) => { + // The remaining fields of the legacy Message struct after the first byte. + #[derive(Serialize, Deserialize)] + struct RemainingLegacyMessage { + pub num_readonly_signed_accounts: u8, + pub num_readonly_unsigned_accounts: u8, + #[serde(with = "short_vec")] + pub account_keys: Vec, + #[serde(with = "as_byte_sequence")] + pub recent_blockhash: H256, + #[serde(with = "short_vec")] + pub instructions: Vec, + } + + let message: RemainingLegacyMessage = + seq.next_element()?.ok_or_else(|| { + // will never happen since tuple length is always 2 + de::Error::invalid_length(1, &self) + })?; + + Ok(VersionedMessage::Legacy(legacy::Message { + header: MessageHeader { + num_required_signatures, + num_readonly_signed_accounts: message.num_readonly_signed_accounts, + num_readonly_unsigned_accounts: message + .num_readonly_unsigned_accounts, + }, + account_keys: message.account_keys, + recent_blockhash: message.recent_blockhash, + instructions: message.instructions, + })) + }, + MessagePrefix::Versioned(version) => { + match version { + 0 => { + Ok(VersionedMessage::V0(seq.next_element()?.ok_or_else( + || { + // will never happen since tuple length is always 2 + de::Error::invalid_length(1, &self) + }, + )?)) + }, + 127 => { + // 0xff is used as the first byte of the off-chain messages + // which corresponds to version 127 of the versioned messages. + // This explicit check is added to prevent the usage of version 127 + // in the runtime as a valid transaction. + Err(de::Error::custom("off-chain messages are not accepted")) + }, + _ => Err(de::Error::invalid_value( + de::Unexpected::Unsigned(version as u64), + &"a valid transaction message version", + )), + } + }, + } + } + } + + deserializer.deserialize_tuple(2, MessageVisitor) + } +} diff --git a/rust/chains/tw_solana/tests/get_default_token_address.rs b/rust/chains/tw_solana/tests/get_default_token_address.rs new file mode 100644 index 00000000000..4f075dde885 --- /dev/null +++ b/rust/chains/tw_solana/tests/get_default_token_address.rs @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::str::FromStr; +use tw_solana::address::SolanaAddress; +use tw_solana::blockhash::Blockhash; +use tw_solana::defined_addresses::TOKEN_PROGRAM_ID_ADDRESS; +use tw_solana::program::stake_program::StakeProgram; + +fn test_get_default_token_address_impl( + main_address: &str, + token_mint_address: &str, + expected: &str, +) { + let main_address = SolanaAddress::from_str(main_address).unwrap(); + let token_mint_address = SolanaAddress::from_str(token_mint_address).unwrap(); + let expected = SolanaAddress::from_str(expected).unwrap(); + + let actual = StakeProgram::get_associated_token_address( + main_address, + *TOKEN_PROGRAM_ID_ADDRESS, + token_mint_address, + ) + .expect("!get_associated_token_address"); + assert_eq!(actual, expected); +} + +#[test] +fn test_get_default_token_address() { + test_get_default_token_address_impl( + "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V", + "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt", + "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP", + ); + test_get_default_token_address_impl( + "FtsZbVDYGBUw4R5Rcy8p58RAdYRFUJcAiBRdQAona7t1", + "FQYWEccPpAVR5Q16FKoTFUa6z8BWzdK5eh3D586fdQbM", + "2XGUJePx3CJSYQvAQcSgLf7xpjCdMUgx8LNaSCUhE2LS", + ); + test_get_default_token_address_impl( + "74nsHsFivzUPLEygULuZLs193MnDNZfnKrSEkgA4qkY7", + "FQYWEccPpAVR5Q16FKoTFUa6z8BWzdK5eh3D586fdQbM", + "823YRj6GozU23rwSyd1f78vPopVoQPCeK79ppWCu5SYQ", + ); + test_get_default_token_address_impl( + "HBYC51YrGFAZ8rM7Sj8e9uqKggpSrDYrinQDZzvMtqQp", + "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt", + "6X4X1Ae24mkoWeCEpktevySVG9jzeCufut5vtUW3wFrD", + ); + test_get_default_token_address_impl( + "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ", + "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt", + "ANVCrmRw7Ww7rTFfMbrjApSPXEEcZpBa6YEiBdf98pAf", + ); +} + +fn test_address_from_recent_blockhash_impl(main_address: &str, blockhash: &str, expected: &str) { + let main_address = SolanaAddress::from_str(main_address).unwrap(); + let expected = SolanaAddress::from_str(expected).unwrap(); + let blockhash = Blockhash::from_str(blockhash).unwrap(); + + let actual = StakeProgram::address_from_recent_blockhash(&main_address, &blockhash); + assert_eq!(actual, expected); +} + +#[test] +fn test_address_from_recent_blockhash() { + test_address_from_recent_blockhash_impl( + "zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu", + "11111111111111111111111111111111", + "GQDDc5EVGJZFC7AvpEJ8eoCQ75Yy4gr7eu17frCjvQRQ", + ); + test_address_from_recent_blockhash_impl( + "zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu", + "9ipJh5xfyoyDaiq8trtrdqQeAhQbQkWy2eANizKvx75K", + "2Kos1xJRBq3Ae1GnVNBx7HgJhq8KvdUe2bXE4QGdNaXb", + ); + test_address_from_recent_blockhash_impl( + "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V", + "AfzzEC8NVXoxKoHdjXLDVzqwqvvZmgPuqyJqjuHiPY1D", + "Fxhhm82PZVuXEwycT28vGqknUEnVeoHh4UWEnJNQUDbv", + ); +} diff --git a/rust/chains/tw_solana/tests/update_blockhash_and_sign.rs b/rust/chains/tw_solana/tests/update_blockhash_and_sign.rs new file mode 100644 index 00000000000..5521e7069af --- /dev/null +++ b/rust/chains/tw_solana/tests/update_blockhash_and_sign.rs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::error::prelude::*; +use tw_encoding::base58; +use tw_solana::modules::utils::SolanaTransaction; +use tw_solana::SOLANA_ALPHABET; + +#[test] +fn test_update_recent_blockhash_and_sign() { + // base64 encoded + let encoded_tx = "AQPWaOi7dMdmQpXi8HyQQKwiqIftrg1igGQxGtZeT50ksn4wAnyH4DtDrkkuE0fqgx80LTp4LwNN9a440SrmoA8BAAEDZsL1CMnFVcrMn7JtiOiN1U4hC7WovOVof2DX51xM0H/GizyJTHgrBanCf8bGbrFNTn0x3pCGq30hKbywSTr6AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgIAAQwCAAAAKgAAAAAAAAA="; + // base58 encoded + let new_blockhash = "CyPYVsYWrsJNfVpi8aazu7WsrswNFuDd385z6GNoBGUg"; + let private_key = base58::decode( + "A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr", + SOLANA_ALPHABET, + ) + .unwrap(); + + let output = + SolanaTransaction::update_blockhash_and_sign(encoded_tx, new_blockhash, &[private_key]); + assert_eq!(output.error, SigningErrorType::OK); + let expected = "AdQl49kO1FxfkAnAuK9KSQEGLzxHNYLqBrYGFN711q7aT/qyrzYMn/7/IdFBy6yMhjOA1CkwZsgmqmbu+XKvVAUBAAEDZsL1CMnFVcrMn7JtiOiN1U4hC7WovOVof2DX51xM0H/GizyJTHgrBanCf8bGbrFNTn0x3pCGq30hKbywSTr6AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAseKSLpOE0fdq67Jk9Ckme2c3SYD//nqcykr/oci67zEBAgIAAQwCAAAAKgAAAAAAAAA="; + assert_eq!(output.encoded, expected); +} diff --git a/rust/chains/tw_sui/Cargo.toml b/rust/chains/tw_sui/Cargo.toml new file mode 100644 index 00000000000..1aa3d7cabb3 --- /dev/null +++ b/rust/chains/tw_sui/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "tw_sui" +version = "0.1.0" +edition = "2021" + +[dependencies] +indexmap = "2.0" +move-core-types = { git = "https://github.com/move-language/move", rev = "ea70797099baea64f05194a918cebd69ed02b285", features = ["address32"] } +serde = { version = "1.0", features = ["derive"] } +serde_repr = "0.1" +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_encoding = { path = "../../tw_encoding" } +tw_hash = { path = "../../tw_hash" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } diff --git a/rust/chains/tw_sui/fuzz/.gitignore b/rust/chains/tw_sui/fuzz/.gitignore new file mode 100644 index 00000000000..5c404b9583f --- /dev/null +++ b/rust/chains/tw_sui/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +Cargo.lock diff --git a/rust/chains/tw_sui/fuzz/Cargo.toml b/rust/chains/tw_sui/fuzz/Cargo.toml new file mode 100644 index 00000000000..32ce151dfaa --- /dev/null +++ b/rust/chains/tw_sui/fuzz/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "tw_sui-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +tw_any_coin = { path = "../../../tw_any_coin", features = ["test-utils"] } +tw_coin_registry = { path = "../../../tw_coin_registry" } +tw_proto = { path = "../../../tw_proto", features = ["fuzz"] } + +[dependencies.tw_sui] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "sign" +path = "fuzz_targets/sign.rs" +test = false +doc = false diff --git a/rust/chains/tw_sui/fuzz/fuzz_targets/sign.rs b/rust/chains/tw_sui/fuzz/fuzz_targets/sign.rs new file mode 100644 index 00000000000..bf0158d275f --- /dev/null +++ b/rust/chains/tw_sui/fuzz/fuzz_targets/sign.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_any_coin::test_utils::sign_utils::AnySignerHelper; +use tw_coin_registry::coin_type::CoinType; +use tw_proto::Sui::Proto; + +fuzz_target!(|input: Proto::SigningInput<'_>| { + let mut signer = AnySignerHelper::::default(); + let _ = signer.sign(CoinType::Sui, input); +}); diff --git a/rust/chains/tw_sui/src/address.rs b/rust/chains/tw_sui/src/address.rs new file mode 100644 index 00000000000..063418906dc --- /dev/null +++ b/rust/chains/tw_sui/src/address.rs @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use move_core_types::account_address::AccountAddress; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::str::FromStr; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::*; +use tw_encoding::hex; +use tw_hash::blake2::blake2_b; +use tw_keypair::ed25519; +use tw_memory::Data; + +#[repr(u8)] +pub enum Scheme { + Ed25519 = 0, +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct SuiAddress(AccountAddress); + +impl SuiAddress { + pub const LENGTH: usize = AccountAddress::LENGTH; + + /// Initializes an address with a `ed25519` public key. + pub fn with_ed25519_pubkey(pubkey: &ed25519::sha512::PublicKey) -> AddressResult { + const CAPACITY: usize = ed25519::sha512::PublicKey::LEN + 1; + + let mut to_hash = Vec::with_capacity(CAPACITY); + to_hash.push(Scheme::Ed25519 as u8); + to_hash.extend_from_slice(pubkey.as_slice()); + let hashed = + blake2_b(to_hash.as_slice(), SuiAddress::LENGTH).map_err(|_| AddressError::Internal)?; + + AccountAddress::from_bytes(hashed) + .map(SuiAddress) + .map_err(|_| AddressError::Internal) + } + + pub fn into_inner(self) -> AccountAddress { + self.0 + } +} + +impl FromStr for SuiAddress { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + let bytes = hex::decode(s).map_err(|_| AddressError::FromHexError)?; + AccountAddress::from_bytes(bytes) + .map(SuiAddress) + .map_err(|_| AddressError::InvalidInput) + } +} + +impl fmt::Display for SuiAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let prefixed = true; + write!(f, "{}", hex::encode(self.0.as_ref(), prefixed)) + } +} + +impl CoinAddress for SuiAddress { + #[inline] + fn data(&self) -> Data { + self.0.to_vec() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_keypair::ed25519::sha512::PrivateKey; + + #[test] + fn test_from_public_key() { + let private = PrivateKey::try_from( + "088baa019f081d6eab8dff5c447f9ce2f83c1babf3d03686299eaf6a1e89156e", + ) + .unwrap(); + let public = private.public(); + let addr = SuiAddress::with_ed25519_pubkey(&public).unwrap(); + assert_eq!( + addr.to_string(), + "0x259ff8074ab425cbb489f236e18e08f03f1a7856bdf7c7a2877bd64f738b5015" + ); + } + + /// https://github.com/trustwallet/wallet-core/issues/3837 + #[test] + fn test_sui_address_str_with_leading_zero() { + let s = "0x0cf10169225a251113b3198dc81d15ba72286f73353a8212f03bad10bd0f0a99"; + let addr = SuiAddress::from_str(s).unwrap(); + assert_eq!(addr.to_string(), s); + } +} diff --git a/rust/chains/tw_sui/src/compiler.rs b/rust/chains/tw_sui/src/compiler.rs new file mode 100644 index 00000000000..1da7236a8f3 --- /dev/null +++ b/rust/chains/tw_sui/src/compiler.rs @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::tx_builder::{TWTransaction, TWTransactionBuilder}; +use crate::modules::tx_signer::{TransactionPreimage, TxSigner}; +use crate::signature::SuiSignatureInfo; +use std::borrow::Cow; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::common::compile_input::SingleSignaturePubkey; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_encoding::base64::{self, STANDARD}; +use tw_keypair::ed25519; +use tw_proto::Sui::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct SuiCompiler; + +impl SuiCompiler { + #[inline] + pub fn preimage_hashes( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + Self::preimage_hashes_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + fn preimage_hashes_impl( + _coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let builder = TWTransactionBuilder::new(input); + let tx_to_sign = builder.build()?; + + let TransactionPreimage { + tx_data_to_sign, + tx_hash_to_sign, + .. + } = match tx_to_sign { + TWTransaction::Transaction(tx) => TxSigner::preimage(&tx)?, + TWTransaction::SignDirect(tx_data) => TxSigner::preimage_direct(tx_data)?, + }; + + Ok(CompilerProto::PreSigningOutput { + data: Cow::from(tx_data_to_sign), + data_hash: Cow::from(tx_hash_to_sign.to_vec()), + ..CompilerProto::PreSigningOutput::default() + }) + } + + #[inline] + pub fn compile( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + Self::compile_impl(coin, input, signatures, public_keys) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn compile_impl( + _coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> SigningResult> { + let builder = TWTransactionBuilder::new(input); + let tx_to_sign = builder.build()?; + + let TransactionPreimage { + unsigned_tx_data, .. + } = match tx_to_sign { + TWTransaction::Transaction(tx) => TxSigner::preimage(&tx), + TWTransaction::SignDirect(tx_data) => TxSigner::preimage_direct(tx_data), + }?; + + let SingleSignaturePubkey { + signature: raw_signature, + public_key: public_key_bytes, + } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; + + let signature = ed25519::Signature::try_from(raw_signature.as_slice())?; + let public_key = ed25519::sha512::PublicKey::try_from(public_key_bytes.as_slice())?; + + let signature_info = SuiSignatureInfo::ed25519(&signature, &public_key); + + let unsigned_tx = base64::encode(&unsigned_tx_data, STANDARD); + Ok(Proto::SigningOutput { + unsigned_tx: Cow::from(unsigned_tx), + signature: Cow::from(signature_info.to_base64()), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/chains/tw_sui/src/constants.rs b/rust/chains/tw_sui/src/constants.rs new file mode 100644 index 00000000000..35c5d525f3e --- /dev/null +++ b/rust/chains/tw_sui/src/constants.rs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::transaction::sui_types::{ObjectID, SequenceNumber}; +use move_core_types::account_address::AccountAddress; +use move_core_types::ident_str; +use move_core_types::identifier::IdentStr; + +pub const OBJECT_START_VERSION: SequenceNumber = SequenceNumber(1); + +/// 0x5: hardcoded object ID for the singleton sui system state object. +pub const SUI_SYSTEM_STATE_ADDRESS: AccountAddress = address_from_single_byte(5); +pub const SUI_SYSTEM_STATE_OBJECT_ID: ObjectID = ObjectID(SUI_SYSTEM_STATE_ADDRESS); +pub const SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION: SequenceNumber = OBJECT_START_VERSION; + +/// 0x3-- account address where sui system modules are stored +/// Same as the ObjectID +pub const SUI_SYSTEM_ADDRESS: AccountAddress = address_from_single_byte(3); +pub const SUI_SYSTEM_PACKAGE_ID: ObjectID = ObjectID(SUI_SYSTEM_ADDRESS); + +pub const SUI_SYSTEM_MODULE_NAME: &IdentStr = ident_str!("sui_system"); +pub const ADD_STAKE_MUL_COIN_FUN_NAME: &IdentStr = ident_str!("request_add_stake_mul_coin"); +pub const WITHDRAW_STAKE_FUN_NAME: &IdentStr = ident_str!("request_withdraw_stake"); + +const fn address_from_single_byte(b: u8) -> AccountAddress { + let mut addr = [0u8; AccountAddress::LENGTH]; + addr[AccountAddress::LENGTH - 1] = b; + AccountAddress::new(addr) +} diff --git a/rust/chains/tw_sui/src/entry.rs b/rust/chains/tw_sui/src/entry.rs new file mode 100644 index 00000000000..63bd4e92df7 --- /dev/null +++ b/rust/chains/tw_sui/src/entry.rs @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SuiAddress; +use crate::compiler::SuiCompiler; +use crate::modules::transaction_util::SuiTransactionUtil; +use crate::signer::SuiSigner; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::wallet_connector::NoWalletConnector; +use tw_coin_entry::prefix::NoPrefix; +use tw_keypair::tw::PublicKey; +use tw_proto::Sui::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct SuiEntry; + +impl CoinEntry for SuiEntry { + type AddressPrefix = NoPrefix; + type Address = SuiAddress; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + type WalletConnector = NoWalletConnector; + type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = SuiTransactionUtil; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + SuiAddress::from_str(address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + SuiAddress::from_str(address) + } + + #[inline] + fn derive_address( + &self, + _coin: &dyn CoinContext, + public_key: PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + let ed25519_public = public_key + .to_ed25519() + .ok_or(AddressError::PublicKeyTypeMismatch)?; + SuiAddress::with_ed25519_pubkey(ed25519_public) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + SuiSigner::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + SuiCompiler::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + SuiCompiler::compile(coin, input, signatures, public_keys) + } + + #[inline] + fn transaction_util(&self) -> Option { + Some(SuiTransactionUtil) + } +} diff --git a/rust/chains/tw_sui/src/lib.rs b/rust/chains/tw_sui/src/lib.rs new file mode 100644 index 00000000000..9a2411b8058 --- /dev/null +++ b/rust/chains/tw_sui/src/lib.rs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod address; +pub mod compiler; +pub mod constants; +pub mod entry; +pub mod modules; +pub mod signature; +pub mod signer; +pub mod transaction; diff --git a/rust/chains/tw_sui/src/modules/mod.rs b/rust/chains/tw_sui/src/modules/mod.rs new file mode 100644 index 00000000000..32f928c8ec3 --- /dev/null +++ b/rust/chains/tw_sui/src/modules/mod.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod transaction_util; +pub mod tx_builder; +pub mod tx_signer; diff --git a/rust/chains/tw_sui/src/modules/transaction_util.rs b/rust/chains/tw_sui/src/modules/transaction_util.rs new file mode 100644 index 00000000000..f2ad4ff585f --- /dev/null +++ b/rust/chains/tw_sui/src/modules/transaction_util.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::transaction_util::TransactionUtil; +use tw_encoding::base58::{self, Alphabet}; +use tw_encoding::base64::{self, STANDARD}; +use tw_hash::blake2::blake2_b; +use tw_hash::H256; + +pub struct SuiTransactionUtil; + +impl TransactionUtil for SuiTransactionUtil { + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + Self::calc_tx_hash_impl(coin, encoded_tx) + } +} + +// See: https://github.com/mofalabs/sui/blob/74908b3ad8b82e5e401d5017fed4fa7dc2361569/lib/builder/hash.dart#L7 +fn hash_typed_data(type_tag: &str, data: &[u8]) -> Result, tw_hash::Error> { + let type_tag_bytes: Vec = format!("{}::", type_tag).into_bytes(); + + let mut data_with_tag = Vec::with_capacity(type_tag_bytes.len() + data.len()); + data_with_tag.extend_from_slice(&type_tag_bytes); + data_with_tag.extend_from_slice(data); + + blake2_b(&data_with_tag, H256::LEN) +} + +impl SuiTransactionUtil { + fn calc_tx_hash_impl(_coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + let tx = base64::decode(encoded_tx, STANDARD)?; + let tx_hash = hash_typed_data("TransactionData", &tx) + .map_err(|_| SigningErrorType::Error_input_parse)?; + + Ok(base58::encode(&tx_hash, Alphabet::Bitcoin)) + } +} diff --git a/rust/chains/tw_sui/src/modules/tx_builder.rs b/rust/chains/tw_sui/src/modules/tx_builder.rs new file mode 100644 index 00000000000..301f9f3cde4 --- /dev/null +++ b/rust/chains/tw_sui/src/modules/tx_builder.rs @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SuiAddress; +use crate::transaction::sui_types::{ObjectDigest, ObjectID, ObjectRef, SequenceNumber}; +use crate::transaction::transaction_builder::TransactionBuilder; +use crate::transaction::transaction_data::TransactionData; +use std::borrow::Cow; +use std::str::FromStr; +use tw_coin_entry::error::prelude::*; +use tw_encoding::base64::{self, STANDARD}; +use tw_keypair::ed25519; +use tw_keypair::traits::KeyPairTrait; +use tw_memory::Data; +use tw_proto::Sui::Proto; +use tw_proto::Sui::Proto::mod_SigningInput::OneOftransaction_payload as TransactionType; + +pub enum TWTransaction { + Transaction(TransactionData), + SignDirect(Data), +} + +pub struct TWTransactionBuilder<'a> { + input: Proto::SigningInput<'a>, +} + +impl<'a> TWTransactionBuilder<'a> { + pub fn new(input: Proto::SigningInput<'a>) -> Self { + TWTransactionBuilder { input } + } + + pub fn signer_key(&self) -> SigningResult { + ed25519::sha512::KeyPair::try_from(self.input.private_key.as_ref()) + .map_err(SigningError::from) + } + + pub fn build(self) -> SigningResult { + let tx_data = match self.input.transaction_payload { + TransactionType::sign_direct_message(ref direct) => { + let raw_data = self.sign_direct_from_proto(direct)?; + return Ok(TWTransaction::SignDirect(raw_data)); + }, + TransactionType::pay_sui(ref pay_sui) => self.pay_sui_from_proto(pay_sui), + TransactionType::pay_all_sui(ref pay_all_sui) => { + self.pay_all_sui_from_proto(pay_all_sui) + }, + TransactionType::pay(ref pay) => self.pay_from_proto(pay), + TransactionType::request_add_stake(ref stake) => self.stake_from_proto(stake), + TransactionType::request_withdraw_stake(ref withdraw) => { + self.withdraw_from_proto(withdraw) + }, + TransactionType::transfer_object(ref transfer_obj) => { + self.transfer_object_from_proto(transfer_obj) + }, + TransactionType::None => SigningError::err(SigningErrorType::Error_invalid_params), + }?; + Ok(TWTransaction::Transaction(tx_data)) + } + + fn sign_direct_from_proto(&self, sign_direct: &Proto::SignDirect<'_>) -> SigningResult { + base64::decode(&sign_direct.unsigned_tx_msg, STANDARD) + .tw_err(|_| SigningErrorType::Error_input_parse) + .context("Error parsing Raw Unsigned TX message as base64") + } + + fn pay_sui_from_proto(&self, pay_sui: &Proto::PaySui<'_>) -> SigningResult { + let signer = self.signer_address()?; + let input_coins = Self::build_coins(&pay_sui.input_coins)?; + let recipients = Self::parse_addresses(&pay_sui.recipients) + .context("Invalid one of the recipients addresses")?; + + TransactionBuilder::pay_sui( + signer, + input_coins, + recipients, + pay_sui.amounts.clone(), + self.input.gas_budget, + self.input.reference_gas_price, + ) + } + + fn pay_all_sui_from_proto( + &self, + pay_all_sui: &Proto::PayAllSui<'_>, + ) -> SigningResult { + let signer = self.signer_address()?; + let input_coins = Self::build_coins(&pay_all_sui.input_coins)?; + let recipient = SuiAddress::from_str(&pay_all_sui.recipient) + .into_tw() + .context("Invalid recipient address")?; + + TransactionBuilder::pay_all_sui( + signer, + input_coins, + recipient, + self.input.gas_budget, + self.input.reference_gas_price, + ) + } + + fn pay_from_proto(&self, pay: &Proto::Pay<'_>) -> SigningResult { + let signer = self.signer_address()?; + let input_coins = Self::build_coins(&pay.input_coins)?; + let recipients = Self::parse_addresses(&pay.recipients) + .context("Invalid one of the recipients addresses")?; + let gas = Self::require_coin(&pay.gas).context("No 'gas' coin specified")?; + + TransactionBuilder::pay( + signer, + input_coins, + recipients, + pay.amounts.clone(), + gas, + self.input.gas_budget, + self.input.reference_gas_price, + ) + } + + fn stake_from_proto( + &self, + stake: &Proto::RequestAddStake<'_>, + ) -> SigningResult { + let signer = self.signer_address()?; + + let input_coins = Self::build_coins(&stake.coins)?; + let amount = stake.amount.as_ref().map(|a| a.amount); + let validator = SuiAddress::from_str(stake.validator.as_ref()) + .into_tw() + .context("Invalid validator address")?; + let gas = Self::require_coin(&stake.gas).context("No 'gas' coin specified")?; + + TransactionBuilder::request_add_stake( + signer, + input_coins, + amount, + validator, + gas, + self.input.gas_budget, + self.input.reference_gas_price, + ) + } + + fn withdraw_from_proto( + &self, + withdraw: &Proto::RequestWithdrawStake<'_>, + ) -> SigningResult { + let signer = self.signer_address()?; + + let staked_sui = + Self::require_coin(&withdraw.staked_sui).context("No 'staked_sui' coin specified")?; + let gas = Self::require_coin(&withdraw.gas).context("No 'gas' coin specified")?; + + TransactionBuilder::request_withdraw_stake( + signer, + staked_sui, + gas, + self.input.gas_budget, + self.input.reference_gas_price, + ) + } + + fn transfer_object_from_proto( + &self, + transfer_obj: &Proto::TransferObject<'_>, + ) -> SigningResult { + let signer = self.signer_address()?; + + let recipient = SuiAddress::from_str(&transfer_obj.recipient) + .into_tw() + .context("Invalid recipient address")?; + let object = Self::require_coin(&transfer_obj.object).context("No 'object' specified")?; + let gas = Self::require_coin(&transfer_obj.gas).context("No 'gas' coin specified")?; + + TransactionBuilder::transfer_object( + signer, + object, + recipient, + gas, + self.input.gas_budget, + self.input.reference_gas_price, + ) + } + + fn signer_address(&self) -> SigningResult { + if self.input.private_key.is_empty() { + SuiAddress::from_str(&self.input.signer) + .into_tw() + .context("Invalid signer address") + } else { + let keypair = self.signer_key()?; + SuiAddress::with_ed25519_pubkey(keypair.public()).map_err(SigningError::from) + } + } + + fn build_coins(coins: &[Proto::ObjectRef]) -> SigningResult> { + coins.iter().map(Self::build_coin).collect() + } + + fn require_coin(maybe_coin: &Option) -> SigningResult { + let coin = maybe_coin + .as_ref() + .or_tw_err(SigningErrorType::Error_invalid_params)?; + Self::build_coin(coin) + } + + fn build_coin(coin: &Proto::ObjectRef) -> SigningResult { + let object_id = ObjectID::from_str(coin.object_id.as_ref()).context("Invalid Object ID")?; + let version = SequenceNumber(coin.version); + let object_digest = ObjectDigest::from_str(coin.object_digest.as_ref())?; + + Ok((object_id, version, object_digest)) + } + + fn parse_addresses(addresses: &[Cow<'_, str>]) -> SigningResult> { + let mut res = Vec::with_capacity(addresses.len()); + for addr in addresses { + res.push(SuiAddress::from_str(addr.as_ref())?); + } + Ok(res) + } +} diff --git a/rust/chains/tw_sui/src/modules/tx_signer.rs b/rust/chains/tw_sui/src/modules/tx_signer.rs new file mode 100644 index 00000000000..34eb476f48b --- /dev/null +++ b/rust/chains/tw_sui/src/modules/tx_signer.rs @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SuiAddress; +use crate::signature::SuiSignatureInfo; +use crate::transaction::transaction_data::TransactionData; +use serde::Serialize; +use serde_repr::Serialize_repr; +use tw_coin_entry::error::prelude::*; +use tw_encoding::bcs; +use tw_hash::blake2::blake2_b; +use tw_hash::H256; +use tw_keypair::ed25519; +use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait}; +use tw_memory::Data; + +/// This enums specifies the intent scope. +#[derive(Serialize_repr)] +#[repr(u8)] +pub enum IntentScope { + /// Used for a user signature on a transaction data. + TransactionData = 0, +} + +/// The version here is to distinguish between signing different versions of the struct +/// or enum. Serialized output between two different versions of the same struct/enum +/// might accidentally (or maliciously on purpose) match. +#[derive(Serialize_repr)] +#[repr(u8)] +pub enum IntentVersion { + V0 = 0, +} + +/// This enums specifies the application ID. Two intents in two different applications +/// (i.e., Narwhal, Sui, Ethereum etc) should never collide, so that even when a signing +/// key is reused, nobody can take a signature designated for app_1 and present it as a +/// valid signature for an (any) intent in app_2. +#[derive(Serialize_repr)] +#[repr(u8)] +pub enum AppId { + Sui = 0, +} + +/// An intent is a compact struct serves as the domain separator for a message that a signature commits to. +/// It consists of three parts: [enum IntentScope] (what the type of the message is), +/// [enum IntentVersion], [enum AppId] (what application that the signature refers to). +/// It is used to construct [struct IntentMessage] that what a signature commits to. +/// +/// The serialization of an Intent is a 3-byte array where each field is represented by a byte. +#[derive(Serialize)] +pub struct Intent { + pub scope: IntentScope, + pub version: IntentVersion, + pub app_id: AppId, +} + +/// Intent Message is a wrapper around a message with its intent. The message can +/// be any type that implements [trait Serialize]. *ALL* signatures in Sui must commits +/// to the intent message, not the message itself. This guarantees any intent +/// message signed in the system cannot collide with another since they are domain +/// separated by intent. +/// +/// The serialization of an IntentMessage is compact: it only appends three bytes +/// to the message itself. +#[derive(Serialize)] +pub struct IntentMessage { + pub intent: Intent, + pub value: T, +} + +pub struct TransactionPreimage { + /// Transaction `bcs` encoded representation. + pub unsigned_tx_data: Data, + /// [`TransactionPreimage::unsigned_tx_data`] extended with the `IntentMessage`. + pub tx_data_to_sign: Data, + /// Hash of the [`TransactionPreimage::tx_data_to_sign`]. + pub tx_hash_to_sign: H256, +} + +pub struct TxSigner; + +impl TxSigner { + pub fn sign( + tx: &TransactionData, + signer_key: &ed25519::sha512::KeyPair, + ) -> SigningResult<(TransactionPreimage, SuiSignatureInfo)> { + let public_key = signer_key.public(); + let signer_address = SuiAddress::with_ed25519_pubkey(public_key)?; + if signer_address != tx.sender() { + return SigningError::err(SigningErrorType::Error_missing_private_key) + .context("Given private key does not belong to the sender address"); + } + + let unsigned_tx_data = bcs::encode(tx) + .tw_err(|_| SigningErrorType::Error_internal) + .context("Error serializing TransactionData")?; + Self::sign_direct(unsigned_tx_data, signer_key) + } + + pub fn sign_direct( + unsigned_tx_data: Data, + signer_key: &ed25519::sha512::KeyPair, + ) -> SigningResult<(TransactionPreimage, SuiSignatureInfo)> { + let preimage = Self::preimage_direct(unsigned_tx_data)?; + let signature = signer_key.sign(preimage.tx_hash_to_sign.into_vec())?; + let signature_info = SuiSignatureInfo::ed25519(&signature, signer_key.public()); + Ok((preimage, signature_info)) + } + + pub fn preimage(tx: &TransactionData) -> SigningResult { + let unsigned_tx_data = bcs::encode(tx) + .tw_err(|_| SigningErrorType::Error_internal) + .context("Error serializing TransactionData")?; + Self::preimage_direct(unsigned_tx_data) + } + + pub fn preimage_direct(unsigned_tx_data: Data) -> SigningResult { + let intent = Intent { + scope: IntentScope::TransactionData, + version: IntentVersion::V0, + app_id: AppId::Sui, + }; + let intent_data = bcs::encode(&intent) + .tw_err(|_| SigningErrorType::Error_internal) + .context("Error serializing Intent message")?; + + let tx_data_to_sign: Data = intent_data + .into_iter() + .chain(unsigned_tx_data.iter().copied()) + .collect(); + let tx_hash_to_sign = blake2_b(&tx_data_to_sign, H256::LEN) + .and_then(|hash| H256::try_from(hash.as_slice())) + .tw_err(|_| SigningErrorType::Error_internal)?; + + Ok(TransactionPreimage { + unsigned_tx_data, + tx_data_to_sign, + tx_hash_to_sign, + }) + } +} diff --git a/rust/chains/tw_sui/src/signature.rs b/rust/chains/tw_sui/src/signature.rs new file mode 100644 index 00000000000..e03aacad05f --- /dev/null +++ b/rust/chains/tw_sui/src/signature.rs @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_encoding::base64::{self, STANDARD}; +use tw_hash::{H256, H512}; +use tw_keypair::ed25519; +use tw_memory::Data; + +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum SignatureScheme { + ED25519 = 0, +} + +pub struct SuiSignatureInfo { + scheme: SignatureScheme, + signature: H512, + public_key: H256, +} + +impl SuiSignatureInfo { + pub fn ed25519( + signature: &ed25519::Signature, + public_key: &ed25519::sha512::PublicKey, + ) -> SuiSignatureInfo { + SuiSignatureInfo { + scheme: SignatureScheme::ED25519, + signature: signature.to_bytes(), + public_key: public_key.to_bytes(), + } + } + + pub fn to_vec(&self) -> Data { + let mut scheme: Data = Vec::with_capacity(H512::LEN + H256::LEN + 1); + scheme.push(self.scheme as u8); + scheme.extend_from_slice(self.signature.as_slice()); + scheme.extend_from_slice(self.public_key.as_slice()); + scheme + } + + pub fn to_base64(&self) -> String { + base64::encode(&self.to_vec(), STANDARD) + } +} diff --git a/rust/chains/tw_sui/src/signer.rs b/rust/chains/tw_sui/src/signer.rs new file mode 100644 index 00000000000..4cf4df45514 --- /dev/null +++ b/rust/chains/tw_sui/src/signer.rs @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::tx_builder::{TWTransaction, TWTransactionBuilder}; +use crate::modules::tx_signer::TxSigner; +use std::borrow::Cow; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_encoding::base64::{self, STANDARD}; +use tw_proto::Sui::Proto; + +pub struct SuiSigner; + +impl SuiSigner { + pub fn sign( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> Proto::SigningOutput<'static> { + Self::sign_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_impl( + _coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let builder = TWTransactionBuilder::new(input); + let signer_key = builder.signer_key()?; + let tx_to_sign = builder.build()?; + + let (preimage, signature) = match tx_to_sign { + TWTransaction::Transaction(tx) => TxSigner::sign(&tx, &signer_key)?, + TWTransaction::SignDirect(tx_data) => TxSigner::sign_direct(tx_data, &signer_key)?, + }; + + let unsigned_tx = base64::encode(&preimage.unsigned_tx_data, STANDARD); + Ok(Proto::SigningOutput { + unsigned_tx: Cow::from(unsigned_tx), + signature: Cow::from(signature.to_base64()), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/chains/tw_sui/src/transaction/command.rs b/rust/chains/tw_sui/src/transaction/command.rs new file mode 100644 index 00000000000..533c0be3df2 --- /dev/null +++ b/rust/chains/tw_sui/src/transaction/command.rs @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::transaction::sui_types::ObjectID; +use move_core_types::identifier::Identifier; +use move_core_types::language_storage::TypeTag; +use serde::{Deserialize, Serialize}; + +/// A single command in a programmable transaction. +#[derive(Debug, Deserialize, Serialize)] +pub enum Command { + /// A call to either an entry or a public Move function + MoveCall(Box), + /// `(Vec, address)` + /// It sends n-objects to the specified address. These objects must have store + /// (public transfer) and either the previous owner must be an address or the object must + /// be newly created. + TransferObjects(Vec, Argument), + /// `(&mut Coin, Vec)` -> `Vec>` + /// It splits off some amounts into a new coins with those amounts + SplitCoins(Argument, Vec), + /// `(&mut Coin, Vec>)` + /// It merges n-coins into the first coin + MergeCoins(Argument, Vec), + /// Publishes a Move package. It takes the package bytes and a list of the package's transitive + /// dependencies to link against on-chain. + Publish(Vec>, Vec), + /// `forall T: Vec -> vector` + /// Given n-values of the same type, it constructs a vector. For non objects or an empty vector, + /// the type tag must be specified. + MakeMoveVec(Option, Vec), +} + +impl Command { + pub fn move_call( + package: ObjectID, + module: Identifier, + function: Identifier, + type_arguments: Vec, + arguments: Vec, + ) -> Self { + Command::MoveCall(Box::new(ProgrammableMoveCall { + package, + module, + function, + type_arguments, + arguments, + })) + } +} + +/// The command for calling a Move function, either an entry function or a public +/// function (which cannot return references). +#[derive(Debug, Deserialize, Serialize)] +pub struct ProgrammableMoveCall { + /// The package containing the module and function. + pub package: ObjectID, + /// The specific module in the package containing the function. + pub module: Identifier, + /// The function to be called. + pub function: Identifier, + /// The type arguments to the function. + pub type_arguments: Vec, + /// The arguments to the function. + pub arguments: Vec, +} + +/// An argument to a programmable transaction command +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub enum Argument { + /// The gas coin. The gas coin can only be used by-ref, except for with + /// `TransferObjects`, which can use it by-value. + GasCoin, + /// One of the input objects or primitive values (from + /// `ProgrammableTransaction` inputs) + Input(u16), + /// The result of another command (from `ProgrammableTransaction` commands) + Result(u16), + /// Like a `Result` but it accesses a nested result. Currently, the only usage + /// of this is to access a value from a Move call with multiple return values. + NestedResult(u16, u16), +} diff --git a/rust/chains/tw_sui/src/transaction/mod.rs b/rust/chains/tw_sui/src/transaction/mod.rs new file mode 100644 index 00000000000..46b7c4a5cf0 --- /dev/null +++ b/rust/chains/tw_sui/src/transaction/mod.rs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod command; +pub mod programmable_transaction; +pub mod sui_types; +pub mod transaction_builder; +pub mod transaction_data; diff --git a/rust/chains/tw_sui/src/transaction/programmable_transaction.rs b/rust/chains/tw_sui/src/transaction/programmable_transaction.rs new file mode 100644 index 00000000000..82445cae86e --- /dev/null +++ b/rust/chains/tw_sui/src/transaction/programmable_transaction.rs @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SuiAddress; +use crate::transaction::command::{Argument, Command}; +use crate::transaction::sui_types::{CallArg, ObjectArg, ObjectID, ObjectRef}; +use indexmap::IndexMap; +use move_core_types::identifier::Identifier; +use move_core_types::language_storage::TypeTag; +use serde::{Deserialize, Serialize}; +use tw_coin_entry::error::prelude::*; +use tw_encoding::bcs; + +/// A series of commands where the results of one command can be used in future +/// commands +#[derive(Debug, Deserialize, Serialize)] +pub struct ProgrammableTransaction { + /// Input objects or primitive values + pub inputs: Vec, + /// The commands to be executed sequentially. A failure in any command will + /// result in the failure of the entire transaction. + pub commands: Vec, +} + +#[derive(Eq, Hash, PartialEq)] +enum BuilderArg { + Object(ObjectID), + Pure(Vec), + ForcedNonUniquePure(usize), +} + +#[derive(Default)] +pub struct ProgrammableTransactionBuilder { + inputs: IndexMap, + commands: Vec, +} + +impl ProgrammableTransactionBuilder { + pub fn finish(self) -> ProgrammableTransaction { + let Self { inputs, commands } = self; + let inputs = inputs.into_values().collect(); + ProgrammableTransaction { inputs, commands } + } + + /// Will fail to generate if recipients and amounts do not have the same lengths. + /// Or if coins is empty + pub fn pay( + &mut self, + coins: Vec, + recipients: Vec, + amounts: Vec, + ) -> SigningResult<()> { + let mut coins = coins.into_iter(); + let Some(coin) = coins.next() else { + // coins vector is empty + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("No coins provided"); + }; + let coin_arg = self.obj(ObjectArg::ImmOrOwnedObject(coin))?; + let merge_args: Vec<_> = coins + .map(|c| self.obj(ObjectArg::ImmOrOwnedObject(c))) + .collect::>()?; + if !merge_args.is_empty() { + self.command(Command::MergeCoins(coin_arg, merge_args)); + } + self.pay_impl(recipients, amounts, coin_arg) + } + + /// Will fail to generate if recipients and amounts do not have the same lengths + pub fn pay_sui(&mut self, recipients: Vec, amounts: Vec) -> SigningResult<()> { + self.pay_impl(recipients, amounts, Argument::GasCoin) + } + + pub fn pay_all_sui(&mut self, recipient: SuiAddress) { + let rec_arg = self.pure(recipient).unwrap(); + self.command(Command::TransferObjects(vec![Argument::GasCoin], rec_arg)); + } + + pub fn transfer_object( + &mut self, + recipient: SuiAddress, + object_ref: ObjectRef, + ) -> SigningResult<()> { + let rec_arg = self.pure(recipient).unwrap(); + let obj_arg = self.obj(ObjectArg::ImmOrOwnedObject(object_ref)); + self.commands + .push(Command::TransferObjects(vec![obj_arg?], rec_arg)); + Ok(()) + } + + /// Will fail to generate if given an empty ObjVec + pub fn move_call( + &mut self, + package: ObjectID, + module: Identifier, + function: Identifier, + type_arguments: Vec, + call_args: Vec, + ) -> SigningResult<()> { + let arguments = call_args + .into_iter() + .map(|a| self.input(a)) + .collect::>()?; + self.command(Command::move_call( + package, + module, + function, + type_arguments, + arguments, + )); + Ok(()) + } + + pub fn input(&mut self, call_arg: CallArg) -> SigningResult { + match call_arg { + CallArg::Pure(bytes) => { + let force_separate = false; + Ok(self.pure_bytes(bytes, force_separate)) + }, + CallArg::Object(obj) => self.obj(obj), + } + } + + pub fn pure_bytes(&mut self, bytes: Vec, force_separate: bool) -> Argument { + let arg = if force_separate { + BuilderArg::ForcedNonUniquePure(self.inputs.len()) + } else { + BuilderArg::Pure(bytes.clone()) + }; + let (i, _) = self.inputs.insert_full(arg, CallArg::Pure(bytes)); + Argument::Input(i as u16) + } + + pub fn pure(&mut self, value: T) -> SigningResult { + let force_separate = false; + Ok(self.pure_bytes(bcs::encode(&value)?, force_separate)) + } + + pub fn obj(&mut self, obj_arg: ObjectArg) -> SigningResult { + let id = obj_arg.id(); + let obj_arg = if let Some(old_value) = self.inputs.get(&BuilderArg::Object(id)) { + let old_obj_arg = match old_value { + CallArg::Pure(_) => { + return SigningError::err(SigningErrorType::Error_internal) + .context("Expected Object, found Pure") + }, + CallArg::Object(arg) => arg, + }; + match (old_obj_arg, obj_arg) { + ( + ObjectArg::SharedObject { + id: id1, + initial_shared_version: v1, + mutable: mut1, + }, + ObjectArg::SharedObject { + id: id2, + initial_shared_version: v2, + mutable: mut2, + }, + ) if v1 == &v2 => { + if id1 != &id2 || id != id2 { + // "invariant violation! object has id does not match call arg" + return SigningError::err(SigningErrorType::Error_internal) + .context("invariant violation! object has id does not match call arg"); + } + ObjectArg::SharedObject { + id, + initial_shared_version: v2, + mutable: *mut1 || mut2, + } + }, + (old_obj_arg, obj_arg) => { + if old_obj_arg != &obj_arg { + return SigningError::err(SigningErrorType::Error_internal) + .with_context(|| format!("Mismatched Object argument kind for object {id:?}. {old_value:?} is not compatible with {obj_arg:?}")); + } + obj_arg + }, + } + } else { + obj_arg + }; + let (i, _) = self + .inputs + .insert_full(BuilderArg::Object(id), CallArg::Object(obj_arg)); + Ok(Argument::Input(i as u16)) + } + + pub fn make_obj_vec( + &mut self, + objs: impl IntoIterator, + ) -> SigningResult { + let make_vec_args = objs + .into_iter() + .map(|obj| self.obj(obj)) + .collect::>()?; + Ok(self.command(Command::MakeMoveVec(None, make_vec_args))) + } + + pub fn command(&mut self, command: Command) -> Argument { + let i = self.commands.len(); + self.commands.push(command); + Argument::Result(i as u16) + } + + fn pay_impl( + &mut self, + recipients: Vec, + amounts: Vec, + coin: Argument, + ) -> SigningResult<()> { + if recipients.len() != amounts.len() { + // "Recipients and amounts mismatch. Got {} recipients but {} amounts" + return SigningError::err(SigningErrorType::Error_invalid_params).with_context(|| { + let recipients_num = recipients.len(); + let amounts_num = amounts.len(); + format!("Recipients and amounts mismatch. Got {recipients_num} recipients but {amounts_num} amounts") + }); + } + if amounts.is_empty() { + return Ok(()); + } + + // collect recipients in the case where they are non-unique in order + // to minimize the number of transfers that must be performed + let mut recipient_map: IndexMap> = IndexMap::new(); + let mut amt_args = vec![]; + for (i, (recipient, amount)) in recipients.into_iter().zip(amounts).enumerate() { + recipient_map.entry(recipient).or_default().push(i); + amt_args.push(self.pure(amount)?); + } + let Argument::Result(split_primary) = self.command(Command::SplitCoins(coin, amt_args)) + else { + return SigningError::err(SigningErrorType::Error_internal) + .context("self.command should always give an Argument::Result"); + }; + for (recipient, split_secondaries) in recipient_map { + let rec_arg = self.pure(recipient).unwrap(); + let coins = split_secondaries + .into_iter() + .map(|j| Argument::NestedResult(split_primary, j as u16)) + .collect(); + self.command(Command::TransferObjects(coins, rec_arg)); + } + Ok(()) + } +} diff --git a/rust/chains/tw_sui/src/transaction/sui_types.rs b/rust/chains/tw_sui/src/transaction/sui_types.rs new file mode 100644 index 00000000000..1948789adf9 --- /dev/null +++ b/rust/chains/tw_sui/src/transaction/sui_types.rs @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SuiAddress; +use crate::constants::{SUI_SYSTEM_STATE_OBJECT_ID, SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION}; +use move_core_types::account_address::AccountAddress; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +use tw_coin_entry::error::prelude::*; +use tw_encoding::base58::{self, Alphabet}; +use tw_hash::{as_bytes, H256}; +use tw_memory::Data; + +pub type ObjectRef = (ObjectID, SequenceNumber, ObjectDigest); +pub type EpochId = u64; + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +pub struct SequenceNumber(pub u64); + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct ObjectID(pub AccountAddress); + +impl FromStr for ObjectID { + type Err = SigningError; + + fn from_str(s: &str) -> Result { + let addr = SuiAddress::from_str(s) + .into_tw() + .context("Invalid Object ID")?; + Ok(ObjectID(addr.into_inner())) + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct ObjectDigest(#[serde(with = "as_bytes")] pub H256); + +impl FromStr for ObjectDigest { + type Err = SigningError; + + fn from_str(s: &str) -> Result { + let bytes = base58::decode(s, Alphabet::Bitcoin) + .tw_err(|_| SigningErrorType::Error_invalid_params) + .context("Invalid Object Digest: expected valid base58 string")?; + H256::try_from(bytes.as_slice()) + .map(ObjectDigest) + .tw_err(|_| SigningErrorType::Error_invalid_params) + .context("Invalid Object Digest: expected exactly 32 bytes") + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum CallArg { + // contains no structs or objects + Pure(Data), + // an object + Object(ObjectArg), +} + +impl CallArg { + pub const SUI_SYSTEM_MUT: Self = Self::Object(ObjectArg::SUI_SYSTEM_MUT); +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +pub enum ObjectArg { + // A Move object, either immutable, or owned mutable. + ImmOrOwnedObject(ObjectRef), + // A Move object that's shared. + // SharedObject::mutable controls whether caller asks for a mutable reference to shared object. + SharedObject { + id: ObjectID, + initial_shared_version: SequenceNumber, + mutable: bool, + }, +} + +impl ObjectArg { + pub const SUI_SYSTEM_MUT: Self = Self::SharedObject { + id: SUI_SYSTEM_STATE_OBJECT_ID, + initial_shared_version: SUI_SYSTEM_STATE_OBJECT_SHARED_VERSION, + mutable: true, + }; + + pub fn id(&self) -> ObjectID { + match self { + ObjectArg::ImmOrOwnedObject((id, _, _)) | ObjectArg::SharedObject { id, .. } => *id, + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct GasData { + pub payment: Vec, + pub owner: SuiAddress, + pub price: u64, + pub budget: u64, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum TransactionExpiration { + /// The transaction has no expiration + None, + /// Validators wont sign a transaction unless the expiration Epoch + /// is greater than or equal to the current epoch + Epoch(EpochId), +} diff --git a/rust/chains/tw_sui/src/transaction/transaction_builder.rs b/rust/chains/tw_sui/src/transaction/transaction_builder.rs new file mode 100644 index 00000000000..3c7309013fb --- /dev/null +++ b/rust/chains/tw_sui/src/transaction/transaction_builder.rs @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SuiAddress; +use crate::constants::{ + ADD_STAKE_MUL_COIN_FUN_NAME, SUI_SYSTEM_MODULE_NAME, SUI_SYSTEM_PACKAGE_ID, + WITHDRAW_STAKE_FUN_NAME, +}; +use crate::transaction::command::Command; +use crate::transaction::programmable_transaction::ProgrammableTransactionBuilder; +use crate::transaction::sui_types::{CallArg, ObjectArg, ObjectRef}; +use crate::transaction::transaction_data::{TransactionData, TransactionKind}; +use tw_coin_entry::error::prelude::*; +use tw_encoding::bcs; + +pub struct TransactionBuilder; + +impl TransactionBuilder { + pub fn request_add_stake( + signer: SuiAddress, + coins: Vec, + amount: Option, + validator: SuiAddress, + gas: ObjectRef, + gas_budget: u64, + gas_price: u64, + ) -> SigningResult { + let obj_vec: Vec<_> = coins.into_iter().map(ObjectArg::ImmOrOwnedObject).collect(); + + let pt = { + let mut builder = ProgrammableTransactionBuilder::default(); + let arguments = vec![ + builder.input(CallArg::SUI_SYSTEM_MUT).unwrap(), + builder.make_obj_vec(obj_vec)?, + builder.input(CallArg::Pure(bcs::encode(&amount)?)).unwrap(), + builder + .input(CallArg::Pure(bcs::encode(&validator)?)) + .unwrap(), + ]; + builder.command(Command::move_call( + SUI_SYSTEM_PACKAGE_ID, + SUI_SYSTEM_MODULE_NAME.to_owned(), + ADD_STAKE_MUL_COIN_FUN_NAME.to_owned(), + vec![], + arguments, + )); + builder.finish() + }; + Ok(TransactionData::new_programmable( + signer, + vec![gas], + pt, + gas_budget, + gas_price, + )) + } + + pub fn request_withdraw_stake( + signer: SuiAddress, + staked_sui: ObjectRef, + gas: ObjectRef, + gas_budget: u64, + gas_price: u64, + ) -> SigningResult { + TransactionData::new_move_call( + signer, + SUI_SYSTEM_PACKAGE_ID, + SUI_SYSTEM_MODULE_NAME.to_owned(), + WITHDRAW_STAKE_FUN_NAME.to_owned(), + vec![], + gas, + vec![ + CallArg::SUI_SYSTEM_MUT, + CallArg::Object(ObjectArg::ImmOrOwnedObject(staked_sui)), + ], + gas_budget, + gas_price, + ) + } + + /// Send `Coin` to a list of addresses, where T can be any coin type, following a list of amounts. + /// The object specified in the gas field will be used to pay the gas fee for the transaction. + /// The gas object can not appear in input_coins. + /// https://docs.sui.io/sui-api-ref#unsafe_pay + #[allow(clippy::too_many_arguments)] + pub fn pay( + signer: SuiAddress, + input_coins: Vec, + recipients: Vec, + amounts: Vec, + gas: ObjectRef, + gas_budget: u64, + gas_price: u64, + ) -> SigningResult { + if input_coins.iter().any(|coin| coin.0 == gas.0) { + return SigningError::err(SigningErrorType::Error_invalid_params).context( + "Gas coin is in input coins of Pay transaction, use PaySui transaction instead!", + ); + } + + TransactionData::new_pay( + signer, + input_coins, + recipients, + amounts, + gas, + gas_budget, + gas_price, + ) + } + + /// Send SUI coins to a list of addresses, following a list of amounts. + /// This is for SUI coin only and does not require a separate gas coin object. + /// https://docs.sui.io/sui-api-ref#unsafe_paysui + pub fn pay_sui( + signer: SuiAddress, + mut input_coins: Vec, + recipients: Vec, + amounts: Vec, + gas_budget: u64, + gas_price: u64, + ) -> SigningResult { + if input_coins.is_empty() { + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("Empty input coins for Pay related transaction"); + } + + let gas_object_ref = input_coins.remove(0); + TransactionData::new_pay_sui( + signer, + input_coins, + recipients, + amounts, + gas_object_ref, + gas_budget, + gas_price, + ) + } + + /// Send all SUI coins to one recipient. + /// This is for SUI coin only and does not require a separate gas coin object. + /// https://docs.sui.io/sui-api-ref#unsafe_payallsui + pub fn pay_all_sui( + signer: SuiAddress, + mut input_coins: Vec, + recipient: SuiAddress, + gas_budget: u64, + gas_price: u64, + ) -> SigningResult { + if input_coins.is_empty() { + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("Empty input coins for Pay related transaction"); + } + + let gas_object_ref = input_coins.remove(0); + Ok(TransactionData::new_pay_all_sui( + signer, + input_coins, + recipient, + gas_object_ref, + gas_budget, + gas_price, + )) + } + + pub fn transfer_object( + signer: SuiAddress, + object: ObjectRef, + recipient: SuiAddress, + gas: ObjectRef, + gas_budget: u64, + gas_price: u64, + ) -> SigningResult { + let mut builder = ProgrammableTransactionBuilder::default(); + builder.transfer_object(recipient, object)?; + + Ok(TransactionData::new( + TransactionKind::ProgrammableTransaction(builder.finish()), + signer, + gas, + gas_budget, + gas_price, + )) + } +} diff --git a/rust/chains/tw_sui/src/transaction/transaction_data.rs b/rust/chains/tw_sui/src/transaction/transaction_data.rs new file mode 100644 index 00000000000..bfe18fa4e3a --- /dev/null +++ b/rust/chains/tw_sui/src/transaction/transaction_data.rs @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::SuiAddress; +use crate::transaction::programmable_transaction::{ + ProgrammableTransaction, ProgrammableTransactionBuilder, +}; +use crate::transaction::sui_types::{CallArg, GasData, ObjectID, ObjectRef, TransactionExpiration}; +use move_core_types::identifier::Identifier; +use move_core_types::language_storage::TypeTag; +use serde::{Deserialize, Serialize}; +use tw_coin_entry::error::prelude::*; + +#[derive(Debug, Deserialize, Serialize)] +pub enum TransactionData { + V1(TransactionDataV1), +} + +impl TransactionData { + #[inline] + pub fn new( + kind: TransactionKind, + sender: SuiAddress, + gas_payment: ObjectRef, + gas_budget: u64, + gas_price: u64, + ) -> Self { + TransactionData::V1(TransactionDataV1 { + kind, + sender, + gas_data: GasData { + price: gas_price, + owner: sender, + payment: vec![gas_payment], + budget: gas_budget, + }, + expiration: TransactionExpiration::None, + }) + } + + #[inline] + pub fn new_programmable( + sender: SuiAddress, + gas_payment: Vec, + pt: ProgrammableTransaction, + gas_budget: u64, + gas_price: u64, + ) -> Self { + Self::new_programmable_allow_sponsor(sender, gas_payment, pt, gas_budget, gas_price, sender) + } + + #[inline] + pub fn new_programmable_allow_sponsor( + sender: SuiAddress, + gas_payment: Vec, + pt: ProgrammableTransaction, + gas_budget: u64, + gas_price: u64, + sponsor: SuiAddress, + ) -> Self { + let kind = TransactionKind::ProgrammableTransaction(pt); + Self::new_with_gas_coins_allow_sponsor( + kind, + sender, + gas_payment, + gas_budget, + gas_price, + sponsor, + ) + } + + #[inline] + pub fn new_with_gas_coins_allow_sponsor( + kind: TransactionKind, + sender: SuiAddress, + gas_payment: Vec, + gas_budget: u64, + gas_price: u64, + gas_sponsor: SuiAddress, + ) -> Self { + TransactionData::V1(TransactionDataV1 { + kind, + sender, + gas_data: GasData { + price: gas_price, + owner: gas_sponsor, + payment: gas_payment, + budget: gas_budget, + }, + expiration: TransactionExpiration::None, + }) + } + + pub fn new_pay( + sender: SuiAddress, + coins: Vec, + recipients: Vec, + amounts: Vec, + gas_payment: ObjectRef, + gas_budget: u64, + gas_price: u64, + ) -> SigningResult { + let pt = { + let mut builder = ProgrammableTransactionBuilder::default(); + builder.pay(coins, recipients, amounts)?; + builder.finish() + }; + Ok(Self::new_programmable( + sender, + vec![gas_payment], + pt, + gas_budget, + gas_price, + )) + } + + pub fn new_pay_sui( + sender: SuiAddress, + mut coins: Vec, + recipients: Vec, + amounts: Vec, + gas_payment: ObjectRef, + gas_budget: u64, + gas_price: u64, + ) -> SigningResult { + coins.insert(0, gas_payment); + let pt = { + let mut builder = ProgrammableTransactionBuilder::default(); + builder.pay_sui(recipients, amounts)?; + builder.finish() + }; + Ok(Self::new_programmable( + sender, coins, pt, gas_budget, gas_price, + )) + } + + pub fn new_pay_all_sui( + sender: SuiAddress, + mut coins: Vec, + recipient: SuiAddress, + gas_payment: ObjectRef, + gas_budget: u64, + gas_price: u64, + ) -> Self { + coins.insert(0, gas_payment); + let pt = { + let mut builder = ProgrammableTransactionBuilder::default(); + builder.pay_all_sui(recipient); + builder.finish() + }; + Self::new_programmable(sender, coins, pt, gas_budget, gas_price) + } + + #[allow(clippy::too_many_arguments)] + pub fn new_move_call( + sender: SuiAddress, + package: ObjectID, + module: Identifier, + function: Identifier, + type_arguments: Vec, + gas_payment: ObjectRef, + arguments: Vec, + gas_budget: u64, + gas_price: u64, + ) -> SigningResult { + Self::new_move_call_with_gas_coins( + sender, + package, + module, + function, + type_arguments, + vec![gas_payment], + arguments, + gas_budget, + gas_price, + ) + } + + #[allow(clippy::too_many_arguments)] + pub fn new_move_call_with_gas_coins( + sender: SuiAddress, + package: ObjectID, + module: Identifier, + function: Identifier, + type_arguments: Vec, + gas_payment: Vec, + arguments: Vec, + gas_budget: u64, + gas_price: u64, + ) -> SigningResult { + let pt = { + let mut builder = ProgrammableTransactionBuilder::default(); + builder.move_call(package, module, function, type_arguments, arguments)?; + builder.finish() + }; + Ok(Self::new_programmable( + sender, + gas_payment, + pt, + gas_budget, + gas_price, + )) + } + + pub fn sender(&self) -> SuiAddress { + match self { + TransactionData::V1(v1) => v1.sender, + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct TransactionDataV1 { + pub kind: TransactionKind, + pub sender: SuiAddress, + pub gas_data: GasData, + pub expiration: TransactionExpiration, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum TransactionKind { + /// A transaction that allows the interleaving of native commands and Move calls + ProgrammableTransaction(ProgrammableTransaction), +} diff --git a/rust/chains/tw_sui/tests/decode_transaction.rs b/rust/chains/tw_sui/tests/decode_transaction.rs new file mode 100644 index 00000000000..4b2c8c4a370 --- /dev/null +++ b/rust/chains/tw_sui/tests/decode_transaction.rs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::str::FromStr; +use tw_encoding::base64::{self, STANDARD}; +use tw_encoding::bcs; +use tw_encoding::hex::DecodeHex; +use tw_sui::address::SuiAddress; +use tw_sui::transaction::command::{Argument, Command}; +use tw_sui::transaction::programmable_transaction::ProgrammableTransaction; +use tw_sui::transaction::sui_types::{ + CallArg, GasData, ObjectDigest, ObjectID, SequenceNumber, TransactionExpiration, +}; +use tw_sui::transaction::transaction_data::{TransactionData, TransactionDataV1, TransactionKind}; + +#[test] +fn test_decode_transfer_tx() { + let programmable = ProgrammableTransaction { + inputs: vec![ + CallArg::Pure("1027000000000000".decode_hex().unwrap()), + CallArg::Pure( + "259ff8074ab425cbb489f236e18e08f03f1a7856bdf7c7a2877bd64f738b5015" + .decode_hex() + .unwrap(), + ), + ], + commands: vec![ + Command::SplitCoins(Argument::GasCoin, vec![Argument::Input(0)]), + Command::TransferObjects(vec![Argument::NestedResult(0, 0)], Argument::Input(1)), + ], + }; + + let v1 = TransactionDataV1 { + kind: TransactionKind::ProgrammableTransaction(programmable), + sender: SuiAddress::from_str( + "0xd575ad7f18e948462a5cf698f564ef394a752a71fec62493af8a055c012c0d50", + ) + .unwrap(), + gas_data: GasData { + payment: vec![( + ObjectID( + SuiAddress::from_str( + "0x06f2c2c8c1d8964df1019d6616e9705719bebabd931da2755cb948ceb7e68964", + ) + .unwrap() + .into_inner(), + ), + SequenceNumber(748), + ObjectDigest::from_str("7UoYeVzREVT17ZyYbRTsKzRCec5xJWm6FMh8AKaDPdDx").unwrap(), + )], + owner: SuiAddress::from_str( + "0xd575ad7f18e948462a5cf698f564ef394a752a71fec62493af8a055c012c0d50", + ) + .unwrap(), + price: 1, + budget: 2000, + }, + expiration: TransactionExpiration::None, + }; + let data = TransactionData::V1(v1); + + let bytes = base64::encode(&bcs::encode(&data).unwrap(), STANDARD); + // Successfully broadcasted https://explorer.sui.io/txblock/HkPo6rYPyDY53x1MBszvSZVZyixVN7CHvCJGX381czAh?network=devnet + assert_eq!(bytes, "AAACAAgQJwAAAAAAAAAgJZ/4B0q0Jcu0ifI24Y4I8D8aeFa998eih3vWT3OLUBUCAgABAQAAAQEDAAAAAAEBANV1rX8Y6UhGKlz2mPVk7zlKdSpx/sYkk6+KBVwBLA1QAQbywsjB2JZN8QGdZhbpcFcZvrq9kx2idVy5SM635olk7AIAAAAAAAAgYEVuxmf1zRBGdoDr+VDtMpIFF12s2Ua7I2ru1XyGF8/Vda1/GOlIRipc9pj1ZO85SnUqcf7GJJOvigVcASwNUAEAAAAAAAAA0AcAAAAAAAAA"); +} diff --git a/rust/chains/tw_thorchain/Cargo.toml b/rust/chains/tw_thorchain/Cargo.toml new file mode 100644 index 00000000000..a39bc4d7c80 --- /dev/null +++ b/rust/chains/tw_thorchain/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tw_thorchain" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk", features = ["test-utils"] } diff --git a/rust/chains/tw_thorchain/src/compiler.rs b/rust/chains/tw_thorchain/src/compiler.rs new file mode 100644 index 00000000000..7e3e81ad090 --- /dev/null +++ b/rust/chains/tw_thorchain/src/compiler.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::signing_input::ThorchainSigningInput; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct ThorchainCompiler; + +impl ThorchainCompiler { + pub fn preimage_hashes( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + ThorchainSigningInput::prepare_signing_input(&mut input); + TWTransactionCompiler::::preimage_hashes(coin, input) + } + + pub fn compile( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + ThorchainSigningInput::prepare_signing_input(&mut input); + TWTransactionCompiler::::compile( + coin, + input, + signatures, + public_keys, + ) + } +} diff --git a/rust/chains/tw_thorchain/src/entry.rs b/rust/chains/tw_thorchain/src/entry.rs new file mode 100644 index 00000000000..9f93134601f --- /dev/null +++ b/rust/chains/tw_thorchain/src/entry.rs @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::compiler::ThorchainCompiler; +use crate::signer::ThorchainSigner; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; +use tw_coin_entry::modules::wallet_connector::NoWalletConnector; +use tw_cosmos_sdk::address::{Address, Bech32Prefix}; +use tw_keypair::tw; +use tw_proto::Cosmos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct ThorchainEntry; + +impl CoinEntry for ThorchainEntry { + type AddressPrefix = Bech32Prefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + type WalletConnector = NoWalletConnector; + type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; + + #[inline] + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + prefix: Option, + ) -> AddressResult { + Address::from_str_with_coin_and_prefix(coin, address.to_string(), prefix) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: tw::PublicKey, + _derivation: Derivation, + prefix: Option, + ) -> AddressResult { + Address::with_public_key_coin_context(coin, &public_key, prefix) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + ThorchainSigner::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + ThorchainCompiler::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + ThorchainCompiler::compile(coin, input, signatures, public_keys) + } +} diff --git a/rust/chains/tw_thorchain/src/lib.rs b/rust/chains/tw_thorchain/src/lib.rs new file mode 100644 index 00000000000..9abacf4dfac --- /dev/null +++ b/rust/chains/tw_thorchain/src/lib.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod compiler; +pub mod entry; +pub mod signer; +pub mod signing_input; diff --git a/rust/chains/tw_thorchain/src/signer.rs b/rust/chains/tw_thorchain/src/signer.rs new file mode 100644 index 00000000000..7346be163af --- /dev/null +++ b/rust/chains/tw_thorchain/src/signer.rs @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::signing_input::ThorchainSigningInput; +use tw_coin_entry::coin_context::CoinContext; +use tw_cosmos_sdk::context::StandardCosmosContext; +use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; +use tw_proto::Cosmos::Proto; + +pub struct ThorchainSigner; + +impl ThorchainSigner { + pub fn sign( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + ) -> Proto::SigningOutput<'static> { + ThorchainSigningInput::prepare_signing_input(&mut input); + TWSigner::::sign(coin, input) + } +} diff --git a/rust/chains/tw_thorchain/src/signing_input.rs b/rust/chains/tw_thorchain/src/signing_input.rs new file mode 100644 index 00000000000..429a62a6434 --- /dev/null +++ b/rust/chains/tw_thorchain/src/signing_input.rs @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::borrow::Cow; +use tw_proto::Cosmos::Proto; +use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +const THORCHAIN_PREFIX_MSG_SEND: &str = "thorchain/MsgSend"; + +pub struct ThorchainSigningInput; + +impl ThorchainSigningInput { + pub fn prepare_signing_input(input: &mut Proto::SigningInput) { + for message in input.messages.iter_mut() { + if let MessageEnum::send_coins_message(ref mut msg_send) = message.message_oneof { + msg_send.type_prefix = Cow::from(THORCHAIN_PREFIX_MSG_SEND); + } + } + } +} diff --git a/rust/chains/tw_ton/Cargo.toml b/rust/chains/tw_ton/Cargo.toml new file mode 100644 index 00000000000..076a7d84f61 --- /dev/null +++ b/rust/chains/tw_ton/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "tw_ton" +version = "0.1.0" +edition = "2021" + +[dependencies] +lazy_static = "1.4.0" +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_encoding = { path = "../../tw_encoding" } +tw_hash = { path = "../../tw_hash" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_number = { path = "../../tw_number" } +tw_misc = { path = "../../tw_misc" } +tw_proto = { path = "../../tw_proto" } +tw_ton_sdk = { path = "../../frameworks/tw_ton_sdk" } diff --git a/rust/chains/tw_ton/fuzz/.gitignore b/rust/chains/tw_ton/fuzz/.gitignore new file mode 100644 index 00000000000..5c404b9583f --- /dev/null +++ b/rust/chains/tw_ton/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +Cargo.lock diff --git a/rust/chains/tw_ton/fuzz/Cargo.toml b/rust/chains/tw_ton/fuzz/Cargo.toml new file mode 100644 index 00000000000..671fc399a02 --- /dev/null +++ b/rust/chains/tw_ton/fuzz/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "tw_ton-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +tw_any_coin = { path = "../../../tw_any_coin", features = ["test-utils"] } +tw_coin_registry = { path = "../../../tw_coin_registry" } +tw_proto = { path = "../../../tw_proto", features = ["fuzz"] } + +[dependencies.tw_ton] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "sign" +path = "fuzz_targets/sign.rs" +test = false +doc = false diff --git a/rust/chains/tw_ton/fuzz/fuzz_targets/sign.rs b/rust/chains/tw_ton/fuzz/fuzz_targets/sign.rs new file mode 100644 index 00000000000..4c2a190be6a --- /dev/null +++ b/rust/chains/tw_ton/fuzz/fuzz_targets/sign.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_any_coin::test_utils::sign_utils::AnySignerHelper; +use tw_coin_registry::coin_type::CoinType; +use tw_proto::TheOpenNetwork::Proto; + +fuzz_target!(|input: Proto::SigningInput<'_>| { + let mut signer = AnySignerHelper::::default(); + let _ = signer.sign(CoinType::TON, input); +}); diff --git a/rust/chains/tw_ton/resources/wallet/wallet_v4r2.code b/rust/chains/tw_ton/resources/wallet/wallet_v4r2.code new file mode 100644 index 00000000000..e1d04cde08a --- /dev/null +++ b/rust/chains/tw_ton/resources/wallet/wallet_v4r2.code @@ -0,0 +1 @@ +te6cckECFAEAAtQAART/APSkE/S88sgLAQIBIAIDAgFIBAUE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8QERITAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNBgcCASAICQB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+gIZ9ADLaRfLH1Jgyz8gyYBA+wAGAIpQBIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UAXKwjiOCEGRzdHKDHrFwgBhQBcsFUAPPFiP6AhPLassfyz/JgED7AJJfA+ICASAKCwBZvSQrb2omhAgKBrkPoCGEcNQICEekk30pkQzmkD6f+YN4EoAbeBAUiYcVnzGEAgFYDA0AEbjJftRNDXCx+AA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIA4PABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AAG7SB/oA1NQi+QAFyMoHFcv/ydB3dIAYyMsFywIizxZQBfoCFMtrEszMyXP7AMhAFIEBCPRR8qcCAHCBAQjXGPoA0z/IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBsgQEI1xj6ANM/MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAAr0AMntVGliJeU= \ No newline at end of file diff --git a/rust/chains/tw_ton/resources/wallet/wallet_v5r1.code b/rust/chains/tw_ton/resources/wallet/wallet_v5r1.code new file mode 100644 index 00000000000..586cfc39e88 --- /dev/null +++ b/rust/chains/tw_ton/resources/wallet/wallet_v5r1.code @@ -0,0 +1 @@ +te6cckECFAEAAoEAART/APSkE/S88sgLAQIBIAINAgFIAwQC3NAg10nBIJFbj2Mg1wsfIIIQZXh0br0hghBzaW50vbCSXwPgghBleHRuuo60gCDXIQHQdNch+kAw+kT4KPpEMFi9kVvg7UTQgQFB1yH0BYMH9A5voTGRMOGAQNchcH/bPOAxINdJgQKAuZEw4HDiEA8CASAFDAIBIAYJAgFuBwgAGa3OdqJoQCDrkOuF/8AAGa8d9qJoQBDrkOuFj8ACAUgKCwAXsyX7UTQcdch1wsfgABGyYvtRNDXCgCAAGb5fD2omhAgKDrkPoCwBAvIOAR4g1wsfghBzaWduuvLgin8PAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYEAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKERITAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNC01sNe \ No newline at end of file diff --git a/rust/chains/tw_ton/src/address.rs b/rust/chains/tw_ton/src/address.rs new file mode 100644 index 00000000000..3175a4cab0b --- /dev/null +++ b/rust/chains/tw_ton/src/address.rs @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::resources::{BASE_WORKCHAIN, MASTER_WORKCHAIN}; +use std::fmt; +use std::str::FromStr; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::*; +use tw_hash::H256; +use tw_memory::Data; +use tw_ton_sdk::address::address_data::AddressData; +use tw_ton_sdk::address::raw_address::RawAddress; +use tw_ton_sdk::address::user_friendly_address::UserFriendlyAddress; + +pub const DEFAULT_BOUNCEABLE: bool = false; +pub const DEFAULT_TESTNET: bool = false; + +/// User-friendly, base64 URL-safe **by default** encoded TON address. +/// Please note it also supports raw (hex) and user-friendly base64 standard representations as well. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TonAddress(UserFriendlyAddress); + +impl TonAddress { + pub const NULL: TonAddress = TonAddress::null(); + + pub const fn null() -> TonAddress { + TonAddress(UserFriendlyAddress::with_flags( + AddressData::null(), + DEFAULT_BOUNCEABLE, + DEFAULT_TESTNET, + )) + } + + pub fn new(workchain: i32, hash_part: H256) -> Self { + let data = AddressData::new(workchain, hash_part); + TonAddress(UserFriendlyAddress::with_flags( + data, + DEFAULT_BOUNCEABLE, + DEFAULT_TESTNET, + )) + } + + pub fn from_hex_str(s: &str) -> AddressResult { + let raw_address = RawAddress::from_str(s)?; + let user_friendly_address = UserFriendlyAddress::with_flags( + raw_address.into_data(), + DEFAULT_BOUNCEABLE, + DEFAULT_TESTNET, + ); + Ok(TonAddress(user_friendly_address)) + } + + #[inline] + pub fn from_base64_url(s: &str) -> AddressResult { + UserFriendlyAddress::from_base64_url(s).map(TonAddress) + } + + #[inline] + pub fn from_base64_std(s: &str) -> AddressResult { + UserFriendlyAddress::from_base64_std(s).map(TonAddress) + } + + #[inline] + pub fn with_address_data(data: AddressData) -> TonAddress { + TonAddress(UserFriendlyAddress::with_flags( + data, + DEFAULT_BOUNCEABLE, + DEFAULT_TESTNET, + )) + } + + /// Normalizes the TON address according to the best wallet practice: + /// https://docs.ton.org/learn/overviews/addresses#bounceable-vs-non-bounceable-addresses + /// + /// Returns error if the address workchain is unexpected. + #[inline] + pub fn normalize(self) -> AddressResult { + let workchain = self.0.as_ref().workchain; + if workchain != MASTER_WORKCHAIN && workchain != BASE_WORKCHAIN { + return Err(AddressError::UnexpectedAddressPrefix); + } + + Ok(self + .set_bounceable(DEFAULT_BOUNCEABLE) + .set_testnet(DEFAULT_TESTNET)) + } + + #[inline] + pub fn set_bounceable(self, bounceable: bool) -> Self { + TonAddress(self.0.set_bounceable(bounceable)) + } + + #[inline] + pub fn set_testnet(self, testnet: bool) -> Self { + TonAddress(self.0.set_testnet(testnet)) + } + + #[inline] + pub fn bounceable(&self) -> bool { + self.0.bounceable() + } +} + +impl AsRef for TonAddress { + fn as_ref(&self) -> &AddressData { + self.0.as_ref() + } +} + +impl CoinAddress for TonAddress { + #[inline] + fn data(&self) -> Data { + self.0.as_ref().hash_part.to_vec() + } +} + +impl FromStr for TonAddress { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + if s.len() != 48 { + return TonAddress::from_hex_str(s); + } + + // Some form of base64 address, check which one + if s.contains('-') || s.contains('_') { + TonAddress::from_base64_url(s) + } else { + TonAddress::from_base64_std(s) + } + } +} + +impl fmt::Display for TonAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.to_base64_url()) + } +} diff --git a/rust/chains/tw_ton/src/compiler.rs b/rust/chains/tw_ton/src/compiler.rs new file mode 100644 index 00000000000..2bcea271351 --- /dev/null +++ b/rust/chains/tw_ton/src/compiler.rs @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::signing_request::builder::SigningRequestBuilder; +use crate::signing_request::cell_creator::ExternalMessageCreator; +use std::borrow::Cow; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_keypair::ed25519::Signature; +use tw_proto::TheOpenNetwork::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_ton_sdk::boc::BagOfCells; +use tw_ton_sdk::error::cell_to_signing_error; + +pub(crate) const HAS_CRC32: bool = true; + +pub struct TheOpenNetworkCompiler; + +impl TheOpenNetworkCompiler { + #[inline] + pub fn preimage_hashes( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + Self::preimage_hashes_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + fn preimage_hashes_impl( + _coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let signing_request = SigningRequestBuilder::build(&input)?; + + let external_message = + ExternalMessageCreator::create_external_message_to_sign(&signing_request) + .map_err(cell_to_signing_error)?; + + Ok(CompilerProto::PreSigningOutput { + data: Cow::from(external_message.cell_hash().to_vec()), + ..CompilerProto::PreSigningOutput::default() + }) + } + + #[inline] + pub fn compile( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + Self::compile_impl(coin, input, signatures, public_keys) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn compile_impl( + _coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + _public_keys: Vec, + ) -> SigningResult> { + if signatures.len() != 1 { + return TWError::err(SigningErrorType::Error_signatures_count) + .context("Expected exactly one signature"); + } + let signature = Signature::try_from(signatures[0].as_slice())?; + + let signing_request = SigningRequestBuilder::build(&input)?; + + let external_message = + ExternalMessageCreator::create_external_message_to_sign(&signing_request) + .map_err(cell_to_signing_error)?; + + let signed_external_message = signing_request + .wallet + .compile_signed_external_message(external_message, signature)?; + + // Whether to add 'StateInit' reference. + let state_init = signing_request.seqno == 0; + let signed_tx = signing_request + .wallet + .compile_transaction(signed_external_message, state_init) + .context("Error compiling an external message")? + .build() + .context("Error generating signed message cell") + .map_err(cell_to_signing_error)?; + + let signed_tx_hash = signed_tx.cell_hash(); + let signed_tx_encoded = BagOfCells::from_root(signed_tx) + .to_base64(HAS_CRC32) + .context("Error serializing signed transaction as BoC") + .map_err(cell_to_signing_error)?; + + Ok(Proto::SigningOutput { + encoded: signed_tx_encoded.into(), + hash: signed_tx_hash.to_vec().into(), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/chains/tw_ton/src/entry.rs b/rust/chains/tw_ton/src/entry.rs new file mode 100644 index 00000000000..a2bd5aadc2d --- /dev/null +++ b/rust/chains/tw_ton/src/entry.rs @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::TonAddress; +use crate::compiler::TheOpenNetworkCompiler; +use crate::modules::transaction_util::TonTransactionUtil; +use crate::signer::TheOpenNetworkSigner; +use crate::wallet::{wallet_v4, VersionedTonWallet}; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::wallet_connector::NoWalletConnector; +use tw_coin_entry::prefix::NoPrefix; +use tw_keypair::tw::PublicKey; +use tw_proto::TheOpenNetwork::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct TheOpenNetworkEntry; + +impl CoinEntry for TheOpenNetworkEntry { + type AddressPrefix = NoPrefix; + type Address = TonAddress; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + type WalletConnector = NoWalletConnector; + type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = TonTransactionUtil; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + // TODO consider checking whether the transaction is on testnet. + TonAddress::from_str(address).and_then(TonAddress::normalize) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + TonAddress::from_str(address).and_then(TonAddress::normalize) + } + + #[inline] + fn derive_address( + &self, + _coin: &dyn CoinContext, + public_key: PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + let ed25519_pubkey = public_key + .to_ed25519() + .ok_or(AddressError::PublicKeyTypeMismatch)?; + // Currently, we use the V4R2 wallet + let wallet = VersionedTonWallet::V4R2( + wallet_v4::WalletV4R2::std_with_public_key(ed25519_pubkey.clone()) + .map_err(|_| AddressError::Internal)?, + ); + Ok(wallet.address().clone()) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + TheOpenNetworkSigner::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + TheOpenNetworkCompiler::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + TheOpenNetworkCompiler::compile(coin, input, signatures, public_keys) + } + + #[inline] + fn transaction_util(&self) -> Option { + Some(TonTransactionUtil) + } +} diff --git a/rust/chains/tw_ton/src/lib.rs b/rust/chains/tw_ton/src/lib.rs new file mode 100644 index 00000000000..7e35d3bc165 --- /dev/null +++ b/rust/chains/tw_ton/src/lib.rs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod address; +pub mod compiler; +pub mod entry; +pub mod message; +pub mod modules; +pub mod resources; +pub mod signer; +pub mod signing_request; +pub mod transaction; +pub mod wallet; diff --git a/rust/chains/tw_ton/src/message/external_message/mod.rs b/rust/chains/tw_ton/src/message/external_message/mod.rs new file mode 100644 index 00000000000..88c077ae30c --- /dev/null +++ b/rust/chains/tw_ton/src/message/external_message/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod wallet_v4; +pub mod wallet_v5; diff --git a/rust/chains/tw_ton/src/message/external_message/wallet_v4.rs b/rust/chains/tw_ton/src/message/external_message/wallet_v4.rs new file mode 100644 index 00000000000..d23d0090de9 --- /dev/null +++ b/rust/chains/tw_ton/src/message/external_message/wallet_v4.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::message::internal_message::InternalMessage; +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::Cell; +use tw_ton_sdk::error::CellResult; + +pub struct ExternalMessageWalletV4 { + pub wallet_id: i32, + pub expire_at: u32, + pub seqno: u32, + pub internal_messages: Vec, +} + +impl ExternalMessageWalletV4 { + pub fn build(&self) -> CellResult { + let mut builder = CellBuilder::new(); + builder + .store_i32(32, self.wallet_id)? + .store_u32(32, self.expire_at)? + .store_u32(32, self.seqno)?; + builder.store_u8(8, 0)?; // has op + for internal_message in self.internal_messages.iter() { + builder.store_u8(8, internal_message.mode)?; + builder.store_reference(&internal_message.message)?; + } + builder.build() + } +} diff --git a/rust/chains/tw_ton/src/message/external_message/wallet_v5.rs b/rust/chains/tw_ton/src/message/external_message/wallet_v5.rs new file mode 100644 index 00000000000..af4a2863323 --- /dev/null +++ b/rust/chains/tw_ton/src/message/external_message/wallet_v5.rs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::message::out_list::build_out_list; +use crate::message::out_list::out_action::OutAction; +use tw_coin_entry::error::prelude::ResultContext; +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::Cell; +use tw_ton_sdk::error::{CellError, CellErrorType, CellResult}; + +const SEND_MODE_IGNORE_ACTION_PHASE_ERRORS: u8 = 0x02; + +pub enum V5R1OpCode { + // Currently, only AuthSignedExternal is supported. AuthSignedInternal/AuthExtension are not supported. + AuthSignedExternal, +} + +impl V5R1OpCode { + // See https://github.com/ton-blockchain/wallet-contract-v5/blob/88557ebc33047a95207f6e47ac8aadb102dff744/contracts/wallet_v5.fc#L33 + pub fn to_ser_tag(&self) -> u32 { + match self { + V5R1OpCode::AuthSignedExternal => 0x7369676e, + } + } +} + +pub struct ExternalMessageWalletV5 { + pub opcode: V5R1OpCode, + pub wallet_id: i32, + pub expire_at: u32, + pub seqno: u32, + /// Currently, only basic actions are supported. Extended actions are not supported. + pub basic_actions: Vec, +} + +impl ExternalMessageWalletV5 { + /// Build the external message for wallet v5. + pub fn build(&self) -> CellResult { + // Check the number of basic actions + if self.basic_actions.len() > 255 { + return CellError::err(CellErrorType::InternalError) + .context("Maximum number of actions in a single request is 255"); + } + + let mut builder = CellBuilder::new(); + match self.opcode { + V5R1OpCode::AuthSignedExternal => { + builder.store_u32(32, self.opcode.to_ser_tag())?; + + // Make sure +2 flag (ignore errors send mode) is set for all external send messages + // See https://github.com/ton-blockchain/wallet-contract-v5/blob/88557ebc33047a95207f6e47ac8aadb102dff744/contracts/wallet_v5.fc#L82 + for action in &self.basic_actions { + if (action.mode & SEND_MODE_IGNORE_ACTION_PHASE_ERRORS) == 0 { + return CellError::err(CellErrorType::InternalError) + .context("External send message must have ignore errors send mode"); + } + } + }, + } + builder + .store_i32(32, self.wallet_id)? + .store_u32(32, self.expire_at)? + .store_u32(32, self.seqno)?; + builder.store_bit(true)?; // true means basic actions is stored in reference + + let mut basic_actions = self.basic_actions.clone(); + basic_actions.reverse(); + + builder.store_child(build_out_list(&basic_actions)?)?; + builder.store_bit(false)?; // false means no extended actions + builder.build() + } +} diff --git a/rust/chains/tw_ton/src/message/internal_message/mod.rs b/rust/chains/tw_ton/src/message/internal_message/mod.rs new file mode 100644 index 00000000000..87516a8f068 --- /dev/null +++ b/rust/chains/tw_ton/src/message/internal_message/mod.rs @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_ton_sdk::cell::{Cell, CellArc}; + +pub mod transfer; + +pub struct InternalMessage { + /// https://docs.ton.org/develop/smart-contracts/messages#message-modes + pub mode: u8, + pub message: CellArc, +} + +impl InternalMessage { + pub fn new(mode: u8, message: Cell) -> InternalMessage { + InternalMessage { + mode, + message: message.into_arc(), + } + } +} diff --git a/rust/chains/tw_ton/src/message/internal_message/transfer.rs b/rust/chains/tw_ton/src/message/internal_message/transfer.rs new file mode 100644 index 00000000000..3da99cc0f09 --- /dev/null +++ b/rust/chains/tw_ton/src/message/internal_message/transfer.rs @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::TonAddress; +use tw_number::U256; +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::{Cell, CellArc}; +use tw_ton_sdk::error::CellResult; + +const BIT_0: bool = false; +const IHR_DISABLED: bool = true; +const BOUNCED: bool = false; +const CURRENCY_COLLECTIONS: bool = false; +const CREATED_LT: u64 = 0; +const CREATED_AT: u32 = 0; +const IHR_FEES: U256 = U256::ZERO; +const FWD_FEES: U256 = U256::ZERO; + +/// Standard Internal message - transfer TON. +pub struct TransferInternalMessage { + pub dest: TonAddress, + pub value: U256, + /// Deploy a smart contract different from the sender's wallet contract. + /// For example, to deploy a chatbot Doge: https://github.com/LaDoger/doge.fc + /// + /// Note consider using [`ExternalMessage::state_init`] to deploy the sender's wallet contract. + pub state_init: Option, + pub data: Option, +} + +impl TransferInternalMessage { + pub fn new(dest: TonAddress, value: U256) -> Self { + TransferInternalMessage { + dest, + value, + state_init: None, + data: None, + } + } + + pub fn with_data(&mut self, data: CellArc) -> &mut Self { + self.data = Some(data); + self + } + + pub fn with_state_init(&mut self, state_init: CellArc) -> &mut Self { + self.state_init = Some(state_init); + self + } + + pub fn build(&self) -> CellResult { + let mut builder = CellBuilder::new(); + builder.store_bit(BIT_0)?; // bit0 + builder.store_bit(IHR_DISABLED)?; // ihr_disabled + builder.store_bit(self.dest.bounceable())?; // bounce + builder.store_bit(BOUNCED)?; // bounced + builder.store_address(&TonAddress::NULL)?; // src_addr + builder.store_address(&self.dest)?; // dest_addr + builder.store_coins(&self.value)?; // value + builder.store_bit(CURRENCY_COLLECTIONS)?; // currency_collections + builder.store_coins(&IHR_FEES)?; // ihr_fees + builder.store_coins(&FWD_FEES)?; // fwd_fees + builder.store_u64(64, CREATED_LT)?; // created_lt + builder.store_u32(32, CREATED_AT)?; // created_at + + // (Maybe (Either StateInit ^StateInit)) + builder.store_bit(self.state_init.is_some())?; // state_init? + if let Some(state_init) = self.state_init.as_ref() { + builder.store_bit(true)?; // store state_init as a reference, not inline + builder.store_reference(state_init)?; + } + + // (Either X ^X) = Message X + builder.store_bit(self.data.is_some())?; // data? + if let Some(data) = self.data.as_ref() { + builder.store_reference(data)?; + } + + builder.build() + } +} diff --git a/rust/chains/tw_ton/src/message/mod.rs b/rust/chains/tw_ton/src/message/mod.rs new file mode 100644 index 00000000000..0996f7b8276 --- /dev/null +++ b/rust/chains/tw_ton/src/message/mod.rs @@ -0,0 +1,5 @@ +pub mod external_message; +pub mod internal_message; +pub mod out_list; +pub mod payload; +pub mod signed_message; diff --git a/rust/chains/tw_ton/src/message/out_list/mod.rs b/rust/chains/tw_ton/src/message/out_list/mod.rs new file mode 100644 index 00000000000..85f6df623d8 --- /dev/null +++ b/rust/chains/tw_ton/src/message/out_list/mod.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::message::out_list::out_action::OutAction; +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::Cell; +use tw_ton_sdk::error::CellResult; + +pub mod out_action; + +pub fn build_out_list(actions: &[OutAction]) -> CellResult { + let cell = actions + .iter() + .fold(CellBuilder::new().build(), |acc, action| { + let mut builder = CellBuilder::new(); + builder.store_child(acc?)?; + builder.store_cell(&action.build()?)?; + builder.build() + }); + + let mut builder = CellBuilder::new(); + builder.store_cell(&cell?)?; + builder.build() +} diff --git a/rust/chains/tw_ton/src/message/out_list/out_action.rs b/rust/chains/tw_ton/src/message/out_list/out_action.rs new file mode 100644 index 00000000000..bf270c307b2 --- /dev/null +++ b/rust/chains/tw_ton/src/message/out_list/out_action.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::{Cell, CellArc}; +use tw_ton_sdk::error::CellResult; + +#[derive(Clone)] +pub enum OutActionType { + // Currently, only SendMsg is supported. SetCode is not supported. + SendMsg, +} + +impl OutActionType { + // See https://ton.org/tblkch.pdf 4.4.11. Serialization of output actions + pub fn to_ser_tag(&self) -> u32 { + match self { + OutActionType::SendMsg => 0x0ec3c86d, + } + } +} + +#[derive(Clone)] +pub struct OutAction { + pub typ: OutActionType, + pub mode: u8, + pub data: CellArc, // out msg (SendMsg) or new code (SetCode) +} + +impl OutAction { + pub fn new(typ: OutActionType, mode: u8, out_msg: CellArc) -> Self { + OutAction { + typ, + mode, + data: out_msg, + } + } + + pub fn build(&self) -> CellResult { + match self.typ { + OutActionType::SendMsg => self.build_out_action_send_msg(), + } + } + + fn build_out_action_send_msg(&self) -> CellResult { + let mut builder = CellBuilder::new(); + builder + .store_u32(32, self.typ.to_ser_tag())? + .store_u8(8, self.mode)? + .store_reference(&self.data)?; + builder.build() + } +} diff --git a/rust/chains/tw_ton/src/message/payload/comment.rs b/rust/chains/tw_ton/src/message/payload/comment.rs new file mode 100644 index 00000000000..64176ae1d15 --- /dev/null +++ b/rust/chains/tw_ton/src/message/payload/comment.rs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::Cell; +use tw_ton_sdk::error::CellResult; + +/// Transaction payload the consists of an arbitrary comment only. +pub struct CommentPayload { + comment: String, +} + +impl CommentPayload { + pub fn new(comment: String) -> Self { + CommentPayload { comment } + } + + pub fn build(&self) -> CellResult { + let mut builder = CellBuilder::new(); + builder.store_u32(32, 0)?.store_string(&self.comment)?; + builder.build() + } +} diff --git a/rust/chains/tw_ton/src/message/payload/empty.rs b/rust/chains/tw_ton/src/message/payload/empty.rs new file mode 100644 index 00000000000..724cad0592e --- /dev/null +++ b/rust/chains/tw_ton/src/message/payload/empty.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::Cell; +use tw_ton_sdk::error::CellResult; + +/// Empty transaction payload. +pub struct EmptyPayload; + +impl EmptyPayload { + pub fn build(&self) -> CellResult { + CellBuilder::new().build() + } +} diff --git a/rust/chains/tw_ton/src/message/payload/jetton_transfer.rs b/rust/chains/tw_ton/src/message/payload/jetton_transfer.rs new file mode 100644 index 00000000000..b6e687551ae --- /dev/null +++ b/rust/chains/tw_ton/src/message/payload/jetton_transfer.rs @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::TonAddress; +use tw_coin_entry::error::prelude::ResultContext; +use tw_number::U256; +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::{Cell, CellArc}; +use tw_ton_sdk::error::{CellError, CellErrorType, CellResult}; + +pub const JETTON_TRANSFER: u32 = 0x0f8a7ea5; + +/// Jetton transfer message payload with an optional comment. +#[derive(Debug)] +pub struct JettonTransferPayload { + /// Arbitrary request number. + query_id: u64, + /// Amount of transferred jettons in elementary units. + jetton_amount: U256, + /// Address of the new owner of the jettons. + destination: TonAddress, + /// Address where to send a response with confirmation of a successful transfer and the rest of the incoming message Toncoins. + response_destination: TonAddress, + /// Optional custom data (which is used by either sender or receiver jetton wallet for inner logic). + /// At WalletCore, we do not use `custom_payload` at the moment. + #[allow(dead_code)] + custom_payload: Option, + /// Amount of nanotons to be sent to the destination address. + forward_ton_amount: U256, + /// Optional custom data that should be sent to the destination address. + /// At WalletCore, we do not use `forward_payload` at the moment. + #[allow(dead_code)] + forward_payload: Option, + /// Optional transfer comment. + comment: Option, +} + +impl JettonTransferPayload { + pub fn new(destination: TonAddress, jetton_amount: U256) -> Self { + JettonTransferPayload { + query_id: 0, + jetton_amount, + destination, + response_destination: TonAddress::null(), + custom_payload: None, + forward_ton_amount: U256::zero(), + forward_payload: None, + comment: None, + } + } + + pub fn with_query_id(&mut self, query_id: u64) -> &mut Self { + self.query_id = query_id; + self + } + + pub fn with_response_destination(&mut self, response_destination: TonAddress) -> &mut Self { + self.response_destination = response_destination; + self + } + + pub fn with_comment(&mut self, comment: String) -> &mut Self { + self.comment = Some(comment); + self + } + + pub fn with_forward_ton_amount(&mut self, forward_ton_amount: U256) -> &mut Self { + self.forward_ton_amount = forward_ton_amount; + self + } + + pub fn build(&self) -> CellResult { + if self.forward_ton_amount.is_zero() && self.forward_payload.is_some() { + return CellError::err(CellErrorType::CellBuilderError) + .context("Forward_ton_amount must be positive when specifying forward_payload"); + } + + let mut message = CellBuilder::new(); + message.store_u32(32, JETTON_TRANSFER)?; + message.store_u64(64, self.query_id)?; + message.store_coins(&self.jetton_amount)?; + message.store_address(&self.destination)?; + message.store_address(&self.response_destination)?; + + if let Some(ref cp) = self.custom_payload { + message.store_bit(true)?; + message.store_reference(cp)?; + } else { + message.store_bit(false)?; + } + + message.store_coins(&self.forward_ton_amount)?; + + if let Some(ref fp) = self.forward_payload { + message.store_bit(true)?; + message.store_reference(fp)?; + } else { + message.store_bit(false)?; + } + + if let Some(ref comment) = self.comment { + message.store_u32(32, 0)?; + message.store_string(comment)?; + } + + message.build() + } +} diff --git a/rust/chains/tw_ton/src/message/payload/mod.rs b/rust/chains/tw_ton/src/message/payload/mod.rs new file mode 100644 index 00000000000..8b50715cd12 --- /dev/null +++ b/rust/chains/tw_ton/src/message/payload/mod.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod comment; +pub mod empty; +pub mod jetton_transfer; diff --git a/rust/chains/tw_ton/src/message/signed_message/mod.rs b/rust/chains/tw_ton/src/message/signed_message/mod.rs new file mode 100644 index 00000000000..cfe3788fe74 --- /dev/null +++ b/rust/chains/tw_ton/src/message/signed_message/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod signed_message_v4; +pub mod signed_message_v5; diff --git a/rust/chains/tw_ton/src/message/signed_message/signed_message_v4.rs b/rust/chains/tw_ton/src/message/signed_message/signed_message_v4.rs new file mode 100644 index 00000000000..4b624b247f0 --- /dev/null +++ b/rust/chains/tw_ton/src/message/signed_message/signed_message_v4.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_hash::H512; +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::Cell; +use tw_ton_sdk::error::*; + +pub struct SignedMessageV4 { + pub signature: H512, + pub external_message: Cell, +} + +impl SignedMessageV4 { + pub fn build(&self) -> CellResult { + let mut body_builder = CellBuilder::new(); + + // In the case of WALLET_V4_R2, the signature is stored before the external message. + body_builder.store_slice(self.signature.as_slice())?; + body_builder.store_cell(&self.external_message)?; + + body_builder.build() + } +} diff --git a/rust/chains/tw_ton/src/message/signed_message/signed_message_v5.rs b/rust/chains/tw_ton/src/message/signed_message/signed_message_v5.rs new file mode 100644 index 00000000000..932fafa9894 --- /dev/null +++ b/rust/chains/tw_ton/src/message/signed_message/signed_message_v5.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_hash::H512; +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::Cell; +use tw_ton_sdk::error::*; + +pub struct SignedMessageV5 { + pub signature: H512, + pub external_message: Cell, +} + +impl SignedMessageV5 { + pub fn build(&self) -> CellResult { + let mut body_builder = CellBuilder::new(); + + // In the case of WALLET_V5_R1, the signature is stored after the external message. + body_builder.store_cell(&self.external_message)?; + body_builder.store_slice(self.signature.as_slice())?; + + body_builder.build() + } +} diff --git a/rust/chains/tw_ton/src/modules/address_converter.rs b/rust/chains/tw_ton/src/modules/address_converter.rs new file mode 100644 index 00000000000..733524140af --- /dev/null +++ b/rust/chains/tw_ton/src/modules/address_converter.rs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::TonAddress; +use tw_coin_entry::error::prelude::ResultContext; +use tw_ton_sdk::boc::BagOfCells; +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::Cell; +use tw_ton_sdk::error::CellResult; + +const HAS_CRC32: bool = true; + +pub struct AddressConverter; + +impl AddressConverter { + /// Converts a TON user address into a single root Cell. + pub fn convert_to_cell(addr: &TonAddress) -> CellResult { + let mut builder = CellBuilder::new(); + builder + .store_address(addr) + .context("Error storing given address to CellBuilder")?; + builder.build() + } + + /// Converts a TON user address into a Bag of Cells (BoC) with a single root Cell. + /// The function is mostly used to request a Jetton user address via `get_wallet_address` RPC. + /// https://docs.ton.org/develop/dapps/asset-processing/jettons#retrieving-jetton-wallet-addresses-for-a-given-user + pub fn convert_to_boc(addr: &TonAddress) -> CellResult { + Self::convert_to_cell(addr).map(BagOfCells::from_root) + } + + /// Converts a TON user address into a Bag of Cells (BoC) with a single root Cell. + /// The function is mostly used to request a Jetton user address via `get_wallet_address` RPC. + /// https://docs.ton.org/develop/dapps/asset-processing/jettons#retrieving-jetton-wallet-addresses-for-a-given-user + pub fn convert_to_boc_base64(addr: &TonAddress) -> CellResult { + Self::convert_to_boc(addr).and_then(|boc| boc.to_base64(HAS_CRC32)) + } + + /// Parses a TON address from a single root Cell. + pub fn parse_from_cell(cell: &Cell) -> CellResult { + cell.parse_fully(|parser| parser.load_address()) + .map(TonAddress::with_address_data) + } + + /// Parses a TON address from a Bag of Cells (BoC) with a single root Cell. + /// The function is mostly used to parse a Jetton user address received on `get_wallet_address` RPC. + /// https://docs.ton.org/develop/dapps/asset-processing/jettons#retrieving-jetton-wallet-addresses-for-a-given-user + pub fn parse_from_boc(boc: &BagOfCells) -> CellResult { + boc.single_root() + .and_then(|cell| Self::parse_from_cell(cell)) + } + + /// Parses a TON address from a Bag of Cells (BoC) with a single root Cell. + /// The function is mostly used to parse a Jetton user address received on `get_wallet_address` RPC. + /// https://docs.ton.org/develop/dapps/asset-processing/jettons#retrieving-jetton-wallet-addresses-for-a-given-user + pub fn parse_from_boc_base64(boc: &str) -> CellResult { + BagOfCells::parse_base64(boc).and_then(|boc| Self::parse_from_boc(&boc)) + } +} diff --git a/rust/chains/tw_ton/src/modules/mod.rs b/rust/chains/tw_ton/src/modules/mod.rs new file mode 100644 index 00000000000..95d32dc4847 --- /dev/null +++ b/rust/chains/tw_ton/src/modules/mod.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod address_converter; +pub mod personal_message_signer; +pub mod transaction_util; +pub mod wallet_provider; diff --git a/rust/chains/tw_ton/src/modules/personal_message_signer.rs b/rust/chains/tw_ton/src/modules/personal_message_signer.rs new file mode 100644 index 00000000000..fad7a7a023d --- /dev/null +++ b/rust/chains/tw_ton/src/modules/personal_message_signer.rs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_hash::sha2::sha512; +use tw_keypair::ed25519::sha512::PrivateKey; +use tw_keypair::ed25519::Signature; +use tw_keypair::traits::SigningKeyTrait; +use tw_keypair::KeyPairResult; + +pub const TON_PERSONAL_MESSAGE_PREFIX: &str = "ton-safe-sign-magic"; + +pub struct PersonalMessageSigner; + +impl PersonalMessageSigner { + /// Signs an arbitrary message. + /// https://www.openmask.app/docs/api-reference/rpc-api#ton_personalsign + /// https://github.com/OpenProduct/openmask-extension/blob/7566ceb2772fed7a3a27d2a67bd34bf89e862557/src/view/screen/notifications/sign/api.ts#L21-L48 + pub fn sign(private_key: &PrivateKey, msg: &str) -> KeyPairResult { + let msg_hash = sha512(msg.as_bytes()); + + let mut msg_to_sign = vec![0xff_u8, 0xff]; + msg_to_sign.extend_from_slice(TON_PERSONAL_MESSAGE_PREFIX.as_bytes()); + msg_to_sign.extend_from_slice(msg_hash.as_slice()); + + private_key.sign(msg_to_sign) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_hash::H512; + + #[test] + fn test_sign_personal_message() { + // The private key has been derived by using [ton-mnemonic](https://www.npmjs.com/package/tonweb-mnemonic/v/0.0.2) + // from the following mnemonic: + // document shield addict crime broom point story depend suit satisfy test chicken valid tail speak fortune sound drill seek cube cheap body music recipe + let private_key = PrivateKey::try_from( + "112d4e2e700a468f1eae699329202f1ee671d6b665caa2d92dea038cf3868c18", + ) + .unwrap(); + + let signature = PersonalMessageSigner::sign(&private_key, "Hello world").unwrap(); + // The following signature has been computed by calling `window.ton.send("ton_personalSign", { data: "Hello world" });`. + let expected_sig = "2490fbaa72aec0b77b19162bbbe0b0e3f7afd42cc9ef469f0494cd4a366a4bf76643300cd5991f66bce6006336742b8d1d435d541d244dcc013d428472e89504"; + assert_eq!(signature.to_bytes(), H512::from(expected_sig)); + } +} diff --git a/rust/chains/tw_ton/src/modules/transaction_util.rs b/rust/chains/tw_ton/src/modules/transaction_util.rs new file mode 100644 index 00000000000..47200e82187 --- /dev/null +++ b/rust/chains/tw_ton/src/modules/transaction_util.rs @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::transaction_util::TransactionUtil; +use tw_ton_sdk::boc::BagOfCells; + +pub struct TonTransactionUtil; + +impl TransactionUtil for TonTransactionUtil { + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + Self::calc_tx_hash_impl(coin, encoded_tx) + } +} + +impl TonTransactionUtil { + // In the TON blockchain, there are both message hashes and transaction hashes. + // Strictly speaking, this function returns the message hash, not the transaction hash, + // because we often use the TON message hash to track transaction status. + // The transaction hash is unknown until the transaction is included in a block. + fn calc_tx_hash_impl(_coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + let boc = BagOfCells::parse_base64(encoded_tx) + .map_err(|_| SigningErrorType::Error_input_parse)?; + let root_cell_hash = boc + .roots + .first() + .ok_or(SigningErrorType::Error_input_parse)? + .cell_hash(); + + // The message hash in TON can be encoded in base64, base64url, or hex. + // Here, we return the message hash in hex encoding. + Ok(root_cell_hash.to_string()) + } +} diff --git a/rust/chains/tw_ton/src/modules/wallet_provider.rs b/rust/chains/tw_ton/src/modules/wallet_provider.rs new file mode 100644 index 00000000000..ed09e7f93df --- /dev/null +++ b/rust/chains/tw_ton/src/modules/wallet_provider.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::wallet::{wallet_v4, wallet_v5}; +use tw_keypair::ed25519::sha512::PublicKey; +use tw_ton_sdk::boc::BagOfCells; +use tw_ton_sdk::error::CellResult; + +const HAS_CRC32: bool = true; + +/// TON Wallet common functionality. +pub struct WalletProvider; + +impl WalletProvider { + /// Constructs a TON Wallet V4R2 stateInit encoded as BoC (BagOfCells) for the given `public_key`. + pub fn v4r2_state_init( + public_key: PublicKey, + workchain: i32, + wallet_id: i32, + ) -> CellResult { + let state_init = wallet_v4::WalletV4R2::with_public_key(workchain, public_key, wallet_id)? + .state_init()? + .to_cell()?; + BagOfCells::from_root(state_init).to_base64(HAS_CRC32) + } + + /// Constructs a TON Wallet V5R1 stateInit encoded as BoC (BagOfCells) for the given `public_key`. + pub fn v5r1_state_init( + public_key: PublicKey, + workchain: i32, + wallet_id: i32, + ) -> CellResult { + let state_init = wallet_v5::WalletV5R1::with_public_key(workchain, public_key, wallet_id)? + .state_init()? + .to_cell()?; + BagOfCells::from_root(state_init).to_base64(HAS_CRC32) + } +} diff --git a/rust/chains/tw_ton/src/resources.rs b/rust/chains/tw_ton/src/resources.rs new file mode 100644 index 00000000000..04a1ad37c81 --- /dev/null +++ b/rust/chains/tw_ton/src/resources.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use lazy_static::lazy_static; +use tw_ton_sdk::boc::BagOfCells; + +pub const DEFAULT_WALLET_ID: i32 = 0x29a9a317; +/// The wallet id 2147483409 comes from: https://github.com/ton-org/ton/blob/f9842909ac0e7d6f66d055dd18a4c41ec3416c02/src/wallets/v5r1/WalletV5R1WalletId.ts#L21C22-L21C32 +/// The V5R1 wallet id differs between the mainnet and testnet. We support V5R1 only on the mainnet. +pub const WALLET_ID_V5R1_TON_MAINNET: i32 = 2147483409; +/// https://docs.ton.org/develop/howto/step-by-step#1-smart-contract-addresses +pub const BASE_WORKCHAIN: i32 = 0; +pub const MASTER_WORKCHAIN: i32 = -1; + +lazy_static! { + pub static ref WALLET_V4R2_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/wallet_v4r2.code"); + BagOfCells::parse_base64(code).expect("Cannot decode wallet_v4r2.code") + }; + pub static ref WALLET_V5R1_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/wallet_v5r1.code"); + BagOfCells::parse_base64(code).expect("Cannot decode wallet_v5r1.code") + }; +} diff --git a/rust/chains/tw_ton/src/signer.rs b/rust/chains/tw_ton/src/signer.rs new file mode 100644 index 00000000000..44306434b73 --- /dev/null +++ b/rust/chains/tw_ton/src/signer.rs @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::compiler::HAS_CRC32; +use crate::signing_request::builder::SigningRequestBuilder; +use crate::signing_request::cell_creator::ExternalMessageCreator; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_misc::traits::ToBytesVec; +use tw_proto::TheOpenNetwork::Proto; +use tw_ton_sdk::boc::BagOfCells; +use tw_ton_sdk::error::cell_to_signing_error; + +pub struct TheOpenNetworkSigner; + +impl TheOpenNetworkSigner { + pub fn sign( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> Proto::SigningOutput<'static> { + Self::sign_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_impl( + _coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let signing_request = SigningRequestBuilder::build(&input)?; + + let external_message = + ExternalMessageCreator::create_external_message_to_sign(&signing_request) + .map_err(cell_to_signing_error)?; + + // Whether to add 'StateInit' reference. + let state_init = signing_request.seqno == 0; + let signed_tx = signing_request + .wallet + .sign_transaction(external_message, state_init) + .context("Error signing/wrapping an external message")? + .build() + .context("Error generating signed message cell") + .map_err(cell_to_signing_error)?; + + let signed_tx_hash = signed_tx.cell_hash(); + let signed_tx_encoded = BagOfCells::from_root(signed_tx) + .to_base64(HAS_CRC32) + .context("Error serializing signed transaction as BoC") + .map_err(cell_to_signing_error)?; + + Ok(Proto::SigningOutput { + encoded: signed_tx_encoded.into(), + hash: signed_tx_hash.to_vec().into(), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/chains/tw_ton/src/signing_request/builder.rs b/rust/chains/tw_ton/src/signing_request/builder.rs new file mode 100644 index 00000000000..5324546deb4 --- /dev/null +++ b/rust/chains/tw_ton/src/signing_request/builder.rs @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::TonAddress; +use crate::signing_request::{ + JettonTransferRequest, SigningRequest, TransferCustomRequest, TransferPayload, TransferRequest, +}; +use crate::wallet::{wallet_v4, wallet_v5, VersionedTonWallet}; +use std::str::FromStr; +use tw_coin_entry::error::prelude::*; +use tw_keypair::ed25519::sha512::{KeyPair, PublicKey}; +use tw_number::U256; +use tw_proto::TheOpenNetwork::Proto; +use tw_ton_sdk::error::cell_to_signing_error; +use Proto::mod_Transfer::OneOfpayload as PayloadType; + +const STATE_INIT_EXPIRE_AT: u32 = 0xffffffff; + +pub struct SigningRequestBuilder; + +impl SigningRequestBuilder { + pub fn build(input: &Proto::SigningInput) -> SigningResult { + let wallet = Self::wallet(input)?; + + let messages = input + .messages + .iter() + .map(Self::transfer_request) + .collect::>>()?; + + let expire_at = if input.sequence_number == 0 { + STATE_INIT_EXPIRE_AT + } else if input.expire_at == 0 { + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("'expire_at' must be set"); + } else { + input.expire_at + }; + + Ok(SigningRequest { + wallet, + messages, + expire_at, + seqno: input.sequence_number, + }) + } + + /// Currently, V4R2 and V5R1 wallets supported. + fn wallet(input: &Proto::SigningInput) -> SigningResult { + if !input.private_key.is_empty() { + let key_pair = KeyPair::try_from(input.private_key.as_ref()) + .into_tw() + .context("Invalid private key")?; + + return match input.wallet_version { + Proto::WalletVersion::WALLET_V4_R2 => Ok(VersionedTonWallet::V4R2( + wallet_v4::WalletV4R2::std_with_key_pair(&key_pair) + .map_err(cell_to_signing_error)?, + )), + Proto::WalletVersion::WALLET_V5_R1 => Ok(VersionedTonWallet::V5R1( + wallet_v5::WalletV5R1::std_with_key_pair(&key_pair) + .map_err(cell_to_signing_error)?, + )), + _ => SigningError::err(SigningErrorType::Error_not_supported) + .context("Wallet version not supported"), + }; + } + + let public_key = PublicKey::try_from(input.public_key.as_ref()) + .into_tw() + .context("Expected either 'private_key' or 'public_key' to be set")?; + + match input.wallet_version { + Proto::WalletVersion::WALLET_V4_R2 => Ok(VersionedTonWallet::V4R2( + wallet_v4::WalletV4R2::std_with_public_key(public_key) + .map_err(cell_to_signing_error)?, + )), + Proto::WalletVersion::WALLET_V5_R1 => Ok(VersionedTonWallet::V5R1( + wallet_v5::WalletV5R1::std_with_public_key(public_key) + .map_err(cell_to_signing_error)?, + )), + _ => SigningError::err(SigningErrorType::Error_not_supported) + .context("Wallet version not supported"), + } + } + + fn transfer_request(input: &Proto::Transfer) -> SigningResult { + let dest = TonAddress::from_str(input.dest.as_ref()) + .into_tw() + .context("Invalid 'dest' address")? + // Set the 'bounceable' flag explicitly as specified in the Protobuf. + .set_bounceable(input.bounceable); + + let comment = if input.comment.is_empty() { + None + } else { + Some(input.comment.to_string()) + }; + + let mode = u8::try_from(input.mode) + .tw_err(|_| SigningErrorType::Error_invalid_params) + .context("'mode' must fit uint8")?; + + let payload = match input.payload { + PayloadType::jetton_transfer(ref jetton) => { + Some(Self::jetton_transfer_request(jetton)?) + }, + PayloadType::custom_payload(ref custom) => Some(Self::custom_request(custom)?), + PayloadType::None => None, + }; + + Ok(TransferRequest { + dest, + ton_amount: U256::from(input.amount), + mode, + comment, + payload, + }) + } + + fn jetton_transfer_request(input: &Proto::JettonTransfer) -> SigningResult { + let dest = TonAddress::from_str(input.to_owner.as_ref()) + .into_tw() + .context("Invalid 'dest' address")?; + + let response_address = TonAddress::from_str(input.response_address.as_ref()) + .into_tw() + .context("Invalid 'response_address' address")?; + + let jetton_payload = JettonTransferRequest { + query_id: input.query_id, + jetton_amount: U256::from(input.jetton_amount), + dest, + response_address, + forward_ton_amount: U256::from(input.forward_amount), + }; + + Ok(TransferPayload::JettonTransfer(jetton_payload)) + } + + fn custom_request(input: &Proto::CustomPayload) -> SigningResult { + let state_init = if input.state_init.is_empty() { + None + } else { + Some(input.state_init.to_string()) + }; + + let payload = if input.payload.is_empty() { + None + } else { + Some(input.payload.to_string()) + }; + + Ok(TransferPayload::Custom(TransferCustomRequest { + state_init, + payload, + })) + } +} diff --git a/rust/chains/tw_ton/src/signing_request/cell_creator.rs b/rust/chains/tw_ton/src/signing_request/cell_creator.rs new file mode 100644 index 00000000000..f2295848d90 --- /dev/null +++ b/rust/chains/tw_ton/src/signing_request/cell_creator.rs @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::message::internal_message::transfer::TransferInternalMessage; +use crate::message::internal_message::InternalMessage; +use crate::message::payload::comment::CommentPayload; +use crate::message::payload::empty::EmptyPayload; +use crate::message::payload::jetton_transfer::JettonTransferPayload; +use crate::signing_request::{ + JettonTransferRequest, SigningRequest, TransferCustomRequest, TransferPayload, TransferRequest, +}; +use std::sync::Arc; +use tw_coin_entry::error::prelude::ResultContext; +use tw_ton_sdk::boc::BagOfCells; +use tw_ton_sdk::cell::{Cell, CellArc}; +use tw_ton_sdk::error::{CellError, CellErrorType, CellResult}; + +pub struct InternalMessageCreator; + +impl InternalMessageCreator { + pub fn create_internal_message( + transfer_request: &TransferRequest, + ) -> CellResult { + let mut transfer_message = TransferInternalMessage::new( + transfer_request.dest.clone(), + transfer_request.ton_amount, + ); + + // Store a custom contract StateInit Cell if it's provided. + if let Some(state_init) = Self::maybe_custom_state_init(transfer_request)? { + transfer_message.with_state_init(state_init); + } + // In WalletCore, we always store the transfer data even if it's an empty Cell. + transfer_message.with_data(Self::transfer_payload(transfer_request)?); + + let transfer_message_cell = transfer_message + .build() + .context("Error generating 'Transfer' internal message cell")?; + + Ok(InternalMessage::new( + transfer_request.mode, + transfer_message_cell, + )) + } + + fn transfer_payload(transfer_request: &TransferRequest) -> CellResult { + match transfer_request.payload { + Some(TransferPayload::JettonTransfer(ref jetton)) => { + Self::jetton_transfer_payload(jetton, transfer_request.comment.clone()) + }, + Some(TransferPayload::Custom(ref custom)) => Self::custom_payload(custom), + // Otherwise, this is an ordinary TON transfer with an optional comment. + None => Self::maybe_comment_payload(transfer_request.comment.clone()), + } + } + + fn maybe_comment_payload(comment: Option) -> CellResult { + match comment { + Some(comment) => CommentPayload::new(comment) + .build() + .context("Error generating Transfer's comment payload"), + None => EmptyPayload + .build() + .context("Error generating Transfer's empty payload"), + } + .map(Cell::into_arc) + } + + fn jetton_transfer_payload( + jetton: &JettonTransferRequest, + comment: Option, + ) -> CellResult { + let mut payload = JettonTransferPayload::new(jetton.dest.clone(), jetton.jetton_amount); + payload + .with_query_id(jetton.query_id) + .with_response_destination(jetton.response_address.clone()) + .with_forward_ton_amount(jetton.forward_ton_amount); + + if let Some(comment) = comment { + payload.with_comment(comment); + } + + payload + .build() + .map(Cell::into_arc) + .context("Error generating Jetton Transfer payload") + } + + fn custom_payload(custom: &TransferCustomRequest) -> CellResult { + match custom.payload { + Some(ref payload) => BagOfCells::parse_base64(payload) + .context("Error parsing custom Transfer payload")? + .single_root() + .map(Arc::clone) + .context("Custom Transfer payload must contain only one single root"), + // Create an empty Cell payload. + None => EmptyPayload + .build() + .map(Cell::into_arc) + .context("Error generating Transfer's empty payload"), + } + } + + fn maybe_custom_state_init(request: &TransferRequest) -> CellResult> { + let Some(TransferPayload::Custom(ref custom)) = request.payload else { + return Ok(None); + }; + + let Some(ref state_init) = custom.state_init else { + return Ok(None); + }; + + let state_init_cell = BagOfCells::parse_base64(state_init) + .context("Error parsing Transfer stateInit")? + .single_root() + .map(Arc::clone) + .context("stateInit must contain only one single root")?; + Ok(Some(state_init_cell)) + } +} + +pub struct ExternalMessageCreator; + +impl ExternalMessageCreator { + pub fn create_external_message_to_sign(request: &SigningRequest) -> CellResult { + if request.messages.is_empty() { + return CellError::err(CellErrorType::CellBuilderError) + .context("There must be at least one Transfer message"); + } + + let internal_messages = request + .messages + .iter() + .map(InternalMessageCreator::create_internal_message) + .collect::>>()?; + + request + .wallet + .create_external_body(request.expire_at, request.seqno, internal_messages) + .context("Error generating an external message cell") + } +} diff --git a/rust/chains/tw_ton/src/signing_request/mod.rs b/rust/chains/tw_ton/src/signing_request/mod.rs new file mode 100644 index 00000000000..d0d0e3b05bf --- /dev/null +++ b/rust/chains/tw_ton/src/signing_request/mod.rs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::TonAddress; +use crate::wallet::VersionedTonWallet; +use tw_number::U256; + +pub mod builder; +pub mod cell_creator; + +pub enum TransferPayload { + /// Jetton Transfer message payload. + JettonTransfer(JettonTransferRequest), + /// Custom Transfer message payload. + Custom(TransferCustomRequest), +} + +pub struct TransferRequest { + /// TON recipient address. + /// Also determines whether the transaction is bounceable or not. + pub dest: TonAddress, + /// Amount to send in nanotons. + pub ton_amount: U256, + /// Send mode. + /// https://ton.org/docs/develop/func/stdlib#send_raw_message + pub mode: u8, + /// Transfer comment message. + pub comment: Option, + /// Transfer payload. + pub payload: Option, +} + +pub struct JettonTransferRequest { + /// Arbitrary request number. + pub query_id: u64, + /// Amount of transferred jettons in elementary integer units. + /// The real value transferred is `jetton_amount` multiplied by ten to the power of token decimal precision. + pub jetton_amount: U256, + /// Address of the new owner of the jettons. + pub dest: TonAddress, + /// Address where to send a response with confirmation of a successful transfer and the rest of the incoming message Toncoins. + /// Usually the sender should get back their toncoins. + pub response_address: TonAddress, + /// Amount in nanotons to forward to recipient. Basically minimum amount - 1 nanoton should be used. + pub forward_ton_amount: U256, +} + +pub struct TransferCustomRequest { + /// (string base64, optional): raw one-cell BoC encoded in Base64. + /// Can be used to deploy a smart contract. + pub state_init: Option, + /// (string base64, optional): raw one-cell BoC encoded in Base64. + pub payload: Option, +} + +pub struct SigningRequest { + /// Wallet initialized with the user's key-pair or public key. + pub wallet: VersionedTonWallet, + pub messages: Vec, + /// External message counter. + /// https://ton.org/docs/develop/smart-contracts/guidelines/external-messages + pub seqno: u32, + /// Expiration UNIX timestamp. + pub expire_at: u32, +} diff --git a/rust/chains/tw_ton/src/transaction.rs b/rust/chains/tw_ton/src/transaction.rs new file mode 100644 index 00000000000..1fdf8e8d925 --- /dev/null +++ b/rust/chains/tw_ton/src/transaction.rs @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::TonAddress; +use tw_number::U256; +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::Cell; +use tw_ton_sdk::error::*; +use tw_ton_sdk::message::state_init::StateInit; + +/// See an example at https://docs.ton.org/develop/smart-contracts/tutorials/wallet#contract-deployment-via-wallet +pub const INCOMING_EXTERNAL_TRANSACTION: u8 = 0b10; + +pub struct SignedTransaction { + pub src_address: TonAddress, + pub dest_address: TonAddress, + pub import_fee: U256, + /// Created via `StateInit`. + pub state_init: Option, + pub signed_body: Cell, +} + +impl SignedTransaction { + pub fn build(&self) -> CellResult { + let mut wrap_builder = CellBuilder::new(); + wrap_builder + .store_u8(2, INCOMING_EXTERNAL_TRANSACTION)? // incoming external transaction + .store_address(&self.src_address)? // src + .store_address(&self.dest_address)? // dest + .store_coins(&self.import_fee)?; // import fee + + if let Some(ref state_init) = self.state_init { + wrap_builder.store_bit(true)?; // state init present + wrap_builder.store_bit(true)?; // state init in ref + wrap_builder.store_child(state_init.to_cell()?)?; // state init + } else { + wrap_builder.store_bit(false)?; // state init absent + } + + wrap_builder.store_bit(true)?; // signed_body is always defined + wrap_builder.store_child(self.signed_body.clone())?; // Signed body + + wrap_builder.build() + } +} diff --git a/rust/chains/tw_ton/src/wallet/mod.rs b/rust/chains/tw_ton/src/wallet/mod.rs new file mode 100644 index 00000000000..8b4a01311b7 --- /dev/null +++ b/rust/chains/tw_ton/src/wallet/mod.rs @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::TonAddress; +use crate::message::internal_message::InternalMessage; +use crate::message::signed_message::signed_message_v4::SignedMessageV4; +use crate::message::signed_message::signed_message_v5::SignedMessageV5; +use crate::transaction::SignedTransaction; +use tw_coin_entry::error::prelude::*; +use tw_keypair::ed25519::Signature; +use tw_keypair::traits::SigningKeyTrait; +use tw_number::U256; +use tw_ton_sdk::cell::Cell; +use tw_ton_sdk::error::{cell_to_signing_error, CellResult}; +use tw_ton_sdk::message::state_init::StateInit; + +pub mod wallet_v4; +pub mod wallet_v5; + +/// Notes: Rust specialization for generic code is not completed. See https://github.com/rust-lang/rust/issues/31844 +/// Currently, we use a workaround to implement the versioned TonWallet struct. +/// After the Rust specialization feature is finished, maybe we can remove this workaround. +pub enum VersionedTonWallet { + V4R2(wallet_v4::WalletV4R2), + V5R1(wallet_v5::WalletV5R1), +} + +impl VersionedTonWallet { + pub fn address(&self) -> &TonAddress { + match self { + Self::V4R2(wallet_v4r2) => &wallet_v4r2.address, + Self::V5R1(wallet_v5r1) => &wallet_v5r1.address, + } + } + + pub fn state_init(&self) -> CellResult { + match self { + Self::V4R2(wallet_v4r2) => wallet_v4r2.state_init(), + Self::V5R1(wallet_v5r1) => wallet_v5r1.state_init(), + } + } + + pub fn create_external_body( + &self, + expire_at: u32, + seqno: u32, + internal_messages: Vec, + ) -> CellResult { + match self { + Self::V4R2(wallet_v4r2) => { + wallet_v4r2.create_external_body(expire_at, seqno, internal_messages) + }, + Self::V5R1(wallet_v5r1) => { + wallet_v5r1.create_external_body(expire_at, seqno, internal_messages) + }, + } + } + + pub fn sign_external_message(&self, external_message: Cell) -> SigningResult { + let message_hash = external_message.cell_hash(); + let sig = match self { + Self::V4R2(wallet_v4r2) => wallet_v4r2.private_key.as_ref(), + Self::V5R1(wallet_v5r1) => wallet_v5r1.private_key.as_ref(), + } + .or_tw_err(SigningErrorType::Error_internal) + .context("'TonWallet' should be initialized with a key-pair to be able to sign a message")? + .sign(message_hash.to_vec())?; + + self.compile_signed_external_message(external_message, sig) + } + + pub fn compile_signed_external_message( + &self, + external_message: Cell, + sig: Signature, + ) -> SigningResult { + match self { + Self::V4R2(_) => Ok(SignedMessageV4 { + signature: sig.to_bytes(), + external_message, + } + .build() + .map_err(cell_to_signing_error)?), + + Self::V5R1(_) => Ok(SignedMessageV5 { + signature: sig.to_bytes(), + external_message, + } + .build() + .map_err(cell_to_signing_error)?), + } + } + + pub fn sign_transaction( + &self, + external_message: Cell, + state_init: bool, + ) -> SigningResult { + let signed_external_message = self.sign_external_message(external_message.clone())?; + self.compile_transaction(signed_external_message, state_init) + } + + pub fn compile_transaction( + &self, + signed_external_message: Cell, + state_init: bool, + ) -> SigningResult { + let state_init = if state_init { + let state_init = self.state_init().map_err(cell_to_signing_error)?; + Some(state_init) + } else { + None + }; + + Ok(SignedTransaction { + src_address: TonAddress::null(), + // The wallet contract address. + dest_address: self.address().clone(), + import_fee: U256::zero(), + state_init, + signed_body: signed_external_message, + }) + } +} diff --git a/rust/chains/tw_ton/src/wallet/wallet_v4.rs b/rust/chains/tw_ton/src/wallet/wallet_v4.rs new file mode 100644 index 00000000000..2ecb279a1e1 --- /dev/null +++ b/rust/chains/tw_ton/src/wallet/wallet_v4.rs @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::TonAddress; +use crate::message::external_message::wallet_v4::ExternalMessageWalletV4; +use crate::message::internal_message::InternalMessage; +use crate::resources::{BASE_WORKCHAIN, DEFAULT_WALLET_ID, WALLET_V4R2_CODE}; +use std::sync::Arc; +use tw_keypair::ed25519::sha512::{KeyPair, PrivateKey, PublicKey}; +use tw_keypair::traits::KeyPairTrait; +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::Cell; +use tw_ton_sdk::error::CellResult; +use tw_ton_sdk::message::state_init::StateInit; + +pub struct WalletV4R2 { + pub public_key: PublicKey, + pub(crate) private_key: Option, + /// TON address derived from the [`TonWallet::public_key`]. + pub address: TonAddress, + pub wallet_id: i32, +} + +impl WalletV4R2 { + /// Creates a standard TON wallet from the given public key. + /// Please note when created with public key only, wallet cannot be used to sign messages. + pub fn std_with_public_key(public_key: PublicKey) -> CellResult { + let wallet_id = DEFAULT_WALLET_ID; + Self::with_public_key(BASE_WORKCHAIN, public_key, wallet_id) + } + + /// Creates a standard TON wallet from the given key-pair. + pub fn std_with_key_pair(key_pair: &KeyPair) -> CellResult { + let wallet_id = DEFAULT_WALLET_ID; + Self::with_key_pair(BASE_WORKCHAIN, key_pair, wallet_id) + } + + /// Creates a TON wallet from the given public key. + /// Please note when created with public key only, wallet cannot be used to sign messages. + pub(crate) fn with_public_key( + workchain: i32, + public_key: PublicKey, + wallet_id: i32, + ) -> CellResult { + Self::new(workchain, public_key, None, wallet_id) + } + + /// Creates a TON wallet from the given key-pair. + fn with_key_pair(workchain: i32, key_pair: &KeyPair, wallet_id: i32) -> CellResult { + let public = key_pair.public().clone(); + let private = key_pair.private().clone(); + Self::new(workchain, public, Some(private), wallet_id) + } + + /// Private function to create the VersionedTonWallet with the given public and optional private keys. + /// Do not make it public as the function caller can provide unrelated keys. + fn new( + workchain: i32, + public_key: PublicKey, + private_key: Option, + wallet_id: i32, + ) -> CellResult { + let state_init_hash = Self::state_init_impl(&public_key, wallet_id)?.create_account_id()?; + let address = TonAddress::new(workchain, state_init_hash); + + Ok(Self { + public_key, + private_key, + address, + wallet_id, + }) + } + + /// Return the stateInit for the wallet. + pub fn state_init(&self) -> CellResult { + Self::state_init_impl(&self.public_key, self.wallet_id) + } + + fn state_init_impl(public_key: &PublicKey, wallet_id: i32) -> CellResult { + let seqno = 0; + + let mut builder = CellBuilder::new(); + builder + .store_u32(32, seqno)? + .store_i32(32, wallet_id)? + .store_slice(public_key.as_slice())? + // empty plugin dict + .store_bit(false)?; + + let initial_data = builder.build()?.into_arc(); + let code = WALLET_V4R2_CODE.single_root().map(Arc::clone)?; + + Ok(StateInit::default().set_code(code).set_data(initial_data)) + } + + pub(crate) fn create_external_body( + &self, + expire_at: u32, + seqno: u32, + internal_messages: Vec, + ) -> CellResult { + ExternalMessageWalletV4 { + wallet_id: self.wallet_id, + expire_at, + seqno, + internal_messages, + } + .build() + } +} diff --git a/rust/chains/tw_ton/src/wallet/wallet_v5.rs b/rust/chains/tw_ton/src/wallet/wallet_v5.rs new file mode 100644 index 00000000000..98a45b6a94e --- /dev/null +++ b/rust/chains/tw_ton/src/wallet/wallet_v5.rs @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::TonAddress; +use crate::message::external_message::wallet_v5::{ExternalMessageWalletV5, V5R1OpCode}; +use crate::message::internal_message::InternalMessage; +use crate::message::out_list::out_action::{OutAction, OutActionType}; +use crate::resources::{BASE_WORKCHAIN, WALLET_ID_V5R1_TON_MAINNET, WALLET_V5R1_CODE}; +use std::sync::Arc; +use tw_keypair::ed25519::sha512::{KeyPair, PrivateKey, PublicKey}; +use tw_keypair::traits::KeyPairTrait; +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::Cell; +use tw_ton_sdk::error::CellResult; +use tw_ton_sdk::message::state_init::StateInit; + +pub struct WalletV5R1 { + pub public_key: PublicKey, + pub(crate) private_key: Option, + /// TON address derived from the [`TonWallet::public_key`]. + pub address: TonAddress, + pub wallet_id: i32, +} + +impl WalletV5R1 { + /// Creates a standard TON wallet from the given public key. + /// Please note when created with public key only, wallet cannot be used to sign messages. + pub fn std_with_public_key(public_key: PublicKey) -> CellResult { + let wallet_id = WALLET_ID_V5R1_TON_MAINNET; + Self::with_public_key(BASE_WORKCHAIN, public_key, wallet_id) + } + + /// Creates a standard TON wallet from the given key-pair. + pub fn std_with_key_pair(key_pair: &KeyPair) -> CellResult { + let wallet_id = WALLET_ID_V5R1_TON_MAINNET; + Self::with_key_pair(BASE_WORKCHAIN, key_pair, wallet_id) + } + + /// Creates a TON wallet from the given public key. + /// Please note when created with public key only, wallet cannot be used to sign messages. + pub(crate) fn with_public_key( + workchain: i32, + public_key: PublicKey, + wallet_id: i32, + ) -> CellResult { + Self::new(workchain, public_key, None, wallet_id) + } + + /// Creates a TON wallet from the given key-pair. + fn with_key_pair(workchain: i32, key_pair: &KeyPair, wallet_id: i32) -> CellResult { + let public = key_pair.public().clone(); + let private = key_pair.private().clone(); + Self::new(workchain, public, Some(private), wallet_id) + } + + /// Private function to create the VersionedTonWallet with the given public and optional private keys. + /// Do not make it public as the function caller can provide unrelated keys. + fn new( + workchain: i32, + public_key: PublicKey, + private_key: Option, + wallet_id: i32, + ) -> CellResult { + let state_init_hash = Self::state_init_impl(&public_key, wallet_id)?.create_account_id()?; + let address = TonAddress::new(workchain, state_init_hash); + + Ok(Self { + public_key, + private_key, + address, + wallet_id, + }) + } + + /// Return the stateInit for the wallet. + pub(crate) fn state_init(&self) -> CellResult { + Self::state_init_impl(&self.public_key, self.wallet_id) + } + + fn state_init_impl(public_key: &PublicKey, wallet_id: i32) -> CellResult { + let seqno = 0; + + let mut builder = CellBuilder::new(); + builder + .store_bit(true)? // signature auth allowed + .store_u32(32, seqno)? + .store_i32(32, wallet_id)? + .store_slice(public_key.as_slice())? + // empty plugin dict + .store_bit(false)?; + + let initial_data = builder.build()?.into_arc(); + let code = WALLET_V5R1_CODE.single_root().map(Arc::clone)?; + + Ok(StateInit::default().set_code(code).set_data(initial_data)) + } + + pub(crate) fn create_external_body( + &self, + expire_at: u32, + seqno: u32, + internal_messages: Vec, + ) -> CellResult { + // Convert internal_messages to basic_actions + let basic_actions: Vec = internal_messages + .into_iter() + .map(|msg| OutAction { + typ: OutActionType::SendMsg, + mode: msg.mode, + data: msg.message, + }) + .collect(); + + ExternalMessageWalletV5 { + opcode: V5R1OpCode::AuthSignedExternal, + wallet_id: self.wallet_id, + expire_at, + seqno, + basic_actions, + } + .build() + } +} diff --git a/rust/chains/tw_ton/tests/address.rs b/rust/chains/tw_ton/tests/address.rs new file mode 100644 index 00000000000..fb99cd0c008 --- /dev/null +++ b/rust/chains/tw_ton/tests/address.rs @@ -0,0 +1,39 @@ +use tw_keypair::ed25519::sha512::KeyPair; +use tw_keypair::traits::KeyPairTrait; +use tw_ton::wallet::wallet_v5; + +/// Tests for TON V5R1 address. +/// +/// Note: These tests should move to rust/tw_any_coin/tests/chains/ton/ton_address.rs after we define +/// a new Derivation for the TON V5R1 address. +#[test] +fn test_ton_v5r1_address_derive() { + let test_cases = [( + "3570e35f54cfb843f2cfaf2b8cae7ceeb7b32225d7dbbd86f611056d74d9073e", // private key + "UQAU3o5-Sp1MYRpw3U7b_wmARxqI49LxiFhEoVCxpUKjTYXk", // V5R1 address on the mainnet + )]; + + for test_case in test_cases.iter() { + let private_key_hex = test_case.0; + let expected_address = test_case.1; + + // Decode the private key from hex + let private_key_bytes = + tw_encoding::hex::decode(private_key_hex).expect("Invalid hex string"); + + // Create a KeyPair from the private key bytes + let key_pair = + KeyPair::try_from(private_key_bytes.as_slice()).expect("Failed to create key pair"); + + // Extract the public key from the KeyPair + let public_key = key_pair.public().clone(); + + // Create the VersionedTonWallet using the public key + let wallet = wallet_v5::WalletV5R1::std_with_public_key(public_key) + .expect("Failed to create wallet"); + + let actual_address = wallet.address.to_string(); + + assert_eq!(actual_address, expected_address); + } +} diff --git a/rust/coverage.stats b/rust/coverage.stats new file mode 100644 index 00000000000..fd6ae261616 --- /dev/null +++ b/rust/coverage.stats @@ -0,0 +1 @@ +94.0 \ No newline at end of file diff --git a/rust/frameworks/tw_ton_sdk/Cargo.toml b/rust/frameworks/tw_ton_sdk/Cargo.toml new file mode 100644 index 00000000000..c8544bef84b --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "tw_ton_sdk" +version = "0.1.0" +edition = "2021" + +[dependencies] +bitreader = "0.3.8" +bitstream-io = "2.5.0" +crc = "3" +lazy_static = "1.4.0" +num-bigint = "0.4" +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_encoding = { path = "../../tw_encoding" } +tw_keypair = { path = "../../tw_keypair" } +tw_hash = { path = "../../tw_hash" } +tw_memory = { path = "../../tw_memory" } +tw_number = { path = "../../tw_number" } + +[dev-dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/rust/frameworks/tw_ton_sdk/fuzz/.gitignore b/rust/frameworks/tw_ton_sdk/fuzz/.gitignore new file mode 100644 index 00000000000..5c404b9583f --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +Cargo.lock diff --git a/rust/frameworks/tw_ton_sdk/fuzz/Cargo.toml b/rust/frameworks/tw_ton_sdk/fuzz/Cargo.toml new file mode 100644 index 00000000000..2536b143a70 --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/fuzz/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "tw_ton_sdk-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.tw_ton_sdk] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "boc_encode" +path = "fuzz_targets/boc_encode.rs" +test = false +doc = false diff --git a/rust/frameworks/tw_ton_sdk/fuzz/fuzz_targets/boc_encode.rs b/rust/frameworks/tw_ton_sdk/fuzz/fuzz_targets/boc_encode.rs new file mode 100644 index 00000000000..fd178943d78 --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/fuzz/fuzz_targets/boc_encode.rs @@ -0,0 +1,8 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_ton_sdk::boc::BagOfCells; + +fuzz_target!(|data: &[u8]| { + let _ = BagOfCells::parse(data); +}); diff --git a/rust/frameworks/tw_ton_sdk/src/address/address_data.rs b/rust/frameworks/tw_ton_sdk/src/address/address_data.rs new file mode 100644 index 00000000000..73784543467 --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/address/address_data.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_hash::H256; + +const WORKCHAIN_MASK: i32 = 0xff; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AddressData { + pub workchain: i32, + pub hash_part: H256, +} + +impl AddressData { + pub const NULL: AddressData = AddressData::null(); + + pub const fn null() -> AddressData { + AddressData { + workchain: 0, + hash_part: H256::new(), + } + } + + pub fn new(workchain: i32, hash_part: H256) -> AddressData { + AddressData { + workchain, + hash_part, + } + } + + pub fn workchain_byte(&self) -> u8 { + (self.workchain & WORKCHAIN_MASK) as u8 + } +} diff --git a/rust/frameworks/tw_ton_sdk/src/address/mod.rs b/rust/frameworks/tw_ton_sdk/src/address/mod.rs new file mode 100644 index 00000000000..0a9b80e7d60 --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/address/mod.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod address_data; +pub mod raw_address; +pub mod user_friendly_address; diff --git a/rust/frameworks/tw_ton_sdk/src/address/raw_address.rs b/rust/frameworks/tw_ton_sdk/src/address/raw_address.rs new file mode 100644 index 00000000000..37c18357c4d --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/address/raw_address.rs @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::address_data::AddressData; +use std::fmt; +use std::str::FromStr; +use tw_coin_entry::error::prelude::AddressError; +use tw_encoding::hex; +use tw_hash::H256; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RawAddress(AddressData); + +impl RawAddress { + #[inline] + pub fn into_data(self) -> AddressData { + self.0 + } +} + +impl AsRef for RawAddress { + #[inline] + fn as_ref(&self) -> &AddressData { + &self.0 + } +} + +impl From for RawAddress { + #[inline] + fn from(value: AddressData) -> Self { + RawAddress(value) + } +} + +impl FromStr for RawAddress { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + let mut it = s.split(':'); + + let workchain = it + .next() + .ok_or(AddressError::MissingPrefix)? + .parse::() + .map_err(|_| AddressError::InvalidInput)?; + + let hash_hex = it.next().ok_or(AddressError::InvalidInput)?; + let decoded_hash_part = hex::decode(hash_hex).map_err(|_| AddressError::FromHexError)?; + + let hash_part = + H256::try_from(decoded_hash_part.as_slice()).map_err(|_| AddressError::InvalidInput)?; + + // Expected only 2 parts of the hex-encoded address. + if it.next().is_some() { + return Err(AddressError::InvalidInput); + } + + Ok(RawAddress(AddressData::new(workchain, hash_part))) + } +} + +impl fmt::Display for RawAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let prefixed = false; + write!( + f, + "{}:{}", + self.0.workchain, + hex::encode(self.0.hash_part, prefixed) + ) + } +} diff --git a/rust/frameworks/tw_ton_sdk/src/address/user_friendly_address.rs b/rust/frameworks/tw_ton_sdk/src/address/user_friendly_address.rs new file mode 100644 index 00000000000..b1723e108d1 --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/address/user_friendly_address.rs @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::address_data::AddressData; +use crate::crc::CRC_16_XMODEM; +use tw_coin_entry::error::prelude::{AddressError, AddressResult}; +use tw_encoding::base64; +use tw_encoding::base64::{NO_PAD, URL_NO_PAD}; +use tw_hash::{H256, H288}; + +const BASE64_ADDRESS_LEN: usize = 48; +const CHECKSUM_MASK: u16 = 0xff; + +const BOUNCEABLE: u8 = 0x11; +const NON_BOUNCEABLE: u8 = 0x51; +const BOUNCEABLE_TESTNET: u8 = 0x91; +const NON_BOUNCEABLE_TESTNET: u8 = 0xD1; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct UserFriendlyAddress { + data: AddressData, + bounceable: bool, + testnet: bool, +} + +impl UserFriendlyAddress { + #[inline] + pub const fn with_flags(data: AddressData, bounceable: bool, testnet: bool) -> Self { + UserFriendlyAddress { + data, + bounceable, + testnet, + } + } + + #[inline] + pub fn into_data(self) -> AddressData { + self.data + } + + #[inline] + pub fn bounceable(&self) -> bool { + self.bounceable + } + + #[inline] + pub fn testnet(&self) -> bool { + self.testnet + } + + #[inline] + pub fn set_bounceable(self, bounceable: bool) -> Self { + UserFriendlyAddress { bounceable, ..self } + } + + #[inline] + pub fn set_testnet(self, testnet: bool) -> Self { + UserFriendlyAddress { testnet, ..self } + } + + /// Parses url-safe base64 representation of an address + /// + /// # Returns + /// the address, non-bounceable flag, non-production flag. + pub fn from_base64_url(s: &str) -> AddressResult { + Self::from_base64_with_config(s, URL_NO_PAD) + } + + /// Parses standard base64 representation of an address + /// + /// # Returns + /// the address, bounceable flag, testnet flag. + pub fn from_base64_std(s: &str) -> AddressResult { + Self::from_base64_with_config(s, NO_PAD) + } + + /// Parses base64 representation of an address with encoding config. + /// + /// # Returns + /// the address, non-bounceable flag, non-production flag. + fn from_base64_with_config(s: &str, config: base64::Config) -> AddressResult { + if s.len() != BASE64_ADDRESS_LEN { + return Err(AddressError::InvalidInput); + } + let bytes = base64::decode(s, config).map_err(|_| AddressError::FromBase64Error)?; + // Address length has been checked already. + let slice = H288::try_from(bytes.as_slice()).map_err(|_| AddressError::Internal)?; + Self::from_base64_bytes(slice) + } + + /// Parses decoded base64 representation of an address + /// + /// # Returns + /// the address, bounceable flag, testnet flag. + fn from_base64_bytes(bytes: H288) -> AddressResult { + let (bounceable, testnet) = match bytes[0] { + BOUNCEABLE => (true, false), + NON_BOUNCEABLE => (false, false), + BOUNCEABLE_TESTNET => (true, true), + NON_BOUNCEABLE_TESTNET => (false, true), + _ => return Err(AddressError::InvalidInput), + }; + + let workchain = bytes[1] as i8 as i32; + + let calc_crc = CRC_16_XMODEM.checksum(&bytes[0..34]); + let addr_crc = ((bytes[34] as u16) << 8) | bytes[35] as u16; + if calc_crc != addr_crc { + return Err(AddressError::InvalidChecksum); + } + + let hash_part = H256::try_from(&bytes[2..34]).expect("Expected exactly 32 bytes"); + let data = AddressData::new(workchain, hash_part); + Ok(UserFriendlyAddress { + data, + bounceable, + testnet, + }) + } + + pub fn to_base64_url(&self) -> String { + self.to_base64_with_config(URL_NO_PAD) + } + + pub fn to_base64_std(&self) -> String { + self.to_base64_with_config(NO_PAD) + } + + fn to_base64_with_config(&self, config: base64::Config) -> String { + let bytes = self.to_base64_bytes(); + base64::encode(bytes.as_slice(), config) + } + + fn to_base64_bytes(&self) -> H288 { + let mut bytes = H288::default(); + let tag: u8 = match (self.bounceable, self.testnet) { + (false, false) => NON_BOUNCEABLE, + (true, false) => BOUNCEABLE, + (false, true) => NON_BOUNCEABLE_TESTNET, + (true, true) => BOUNCEABLE_TESTNET, + }; + bytes[0] = tag; + bytes[1] = self.data.workchain_byte(); + bytes[2..34].clone_from_slice(self.data.hash_part.as_slice()); + let crc = CRC_16_XMODEM.checksum(&bytes[0..34]); + bytes[34] = ((crc >> 8) & CHECKSUM_MASK) as u8; + bytes[35] = (crc & CHECKSUM_MASK) as u8; + + bytes + } +} + +impl AsRef for UserFriendlyAddress { + #[inline] + fn as_ref(&self) -> &AddressData { + &self.data + } +} diff --git a/rust/frameworks/tw_ton_sdk/src/boc/binary_reader.rs b/rust/frameworks/tw_ton_sdk/src/boc/binary_reader.rs new file mode 100644 index 00000000000..5ae28069164 --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/boc/binary_reader.rs @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::error::{CellErrorType, CellResult}; +use bitstream_io::{BigEndian, ByteRead, ByteReader}; +use std::io::Cursor; +use tw_coin_entry::error::prelude::*; +use tw_memory::Data; + +pub struct BinaryReader<'a> { + reader: ByteReader, BigEndian>, +} + +#[allow(dead_code)] +impl<'a> BinaryReader<'a> { + pub fn new(data: &'a [u8]) -> Self { + let cursor = Cursor::new(data); + BinaryReader { + reader: ByteReader::new(cursor), + } + } + + pub fn read_u8(&mut self) -> CellResult { + self.reader + .read::() + .tw_err(|_| CellErrorType::BagOfCellsDeserializationError) + } + + pub fn read_u32(&mut self) -> CellResult { + self.reader + .read::() + .tw_err(|_| CellErrorType::BagOfCellsDeserializationError) + } + + pub fn read_bytes(&mut self, buf: &mut [u8]) -> CellResult<()> { + self.reader + .read_bytes(buf) + .tw_err(|_| CellErrorType::BagOfCellsDeserializationError) + } + + pub fn read_to_vec(&mut self, num_bytes: usize) -> CellResult { + self.reader + .read_to_vec(num_bytes) + .tw_err(|_| CellErrorType::BagOfCellsDeserializationError) + } + + pub fn read_var_size(&mut self, num_bytes: usize) -> CellResult { + let mut bytes = vec![0; num_bytes]; + self.read_bytes(&mut bytes)?; + + let mut result = 0; + for &byte in &bytes { + result <<= 8; + result |= usize::from(byte); + } + Ok(result) + } + + pub fn skip(&mut self, num_bytes: u32) -> CellResult<()> { + self.reader + .skip(num_bytes) + .tw_err(|_| CellErrorType::BagOfCellsDeserializationError) + } +} diff --git a/rust/frameworks/tw_ton_sdk/src/boc/binary_writer.rs b/rust/frameworks/tw_ton_sdk/src/boc/binary_writer.rs new file mode 100644 index 00000000000..688d71f4e18 --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/boc/binary_writer.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::error::{CellErrorType, CellResult}; +use bitstream_io::{BigEndian, BitWrite, BitWriter, Numeric}; +use tw_coin_entry::error::prelude::{MapTWError, OrTWError, ResultContext}; +use tw_memory::Data; + +pub struct BinaryWriter { + writer: BitWriter, +} + +impl BinaryWriter { + pub fn with_capacity(capacity: usize) -> BinaryWriter { + BinaryWriter { + writer: BitWriter::new(Vec::with_capacity(capacity)), + } + } + + pub fn write_bit(&mut self, bit: bool) -> CellResult<&mut Self> { + self.writer + .write_bit(bit) + .tw_err(|_| CellErrorType::BagOfCellsSerializationError)?; + Ok(self) + } + + pub fn write(&mut self, bits: u32, val: V) -> CellResult<&mut Self> + where + V: Numeric, + { + self.writer + .write(bits, val) + .tw_err(|_| CellErrorType::BagOfCellsSerializationError)?; + Ok(self) + } + + pub fn write_bytes(&mut self, bytes: &[u8]) -> CellResult<&mut Self> { + self.writer + .write_bytes(bytes) + .tw_err(|_| CellErrorType::BagOfCellsSerializationError)?; + Ok(self) + } + + /// TODO the function doesn't count `bit_len / 8` count of bytes. + /// Original code: https://github.com/ston-fi/tonlib-rs/blob/b96a5252df583261ed755656292930af46c2039a/src/cell.rs#L507-L526 + pub(crate) fn write_bits(&mut self, data: &[u8], bit_len: usize) -> CellResult<()> { + let data_len = data.len(); + let rest_bits = bit_len % 8; + let full_bytes = rest_bits == 0; + + if full_bytes { + self.write_bytes(data)?; + } else { + self.write_bytes(&data[..data_len - 1])?; + let last_byte = data[data_len - 1]; + let l = last_byte | 1 << (8 - rest_bits - 1); + self.write(8, l)?; + } + + Ok(()) + } + + pub fn bytes_if_aligned(&mut self) -> CellResult<&[u8]> { + self.writer + .writer() + .map(|vec| vec.as_slice()) + .or_tw_err(CellErrorType::BagOfCellsSerializationError) + .context("Stream is not byte-aligned") + } + + /// Pads the stream with 0 bits until it is aligned at a whole byte. + /// Does nothing if the stream is already aligned. + /// Returns the number of trailing zero bits required to align the Cell. + pub fn align(&mut self) -> CellResult { + let mut trailing_zeros = 0; + while !self.writer.byte_aligned() { + self.write_bit(false)?; + trailing_zeros += 1; + } + Ok(trailing_zeros) + } + + pub fn finish(mut self) -> CellResult { + self.bytes_if_aligned().map(|slice| slice.to_vec()) + } +} + +impl Default for BinaryWriter { + fn default() -> Self { + BinaryWriter { + writer: BitWriter::new(Vec::default()), + } + } +} diff --git a/rust/frameworks/tw_ton_sdk/src/boc/boc_to_raw_boc.rs b/rust/frameworks/tw_ton_sdk/src/boc/boc_to_raw_boc.rs new file mode 100644 index 00000000000..c3f4e3d60e0 --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/boc/boc_to_raw_boc.rs @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +//! Original source code: https://github.com/ston-fi/tonlib-rs/blob/b96a5252df583261ed755656292930af46c2039a/src/cell/raw_boc_from_boc.rs + +use crate::boc::raw::{RawBagOfCells, RawCell}; +use crate::boc::BagOfCells; +use crate::cell::{Cell, CellArc}; +use crate::error::{CellErrorType, CellResult}; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::sync::Arc; +use tw_coin_entry::error::prelude::{OrTWError, ResultContext}; +use tw_hash::H256; + +type IndexedCellRef = RefCell; +type CellsByHash = BTreeMap; + +#[derive(Debug, Clone)] +struct IndexedCell { + index: usize, + cell: CellArc, +} + +pub(crate) fn convert_to_raw_boc(boc: &BagOfCells) -> CellResult { + let cells_by_hash = build_and_verify_index(&boc.roots); + + // Sort indexed cells by their index value. + let mut index_slice: Vec<_> = cells_by_hash.values().collect(); + index_slice.sort_unstable_by(|a, b| a.borrow().index.cmp(&b.borrow().index)); + + // Remove gaps in indices. + index_slice + .iter() + .enumerate() + .for_each(|(real_index, indexed_cell)| indexed_cell.borrow_mut().index = real_index); + + let cells_iter = index_slice + .into_iter() + .map(|indexed_cell| indexed_cell.borrow().cell.clone()); + let raw_cells = raw_cells_from_cells(cells_iter, &cells_by_hash)?; + let root_indices = root_indices(&boc.roots, &cells_by_hash)?; + + Ok(RawBagOfCells { + cells: raw_cells, + roots: root_indices, + }) +} + +fn build_and_verify_index(roots: &[CellArc]) -> CellsByHash { + let mut current_cells: Vec<_> = roots.iter().map(Arc::clone).collect(); + let mut new_hash_index = 0; + + // The Bag of Cells serialization process is not deterministic, + // and these uncertainties make it difficult to write test cases. + // Therefore, we use a BTreeMap instead of a HashMap to remove the uncertainty. + let mut cells_by_hash = BTreeMap::new(); + + // Process cells to build the initial index. + while !current_cells.is_empty() { + let mut next_cells = Vec::with_capacity(current_cells.len() * 4); + for cell in current_cells.iter() { + let hash = cell.cell_hash(); + + if cells_by_hash.contains_key(&hash) { + continue; // Skip if already indexed. + } + + cells_by_hash.insert( + hash, + RefCell::new(IndexedCell { + cell: Arc::clone(cell), + index: new_hash_index, + }), + ); + + new_hash_index += 1; + next_cells.extend(cell.references().iter().map(Arc::clone)); // Add referenced cells for the next iteration. + } + + current_cells = next_cells; + } + + // Ensure indices are in the correct order based on cell references. + let mut verify_order = true; + while verify_order { + verify_order = false; + + for index_cell in cells_by_hash.values() { + for reference in index_cell.borrow().cell.references().iter() { + let ref_hash = reference.cell_hash(); + if let Some(id_ref) = cells_by_hash.get(&ref_hash) { + if id_ref.borrow().index < index_cell.borrow().index { + id_ref.borrow_mut().index = new_hash_index; + new_hash_index += 1; + verify_order = true; // Reverify if an index was updated. + } + } + } + } + } + + cells_by_hash +} + +fn root_indices(roots: &[CellArc], cells_dict: &CellsByHash) -> CellResult> { + roots + .iter() + .map(|root_cell| root_cell.cell_hash()) + .map(|root_cell_hash| { + cells_dict + .get(&root_cell_hash) + .map(|index_record| index_record.borrow().index) + .or_tw_err(CellErrorType::BagOfCellsSerializationError) + .with_context(|| { + format!( + "Couldn't find cell with hash {root_cell_hash} while searching for roots" + ) + }) + }) + .collect() +} + +fn raw_cells_from_cells( + cells: impl Iterator, + cells_by_hash: &CellsByHash, +) -> CellResult> { + cells + .map(|cell| raw_cell_from_cell(&cell, cells_by_hash)) + .collect() +} + +fn raw_cell_from_cell(cell: &Cell, cells_by_hash: &CellsByHash) -> CellResult { + raw_cell_reference_indices(cell, cells_by_hash).map(|reference_indices| { + RawCell::new( + cell.data().to_vec(), + cell.bit_len(), + reference_indices, + cell.get_level_mask(), + cell.is_exotic(), + ) + }) +} + +fn raw_cell_reference_indices(cell: &Cell, cells_by_hash: &CellsByHash) -> CellResult> { + cell.references() + .iter() + .map(|cell| { + cells_by_hash + .get(&cell.cell_hash()) + .or_tw_err(CellErrorType::BagOfCellsSerializationError) + .with_context(|| { + format!( + "Couldn't find cell with hash {:?} while searching for references", + cell.cell_hash() + ) + }) + .map(|cell| cell.borrow().index) + }) + .collect() +} diff --git a/rust/frameworks/tw_ton_sdk/src/boc/mod.rs b/rust/frameworks/tw_ton_sdk/src/boc/mod.rs new file mode 100644 index 00000000000..b0d4a9f2be1 --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/boc/mod.rs @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +//! Original source code: https://github.com/ston-fi/tonlib-rs/blob/b96a5252df583261ed755656292930af46c2039a/src/cell/bag_of_cells.rs + +use crate::boc::raw::RawBagOfCells; +use crate::cell::{Cell, CellArc}; +use crate::error::{CellError, CellErrorType, CellResult}; +use std::sync::Arc; +use tw_coin_entry::error::prelude::*; +use tw_encoding::base64::{self, STANDARD}; +use tw_memory::Data; + +pub mod binary_reader; +pub mod binary_writer; +pub mod boc_to_raw_boc; +pub mod raw; + +use boc_to_raw_boc::convert_to_raw_boc; + +#[derive(PartialEq, Eq, Debug, Clone, Hash)] +pub struct BagOfCells { + pub roots: Vec, +} + +impl BagOfCells { + pub fn from_root(root: Cell) -> BagOfCells { + BagOfCells { + roots: vec![root.into_arc()], + } + } + + pub fn single_root(&self) -> CellResult<&CellArc> { + let root_count = self.roots.len(); + if root_count == 1 { + Ok(&self.roots[0]) + } else { + CellError::err(CellErrorType::CellParserError) + .context(format!("Single root expected, got {root_count}")) + } + } + + pub fn parse(serial: &[u8]) -> CellResult { + let raw = RawBagOfCells::parse(serial)?; + let num_cells = raw.cells.len(); + let mut cells: Vec = Vec::with_capacity(num_cells); + + for (cell_index, raw_cell) in raw.cells.into_iter().enumerate().rev() { + let mut references = Vec::with_capacity(raw_cell.references.len()); + for ref_index in &raw_cell.references { + if *ref_index <= cell_index { + return CellError::err(CellErrorType::BagOfCellsDeserializationError) + .context("References to previous cells are not supported"); + } + let cell_ref_idx = (num_cells - 1) + .checked_sub(*ref_index) + .or_tw_err(CellErrorType::BagOfCellsDeserializationError) + .context("Cell references to an out-of-bound cell")?; + references.push(cells[cell_ref_idx].clone()); + } + + let cell = Cell::new( + raw_cell.data, + raw_cell.bit_len, + references, + raw_cell.is_exotic, + ) + .tw_err(|_| CellErrorType::BagOfCellsDeserializationError)?; + cells.push(cell.into_arc()); + } + + if num_cells < raw.roots.len() { + return CellError::err(CellErrorType::BagOfCellsDeserializationError) + .context("BagOfCells contains more roots than cells"); + } + let roots = raw + .roots + .into_iter() + .map(|r| { + let cell_idx = (num_cells - 1) + .checked_sub(r) + .or_tw_err(CellErrorType::BagOfCellsDeserializationError) + .context("Root index doesn't correspond to a Cell")?; + Ok(Arc::clone(&cells[cell_idx])) + }) + .collect::>>()?; + + Ok(BagOfCells { roots }) + } + + pub fn parse_base64(base64: &str) -> CellResult { + let bin = base64::decode(base64, STANDARD) + .tw_err(|_| CellErrorType::BagOfCellsDeserializationError) + .context("Expected base64 encoded BagOfCells")?; + Self::parse(&bin) + } + + pub fn serialize(&self, has_crc32: bool) -> CellResult { + let raw = convert_to_raw_boc(self)?; + raw.serialize(has_crc32) + } + + pub fn to_base64(&self, has_crc32: bool) -> CellResult { + let encoded = self.serialize(has_crc32)?; + Ok(base64::encode(&encoded, STANDARD)) + } +} diff --git a/rust/frameworks/tw_ton_sdk/src/boc/raw.rs b/rust/frameworks/tw_ton_sdk/src/boc/raw.rs new file mode 100644 index 00000000000..3b02a02d415 --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/boc/raw.rs @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +//! Original source code: https://github.com/ston-fi/tonlib-rs/blob/b96a5252df583261ed755656292930af46c2039a/src/cell/raw.rs + +use crate::boc::binary_reader::BinaryReader; +use crate::boc::binary_writer::BinaryWriter; +use crate::cell::level_mask::LevelMask; +use crate::crc::CRC_32_ISCSI; +use crate::error::{CellError, CellErrorType, CellResult}; +use tw_coin_entry::error::prelude::*; +use tw_memory::Data; + +const GENERIC_BOC_MAGIC: u32 = 0xb5ee9c72; +/// The max number of cells in a BoC. +const MAX_CELLS: usize = 4096; + +/// Raw representation of Cell. +/// +/// References are stored as indices in BagOfCells. +#[derive(PartialEq, Eq, Debug, Clone, Hash)] +pub(crate) struct RawCell { + pub(crate) data: Data, + pub(crate) bit_len: usize, + pub(crate) references: Vec, + pub(crate) is_exotic: bool, + level_mask: u32, +} + +impl RawCell { + pub(crate) fn new( + data: Vec, + bit_len: usize, + references: Vec, + level_mask: u32, + is_exotic: bool, + ) -> Self { + Self { + data, + bit_len, + references, + level_mask: level_mask & 7, + is_exotic, + } + } +} + +/// Raw representation of BagOfCells. +/// +/// `cells` must be topologically sorted. +#[derive(PartialEq, Eq, Debug, Clone, Hash)] +pub(crate) struct RawBagOfCells { + pub(crate) cells: Vec, + pub(crate) roots: Vec, +} + +impl RawBagOfCells { + pub(crate) fn parse(serial: &[u8]) -> CellResult { + let mut reader = BinaryReader::new(serial); + + // serialized_boc#b5ee9c72 + let magic = reader.read_u32()?; + + let (has_idx, has_crc32c, _has_cache_bits, size) = match magic { + GENERIC_BOC_MAGIC => { + // has_idx:(## 1) has_crc32c:(## 1) has_cache_bits:(## 1) flags:(## 2) { flags = 0 } + let header = reader.read_u8()?; + let has_idx = (header >> 7) & 1 == 1; + let has_crc32c = (header >> 6) & 1 == 1; + let has_cache_bits = (header >> 5) & 1 == 1; + // size:(## 3) { size <= 4 } + let size = header & 0b0000_0111; + + (has_idx, has_crc32c, has_cache_bits, size) + }, + magic => { + return CellError::err(CellErrorType::BagOfCellsDeserializationError) + .context(format!("Unsupported cell magic number: {:#}", magic)); + }, + }; + // off_bytes:(## 8) { off_bytes <= 8 } + let off_bytes = reader.read_u8()?; + //cells:(##(size * 8)) + let cells = reader.read_var_size(size as usize)?; + if cells > MAX_CELLS { + return CellError::err(CellErrorType::BagOfCellsDeserializationError).context(format!( + "Max number of cells is '{MAX_CELLS}', but given '{cells}' Cells" + )); + } + + // roots:(##(size * 8)) { roots >= 1 } + let roots = reader.read_var_size(size as usize)?; + if roots > MAX_CELLS { + return CellError::err(CellErrorType::BagOfCellsDeserializationError).context(format!( + "Max number of cells is '{MAX_CELLS}', but given '{roots}' root Cells" + )); + } + + // absent:(##(size * 8)) { roots + absent <= cells } + let _absent = reader.read_var_size(size as usize)?; + // tot_cells_size:(##(off_bytes * 8)) + let _tot_cells_size = reader.read_var_size(off_bytes as usize)?; + // root_list:(roots * ##(size * 8)) + let mut root_list = vec![]; + for _ in 0..roots { + root_list.push(reader.read_var_size(size as usize)?) + } + // index:has_idx?(cells * ##(off_bytes * 8)) + let mut index = vec![]; + if has_idx { + for _ in 0..cells { + index.push(reader.read_var_size(off_bytes as usize)?) + } + } + // cell_data:(tot_cells_size * [ uint8 ]) + let mut cell_vec = Vec::with_capacity(cells); + + for _ in 0..cells { + let cell = read_cell(&mut reader, size)?; + cell_vec.push(cell); + } + // crc32c:has_crc32c?uint32 + let _crc32c = if has_crc32c { reader.read_u32()? } else { 0 }; + // TODO: Check crc32 + + Ok(RawBagOfCells { + cells: cell_vec, + roots: root_list, + }) + } + + pub(crate) fn serialize(&self, has_crc32: bool) -> CellResult { + // Based on https://github.com/toncenter/tonweb/blob/c2d5d0fc23d2aec55a0412940ce6e580344a288c/src/boc/Cell.js#L198 + + let root_count = self.roots.len(); + let num_ref_bits = 32 - (self.cells.len() as u32).leading_zeros(); + let num_ref_bytes = (num_ref_bits + 7) / 8; + let has_idx = false; + + let mut full_size = 0u32; + + for cell in &self.cells { + full_size += raw_cell_size(cell, num_ref_bytes); + } + + let num_offset_bits = 32 - full_size.leading_zeros(); + let num_offset_bytes = (num_offset_bits + 7) / 8; + + let total_size = 4 + // magic + 1 + // flags and s_bytes + 1 + // offset_bytes + 3 * num_ref_bytes + // cells_num, roots, complete + num_offset_bytes + // full_size + num_ref_bytes + // root_idx + (if has_idx { self.cells.len() as u32 * num_offset_bytes } else { 0 }) + + full_size + + (if has_crc32 { 4 } else { 0 }); + + let mut writer = BinaryWriter::with_capacity(total_size as usize); + + writer.write(32, GENERIC_BOC_MAGIC)?; + + //write flags byte + let has_cache_bits = false; + let flags: u8 = 0; + writer.write_bit(has_idx)?; + writer.write_bit(has_crc32)?; + writer.write_bit(has_cache_bits)?; + writer.write(2, flags)?; + writer.write(3, num_ref_bytes)?; + writer.write(8, num_offset_bytes)?; + writer.write(8 * num_ref_bytes, self.cells.len() as u32)?; + writer.write(8 * num_ref_bytes, root_count as u32)?; + writer.write(8 * num_ref_bytes, 0)?; // Complete BOCs only + writer.write(8 * num_offset_bytes, full_size)?; + for &root in &self.roots { + writer.write(8 * num_ref_bytes, root as u32)?; + } + + for cell in &self.cells { + write_raw_cell(&mut writer, cell, num_ref_bytes)?; + } + + if has_crc32 { + let bytes = writer.bytes_if_aligned()?; + let cs = CRC_32_ISCSI.checksum(bytes); + writer.write_bytes(cs.to_le_bytes().as_slice())?; + } + writer.align()?; + writer.finish() + } +} + +fn read_cell(reader: &mut BinaryReader, size: u8) -> CellResult { + let d1 = reader.read_u8()?; + let d2 = reader.read_u8()?; + + let ref_num = d1 & 0b111; + let is_exotic = (d1 & 0b1000) != 0; + let has_hashes = (d1 & 0b10000) != 0; + let level_mask = (d1 >> 5) as u32; + let data_size = ((d2 >> 1) + (d2 & 1)).into(); + let full_bytes = (d2 & 0x01) == 0; + + if has_hashes { + let hash_count = LevelMask::new(level_mask).hash_count(); + let skip_size = hash_count * (32 + 2); + + // TODO: check depth and hashes + reader.skip(skip_size as u32)?; + } + + let mut data = reader.read_to_vec(data_size)?; + + let data_len = data.len(); + let padding_len = if data_len > 0 && !full_bytes { + // Fix last byte, + // see https://github.com/toncenter/tonweb/blob/c2d5d0fc23d2aec55a0412940ce6e580344a288c/src/boc/BitString.js#L302 + let num_zeros = data[data_len - 1].trailing_zeros(); + if num_zeros >= 8 { + return CellError::err(CellErrorType::BagOfCellsDeserializationError) + .context("Last byte of binary must not be zero if full_byte flag is not set"); + } + data[data_len - 1] &= !(1 << num_zeros); + num_zeros + 1 + } else { + 0 + }; + let bit_len = data.len() * 8 - padding_len as usize; + let mut references: Vec = Vec::new(); + for _ in 0..ref_num { + references.push(reader.read_var_size(size as usize)?); + } + let cell = RawCell::new(data, bit_len, references, level_mask, is_exotic); + Ok(cell) +} + +fn raw_cell_size(cell: &RawCell, ref_size_bytes: u32) -> u32 { + let data_len = (cell.bit_len + 7) / 8; + 2 + data_len as u32 + cell.references.len() as u32 * ref_size_bytes +} + +fn write_raw_cell( + writer: &mut BinaryWriter, + cell: &RawCell, + ref_size_bytes: u32, +) -> CellResult<()> { + let level = cell.level_mask; + let is_exotic = cell.is_exotic as u32; + let num_refs = cell.references.len() as u32; + let d1 = num_refs + is_exotic * 8 + level * 32; + + let padding_bits = cell.bit_len % 8; + let full_bytes = padding_bits == 0; + let data = cell.data.as_slice(); + let data_len_bytes = (cell.bit_len + 7) / 8; + // data_len_bytes <= 128 by spec, but d2 must be u8 by spec as well + let d2 = (data_len_bytes * 2 - if full_bytes { 0 } else { 1 }) as u8; //subtract 1 if the last byte is not full + + writer.write(8, d1)?; + writer.write(8, d2)?; + if !full_bytes { + writer.write_bytes(&data[..data_len_bytes - 1])?; + let last_byte = data[data_len_bytes - 1]; + let l = last_byte | 1 << (8 - padding_bits - 1); + writer.write(8, l)?; + } else { + writer.write_bytes(data)?; + } + + for r in cell.references.as_slice() { + writer.write(8 * ref_size_bytes, *r as u32)?; + } + + Ok(()) +} diff --git a/rust/frameworks/tw_ton_sdk/src/cell/cell_builder.rs b/rust/frameworks/tw_ton_sdk/src/cell/cell_builder.rs new file mode 100644 index 00000000000..99d394ca169 --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/cell/cell_builder.rs @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +//! Original source code: https://github.com/ston-fi/tonlib-rs/blob/b96a5252df583261ed755656292930af46c2039a/src/cell/builder.rs + +use crate::address::address_data::AddressData; +use crate::boc::binary_writer::BinaryWriter; +use crate::cell::cell_parser::CellParser; +use crate::cell::{Cell, CellArc}; +use crate::error::{CellError, CellErrorType, CellResult}; +use bitstream_io::Numeric; +use std::sync::Arc; +use tw_coin_entry::error::prelude::{MapTWError, ResultContext}; +use tw_number::U256; + +const MAX_CELL_BITS: usize = 1023; +const MAX_CELL_REFERENCES: usize = 4; + +#[derive(Default)] +pub struct CellBuilder { + bit_writer: BinaryWriter, + references: Vec, + is_cell_exotic: bool, +} + +impl CellBuilder { + pub fn new() -> CellBuilder { + CellBuilder::default() + } + + pub fn store_bit(&mut self, val: bool) -> CellResult<&mut Self> { + self.bit_writer.write_bit(val)?; + Ok(self) + } + + pub fn store_byte(&mut self, val: u8) -> CellResult<&mut Self> { + self.store_u8(8, val) + } + + pub fn store_u8(&mut self, bit_len: usize, val: u8) -> CellResult<&mut Self> { + self.store_numeric(bit_len, val) + } + + pub fn store_u32(&mut self, bit_len: usize, val: u32) -> CellResult<&mut Self> { + self.store_numeric(bit_len, val) + } + + pub fn store_i32(&mut self, bit_len: usize, val: i32) -> CellResult<&mut Self> { + self.store_numeric(bit_len, val) + } + + pub fn store_u64(&mut self, bit_len: usize, val: u64) -> CellResult<&mut Self> { + self.store_numeric(bit_len, val) + } + + pub fn store_uint(&mut self, bit_len: usize, val: &U256) -> CellResult<&mut Self> { + if val.bits() > bit_len { + return CellError::err(CellErrorType::CellBuilderError).context(format!( + "Value {val} doesn't fit in {bit_len} bits (takes {} bits)", + val.bits() + )); + } + // example: bit_len=13, val=5. 5 = 00000101, we must store 0000000000101 + // leading_zeros_bits = 10 + // leading_zeros_bytes = 10 / 8 = 1 + let leading_zero_bits = bit_len - val.bits(); + let leading_zeros_bytes = leading_zero_bits / 8; + for _ in 0..leading_zeros_bytes { + self.store_byte(0)?; + } + // we must align high byte of val to specified bit_len, 00101 in our case + let extra_zeros = leading_zero_bits % 8; + for _ in 0..extra_zeros { + self.store_bit(false)?; + } + // and then store val's high byte in minimum number of bits + let val_bytes = val.to_big_endian_compact(); + let high_bits_cnt = { + let cnt = val.bits() % 8; + if cnt == 0 { + 8 + } else { + cnt + } + }; + let high_byte = val_bytes[0]; + for i in 0..high_bits_cnt { + self.store_bit(high_byte & (1 << (high_bits_cnt - i - 1)) != 0)?; + } + // store the rest of val + for byte in val_bytes.iter().skip(1) { + self.store_byte(*byte)?; + } + Ok(self) + } + + pub fn store_slice(&mut self, slice: &[u8]) -> CellResult<&mut Self> { + for val in slice { + self.store_byte(*val)?; + } + Ok(self) + } + + pub fn store_string(&mut self, val: &str) -> CellResult<&mut Self> { + self.store_slice(val.as_bytes()) + } + + pub fn store_coins(&mut self, val: &U256) -> CellResult<&mut Self> { + if val.is_zero() { + self.store_u8(4, 0) + } else { + let num_bytes = (val.bits() + 7) / 8; + self.store_u8(4, num_bytes as u8)?; + self.store_uint(num_bytes * 8, val) + } + } + + /// Stores address without optimizing hole address. + pub fn store_raw_address(&mut self, val: A) -> CellResult<&mut Self> + where + A: AsRef, + { + let val = val.as_ref(); + + self.store_u8(2, 0b10_u8)?; + self.store_bit(false)?; + self.store_u8(8, val.workchain_byte())?; + self.store_slice(val.hash_part.as_slice())?; + Ok(self) + } + + /// Stores address optimizing hole address two to bits + pub fn store_address(&mut self, val: A) -> CellResult<&mut Self> + where + A: AsRef, + { + if val.as_ref() == &AddressData::NULL { + self.store_u8(2, 0)?; + } else { + self.store_raw_address(val)?; + } + Ok(self) + } + + /// Adds reference to an existing `Cell`. + /// + /// The reference is passed as `ArcCell` so it might be references from other cells. + pub fn store_reference(&mut self, cell: &CellArc) -> CellResult<&mut Self> { + let ref_count = self.references.len() + 1; + if ref_count > MAX_CELL_REFERENCES { + return CellError::err(CellErrorType::CellBuilderError).context(format!( + "Cell must contain at most 4 references, got {ref_count}" + )); + } + self.references.push(Arc::clone(cell)); + Ok(self) + } + + pub fn store_references(&mut self, refs: &[CellArc]) -> CellResult<&mut Self> { + for r in refs { + self.store_reference(r)?; + } + Ok(self) + } + + /// Adds a reference to a newly constructed `Cell`. + /// + /// The cell is wrapped it the `Arc`. + pub fn store_child(&mut self, cell: Cell) -> CellResult<&mut Self> { + self.store_reference(&cell.into_arc()) + } + + pub fn store_remaining_bits(&mut self, parser: &mut CellParser) -> CellResult<&mut Self> { + let num_full_bytes = parser.remaining_bits() / 8; + let bytes = parser.load_bytes(num_full_bytes)?; + self.store_slice(bytes.as_slice())?; + let num_bits = parser.remaining_bits() % 8; + let tail = parser.load_u8(num_bits)?; + self.store_u8(num_bits, tail)?; + Ok(self) + } + + pub fn store_cell_data(&mut self, cell: &Cell) -> CellResult<&mut Self> { + let mut parser = cell.parser(); + self.store_remaining_bits(&mut parser)?; + Ok(self) + } + + pub fn store_cell(&mut self, cell: &Cell) -> CellResult<&mut Self> { + self.store_cell_data(cell)?; + self.store_references(cell.references.as_slice())?; + Ok(self) + } + + pub fn build(mut self) -> CellResult { + let trailing_zeros = self.bit_writer.align()?; + + let vec = self + .bit_writer + .finish() + .tw_err(|_| CellErrorType::InternalError) + .context("Stream must be byte-aligned already")?; + + let bit_len = vec.len() * 8 - trailing_zeros; + if bit_len > MAX_CELL_BITS { + return CellError::err(CellErrorType::CellBuilderError).context(format!( + "Cell must contain at most {MAX_CELL_BITS} bits, got {bit_len}", + )); + } + let ref_count = self.references.len(); + if ref_count > MAX_CELL_REFERENCES { + return CellError::err(CellErrorType::CellBuilderError).context(format!( + "Cell must contain at most 4 references, got {ref_count}", + )); + } + + Cell::new(vec, bit_len, self.references.clone(), self.is_cell_exotic) + } + + fn store_numeric(&mut self, bit_len: usize, val: V) -> CellResult<&mut Self> { + self.bit_writer.write(bit_len as u32, val)?; + Ok(self) + } +} diff --git a/rust/frameworks/tw_ton_sdk/src/cell/cell_parser.rs b/rust/frameworks/tw_ton_sdk/src/cell/cell_parser.rs new file mode 100644 index 00000000000..e7143ded0a3 --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/cell/cell_parser.rs @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +//! Original source code: https://github.com/ston-fi/tonlib-rs/blob/b96a5252df583261ed755656292930af46c2039a/src/cell/parser.rs + +use crate::address::address_data::AddressData; +use crate::error::{CellError, CellErrorType, CellResult}; +use bitreader::BitReader; +use num_bigint::BigUint; +use tw_coin_entry::error::prelude::{MapTWError, ResultContext}; +use tw_hash::H256; +use tw_memory::Data; +use tw_number::U256; + +pub struct CellParser<'a> { + bit_reader: BitReader<'a>, +} + +impl<'a> CellParser<'a> { + pub fn new(data: &'a [u8], bit_len: usize) -> Self { + CellParser { + bit_reader: BitReader::new(data).relative_reader_atmost(bit_len as u64), + } + } + + pub fn remaining_bits(&self) -> usize { + self.bit_reader.remaining() as usize + } + + pub fn load_bit(&mut self) -> CellResult { + self.bit_reader + .read_bool() + .tw_err(|_| CellErrorType::CellParserError) + } + + pub fn load_u8(&mut self, bit_len: usize) -> CellResult { + self.bit_reader + .read_u8(bit_len as u8) + .tw_err(|_| CellErrorType::CellParserError) + } + + pub fn load_u32(&mut self, bit_len: usize) -> CellResult { + self.bit_reader + .read_u32(bit_len as u8) + .tw_err(|_| CellErrorType::CellParserError) + } + + pub fn load_u64(&mut self, bit_len: usize) -> CellResult { + self.bit_reader + .read_u64(bit_len as u8) + .tw_err(|_| CellErrorType::CellParserError) + } + + pub fn load_uint(&mut self, bit_len: usize) -> CellResult { + let num_words = (bit_len + 31) / 32; + let high_word_bits = if bit_len % 32 == 0 { 32 } else { bit_len % 32 }; + let mut words: Vec = vec![0; num_words]; + let high_word = self.load_u32(high_word_bits)?; + words[num_words - 1] = high_word; + for i in (0..num_words - 1).rev() { + let word = self.load_u32(32)?; + words[i] = word; + } + let big_uint = BigUint::new(words); + let uint = U256::from_big_endian_slice(&big_uint.to_bytes_be()) + .tw_err(|_| CellErrorType::CellParserError) + .context("Expected up to 32 bytes of uint")?; + Ok(uint) + } + + pub fn load_slice(&mut self, slice: &mut [u8]) -> CellResult<()> { + self.bit_reader + .read_u8_slice(slice) + .tw_err(|_| CellErrorType::CellParserError) + } + + pub fn load_bytes(&mut self, num_bytes: usize) -> CellResult { + let mut res = vec![0; num_bytes]; + self.load_slice(res.as_mut_slice())?; + Ok(res) + } + + pub fn load_string(&mut self, num_bytes: usize) -> CellResult { + let bytes = self.load_bytes(num_bytes)?; + String::from_utf8(bytes).tw_err(|_| CellErrorType::CellParserError) + } + + pub fn load_coins(&mut self) -> CellResult { + let num_bytes = self.load_u8(4)?; + if num_bytes == 0 { + Ok(U256::zero()) + } else { + self.load_uint((num_bytes * 8) as usize) + } + } + + pub fn load_address(&mut self) -> CellResult { + let tp = self.load_u8(2)?; + match tp { + 0 => Ok(AddressData::null()), + 2 => { + let _res1 = self.load_u8(1)?; + let wc = self.load_u8(8)?; + let mut hash_part = H256::default(); + self.load_slice(hash_part.as_mut_slice())?; + Ok(AddressData::new(wc as i32, hash_part)) + }, + _ => CellError::err(CellErrorType::CellParserError) + .context(format!("Invalid address type: {tp}")), + } + } + + pub fn ensure_empty(&self) -> CellResult<()> { + let remaining = self.remaining_bits(); + if remaining == 0 { + Ok(()) + } else { + CellError::err(CellErrorType::CellParserError) + .context(format!("{remaining} unread bits left")) + } + } +} diff --git a/rust/frameworks/tw_ton_sdk/src/cell/cell_type.rs b/rust/frameworks/tw_ton_sdk/src/cell/cell_type.rs new file mode 100644 index 00000000000..3effee51a1d --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/cell/cell_type.rs @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +//! Original source code: https://github.com/ston-fi/tonlib-rs/blob/b96a5252df583261ed755656292930af46c2039a/src/cell/cell_type.rs + +use crate::cell::level_mask::LevelMask; +use crate::cell::{Cell, CellArc}; +use crate::error::{CellError, CellErrorType, CellResult}; +use bitstream_io::{BigEndian, ByteRead, ByteReader}; +use std::io::Cursor; +use tw_coin_entry::error::prelude::*; +use tw_hash::H256; + +struct Pruned { + hash: H256, + depth: u16, +} + +pub(crate) struct HashesAndDepths { + pub hashes: [H256; 4], + pub depths: [u16; 4], +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) enum CellType { + Ordinary, + PrunedBranch, + Library, + MerkleProof, + MerkleUpdate, +} + +impl CellType { + pub(crate) fn determine_exotic_cell_type(data: &[u8]) -> CellResult { + let Some(type_byte) = data.first() else { + return CellError::err(CellErrorType::InvalidExoticCell) + .context("Not enough data to determine exotic cell type"); + }; + + let cell_type = match type_byte { + 1 => CellType::PrunedBranch, + 2 => CellType::Library, + 3 => CellType::MerkleProof, + 4 => CellType::MerkleUpdate, + cell_type => { + return CellError::err(CellErrorType::InvalidExoticCell).context(format!( + "Invalid first byte in exotic cell data: {cell_type}" + )); + }, + }; + Ok(cell_type) + } + + pub(crate) fn validate( + &self, + _data: &[u8], + _bit_len: usize, + _references: impl AsRef<[CellArc]>, + ) -> CellResult<()> { + // TODO consider implementing data validation according to the cell type. + // match self { + // CellType::Ordinary => Ok(()), + // CellType::PrunedBranch => self.validate_exotic_pruned(data, bit_len, references), + // CellType::Library => self.validate_library(bit_len), + // CellType::MerkleProof => self.validate_merkle_proof(data, bit_len, references), + // CellType::MerkleUpdate => self.validate_merkle_update(data, bit_len, references), + // } + Ok(()) + } + + pub(crate) fn level_mask( + &self, + cell_data: &[u8], + cell_data_bit_len: usize, + references: &[CellArc], + ) -> CellResult { + let ensure_ref_at_least = |at_least_count: usize| { + if references.len() < at_least_count { + return CellError::err(CellErrorType::CellParserError) + .context("Invalid number of Cell references to get level_mask"); + } + Ok(()) + }; + + let result = match self { + CellType::Ordinary => references + .iter() + .fold(LevelMask::new(0), |level_mask, reference| { + level_mask.apply_or(reference.level_mask) + }), + CellType::PrunedBranch => self.pruned_level_mask(cell_data, cell_data_bit_len)?, + CellType::Library => LevelMask::new(0), + CellType::MerkleProof => { + ensure_ref_at_least(1)?; + references[0].level_mask.shift_right() + }, + CellType::MerkleUpdate => { + ensure_ref_at_least(2)?; + references[0] + .level_mask + .apply_or(references[1].level_mask) + .shift_right() + }, + }; + + Ok(result) + } + + pub(crate) fn child_depth(&self, child: &Cell, level: u8) -> u16 { + if matches!(self, CellType::MerkleProof | CellType::MerkleUpdate) { + child.get_depth(level + 1) + } else { + child.get_depth(level) + } + } + + pub(crate) fn resolve_hashes_and_depths( + &self, + hashes: Vec, + depths: Vec, + data: &[u8], + bit_len: usize, + level_mask: LevelMask, + ) -> CellResult { + let mut resolved_hashes = [H256::default(); 4]; + let mut resolved_depths = [0; 4]; + + for i in 0..4 { + let hash_index = level_mask.apply(i).hash_index(); + + let (hash, depth) = if self == &CellType::PrunedBranch { + let this_hash_index = level_mask.hash_index(); + if hash_index != this_hash_index { + let pruned = self.pruned(data, bit_len, level_mask)?; + (pruned[hash_index].hash, pruned[hash_index].depth) + } else { + (hashes[0], depths[0]) + } + } else { + (hashes[hash_index], depths[hash_index]) + }; + + resolved_hashes[i as usize] = hash; + resolved_depths[i as usize] = depth; + } + + Ok(HashesAndDepths { + hashes: resolved_hashes, + depths: resolved_depths, + }) + } + + fn pruned_level_mask(&self, data: &[u8], bit_len: usize) -> CellResult { + if data.len() < 5 { + return CellError::err(CellErrorType::InvalidExoticCell).context(format!( + "Pruned Branch cell date can't be shorter than 5 bytes, got {}", + data.len() + )); + } + + let level_mask = if self.is_config_proof(bit_len) { + LevelMask::new(1) + } else { + let mask_byte = data[1]; + LevelMask::new(mask_byte as u32) + }; + + Ok(level_mask) + } + + fn pruned( + &self, + data: &[u8], + bit_len: usize, + level_mask: LevelMask, + ) -> CellResult> { + type RawCellHash = [u8; H256::LEN]; + + let current_index = if self.is_config_proof(bit_len) { 1 } else { 2 }; + + let cursor = Cursor::new(&data[current_index..]); + let mut reader = ByteReader::endian(cursor, BigEndian); + + let level = level_mask.level() as usize; + let hashes = (0..level) + .map(|_| reader.read::().map(H256::from)) + .collect::, _>>() + .tw_err(|_| CellErrorType::CellBuilderError)?; + let depths = (0..level) + .map(|_| reader.read::()) + .collect::, _>>() + .tw_err(|_| CellErrorType::CellBuilderError)?; + + let result = hashes + .into_iter() + .zip(depths) + .map(|(hash, depth)| Pruned { hash, depth }) + .collect(); + + Ok(result) + } + + /// Special case for config proof + /// This test proof is generated in the moment of voting for a slashing + /// it seems that tools generate it incorrectly and therefore doesn't have mask in it + /// so we need to hardcode it equal to 1 in this case + fn is_config_proof(&self, bit_len: usize) -> bool { + self == &CellType::PrunedBranch && bit_len == 280 + } +} diff --git a/rust/frameworks/tw_ton_sdk/src/cell/level_mask.rs b/rust/frameworks/tw_ton_sdk/src/cell/level_mask.rs new file mode 100644 index 00000000000..18075a49368 --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/cell/level_mask.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +//! Original source code: https://github.com/ston-fi/tonlib-rs/blob/b96a5252df583261ed755656292930af46c2039a/src/cell/level_mask.rs + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct LevelMask { + mask: u32, +} + +impl LevelMask { + pub fn new(new_mask: u32) -> Self { + Self { mask: new_mask } + } + + pub fn mask(&self) -> u32 { + self.mask + } + + pub fn level(&self) -> u8 { + 32 - self.mask.leading_zeros() as u8 + } + + pub fn hash_index(&self) -> usize { + self.mask.count_ones() as usize + } + + pub fn hash_count(&self) -> usize { + self.hash_index() + 1 + } + + pub fn apply(&self, level: u8) -> Self { + LevelMask { + mask: self.mask & ((1u32 << level) - 1), + } + } + + pub fn apply_or(&self, other: Self) -> Self { + LevelMask { + mask: self.mask | other.mask, + } + } + + pub fn shift_right(&self) -> Self { + LevelMask { + mask: self.mask >> 1, + } + } + + pub fn is_significant(&self, level: u8) -> bool { + level == 0 || ((self.mask >> (level - 1)) % 2 != 0) + } +} diff --git a/rust/frameworks/tw_ton_sdk/src/cell/mod.rs b/rust/frameworks/tw_ton_sdk/src/cell/mod.rs new file mode 100644 index 00000000000..c6edaf7adfb --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/cell/mod.rs @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +//! Original source code: https://github.com/ston-fi/tonlib-rs/blob/b96a5252df583261ed755656292930af46c2039a/src/cell.rs + +use crate::boc::binary_writer::BinaryWriter; +use crate::cell::cell_parser::CellParser; +use std::fmt; +use std::sync::Arc; +use tw_coin_entry::error::prelude::{MapTWError, OrTWError, ResultContext}; +use tw_encoding::base64::{self, URL_NO_PAD}; +use tw_encoding::hex::ToHex; +use tw_hash::sha2::sha256; +use tw_hash::H256; +use tw_memory::Data; + +pub mod cell_builder; +pub mod cell_parser; +pub mod cell_type; +pub mod level_mask; + +use crate::cell::cell_type::{CellType, HashesAndDepths}; +use crate::cell::level_mask::LevelMask; +use crate::error::{CellError, CellErrorType, CellResult}; + +const MAX_LEVEL: u8 = 3; + +pub type CellArc = Arc; + +#[derive(PartialEq, Eq, Clone, Hash)] +pub struct Cell { + data: Data, + bit_len: usize, + references: Vec, + cell_type: CellType, + level_mask: LevelMask, + hashes: [H256; 4], + depths: [u16; 4], +} + +impl Cell { + pub fn new( + data: Data, + bit_len: usize, + references: Vec, + is_exotic: bool, + ) -> CellResult { + let cell_type = if is_exotic { + CellType::determine_exotic_cell_type(&data)? + } else { + CellType::Ordinary + }; + + cell_type.validate(&data, bit_len, &references)?; + let level_mask = cell_type.level_mask(&data, bit_len, &references)?; + let HashesAndDepths { hashes, depths } = + calculate_hashes_and_depths(cell_type, &data, bit_len, &references, level_mask)?; + + let result = Self { + data, + bit_len, + references, + level_mask, + cell_type, + hashes, + depths, + }; + + Ok(result) + } + + pub fn into_arc(self) -> CellArc { + Arc::new(self) + } + + pub fn data(&self) -> &[u8] { + self.data.as_slice() + } + + pub fn bit_len(&self) -> usize { + self.bit_len + } + + pub fn references(&self) -> &[CellArc] { + &self.references + } + + pub(crate) fn get_level_mask(&self) -> u32 { + self.level_mask.mask() + } + + pub fn is_exotic(&self) -> bool { + self.cell_type != CellType::Ordinary + } + + pub fn cell_hash(&self) -> H256 { + self.get_hash(MAX_LEVEL) + } + + pub fn cell_hash_base64(&self) -> String { + base64::encode(self.cell_hash().as_slice(), URL_NO_PAD) + } + + pub fn get_hash(&self, level: u8) -> H256 { + self.hashes[level.min(3) as usize] + } + + pub fn get_depth(&self, level: u8) -> u16 { + self.depths[level.min(3) as usize] + } + + pub fn parser(&self) -> CellParser { + CellParser::new(&self.data, self.bit_len) + } + + pub fn parse_fully(&self, parse: F) -> Result + where + F: FnOnce(&mut CellParser) -> CellResult, + { + let mut reader = self.parser(); + let res = parse(&mut reader); + reader.ensure_empty()?; + res + } + + fn fmt_debug(&self, f: &mut fmt::Formatter<'_>, depth: usize) -> fmt::Result { + for _ in 0..depth { + write!(f, "\t")?; + } + // Append Cell IDX. + if depth == 0 { + write!(f, "Cell(root)")?; + } else { + write!(f, "-> Cell(ref {depth})")?; + } + + let maybe_exotic_type = if matches!(self.cell_type, CellType::Ordinary) { + "".to_string() + } else { + format!("exotic={:?} ", self.cell_type) + }; + + write!( + f, + " {{ data={}, bit_len={} {maybe_exotic_type}}}", + self.data.to_hex(), + self.bit_len, + )?; + + writeln!(f)?; + let ref_depth = depth + 1; + for reference in self.references() { + reference.fmt_debug(f, ref_depth)?; + } + Ok(()) + } +} + +impl fmt::Debug for Cell { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let root_depth = 0; + self.fmt_debug(f, root_depth) + } +} + +fn get_repr_for_data( + (original_data, original_data_bit_len): (&[u8], usize), + (data, data_bit_len): (&[u8], usize), + refs: &[CellArc], + level_mask: LevelMask, + level: u8, + cell_type: CellType, +) -> CellResult { + // Allocate + let data_len = data.len(); + // descriptors + data + (hash + depth) * refs_count + let buffer_len = 2 + data_len + (32 + 2) * refs.len(); + + let mut writer = BinaryWriter::with_capacity(buffer_len); + let d1 = get_refs_descriptor(cell_type, refs, level_mask.apply(level).mask())?; + let d2 = get_bits_descriptor(original_data, original_data_bit_len)?; + + // Write descriptors + writer.write(8, d1)?; + writer.write(8, d2)?; + // Write main data + writer.write_bits(data, data_bit_len)?; + // Write ref data + write_ref_depths(&mut writer, refs, cell_type, level)?; + write_ref_hashes(&mut writer, refs, cell_type, level)?; + + writer.bytes_if_aligned().map(|b| b.to_vec()) +} + +/// This function replicates unknown logic of resolving cell data +/// https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/vm/cells/DataCell.cpp#L214 +fn calculate_hashes_and_depths( + cell_type: CellType, + data: &[u8], + bit_len: usize, + references: &[CellArc], + level_mask: LevelMask, +) -> CellResult { + let hash_count = if cell_type == CellType::PrunedBranch { + 1 + } else { + level_mask.hash_count() + }; + + let total_hash_count = level_mask.hash_count(); + let hash_i_offset = total_hash_count - hash_count; + + let mut depths: Vec = Vec::with_capacity(hash_count); + let mut hashes: Vec = Vec::with_capacity(hash_count); + + // Iterate through significant levels + for (hash_i, level_i) in (0..=level_mask.level()) + .filter(|&i| level_mask.is_significant(i)) + .enumerate() + { + if hash_i < hash_i_offset { + continue; + } + + let (current_data, current_bit_len) = if hash_i == hash_i_offset { + (data, bit_len) + } else { + let previous_hash = hashes + .get(hash_i - hash_i_offset - 1) + .or_tw_err(CellErrorType::InternalError) + .context("Can't get right hash")?; + (previous_hash.as_slice(), 256) + }; + + // Calculate Depth + let depth = if references.is_empty() { + 0 + } else { + let max_ref_depth = references.iter().fold(0, |max_depth, reference| { + let child_depth = cell_type.child_depth(reference, level_i); + max_depth.max(child_depth) + }); + + max_ref_depth + .checked_add(1) + .or_tw_err(CellErrorType::CellParserError) + .with_context(|| format!("max_ref_depth is too large: {max_ref_depth}"))? + }; + + // Calculate Hash + let repr = get_repr_for_data( + (data, bit_len), + (current_data, current_bit_len), + references, + level_mask, + level_i, + cell_type, + )?; + let hash = sha256(&repr); + let hash = H256::try_from(hash.as_slice()).expect("Expected 32 bytes hash"); + + depths.push(depth); + hashes.push(hash); + } + + cell_type.resolve_hashes_and_depths(hashes, depths, data, bit_len, level_mask) +} + +/// `references.len() as u8 + 8 * cell_type_var + level_mask as u8 * 32` +fn get_refs_descriptor( + cell_type: CellType, + references: &[CellArc], + level_mask: u32, +) -> CellResult { + let cell_type_var = (cell_type != CellType::Ordinary) as u8; + let references_len: u8 = references + .len() + .try_into() + .tw_err(|_| CellErrorType::CellParserError) + .with_context(|| format!("Got too much Cell references: {}", references.len()))?; + let level_mask_u8: u8 = level_mask + .try_into() + .tw_err(|_| CellErrorType::CellParserError) + .with_context(|| format!("Cell level_mask is too large: {level_mask}"))?; + + level_mask_u8 + .checked_mul(32) + .and_then(|v| v.checked_add(8 * cell_type_var)) + .and_then(|v| v.checked_add(references_len)) + .or_tw_err(CellErrorType::CellParserError) + .context("!get_refs_descriptor") +} + +fn get_bits_descriptor(data: &[u8], bit_len: usize) -> CellResult { + let rest_bits = bit_len % 8; + let full_bytes = rest_bits == 0; + + let double_len = data + .len() + .try_into() + .ok() + .and_then(|len: u8| len.checked_mul(2)) + .or_tw_err(CellErrorType::CellParserError) + .context("!get_bits_descriptor()")?; + + let inverted_full_bytes = !full_bytes as u8; + + // subtract 1 if the last byte is not full + double_len + .checked_sub(inverted_full_bytes) + .or_tw_err(CellErrorType::CellParserError) + .context("!get_bits_descriptor()") +} + +fn write_ref_depths( + writer: &mut BinaryWriter, + refs: &[CellArc], + parent_cell_type: CellType, + level: u8, +) -> CellResult<()> { + for reference in refs { + let child_depth = if matches!( + parent_cell_type, + CellType::MerkleProof | CellType::MerkleUpdate + ) { + reference.get_depth(level + 1) + } else { + reference.get_depth(level) + }; + + writer.write(8, child_depth / 256)?; + writer.write(8, child_depth % 256)?; + } + + Ok(()) +} + +fn write_ref_hashes( + writer: &mut BinaryWriter, + refs: &[CellArc], + parent_cell_type: CellType, + level: u8, +) -> CellResult<()> { + for reference in refs { + let child_hash = if matches!( + parent_cell_type, + CellType::MerkleProof | CellType::MerkleUpdate + ) { + reference.get_hash(level + 1) + } else { + reference.get_hash(level) + }; + + writer.write_bytes(child_hash.as_slice())?; + } + + Ok(()) +} diff --git a/rust/frameworks/tw_ton_sdk/src/crc.rs b/rust/frameworks/tw_ton_sdk/src/crc.rs new file mode 100644 index 00000000000..fcd860b6b0e --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/crc.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crc::Crc; +use lazy_static::lazy_static; + +lazy_static! { + pub static ref CRC_32_ISCSI: Crc = Crc::::new(&crc::CRC_32_ISCSI); + pub static ref CRC_16_XMODEM: Crc = Crc::::new(&crc::CRC_16_XMODEM); +} diff --git a/rust/frameworks/tw_ton_sdk/src/error.rs b/rust/frameworks/tw_ton_sdk/src/error.rs new file mode 100644 index 00000000000..0994ba2a614 --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/error.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::error::prelude::*; + +pub type CellResult = Result; +pub type CellError = TWError; + +#[derive(Debug)] +pub enum CellErrorType { + BagOfCellsDeserializationError, + BagOfCellsSerializationError, + CellBuilderError, + CellParserError, + InvalidAddressType, + InvalidExoticCell, + NonEmptyReader, + InternalError, +} + +pub fn cell_to_signing_error(cell_err: CellError) -> SigningError { + cell_err.map_err(|cell_ty| match cell_ty { + CellErrorType::BagOfCellsDeserializationError + | CellErrorType::CellParserError + | CellErrorType::InvalidExoticCell + | CellErrorType::NonEmptyReader => SigningErrorType::Error_input_parse, + CellErrorType::BagOfCellsSerializationError | CellErrorType::CellBuilderError => { + SigningErrorType::Error_internal + }, + CellErrorType::InvalidAddressType => SigningErrorType::Error_invalid_address, + CellErrorType::InternalError => SigningErrorType::Error_internal, + }) +} diff --git a/rust/frameworks/tw_ton_sdk/src/lib.rs b/rust/frameworks/tw_ton_sdk/src/lib.rs new file mode 100644 index 00000000000..7224847dd6b --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/lib.rs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod address; +pub mod boc; +pub mod cell; +pub mod crc; +pub mod error; +pub mod message; diff --git a/rust/frameworks/tw_ton_sdk/src/message/mod.rs b/rust/frameworks/tw_ton_sdk/src/message/mod.rs new file mode 100644 index 00000000000..2558269f38c --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/message/mod.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod state_init; diff --git a/rust/frameworks/tw_ton_sdk/src/message/state_init.rs b/rust/frameworks/tw_ton_sdk/src/message/state_init.rs new file mode 100644 index 00000000000..a0d8a9d1630 --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/src/message/state_init.rs @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::cell::cell_builder::CellBuilder; +use crate::cell::{Cell, CellArc}; +use crate::error::CellResult; +use tw_hash::H256; + +#[derive(Default)] +pub struct StateInit { + code: Option, + data: Option, +} + +impl StateInit { + pub fn set_code(mut self, code: CellArc) -> Self { + self.code = Some(code); + self + } + + pub fn set_data(mut self, data: CellArc) -> Self { + self.data = Some(data); + self + } + + pub fn create_account_id(&self) -> CellResult { + Ok(self.to_cell()?.cell_hash()) + } + + pub fn to_cell(&self) -> CellResult { + let split_depth = false; + let tick_tock = false; + let library = false; + + let mut builder = CellBuilder::new(); + builder + .store_bit(split_depth)? + .store_bit(tick_tock)? + .store_bit(self.code.is_some())? + .store_bit(self.data.is_some())? + .store_bit(library)?; + if let Some(ref code) = self.code { + builder.store_reference(code)?; + } + if let Some(ref data) = self.data { + builder.store_reference(data)?; + } + builder.build() + } +} diff --git a/rust/frameworks/tw_ton_sdk/tests/address.rs b/rust/frameworks/tw_ton_sdk/tests/address.rs new file mode 100644 index 00000000000..8e84bf301b8 --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/tests/address.rs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::str::FromStr; +use tw_hash::H256; +use tw_ton_sdk::address::address_data::AddressData; +use tw_ton_sdk::address::raw_address::RawAddress; +use tw_ton_sdk::address::user_friendly_address::UserFriendlyAddress; + +const WORKCHAIN: i32 = 0; +const ADDRESS_BYTES: &str = "e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76"; + +const RAW_ADDRESS: &str = "0:e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76"; +const BOUNCEABLE_URL_ADDRESS: &str = "EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR"; +const BOUNCEABLE_ADDRESS: &str = "EQDk2VTvn04SUKJrW7rXahzdF8/Qi6utb0wj43InCu9vdjrR"; + +fn addr_data() -> AddressData { + let bytes = H256::from_str(ADDRESS_BYTES).unwrap(); + AddressData::new(WORKCHAIN, bytes) +} + +#[test] +fn test_raw_address_from_to_string() { + let actual = RawAddress::from_str(RAW_ADDRESS).unwrap(); + let expected = RawAddress::from(addr_data()); + assert_eq!(actual, expected); + let actual_encoded = actual.to_string(); + assert_eq!(actual_encoded, RAW_ADDRESS); +} + +#[test] +fn test_user_friendly_address_from_to_url_string() { + let actual = UserFriendlyAddress::from_base64_url(BOUNCEABLE_URL_ADDRESS).unwrap(); + let expected = UserFriendlyAddress::with_flags(addr_data(), true, false); + assert_eq!(actual, expected); + let actual_encoded = actual.to_base64_url(); + assert_eq!(actual_encoded, BOUNCEABLE_URL_ADDRESS); +} + +#[test] +fn test_user_friendly_address_from_to_std_string() { + let actual = UserFriendlyAddress::from_base64_std(BOUNCEABLE_ADDRESS).unwrap(); + let expected = UserFriendlyAddress::with_flags(addr_data(), true, false); + assert_eq!(actual, expected); + let actual_encoded = actual.to_base64_std(); + assert_eq!(actual_encoded, BOUNCEABLE_ADDRESS); +} diff --git a/rust/frameworks/tw_ton_sdk/tests/boc_encode.rs b/rust/frameworks/tw_ton_sdk/tests/boc_encode.rs new file mode 100644 index 00000000000..6c4a9dcf2ae --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/tests/boc_encode.rs @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use serde::Deserialize; +use serde_json::{json, Value as Json}; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_ton_sdk::boc::BagOfCells; +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::Cell; + +#[derive(Deserialize)] +struct ExpectedCell { + data: String, + bit_len: usize, + references: Vec, +} + +struct TestCase { + input_encoded: &'static str, + expected: Json, + expected_hash: &'static str, + /// Expected encoded can be different from the original [`TestCase::input_encoded`] + /// since same Cell can be BoC encoded differently. + expected_encoded: &'static str, +} + +/// Takes a JSON object that will be deserialized as `ExpectedCell`. +/// It's done to allow the function caller to use `json!` macro for readability. +#[track_caller] +fn test_boc_encode_decode(input: TestCase) { + let boc_decoded = BagOfCells::parse_base64(input.input_encoded).unwrap(); + let root_cell = boc_decoded + .single_root() + .expect("Expected single root Cell"); + + let expected: ExpectedCell = serde_json::from_value(input.expected) + .expect("Error deserializing `ExpectedCell` from JSON"); + assert_eq_cell(root_cell, expected); + + let actual_hash = root_cell.cell_hash_base64(); + assert_eq!(actual_hash, input.expected_hash); + + // Wrap the Cell to the BoC again. + let boc_encoded = BagOfCells::from_root(root_cell.as_ref().clone()) + .to_base64(true) + .unwrap(); + assert_eq!(boc_encoded, input.expected_encoded); +} + +#[track_caller] +fn assert_eq_cell(cell: &Cell, expected: ExpectedCell) { + assert_eq!(cell.data().to_hex(), expected.data, "Invalid Cell.data"); + assert_eq!(cell.bit_len(), expected.bit_len, "Invalid Cell.bit_len"); + for (cell_ref, expected_ref) in cell.references().iter().zip(expected.references) { + assert_eq_cell(cell_ref, expected_ref); + } +} + +#[test] +fn test_boc_encode_jetton_transfer_tx() { + let expected = json!({ + "data": "8800b4510655c8136d4ff5be8ea40a9e161ab0f88321d4969cd828d453f22c7c4b2a08", + "bit_len": 277, + "references": [{ + "data": "688595a2c8b55e7bde026a06f72d3f98f78d52a52eec663bea675b44069578d78338f0e58793370446f6ea491ce97a180de915eb4a3688ea1b37e4c64bbea30529a9a3176a8e07f6000000010003", + "bit_len": 624, + "references": [{ + "data": "620031341f879da9b83ede2949836e1a9fb5ae1c75431117aeb6531a77cf3aae83f3202faf080000000000000000000000000001", + "bit_len": 416, + "references": [{ + "data": "0f8a7ea5000000000000000041dcd65008000b8196730b5e1d033e99c42857e699fa6c827758d8a9489ac210b3bb131d133900168a20cab9026da9feb7d1d48153c2c3561f10643a92d39b051a8a7e458f89654202000000007465737420636f6d6d656e74", + "bit_len": 808, + "references": [] + }] + }] + }] + }); + + // The same Cell can be BoC encoded differently. + // Try to decode the original encoded transaction: https://testnet.tonviewer.com/transaction/12bfe84f947740aec3faa54f04a50690900e3aae9ac9596cfa6804a61a48f429 + test_boc_encode_decode(TestCase { + input_encoded: "te6ccgICAAQAAQAAARgAAAFFiAC0UQZVyBNtT/W+jqQKnhYasPiDIdSWnNgo1FPyLHxLKgwAAQGcaIWVosi1XnveAmoG9y0/mPeNUqUu7GY76mdbRAaVeNeDOPDlh5M3BEb26kkc6XoYDekV60o2iOobN+TGS76jBSmpoxdqjgf2AAAAAQADAAIBaGIAMTQfh52puD7eKUmDbhqfta4cdUMRF662Uxp3zzqug/MgL68IAAAAAAAAAAAAAAAAAAEAAwDKD4p+pQAAAAAAAAAAQdzWUAgAC4GWcwteHQM+mcQoV+aZ+myCd1jYqUiawhCzuxMdEzkAFoogyrkCban+t9HUgVPCw1YfEGQ6ktObBRqKfkWPiWVCAgAAAAB0ZXN0IGNvbW1lbnQ=", + expected: expected.clone(), + expected_hash: "yYwgXI3TfZpqtdthYvW503zvoGfeJKdlFUpet6NZ8i8", + expected_encoded: "te6cckECBAEAARUAAUWIALRRBlXIE21P9b6OpAqeFhqw+IMh1Jac2CjUU/IsfEsqDAEBnGiFlaLItV573gJqBvctP5j3jVKlLuxmO+pnW0QGlXjXgzjw5YeTNwRG9upJHOl6GA3pFetKNojqGzfkxku+owUpqaMXao4H9gAAAAEAAwIBaGIAMTQfh52puD7eKUmDbhqfta4cdUMRF662Uxp3zzqug/MgL68IAAAAAAAAAAAAAAAAAAEDAMoPin6lAAAAAAAAAABB3NZQCAALgZZzC14dAz6ZxChX5pn6bIJ3WNipSJrCELO7Ex0TOQAWiiDKuQJtqf630dSBU8LDVh8QZDqS05sFGop+RY+JZUICAAAAAHRlc3QgY29tbWVudG/bd5c=", + }); + + // Try to decode with same encoded data. + test_boc_encode_decode(TestCase { + input_encoded: "te6cckECBAEAARUAAUWIALRRBlXIE21P9b6OpAqeFhqw+IMh1Jac2CjUU/IsfEsqDAEBnGiFlaLItV573gJqBvctP5j3jVKlLuxmO+pnW0QGlXjXgzjw5YeTNwRG9upJHOl6GA3pFetKNojqGzfkxku+owUpqaMXao4H9gAAAAEAAwIBaGIAMTQfh52puD7eKUmDbhqfta4cdUMRF662Uxp3zzqug/MgL68IAAAAAAAAAAAAAAAAAAEDAMoPin6lAAAAAAAAAABB3NZQCAALgZZzC14dAz6ZxChX5pn6bIJ3WNipSJrCELO7Ex0TOQAWiiDKuQJtqf630dSBU8LDVh8QZDqS05sFGop+RY+JZUICAAAAAHRlc3QgY29tbWVudG/bd5c=", + expected, + expected_hash: "yYwgXI3TfZpqtdthYvW503zvoGfeJKdlFUpet6NZ8i8", + expected_encoded: "te6cckECBAEAARUAAUWIALRRBlXIE21P9b6OpAqeFhqw+IMh1Jac2CjUU/IsfEsqDAEBnGiFlaLItV573gJqBvctP5j3jVKlLuxmO+pnW0QGlXjXgzjw5YeTNwRG9upJHOl6GA3pFetKNojqGzfkxku+owUpqaMXao4H9gAAAAEAAwIBaGIAMTQfh52puD7eKUmDbhqfta4cdUMRF662Uxp3zzqug/MgL68IAAAAAAAAAAAAAAAAAAEDAMoPin6lAAAAAAAAAABB3NZQCAALgZZzC14dAz6ZxChX5pn6bIJ3WNipSJrCELO7Ex0TOQAWiiDKuQJtqf630dSBU8LDVh8QZDqS05sFGop+RY+JZUICAAAAAHRlc3QgY29tbWVudG/bd5c=", + }); +} + +#[test] +fn test_wallet_code_hashes() { + let wallet_v3r1_code = BagOfCells::parse_base64("te6cckEBAQEAYgAAwP8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVD++buA=").unwrap(); + assert_eq!( + wallet_v3r1_code.single_root().unwrap().cell_hash_base64(), + "thBBpYp5gLlG6PueGY48kE0keZ_6NldOpCUcQaVm9YE" + ); + + let wallet_v3r2_code = BagOfCells::parse_base64("te6cckEBAQEAcQAA3v8AIN0gggFMl7ohggEznLqxn3Gw7UTQ0x/THzHXC//jBOCk8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVBC9ba0=").unwrap(); + assert_eq!( + wallet_v3r2_code.single_root().unwrap().cell_hash_base64(), + "hNr6RJ-Ypph3ibojI1gHK8D3bcRSQAKl0JGLmnXS1Zk" + ); + + let wallet_v4r2_code = BagOfCells::parse_base64("te6cckECFAEAAtQAART/APSkE/S88sgLAQIBIAIDAgFIBAUE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8QERITAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNBgcCASAICQB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+gIZ9ADLaRfLH1Jgyz8gyYBA+wAGAIpQBIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UAXKwjiOCEGRzdHKDHrFwgBhQBcsFUAPPFiP6AhPLassfyz/JgED7AJJfA+ICASAKCwBZvSQrb2omhAgKBrkPoCGEcNQICEekk30pkQzmkD6f+YN4EoAbeBAUiYcVnzGEAgFYDA0AEbjJftRNDXCx+AA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIA4PABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AAG7SB/oA1NQi+QAFyMoHFcv/ydB3dIAYyMsFywIizxZQBfoCFMtrEszMyXP7AMhAFIEBCPRR8qcCAHCBAQjXGPoA0z/IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBsgQEI1xj6ANM/MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAAr0AMntVGliJeU=").unwrap(); + assert_eq!( + wallet_v4r2_code.single_root().unwrap().cell_hash_base64(), + "_rX_aCDi_w2Ug-fg1iyBfYRniftK5YDIeIZtlZ2r1cA" + ); +} + +#[test] +fn test_boc_encode_cell_builder() { + let leaf = { + let mut builder = CellBuilder::new(); + builder.store_byte(10).unwrap(); + builder.build().unwrap() + }; + let inter = { + let mut builder = CellBuilder::new(); + builder.store_byte(20).unwrap().store_child(leaf).unwrap(); + builder.build().unwrap() + }; + let root = { + let mut builder = CellBuilder::new(); + builder.store_byte(30).unwrap().store_child(inter).unwrap(); + builder.build().unwrap() + }; + + let boc = BagOfCells::from_root(root); + assert_eq!( + boc.to_base64(true).unwrap(), + "te6cckEBAwEACwABAh4BAQIUAgACCjHga8U=" + ); +} + +fn typical_boc_test(boc_base64: &str, expected_hash: &str) { + let boc = BagOfCells::parse_base64(boc_base64).unwrap(); + let root_cell = boc.single_root().unwrap(); + let hash = root_cell.cell_hash(); + assert_eq!(hash.to_string(), expected_hash); + + let boc_base64_again = boc.to_base64(false).unwrap(); + assert_eq!(boc_base64_again, boc_base64); +} + +#[test] +fn test_boc_encode_pruned_block() { + let boc = "te6ccgEBBAEArwAJRgPIr248LcbQSSCsDD5Rb27WLhRGYiTEGG+uChgAAXoNHAAIASJxwAtrH/x8t+GjDO5/X/f1fk4Rw3oYx+9S1gRE8vya04qzwiyFkEMdYglgAAAaNN8fbBluIJfFw9NAAgMoSAEB/rX/aCDi/w2Ug+fg1iyBfYRniftK5YDIeIZtlZ2r1cAAByhIAQEg0z54hgTX/ohMEnHs6qluCydagWgxQoxSyLwK8qfAOQAA"; + let hash = "a6f4b8afa43a9ee61f6d89050d665d164c94c5eca658ddb6c2ab34b4118ab34c"; + typical_boc_test(boc, hash); +} + +/// Checks whether BoC encoding doesn't panic because of invalid input. +#[test] +fn test_boc_decode_invalid() { + #[track_caller] + fn test_invalid(input_hex: &str) { + let input = input_hex.decode_hex().unwrap(); + BagOfCells::parse(&input).unwrap_err(); + } + + test_invalid("b5ee9c725e0000030000000000000000000000000000000000005e"); + + // Errors in `BagOfCells::parse()`. + test_invalid("b5ee9c72c9000001000000000000100000000000000000ff20d1fffe20000052180000001926"); + test_invalid("b5ee9c7201000001000056600000000c000c0cff5e0000005eb5ee9c72ca0c0c0c0c0c0c00"); + test_invalid("b5ee9c72ca0000010000560c0c130c0c0c0c0c0c0c0c000c0c0c5e5e0c0c00b5ee0c5e5e"); + + // Errors in `cell::get_bit_descriptor()`. + test_invalid("b5ee9c72ca0000010000560c0c130c0c0c0c0c0c0c0c000c0c0c5e5e0c0c00b5ee0c5e5e"); + test_invalid("b5ee9c72ca0000230000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000c000c0cffffffffffff0000000000000000000000000000000000000000000600080c"); + + // Error in `cell::get_refs_descriptor()`. + test_invalid("b5ee9c72d1000c0c0c0c20260cba5e0900002a2600000000000000090909090909090909090909090909090909090909091f1f1f1f090909090909090909090971ee31310909090909090909090200000900090909090901680909090909090909090909090909090909090909090000000000000000000000000c88f3"); + // Errors in `CellType::level_mask()`. + test_invalid("b5ee9c72ca0000180000250125000000000000000b0b0b0b0b0b0404040404040404030404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040408080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808040404040c04040404040404040404040404040404040404040404040404040404040404040404040404270404040404040404040404040404040404040400005204040404040404040404000404040404040404040404040404040403fb04040404040404040404040404040404040404040404040400002501250b4b0b0800ca00250c00000c000c100c0c0c26"); + // Error in `cell::calculate_hashes_and_depths()`. + test_invalid("b5ee9c72d1000a000000000000000008860101ff041cffff000100000000000010081c01000000000000000000000000000000000000b5ee00000000ff9c72d1000a0000000000000000000000ac0000000006060606060606060606060606000008d60104ff031cff530000002e0000080000000000000000b0504f4f4ab0b0b0b0b0b0b0b0b00f00b00500000f0000000000030053a900002f00000000000000feffffffff0000000000009ce4ee6100000000000000000000000000000886fc00ff041cffff00000000000063000000000000eeee9c72069c720606060000060600"); +} diff --git a/rust/frameworks/tw_ton_sdk/tests/cell.rs b/rust/frameworks/tw_ton_sdk/tests/cell.rs new file mode 100644 index 00000000000..0baf948a31b --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/tests/cell.rs @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_ton_sdk::boc::BagOfCells; + +#[test] +fn test_cell_format() { + let boc = BagOfCells::parse_base64( + "te6ccgEBAgEALQABDv8AiNDtHtgBCEIC5wowbAAnJ5YkP1ac4Mko6kz8nxtlxbAGbjghWfXoDfU=", + ) + .unwrap(); + let cell = boc.single_root().unwrap(); + + let actual_fmt = format!("{cell:?}"); + let expected = +"Cell(root) { data=ff0088d0ed1ed8, bit_len=56 } + -> Cell(ref 1) { data=02e70a306c00272796243f569ce0c928ea4cfc9f1b65c5b0066e382159f5e80df5, bit_len=264 exotic=Library } +"; + assert_eq!(actual_fmt, expected); +} diff --git a/rust/frameworks/tw_ton_sdk/tests/cell_parser.rs b/rust/frameworks/tw_ton_sdk/tests/cell_parser.rs new file mode 100644 index 00000000000..445e3842a3d --- /dev/null +++ b/rust/frameworks/tw_ton_sdk/tests/cell_parser.rs @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_number::U256; +use tw_ton_sdk::address::address_data::AddressData; +use tw_ton_sdk::address::user_friendly_address::UserFriendlyAddress; +use tw_ton_sdk::boc::BagOfCells; + +/// In this test we parse a TON internal transfer message encoded as BoC. +#[test] +fn test_cell_parse_internal_transfer_message() { + let boc = BagOfCells::parse_base64( + "te6cckEBAQEAOgAAcGIARUMTww0u7LZO2ecEA9iRbS9qOcmmc6tFLaWOGw5dlFdEAAAAAAAAAAAAAAAAAAAAAAAAAAAAfRXk0w==", + ) + .unwrap(); + let cell = boc.single_root().unwrap(); + let mut parser = cell.parser(); + + let bit_0 = parser.load_bit().unwrap(); + let ihr_disabled = parser.load_bit().unwrap(); + let bounce = parser.load_bit().unwrap(); + let bounced = parser.load_bit().unwrap(); + let src_addr = parser.load_address().unwrap(); + let dest_addr = parser.load_address().unwrap(); + let value = parser.load_coins().unwrap(); + let currency_collections = parser.load_bit().unwrap(); + let ihr_fees = parser.load_coins().unwrap(); + let fwd_fees = parser.load_coins().unwrap(); + let created_lt = parser.load_u64(64).unwrap(); + let created_at = parser.load_u32(32).unwrap(); + let contains_state_init = parser.load_bit().unwrap(); + let contains_data = parser.load_bit().unwrap(); + + parser.ensure_empty().expect("Must be read fully"); + + let expected_dest_addr = + UserFriendlyAddress::from_base64_url("EQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorkdl") + .unwrap(); + let expected_value = U256::from(9_223_372_036_854_775_808_u64); + + assert_eq!(bit_0, false); + assert_eq!(ihr_disabled, true); + assert_eq!(bounce, true); + assert_eq!(bounced, false); + assert_eq!(src_addr, AddressData::NULL); + assert_eq!(dest_addr, expected_dest_addr.into_data()); + assert_eq!(value, expected_value); + assert_eq!(currency_collections, false); + assert_eq!(ihr_fees, U256::ZERO); + assert_eq!(fwd_fees, U256::ZERO); + assert_eq!(created_lt, 0); + assert_eq!(created_at, 0); + assert_eq!(contains_state_init, false); + assert_eq!(contains_data, false); +} diff --git a/rust/frameworks/tw_utxo/Cargo.toml b/rust/frameworks/tw_utxo/Cargo.toml new file mode 100644 index 00000000000..08009365f7f --- /dev/null +++ b/rust/frameworks/tw_utxo/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "tw_utxo" +version = "0.1.0" +edition = "2021" + +[dependencies] +bech32 = "0.9.1" +bitcoin = { version = "0.30.0", features = ["rand-std"] } +byteorder = "1.4" +itertools = "0.10.5" +secp256k1 = { version = "0.27.0", features = ["rand-std"] } +strum_macros = "0.25" +tw_base58_address = { path = "../../tw_base58_address" } +tw_bech32_address = { path = "../../tw_bech32_address" } +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_encoding = { path = "../../tw_encoding" } +tw_hash = { path = "../../tw_hash" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_misc = { path = "../../tw_misc" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_coin_entry = { path = "../../tw_coin_entry", features = ["test-utils"] } diff --git a/rust/frameworks/tw_utxo/src/address/derivation.rs b/rust/frameworks/tw_utxo/src/address/derivation.rs new file mode 100644 index 00000000000..05ced642e02 --- /dev/null +++ b/rust/frameworks/tw_utxo/src/address/derivation.rs @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::derivation::{ChildIndex, Derivation}; + +pub const SEGWIT_DERIVATION_PATH_TYPE: ChildIndex = ChildIndex::Hardened(84); + +pub enum BitcoinDerivation { + Legacy, + Segwit, +} + +impl BitcoinDerivation { + /// TrustWallet derivation inherited from: + /// https://github.com/trustwallet/wallet-core/blob/b65adc4c86e49eb905f659ade025185a62e87ca9/src/Bitcoin/Entry.cpp#L67 + pub fn tw_derivation(coin: &dyn CoinContext, derivation: Derivation) -> BitcoinDerivation { + match derivation { + // In case of a default derivation specified by the function caller, + // we should check the default derivation in the `coin`'s context. + // Please note that testnet derivation is no longer supported. Instead, use address prefix. + Derivation::Default | Derivation::Testnet => (), + Derivation::Segwit => return BitcoinDerivation::Segwit, + Derivation::Legacy => return BitcoinDerivation::Legacy, + } + + let Some(default_derivation) = coin.derivations().first() else { + return BitcoinDerivation::Legacy; + }; + let derivation_path_type = default_derivation.path.path().first().copied(); + + match default_derivation.name { + Derivation::Segwit => BitcoinDerivation::Segwit, + Derivation::Default if derivation_path_type == Some(SEGWIT_DERIVATION_PATH_TYPE) => { + BitcoinDerivation::Segwit + }, + Derivation::Default | Derivation::Legacy | Derivation::Testnet => { + BitcoinDerivation::Legacy + }, + } + } + + /// TrustWallet behaviour inherited from: + /// https://github.com/trustwallet/wallet-core/blob/b65adc4c86e49eb905f659ade025185a62e87ca9/src/Bitcoin/Entry.cpp#L14 + pub fn tw_supports_segwit(coin: &dyn CoinContext) -> bool { + coin.derivations().iter().any(|der| { + der.name == Derivation::Segwit + || der.path.path().first().copied() == Some(SEGWIT_DERIVATION_PATH_TYPE) + }) + } +} diff --git a/rust/frameworks/tw_utxo/src/address/legacy.rs b/rust/frameworks/tw_utxo/src/address/legacy.rs new file mode 100644 index 00000000000..22def1d3750 --- /dev/null +++ b/rust/frameworks/tw_utxo/src/address/legacy.rs @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::script::Script; +use std::fmt; +use std::str::FromStr; +use tw_base58_address::Base58Address; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::prefix::BitcoinBase58Prefix; +use tw_encoding::base58::Alphabet; +use tw_hash::hasher::{sha256_ripemd, Hasher}; +use tw_hash::H160; +use tw_keypair::{ecdsa, tw}; + +pub const BITCOIN_ADDRESS_SIZE: usize = 21; +pub const BITCOIN_ADDRESS_CHECKSUM_SIZE: usize = 4; + +type BitcoinBase58Address = Base58Address; + +#[derive(Debug, Eq, PartialEq)] +pub struct LegacyAddress(BitcoinBase58Address); + +impl LegacyAddress { + pub fn new(prefix: u8, data: &[u8]) -> AddressResult { + let mut bytes = Vec::with_capacity(data.len() + 1); + // Insert the prefix to the beginning of the address bytes array. + bytes.push(prefix); + bytes.extend_from_slice(data); + + BitcoinBase58Address::new(&bytes, Alphabet::Bitcoin, Hasher::Sha256d).map(LegacyAddress) + } + + pub fn p2pkh_with_public_key( + p2pkh_prefix: u8, + public_key: &ecdsa::secp256k1::PublicKey, + ) -> AddressResult { + let public_key_hash = sha256_ripemd(public_key.compressed().as_slice()); + LegacyAddress::new(p2pkh_prefix, &public_key_hash) + } + + /// Tries to parse a `LegacyAddress` and check if + pub fn p2pkh_with_coin_and_prefix( + coin: &dyn CoinContext, + public_key: &tw::PublicKey, + prefix: Option, + ) -> AddressResult { + let p2pkh_prefix = match prefix { + Some(prefix) => prefix.p2pkh, + None => coin.p2pkh_prefix().ok_or(AddressError::InvalidRegistry)?, + }; + + let ecdsa_public_key = public_key + .to_secp256k1() + .ok_or(AddressError::PublicKeyTypeMismatch)?; + + LegacyAddress::p2pkh_with_public_key(p2pkh_prefix, ecdsa_public_key) + } + + pub fn p2sh_with_prefix_byte( + redeem_script: &Script, + p2sh_prefix: u8, + ) -> AddressResult { + let script_hash = sha256_ripemd(redeem_script.as_slice()); + LegacyAddress::new(p2sh_prefix, &script_hash) + } + + pub fn from_str_with_coin_and_prefix( + coin: &dyn CoinContext, + s: &str, + prefix: Option, + ) -> AddressResult { + let base58_prefix = match prefix { + Some(base58_prefix) => base58_prefix, + None => { + let p2pkh = coin.p2pkh_prefix().ok_or(AddressError::InvalidRegistry)?; + let p2sh = coin.p2sh_prefix().ok_or(AddressError::InvalidRegistry)?; + BitcoinBase58Prefix { p2pkh, p2sh } + }, + }; + + LegacyAddress::from_str_checked(s, base58_prefix.p2pkh, base58_prefix.p2sh) + } + + pub fn from_str_checked( + s: &str, + p2pkh_prefix: u8, + p2sh_prefix: u8, + ) -> AddressResult { + let addr = LegacyAddress::from_str(s)?; + if addr.prefix() == p2pkh_prefix || addr.prefix() == p2sh_prefix { + Ok(addr) + } else { + Err(AddressError::UnexpectedAddressPrefix) + } + } + + pub fn prefix(&self) -> u8 { + self.0.as_ref()[0] + } + + /// Address bytes excluding the prefix (skip first byte). + pub fn bytes(&self) -> &[u8] { + &self.0.as_ref()[1..] + } + + pub fn payload(&self) -> H160 { + debug_assert_eq!(self.bytes().len(), H160::LEN); + H160::try_from(self.bytes()).expect("Legacy address must be exactly 20 bytes") + } +} + +impl FromStr for LegacyAddress { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + BitcoinBase58Address::from_str_with_alphabet(s, Alphabet::Bitcoin, Hasher::Sha256d) + .map(LegacyAddress) + } +} + +impl<'a> TryFrom<&'a [u8]> for LegacyAddress { + type Error = AddressError; + + fn try_from(bytes: &'a [u8]) -> Result { + BitcoinBase58Address::new(bytes, Alphabet::Bitcoin, Hasher::Sha256d).map(LegacyAddress) + } +} + +impl fmt::Display for LegacyAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/rust/frameworks/tw_utxo/src/address/mod.rs b/rust/frameworks/tw_utxo/src/address/mod.rs new file mode 100644 index 00000000000..c7c4539b421 --- /dev/null +++ b/rust/frameworks/tw_utxo/src/address/mod.rs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod derivation; +pub mod legacy; +pub mod segwit; +pub mod standard_bitcoin; +pub mod taproot; +pub mod witness_program; + +type Bech32Prefix = tw_bech32_address::bech32_prefix::Bech32Prefix; diff --git a/rust/frameworks/tw_utxo/src/address/segwit.rs b/rust/frameworks/tw_utxo/src/address/segwit.rs new file mode 100644 index 00000000000..8098fc4ad93 --- /dev/null +++ b/rust/frameworks/tw_utxo/src/address/segwit.rs @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use super::Bech32Prefix; +use crate::address::witness_program::{WitnessProgram, WITNESS_V0}; +use crate::script::Script; +use core::fmt; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_hash::hasher::sha256_ripemd; +use tw_hash::sha2::sha256; +use tw_hash::{H160, H256}; +use tw_keypair::tw; +use tw_memory::Data; + +/// Witness program sizes valid for V0 (Segwit). +const WITNESS_V0_VALID_PROGRAM_SIZES: [usize; 2] = [H160::LEN, H256::LEN]; + +#[derive(Debug, Eq, PartialEq)] +pub struct SegwitAddress { + inner: WitnessProgram, +} + +impl SegwitAddress { + pub fn new(hrp: String, witness_program: Data) -> AddressResult { + // Specific segwit v0 check. These addresses can never spend funds sent to them. + if !WITNESS_V0_VALID_PROGRAM_SIZES.contains(&witness_program.len()) { + return Err(AddressError::InvalidInput); + } + + let inner = WitnessProgram::new(hrp, WITNESS_V0, witness_program, bech32::Variant::Bech32)?; + Ok(SegwitAddress { inner }) + } + + pub fn p2wpkh_with_coin_and_prefix( + coin: &dyn CoinContext, + public_key: &tw::PublicKey, + prefix: Option, + ) -> AddressResult { + let hrp = match prefix { + Some(Bech32Prefix { hrp }) => hrp, + None => coin.hrp().ok_or(AddressError::InvalidRegistry)?, + }; + + let public_key_bytes = public_key + .to_secp256k1() + .ok_or(AddressError::PublicKeyTypeMismatch)? + .compressed(); + let public_key_hash = sha256_ripemd(public_key_bytes.as_slice()); + + Self::new(hrp, public_key_hash.to_vec()) + } + + pub fn p2wsh_with_hrp(redeem_script: &Script, hrp: String) -> AddressResult { + let script_hash = sha256(redeem_script.as_slice()); + Self::new(hrp, script_hash) + } + + pub fn from_str_checked(s: &str, expected_hrp: &str) -> AddressResult { + let address = Self::from_str(s)?; + if address.inner.hrp() != expected_hrp { + return Err(AddressError::InvalidHrp); + } + Ok(address) + } + + pub fn from_str_with_coin_and_prefix( + coin: &dyn CoinContext, + s: &str, + prefix: Option, + ) -> AddressResult { + let hrp = match prefix { + Some(Bech32Prefix { hrp }) => hrp, + None => coin.hrp().ok_or(AddressError::InvalidRegistry)?, + }; + SegwitAddress::from_str_checked(s, &hrp) + } + + pub fn witness_program(&self) -> &[u8] { + self.inner.witness_program() + } +} + +impl FromStr for SegwitAddress { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + let inner = WitnessProgram::from_str_checked( + s, + WITNESS_V0, + bech32::Variant::Bech32, + &WITNESS_V0_VALID_PROGRAM_SIZES, + )?; + Ok(SegwitAddress { inner }) + } +} + +impl fmt::Display for SegwitAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.inner) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_encoding::hex::DecodeHex; + + struct TestInputValid { + str: &'static str, + normalized: &'static str, + expected: SegwitAddress, + } + + #[track_caller] + fn segwit_addr(hrp: &str, program: &str) -> SegwitAddress { + SegwitAddress::new(hrp.to_string(), program.decode_hex().unwrap()) + .expect("Cannot construct a SegwitAddress from the input") + } + + /// Tests if the given `s` string representation is converted from and to `expected` segwit address. + #[track_caller] + fn test_to_from_str_valid(input: TestInputValid) { + let actual = SegwitAddress::from_str(input.str).expect("Expected a valid address"); + assert_eq!(actual, input.expected, "String -> SegwitAddress"); + + let actual_str = actual.to_string(); + assert_eq!(actual_str, input.normalized, "SegwitAddress -> String"); + } + + #[track_caller] + fn test_from_str_invalid(str: &str) { + let _ = SegwitAddress::from_str(str).expect_err("Expected an invalid Segwit address"); + } + + #[test] + fn test_segwit_address_to_from_str() { + test_to_from_str_valid(TestInputValid { + str: "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", + normalized: "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", + expected: segwit_addr("bc", "751e76e8199196d454941c45d1b3a323f1433bd6"), + }); + + test_to_from_str_valid(TestInputValid { + str: "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", + normalized: "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", + expected: segwit_addr( + "tb", + "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262", + ), + }); + + test_to_from_str_valid(TestInputValid { + str: "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", + normalized: "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", + expected: segwit_addr( + "tb", + "000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433", + ), + }); + + test_to_from_str_valid(TestInputValid { + str: "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8", + normalized: "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8", + expected: segwit_addr("bc", "0cb9f5c6b62c03249367bc20a90dd2425e6926af"), + }); + + test_to_from_str_valid(TestInputValid { + str: "bc1qm9jzmujvdqjj6y28hptk859zs3yyv78hpqqjfj", + normalized: "bc1qm9jzmujvdqjj6y28hptk859zs3yyv78hpqqjfj", + expected: segwit_addr("bc", "d9642df24c68252d1147b85763d0a284484678f7"), + }); + + test_to_from_str_valid(TestInputValid { + str: "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", + normalized: "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", + expected: segwit_addr( + "tb", + "000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433", + ), + }); + } + + #[test] + fn test_segwit_address_from_str_invalid() { + // witness program size 38 + test_from_str_invalid( + "bc1q0xlxvlhemja6c4dqv22uapctqupfhlxm0xlxvlhemja6c4dqv22uapctqupfkpvgusg", + ); + + // version 1 + test_from_str_invalid("bc1ptmsk7c2yut2xah4pgflpygh2s7fh0cpfkrza9cjj29awapv53mrslgd5cf"); + + // version 1 + test_from_str_invalid( + "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", + ); + + // version 2 + test_from_str_invalid("bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs"); + + // version 16 + test_from_str_invalid("BC1SW50QGDZ25J"); + } +} diff --git a/rust/frameworks/tw_utxo/src/address/standard_bitcoin.rs b/rust/frameworks/tw_utxo/src/address/standard_bitcoin.rs new file mode 100644 index 00000000000..6f582b0976b --- /dev/null +++ b/rust/frameworks/tw_utxo/src/address/standard_bitcoin.rs @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +//! This module contains a standard Bitcoin address enumeration each of these exist on the mainnet network. +//! TODO consider moving the file to `tw_bitcoin`. + +use crate::address::derivation::BitcoinDerivation; +use crate::address::legacy::LegacyAddress; +use crate::address::segwit::SegwitAddress; +use crate::address::taproot::TaprootAddress; +use crate::address::Bech32Prefix; +use std::fmt; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::prefix::{AddressPrefix, BitcoinBase58Prefix}; +use tw_keypair::tw; +use tw_memory::Data; + +/// A standard set of Bitcoin address prefixes. +/// The set of address prefixes can differ for Bitcoin forks. +/// TODO add `TaprootBech32` enum variant. +pub enum StandardBitcoinPrefix { + Base58(BitcoinBase58Prefix), + Bech32(Bech32Prefix), +} + +impl TryFrom for StandardBitcoinPrefix { + type Error = AddressError; + + fn try_from(prefix: AddressPrefix) -> Result { + match prefix { + AddressPrefix::BitcoinBase58(base58) => Ok(StandardBitcoinPrefix::Base58(base58)), + AddressPrefix::Hrp(hrp) => Ok(StandardBitcoinPrefix::Bech32(Bech32Prefix { hrp })), + } + } +} + +/// A standard set of Bitcoin address types. +/// +/// The set of address types can differ for Bitcoin forks. +/// For example, Zcash does not support segwit addresses. +#[derive(Debug, Eq, PartialEq)] +pub enum StandardBitcoinAddress { + Legacy(LegacyAddress), + Segwit(SegwitAddress), + Taproot(TaprootAddress), +} + +impl StandardBitcoinAddress { + /// Tries to parse one of the `BitcoinAddress` variants + /// and validates if the result address matches the given `prefix` address or belongs to the `coin` network. + pub fn from_str_with_coin_and_prefix( + coin: &dyn CoinContext, + s: &str, + prefix: Option, + ) -> AddressResult { + match prefix { + Some(StandardBitcoinPrefix::Base58(base58)) => { + LegacyAddress::from_str_with_coin_and_prefix(coin, s, Some(base58)) + .map(StandardBitcoinAddress::Legacy) + }, + Some(StandardBitcoinPrefix::Bech32(bech32)) => { + SegwitAddress::from_str_with_coin_and_prefix(coin, s, Some(bech32)) + .map(StandardBitcoinAddress::Segwit) + }, + None => StandardBitcoinAddress::from_str_checked(coin, s), + } + } + + /// Tries to parse one of the `BitcoinAddress` variants + /// and validates if the result address belongs to the `coin` network. + pub fn from_str_checked( + coin: &dyn CoinContext, + s: &str, + ) -> AddressResult { + // Try to parse a Segwit address if the coin supports it. + if BitcoinDerivation::tw_supports_segwit(coin) { + if let Ok(segwit) = SegwitAddress::from_str_with_coin_and_prefix(coin, s, None) { + return Ok(StandardBitcoinAddress::Segwit(segwit)); + } + + // TODO use `BitcoinDerivation::tw_supports_taproot` based on `registry.json`. + if let Ok(taproot) = TaprootAddress::from_str_with_coin_and_prefix(coin, s, None) { + return Ok(StandardBitcoinAddress::Taproot(taproot)); + } + } + + // Otherwise, try to parse a Legacy address. + if let Ok(legacy) = LegacyAddress::from_str_with_coin_and_prefix(coin, s, None) { + return Ok(StandardBitcoinAddress::Legacy(legacy)); + } + + Err(AddressError::InvalidInput) + } + + /// TrustWallet derivation inherited from: + /// https://github.com/trustwallet/wallet-core/blob/b65adc4c86e49eb905f659ade025185a62e87ca9/src/Bitcoin/Entry.cpp#L67 + pub fn derive_as_tw( + coin: &dyn CoinContext, + public_key: &tw::PublicKey, + derivation: Derivation, + maybe_prefix: Option, + ) -> AddressResult { + match maybe_prefix { + Some(StandardBitcoinPrefix::Base58(prefix)) => { + return LegacyAddress::p2pkh_with_coin_and_prefix(coin, public_key, Some(prefix)) + .map(StandardBitcoinAddress::Legacy); + }, + Some(StandardBitcoinPrefix::Bech32(prefix)) => { + return SegwitAddress::p2wpkh_with_coin_and_prefix(coin, public_key, Some(prefix)) + .map(StandardBitcoinAddress::Segwit); + }, + // Derive an address as declared in registry.json. + None => (), + } + + match BitcoinDerivation::tw_derivation(coin, derivation) { + BitcoinDerivation::Legacy => { + LegacyAddress::p2pkh_with_coin_and_prefix(coin, public_key, None) + .map(StandardBitcoinAddress::Legacy) + }, + BitcoinDerivation::Segwit => { + SegwitAddress::p2wpkh_with_coin_and_prefix(coin, public_key, None) + .map(StandardBitcoinAddress::Segwit) + }, + } + } +} + +impl FromStr for StandardBitcoinAddress { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + if let Ok(legacy) = LegacyAddress::from_str(s) { + return Ok(StandardBitcoinAddress::Legacy(legacy)); + } + if let Ok(segwit) = SegwitAddress::from_str(s) { + return Ok(StandardBitcoinAddress::Segwit(segwit)); + } + if let Ok(taproot) = TaprootAddress::from_str(s) { + return Ok(StandardBitcoinAddress::Taproot(taproot)); + } + Err(AddressError::InvalidInput) + } +} + +impl fmt::Display for StandardBitcoinAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + StandardBitcoinAddress::Legacy(legacy) => write!(f, "{legacy}"), + StandardBitcoinAddress::Segwit(segwit) => write!(f, "{segwit}"), + StandardBitcoinAddress::Taproot(taproot) => write!(f, "{taproot}"), + } + } +} + +impl CoinAddress for StandardBitcoinAddress { + fn data(&self) -> Data { + match self { + StandardBitcoinAddress::Legacy(legacy) => legacy.bytes().to_vec(), + StandardBitcoinAddress::Segwit(segwit) => segwit.witness_program().to_vec(), + StandardBitcoinAddress::Taproot(taproot) => taproot.witness_program().to_vec(), + } + } +} diff --git a/rust/frameworks/tw_utxo/src/address/taproot.rs b/rust/frameworks/tw_utxo/src/address/taproot.rs new file mode 100644 index 00000000000..d0f18810a20 --- /dev/null +++ b/rust/frameworks/tw_utxo/src/address/taproot.rs @@ -0,0 +1,250 @@ +use super::Bech32Prefix; +use crate::address::witness_program::WitnessProgram; +use bitcoin::key::TapTweak; +use core::fmt; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_hash::{H256, H264}; +use tw_keypair::tw; +use tw_memory::Data; +use tw_misc::traits::ToBytesVec; + +/// cbindgen:ignore +pub const WITNESS_V1: u8 = 1; +/// Witness program sizes valid for V1 (Taproot). +/// cbindgen:ignore +pub const WITNESS_V1_VALID_PROGRAM_SIZES: [usize; 1] = [H256::LEN]; + +#[derive(Debug, Eq, PartialEq)] +pub struct TaprootAddress { + inner: WitnessProgram, +} + +impl TaprootAddress { + pub fn new(hrp: String, witness_program: Data) -> AddressResult { + // Specific Taproot V1 check. These addresses can never spend funds sent to them. + if !WITNESS_V1_VALID_PROGRAM_SIZES.contains(&witness_program.len()) { + return Err(AddressError::InvalidInput); + } + + let inner = + WitnessProgram::new(hrp, WITNESS_V1, witness_program, bech32::Variant::Bech32m)?; + Ok(TaprootAddress { inner }) + } + + /// Create a Taproot address from a public key and an optional merkle root. + /// Taproot transactions come in two variants: + /// + /// * P2TR key-path: which is used for "normal" balance transfers and is + /// internally _tweaked_ with an empty (None) merkle root. + /// * P2TR script-path: which is used for complex scripts, such as + /// Ordinals/BRC20, and is internally _tweaked_ with a merkle root of all + /// possible spending conditions. + pub fn p2tr_with_public_key( + hrp: String, + internal_pubkey: &H264, + merkle_root: Option<&H256>, + ) -> AddressResult { + // We're relying on the `bitcoin` crate to generate anything Taproot related. + + // Convert the native `H256` to `TapNodeHash` from the `bitcoin` crate. + let merkle_root = merkle_root.map(|hash| { + let tap_hash = + as bitcoin::hashes::Hash>::from_slice( + hash.as_slice(), + ) + .expect("merkle_root length is 32 bytes"); + + bitcoin::taproot::TapNodeHash::from_raw_hash(tap_hash) + }); + + // Tweak the public key with the (empty) merkle root. + let pubkey = bitcoin::PublicKey::from_slice(internal_pubkey.as_slice()).unwrap(); + let internal_key = bitcoin::secp256k1::XOnlyPublicKey::from(pubkey.inner); + let (output_key, _parity) = + internal_key.tap_tweak(&bitcoin::secp256k1::Secp256k1::new(), merkle_root); + + Self::new(hrp, output_key.serialize().to_vec()) + } + + /// Create a Taproot address from a public key and an optional merkle root. + /// Taproot transactions come in two variants: + /// + /// * P2TR key-path: which is used for "normal" balance transfers and is + /// internally _tweaked_ with an empty (None) merkle root. + /// * P2TR script-path: which is used for complex scripts, such as + /// Ordinals/BRC20, and is internally _tweaked_ with a merkle root of all + /// possible spending conditions. + pub fn p2tr_with_coin_and_prefix( + coin: &dyn CoinContext, + public_key: &tw::PublicKey, + prefix: Option, + merkle_root: Option<&H256>, + ) -> AddressResult { + let hrp = match prefix { + Some(Bech32Prefix { hrp }) => hrp, + None => coin.hrp().ok_or(AddressError::InvalidRegistry)?, + }; + + let public_key_bytes = public_key + .to_secp256k1() + .ok_or(AddressError::PublicKeyTypeMismatch)? + .compressed(); + + Self::p2tr_with_public_key(hrp, &public_key_bytes, merkle_root) + } + + pub fn from_str_checked(s: &str, expected_hrp: &str) -> AddressResult { + let address = Self::from_str(s)?; + if address.inner.hrp() != expected_hrp { + return Err(AddressError::InvalidHrp); + } + Ok(address) + } + + pub fn from_str_with_coin_and_prefix( + coin: &dyn CoinContext, + s: &str, + prefix: Option, + ) -> AddressResult { + let hrp = match prefix { + Some(Bech32Prefix { hrp }) => hrp, + None => coin.hrp().ok_or(AddressError::InvalidRegistry)?, + }; + TaprootAddress::from_str_checked(s, &hrp) + } + + pub fn witness_program(&self) -> &[u8] { + self.inner.witness_program() + } +} + +impl FromStr for TaprootAddress { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + let inner = WitnessProgram::from_str_checked( + s, + WITNESS_V1, + bech32::Variant::Bech32m, + &WITNESS_V1_VALID_PROGRAM_SIZES, + )?; + Ok(TaprootAddress { inner }) + } +} + +impl fmt::Display for TaprootAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.inner) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_coin_entry::test_utils::test_context::TestCoinContext; + use tw_encoding::hex::DecodeHex; + + struct TestInputValid { + str: &'static str, + normalized: &'static str, + expected: TaprootAddress, + } + + #[track_caller] + fn taproot_addr(hrp: &str, program: &str) -> TaprootAddress { + TaprootAddress::new(hrp.to_string(), program.decode_hex().unwrap()) + .expect("Cannot construct a TaprootAddress from the input") + } + + /// Tests if the given `s` string representation is converted from and to `expected` segwit address. + #[track_caller] + fn test_to_from_str_valid(input: TestInputValid) { + let actual = TaprootAddress::from_str(input.str).expect("Expected a valid address"); + assert_eq!(actual, input.expected, "String -> TaprootAddress"); + + let actual_str = actual.to_string(); + assert_eq!(actual_str, input.normalized, "TaprootAddress -> String"); + } + + #[track_caller] + fn test_from_str_invalid(str: &str) { + let _ = TaprootAddress::from_str(str).expect_err("Expected an invalid Taproot address"); + } + + #[test] + fn test_segwit_address_to_from_str() { + test_to_from_str_valid(TestInputValid { + str: "bc1ptmsk7c2yut2xah4pgflpygh2s7fh0cpfkrza9cjj29awapv53mrslgd5cf", + normalized: "bc1ptmsk7c2yut2xah4pgflpygh2s7fh0cpfkrza9cjj29awapv53mrslgd5cf", + expected: taproot_addr( + "bc", + "5ee16f6144e2d46edea1427e1222ea879377e029b0c5d2e252517aee85948ec7", + ), + }); + + test_to_from_str_valid(TestInputValid { + str: "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c", + normalized: "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c", + expected: taproot_addr( + "tb", + "000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433", + ), + }); + + test_to_from_str_valid(TestInputValid { + str: "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0", + normalized: "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0", + expected: taproot_addr( + "bc", + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + ), + }); + } + + #[test] + fn test_taproot_address_from_str_invalid() { + // version 0 + test_from_str_invalid("tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy"); + + // version 2 + test_from_str_invalid("bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs"); + + // version 2 + test_from_str_invalid("bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs"); + + // version 16 + test_from_str_invalid("BC1SW50QGDZ25J"); + + // program size 40 + test_from_str_invalid( + "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", + ); + } + + #[test] + fn test_taproot_address_create_with_coin_and_prefix() { + let coin = TestCoinContext::default(); + let hrp = "bc".to_string(); + let merkle_root = None; + + let public_key_bytes = "03cdf7e208a0146c3a35c181944a96a15b2a58256be69adad640a9a97d408b9b44" + .decode_hex() + .unwrap(); + let public_key = + tw::PublicKey::new(public_key_bytes.clone(), tw::PublicKeyType::Secp256k1).unwrap(); + + let addr = TaprootAddress::p2tr_with_coin_and_prefix( + &coin, + &public_key, + Some(Bech32Prefix { hrp }), + merkle_root, + ) + .unwrap(); + assert_eq!( + addr.to_string(), + "bc1purekytqrqzfzdulufmll8a335jhvw9x4glhzp8fv76yxlsxeyptsfylq9h" + ); + } +} diff --git a/rust/frameworks/tw_utxo/src/address/witness_program.rs b/rust/frameworks/tw_utxo/src/address/witness_program.rs new file mode 100644 index 00000000000..0edb5ea65be --- /dev/null +++ b/rust/frameworks/tw_utxo/src/address/witness_program.rs @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use bech32::FromBase32; +use std::fmt; +use std::ops::RangeInclusive; +use tw_coin_entry::error::prelude::*; +use tw_memory::Data; + +/// cbindgen:ignore +pub const WITNESS_V0: u8 = 0; +/// cbindgen:ignore +pub const MAX_WITNESS_VERSION: u8 = 16; +/// cbindgen:ignore +pub const WITNESS_VERSIONS: RangeInclusive = WITNESS_V0..=MAX_WITNESS_VERSION; +/// Witness program sizes valid for most of the witness versions. +/// Please note that V0 is more constraint. +/// cbindgen:ignore +pub const WITNESS_VALID_PROGRAM_SIZES: RangeInclusive = 2..=40; + +/// A segwit address implementation that supports various program versions. +/// For example: +/// * witness V0 is Segwit address +/// * witness V1 is Taproot address +#[derive(Debug, Eq, PartialEq)] +pub struct WitnessProgram { + hrp: String, + witness_version: u8, + witness_program: Data, + /// An address string created from this `hrp`, `witness_version` and `witness_program`. + address_str: String, + bech32_variant: bech32::Variant, +} + +impl WitnessProgram { + pub fn new( + hrp: String, + witness_version: u8, + witness_program: Data, + bech32_variant: bech32::Variant, + ) -> AddressResult { + if !WITNESS_VERSIONS.contains(&witness_version) { + return Err(AddressError::Unsupported); + } + + if !WITNESS_VALID_PROGRAM_SIZES.contains(&witness_program.len()) { + return Err(AddressError::InvalidInput); + } + + let address_str = + Self::fmt_internal(&hrp, witness_version, &witness_program, bech32_variant)?; + Ok(WitnessProgram { + hrp, + witness_version, + witness_program, + address_str, + bech32_variant, + }) + } + + pub fn witness_version(&self) -> u8 { + self.witness_version + } + + pub fn witness_program(&self) -> &[u8] { + &self.witness_program + } + + pub fn hrp(&self) -> &str { + &self.hrp + } + + pub fn from_str_checked( + s: &str, + expected_version: u8, + expected_checksum_type: bech32::Variant, + valid_program_sizes: &[usize], + ) -> AddressResult { + let (hrp, payload_u5, checksum_variant) = + bech32::decode(s).map_err(|_| AddressError::FromBech32Error)?; + + if payload_u5.is_empty() { + return Err(AddressError::InvalidInput); + } + + // Get the script version and program (converted from 5-bit to 8-bit) + let (version, program) = payload_u5.split_at(1); + let version = version[0].to_u8(); + let program = Data::from_base32(program).map_err(|_| AddressError::FromBech32Error)?; + + // Check witness version. + if version != expected_version { + return Err(AddressError::Unsupported); + } + + // Check encoding. + if checksum_variant != expected_checksum_type { + return Err(AddressError::InvalidInput); + } + + // Check witness program sizes. + if !valid_program_sizes.contains(&program.len()) { + return Err(AddressError::InvalidInput); + } + + WitnessProgram::new(hrp, version, program, checksum_variant) + } + + fn fmt_internal( + hrp: &str, + witness_version: u8, + witness_program: &[u8], + bech32_variant: bech32::Variant, + ) -> AddressResult { + const STRING_CAPACITY: usize = 100; + + let mut result_addr = String::with_capacity(STRING_CAPACITY); + + let version_u5 = + bech32::u5::try_from_u8(witness_version).expect("WitnessVersion must be 0..=16"); + + { + let mut bech32_writer = + bech32::Bech32Writer::new(hrp, bech32_variant, &mut result_addr) + .map_err(|_| AddressError::FromBech32Error)?; + bech32::WriteBase32::write_u5(&mut bech32_writer, version_u5) + .map_err(|_| AddressError::FromBech32Error)?; + bech32::ToBase32::write_base32(&witness_program, &mut bech32_writer) + .map_err(|_| AddressError::FromBech32Error)?; + } + + Ok(result_addr) + } +} + +impl fmt::Display for WitnessProgram { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.address_str) + } +} diff --git a/rust/frameworks/tw_utxo/src/constants.rs b/rust/frameworks/tw_utxo/src/constants.rs new file mode 100644 index 00000000000..0a99f519fac --- /dev/null +++ b/rust/frameworks/tw_utxo/src/constants.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +/// A standard transaction is limited to 400k weight units (WU). +/// https://bitcoin.stackexchange.com/questions/35570/what-is-the-maximum-number-of-inputs-outputs-a-transaction-can-have +pub const MAX_TRANSACTION_WEIGHT: usize = 400_000; diff --git a/rust/frameworks/tw_utxo/src/dust/dust_filter.rs b/rust/frameworks/tw_utxo/src/dust/dust_filter.rs new file mode 100644 index 00000000000..0d68ee106dc --- /dev/null +++ b/rust/frameworks/tw_utxo/src/dust/dust_filter.rs @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::dust::DustPolicy; +use crate::script::standard_script::conditions; +use crate::transaction::transaction_interface::{TransactionInterface, TxOutputInterface}; +use crate::transaction::unsigned_transaction::UnsignedTransaction; +use std::marker::PhantomData; +use tw_coin_entry::error::prelude::*; + +pub struct DustFilter { + dust_policy: DustPolicy, + _phantom: PhantomData, +} + +impl DustFilter { + pub fn new(dust_policy: DustPolicy) -> Self { + DustFilter { + dust_policy, + _phantom: PhantomData, + } + } + + /// Filter dust UTXOs out. + /// Returns an error if there are no valid UTXOs. + pub fn filter_inputs( + &self, + mut transaction: UnsignedTransaction, + ) -> SigningResult> { + let dust_threshold = self.dust_policy.dust_threshold(); + + transaction.retain_inputs(|_utxo, utxo_args| utxo_args.amount >= dust_threshold)?; + + Ok(transaction) + } + + /// Checks if all transaction output amounts are greater or equal to a dust threshold. + pub fn check_outputs( + &self, + transaction: &UnsignedTransaction, + ) -> SigningResult<()> { + let dust_threshold = self.dust_policy.dust_threshold(); + + let has_dust_output = transaction.transaction().outputs().iter().any(|output| { + if conditions::is_op_return(output.script_pubkey()) { + // Ignore the OP_RETURN output value. It can (or even should) be 0. + return false; + } + output.value() < dust_threshold + }); + + if has_dust_output { + return SigningError::err(SigningErrorType::Error_dust_amount_requested); + } + Ok(()) + } +} diff --git a/rust/frameworks/tw_utxo/src/dust/mod.rs b/rust/frameworks/tw_utxo/src/dust/mod.rs new file mode 100644 index 00000000000..5e605ae241f --- /dev/null +++ b/rust/frameworks/tw_utxo/src/dust/mod.rs @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::transaction::transaction_parts::Amount; + +pub mod dust_filter; + +/// Transaction dust amount calculator. +/// Later, we plan to add support for `DynamicDust` policy with a `min_relay_fee` amount. +#[derive(Clone, Copy)] +pub enum DustPolicy { + FixedAmount(Amount), +} + +impl DustPolicy { + pub fn dust_threshold(&self) -> Amount { + match self { + DustPolicy::FixedAmount(amount) => *amount, + } + } +} diff --git a/rust/frameworks/tw_utxo/src/encode/compact_integer.rs b/rust/frameworks/tw_utxo/src/encode/compact_integer.rs new file mode 100644 index 00000000000..4a95a378699 --- /dev/null +++ b/rust/frameworks/tw_utxo/src/encode/compact_integer.rs @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::encode::stream::Stream; +use crate::encode::Encodable; +use std::ops::RangeInclusive; + +const ONE_BYTE_RANGE: RangeInclusive = 0..=0xFC; +const TWO_BYTES_RANGE: RangeInclusive = 0xFD..=0xFFFF; +const FOUR_BYTES_RANGE: RangeInclusive = 0x10000..=0xFFFF_FFFF; + +const TWO_BYTES_FLAG: u8 = 0xFD_u8; +const FOUR_BYTES_FLAG: u8 = 0xFE_u8; +const EIGHT_BYTES_FLAG: u8 = 0xFF_u8; + +/// A type of variable-length integer commonly used in the Bitcoin P2P protocol and Bitcoin serialized data structures. +#[derive(Default, Debug, Clone, Copy, PartialEq)] +pub struct CompactInteger(u64); + +impl From for CompactInteger { + fn from(value: usize) -> Self { + CompactInteger(value as u64) + } +} + +impl Encodable for CompactInteger { + fn encode(&self, stream: &mut Stream) { + let v = self.0; + + if ONE_BYTE_RANGE.contains(&v) { + stream.append(&(v as u8)); + } else if TWO_BYTES_RANGE.contains(&v) { + stream.append(&TWO_BYTES_FLAG).append(&(v as u16)); + } else if FOUR_BYTES_RANGE.contains(&v) { + stream.append(&FOUR_BYTES_FLAG).append(&(v as u32)); + } else { + stream.append(&EIGHT_BYTES_FLAG).append(&v); + } + } + + fn encoded_size(&self) -> usize { + const BYTE_FLAG: usize = 1; + + let v = self.0; + if ONE_BYTE_RANGE.contains(&v) { + BYTE_FLAG + } else if TWO_BYTES_RANGE.contains(&v) { + BYTE_FLAG + 2 + } else if FOUR_BYTES_RANGE.contains(&v) { + BYTE_FLAG + 4 + } else { + BYTE_FLAG + 8 + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_compact_integer_stream() { + let mut stream = Stream::default(); + + stream + .append(&CompactInteger::from(0_usize)) + .append(&CompactInteger::from(0xfc_usize)) + .append(&CompactInteger::from(0xfd_usize)) + .append(&CompactInteger::from(0xffff_usize)) + .append(&CompactInteger::from(0x10000_usize)) + .append(&CompactInteger::from(0xffff_ffff_usize)) + .append(&CompactInteger(0x1_0000_0000_u64)); + + let expected = vec![ + 0_u8, 0xfc, 0xfd, 0xfd, 0x00, 0xfd, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x01, 0x00, 0xfe, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + ]; + + assert_eq!(stream.out(), expected); + } +} diff --git a/rust/frameworks/tw_utxo/src/encode/impls.rs b/rust/frameworks/tw_utxo/src/encode/impls.rs new file mode 100644 index 00000000000..b21b1567cc1 --- /dev/null +++ b/rust/frameworks/tw_utxo/src/encode/impls.rs @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::encode::compact_integer::CompactInteger; +use crate::encode::stream::Stream; +use crate::encode::Encodable; +use byteorder::{LittleEndian, WriteBytesExt}; +use tw_hash::Hash; +use tw_memory::Data; + +impl Encodable for Data { + fn encode(&self, stream: &mut Stream) { + stream + .append(&CompactInteger::from(self.len())) + .append_raw_slice(self.as_slice()); + } + + fn encoded_size(&self) -> usize { + CompactInteger::from(self.len()).encoded_size() + self.len() + } +} + +impl Encodable for Hash { + #[inline] + fn encode(&self, stream: &mut Stream) { + stream.append_raw_slice(self.as_slice()); + } + + #[inline] + fn encoded_size(&self) -> usize { + N + } +} + +impl Encodable for u8 { + #[inline] + fn encode(&self, s: &mut Stream) { + s.write_u8(*self).unwrap(); + } + + #[inline] + fn encoded_size(&self) -> usize { + 1 + } +} + +macro_rules! impl_encodable_for_int { + ($int:ty, $size:literal, $write_fn:tt) => { + impl Encodable for $int { + #[inline] + fn encode(&self, s: &mut Stream) { + s.$write_fn::(*self).unwrap(); + } + + #[inline] + fn encoded_size(&self) -> usize { + $size + } + } + }; +} + +impl_encodable_for_int!(i32, 4, write_i32); +impl_encodable_for_int!(i64, 8, write_i64); +impl_encodable_for_int!(u16, 2, write_u16); +impl_encodable_for_int!(u32, 4, write_u32); +impl_encodable_for_int!(u64, 8, write_u64); + +#[cfg(test)] +mod tests { + use super::*; + use crate::encode::encode; + use tw_encoding::hex::{DecodeHex, ToHex}; + + #[test] + fn test_stream_append() { + let mut stream = Stream::default(); + + stream + .append(&1u8) + .append(&2u16) + .append(&3u32) + .append(&4u64); + + let expected = vec![1_u8, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0]; + assert_eq!(stream.out(), expected); + } + + #[test] + fn test_bytes_serialize() { + let expected = "020145".decode_hex().unwrap(); + let bytes = "0145".decode_hex().unwrap(); + assert_eq!(expected, encode(&bytes)); + } + + #[test] + fn test_steam_append_slice() { + let mut slice = [0u8; 4]; + slice[0] = 0x64; + let mut stream = Stream::default(); + stream.append_raw_slice(&slice); + assert_eq!(stream.out().to_hex(), "64000000"); + } +} diff --git a/rust/frameworks/tw_utxo/src/encode/mod.rs b/rust/frameworks/tw_utxo/src/encode/mod.rs new file mode 100644 index 00000000000..14eaad21574 --- /dev/null +++ b/rust/frameworks/tw_utxo/src/encode/mod.rs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::encode::stream::Stream; +use tw_memory::Data; + +pub mod compact_integer; +pub mod impls; +pub mod stream; + +pub fn encode(t: &T) -> Data +where + T: Encodable, +{ + let mut stream = Stream::default(); + stream.append(t); + stream.out() +} + +pub trait Encodable { + /// Serialize the struct and appends it to the end of stream. + fn encode(&self, stream: &mut Stream); + + /// Hint about the size of serialized struct. + fn encoded_size(&self) -> usize; +} diff --git a/rust/frameworks/tw_utxo/src/encode/stream.rs b/rust/frameworks/tw_utxo/src/encode/stream.rs new file mode 100644 index 00000000000..566debfd62b --- /dev/null +++ b/rust/frameworks/tw_utxo/src/encode/stream.rs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::encode::compact_integer::CompactInteger; +use crate::encode::Encodable; +use std::io; +use std::io::Write; +use tw_memory::Data; + +/// Stream used for serialization of Bitcoin structures +#[derive(Default)] +pub struct Stream { + buffer: Data, +} + +impl Stream { + /// New stream + pub fn new() -> Self { + Stream { + buffer: Data::default(), + } + } + + /// Serializes the struct and appends it to the end of stream. + pub fn append(&mut self, t: &T) -> &mut Self + where + T: Encodable, + { + t.encode(self); + self + } + + /// Appends raw bytes to the end of the stream. + pub fn append_raw_slice(&mut self, bytes: &[u8]) -> &mut Self { + // discard error for now, since we write to simple vector + self.buffer.write_all(bytes).unwrap(); + self + } + + /// Appends a list of serializable structs to the end of the stream. + pub fn append_list(&mut self, t: &[T]) -> &mut Self { + CompactInteger::from(t.len()).encode(self); + for i in t { + i.encode(self); + } + self + } + + /// Full stream. + pub fn out(self) -> Data { + self.buffer + } +} + +impl Write for Stream { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + self.buffer.write(buf) + } + + #[inline] + fn flush(&mut self) -> Result<(), io::Error> { + self.buffer.flush() + } +} diff --git a/rust/frameworks/tw_utxo/src/lib.rs b/rust/frameworks/tw_utxo/src/lib.rs new file mode 100644 index 00000000000..cdfe6be6642 --- /dev/null +++ b/rust/frameworks/tw_utxo/src/lib.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod address; +pub mod constants; +pub mod dust; +pub mod encode; +pub mod modules; +pub mod script; +pub mod sighash; +pub mod signature; +pub mod signing_mode; +pub mod spending_data; +pub mod transaction; +pub mod utxo_entry; diff --git a/rust/frameworks/tw_utxo/src/modules/fee_estimator.rs b/rust/frameworks/tw_utxo/src/modules/fee_estimator.rs new file mode 100644 index 00000000000..f6fc0242ffb --- /dev/null +++ b/rust/frameworks/tw_utxo/src/modules/fee_estimator.rs @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::transaction::transaction_interface::TransactionInterface; +use crate::transaction::transaction_parts::Amount; +use std::marker::PhantomData; +use tw_coin_entry::error::prelude::*; + +pub struct FeeEstimator { + _phantom: PhantomData, +} + +impl FeeEstimator { + pub fn estimate_fee(tx: &Transaction, fee_rate: Amount) -> SigningResult { + let vsize = tx.vsize(); + Amount::try_from(vsize) + .ok() + .and_then(|vsize| vsize.checked_mul(fee_rate)) + .or_tw_err(SigningErrorType::Error_wrong_fee) + .with_context(|| format!("feePerVByte is too large: '{vsize} * {fee_rate}' overflow")) + } +} diff --git a/rust/frameworks/tw_utxo/src/modules/keys_manager.rs b/rust/frameworks/tw_utxo/src/modules/keys_manager.rs new file mode 100644 index 00000000000..cb272d37529 --- /dev/null +++ b/rust/frameworks/tw_utxo/src/modules/keys_manager.rs @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::sighash_computer::TaprootTweak; +use std::collections::HashMap; +use tw_coin_entry::error::prelude::*; +use tw_hash::H264; +use tw_keypair::{ecdsa, schnorr}; + +/// Standard Bitcoin keys manager. +/// Supports ecdsa and schnorr private keys. +#[derive(Default)] +pub struct KeysManager { + /// Ecdsa public to private keys. + ecdsa_public_private_map: HashMap, + /// Schnorr private keys. + schnorr_private_keys: Vec, +} + +impl KeysManager { + pub fn add_ecdsa_private(&mut self, private: ecdsa::secp256k1::PrivateKey) -> &mut Self { + self.ecdsa_public_private_map + .insert(private.public().compressed(), private); + self + } + + pub fn add_schnorr_private(&mut self, private: schnorr::PrivateKey) -> &mut Self { + self.schnorr_private_keys.push(private); + self + } + + pub fn get_ecdsa_private( + &self, + public: &ecdsa::secp256k1::PublicKey, + ) -> SigningResult<&ecdsa::secp256k1::PrivateKey> { + let pubkey_bytes = public.compressed(); + + self.ecdsa_public_private_map + .get(&pubkey_bytes) + .or_tw_err(SigningErrorType::Error_missing_private_key) + .with_context(|| format!("Cannot find a private key corresponding to the ecdsa public key: {pubkey_bytes}")) + } + + /// Gets a schnorr private key by an either tweaked or untweaked x-only public key. + /// The function iterates over the private keys, tweaks them if specified in `taproot_tweak`, + /// and returns `Ok(schnorr::PrivateKey)` if found. + pub fn get_schnorr_private( + &self, + public: &schnorr::XOnlyPublicKey, + taproot_tweak: &Option, + ) -> SigningResult { + let pubkey_bytes = public.bytes(); + + for private_key in self.schnorr_private_keys.iter() { + match taproot_tweak { + Some(ref tweak) => { + let tweaked_private = private_key.clone().tweak(tweak.merkle_root); + if tweaked_private.public().x_only().bytes() == pubkey_bytes { + return Ok(tweaked_private); + } + // Otherwise, continue searching for a private key. + }, + None => { + if private_key.public().x_only().bytes() == pubkey_bytes { + return Ok(private_key.clone()); + } + // Otherwise, continue searching for a private key. + }, + } + } + + SigningError::err(SigningErrorType::Error_missing_private_key) + .context(format!("Cannot find a private key corresponding to the x-only schnorr public key: {pubkey_bytes}")) + } +} diff --git a/rust/frameworks/tw_utxo/src/modules/mod.rs b/rust/frameworks/tw_utxo/src/modules/mod.rs new file mode 100644 index 00000000000..9d59f9124d1 --- /dev/null +++ b/rust/frameworks/tw_utxo/src/modules/mod.rs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod fee_estimator; +pub mod keys_manager; +pub mod sighash_computer; +pub mod sighash_verifier; +pub mod tx_compiler; +pub mod tx_planner; +pub mod tx_signer; +pub mod utxo_selector; diff --git a/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs b/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs new file mode 100644 index 00000000000..0bcfb626960 --- /dev/null +++ b/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::script::Script; +use crate::signing_mode::SigningMethod; +use crate::transaction::transaction_interface::TransactionInterface; +use crate::transaction::transaction_parts::Amount; +use crate::transaction::unsigned_transaction::UnsignedTransaction; +use crate::transaction::{ + TransactionPreimage, UtxoPreimageArgs, UtxoTaprootPreimageArgs, UtxoToSign, +}; +use std::marker::PhantomData; +use tw_coin_entry::coin_entry::PublicKeyBytes; +use tw_coin_entry::error::prelude::SigningResult; +use tw_hash::H256; + +#[derive(Debug, Clone)] +pub struct TxPreimage { + /// Transaction signatures in the same order as the transaction UTXOs. + pub sighashes: Vec, +} + +#[derive(Debug, Clone)] +pub struct UtxoSighash { + /// The signing method needs to be used for this sighash. + pub signing_method: SigningMethod, + pub sighash: H256, + pub signer_pubkey: PublicKeyBytes, + /// Taproot tweak if [`SigningMethod::Taproot`] signing method is used. + /// Empty if there is no need to tweak the private to sign the sighash. + pub taproot_tweak: Option, +} + +#[derive(Debug, Clone)] +pub struct TaprootTweak { + /// 32 bytes merkle root of the script tree. + /// `None` if there are no scripts, and the private key should be tweaked without a merkle root. + pub merkle_root: Option, +} + +/// Sighash Computer with a standard Bitcoin behaviour. +/// +/// # Important +/// +/// If needed to implement a custom logic, consider adding a different Sighash Computer. +pub struct SighashComputer { + _phantom: PhantomData, +} + +impl SighashComputer +where + Transaction: TransactionPreimage + TransactionInterface, +{ + /// Computes sighashes of [`SighashComputer::transaction`]. + pub fn preimage_tx( + unsigned_tx: &UnsignedTransaction, + ) -> SigningResult { + unsigned_tx + .input_args() + .iter() + .enumerate() + .map(|(input_index, utxo)| { + let signing_method = utxo.signing_method; + + let utxo_args = UtxoPreimageArgs { + input_index, + script_pubkey: utxo.script_pubkey.clone(), + amount: utxo.amount, + // TODO move `leaf_hash_code_separator` to `UtxoTaprootPreimageArgs`. + leaf_hash_code_separator: utxo.leaf_hash_code_separator, + sighash_ty: utxo.sighash_ty, + tx_hasher: utxo.tx_hasher, + signing_method, + }; + + let (sighash, taproot_tweak) = match signing_method { + SigningMethod::Legacy | SigningMethod::Segwit => { + let sighash = unsigned_tx.transaction().preimage_tx(&utxo_args)?; + (sighash, None) + }, + SigningMethod::Taproot => { + // TODO Move `tr_spent_amounts` and `tr_spent_script_pubkeys` logic to `Transaction::preimage_taproot_tx()`. + let tr_spent_amounts: Vec = unsigned_tx + .input_args() + .iter() + .map(|utxo| utxo.amount) + .collect(); + + let tr_spent_script_pubkeys: Vec + + + + + + diff --git a/samples/wasm/wallet-core.html b/samples/wasm/wallet-core.html deleted file mode 100644 index 881cb995399..00000000000 --- a/samples/wasm/wallet-core.html +++ /dev/null @@ -1,1306 +0,0 @@ - - - - - - Emscripten-Generated Code - - - - - image/svg+xml - - -
-
Downloading...
- - - Resize canvas - Lock/hide mouse pointer     - - - - -
- -
- - -
- -
- - - - - - - - diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 00000000000..ec607dbae00 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,7 @@ +sonar.organization=trustwallet +sonar.projectKey=TrustWallet_wallet-core +sonar.cfamily.compile-commands=build/compile_commands.json +sonar.cfamily.reportingCppStandardOverride=c++20 +sonar.cfamily.cache.enabled=false +sonar.lang.patterns.cpp=**/*.cc,**/*.cpp,**/*.cxx,**/*.c++,**/*.hh,**/*.hpp,**/*.hxx,**/*.h++,**/*.ipp,**/*.h +sonar.lang.patterns.c=**/*.c diff --git a/src/Aeternity/Address.cpp b/src/Aeternity/Address.cpp index 101ba527854..381e4b41ba0 100644 --- a/src/Aeternity/Address.cpp +++ b/src/Aeternity/Address.cpp @@ -1,16 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" #include "Identifiers.h" #include -#include -#include -using namespace TW::Aeternity; +namespace TW::Aeternity { /// Determines whether a string makes a valid address. bool Address::isValid(const std::string& string) { @@ -25,7 +21,7 @@ bool Address::isValid(const std::string& string) { } /// Initializes an address from a public key. -Address::Address(const PublicKey &publicKey) { +Address::Address(const PublicKey& publicKey) { if (publicKey.type != TWPublicKeyTypeED25519) { throw std::invalid_argument("Invalid public key type"); } @@ -40,12 +36,12 @@ Address::Address(const std::string& string) { } auto payload = string.substr(Identifiers::prefixAccountPubkey.size(), string.size()); - bytes = Base58::bitcoin.decodeCheck(payload); + bytes = Base58::decodeCheck(payload); } /// Returns a string representation of the Aeternity address. std::string Address::string() const { - return Identifiers::prefixAccountPubkey + Base58::bitcoin.encodeCheck(bytes); + return Identifiers::prefixAccountPubkey + Base58::encodeCheck(bytes); } bool Address::checkType(const std::string& type) { @@ -53,6 +49,8 @@ bool Address::checkType(const std::string& type) { } bool Address::checkPayload(const std::string& payload) { - unsigned long base58 = Base58::bitcoin.decodeCheck(payload).size(); + unsigned long base58 = Base58::decodeCheck(payload).size(); return base58 == size; } + +} // namespace TW::Aeternity diff --git a/src/Aeternity/Address.h b/src/Aeternity/Address.h index 07e9b732939..3d4379a5bde 100644 --- a/src/Aeternity/Address.h +++ b/src/Aeternity/Address.h @@ -1,8 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. + +#pragma once #include #include @@ -32,8 +32,4 @@ class Address { static bool checkPayload(const std::string& payload); }; -inline bool operator==(const Address& lhs, const Address& rhs) { - return lhs.bytes == rhs.bytes; -} - } // namespace TW::Aeternity diff --git a/src/Aeternity/Entry.cpp b/src/Aeternity/Entry.cpp index 97d72906e63..1a8c25ad659 100644 --- a/src/Aeternity/Entry.cpp +++ b/src/Aeternity/Entry.cpp @@ -1,27 +1,27 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" #include "Signer.h" -using namespace TW::Aeternity; using namespace std; +namespace TW::Aeternity { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Aeternity diff --git a/src/Aeternity/Entry.h b/src/Aeternity/Entry.h index 5fe072fc4bb..36f63bff565 100644 --- a/src/Aeternity/Entry.h +++ b/src/Aeternity/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,11 +10,11 @@ namespace TW::Aeternity { /// Entry point for implementation of Aeternity coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Aeternity diff --git a/src/Aeternity/Identifiers.h b/src/Aeternity/Identifiers.h index d42acac5d5c..d0d8a3e5df5 100644 --- a/src/Aeternity/Identifiers.h +++ b/src/Aeternity/Identifiers.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Aeternity/Signer.cpp b/src/Aeternity/Signer.cpp index 7c9adf1fafa..ec22012eb88 100644 --- a/src/Aeternity/Signer.cpp +++ b/src/Aeternity/Signer.cpp @@ -1,11 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" -#include "Address.h" #include "Base58.h" #include "Base64.h" #include "HexCoding.h" @@ -14,9 +11,10 @@ #include using namespace TW; -using namespace TW::Aeternity; -Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { +namespace TW::Aeternity { + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); std::string sender_id = input.from_address(); std::string recipient_id = input.to_address(); @@ -29,7 +27,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { /// implementation copied from /// https://github.com/aeternity/aepp-sdk-go/blob/07aa8a77e5/aeternity/helpers.go#L367 -Proto::SigningOutput Signer::sign(const TW::PrivateKey &privateKey, Transaction &transaction) { +Proto::SigningOutput Signer::sign(const TW::PrivateKey& privateKey, Transaction& transaction) { auto txRlp = transaction.encode(); /// append networkId and txRaw @@ -37,7 +35,7 @@ Proto::SigningOutput Signer::sign(const TW::PrivateKey &privateKey, Transaction /// sign ed25519 auto sigRaw = privateKey.sign(msg, TWCurveED25519); - auto signature = Identifiers::prefixSignature + Base58::bitcoin.encodeCheck(sigRaw); + auto signature = Identifiers::prefixSignature + Base58::encodeCheck(sigRaw); /// encode the message using rlp auto rlpTxRaw = buildRlpTxRaw(txRlp, sigRaw); @@ -48,20 +46,23 @@ Proto::SigningOutput Signer::sign(const TW::PrivateKey &privateKey, Transaction return createProtoOutput(signature, signedEncodedTx); } -Data Signer::buildRlpTxRaw(Data& txRaw, Data& sigRaw) { - auto rlpTxRaw = Data(); - auto signaturesList = Data(); - append(signaturesList, Ethereum::RLP::encode(sigRaw)); +Data Signer::buildRlpTxRaw(const Data& txRaw, const Data& sigRaw) { + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); + + rlpList->add_items()->set_number_u64(Identifiers::objectTagSignedTransaction); + rlpList->add_items()->set_number_u64(Identifiers::rlpMessageVersion); + + // Append a list of signatures. + auto* signaturesList = rlpList->add_items()->mutable_list(); + signaturesList->add_items()->set_data(sigRaw.data(), sigRaw.size()); - append(rlpTxRaw, Ethereum::RLP::encode(Identifiers::objectTagSignedTransaction)); - append(rlpTxRaw, Ethereum::RLP::encode(Identifiers::rlpMessageVersion)); - append(rlpTxRaw, Ethereum::RLP::encodeList(signaturesList)); - append(rlpTxRaw, Ethereum::RLP::encode(txRaw)); + rlpList->add_items()->set_data(txRaw.data(), txRaw.size()); - return Ethereum::RLP::encodeList(rlpTxRaw); + return Ethereum::RLP::encode(input); } -Data Signer::buildMessageToSign(Data& txRaw) { +Data Signer::buildMessageToSign(const Data& txRaw) { auto data = Data(); Data bytes(Identifiers::networkId.begin(), Identifiers::networkId.end()); append(data, bytes); @@ -86,4 +87,6 @@ std::string Signer::encodeBase64WithChecksum(const std::string& prefix, const TW append(data, checksumPart); return prefix + TW::Base64::encode(data); -} \ No newline at end of file +} + +} // namespace TW::Aeternity diff --git a/src/Aeternity/Signer.h b/src/Aeternity/Signer.h index dd289e5487d..41aacb86b19 100644 --- a/src/Aeternity/Signer.h +++ b/src/Aeternity/Signer.h @@ -1,8 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. + +#pragma once #include "Transaction.h" #include "../proto/Aeternity.pb.h" @@ -11,19 +11,19 @@ namespace TW::Aeternity { class Signer { - public: +public: /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; /// Signs the given transaction. - static Proto::SigningOutput sign(const PrivateKey &privateKey, Transaction &transaction); + static Proto::SigningOutput sign(const PrivateKey& privateKey, Transaction& transaction); - private: +private: static const uint8_t checkSumSize = 4; - static Data buildRlpTxRaw(Data& txRaw, Data& sigRaw); + static Data buildRlpTxRaw(const Data& txRaw, const Data& sigRaw); - static Data buildMessageToSign(Data& txRaw); + static Data buildMessageToSign(const Data& txRaw); static Proto::SigningOutput createProtoOutput(std::string& signature, const std::string& signedTx); diff --git a/src/Aeternity/Transaction.cpp b/src/Aeternity/Transaction.cpp index c06d74d7efe..af3f8002928 100644 --- a/src/Aeternity/Transaction.cpp +++ b/src/Aeternity/Transaction.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Transaction.h" #include "Identifiers.h" @@ -10,24 +8,46 @@ #include #include -using namespace TW; -using namespace TW::Aeternity; +namespace TW::Aeternity { + +/// Aeternity network does not accept zero int values as rlp param, +/// instead empty byte array should be encoded +/// see https://forum.aeternity.com/t/invalid-tx-error-on-mainnet-goggle-says-it-looks-good/4118/5?u=defuera +EthereumRlp::Proto::RlpItem prepareSafeZero(const uint256_t& value) { + EthereumRlp::Proto::RlpItem item; + + if (value == 0) { + Data zeroValue{0}; + item.set_data(zeroValue.data(), zeroValue.size()); + } else { + auto valueData = store(value); + item.set_number_u256(valueData.data(), valueData.size()); + } + + return item; +} /// RLP returns a byte serialized representation Data Transaction::encode() { - auto encoded = Data(); - append(encoded, Ethereum::RLP::encode(Identifiers::objectTagSpendTransaction)); - append(encoded, Ethereum::RLP::encode(Identifiers::rlpMessageVersion)); - append(encoded, Ethereum::RLP::encode(buildTag(sender_id))); - append(encoded, Ethereum::RLP::encode(buildTag(recipient_id))); - append(encoded, encodeSafeZero(amount)); - append(encoded, encodeSafeZero(fee)); - append(encoded, encodeSafeZero(ttl)); - append(encoded, encodeSafeZero(nonce)); - append(encoded, Ethereum::RLP::encode(payload)); - - const Data& raw = Ethereum::RLP::encodeList(encoded); - return raw; + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); + + auto senderIdTag = buildTag(sender_id); + auto recipientIdTag = buildTag(recipient_id); + + rlpList->add_items()->set_number_u64(Identifiers::objectTagSpendTransaction); + rlpList->add_items()->set_number_u64(Identifiers::rlpMessageVersion); + rlpList->add_items()->set_data(senderIdTag.data(), senderIdTag.size()); + rlpList->add_items()->set_data(recipientIdTag.data(), recipientIdTag.size()); + + *rlpList->add_items() = prepareSafeZero(amount); + *rlpList->add_items() = prepareSafeZero(fee); + *rlpList->add_items() = prepareSafeZero(ttl); + *rlpList->add_items() = prepareSafeZero(nonce); + + rlpList->add_items()->set_data(payload.data(), payload.size()); + + return Ethereum::RLP::encode(input); } TW::Data Transaction::buildTag(const std::string& address) { @@ -35,14 +55,9 @@ TW::Data Transaction::buildTag(const std::string& address) { auto data = Data(); append(data, Identifiers::iDTagAccount); - append(data, Base58::bitcoin.decodeCheck(payload)); + append(data, Base58::decodeCheck(payload)); return data; } -TW::Data Transaction::encodeSafeZero(uint256_t value) { - if (value == 0) { - return Ethereum::RLP::encode(Data{0}); - } - return Ethereum::RLP::encode(value); -} +} // namespace TW::Aeternity diff --git a/src/Aeternity/Transaction.h b/src/Aeternity/Transaction.h index 6bace4629b8..4d938f45503 100644 --- a/src/Aeternity/Transaction.h +++ b/src/Aeternity/Transaction.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -52,13 +50,6 @@ class Transaction { //// buildIDTag assemble an id() object //// see https://github.com/aeternity/protocol/blob/epoch-v0.22.0/serializations.md#the-id-type static Data buildTag(const std::string& address); - - /// Awternity network does not accept zero int values as rlp param, - /// instead empty byte array should be encoded - /// see https://forum.aeternity.com/t/invalid-tx-error-on-mainnet-goggle-says-it-looks-good/4118/5?u=defuera - static Data encodeSafeZero(uint256_t value); - - }; } // namespace TW::Aeternity diff --git a/src/Aion/Address.cpp b/src/Aion/Address.cpp index 58872b004d1..4a357ef8a2e 100644 --- a/src/Aion/Address.cpp +++ b/src/Aion/Address.cpp @@ -1,14 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" -#include "../Hash.h" #include "../HexCoding.h" -using namespace TW::Aion; +namespace TW::Aion { bool Address::isValid(const std::string& string) { const auto data = parse_hex(string); @@ -40,3 +37,5 @@ Address::Address(const PublicKey& publicKey) { std::string Address::string() const { return "0x" + hex(bytes); } + +} // namespace TW::Aion diff --git a/src/Aion/Address.h b/src/Aion/Address.h index 0a1bc7f4d13..459de4faffb 100644 --- a/src/Aion/Address.h +++ b/src/Aion/Address.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -43,8 +41,4 @@ class Address { std::string string() const; }; -inline bool operator==(const Address& lhs, const Address& rhs) { - return lhs.bytes == rhs.bytes; -} - } // namespace TW::Aion diff --git a/src/Aion/Entry.cpp b/src/Aion/Entry.cpp index 66fe6240a60..9c979ddc9e0 100644 --- a/src/Aion/Entry.cpp +++ b/src/Aion/Entry.cpp @@ -1,33 +1,57 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" #include "Signer.h" +#include "proto/TransactionCompiler.pb.h" -using namespace TW::Aion; using namespace TW; using namespace std; +namespace TW::Aion { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { - const auto addr = Address(address); - return {addr.bytes.begin(), addr.bytes.end()}; +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto preImage = Signer::signaturePreimage(input); + auto preImageHash = Hash::blake2b(preImage, 32); + output.set_data(preImage.data(), preImage.size()); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + }); } + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + if (signatures.size() > 1 || publicKeys.size() > 1) { + output.set_error(Common::Proto::Error_no_support_n2n); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_no_support_n2n)); + return; + } + output = Signer::compile(signatures[0], publicKeys[0], input); + }); +} + +} // namespace TW::Aion diff --git a/src/Aion/Entry.h b/src/Aion/Entry.h index dcf26bda380..981042894ed 100644 --- a/src/Aion/Entry.h +++ b/src/Aion/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,12 +10,14 @@ namespace TW::Aion { /// Entry point for implementation of Aion coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Aion diff --git a/src/Aion/RLP.h b/src/Aion/RLP.h index b713b79c47a..643c7289d16 100644 --- a/src/Aion/RLP.h +++ b/src/Aion/RLP.h @@ -1,36 +1,40 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Transaction.h" #include "Ethereum/RLP.h" +#include "uint256.h" -#include #include #include #include namespace TW::Aion { -/// Aion's RLP encoging for long numbers +/// Aion's RLP encoding for long numbers /// https://github.com/aionnetwork/aion/issues/680 struct RLP { - static Data encodeLong(boost::multiprecision::uint128_t l) noexcept { + static EthereumRlp::Proto::RlpItem prepareLong(uint128_t l) { + EthereumRlp::Proto::RlpItem item; + if ((l & 0x00000000FFFFFFFFL) == l) { - return Ethereum::RLP::encode(static_cast(l)); - } - Data result(9); - result[0] = 0x80 + 8; - for (int i = 8; i > 0; i--) { - result[i] = (byte)(l & 0xFF); - l >>= 8; + auto u256 = store(l); + item.set_number_u256(u256.data(), u256.size()); + } else { + Data result(9); + result[0] = 0x80 + 8; + for (int i = 8; i > 0; i--) { + result[i] = (byte)(l & 0xFF); + l >>= 8; + } + item.set_raw_encoded(result.data(), result.size()); } - return result; + + return item; } }; diff --git a/src/Aion/Signer.cpp b/src/Aion/Signer.cpp index a4a3a4adf14..8fe86f3904f 100644 --- a/src/Aion/Signer.cpp +++ b/src/Aion/Signer.cpp @@ -1,30 +1,16 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" -#include "../Hash.h" -#include "../uint256.h" -#include - using namespace TW; -using namespace TW::Aion; -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - using boost::multiprecision::uint128_t; +namespace TW::Aion { +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto transaction = Transaction( - /* nonce: */ static_cast(load(input.nonce())), - /* gasPrice: */ static_cast(load(input.gas_price())), - /* gasLimit: */ static_cast(load(input.gas_limit())), - /* to: */ Address(input.to_address()), - /* amount: */ static_cast(load(input.amount())), - /* timestamp */ static_cast(input.timestamp()), - /* payload: */ Data(input.payload().begin(), input.payload().end())); + auto transaction = Signer::buildTransaction(input); Signer::sign(key, transaction); auto output = Proto::SigningOutput(); @@ -46,3 +32,41 @@ void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexce transaction.signature = result; } + +TW::Data Signer::signaturePreimage(const Proto::SigningInput& input) noexcept { + auto transaction = Signer::buildTransaction(input); + auto encoded = transaction.encode(); + return transaction.encode(); +} + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey, const Proto::SigningInput& input) noexcept { + auto transaction = Signer::buildTransaction(input); + + // Aion signature = pubKeyBytes + signatureBytes + Data result(publicKey.bytes.begin(), publicKey.bytes.end()); + result.insert(result.end(), signature.begin(), signature.end()); + + transaction.signature = result; + + auto output = Proto::SigningOutput(); + auto encoded = transaction.encode(); + output.set_encoded(encoded.data(), encoded.size()); + output.set_signature(transaction.signature.data(), transaction.signature.size()); + + return output; +} + +Transaction Signer::buildTransaction(const Proto::SigningInput& input) noexcept { + auto transaction = Transaction( + /* nonce: */ static_cast(load(input.nonce())), + /* gasPrice: */ static_cast(load(input.gas_price())), + /* gasLimit: */ static_cast(load(input.gas_limit())), + /* to: */ Address(input.to_address()), + /* amount: */ static_cast(load(input.amount())), + /* timestamp */ static_cast(input.timestamp()), + /* payload: */ Data(input.payload().begin(), input.payload().end())); + + return transaction; +} + +} // namespace TW::Aion diff --git a/src/Aion/Signer.h b/src/Aion/Signer.h index 68a95d79628..3bf90e3c4a3 100644 --- a/src/Aion/Signer.h +++ b/src/Aion/Signer.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/Aion.pb.h" @@ -28,6 +26,14 @@ class Signer { /// Signs the given transaction. static void sign(const PrivateKey& privateKey, Transaction& transaction) noexcept; + + /// Get transaction data to be signed + static TW::Data signaturePreimage(const Proto::SigningInput& input) noexcept; + static Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey, const Proto::SigningInput& input) noexcept; + + private: + /// Builds an Aion transaction from the given `Proto::SigningInput`. + static Transaction buildTransaction(const Proto::SigningInput& input) noexcept; }; } // namespace TW::Aion diff --git a/src/Aion/Transaction.cpp b/src/Aion/Transaction.cpp index 017dbe96896..c2c6eee46aa 100644 --- a/src/Aion/Transaction.cpp +++ b/src/Aion/Transaction.cpp @@ -1,29 +1,43 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "RLP.h" #include "Transaction.h" -#include "../Ethereum/RLP.h" + +#include "Ethereum/RLP.h" +#include "RLP.h" +#include "proto/EthereumRlp.pb.h" +#include "uint256.h" using namespace TW; -using namespace TW::Aion; -using boost::multiprecision::uint128_t; + +namespace TW::Aion { + +static const uint128_t gTransactionType = 1; Data Transaction::encode() const noexcept { - auto encoded = Data(); - append(encoded, Ethereum::RLP::encode(nonce)); - append(encoded, Ethereum::RLP::encode(to.bytes)); - append(encoded, Ethereum::RLP::encode(amount)); - append(encoded, Ethereum::RLP::encode(payload)); - append(encoded, Ethereum::RLP::encode(timestamp)); - append(encoded, RLP::encodeLong(gasLimit)); - append(encoded, RLP::encodeLong(gasPrice)); - append(encoded, RLP::encodeLong(uint128_t(1))); // Aion transaction type + auto nonceData = store(nonce); + auto amountData = store(amount); + auto timestampData = store(timestamp); + + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); + + rlpList->add_items()->set_number_u256(nonceData.data(), nonceData.size()); + rlpList->add_items()->set_data(to.bytes.data(), to.bytes.size()); + rlpList->add_items()->set_number_u256(amountData.data(), amountData.size()); + rlpList->add_items()->set_data(payload.data(), payload.size()); + rlpList->add_items()->set_number_u256(timestampData.data(), timestampData.size()); + + *rlpList->add_items() = RLP::prepareLong(gasLimit); + *rlpList->add_items() = RLP::prepareLong(gasPrice); + *rlpList->add_items() = RLP::prepareLong(gTransactionType); + if (!signature.empty()) { - append(encoded, Ethereum::RLP::encode(signature)); + rlpList->add_items()->set_data(signature.data(), signature.size()); } - return Ethereum::RLP::encodeList(encoded); + + return Ethereum::RLP::encode(input); } + +} // namespace TW::Aion diff --git a/src/Aion/Transaction.h b/src/Aion/Transaction.h index dc975dcaac9..257ddb68736 100644 --- a/src/Aion/Transaction.h +++ b/src/Aion/Transaction.h @@ -1,21 +1,17 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Address.h" -#include "../Data.h" - -#include +#include "Data.h" +#include "uint256.h" namespace TW::Aion { class Transaction { public: - using uint128_t = boost::multiprecision::uint128_t; uint128_t nonce; uint128_t gasPrice; diff --git a/src/Algorand/Address.cpp b/src/Algorand/Address.cpp index 3e2b8d3b1fa..ba6b6528639 100644 --- a/src/Algorand/Address.cpp +++ b/src/Algorand/Address.cpp @@ -1,17 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" -#include "../HexCoding.h" -#include "../Hash.h" #include "../Base32.h" #include -using namespace TW::Algorand; +namespace TW::Algorand { bool Address::isValid(const std::string& string) { if (string.size() != encodedSize) { @@ -63,3 +59,5 @@ std::string Address::string() const { std::string encoded = Base32::encode(data); return encoded; } + +} // namespace TW::Algorand diff --git a/src/Algorand/Address.h b/src/Algorand/Address.h index 93f82788676..5215b7bb6b7 100644 --- a/src/Algorand/Address.h +++ b/src/Algorand/Address.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include @@ -37,8 +35,4 @@ class Address { std::string string() const; }; -inline bool operator==(const Address& lhs, const Address& rhs) { - return lhs.bytes == rhs.bytes; -} - } // namespace TW::Algorand diff --git a/src/Algorand/AssetTransfer.cpp b/src/Algorand/AssetTransfer.cpp index eecb4cf8335..f8644988cbc 100644 --- a/src/Algorand/AssetTransfer.cpp +++ b/src/Algorand/AssetTransfer.cpp @@ -1,14 +1,11 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "AssetTransfer.h" #include "BinaryCoding.h" -using namespace TW; -using namespace TW::Algorand; +namespace TW::Algorand { Data AssetTransfer::serialize() const { Data data; @@ -59,3 +56,5 @@ Data AssetTransfer::serialize() const { return data; } + +} // namespace TW::Algorand diff --git a/src/Algorand/AssetTransfer.h b/src/Algorand/AssetTransfer.h index cdc5bab9c2b..3de0f22bb82 100644 --- a/src/Algorand/AssetTransfer.h +++ b/src/Algorand/AssetTransfer.h @@ -1,12 +1,12 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. + +#pragma once #include "Address.h" #include "BaseTransaction.h" -#include "../Data.h" +#include "Data.h" #include "../proto/Algorand.pb.h" namespace TW::Algorand { diff --git a/src/Algorand/BaseTransaction.h b/src/Algorand/BaseTransaction.h index 1db984c5a3d..4938e43bb2a 100644 --- a/src/Algorand/BaseTransaction.h +++ b/src/Algorand/BaseTransaction.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,7 +10,8 @@ namespace TW::Algorand { class BaseTransaction { - public: +public: + virtual ~BaseTransaction() noexcept = default; virtual Data serialize() const = 0; virtual Data serialize(const Data& signature) const { /* Algorand transaction and signature are encoded with msgpack: diff --git a/src/Algorand/BinaryCoding.h b/src/Algorand/BinaryCoding.h index ee4220d46b3..2b7ca30fd22 100644 --- a/src/Algorand/BinaryCoding.h +++ b/src/Algorand/BinaryCoding.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Algorand/Entry.cpp b/src/Algorand/Entry.cpp index 29f9809955f..14a1320fede 100644 --- a/src/Algorand/Entry.cpp +++ b/src/Algorand/Entry.cpp @@ -1,31 +1,57 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" #include "Signer.h" +#include "proto/TransactionCompiler.pb.h" -using namespace TW::Algorand; -using namespace std; +namespace TW::Algorand { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + // Algo has no preImageHash + auto preImage = Signer::signaturePreimage(input); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + if (signatures.size() > 1 || publicKeys.size() > 1) { + output.set_error(Common::Proto::Error_no_support_n2n); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_no_support_n2n)); + return; + } + output = Signer::compile(signatures[0], publicKeys[0], input); + }); +} + +} // namespace TW::Algorand diff --git a/src/Algorand/Entry.h b/src/Algorand/Entry.h index 73e79334356..cc47a32118e 100644 --- a/src/Algorand/Entry.h +++ b/src/Algorand/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,13 +10,16 @@ namespace TW::Algorand { /// Entry point for implementation of Algorand coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Algorand diff --git a/src/Algorand/OptInAssetTransaction.cpp b/src/Algorand/OptInAssetTransaction.cpp index f83df502f78..7246c4e8c54 100644 --- a/src/Algorand/OptInAssetTransaction.cpp +++ b/src/Algorand/OptInAssetTransaction.cpp @@ -1,14 +1,11 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "OptInAssetTransaction.h" #include "BinaryCoding.h" -using namespace TW; -using namespace TW::Algorand; +namespace TW::Algorand { Data OptInAssetTransaction::serialize() const { Data data; @@ -56,3 +53,5 @@ Data OptInAssetTransaction::serialize() const { return data; } + +} // namespace TW::Algorand diff --git a/src/Algorand/OptInAssetTransaction.h b/src/Algorand/OptInAssetTransaction.h index 2f9b7c55394..ca98873fa71 100644 --- a/src/Algorand/OptInAssetTransaction.h +++ b/src/Algorand/OptInAssetTransaction.h @@ -1,12 +1,14 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include #include "Address.h" #include "BaseTransaction.h" -#include "../Data.h" +#include "Data.h" #include "../proto/Algorand.pb.h" namespace TW::Algorand { @@ -29,7 +31,7 @@ class OptInAssetTransaction: public BaseTransaction { : address(address), fee(fee) , assetId(assetId), firstRound(firstRound) , lastRound(lastRound), note(note) - , type(type), genesisId(genesisId) + , type(std::move(type)), genesisId(genesisId) , genesisHash(genesisHash) {} public: diff --git a/src/Algorand/Signer.cpp b/src/Algorand/Signer.cpp index f979b24fdf9..71f44b6fcce 100644 --- a/src/Algorand/Signer.cpp +++ b/src/Algorand/Signer.cpp @@ -1,28 +1,102 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "Address.h" #include "BaseTransaction.h" +#include "Base64.h" #include "../HexCoding.h" #include -using namespace TW; -using namespace TW::Algorand; +namespace TW::Algorand { const Data TRANSACTION_TAG = {84, 88}; const std::string TRANSACTION_PAY = "pay"; const std::string ASSET_TRANSACTION = "axfer"; -Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto protoOutput = Proto::SigningOutput(); auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); auto pubkey = key.getPublicKey(TWPublicKeyTypeED25519); - auto from = Address(pubkey); + + auto preImageData = Signer::preImage(pubkey, input); + auto signature = key.sign(preImageData, TWCurveED25519); + return Signer::encodeTransaction(signature, pubkey, input); +} + +std::string Signer::signJSON(const std::string& json, const Data& key) { + auto input = Proto::SigningInput(); + google::protobuf::util::JsonStringToMessage(json, &input); + input.set_private_key(key.data(), key.size()); + return hex(Signer::sign(input).encoded()); +} + +Data Signer::sign(const PrivateKey& privateKey, const BaseTransaction& transaction) noexcept { + Data data; + append(data, TRANSACTION_TAG); + append(data, transaction.serialize()); + auto signature = privateKey.sign(data, TWCurveED25519); + return {signature.begin(), signature.end()}; +} + +TW::Data Signer::signaturePreimage(const Proto::SigningInput& input) noexcept { + auto pubKey = input.public_key(); + return Signer::preImage(PublicKey(Data(pubKey.begin(), pubKey.end()), TWPublicKeyTypeED25519), input); +} + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey, const Proto::SigningInput& input) noexcept { + return Signer::encodeTransaction(signature, publicKey, input); +} + +TW::Data Signer::preImage(const TW::PublicKey& pubKey, const Proto::SigningInput& input) noexcept { + auto from = Address(pubKey); + auto firstRound = input.first_round(); + auto lastRound = input.last_round(); + auto fee = input.fee(); + + auto note = Data(input.note().begin(), input.note().end()); + auto genesisId = input.genesis_id(); + auto genesisHash = Data(input.genesis_hash().begin(), input.genesis_hash().end()); + + TW::Data transactionData; + if (input.has_transfer()) { + auto message = input.transfer(); + auto to = Address(message.to_address()); + + auto transaction = Transfer(from, to, fee, message.amount(), firstRound, + lastRound, note, TRANSACTION_PAY, genesisId, genesisHash); + transactionData = transaction.serialize(); + } else if (input.has_asset_transfer()) { + auto message = input.asset_transfer(); + auto to = Address(message.to_address()); + + auto transaction = + AssetTransfer(from, to, fee, message.amount(), + message.asset_id(), firstRound, lastRound, note, + ASSET_TRANSACTION, genesisId, genesisHash); + transactionData = transaction.serialize(); + } else if (input.has_asset_opt_in()) { + auto message = input.asset_opt_in(); + auto transaction = OptInAssetTransaction(from, fee, message.asset_id(), + firstRound, lastRound, note, + ASSET_TRANSACTION, genesisId, genesisHash); + transactionData = transaction.serialize(); + } else { + return {Data{}}; + } + + Data data; + append(data, TRANSACTION_TAG); + append(data, transactionData); + return data; +} + +Proto::SigningOutput Signer::encodeTransaction(const Data& signature, const TW::PublicKey& pubKey, const Proto::SigningInput& input) noexcept { + auto protoOutput = Proto::SigningOutput(); + + auto from = Address(pubKey); auto firstRound = input.first_round(); auto lastRound = input.last_round(); auto fee = input.fee(); @@ -35,46 +109,35 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { auto to = Address(message.to_address()); auto transaction = Transfer(from, to, fee, message.amount(), firstRound, - lastRound, note, TRANSACTION_PAY, genesisId, genesisHash); - auto signature = sign(key, transaction); + lastRound, note, TRANSACTION_PAY, genesisId, genesisHash); + auto serialized = transaction.BaseTransaction::serialize(signature); protoOutput.set_encoded(serialized.data(), serialized.size()); + protoOutput.set_signature(Base64::encode(signature)); } else if (input.has_asset_transfer()) { auto message = input.asset_transfer(); auto to = Address(message.to_address()); auto transaction = AssetTransfer(from, to, fee, message.amount(), - message.asset_id(), firstRound, lastRound, note, - ASSET_TRANSACTION,genesisId, genesisHash); - auto signature = sign(key, transaction); + message.asset_id(), firstRound, lastRound, note, + ASSET_TRANSACTION, genesisId, genesisHash); + auto serialized = transaction.BaseTransaction::serialize(signature); protoOutput.set_encoded(serialized.data(), serialized.size()); + protoOutput.set_signature(Base64::encode(signature)); } else if (input.has_asset_opt_in()) { auto message = input.asset_opt_in(); auto transaction = OptInAssetTransaction(from, fee, message.asset_id(), firstRound, lastRound, note, - ASSET_TRANSACTION,genesisId, genesisHash); - auto signature = sign(key, transaction); + ASSET_TRANSACTION, genesisId, genesisHash); + auto serialized = transaction.BaseTransaction::serialize(signature); protoOutput.set_encoded(serialized.data(), serialized.size()); + protoOutput.set_signature(Base64::encode(signature)); } - return protoOutput; } -std::string Signer::signJSON(const std::string& json, const Data& key) { - auto input = Proto::SigningInput(); - google::protobuf::util::JsonStringToMessage(json, &input); - input.set_private_key(key.data(), key.size()); - return hex(Signer::sign(input).encoded()); -} - -Data Signer::sign(const PrivateKey& privateKey, const BaseTransaction& transaction) noexcept { - Data data; - append(data, TRANSACTION_TAG); - append(data, transaction.serialize()); - auto signature = privateKey.sign(data, TWCurveED25519); - return Data(signature.begin(), signature.end()); -} +} // namespace TW::Algorand diff --git a/src/Algorand/Signer.h b/src/Algorand/Signer.h index 7942022585c..2c0514328c7 100644 --- a/src/Algorand/Signer.h +++ b/src/Algorand/Signer.h @@ -1,16 +1,15 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "AssetTransfer.h" +#include "proto/Common.pb.h" #include "OptInAssetTransaction.h" #include "Transfer.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" namespace TW::Algorand { @@ -28,6 +27,14 @@ class Signer { /// Signs the given transaction. static Data sign(const PrivateKey& privateKey, const BaseTransaction& transaction) noexcept; + + /// Get transaction data to be signed + static TW::Data signaturePreimage(const Proto::SigningInput& input) noexcept; + static Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey, const Proto::SigningInput& input) noexcept; + + private: + static TW::Data preImage(const TW::PublicKey& pubKey, const Proto::SigningInput& input) noexcept; + static Proto::SigningOutput encodeTransaction(const Data& signature, const TW::PublicKey& pubKey, const Proto::SigningInput& input) noexcept; }; } // namespace TW::Algorand diff --git a/src/Algorand/Transfer.cpp b/src/Algorand/Transfer.cpp index 325026ea69b..645463679c0 100644 --- a/src/Algorand/Transfer.cpp +++ b/src/Algorand/Transfer.cpp @@ -1,15 +1,11 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Transfer.h" #include "BinaryCoding.h" -#include "../HexCoding.h" -using namespace TW; -using namespace TW::Algorand; +namespace TW::Algorand { Data Transfer::serialize() const { /* Algorand transaction is encoded with msgpack @@ -76,3 +72,5 @@ Data Transfer::serialize() const { encodeString(type, data); return data; } + +} // namespace TW::Algorand diff --git a/src/Algorand/Transfer.h b/src/Algorand/Transfer.h index 29b4f6169e5..8ca7bbcad59 100644 --- a/src/Algorand/Transfer.h +++ b/src/Algorand/Transfer.h @@ -1,14 +1,14 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once +#include + #include "Address.h" #include "BaseTransaction.h" -#include "../Data.h" +#include "Data.h" #include "../proto/Algorand.pb.h" namespace TW::Algorand { @@ -32,7 +32,7 @@ class Transfer : public BaseTransaction { : from(from) , to(to) , fee(fee), amount(amount) , firstRound(firstRound), lastRound(lastRound) - , note(note), type(type) + , note(note), type(std::move(type)) , genesisId(genesisIdg), genesisHash(genesisHash) {} public: diff --git a/src/AnyAddress.cpp b/src/AnyAddress.cpp new file mode 100644 index 00000000000..ab4267ac388 --- /dev/null +++ b/src/AnyAddress.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "AnyAddress.h" + +#include +#include "Coin.h" + +namespace TW { + +Data AnyAddress::getData() const { + return TW::addressToData(coin, address); +} + +AnyAddress* AnyAddress::createAddress(const std::string& address, enum TWCoinType coin, const PrefixVariant& prefix) { + const bool hasPrefix = !std::holds_alternative(prefix); + auto normalized = hasPrefix ? TW::normalizeAddress(coin, address, prefix) : TW::normalizeAddress(coin, address); + if (normalized.empty()) { + return nullptr; + } + + return new AnyAddress{.address = std::move(normalized), .coin = coin}; +} + +AnyAddress* AnyAddress::createAddress(const PublicKey& publicKey, enum TWCoinType coin, TWDerivation derivation, const PrefixVariant& prefix) { + const auto derivedAddress = TW::deriveAddress(coin, publicKey, derivation, prefix); + return new AnyAddress{.address = std::move(derivedAddress), .coin = coin}; +} + +} // namespace TW diff --git a/src/AnyAddress.h b/src/AnyAddress.h new file mode 100644 index 00000000000..f3e7434272a --- /dev/null +++ b/src/AnyAddress.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "PublicKey.h" + +#include +#include +#include +#include +#include + +namespace TW { + +class AnyAddress { +public: + std::string address; + + enum TWCoinType coin; + + // Create address from string address and optional prefix; also normalizes the address. + static AnyAddress* createAddress(const std::string& address, enum TWCoinType coin, const PrefixVariant& prefix = std::monostate()); + // Create address from private key, with optional non-standard derivation and prefix + static AnyAddress* createAddress(const PublicKey& publicKey, enum TWCoinType coin, TWDerivation derivation = TWDerivationDefault, const PrefixVariant& prefix = std::monostate()); + + Data getData() const; +}; + +inline bool operator==(const AnyAddress& lhs, const AnyAddress& rhs) { + return lhs.address == rhs.address && lhs.coin == rhs.coin; +} + +} // namespace TW + +/// Wrapper for C interface. +struct TWAnyAddress { + // Pointer to the underlying implementation + TW::AnyAddress* impl; +}; diff --git a/src/Aptos/Entry.h b/src/Aptos/Entry.h new file mode 100644 index 00000000000..24ab3d38afe --- /dev/null +++ b/src/Aptos/Entry.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "rust/RustCoinEntry.h" + +namespace TW::Aptos { + +/// Entry point for implementation of Aptos coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final : public Rust::RustCoinEntry { +}; + +} // namespace TW::Aptos diff --git a/src/AsnParser.h b/src/AsnParser.h new file mode 100644 index 00000000000..a6d7d7e8443 --- /dev/null +++ b/src/AsnParser.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "rust/bindgen/WalletCoreRSBindgen.h" +#include "rust/Wrapper.h" + +namespace TW::ASN { + +struct AsnParser { + static std::optional ecdsa_signature_from_der(const Data& derEncoded) { + Rust::CByteArrayResultWrapper res = Rust::ecdsa_signature_from_asn_der(derEncoded.data(), derEncoded.size()); + if (!res.isOk()) { + return std::nullopt; + } + return res.unwrap().data; + } +}; + +} // namespace TW::ASN diff --git a/src/Base32.h b/src/Base32.h index 7119a9944be..595b36591a8 100644 --- a/src/Base32.h +++ b/src/Base32.h @@ -1,14 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Data.h" - -#include +#include "rust/bindgen/WalletCoreRSBindgen.h" +#include "rust/Wrapper.h" #include @@ -17,41 +15,28 @@ namespace TW::Base32 { /// Decode Base32 string, return bytes as Data /// alphabet: Optional alphabet, if missing, default ALPHABET_RFC4648 inline bool decode(const std::string& encoded_in, Data& decoded_out, const char* alphabet_in = nullptr) { - size_t inLen = encoded_in.size(); - // obtain output length first - size_t outLen = base32_decoded_length(inLen); - uint8_t buf[outLen]; - if (alphabet_in == nullptr) { - alphabet_in = BASE32_ALPHABET_RFC4648; + if (encoded_in.empty()) { + return true; } - // perform the base32 decode - uint8_t* retval = base32_decode(encoded_in.data(), inLen, buf, outLen, alphabet_in); - if (retval == nullptr) { - return false; + Rust::CByteArrayResultWrapper res = Rust::decode_base32(encoded_in.c_str(), alphabet_in, false); + if (res.isOk()) { + decoded_out = res.unwrap().data; + return true; } - decoded_out.assign(buf, buf + outLen); - return true; + return false; } + /// Encode bytes in Data to Base32 string /// alphabet: Optional alphabet, if missing, default ALPHABET_RFC4648 -inline std::string encode(const Data& val, const char* alphabet = nullptr) { - size_t inLen = val.size(); - // obtain output length first, reserve for terminator - size_t outLen = base32_encoded_length(inLen) + 1; - char buf[outLen]; - if (alphabet == nullptr) { - alphabet = BASE32_ALPHABET_RFC4648; - } - // perform the base32 encode - char* retval = base32_encode(val.data(), inLen, buf, outLen, alphabet); - if (retval == nullptr) { - // return empty string if failed - return std::string(); +inline std::string encode(const Data &val, const char *alphabet = nullptr, bool padding = false) { + auto res = Rust::encode_base32(val.data(), val.size(), alphabet, padding); + if (res.code != Rust::OK_CODE) { + return {}; } - // make sure there is a terminator ath the end - buf[outLen - 1] = '\0'; - return std::string(buf); + std::string encoded_str(res.result); + Rust::free_string(res.result); + return encoded_str; } } // namespace TW::Base32 diff --git a/src/Base58.cpp b/src/Base58.cpp deleted file mode 100644 index 61ed8d4600b..00000000000 --- a/src/Base58.cpp +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright © 2014-2018 The Bitcoin Core developers -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Base58.h" - -#include "Hash.h" - -#include -#include -#include -#include - -using namespace TW; - -// clang-format off - -static const std::array bitcoinDigits = { - '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', - 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', - 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' -}; - -static const std::array bitcoinCharacterMap = { - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8,-1,-1,-1,-1,-1,-1, - -1, 9,10,11,12,13,14,15,16,-1,17,18,19,20,21,-1, - 22,23,24,25,26,27,28,29,30,31,32,-1,-1,-1,-1,-1, - -1,33,34,35,36,37,38,39,40,41,42,43,-1,44,45,46, - 47,48,49,50,51,52,53,54,55,56,57,-1,-1,-1,-1,-1, -}; - -static const std::array rippleDigits = { - 'r', 'p', 's', 'h', 'n', 'a', 'f', '3', '9', 'w', 'B', 'U', 'D', 'N', 'E', - 'G', 'H', 'J', 'K', 'L', 'M', '4', 'P', 'Q', 'R', 'S', 'T', '7', 'V', 'W', - 'X', 'Y', 'Z', '2', 'b', 'c', 'd', 'e', 'C', 'g', '6', '5', 'j', 'k', 'm', - '8', 'o', 'F', 'q', 'i', '1', 't', 'u', 'v', 'A', 'x', 'y', 'z' -}; - -static const std::array rippleCharacterMap = { - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, - -1,50,33,7,21,41,40,27,45,8,-1,-1,-1,-1,-1,-1, - -1,54,10,38,12,14,47,15,16,-1,17,18,19,20,13,-1, - 22,23,24,25,26,11,28,29,30,31,32,-1,-1,-1,-1,-1, - -1,5,34,35,36,37,6,39,3,49,42,43,-1,44,4,46, - 1,48,0,2,51,52,53,9,55,56,57,-1,-1,-1,-1,-1, -}; - -// clang-format on - -Base58 Base58::bitcoin = Base58(bitcoinDigits, bitcoinCharacterMap); - -Base58 Base58::ripple = Base58(rippleDigits, rippleCharacterMap); - -Data Base58::decodeCheck(const char* begin, const char* end, Hash::Hasher hasher) const { - auto result = decode(begin, end); - if (result.size() < 4) { - return {}; - } - - // re-calculate the checksum, ensure it matches the included 4-byte checksum - auto hash = Hash::hash(hasher, result.data(), result.size() - 4); - if (!std::equal(hash.begin(), hash.begin() + 4, result.end() - 4)) { - return {}; - } - - return Data(result.begin(), result.end() - 4); -} - -Data Base58::decode(const char* begin, const char* end) const { - const auto* it = begin; - - // Skip leading spaces. - it = std::find_if_not(it, end, [](char c) { return std::isspace(c);}); - - // Skip and count leading zeros. - std::size_t zeroes = 0; - std::size_t length = 0; - while (it != end && *it == digits[0]) { - zeroes += 1; - it += 1; - } - - // Allocate enough space in big-endian base256 representation. - std::size_t base258Size = (end - it) * 733 / 1000 + 1; // log(58) / log(256), rounded up. - Data b256(base258Size); - - // Process the characters. - while (it != end && !std::isspace(*it)) { - if (static_cast(*it) >= 128) { - // Invalid b58 character - return {}; - } - - // Decode base58 character - int carry = characterMap[static_cast(*it)]; - if (carry == -1) { - // Invalid b58 character - return {}; - } - - std::size_t i = 0; - for (auto b256it = b256.rbegin(); (carry != 0 || i < length) && (b256it != b256.rend()); - ++b256it, ++i) { - carry += 58 * (*b256it); - *b256it = static_cast(carry % 256); - carry /= 256; - } - assert(carry == 0); - length = i; - it += 1; - } - - // Skip trailing spaces. - it = std::find_if_not(it, end, [](char c) { return std::isspace(c);}); - if (it != end) { - // Extra charaters at the end - return {}; - } - - // Skip leading zeroes in b256. - auto b256it = b256.begin() + (base258Size - length); - while (b256it != b256.end() && *b256it == 0) { - b256it++; - } - - // Copy result into output vector. - Data result; - result.reserve(zeroes + (b256.end() - b256it)); - result.assign(zeroes, 0x00); - std::copy(b256it, b256.end(), std::back_inserter(result)); - - return result; -} - -std::string Base58::encodeCheck(const byte* begin, const byte* end, Hash::Hasher hasher) const { - // add 4-byte hash check to the end - Data dataWithCheck(begin, end); - auto hash = Hash::hash(hasher, begin, end - begin); - dataWithCheck.insert(dataWithCheck.end(), hash.begin(), hash.begin() + 4); - return encode(dataWithCheck); -} - -std::string Base58::encode(const byte* begin, const byte* end) const { - // Skip & count leading zeroes. - int zeroes = 0; - int length = 0; - while (begin != end && *begin == 0) { - begin += 1; - zeroes += 1; - } - - // Allocate enough space in big-endian base58 representation. - auto base58Size = (end - begin) * 138 / 100 + 1; // log(256) / log(58), rounded up. - Data b58(base58Size); - - while (begin != end) { - int carry = *begin; - int i = 0; - // Apply "b58 = b58 * 256 + ch". - for (auto b58it = b58.rbegin(); (carry != 0 || i < length) && (b58it != b58.rend()); - b58it++, i++) { - carry += 256 * (*b58it); - *b58it = carry % 58; - carry /= 58; - } - - assert(carry == 0); - length = i; - begin += 1; - } - - // Skip leading zeroes in base58 result. - auto it = b58.begin() + (base58Size - length); - while (it != b58.end() && *it == 0) { - it++; - } - - // Translate the result into a string. - std::string str; - str.reserve(zeroes + (b58.end() - it)); - str.assign(zeroes, digits[0]); - while (it != b58.end()) { - str += digits[*it]; - it += 1; - } - return str; -} diff --git a/src/Base58.h b/src/Base58.h index d5e231d307d..5b36feba2e9 100644 --- a/src/Base58.h +++ b/src/Base58.h @@ -1,71 +1,54 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Data.h" #include "Hash.h" +#include "rust/bindgen/WalletCoreRSBindgen.h" +#include "rust/Wrapper.h" -#include #include -namespace TW { - -class Base58 { - public: - /// Base58 coder with Bitcoin character map. - static Base58 bitcoin; - - /// Base58 coder with Ripple character map. - static Base58 ripple; - - public: - /// Ordered list of valid characters. - const std::array digits; - - /// Maps characters to base58 values. - const std::array characterMap; - - /// Initializes a Base58 class with custom digit mapping. - Base58(const std::array& digits, const std::array& characterMap) - : digits(digits), characterMap(characterMap) {} - - /// Decodes a base 58 string verifying the checksum, returns empty on failure. - Data decodeCheck(const std::string& string, Hash::Hasher hasher = Hash::HasherSha256d) const { - return decodeCheck(string.data(), string.data() + string.size(), hasher); +namespace TW::Base58 { + /// Decodes a base 58 string into `result`, returns `false` on failure. + static inline Data decode(const std::string& string, Rust::Base58Alphabet alphabet = Rust::Base58Alphabet::Bitcoin) { + if (string.empty()) { + return {}; + } + Rust::CByteArrayResultWrapper res = Rust::decode_base58(string.c_str(), alphabet); + return res.unwrap_or_default().data; } - /// Decodes a base 58 string verifying the checksum, returns empty on failure. - Data decodeCheck(const char* begin, const char* end, Hash::Hasher hasher = Hash::HasherSha256d) const; + static inline Data decodeCheck(const std::string& string, Rust::Base58Alphabet alphabet = Rust::Base58Alphabet::Bitcoin, Hash::Hasher hasher = Hash::HasherSha256d) { + auto result = decode(string, alphabet); + if (result.size() < 4) { + return {}; + } - /// Decodes a base 58 string into `result`, returns `false` on failure. - Data decode(const std::string& string) const { - return decode(string.data(), string.data() + string.size()); - } + // re-calculate the checksum, ensure it matches the included 4-byte checksum + auto hash = Hash::hash(hasher, result.data(), result.size() - 4); + if (!std::equal(hash.begin(), hash.begin() + 4, result.end() - 4)) { + return {}; + } - /// Decodes a base 58 string into `result`, returns `false` on failure. - Data decode(const char* begin, const char* end) const; + return Data(result.begin(), result.end() - 4); + } - /// Encodes data as a base 58 string with a checksum. template - std::string encodeCheck(const T& data, Hash::Hasher hasher = Hash::HasherSha256d) const { - return encodeCheck(data.data(), data.data() + data.size(), hasher); + static inline std::string encode(const T& data, Rust::Base58Alphabet alphabet = Rust::Base58Alphabet::Bitcoin) { + auto encoded = encode_base58(data.data(), data.size(), alphabet); + std::string encoded_str(encoded); + Rust::free_string(encoded); + return encoded_str; } - /// Encodes data as a base 58 string with a checksum. - std::string encodeCheck(const byte* pbegin, const byte* pend, Hash::Hasher hasher = Hash::HasherSha256d) const; - - /// Encodes data as a base 58 string. template - std::string encode(const T& data) const { - return encode(data.data(), data.data() + data.size()); + static inline std::string encodeCheck(const T& data, Rust::Base58Alphabet alphabet = Rust::Base58Alphabet::Bitcoin, Hash::Hasher hasher = Hash::HasherSha256d) { + auto hash = Hash::hash(hasher, data); + Data toBeEncoded(std::begin(data), std::end(data)); + toBeEncoded.insert(toBeEncoded.end(), hash.begin(), hash.begin() + 4); + return encode(toBeEncoded, alphabet); } - - /// Encodes data as a base 58 string. - std::string encode(const byte* pbegin, const byte* pend) const; -}; - -} // namespace TW +} diff --git a/src/Base58Address.h b/src/Base58Address.h index ee66c495a93..1d2092c495e 100644 --- a/src/Base58Address.h +++ b/src/Base58Address.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -33,7 +31,7 @@ class Base58Address { /// Determines whether a string makes a valid address. static bool isValid(const std::string& string) { - const auto decoded = Base58::bitcoin.decodeCheck(string); + const auto decoded = Base58::decodeCheck(string); if (decoded.size() != Base58Address::size) { return false; } @@ -43,7 +41,7 @@ class Base58Address { /// Determines whether a string makes a valid address, and the prefix is /// within the valid set. static bool isValid(const std::string& string, const std::vector& validPrefixes) { - const auto decoded = Base58::bitcoin.decodeCheck(string); + const auto decoded = Base58::decodeCheck(string); if (decoded.size() != Base58Address::size) { return false; } @@ -59,7 +57,7 @@ class Base58Address { /// Initializes an address with a string representation. explicit Base58Address(const std::string& string) { - const auto decoded = Base58::bitcoin.decodeCheck(string); + const auto decoded = Base58::decodeCheck(string); if (decoded.size() != Base58Address::size) { throw std::invalid_argument("Invalid address string"); } @@ -86,7 +84,7 @@ class Base58Address { /// Returns a string representation of the address. std::string string() const { - return Base58::bitcoin.encodeCheck(bytes); + return Base58::encodeCheck(bytes); } }; diff --git a/src/Base64.cpp b/src/Base64.cpp index 6f5aeb44969..dfcfa1353aa 100644 --- a/src/Base64.cpp +++ b/src/Base64.cpp @@ -1,86 +1,67 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Base64.h" +#include "rust/bindgen/WalletCoreRSBindgen.h" +#include "rust/Wrapper.h" -#include -#include -#include -#include +namespace TW::Base64::internal { + +std::string encode(const Data& val, bool is_url) { + Rust::CStringWrapper res = Rust::encode_base64(val.data(), val.size(), is_url); + return res.str; +} + +Data decode(const std::string& val, bool is_url) { + if (val.empty()) { + return {}; + } + Rust::CByteArrayResultWrapper res = Rust::decode_base64(val.c_str(), is_url); + return res.unwrap_or_default().data; +} + +} // namespace TW::Base64::internal namespace TW::Base64 { using namespace TW; using namespace std; -Data decode(const string& val) { - using namespace boost::archive::iterators; - using It = transform_width, 8, 6>; - return boost::algorithm::trim_right_copy_if(Data(It(begin(val)), It(end(val))), - [](char c) { return c == '\0'; }); -} - -string encode(const Data& val) { - using namespace boost::archive::iterators; - using It = base64_from_binary>; - auto encoded = string(It(begin(val)), It(end(val))); - return encoded.append((3 - val.size() % 3) % 3, '='); -} +static bool isBase64Any(const string& val, const char* alphabet) { + if (val.length() % 4 != 0) { + return false; + } + size_t first_non_alphabet = val.find_first_not_of(alphabet); + size_t first_non_padding = val.find_first_not_of("=", first_non_alphabet); -/// Convert from Base64Url format to regular -void convertFromBase64Url(string& b) { - // '-' and '_' (Base64URL format) are changed to '+' and '/' - // in-place replace - size_t n = b.length(); - char* start = b.data(); - char* end = start + n; - for (auto* p = start; p < end; ++p) { - if (*p == '-') { - *p = '+'; - } else if (*p == '_') { - *p = '/'; - } + if (first_non_alphabet == std::string::npos || + (first_non_padding == std::string::npos && (val.length() - first_non_alphabet < 3))) { + return true; } + return false; } -/// Convert from regular format to Base64Url -void convertToBase64Url(string& b) { - // '+' and '/' are changed to '-' and '_' (Base64URL format) - // in-place replace - size_t n = b.length(); - char* start = b.data(); - char* end = start + n; - for (auto* p = start; p < end; ++p) { - if (*p == '+') { - *p = '-'; - } else if (*p == '/') { - *p = '_'; - } - } +bool isBase64orBase64Url(const string& val) { + const char* base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const char* base64_url_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + return isBase64Any(val, base64_chars) || isBase64Any(val, base64_url_chars); } Data decodeBase64Url(const string& val) { - Data bytes; - try { - return decode(val); - } catch (const exception& ex) { - // 2nd try: Base64URL format (replaced by '-' and '_' by '+' and '/' ) - string base64Url = val; - convertFromBase64Url(base64Url); - return decode(base64Url); - } + return internal::decode(val, true); } string encodeBase64Url(const Data& val) { - using namespace boost::archive::iterators; - using It = base64_from_binary>; - auto encoded = string(It(begin(val)), It(end(val))); - encoded.append((3 - val.size() % 3) % 3, '='); - convertToBase64Url(encoded); - return encoded; + return internal::encode(val, true); +} + +std::string encode(const Data& val) { + return internal::encode(val, false); +} + +Data decode(const string& val) { + return internal::decode(val, false); } } // namespace TW::Base64 diff --git a/src/Base64.h b/src/Base64.h index 089e1c21a93..a930522886e 100644 --- a/src/Base64.h +++ b/src/Base64.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -10,17 +8,20 @@ namespace TW::Base64 { +// Checks if as string is in Base64-format or Base64Url-format +bool isBase64orBase64Url(const std::string& val); + // Decode a Base64-format string Data decode(const std::string& val); // Encode bytes into Base64 string -std::string encode(const Data& val); +std::string encode(const TW::Data& val); // Decode a Base64Url-format or a regular Base64 string. // Base64Url format uses '-' and '_' as the two special characters, Base64 uses '+'and '/'. Data decodeBase64Url(const std::string& val); -// Encode bytes into Base64Url string (uses '-' and '_' as pecial characters) +// Encode bytes into Base64Url string (uses '-' and '_' as special characters) std::string encodeBase64Url(const Data& val); } // namespace TW::Base64 diff --git a/src/Bech32.cpp b/src/Bech32.cpp index f1c6699eba6..d6bd309a1de 100644 --- a/src/Bech32.cpp +++ b/src/Bech32.cpp @@ -1,9 +1,7 @@ // Copyright © 2017 Pieter Wuille -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Bech32.h" #include "Data.h" @@ -14,9 +12,11 @@ // Bech32M variant also supported (BIP350) // Max length of 90 constraint is extended here to 120 for other usages -using namespace TW::Bech32; + using namespace TW; +namespace TW::Bech32 { + namespace { /** The Bech32 character set for encoding. */ @@ -26,15 +26,14 @@ const char* charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; constexpr std::array charset_rev = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, -1, 29, - -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, - 6, 4, 2, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, - 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1}; + -1, -1, -1, -1, 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, -1, 29, + -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, + 6, 4, 2, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, + 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1}; const uint32_t BECH32_XOR_CONST = 0x01; const uint32_t BECH32M_XOR_CONST = 0x2bc830a3; - /** Find the polynomial with value coefficients mod the generator as 30-bit. */ uint32_t polymod(const Data& values) { uint32_t chk = 1; @@ -105,7 +104,7 @@ Data create_checksum(const std::string& hrp, const Data& values, ChecksumVariant } // namespace /** Encode a Bech32 string. */ -std::string Bech32::encode(const std::string& hrp, const Data& values, ChecksumVariant variant) { +std::string encode(const std::string& hrp, const Data& values, ChecksumVariant variant) { Data checksum = create_checksum(hrp, values, variant); Data combined = values; append(combined, checksum); @@ -118,7 +117,7 @@ std::string Bech32::encode(const std::string& hrp, const Data& values, ChecksumV } /** Decode a Bech32 string. */ -std::tuple Bech32::decode(const std::string& str) { +std::tuple decode(const std::string& str) { if (str.length() > 120 || str.length() < 2) { // too long or too short return std::make_tuple(std::string(), Data(), None); @@ -141,7 +140,7 @@ std::tuple Bech32::decode(const std::string& ok = false; } size_t pos = str.rfind('1'); - if (ok && pos != str.npos && pos >= 1 && pos + 7 <= str.size()) { + if (ok && pos != std::string::npos && pos >= 1 && pos + 7 <= str.size()) { Data values; values.resize(str.size() - 1 - pos); for (size_t i = 0; i < str.size() - 1 - pos; ++i) { @@ -164,3 +163,5 @@ std::tuple Bech32::decode(const std::string& } return std::make_tuple(std::string(), Data(), None); } + +} // namespace TW::Bech32 diff --git a/src/Bech32.h b/src/Bech32.h index 60037fc5794..cb0a4bc7b9d 100644 --- a/src/Bech32.h +++ b/src/Bech32.h @@ -1,9 +1,9 @@ // Copyright © 2017 Pieter Wuille -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. + +#pragma once #include "Data.h" diff --git a/src/Bech32Address.cpp b/src/Bech32Address.cpp index da1c7af32a5..8394773b635 100644 --- a/src/Bech32Address.cpp +++ b/src/Bech32Address.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Bech32Address.h" #include "Bech32.h" diff --git a/src/Bech32Address.h b/src/Bech32Address.h index 23a88d6a23f..b7a1707b34d 100644 --- a/src/Bech32Address.h +++ b/src/Bech32Address.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Binance/Address.cpp b/src/Binance/Address.cpp index c0c7b91d0f7..0ab1d1a77c0 100644 --- a/src/Binance/Address.cpp +++ b/src/Binance/Address.cpp @@ -1,27 +1,43 @@ // Copyright © 2017 Pieter Wuille -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" +#include "Coin.h" #include #include -using namespace TW::Binance; +namespace TW::Binance { -const std::string Address::hrp = HRP_BINANCE; const std::string Address::hrpValidator = "bva"; -const std::vector validHrps = {Address::hrp, Address::hrpValidator, "bnbp", "bvap", "bca", "bcap"}; -bool Address::isValid(const std::string& addr) { - Address addrNotUsed; +Address::Address(TWCoinType coin): + Bech32Address(stringForHRP(TW::hrp(coin))) { +} + +Address::Address(const Data& keyHash, TWCoinType coin): + Bech32Address(stringForHRP(TW::hrp(coin)), keyHash) { +} + +Address::Address(const PublicKey& publicKey, TWCoinType coin): + Bech32Address(stringForHRP(TW::hrp(coin)), Hash::HasherSha256ripemd, publicKey) { +} + +bool Address::isValid(TWCoinType coin, const std::string& addr) { + const auto* const hrp = stringForHRP(TW::hrp(coin)); + Address addrNotUsed(hrp); + return decode(addr, addrNotUsed); +} + +bool Address::isValid(const std::string& addr, const std::string& hrp) { + Address addrNotUsed(hrp); return decode(addr, addrNotUsed); } bool Address::decode(const std::string& addr, Address& obj_out) { + std::vector validHrps = {obj_out.getHrp(), Address::hrpValidator, "bnbp", "bvap", "bca", "bcap"}; for (const auto& hrp: validHrps) { if (Bech32Address::decode(addr, obj_out, hrp)) { return true; @@ -29,3 +45,5 @@ bool Address::decode(const std::string& addr, Address& obj_out) { } return false; } + +} // namespace TW::Binance diff --git a/src/Binance/Address.h b/src/Binance/Address.h index f3494c9149f..227f3088727 100644 --- a/src/Binance/Address.h +++ b/src/Binance/Address.h @@ -1,13 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../Bech32Address.h" +#include #include namespace TW::Binance { @@ -15,18 +14,28 @@ namespace TW::Binance { /// Binance address is a Bech32Address, with "bnb" prefix and sha256ripemd hash class Address: public Bech32Address { public: - static const std::string hrp; // HRP_BINANCE - static const std::string hrpValidator; // HRP_BINANCE + static const std::string hrpValidator; - static bool isValid(const std::string& addr); + /// Checks if the given `addr` is a valid Binance address and has a known hrp. + static bool isValid(TWCoinType coin, const std::string& addr); + /// Checks if the given `addr` is a valid Binance address and has the given `chainHrp`. + static bool isValid(const std::string& addr, const std::string& chainHrp); - Address() : Bech32Address(hrp) {} + explicit Address(TWCoinType coin = TWCoinTypeBinance); - /// Initializes an address with a key hash. - Address(const Data& keyHash) : Bech32Address(hrp, keyHash) {} + explicit Address(const std::string& chainHrp) : Bech32Address(chainHrp) {} - /// Initializes an address with a public key. - Address(const PublicKey& publicKey) : Bech32Address(hrp, Hash::HasherSha256ripemd, publicKey) {} + /// Initializes an address with a key hash and hrp. + explicit Address(const Data& keyHash, const std::string& chainHrp) : Bech32Address(chainHrp, keyHash) {} + + /// Initializes an address with a key hash and coin type. + explicit Address(const Data& keyHash, TWCoinType coin = TWCoinTypeBinance); + + /// Initializes an address with a public key and hrp. + explicit Address(const PublicKey& publicKey, const std::string& chainHrp) : Bech32Address(chainHrp, Hash::HasherSha256ripemd, publicKey) {} + + /// Initializes an address with a public key and coin type. + explicit Address(const PublicKey& publicKey, TWCoinType coin = TWCoinTypeBinance); static bool decode(const std::string& addr, Address& obj_out); }; diff --git a/src/Binance/Entry.cpp b/src/Binance/Entry.cpp index c98b659e824..c1915455dec 100644 --- a/src/Binance/Entry.cpp +++ b/src/Binance/Entry.cpp @@ -1,111 +1,21 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" -#include "../proto/TransactionCompiler.pb.h" -#include "Address.h" -#include "Coin.h" -#include "Signer.h" +#include "HexCoding.h" +#include "proto/Binance.pb.h" -using namespace TW::Binance; -using namespace TW; -using namespace std; +namespace TW::Binance { -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { - return Address::isValid(address); +std::string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { + return signJSONHelper( + coin, + json, + key, + [](const Proto::SigningOutput& output) { return hex(output.encoded()); } + ); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { - return Address(publicKey).string(); -} - -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { - Address addr; - if (!Address::decode(address, addr)) { - return Data(); - } - return addr.getKeyHash(); -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); -} - -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { - return Signer::signJSON(json, key); -} - -Data Entry::preImageHashes(TWCoinType coin, const Data& txInputData) const { - return txCompilerTemplate( - txInputData, [](const auto& input, auto& output) { - Signer signer(input); - - auto preImageHash = signer.preImageHash(); - auto preImage = signer.signaturePreimage(); - output.set_data_hash(preImageHash.data(), preImageHash.size()); - output.set_data(preImage.data(), preImage.size()); - }); -} - -void Entry::compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { - dataOut = txCompilerTemplate( - txInputData, [&](const auto& input, auto& output) { - if (signatures.size() == 0 || publicKeys.size() == 0) { - output.set_error(Common::Proto::Error_invalid_params); - output.set_error_message("empty signatures or publickeys"); - return; - } - if (signatures.size() > 1 || publicKeys.size() > 1) { - output.set_error(Common::Proto::Error_no_support_n2n); - output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_no_support_n2n)); - return; - } - output = Signer(input).compile(signatures[0], publicKeys[0]); - }); -} - -Data Entry::buildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) const { - auto input = Proto::SigningInput(); - input.set_chain_id(chainId); - input.set_account_number(0); - input.set_sequence(0); - input.set_source(0); - input.set_memo(memo); - // do not set private_key! - input.set_private_key(""); - - auto& order = *input.mutable_send_order(); - - Address fromAddress; - if (!Address::decode(from, fromAddress)) { - throw std::invalid_argument("Invalid from address"); - } - const auto fromKeyhash = fromAddress.getKeyHash(); - Address toAddress; - if (!Address::decode(to, toAddress)) { - throw std::invalid_argument("Invalid to address"); - } - const auto toKeyhash = toAddress.getKeyHash(); - - { - auto* input = order.add_inputs(); - input->set_address(fromKeyhash.data(), fromKeyhash.size()); - auto* inputCoin = input->add_coins(); - inputCoin->set_denom(asset); - inputCoin->set_amount(static_cast(amount)); - } - { - auto* output = order.add_outputs(); - output->set_address(toKeyhash.data(), toKeyhash.size()); - auto* outputCoin = output->add_coins(); - outputCoin->set_denom(asset); - outputCoin->set_amount(static_cast(amount)); - } - - const auto txInputData = data(input.SerializeAsString()); - return txInputData; -} +} // namespace TW::Binance diff --git a/src/Binance/Entry.h b/src/Binance/Entry.h index 150dc15a6b8..dab9768dff7 100644 --- a/src/Binance/Entry.h +++ b/src/Binance/Entry.h @@ -1,29 +1,19 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../CoinEntry.h" +#include "rust/RustCoinEntry.h" namespace TW::Binance { /// Binance entry dispatcher. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public Rust::RustCoinEntryWithSignJSON { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; - - virtual Data preImageHashes(TWCoinType coin, const Data& txInputData) const; - virtual void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; - virtual Data buildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) const; + bool supportsJSONSigning() const override { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const override; }; } // namespace TW::Binance diff --git a/src/Binance/Serialization.cpp b/src/Binance/Serialization.cpp deleted file mode 100644 index 2869341207b..00000000000 --- a/src/Binance/Serialization.cpp +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Serialization.h" - -#include "Address.h" -#include "Bech32Address.h" -#include "Ethereum/Address.h" -#include "../HexCoding.h" - -using namespace TW; -using namespace TW::Binance; -using namespace google::protobuf; - -using json = nlohmann::json; - -static inline std::string addressString(const std::string& bytes) { - auto data = Data(bytes.begin(), bytes.end()); - return Address(data).string(); -} - -static inline std::string validatorAddress(const std::string& bytes) { - auto data = Data(bytes.begin(), bytes.end()); - return Bech32Address(Address::hrpValidator, data).string(); -} - -json Binance::signatureJSON(const Proto::SigningInput& input) { - json j; - j["account_number"] = std::to_string(input.account_number()); - j["chain_id"] = input.chain_id(); - j["data"] = nullptr; - j["memo"] = input.memo(); - j["msgs"] = json::array({orderJSON(input)}); - j["sequence"] = std::to_string(input.sequence()); - j["source"] = std::to_string(input.source()); - return j; -} - -json Binance::orderJSON(const Proto::SigningInput& input) { - json j; - if (input.has_trade_order()) { - j["id"] = input.trade_order().id(); - j["ordertype"] = 2; - j["price"] = input.trade_order().price(); - j["quantity"] = input.trade_order().quantity(); - j["sender"] = addressString(input.trade_order().sender()); - j["side"] = input.trade_order().side(); - j["symbol"] = input.trade_order().symbol(); - j["timeinforce"] = input.trade_order().timeinforce(); - } else if (input.has_cancel_trade_order()) { - j["refid"] = input.cancel_trade_order().refid(); - j["sender"] = addressString(input.cancel_trade_order().sender()); - j["symbol"] = input.cancel_trade_order().symbol(); - } else if (input.has_send_order()) { - j["inputs"] = inputsJSON(input.send_order()); - j["outputs"] = outputsJSON(input.send_order()); - } else if (input.has_freeze_order()) { - j["from"] = addressString(input.freeze_order().from()); - j["symbol"] = input.freeze_order().symbol(); - j["amount"] = input.freeze_order().amount(); - } else if (input.has_unfreeze_order()) { - j["from"] = addressString(input.unfreeze_order().from()); - j["symbol"] = input.unfreeze_order().symbol(); - j["amount"] = input.unfreeze_order().amount(); - } else if (input.has_htlt_order()) { - j["from"] = addressString(input.htlt_order().from()); - j["to"] = addressString(input.htlt_order().to()); - j["recipient_other_chain"] = input.htlt_order().recipient_other_chain(); - j["sender_other_chain"] = input.htlt_order().sender_other_chain(); - j["random_number_hash"] = hex(input.htlt_order().random_number_hash()); - j["timestamp"] = input.htlt_order().timestamp(); - j["amount"] = tokensJSON(input.htlt_order().amount()); - j["expected_income"] = input.htlt_order().expected_income(); - j["height_span"] = input.htlt_order().height_span(); - j["cross_chain"] = input.htlt_order().cross_chain(); - } else if (input.has_deposithtlt_order()) { - j["from"] = addressString(input.deposithtlt_order().from()); - j["swap_id"] = hex(input.deposithtlt_order().swap_id()); - j["amount"] = tokensJSON(input.deposithtlt_order().amount()); - } else if (input.has_claimhtlt_order()) { - j["from"] = addressString(input.claimhtlt_order().from()); - j["swap_id"] = hex(input.claimhtlt_order().swap_id()); - j["random_number"] = hex(input.claimhtlt_order().random_number()); - } else if (input.has_refundhtlt_order()) { - j["from"] = addressString(input.refundhtlt_order().from()); - j["swap_id"] = hex(input.refundhtlt_order().swap_id()); - } else if (input.has_transfer_out_order()) { - auto to = input.transfer_out_order().to(); - auto addr = Ethereum::Address(Data(to.begin(), to.end())); - j["from"] = addressString(input.transfer_out_order().from()); - j["to"] = addr.string(); - j["amount"] = tokenJSON(input.transfer_out_order().amount()); - j["expire_time"] = input.transfer_out_order().expire_time(); - } else if (input.has_side_delegate_order()) { - j["type"] = "cosmos-sdk/MsgSideChainDelegate"; - j["value"] = { - {"delegator_addr", addressString(input.side_delegate_order().delegator_addr())}, - {"validator_addr",validatorAddress(input.side_delegate_order().validator_addr())}, - {"delegation", tokenJSON(input.side_delegate_order().delegation(), true)}, - {"side_chain_id", input.side_delegate_order().chain_id()}, - }; - } else if (input.has_side_redelegate_order()) { - j["type"] = "cosmos-sdk/MsgSideChainRedelegate"; - j["value"] = { - {"delegator_addr", addressString(input.side_redelegate_order().delegator_addr())}, - {"validator_src_addr", validatorAddress(input.side_redelegate_order().validator_src_addr())}, - {"validator_dst_addr", validatorAddress(input.side_redelegate_order().validator_dst_addr())}, - {"amount", tokenJSON(input.side_redelegate_order().amount(), true)}, - {"side_chain_id", input.side_redelegate_order().chain_id()}, - }; - } else if (input.has_side_undelegate_order()) { - j["type"] = "cosmos-sdk/MsgSideChainUndelegate"; - j["value"] = { - {"delegator_addr", addressString(input.side_undelegate_order().delegator_addr())}, - {"validator_addr", validatorAddress(input.side_undelegate_order().validator_addr())}, - {"amount", tokenJSON(input.side_undelegate_order().amount(), true)}, - {"side_chain_id", input.side_undelegate_order().chain_id()}, - }; - } else if (input.has_time_lock_order()) { - j["from"] = addressString(input.time_lock_order().from_address()); - j["description"] = input.time_lock_order().description(); - j["amount"] = tokensJSON(input.time_lock_order().amount()); - j["lock_time"] = input.time_lock_order().lock_time(); - } else if (input.has_time_relock_order()) { - const auto amount = input.time_relock_order().amount(); - j["from"] = addressString(input.time_relock_order().from_address()); - j["time_lock_id"] = input.time_relock_order().id(); - j["description"] = input.time_relock_order().description(); - // if amount is empty or omitted, set null to avoid signature verification error - j["amount"] = nullptr; - if (amount.size() > 0) { - j["amount"] = tokensJSON(amount); - } - j["lock_time"] = input.time_relock_order().lock_time(); - } else if (input.has_time_unlock_order()) { - j["from"] = addressString(input.time_unlock_order().from_address()); - j["time_lock_id"] = input.time_unlock_order().id(); - } - return j; -} - -json Binance::inputsJSON(const Proto::SendOrder& order) { - json j = json::array(); - for (auto& input : order.inputs()) { - j.push_back({ - {"address", addressString(input.address())}, - {"coins", tokensJSON(input.coins())} - }); - } - return j; -} - -json Binance::outputsJSON(const Proto::SendOrder& order) { - json j = json::array(); - for (auto& output : order.outputs()) { - j.push_back({ - {"address", addressString(output.address())}, - {"coins", tokensJSON(output.coins())} - }); - } - return j; -} - -json Binance::tokenJSON(const Proto::SendOrder_Token& token, bool stringAmount) { - json j = { {"denom", token.denom()} }; - if (stringAmount) { - j["amount"] = std::to_string(token.amount()); - } else { - j["amount"] = token.amount(); - } - return j; -} - -json Binance::tokensJSON(const RepeatedPtrField& tokens) { - json j = json::array(); - for (auto& token : tokens) { - j.push_back(tokenJSON(token)); - } - return j; -} diff --git a/src/Binance/Serialization.h b/src/Binance/Serialization.h deleted file mode 100644 index 9b06682fbec..00000000000 --- a/src/Binance/Serialization.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "../proto/Binance.pb.h" -#include - -namespace TW::Binance { - -nlohmann::json signatureJSON(const Proto::SigningInput& input); -nlohmann::json orderJSON(const Proto::SigningInput& input); -nlohmann::json inputsJSON(const Proto::SendOrder& order); -nlohmann::json outputsJSON(const Proto::SendOrder& order); -nlohmann::json tokenJSON(const Proto::SendOrder_Token& token, bool stringAmount = false); -nlohmann::json tokensJSON(const ::google::protobuf::RepeatedPtrField& tokens); - -} // namespace TW::Binance diff --git a/src/Binance/Signer.cpp b/src/Binance/Signer.cpp deleted file mode 100644 index 4a535d65e7b..00000000000 --- a/src/Binance/Signer.cpp +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Signer.h" -#include "Serialization.h" -#include "../Hash.h" -#include "../HexCoding.h" -#include "../PrivateKey.h" - -#include -#include -#include - -#include - -using namespace TW; -using namespace TW::Binance; - -// Message prefixes -// see https://docs.binance.org/api-reference/transactions.html#amino-types -static const auto sendOrderPrefix = Data{0x2A, 0x2C, 0x87, 0xFA}; -static const auto tradeOrderPrefix = Data{0xCE, 0x6D, 0xC0, 0x43}; -static const auto cancelTradeOrderPrefix = Data{0x16, 0x6E, 0x68, 0x1B}; -static const auto HTLTOrderPrefix = Data{0xB3, 0x3F, 0x9A, 0x24}; -static const auto depositHTLTOrderPrefix = Data{0x63, 0x98, 0x64, 0x96}; -static const auto claimHTLTOrderPrefix = Data{0xC1, 0x66, 0x53, 0x00}; -static const auto refundHTLTOrderPrefix = Data{0x34, 0x54, 0xA2, 0x7C}; -static const auto pubKeyPrefix = Data{0xEB, 0x5A, 0xE9, 0x87}; -static const auto transactionPrefix = Data{0xF0, 0x62, 0x5D, 0xEE}; -static const auto tokenIssueOrderPrefix = Data{0x17, 0xEF, 0xAB, 0x80}; -static const auto tokenMintOrderPrefix = Data{0x46, 0x7E, 0x08, 0x29}; -static const auto tokenBurnOrderPrefix = Data{0x7E, 0xD2, 0xD2, 0xA0}; -static const auto tokenFreezeOrderPrefix = Data{0xE7, 0x74, 0xB3, 0x2D}; -static const auto tokenUnfreezeOrderPrefix = Data{0x65, 0x15, 0xFF, 0x0D}; -static const auto transferOutOrderPrefix = Data{0x80, 0x08, 0x19, 0xC0}; -static const auto sideDelegateOrderPrefix = Data{0xE3, 0xA0, 0x7F, 0xD2}; -static const auto sideRedelegateOrderPrefix = Data{0xE3, 0xCE, 0xD3, 0x64}; -static const auto sideUndelegateOrderPrefix = Data{0x51, 0x4F, 0x7E, 0x0E}; -static const auto timeLockOrderPrefix = Data{0x07, 0x92, 0x15, 0x31}; -static const auto timeRelockOrderPrefix = Data{0x50, 0x47, 0x11, 0xDA}; -static const auto timeUnlockOrderPrefix = Data{0xC4, 0x05, 0x0C, 0x6C}; - -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto signer = Signer(input); - auto encoded = signer.build(); - auto output = Proto::SigningOutput(); - output.set_encoded(encoded.data(), encoded.size()); - return output; -} - -std::string Signer::signJSON(const std::string& json, const Data& key) { - auto input = Proto::SigningInput(); - google::protobuf::util::JsonStringToMessage(json, &input); - input.set_private_key(key.data(), key.size()); - auto output = Signer::sign(input); - return hex(output.encoded()); -} - -Data Signer::build() const { - auto signature = encodeSignature(sign()); - return encodeTransaction(signature); -} - -Data Signer::sign() const { - auto hash = preImageHash(); - auto key = PrivateKey(input.private_key()); - auto signature = key.sign(hash, TWCurveSECP256k1); - return Data(signature.begin(), signature.end() - 1); -} - -Data Signer::preImageHash() const { - return Hash::sha256(signaturePreimage()); -} - -Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey) const { - // validate public key - if (publicKey.type != TWPublicKeyTypeSECP256k1) { - throw std::invalid_argument("Invalid public key"); - } - { - // validate correctness of signature - const auto hash = this->preImageHash(); - if (!publicKey.verify(signature, hash)) { - throw std::invalid_argument("Invalid signature/hash/publickey combination"); - } - } - const auto encoded = encodeTransaction(encodeSignature(signature, publicKey)); - auto output = Proto::SigningOutput(); - output.set_encoded(encoded.data(), encoded.size()); - return output; -} - -std::string Signer::signaturePreimage() const { - auto json = signatureJSON(input); - return json.dump(); -} - -Data Signer::encodeTransaction(const Data& signature) const { - auto msg = encodeOrder(); - auto transaction = Binance::Proto::Transaction(); - transaction.add_msgs(msg.data(), msg.size()); - transaction.add_signatures(signature.data(), signature.size()); - transaction.set_memo(input.memo()); - transaction.set_source(input.source()); - - auto data = transaction.SerializeAsString(); - return aminoWrap(data, transactionPrefix, true); -} - -Data Signer::encodeOrder() const { - std::string data; - Data prefix; - if (input.has_trade_order()) { - data = input.trade_order().SerializeAsString(); - prefix = tradeOrderPrefix; - } else if (input.has_cancel_trade_order()) { - data = input.cancel_trade_order().SerializeAsString(); - prefix = cancelTradeOrderPrefix; - } else if (input.has_send_order()) { - data = input.send_order().SerializeAsString(); - prefix = sendOrderPrefix; - } else if (input.has_issue_order()) { - data = input.issue_order().SerializeAsString(); - prefix = tokenIssueOrderPrefix; - } else if (input.has_mint_order()) { - data = input.mint_order().SerializeAsString(); - prefix = tokenMintOrderPrefix; - } else if (input.has_burn_order()) { - data = input.burn_order().SerializeAsString(); - prefix = tokenBurnOrderPrefix; - } else if (input.has_freeze_order()) { - data = input.freeze_order().SerializeAsString(); - prefix = tokenFreezeOrderPrefix; - } else if (input.has_unfreeze_order()) { - data = input.unfreeze_order().SerializeAsString(); - prefix = tokenUnfreezeOrderPrefix; - } else if (input.has_htlt_order()) { - data = input.htlt_order().SerializeAsString(); - prefix = HTLTOrderPrefix; - } else if (input.has_deposithtlt_order()) { - data = input.deposithtlt_order().SerializeAsString(); - prefix = depositHTLTOrderPrefix; - } else if (input.has_claimhtlt_order()) { - data = input.claimhtlt_order().SerializeAsString(); - prefix = claimHTLTOrderPrefix; - } else if (input.has_refundhtlt_order()) { - data = input.refundhtlt_order().SerializeAsString(); - prefix = refundHTLTOrderPrefix; - } else if (input.has_transfer_out_order()) { - data = input.transfer_out_order().SerializeAsString(); - prefix = transferOutOrderPrefix; - } else if (input.has_side_delegate_order()) { - data = input.side_delegate_order().SerializeAsString(); - prefix = sideDelegateOrderPrefix; - } else if (input.has_side_redelegate_order()) { - data = input.side_redelegate_order().SerializeAsString(); - prefix = sideRedelegateOrderPrefix; - } else if (input.has_side_undelegate_order()) { - data = input.side_undelegate_order().SerializeAsString(); - prefix = sideUndelegateOrderPrefix; - } else if (input.has_time_lock_order()) { - data = input.time_lock_order().SerializeAsString(); - prefix = timeLockOrderPrefix; - } else if (input.has_time_relock_order()) { - data = input.time_relock_order().SerializeAsString(); - prefix = timeRelockOrderPrefix; - } else if (input.has_time_unlock_order()) { - data = input.time_unlock_order().SerializeAsString(); - prefix = timeUnlockOrderPrefix; - } else { - return {}; - } - return aminoWrap(data, prefix, false); -} - -Data Signer::encodeSignature(const Data& signature) const { - auto key = PrivateKey(input.private_key()); - auto publicKey = key.getPublicKey(TWPublicKeyTypeSECP256k1); - return encodeSignature(signature, publicKey); -} - -Data Signer::encodeSignature(const Data& signature, const PublicKey& publicKey) const { - auto encodedPublicKey = pubKeyPrefix; - encodedPublicKey.insert(encodedPublicKey.end(), static_cast(publicKey.bytes.size())); - encodedPublicKey.insert(encodedPublicKey.end(), publicKey.bytes.begin(), publicKey.bytes.end()); - - auto object = Binance::Proto::Signature(); - object.set_pub_key(encodedPublicKey.data(), encodedPublicKey.size()); - object.set_signature(signature.data(), signature.size()); - object.set_account_number(input.account_number()); - object.set_sequence(input.sequence()); - - return aminoWrap(object.SerializeAsString(), {}, false); -} - -Data Signer::aminoWrap(const std::string& raw, const Data& typePrefix, bool prefixWithSize) const { - const auto contentsSize = raw.size() + typePrefix.size(); - auto size = contentsSize; - if (prefixWithSize) { - size += 10; - } - - std::string msg; - msg.reserve(size); - { - google::protobuf::io::StringOutputStream output(&msg); - google::protobuf::io::CodedOutputStream cos(&output); - if (prefixWithSize) { - cos.WriteVarint64(contentsSize); - } - cos.WriteRaw(typePrefix.data(), static_cast(typePrefix.size())); - cos.WriteRaw(raw.data(), static_cast(raw.size())); - } - - return Data(msg.begin(), msg.end()); -} diff --git a/src/Binance/Signer.h b/src/Binance/Signer.h deleted file mode 100644 index aceb774d12e..00000000000 --- a/src/Binance/Signer.h +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Data.h" -#include "PublicKey.h" -#include "../proto/Binance.pb.h" - -#include - -namespace TW::Binance { - -/// Helper class that performs Binance transaction signing. -class Signer { - public: - /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; - /// Signs a json Proto::SigningInput with private key - static std::string signJSON(const std::string& json, const Data& key); - public: - Proto::SigningInput input; - - /// Initializes a transaction signer. - explicit Signer(const Proto::SigningInput& input) : input(input) {} - - /// Builds a signed transaction. - /// - /// \returns the signed transaction data or an empty vector if there is an - /// error. - TW::Data build() const; - - /// Signs the transaction. - /// - /// \returns the transaction signature or an empty vector if there is an - /// error. - TW::Data sign() const; - - TW::Data preImageHash() const; - Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) const; - std::string signaturePreimage() const; - - private: - TW::Data encodeTransaction(const TW::Data& signature) const; - TW::Data encodeOrder() const; - TW::Data encodeSignature(const TW::Data& signature) const; - TW::Data encodeSignature(const TW::Data& signature, const PublicKey& publicKey) const; - TW::Data aminoWrap(const std::string& raw, const TW::Data& typePrefix, - bool isPrefixLength) const; -}; - -} // namespace TW::Binance diff --git a/src/BinaryCoding.cpp b/src/BinaryCoding.cpp index 51090acc5ea..210101accbe 100644 --- a/src/BinaryCoding.cpp +++ b/src/BinaryCoding.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "BinaryCoding.h" diff --git a/src/BinaryCoding.h b/src/BinaryCoding.h index 1b7077687c3..45050e315d5 100644 --- a/src/BinaryCoding.h +++ b/src/BinaryCoding.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -62,12 +60,12 @@ uint8_t varIntSize(uint64_t value); /// being encoded. It produces fewer bytes for smaller numbers as opposed to a /// fixed-size encoding. Little endian byte order is used. /// -/// @returns the number of bytes written. +/// \returns the number of bytes written. uint8_t encodeVarInt(uint64_t size, std::vector& data); /// Decodes an integer as a variable-length integer. See encodeVarInt(). /// -/// @returns a tuple with a success indicator and the decoded integer. +/// \returns a tuple with a success indicator and the decoded integer. std::tuple decodeVarInt(const Data& in, size_t& indexInOut); /// Encodes a 16-bit big-endian value into the provided buffer. @@ -109,7 +107,7 @@ uint64_t decode64BE(const uint8_t* _Nonnull src); void encodeString(const std::string& str, std::vector& data); /// Decodes an ASCII string prefixed by its length (varInt) -/// @returns a tuple with a success indicator and the decoded string. +/// \returns a tuple with a success indicator and the decoded string. std::tuple decodeString(const Data& in, size_t& indexInOut); } // namespace TW diff --git a/src/Bitcoin/Address.cpp b/src/Bitcoin/Address.cpp deleted file mode 100644 index 5aa19d29eab..00000000000 --- a/src/Bitcoin/Address.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Address.h" - -#include "../Base58.h" - -using namespace TW::Bitcoin; diff --git a/src/Bitcoin/Address.h b/src/Bitcoin/Address.h index 9ab00322ed8..50aab3e10af 100644 --- a/src/Bitcoin/Address.h +++ b/src/Bitcoin/Address.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../Base58Address.h" -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/Bitcoin/Amount.h b/src/Bitcoin/Amount.h index 77dc499cb07..7df4f56adcd 100644 --- a/src/Bitcoin/Amount.h +++ b/src/Bitcoin/Amount.h @@ -1,10 +1,8 @@ // Copyright © 2009-2010 Satoshi Nakamoto // Copyright © 2009-2016 The Bitcoin Core developers -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -15,10 +13,4 @@ namespace TW::Bitcoin { /// Amount in satoshis (can be negative) using Amount = int64_t; -/// One bitcoin in satoshis -inline constexpr Amount coin = 100000000; - -/// Maxximum valid amount in satoshis. -inline constexpr Amount maxAmount = 21000000 * coin; - } // namespace TW::Bitcoin diff --git a/src/Bitcoin/CashAddress.cpp b/src/Bitcoin/CashAddress.cpp index f0c8b8d0a0c..1f98d5fb48c 100644 --- a/src/Bitcoin/CashAddress.cpp +++ b/src/Bitcoin/CashAddress.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "CashAddress.h" #include "../Coin.h" @@ -14,13 +12,15 @@ #include #include -using namespace TW::Bitcoin; -using namespace TW; +namespace TW::Bitcoin { /// From https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md namespace { -enum class Version : uint8_t { p2kh = 0x00, p2sh = 0x08 }; +enum class Version : uint8_t { + p2kh = 0x00, + p2sh = 0x08 +}; constexpr size_t maxHRPSize{20}; constexpr size_t maxDataSize{104}; @@ -61,7 +61,8 @@ bool CashAddress::isValid(const std::string& hrp, const std::string& string) noe std::string(decodedHRP.data()).compare(0, std::min(hrp.size(), maxHRPSize), hrp) == 0; } -CashAddress::CashAddress(const std::string& hrp, const std::string& string) : hrp(hrp) { +CashAddress::CashAddress(const std::string& hrp, const std::string& string) + : hrp(hrp) { const auto withPrefix = details::buildPrefix(hrp, string); std::array decodedHRP{}; std::array data{}; @@ -75,7 +76,8 @@ CashAddress::CashAddress(const std::string& hrp, const std::string& string) : hr std::copy(data.begin(), data.begin() + dataLen, bytes.begin()); } -CashAddress::CashAddress(std::string hrp, const PublicKey& publicKey) : hrp(std::move(hrp)) { +CashAddress::CashAddress(std::string hrp, const PublicKey& publicKey) + : hrp(std::move(hrp)) { if (publicKey.type != TWPublicKeyTypeSECP256k1) { throw std::invalid_argument("CashAddress needs a compressed SECP256k1 public key."); } @@ -111,3 +113,5 @@ Data CashAddress::getData() const { cash_data_to_addr(data.data(), &outlen, bytes.data(), CashAddress::size); return data; } + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/CashAddress.h b/src/Bitcoin/CashAddress.h index f428a43634e..58b1a22d5b6 100644 --- a/src/Bitcoin/CashAddress.h +++ b/src/Bitcoin/CashAddress.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Address.h" -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/Bitcoin/DustCalculator.cpp b/src/Bitcoin/DustCalculator.cpp new file mode 100644 index 00000000000..3aeea03cecb --- /dev/null +++ b/src/Bitcoin/DustCalculator.cpp @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "DustCalculator.h" + +namespace TW::Bitcoin { + +FixedDustCalculator::FixedDustCalculator(Amount fixed) noexcept + : fixedDustAmount(fixed) { +} + +Amount FixedDustCalculator::dustAmount([[maybe_unused]] Amount byteFee) noexcept { + return fixedDustAmount; +} + +LegacyDustCalculator::LegacyDustCalculator(TWCoinType coinType) noexcept + : feeCalculator(getFeeCalculator(coinType, false)) { +} + +Amount LegacyDustCalculator::dustAmount([[maybe_unused]] Amount byteFee) noexcept { + return feeCalculator.calculateSingleInput(byteFee); +} + +DustCalculatorShared getDustCalculator(const Proto::SigningInput& input) { + if (input.disable_dust_filter()) { + return std::make_shared(0); + } + + if (input.has_fixed_dust_threshold()) { + return std::make_shared(input.fixed_dust_threshold()); + } + + const auto coinType = static_cast(input.coin_type()); + return std::make_shared(coinType); +} + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/DustCalculator.h b/src/Bitcoin/DustCalculator.h new file mode 100644 index 00000000000..825ea9b2ba6 --- /dev/null +++ b/src/Bitcoin/DustCalculator.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Amount.h" +#include "FeeCalculator.h" +#include "proto/Bitcoin.pb.h" + +#include +#include + +namespace TW::Bitcoin { + +/// Interface for transaction dust amount calculator. +struct DustCalculator { + virtual ~DustCalculator() noexcept = default; + + /// Returns a Dust threshold of a transaction UTXO or output. + virtual Amount dustAmount(Amount byteFee) noexcept = 0; +}; + +/// Always returns a fixed Dust amount specified in the signing request. +class FixedDustCalculator final: public DustCalculator { +public: + explicit FixedDustCalculator(Amount fixed) noexcept; + + Amount dustAmount([[maybe_unused]] Amount byteFee) noexcept override; + +private: + Amount fixedDustAmount {0}; +}; + +/// Legacy Dust filter implementation using [`FeeCalculator::calculateSingleInput`]. +/// Depends on a coin type, sats/Byte fee. +class LegacyDustCalculator final: public DustCalculator { +public: + explicit LegacyDustCalculator(TWCoinType coinType) noexcept; + + Amount dustAmount(Amount byteFee) noexcept override; + +private: + const FeeCalculator& feeCalculator; +}; + +using DustCalculatorShared = std::shared_ptr; + +DustCalculatorShared getDustCalculator(const Proto::SigningInput& input); + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Entry.cpp b/src/Bitcoin/Entry.cpp index 28f25c19ed2..ab953545fcb 100644 --- a/src/Bitcoin/Entry.cpp +++ b/src/Bitcoin/Entry.cpp @@ -1,24 +1,23 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" #include "CashAddress.h" +#include "ExchangeAddress.h" #include "SegwitAddress.h" #include "Signer.h" -#include +namespace TW::Bitcoin { -using namespace TW::Bitcoin; -using namespace TW; -using namespace std; +bool Entry::validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const { + auto* base58Prefix = std::get_if(&addressPrefix); + auto* hrp = std::get_if(&addressPrefix); + bool isValidBase58 = base58Prefix ? Address::isValid(address, {{base58Prefix->p2pkh}, {base58Prefix->p2sh}}) : false; + bool isValidHrp = hrp ? SegwitAddress::isValid(address, *hrp) : false; -bool Entry::validateAddress(TWCoinType coin, const string& address, byte p2pkh, byte p2sh, - const char* hrp) const { switch (coin) { case TWCoinTypeBitcoin: case TWCoinTypeDigiByte: @@ -27,24 +26,25 @@ bool Entry::validateAddress(TWCoinType coin, const string& address, byte p2pkh, case TWCoinTypeQtum: case TWCoinTypeViacoin: case TWCoinTypeBitcoinGold: - return SegwitAddress::isValid(address, hrp) || Address::isValid(address, {{p2pkh}, {p2sh}}); - + case TWCoinTypeSyscoin: + case TWCoinTypeStratis: + return isValidBase58 || isValidHrp; case TWCoinTypeBitcoinCash: - return BitcoinCashAddress::isValid(address) || Address::isValid(address, {{p2pkh}, {p2sh}}); - + return base58Prefix ? isValidBase58 : BitcoinCashAddress::isValid(address); case TWCoinTypeECash: - return ECashAddress::isValid(address) || Address::isValid(address, {{p2pkh}, {p2sh}}); - + return base58Prefix ? isValidBase58 : ECashAddress::isValid(address); + case TWCoinTypeFiro: + return isValidBase58 || ExchangeAddress::isValid(address); case TWCoinTypeDash: case TWCoinTypeDogecoin: + case TWCoinTypePivx: case TWCoinTypeRavencoin: - case TWCoinTypeFiro: default: - return Address::isValid(address, {{p2pkh}, {p2sh}}); + return isValidBase58; } } -string Entry::normalizeAddress(TWCoinType coin, const string& address) const { +std::string Entry::normalizeAddress(TWCoinType coin, const std::string& address) const { switch (coin) { case TWCoinTypeBitcoinCash: // normalized with bitcoincash: prefix @@ -66,123 +66,115 @@ string Entry::normalizeAddress(TWCoinType coin, const string& address) const { } } -string Entry::deriveAddress(TWCoinType coin, TWDerivation derivation, const PublicKey& publicKey, - byte p2pkh, const char* hrp) const { +std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const { + byte p2pkh = getFromPrefixPkhOrDefault(addressPrefix, coin); + const char* hrp = getFromPrefixHrpOrDefault(addressPrefix, coin); + switch (coin) { case TWCoinTypeBitcoin: case TWCoinTypeLitecoin: + case TWCoinTypeDigiByte: + case TWCoinTypeViacoin: + case TWCoinTypeBitcoinGold: + case TWCoinTypeSyscoin: + case TWCoinTypeStratis: switch (derivation) { case TWDerivationBitcoinLegacy: case TWDerivationLitecoinLegacy: return Address(publicKey, p2pkh).string(); + case TWDerivationBitcoinTestnet: + return SegwitAddress::createTestnetFromPublicKey(publicKey).string(); + case TWDerivationBitcoinSegwit: case TWDerivationDefault: default: return SegwitAddress(publicKey, hrp).string(); } - case TWCoinTypeDigiByte: - case TWCoinTypeViacoin: - case TWCoinTypeBitcoinGold: - return SegwitAddress(publicKey, hrp).string(); - case TWCoinTypeBitcoinCash: return BitcoinCashAddress(publicKey).string(); case TWCoinTypeECash: return ECashAddress(publicKey).string(); + case TWCoinTypeFiro: + if (std::get_if(&addressPrefix)) { + return ExchangeAddress(publicKey).string(); + } + return Address(publicKey, p2pkh).string(); + case TWCoinTypeDash: case TWCoinTypeDogecoin: case TWCoinTypeMonacoin: + case TWCoinTypePivx: case TWCoinTypeQtum: case TWCoinTypeRavencoin: - case TWCoinTypeFiro: default: return Address(publicKey, p2pkh).string(); } } -template +template inline Data cashAddressToData(const CashAddress&& addr) { return subData(addr.getData(), 1); } Data Entry::addressToData(TWCoinType coin, const std::string& address) const { switch (coin) { - case TWCoinTypeBitcoin: - case TWCoinTypeDigiByte: - case TWCoinTypeGroestlcoin: - case TWCoinTypeLitecoin: - case TWCoinTypeViacoin: - { - const auto decoded = SegwitAddress::decode(address); - if (!std::get<2>(decoded)) { - return Data(); - } - return std::get<0>(decoded).witnessProgram; - } - - case TWCoinTypeBitcoinCash: - return cashAddressToData(BitcoinCashAddress(address)); + case TWCoinTypeBitcoinCash: + return cashAddressToData(BitcoinCashAddress(address)); - case TWCoinTypeECash: - return cashAddressToData(ECashAddress(address)); + case TWCoinTypeECash: + return cashAddressToData(ECashAddress(address)); + + case TWCoinTypeFiro: { + // check if it is a legacy address + if (Address::isValid(address)) { + const auto addr = Address(address); + return {addr.bytes.begin() + 1, addr.bytes.end()}; + } else if (ExchangeAddress::isValid(address)) { + const auto addr = ExchangeAddress(address); + return {addr.bytes.begin() + 3, addr.bytes.end()}; + } + return {}; + } - case TWCoinTypeDash: - case TWCoinTypeDogecoin: - case TWCoinTypeMonacoin: - case TWCoinTypeQtum: - case TWCoinTypeRavencoin: - case TWCoinTypeFiro: - { + default: { + const auto decoded = SegwitAddress::decode(address); + if (!std::get<2>(decoded)) { + // check if it is a legacy address + if (Address::isValid(address)) { const auto addr = Address(address); return {addr.bytes.begin() + 1, addr.bytes.end()}; } - - default: - return Data(); + return {Data()}; + } + return std::get<0>(decoded).witnessProgram; + } } } -void Entry::sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const Data& dataIn, Data& dataOut) const { signTemplate(dataIn, dataOut); } -void Entry::plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const { +void Entry::plan([[maybe_unused]] TWCoinType coin, const Data& dataIn, Data& dataOut) const { planTemplate(dataIn, dataOut); } -Data Entry::preImageHashes(TWCoinType coin, const Data& txInputData) const { +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { return txCompilerTemplate( txInputData, [](auto&& input, auto&& output) { output = Signer::preImageHashes(input); }); } -void Entry::compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { auto txCompilerFunctor = [&signatures, &publicKeys](auto&& input, auto&& output) noexcept { - if (signatures.empty() || publicKeys.empty()) { - output.set_error(Common::Proto::Error_invalid_params); - output.set_error_message("empty signatures or publickeys"); - return; - } - - if (signatures.size() != publicKeys.size()) { - output.set_error(Common::Proto::Error_invalid_params); - output.set_error_message("signatures size and publickeys size not equal"); - return; - } - - HashPubkeyList externalSignatures; - auto insertFunctor = [](auto&& signature, auto&& pubkey) noexcept { - return std::make_pair(signature, pubkey.bytes); - }; - transform(begin(signatures), end(signatures), begin(publicKeys), - back_inserter(externalSignatures), insertFunctor); - output = Signer::sign(input, externalSignatures); + output = Signer::compile(input, signatures, publicKeys); }; - dataOut = txCompilerTemplate(txInputData, txCompilerFunctor); } + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Entry.h b/src/Bitcoin/Entry.h index cdffd5e221d..9d2c8503097 100644 --- a/src/Bitcoin/Entry.h +++ b/src/Bitcoin/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -13,24 +11,18 @@ namespace TW::Bitcoin { /// Bitcoin entry dispatcher. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific /// includes in this file -class Entry : public CoinEntry { +class Entry final : public CoinEntry { public: - bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, - const char* hrp) const final; - std::string normalizeAddress(TWCoinType coin, const std::string& address) const final; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, - const char* hrp) const final { - return deriveAddress(coin, TWDerivationDefault, publicKey, p2pkh, hrp); - } - std::string deriveAddress(TWCoinType coin, TWDerivation derivation, const PublicKey& publicKey, - TW::byte p2pkh, const char* hrp) const final; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; Data addressToData(TWCoinType coin, const std::string& address) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const final; - void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const final; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - Data preImageHashes(TWCoinType coin, const Data& txInputData) const final; + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, - const std::vector& publicKeys, Data& dataOut) const final; + const std::vector& publicKeys, Data& dataOut) const; // Note: buildTransactionInput is not implemented for Binance chain with UTXOs }; diff --git a/src/Bitcoin/ExchangeAddress.h b/src/Bitcoin/ExchangeAddress.h new file mode 100644 index 00000000000..dbdaa0bd765 --- /dev/null +++ b/src/Bitcoin/ExchangeAddress.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../Base58Address.h" +#include "Data.h" +#include "../PublicKey.h" + +#include + +namespace TW::Bitcoin { + +// see: https://github.com/firoorg/firo/blob/8bd4abdea223e22f15c36e7d2d42618dc843e2ef/src/chainparams.cpp#L357 +static const size_t kExchangeAddressSize = 23; +static const Data kPrefix = {0x01, 0xb9, 0xbb}; + +/// Class for firo exchange addresses +class ExchangeAddress : public TW::Base58Address { + public: + /// Initializes an address with a string representation. + explicit ExchangeAddress(const std::string& string) : TW::Base58Address(string) {} + + /// Initializes an address with a collection of bytes. + explicit ExchangeAddress(const Data& data) : TW::Base58Address(data) {} + + /// Initializes an address with a public key and prefix. + ExchangeAddress(const PublicKey& publicKey) : TW::Base58Address(publicKey, kPrefix) {} + + /// Determines whether a string makes a valid Firo exchange address. + static bool isValid(const std::string& string) { + return TW::Base58Address::isValid(string, {kPrefix}); + } +}; + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/FeeCalculator.cpp b/src/Bitcoin/FeeCalculator.cpp index 961ce3dfbcb..3294bfb64d5 100644 --- a/src/Bitcoin/FeeCalculator.cpp +++ b/src/Bitcoin/FeeCalculator.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "FeeCalculator.h" @@ -29,18 +27,35 @@ int64_t LinearFeeCalculator::calculateSingleInput(int64_t byteFee) const noexcep } class DecredFeeCalculator : public LinearFeeCalculator { +private: + bool disableDustFilter = false; + public: - constexpr DecredFeeCalculator() noexcept - : LinearFeeCalculator(gDecredBytesPerInput, gDecredBytesPerOutput, gDecredBytesBase) {} + constexpr DecredFeeCalculator(bool disableFilter = false) noexcept + : LinearFeeCalculator(gDecredBytesPerInput, gDecredBytesPerOutput, gDecredBytesBase) + , disableDustFilter(disableFilter) {} + + int64_t calculateSingleInput(int64_t byteFee) const noexcept override { + if (disableDustFilter) { + return 0; + } + return LinearFeeCalculator::calculateSingleInput(byteFee); + } }; -constexpr DefaultFeeCalculator defaultFeeCalculator{}; -constexpr DecredFeeCalculator decredFeeCalculator{}; -constexpr SegwitFeeCalculator segwitFeeCalculator{}; +static constexpr DefaultFeeCalculator defaultFeeCalculator{}; +static constexpr DefaultFeeCalculator defaultFeeCalculatorNoDustFilter(true); +static constexpr DecredFeeCalculator decredFeeCalculator{}; +static constexpr DecredFeeCalculator decredFeeCalculatorNoDustFilter(true); +static constexpr SegwitFeeCalculator segwitFeeCalculator{}; +static constexpr SegwitFeeCalculator segwitFeeCalculatorNoDustFilter(true); -const FeeCalculator& getFeeCalculator(TWCoinType coinType) noexcept { +const FeeCalculator& getFeeCalculator(TWCoinType coinType, bool disableFilter) noexcept { switch (coinType) { case TWCoinTypeDecred: + if (disableFilter) { + return decredFeeCalculatorNoDustFilter; + } return decredFeeCalculator; case TWCoinTypeBitcoin: @@ -49,9 +64,17 @@ const FeeCalculator& getFeeCalculator(TWCoinType coinType) noexcept { case TWCoinTypeLitecoin: case TWCoinTypeViacoin: case TWCoinTypeGroestlcoin: + case TWCoinTypeSyscoin: + case TWCoinTypeStratis: + if (disableFilter) { + return segwitFeeCalculatorNoDustFilter; + } return segwitFeeCalculator; default: + if (disableFilter) { + return defaultFeeCalculatorNoDustFilter; + } return defaultFeeCalculator; } } diff --git a/src/Bitcoin/FeeCalculator.h b/src/Bitcoin/FeeCalculator.h index 5d7dbb857e3..32d0f083959 100644 --- a/src/Bitcoin/FeeCalculator.h +++ b/src/Bitcoin/FeeCalculator.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -20,6 +18,7 @@ inline constexpr double gSegwitBytesBase{gDefaultBytesBase}; /// Interface for transaction fee calculator. class FeeCalculator { public: + virtual ~FeeCalculator() noexcept = default; [[nodiscard]] virtual int64_t calculate(int64_t inputs, int64_t outputs, int64_t byteFee) const noexcept = 0; [[nodiscard]] virtual int64_t calculateSingleInput(int64_t byteFee) const noexcept = 0; @@ -46,28 +45,50 @@ class ConstantFeeCalculator : public FeeCalculator { const int64_t fee; explicit constexpr ConstantFeeCalculator(int64_t fee) noexcept : fee(fee) {} - [[nodiscard]] int64_t calculate(int64_t inputs, int64_t outputs, - int64_t byteFee) const noexcept final { + [[nodiscard]] int64_t calculate([[maybe_unused]] int64_t inputs, [[maybe_unused]] int64_t outputs, + [[maybe_unused]] int64_t byteFee) const noexcept final { return fee; } - [[nodiscard]] int64_t calculateSingleInput(int64_t byteFee) const noexcept final { return 0; } + [[nodiscard]] int64_t calculateSingleInput([[maybe_unused]] int64_t byteFee) const noexcept final { return 0; } }; /// Default Bitcoin transaction fee calculator, non-segwit. class DefaultFeeCalculator : public LinearFeeCalculator { +private: + bool disableDustFilter = false; + public: - constexpr DefaultFeeCalculator() noexcept - : LinearFeeCalculator(gDefaultBytesPerInput, gDefaultBytesPerOutput, gDefaultBytesBase) {} + constexpr DefaultFeeCalculator(bool disableFilter = false) noexcept + : LinearFeeCalculator(gDefaultBytesPerInput, gDefaultBytesPerOutput, gDefaultBytesBase) + , disableDustFilter(disableFilter) {} + + [[nodiscard]] int64_t calculateSingleInput(int64_t byteFee) const noexcept override { + if (disableDustFilter) { + return 0; + } + return LinearFeeCalculator::calculateSingleInput(byteFee); + } }; /// Bitcoin Segwit transaction fee calculator class SegwitFeeCalculator : public LinearFeeCalculator { +private: + bool disableDustFilter = false; + public: - constexpr SegwitFeeCalculator() noexcept - : LinearFeeCalculator(gSegwitBytesPerInput, gSegwitBytesPerOutput, gSegwitBytesBase) {} + constexpr SegwitFeeCalculator(bool disableFilter = false) noexcept + : LinearFeeCalculator(gSegwitBytesPerInput, gSegwitBytesPerOutput, gSegwitBytesBase) + , disableDustFilter(disableFilter) {} + + [[nodiscard]] int64_t calculateSingleInput(int64_t byteFee) const noexcept override { + if (disableDustFilter) { + return 0; + } + return LinearFeeCalculator::calculateSingleInput(byteFee); + } }; /// Return the fee calculator for the given coin. -const FeeCalculator& getFeeCalculator(TWCoinType coinType) noexcept; +const FeeCalculator& getFeeCalculator(TWCoinType coinType, bool disableFilter = false) noexcept; } // namespace TW::Bitcoin diff --git a/src/Bitcoin/InputSelector.cpp b/src/Bitcoin/InputSelector.cpp index 97b34ff8603..98a5f4e3358 100644 --- a/src/Bitcoin/InputSelector.cpp +++ b/src/Bitcoin/InputSelector.cpp @@ -1,76 +1,46 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "InputSelector.h" -#include "NumericLiteral.h" #include "UTXO.h" -#include "algorithm/erase.h" -#include "algorithm/sort_copy.h" #include +#include #include -using namespace TW; -using namespace TW::Bitcoin; - -using MaybeDust = std::optional; -using MaybeFee = std::optional; -using Inputs = std::vector; - -namespace details { - -struct FeeCalculatorArgs { - int64_t inputs; - int64_t outputs; - int64_t byteFee; -}; - -static inline int64_t distFrom2x(int64_t doubleTargetValue, int64_t val) noexcept { - return val > doubleTargetValue ? val - doubleTargetValue : doubleTargetValue - val; -} +namespace TW::Bitcoin { template -static inline std::vector -selectOutDustElement(int64_t doubleTargetValue, MaybeDust dustThreshold, - const std::vector>& slices) noexcept { - if (dustThreshold.has_value()) { - const auto minFunctor = [doubleTargetValue](auto&& lhs, auto&& rhs) noexcept { - return details::distFrom2x(doubleTargetValue, InputSelector::sum(lhs)) < - details::distFrom2x(doubleTargetValue, InputSelector::sum(rhs)); - }; - const auto min = std::min_element(cbegin(slices), cend(slices), minFunctor); - return *min; +uint64_t InputSelector::sum(const std::vector& amounts) noexcept { + uint64_t sum = 0; + for (auto& i : amounts) { + sum += i.amount; } - return slices.front(); + return sum; } -static MaybeFee calculateTargetFee(const Inputs& maxWithXInputs, const FeeCalculator& feeCalculator, - FeeCalculatorArgs feeCalculatorArgs, int64_t targetValue, - MaybeDust dust = std::nullopt) noexcept { - auto&& [numInputs, numOutputs, byteFee] = feeCalculatorArgs; - const auto fee = feeCalculator.calculate(numInputs, numOutputs, byteFee); - auto targetFee = targetValue + fee; - if (dust) { - targetFee += *dust; - } - if (maxWithXInputs[numInputs] < targetFee) { - // no way to satisfy with only numInputs inputs, skip - return std::nullopt; - } - return std::make_optional(targetFee); +template +std::vector +InputSelector::filterOutDust(const std::vector& inputs, + int64_t byteFee) noexcept { + auto dustThreshold = static_cast(dustCalculator->dustAmount(byteFee)); + return filterThreshold(inputs, dustThreshold); } -// Precompute maximum amount possible to obtain with given number of inputs +// Filters utxos that are dust template -static inline Inputs collectMaxWithXInputs(const std::vector& sorted) noexcept { - Inputs maxWithXInputs(sorted.size() + 1); - std::transform_inclusive_scan(crbegin(sorted), crend(sorted), next(begin(maxWithXInputs)), - std::plus<>{}, std::mem_fn(&TypeWithAmount::amount), uint64_t{}); - return maxWithXInputs; +std::vector +InputSelector::filterThreshold(const std::vector& inputs, + uint64_t minimumAmount) noexcept { + std::vector filtered; + for (auto& i : inputs) { + if (static_cast(i.amount) >= minimumAmount) { + filtered.push_back(i); + } + } + return filtered; } // Slice Array @@ -80,111 +50,116 @@ static inline Inputs collectMaxWithXInputs(const std::vector& so // [7, 8, 9]] template static inline std::vector> -sliding(const std::vector& inputs, size_t sliceSize) noexcept { +slice(const std::vector& inputs, size_t sliceSize) { std::vector> slices; - auto&& [from, to] = std::make_pair(cbegin(inputs), cend(inputs) - sliceSize); - slices.reserve(std::distance(from, to)); - for (auto&& it = std::move(from); it <= to; ++it) { - slices.emplace_back(it, it + sliceSize); + for (auto i = 0ul; i <= inputs.size() - sliceSize; ++i) { + slices.emplace_back(); + slices[i].reserve(sliceSize); + for (auto j = i; j < i + sliceSize; j++) { + slices[i].push_back(inputs[j]); + } } return slices; } -} // namespace details - -template -uint64_t InputSelector::sum(const std::vector& amounts) noexcept { - auto accumulateFunctor = [](std::size_t sum, auto&& cur) noexcept { return sum + cur.amount; }; - return std::accumulate(cbegin(amounts), cend(amounts), 0_uz, accumulateFunctor); -} - -template -std::vector -InputSelector::filterOutDust(const std::vector& inputsIn, - int64_t byteFee) noexcept { - auto inputFeeLimit = static_cast(feeCalculator.calculateSingleInput(byteFee)); - return filterThreshold(inputsIn, inputFeeLimit); -} - -// Filters utxos that are dust template std::vector -InputSelector::filterThreshold(const std::vector& inputsIn, - uint64_t minimumAmount) noexcept { - std::vector filtered; - auto copyFunctor = [minimumAmount](auto&& cur) noexcept { return cur.amount > minimumAmount; }; - std::copy_if(cbegin(inputsIn), cend(inputsIn), std::back_inserter(filtered), copyFunctor); - return filtered; -} - -template -std::vector -InputSelector::select(int64_t targetValue, int64_t byteFee, int64_t numOutputs) { +InputSelector::select(uint64_t targetValue, uint64_t byteFee, uint64_t numOutputs) { // if target value is zero, no UTXOs are needed if (targetValue == 0) { return {}; } + // Get all possible utxo selections up to a maximum size, sort by total amount, increasing + std::vector sorted = filterOutDust(_inputs, byteFee); + std::sort( + sorted.begin(), + sorted.end(), + [](const TypeWithAmount& lhs, const TypeWithAmount& rhs) { + return lhs.amount < rhs.amount; + }); + // total values of utxos should be greater than targetValue - if (inputs.empty() || sum(inputs) < targetValue) { + if (sorted.empty() || sum(sorted) < targetValue) { return {}; } - assert(inputs.size() >= 1); + assert(sorted.size() >= 1); - // Get all possible utxo selections up to a maximum size, sort by total amount, increasing - const auto sortFunctor = [](auto&& l, auto&& r) noexcept { return l.amount < r.amount; }; - const auto sorted = sortCopy(inputs, sortFunctor); + // definitions for the following calculation + const auto doubleTargetValue = targetValue * 2; + + // Precompute maximum amount possible to obtain with given number of inputs const auto n = sorted.size(); - const auto maxWithXInputs = details::collectMaxWithXInputs(sorted); + std::vector maxWithXInputs = std::vector(); + maxWithXInputs.push_back(0); + int64_t maxSum = 0; + for (auto i = 0ul; i < n; ++i) { + maxSum += sorted[n - 1 - i].amount; + maxWithXInputs.push_back(maxSum); + } - auto sliceFunctor = [&sorted](int64_t numInputs, int64_t targetFee) noexcept { - auto slices = details::sliding(sorted, static_cast(numInputs)); - auto eraseFunctor = [targetFee](auto&& slice) noexcept { return sum(slice) < targetFee; }; - erase_if(slices, eraseFunctor); - return slices; + // difference from 2x targetValue + auto distFrom2x = [doubleTargetValue](uint64_t val) -> uint64_t { + if (val > doubleTargetValue) { + return val - doubleTargetValue; + } + return doubleTargetValue - val; }; - auto selectFunctor = - [numOutputs, byteFee, &maxWithXInputs, targetValue, &sliceFunctor, - this](int64_t numInputs, MaybeDust dustThreshold = std::nullopt) noexcept - -> std::optional> { - auto feeArgs = details::FeeCalculatorArgs{ - .inputs = numInputs, .outputs = numOutputs, .byteFee = byteFee}; - auto maybeTargetFee = details::calculateTargetFee(maxWithXInputs, feeCalculator, - feeArgs, targetValue, dustThreshold); - if (maybeTargetFee) { - auto slices = sliceFunctor(numInputs, *maybeTargetFee); - if (!slices.empty()) { - const auto doubleTargetValue = targetValue * 2; - const auto filteredInputs = filterOutDust( - details::selectOutDustElement(doubleTargetValue, dustThreshold, slices), - byteFee); - return std::make_optional(filteredInputs); - } - } - return std::nullopt; - }; - - const int64_t dustThreshold = feeCalculator.calculateSingleInput(byteFee); + const int64_t dustThreshold = dustCalculator->dustAmount(byteFee); // 1. Find a combination of the fewest inputs that is // (1) bigger than what we need // (2) closer to 2x the amount, // (3) and does not produce dust change. - for (int64_t numInputs = 1; numInputs <= n; ++numInputs) { - if (auto selected = selectFunctor(numInputs, dustThreshold); selected) { - return *selected; + for (size_t numInputs = 1; numInputs <= n; ++numInputs) { + const auto fee = feeCalculator.calculate(numInputs, numOutputs, byteFee); + const auto targetWithFeeAndDust = targetValue + fee + dustThreshold; + if (maxWithXInputs[numInputs] < targetWithFeeAndDust) { + // no way to satisfy with only numInputs inputs, skip + continue; + } + auto slices = slice(sorted, static_cast(numInputs)); + + slices.erase( + std::remove_if(slices.begin(), slices.end(), + [targetWithFeeAndDust](const std::vector& slice) { + return sum(slice) < targetWithFeeAndDust; + }), + slices.end()); + if (!slices.empty()) { + std::sort(slices.begin(), slices.end(), + [distFrom2x](const std::vector& lhs, + const std::vector& rhs) { + return distFrom2x(sum(lhs)) < distFrom2x(sum(rhs)); + }); + return slices.front(); } } // 2. If not, find a valid combination of outputs even if they produce dust change. - for (int64_t numInputs = 1; numInputs <= n; ++numInputs) { - if (auto selected = selectFunctor(numInputs); selected) { - return *selected; + for (size_t numInputs = 1; numInputs <= n; ++numInputs) { + const auto fee = feeCalculator.calculate(numInputs, numOutputs, byteFee); + const auto targetWithFee = targetValue + fee; + if (maxWithXInputs[numInputs] < targetWithFee) { + // no way to satisfy with only numInputs inputs, skip + continue; + } + auto slices = slice(sorted, static_cast(numInputs)); + slices.erase(std::remove_if(slices.begin(), slices.end(), + [targetWithFee](const std::vector& slice) { + return sum(slice) < targetWithFee; + }), + slices.end()); + if (!slices.empty()) { + return slices.front(); } } - return {}; + // If we couldn't find a combination of inputs to cover estimated transaction fee and the target amount, + // return the whole set of UTXOs. Later, the transaction fee will be calculated more accurately, + // and these UTXOs can be enough. + return sorted; } template @@ -195,26 +170,27 @@ std::vector InputSelector::selectSimple(int64_t if (targetValue == 0) { return {}; } - if (inputs.empty()) { + if (_inputs.empty()) { return {}; } + assert(_inputs.size() >= 1); - // target value is larger that original, but not by a factor of 2 (optimized for large UTXO + // target value is larger than original, but not by a factor of 2 (optimized for large UTXO // cases) const auto increasedTargetValue = (uint64_t)((double)targetValue * 1.1 + - feeCalculator.calculate(inputs.size(), numOutputs, byteFee) + 1000); + feeCalculator.calculate(_inputs.size(), numOutputs, byteFee) + 1000); - const int64_t dustThreshold = feeCalculator.calculateSingleInput(byteFee); + const int64_t dustThreshold = dustCalculator->dustAmount(byteFee); // Go through inputs in a single pass, in the order they appear, no optimization uint64_t sum = 0; std::vector selected; - for (auto&& input : inputs) { + for (auto& input : _inputs) { if (input.amount <= dustThreshold) { continue; // skip dust } - selected.emplace_back(input); + selected.push_back(input); sum += input.amount; if (sum >= increasedTargetValue) { // we have enough @@ -222,15 +198,19 @@ std::vector InputSelector::selectSimple(int64_t } } - // not enough - return {}; + // If we couldn't find a combination of inputs to cover estimated transaction fee and the target amount, + // return the whole set of UTXOs. Later, the transaction fee will be calculated more accurately, + // and these UTXOs can be enough. + return selected; } template std::vector InputSelector::selectMaxAmount(int64_t byteFee) noexcept { - return filterOutDust(inputs, byteFee); + return filterOutDust(_inputs, byteFee); } // Explicitly instantiate template class Bitcoin::InputSelector; + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/InputSelector.h b/src/Bitcoin/InputSelector.h index 2031143cd95..848dae390d4 100644 --- a/src/Bitcoin/InputSelector.h +++ b/src/Bitcoin/InputSelector.h @@ -1,12 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "FeeCalculator.h" +#include "DustCalculator.h" #include #include @@ -14,21 +13,23 @@ namespace TW::Bitcoin { -template // TypeWithAmount has to have a uint64_t amount +template // TypeWithAmount has to have an uint64_t amount class InputSelector { public: /// Selects unspent transactions to use given a target transaction value, using complete logic. /// - /// \returns the list of indices of selected inputs, or an empty list if there are insufficient - /// funds. - std::vector select(int64_t targetValue, int64_t byteFee, - int64_t numOutputs = 2); + /// \returns the list of indices of selected inputs. May return the entire list of UTXOs + /// even if they aren't enough to cover `targetValue + fee`. + /// That's because `InputSelector` has a rough segwit fee estimation algorithm, and the UTXOs can actually be enough. + std::vector select(uint64_t targetValue, uint64_t byteFee, + uint64_t numOutputs = 2); /// Selects unspent transactions to use given a target transaction value; /// Simplified version suitable for large number of inputs /// - /// \returns the list of indices of selected inputs, or an empty list if there are insufficient - /// funds. + /// \returns the list of indices of selected inputs. May return the entire list of UTXOs + /// even if they aren't enough to cover `targetValue + fee`. + /// That's because `InputSelector` has a rough segwit fee estimation algorithm, and the UTXOs can actually be enough. std::vector selectSimple(int64_t targetValue, int64_t byteFee, int64_t numOutputs = 2); @@ -36,12 +37,20 @@ class InputSelector { /// Return indices. One output and no change is assumed. std::vector selectMaxAmount(int64_t byteFee) noexcept; - /// Construct, using provided feeCalculator (see getFeeCalculator()). + /// Construct, using provided feeCalculator (see getFeeCalculator()) and dustCalculator (see getDustCalculator()). explicit InputSelector(const std::vector& inputs, - const FeeCalculator& feeCalculator) noexcept - : inputs(inputs), feeCalculator(feeCalculator) {} + const FeeCalculator& feeCalculator, + DustCalculatorShared dustCalculator) noexcept + : _inputs(inputs), + feeCalculator(feeCalculator), + dustCalculator(std::move(dustCalculator)) { + } + explicit InputSelector(const std::vector& inputs) noexcept - : InputSelector(inputs, getFeeCalculator(TWCoinTypeBitcoin)) {} + : _inputs(inputs), + feeCalculator(getFeeCalculator(TWCoinTypeBitcoin)), + dustCalculator(std::make_shared(TWCoinTypeBitcoin)) { + } /// Sum of input amounts static uint64_t sum(const std::vector& amounts) noexcept; @@ -53,8 +62,9 @@ class InputSelector { uint64_t minimumAmount) noexcept; private: - const std::vector inputs; + const std::vector _inputs; const FeeCalculator& feeCalculator; + const DustCalculatorShared dustCalculator; }; } // namespace TW::Bitcoin diff --git a/src/Bitcoin/MessageSigner.cpp b/src/Bitcoin/MessageSigner.cpp new file mode 100644 index 00000000000..af8a6565322 --- /dev/null +++ b/src/Bitcoin/MessageSigner.cpp @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "MessageSigner.h" +#include "Address.h" + +#include "Base64.h" +#include "BinaryCoding.h" +#include "Coin.h" +#include "Data.h" +#include "HexCoding.h" + +using namespace TW; + +namespace TW::Bitcoin { + +// length-encode a message string +Data messageToData(const std::string& message) { + Data d; + TW::encodeVarInt(message.size(), d); + TW::append(d, TW::data(message)); + return d; +} + +// append prefix and length-encode message string +Data messageToFullData(const std::string& message) { + Data d = messageToData(MessageSigner::MessagePrefix); + TW::append(d, messageToData(message)); + return d; +} + +Data MessageSigner::messageToHash(const std::string& message) { + Data d = messageToFullData(message); + return Hash::sha256d(d.data(), d.size()); +} + +std::string MessageSigner::signMessage(const PrivateKey& privateKey, const std::string& address, const std::string& message, bool compressed) { + if (!Address::isValid(address)) { + throw std::invalid_argument("Address is not valid (legacy) address"); + } + std::string addrFromKey; + if (compressed) { + const auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + addrFromKey = Address(pubKey, TW::p2pkhPrefix(TWCoinTypeBitcoin)).string(); + } else { + const auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + const auto keyHash = pubKey.hash(Data{TW::p2pkhPrefix(TWCoinTypeBitcoin)}, Hash::HasherSha256ripemd); + addrFromKey = Address(keyHash).string(); + } + if (addrFromKey != address) { + throw std::invalid_argument("Address does not match key"); + } + const auto messageHash = messageToHash(message); + const auto signature = privateKey.sign(messageHash, TWCurveSECP256k1); + + // The V value: add 31 (or 27 for compressed), and move to the first byte + const byte v = signature[SignatureRSLength] + PublicKey::SignatureVOffset + (compressed ? 4ul : 0ul); + auto sigAdjusted = Data{v}; + TW::append(sigAdjusted, TW::subData(signature, 0, SignatureRSLength)); + return Base64::encode(sigAdjusted); +} + +std::string MessageSigner::recoverAddressFromMessage(const std::string& message, const Data& signature) { + if (signature.size() < SignatureRSVLength) { + throw std::invalid_argument("signature too short"); + } + const auto messageHash = MessageSigner::messageToHash(message); + auto recId = signature[0]; + auto compressed = false; + if (recId >= PublicKey::SignatureVOffset + 4) { + recId -= 4; + compressed = true; + } + if (recId >= PublicKey::SignatureVOffset) { + recId -= PublicKey::SignatureVOffset; + } + + const auto publicKeyRecovered = PublicKey::recoverRaw(TW::subData(signature, 1), recId, messageHash); + + if (!compressed) { + // uncompressed public key + const auto keyHash = publicKeyRecovered.hash(Data{TW::p2pkhPrefix(TWCoinTypeBitcoin)}, Hash::HasherSha256ripemd); + return Bitcoin::Address(keyHash).string(); + } + // compressed + const auto publicKeyRecoveredCompressed = publicKeyRecovered.compressed(); + return Bitcoin::Address(publicKeyRecoveredCompressed, TW::p2pkhPrefix(TWCoinTypeBitcoin)).string(); +} + +bool MessageSigner::verifyMessage(const std::string& address, const std::string& message, const std::string& signature) noexcept { + try { + const auto signatureData = Base64::decode(signature); + return verifyMessage(address, message, signatureData); + } catch (...) { + return false; + } +} + +/// May throw +bool MessageSigner::verifyMessage(const std::string& address, const std::string& message, const Data& signature) { + if (!Bitcoin::Address::isValid(address)) { + throw std::invalid_argument("Input address invalid, must be valid legacy"); + } + const auto addressRecovered = recoverAddressFromMessage(message, signature); + return (addressRecovered == address); +} + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/MessageSigner.h b/src/Bitcoin/MessageSigner.h new file mode 100644 index 00000000000..f81f7f9bdb9 --- /dev/null +++ b/src/Bitcoin/MessageSigner.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "PrivateKey.h" + +#include + +namespace TW::Bitcoin { + +/// Class for message signing and verification. +/// +/// Bitcoin Core and some other wallets support a message signing & verification format, to create a proof (a signature) +/// that someone has access to the private keys of a specific address. +/// This feature currently works on old legacy addresses only. +class MessageSigner { + public: + /// Sign a message. + /// privateKey: the private key used for signing + /// address: the address that matches the privateKey, must be a legacy address (P2PKH) + /// message: A custom message which is input to the signing. + /// compressed: True by default, as addresses are generated from the hash of the compressed public key. + /// However, in some instances key hash is generated from the hash of the extended public key, + /// that's also supported here as well for compatibility. + /// Returns the signature, Base64-encoded. + /// Throws on invalid input. + static std::string signMessage(const PrivateKey& privateKey, const std::string& address, const std::string& message, bool compressed = true); + + /// Verify signature for a message. + /// address: address to use, only legacy is supported + /// message: the message signed (without prefix) + /// signature: in Base64-encoded form. + /// Returns false on any invalid input (does not throw). + static bool verifyMessage(const std::string& address, const std::string& message, const std::string& signature) noexcept; + + /// Verify signature for a message. + /// Address: address to use, only legacy is supported + /// message: the message signed (without prefix) + /// signature: in binary form. + /// May throw + static bool verifyMessage(const std::string& address, const std::string& message, const Data& signature); + + /// Recover address from signature and message. May throw. + static std::string recoverAddressFromMessage(const std::string& message, const Data& signature); + + /// Append prefix and compute hash for a message + static Data messageToHash(const std::string& message); + + static constexpr auto MessagePrefix = "Bitcoin Signed Message:\n"; + static const byte DigestLength = 32; + static const byte SignatureRSLength = 64; + static constexpr byte SignatureRSVLength = SignatureRSLength + 1; + static const byte VOffset = 27; +}; + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/OpCodes.h b/src/Bitcoin/OpCodes.h index f3a4405d697..c76b653949f 100644 --- a/src/Bitcoin/OpCodes.h +++ b/src/Bitcoin/OpCodes.h @@ -1,147 +1,150 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once enum OpCode { // push value OP_0 = 0x00, - OP_FALSE = OP_0, + OP_FALSE [[maybe_unused]] = OP_0, + // Note: values 0x01 -- 0x4b (1--75) mean that the next N bytes are interpreted as data pushed into the stack OP_PUSHDATA1 = 0x4c, OP_PUSHDATA2 = 0x4d, OP_PUSHDATA4 = 0x4e, - OP_1NEGATE = 0x4f, - OP_RESERVED = 0x50, + OP_1NEGATE [[maybe_unused]] = 0x4f, + OP_RESERVED [[maybe_unused]] = 0x50, OP_1 = 0x51, - OP_TRUE = OP_1, - OP_2 = 0x52, + OP_TRUE [[maybe_unused]] = OP_1, + OP_2 [[maybe_unused]] = 0x52, OP_3 = 0x53, - OP_4 = 0x54, - OP_5 = 0x55, - OP_6 = 0x56, - OP_7 = 0x57, - OP_8 = 0x58, + OP_4 [[maybe_unused]] = 0x54, + OP_5 [[maybe_unused]] = 0x55, + OP_6 [[maybe_unused]] = 0x56, + OP_7 [[maybe_unused]] = 0x57, + OP_8 [[maybe_unused]] = 0x58, OP_9 = 0x59, - OP_10 = 0x5a, - OP_11 = 0x5b, - OP_12 = 0x5c, - OP_13 = 0x5d, - OP_14 = 0x5e, - OP_15 = 0x5f, + OP_10 [[maybe_unused]] = 0x5a, + OP_11 [[maybe_unused]] = 0x5b, + OP_12 [[maybe_unused]] = 0x5c, + OP_13 [[maybe_unused]] = 0x5d, + OP_14 [[maybe_unused]] = 0x5e, + OP_15 [[maybe_unused]] = 0x5f, OP_16 = 0x60, // control - OP_NOP = 0x61, - OP_VER = 0x62, - OP_IF = 0x63, - OP_NOTIF = 0x64, - OP_VERIF = 0x65, - OP_VERNOTIF = 0x66, - OP_ELSE = 0x67, - OP_ENDIF = 0x68, - OP_VERIFY = 0x69, + OP_NOP [[maybe_unused]] = 0x61, + OP_VER [[maybe_unused]] = 0x62, + OP_IF [[maybe_unused]] = 0x63, + OP_NOTIF [[maybe_unused]] = 0x64, + OP_VERIF [[maybe_unused]] = 0x65, + OP_VERNOTIF [[maybe_unused]] = 0x66, + OP_ELSE [[maybe_unused]] = 0x67, + OP_ENDIF [[maybe_unused]] = 0x68, + OP_VERIFY [[maybe_unused]] = 0x69, OP_RETURN = 0x6a, // stack ops - OP_TOALTSTACK = 0x6b, - OP_FROMALTSTACK = 0x6c, - OP_2DROP = 0x6d, - OP_2DUP = 0x6e, - OP_3DUP = 0x6f, - OP_2OVER = 0x70, - OP_2ROT = 0x71, - OP_2SWAP = 0x72, - OP_IFDUP = 0x73, - OP_DEPTH = 0x74, - OP_DROP = 0x75, + OP_TOALTSTACK [[maybe_unused]] = 0x6b, + OP_FROMALTSTACK [[maybe_unused]] = 0x6c, + OP_2DROP [[maybe_unused]] = 0x6d, + OP_2DUP [[maybe_unused]] = 0x6e, + OP_3DUP [[maybe_unused]] = 0x6f, + OP_2OVER [[maybe_unused]] = 0x70, + OP_2ROT [[maybe_unused]] = 0x71, + OP_2SWAP [[maybe_unused]] = 0x72, + OP_IFDUP [[maybe_unused]] = 0x73, + OP_DEPTH [[maybe_unused]] = 0x74, + OP_DROP [[maybe_unused]] = 0x75, OP_DUP = 0x76, - OP_NIP = 0x77, - OP_OVER = 0x78, - OP_PICK = 0x79, - OP_ROLL = 0x7a, - OP_ROT = 0x7b, - OP_SWAP = 0x7c, - OP_TUCK = 0x7d, + OP_NIP [[maybe_unused]] = 0x77, + OP_OVER [[maybe_unused]] = 0x78, + OP_PICK [[maybe_unused]] = 0x79, + OP_ROLL [[maybe_unused]] = 0x7a, + OP_ROT [[maybe_unused]] = 0x7b, + OP_SWAP [[maybe_unused]] = 0x7c, + OP_TUCK [[maybe_unused]] = 0x7d, // splice ops - OP_CAT = 0x7e, - OP_SUBSTR = 0x7f, - OP_LEFT = 0x80, - OP_RIGHT = 0x81, - OP_SIZE = 0x82, + OP_CAT [[maybe_unused]] = 0x7e, + OP_SUBSTR [[maybe_unused]] = 0x7f, + OP_LEFT [[maybe_unused]] = 0x80, + OP_RIGHT [[maybe_unused]] = 0x81, + OP_SIZE [[maybe_unused]] = 0x82, // bit logic - OP_INVERT = 0x83, - OP_AND = 0x84, - OP_OR = 0x85, - OP_XOR = 0x86, + OP_INVERT [[maybe_unused]] = 0x83, + OP_AND [[maybe_unused]] = 0x84, + OP_OR [[maybe_unused]] = 0x85, + OP_XOR [[maybe_unused]] = 0x86, OP_EQUAL = 0x87, OP_EQUALVERIFY = 0x88, - OP_RESERVED1 = 0x89, - OP_RESERVED2 = 0x8a, + OP_RESERVED1 [[maybe_unused]] = 0x89, + OP_RESERVED2 [[maybe_unused]] = 0x8a, // numeric - OP_1ADD = 0x8b, - OP_1SUB = 0x8c, - OP_2MUL = 0x8d, - OP_2DIV = 0x8e, - OP_NEGATE = 0x8f, - OP_ABS = 0x90, - OP_NOT = 0x91, - OP_0NOTEQUAL = 0x92, + OP_1ADD [[maybe_unused]] = 0x8b, + OP_1SUB [[maybe_unused]] = 0x8c, + OP_2MUL [[maybe_unused]] = 0x8d, + OP_2DIV [[maybe_unused]] = 0x8e, + OP_NEGATE [[maybe_unused]] = 0x8f, + OP_ABS [[maybe_unused]] = 0x90, + OP_NOT [[maybe_unused]] = 0x91, + OP_0NOTEQUAL [[maybe_unused]] = 0x92, - OP_ADD = 0x93, - OP_SUB = 0x94, - OP_MUL = 0x95, - OP_DIV = 0x96, - OP_MOD = 0x97, - OP_LSHIFT = 0x98, - OP_RSHIFT = 0x99, + OP_ADD [[maybe_unused]] = 0x93, + OP_SUB [[maybe_unused]] = 0x94, + OP_MUL [[maybe_unused]] = 0x95, + OP_DIV [[maybe_unused]] = 0x96, + OP_MOD [[maybe_unused]] = 0x97, + OP_LSHIFT [[maybe_unused]] = 0x98, + OP_RSHIFT [[maybe_unused]] = 0x99, - OP_BOOLAND = 0x9a, - OP_BOOLOR = 0x9b, - OP_NUMEQUAL = 0x9c, - OP_NUMEQUALVERIFY = 0x9d, - OP_NUMNOTEQUAL = 0x9e, - OP_LESSTHAN = 0x9f, - OP_GREATERTHAN = 0xa0, - OP_LESSTHANOREQUAL = 0xa1, - OP_GREATERTHANOREQUAL = 0xa2, - OP_MIN = 0xa3, - OP_MAX = 0xa4, + OP_BOOLAND [[maybe_unused]] = 0x9a, + OP_BOOLOR [[maybe_unused]] = 0x9b, + OP_NUMEQUAL [[maybe_unused]] = 0x9c, + OP_NUMEQUALVERIFY [[maybe_unused]] = 0x9d, + OP_NUMNOTEQUAL [[maybe_unused]] = 0x9e, + OP_LESSTHAN [[maybe_unused]] = 0x9f, + OP_GREATERTHAN [[maybe_unused]] = 0xa0, + OP_LESSTHANOREQUAL [[maybe_unused]] = 0xa1, + OP_GREATERTHANOREQUAL [[maybe_unused]] = 0xa2, + OP_MIN [[maybe_unused]] = 0xa3, + OP_MAX [[maybe_unused]] = 0xa4, - OP_WITHIN = 0xa5, + OP_WITHIN [[maybe_unused]] = 0xa5, // crypto - OP_RIPEMD160 = 0xa6, - OP_SHA1 = 0xa7, - OP_SHA256 = 0xa8, + OP_RIPEMD160 [[maybe_unused]] = 0xa6, + OP_SHA1 [[maybe_unused]] = 0xa7, + OP_SHA256 [[maybe_unused]] = 0xa8, OP_HASH160 = 0xa9, - OP_HASH256 = 0xaa, - OP_CODESEPARATOR = 0xab, + OP_HASH256 [[maybe_unused]] = 0xaa, + OP_CODESEPARATOR [[maybe_unused]] = 0xab, OP_CHECKSIG = 0xac, - OP_CHECKSIGVERIFY = 0xad, + OP_CHECKSIGVERIFY [[maybe_unused]] = 0xad, OP_CHECKMULTISIG = 0xae, - OP_CHECKMULTISIGVERIFY = 0xaf, + OP_CHECKMULTISIGVERIFY [[maybe_unused]] = 0xaf, // expansion - OP_NOP1 = 0xb0, + OP_NOP1 [[maybe_unused]] = 0xb0, OP_CHECKLOCKTIMEVERIFY = 0xb1, - OP_NOP2 = OP_CHECKLOCKTIMEVERIFY, + OP_NOP2 [[maybe_unused]] = OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY = 0xb2, - OP_NOP3 = OP_CHECKSEQUENCEVERIFY, - OP_NOP4 = 0xb3, - OP_NOP5 = 0xb4, - OP_NOP6 = 0xb5, - OP_NOP7 = 0xb6, - OP_NOP8 = 0xb7, - OP_NOP9 = 0xb8, - OP_NOP10 = 0xb9, + OP_NOP3 [[maybe_unused]] = OP_CHECKSEQUENCEVERIFY, + OP_NOP4 [[maybe_unused]] = 0xb3, + OP_NOP5 [[maybe_unused]] = 0xb4, + OP_CHECKBLOCKATHEIGHT = OP_NOP5, + OP_NOP6 [[maybe_unused]] = 0xb5, + OP_NOP7 [[maybe_unused]] = 0xb6, + OP_NOP8 [[maybe_unused]] = 0xb7, + OP_NOP9 [[maybe_unused]] = 0xb8, + OP_NOP10 [[maybe_unused]] = 0xb9, - OP_INVALIDOPCODE = 0xff, + // firo, see: https://github.com/firoorg/firo/blob/8bd4abdea223e22f15c36e7d2d42618dc843e2ef/src/script/script.h#L212 + OP_EXCHANGEADDR = 0xe0, + + OP_INVALIDOPCODE [[maybe_unused]] = 0xff, }; static inline bool TWOpCodeIsSmallInteger(uint8_t opcode) { diff --git a/src/Bitcoin/OutPoint.cpp b/src/Bitcoin/OutPoint.cpp index 611c837003c..fd65e3a6a51 100644 --- a/src/Bitcoin/OutPoint.cpp +++ b/src/Bitcoin/OutPoint.cpp @@ -1,17 +1,19 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "OutPoint.h" #include "../BinaryCoding.h" -using namespace TW::Bitcoin; +namespace TW::Bitcoin { -void OutPoint::encode(Data& data) const { +void OutPoint::encode(Data& data) const noexcept { std::copy(std::begin(hash), std::end(hash), std::back_inserter(data)); encode32LE(index, data); // sequence is encoded in TransactionInputs + // tree is only for DCR } + +} // namespace TW::Bitcoin + diff --git a/src/Bitcoin/OutPoint.h b/src/Bitcoin/OutPoint.h index eb5e61f496d..bb7f9a951c1 100644 --- a/src/Bitcoin/OutPoint.h +++ b/src/Bitcoin/OutPoint.h @@ -1,12 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "algorithm/to_array.h" +#include "Data.h" #include "../proto/Bitcoin.pb.h" #include @@ -16,55 +15,42 @@ namespace TW::Bitcoin { /// Bitcoin transaction out-point reference. -class OutPoint { - public: +struct OutPoint { /// The hash of the referenced transaction. std::array hash; /// The index of the specific output in the transaction. uint32_t index; - /// Sequence number, matches sequence from Proto::OutPoint (not always used, see also TransactionInput.sequence) + /// Sequence number, matches sequence from Proto::OutPoint (not always used, see also + /// TransactionInput.sequence) uint32_t sequence; - OutPoint() = default; + /// The tree in utxo, only works for DCR + int8_t tree; + + OutPoint() noexcept = default; /// Initializes an out-point reference with hash, index. template - OutPoint(const T& h, uint32_t index, uint32_t sequence = 0 ) { - std::copy(std::begin(h), std::end(h), hash.begin()); - this->index = index; - this->sequence = sequence; - } + OutPoint(const T& h, uint32_t index, uint32_t sequence = 0, int8_t tree = 0) noexcept + : hash(to_array(h)), index(index), sequence(sequence), tree(tree) {} /// Initializes an out-point from a Protobuf out-point. - OutPoint(const Proto::OutPoint& other) { + OutPoint(const Proto::OutPoint& other) noexcept + : OutPoint(other.hash(), other.index(), other.sequence(), int8_t(other.tree())) { assert(other.hash().size() == 32); - std::copy(other.hash().begin(), other.hash().end(), hash.begin()); - index = other.index(); - sequence = other.sequence(); } /// Encodes the out-point into the provided buffer. - void encode(Data& data) const; - - friend bool operator<(const OutPoint& a, const OutPoint& b) { - int cmp = std::memcmp(a.hash.data(), b.hash.data(), 32); - return cmp < 0 || (cmp == 0 && a.index < b.index); - } - - friend bool operator==(const OutPoint& a, const OutPoint& b) { - int cmp = std::memcmp(a.hash.data(), b.hash.data(), 32); - return (cmp == 0 && a.index == b.index); - } - - friend bool operator!=(const OutPoint& a, const OutPoint& b) { return !(a == b); } + void encode(Data& data) const noexcept; Proto::OutPoint proto() const { auto op = Proto::OutPoint(); op.set_hash(std::string(hash.begin(), hash.end())); op.set_index(index); op.set_sequence(sequence); + op.set_tree(int32_t(tree)); return op; } }; diff --git a/src/Bitcoin/Psbt.cpp b/src/Bitcoin/Psbt.cpp new file mode 100644 index 00000000000..99484238ea1 --- /dev/null +++ b/src/Bitcoin/Psbt.cpp @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Psbt.h" +#include "rust/Wrapper.h" + +namespace TW::Bitcoin { + +Data Psbt::sign(const Data &input, TWCoinType coin) { + const Rust::TWDataWrapper inputRust = input; + const Rust::TWDataWrapper outputRust = Rust::tw_bitcoin_psbt_sign(inputRust.get(), static_cast(coin)); + return outputRust.toDataOrDefault(); +} + +Data Psbt::plan(const Data& input, TWCoinType coin) { + const Rust::TWDataWrapper inputRust = input; + const Rust::TWDataWrapper outputRust = Rust::tw_bitcoin_psbt_plan(inputRust.get(), static_cast(coin)); + return outputRust.toDataOrDefault(); +} + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Psbt.h b/src/Bitcoin/Psbt.h new file mode 100644 index 00000000000..7ee128ea129 --- /dev/null +++ b/src/Bitcoin/Psbt.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "TrustWalletCore/TWCoinType.h" + +namespace TW::Bitcoin { + +class Psbt { +public: + /// Signs a PSBT (Partially Signed Bitcoin Transaction) specified by the signing input and coin type. + static Data sign(const Data& input, TWCoinType coin); + + /// Plans a PSBT (Partially Signed Bitcoin Transaction). + /// Can be used to get the transaction detailed decoded from PSBT. + static Data plan(const Data& input, TWCoinType coin); +}; + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Script.cpp b/src/Bitcoin/Script.cpp index 126e165fd3a..1d2112769d4 100644 --- a/src/Bitcoin/Script.cpp +++ b/src/Bitcoin/Script.cpp @@ -1,35 +1,28 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Script.h" +// Copyright © 2017 Trust Wallet. #include "Address.h" #include "CashAddress.h" +#include "ExchangeAddress.h" +#include "OpCodes.h" +#include "Script.h" #include "SegwitAddress.h" - -#include "../Base58.h" -#include "../Coin.h" +#include "proto/Bitcoin.pb.h" +#include #include "../BinaryCoding.h" -#include "../Data.h" +#include "../Coin.h" #include "../Decred/Address.h" #include "../Groestlcoin/Address.h" -#include "../Hash.h" -#include "../PublicKey.h" #include "../Zcash/TAddress.h" - -#include "OpCodes.h" +#include "../Zen/Address.h" #include #include #include -#include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { Data Script::hash() const { return Hash::ripemd(Hash::sha256(bytes)); @@ -41,6 +34,12 @@ bool Script::isPayToScriptHash() const { bytes[22] == OP_EQUAL; } +bool Script::isPayToScriptHashReplay() const { + // Extra-fast test for pay-to-script-hash-replay + return bytes.size() == 61 && bytes[0] == OP_HASH160 && bytes[1] == 0x14 && + bytes[22] == OP_EQUAL && bytes.back() == OP_CHECKBLOCKATHEIGHT; +} + bool Script::isPayToWitnessScriptHash() const { // Extra-fast test for pay-to-witness-script-hash return bytes.size() == 34 && bytes[0] == OP_0 && bytes[1] == 0x20; @@ -89,6 +88,28 @@ bool Script::matchPayToPublicKeyHash(Data& result) const { return false; } +// see: https://github.com/firoorg/firo/blob/8bd4abdea223e22f15c36e7d2d42618dc843e2ef/src/script/standard.cpp#L355 +bool Script::matchPayToExchangePublicKeyHash(Data& result) const { + if (bytes.size() == 26 && bytes[0] == OP_EXCHANGEADDR && bytes[1] == OP_DUP && bytes[2] == OP_HASH160 && bytes[3] == 20 && + bytes[24] == OP_EQUALVERIFY && bytes[25] == OP_CHECKSIG) { + result.clear(); + std::copy(std::begin(bytes) + 4, std::begin(bytes) + 4 + 20, std::back_inserter(result)); + return true; + } + return false; +} + +bool Script::matchPayToPublicKeyHashReplay(Data& result) const { + if (bytes.size() == 63 && bytes[0] == OP_DUP && bytes[1] == OP_HASH160 && bytes[2] == 20 && + bytes[23] == OP_EQUALVERIFY && bytes[24] == OP_CHECKSIG && bytes[25] == 32 && + bytes.back() == OP_CHECKBLOCKATHEIGHT) { + result.clear(); + std::copy(std::begin(bytes) + 3, std::begin(bytes) + 3 + 20, std::back_inserter(result)); + return true; + } + return false; +} + bool Script::matchPayToScriptHash(Data& result) const { if (!isPayToScriptHash()) { return false; @@ -98,6 +119,16 @@ bool Script::matchPayToScriptHash(Data& result) const { return true; } +bool Script::matchPayToScriptHashReplay(Data& result) const { + if (!isPayToScriptHashReplay()) { + return false; + } + result.clear(); + std::copy(std::begin(bytes) + 2, std::begin(bytes) + 22, std::back_inserter(result)); + return true; +} + + bool Script::matchPayToWitnessPublicKeyHash(Data& result) const { if (!isPayToWitnessPublicKeyHash()) { return false; @@ -148,8 +179,8 @@ bool Script::matchMultisig(std::vector& keys, int& required) const { return false; } - auto expectedCount = decodeNumber(opcode); - if (keys.size() != expectedCount || expectedCount < required) { + std::size_t expectedCount = decodeNumber(opcode); + if (keys.size() != expectedCount || expectedCount < static_cast(required)) { return false; } if (it + 1 != bytes.size()) { @@ -227,6 +258,46 @@ Script Script::buildPayToPublicKeyHash(const Data& hash) { return script; } +// see: https://github.com/firoorg/firo/blob/8bd4abdea223e22f15c36e7d2d42618dc843e2ef/src/script/standard.cpp#L355 +Script Script::buildPayToExchangePublicKeyHash(const Data& hash) { + assert(hash.size() == 20); + Script script; + script.bytes.push_back(OP_EXCHANGEADDR); + script.bytes.push_back(OP_DUP); + script.bytes.push_back(OP_HASH160); + script.bytes.push_back(20); + append(script.bytes, hash); + script.bytes.push_back(OP_EQUALVERIFY); + script.bytes.push_back(OP_CHECKSIG); + return script; +} + +Script Script::buildPayToPublicKeyHashReplay(const Data& hash, const Data& blockHash, int64_t blockHeight) { + assert(hash.size() == 20); + assert(blockHash.size() == 32); + Script script; + script.bytes.push_back(OP_DUP); + script.bytes.push_back(OP_HASH160); + script.bytes.push_back(20); + append(script.bytes, hash); + script.bytes.push_back(OP_EQUALVERIFY); + script.bytes.push_back(OP_CHECKSIG); + + // blockhash + script.bytes.push_back(32); + append(script.bytes, blockHash); + + // blockheight + auto blockHeightData = encodeNumber(blockHeight); + // blockHeight size will never beyond 1 byte size + script.bytes.push_back(static_cast(blockHeightData.size())); + append(script.bytes, blockHeightData); + script.bytes.push_back(OP_CHECKBLOCKATHEIGHT); + + return script; +} + + Script Script::buildPayToScriptHash(const Data& scriptHash) { assert(scriptHash.size() == 20); Script script; @@ -237,11 +308,48 @@ Script Script::buildPayToScriptHash(const Data& scriptHash) { return script; } +Script Script::buildPayToScriptHashReplay(const Data& scriptHash, const Data& blockHash, int64_t blockHeight) { + assert(scriptHash.size() == 20); + assert(blockHash.size() == 32); + Script script; + script.bytes.push_back(OP_HASH160); + script.bytes.push_back(20); + append(script.bytes, scriptHash); + script.bytes.push_back(OP_EQUAL); + + // blockhash + script.bytes.push_back(32); + append(script.bytes, blockHash); + + // blockheight + auto blockHeightData = encodeNumber(blockHeight); + // blockHeight size will never beyond 1 byte size + script.bytes.push_back(static_cast(blockHeightData.size())); + append(script.bytes, blockHeightData); + script.bytes.push_back(OP_CHECKBLOCKATHEIGHT); + + return script; +} + + +// Append to the buffer the length for the upcoming data (push). Supported length range: 0-75 bytes +void pushDataLength(Data& buffer, size_t len) { + assert(len <= 255); + if (len < static_cast(OP_PUSHDATA1)) { + // up to 75 bytes, simple OP_PUSHBYTES with len + buffer.push_back(static_cast(len)); + return; + } + // 75 < len < 256, OP_PUSHDATA with 1-byte len + buffer.push_back(OP_PUSHDATA1); + buffer.push_back(static_cast(len)); +} + Script Script::buildPayToV0WitnessProgram(const Data& program) { assert(program.size() == 20 || program.size() == 32); Script script; script.bytes.push_back(OP_0); - script.bytes.push_back(static_cast(program.size())); + pushDataLength(script.bytes, static_cast(program.size())); append(script.bytes, program); assert(script.bytes.size() == 22 || script.bytes.size() == 34); return script; @@ -261,19 +369,23 @@ Script Script::buildPayToV1WitnessProgram(const Data& publicKey) { assert(publicKey.size() == 32); Script script; script.bytes.push_back(OP_1); - script.bytes.push_back(static_cast(publicKey.size())); + pushDataLength(script.bytes, static_cast(publicKey.size())); append(script.bytes, publicKey); assert(script.bytes.size() == 34); return script; } Script Script::buildOpReturnScript(const Data& data) { - static const size_t MaxOpReturnLength = 64; + if (data.size() > MaxOpReturnLength) { + // data too long, cannot fit, fail (do not truncate) + return {}; + } + assert(data.size() <= MaxOpReturnLength); Script script; script.bytes.push_back(OP_RETURN); - size_t size = std::min(data.size(), MaxOpReturnLength); - script.bytes.push_back(static_cast(size)); - script.bytes.insert(script.bytes.end(), data.begin(), data.begin() + size); + pushDataLength(script.bytes, data.size()); + script.bytes.insert(script.bytes.end(), data.begin(), data.begin() + data.size()); + assert(script.bytes.size() <= 83); // max script length, must always hold return script; } @@ -282,7 +394,46 @@ void Script::encode(Data& data) const { std::copy(std::begin(bytes), std::end(bytes), std::back_inserter(data)); } +Data Script::encodeNumber(int64_t n) { + Data result; + // check bitcoin Script::push_int64 + if (n == -1 || (n >= 1 && n <= 16)) { + result.push_back(OP_1 + uint8_t(n - 1)); + return result; + } + if (n == 0) { + result.push_back(OP_0); + return result; + } + + const bool neg = n < 0; + uint64_t absvalue = neg ? -n : n; + + while (absvalue) { + result.push_back(absvalue & 0xff); + absvalue >>= 8; + } + + if (result.back() & 0x80) { + result.push_back(neg ? 0x80 : 0); + } else if (neg) { + result.back() |= 0x80; + } + return result; +} + +bool isLtcP2sh(enum TWCoinType coin, byte start) { + // For ltc, we need to support legacy p2sh which starts with 5. + // Here we check prefix 5 and 50 in case of wallet-core changing its config value. + // Ref: https://github.com/litecoin-project/litecoin/blob/0.21/src/chainparams.cpp#L128 + if (TWCoinTypeLitecoin == coin && (5 == start || 50 == start)) { + return true; + } + return false; +} + Script Script::lockScriptForAddress(const std::string& string, enum TWCoinType coin) { + // First try legacy address, for all coins if (Address::isValid(string)) { auto address = Address(string); auto p2pkh = TW::p2pkhPrefix(coin); @@ -293,16 +444,23 @@ Script Script::lockScriptForAddress(const std::string& string, enum TWCoinType c data.reserve(Address::size - 1); std::copy(address.bytes.begin() + 1, address.bytes.end(), std::back_inserter(data)); return buildPayToPublicKeyHash(data); - } else if (p2sh == address.bytes[0]) { + } else if (p2sh == address.bytes[0] + || isLtcP2sh(coin, address.bytes[0])) { // address starts with 3/M auto data = Data(); data.reserve(Address::size - 1); std::copy(address.bytes.begin() + 1, address.bytes.end(), std::back_inserter(data)); return buildPayToScriptHash(data); } - } else if (SegwitAddress::isValid(string)) { - const auto result = SegwitAddress::decode(string); - // address starts with bc/ltc + return {}; + } + + // Second, try Segwit address, for all coins; also check HRP + if (const auto result = SegwitAddress::decode(string); + std::get<2>(result) && + (std::get<1>(result) == stringForHRP(TW::hrp(coin)) || (coin == TWCoinTypeBitcoin && std::get<1>(result) == SegwitAddress::TestnetPrefix)) + ) { + // address starts with bc/ltc/... const auto address = std::get<0>(result); if (address.witnessVersion == 0) { return buildPayToV0WitnessProgram(address.witnessProgram); @@ -310,43 +468,98 @@ Script Script::lockScriptForAddress(const std::string& string, enum TWCoinType c if (address.witnessVersion == 1 && address.witnessProgram.size() == 32) { return buildPayToV1WitnessProgram(address.witnessProgram); } - } else if (BitcoinCashAddress::isValid(string)) { - auto address = BitcoinCashAddress(string); - auto bitcoinAddress = address.legacyAddress(); - return lockScriptForAddress(bitcoinAddress.string(), TWCoinTypeBitcoinCash); - } else if (Decred::Address::isValid(string)) { - auto bytes = Base58::bitcoin.decodeCheck(string, Hash::HasherBlake256d); - if (bytes[1] == TW::p2pkhPrefix(TWCoinTypeDecred)) { - return buildPayToPublicKeyHash(Data(bytes.begin() + 2, bytes.end())); - } - if (bytes[1] == TW::p2shPrefix(TWCoinTypeDecred)) { - return buildPayToScriptHash(Data(bytes.begin() + 2, bytes.end())); - } - } else if (ECashAddress::isValid(string)) { - auto address = ECashAddress(string); - auto bitcoinAddress = address.legacyAddress(); - return lockScriptForAddress(bitcoinAddress.string(), TWCoinTypeECash); - } else if (Groestlcoin::Address::isValid(string)) { - auto address = Groestlcoin::Address(string); - auto data = Data(); - data.reserve(Address::size - 1); - std::copy(address.bytes.begin() + 1, address.bytes.end(), std::back_inserter(data)); - if (address.bytes[0] == TW::p2pkhPrefix(TWCoinTypeGroestlcoin)) { - return buildPayToPublicKeyHash(data); - } - if (address.bytes[0] == TW::p2shPrefix(TWCoinTypeGroestlcoin)) { - return buildPayToScriptHash(data); - } - } else if (Zcash::TAddress::isValid(string)) { - auto address = Zcash::TAddress(string); + return {}; + } + + // Thirdly, coin-specific address formats + switch (coin) { + case TWCoinTypeBitcoinCash: + if (BitcoinCashAddress::isValid(string)) { + auto address = BitcoinCashAddress(string); + auto bitcoinAddress = address.legacyAddress(); + return lockScriptForAddress(bitcoinAddress.string(), TWCoinTypeBitcoinCash); + } + return {}; + + case TWCoinTypeDecred: + if (Decred::Address::isValid(string)) { + auto bytes = Base58::decodeCheck(string, Rust::Base58Alphabet::Bitcoin, Hash::HasherBlake256d); + if (bytes[1] == TW::p2pkhPrefix(TWCoinTypeDecred)) { + return buildPayToPublicKeyHash(Data(bytes.begin() + 2, bytes.end())); + } + if (bytes[1] == TW::p2shPrefix(TWCoinTypeDecred)) { + return buildPayToScriptHash(Data(bytes.begin() + 2, bytes.end())); + } + } + return {}; + + case TWCoinTypeECash: + if (ECashAddress::isValid(string)) { + auto address = ECashAddress(string); + auto bitcoinAddress = address.legacyAddress(); + return lockScriptForAddress(bitcoinAddress.string(), TWCoinTypeECash); + } + return {}; + + case TWCoinTypeFiro: + if (ExchangeAddress::isValid(string)) { + auto address = ExchangeAddress(string); + auto data = Data(); + data.reserve(ExchangeAddress::size - 3); + std::copy(address.bytes.begin() + 3, address.bytes.end(), std::back_inserter(data)); + return buildPayToExchangePublicKeyHash(data); + } + return {}; + + case TWCoinTypeGroestlcoin: + if (Groestlcoin::Address::isValid(string)) { + auto address = Groestlcoin::Address(string); + auto data = Data(); + data.reserve(Groestlcoin::Address::size - 1); + std::copy(address.bytes.begin() + 1, address.bytes.end(), std::back_inserter(data)); + if (address.bytes[0] == TW::p2pkhPrefix(TWCoinTypeGroestlcoin)) { + return buildPayToPublicKeyHash(data); + } + if (address.bytes[0] == TW::p2shPrefix(TWCoinTypeGroestlcoin)) { + return buildPayToScriptHash(data); + } + } + return {}; + + case TWCoinTypeZcash: + case TWCoinTypeZelcash: + if (Zcash::TAddress::isValid(string)) { + auto address = Zcash::TAddress(string); + auto data = Data(); + data.reserve(Zcash::TAddress::size - 2); + std::copy(address.bytes.begin() + 2, address.bytes.end(), std::back_inserter(data)); + if (address.bytes[1] == TW::p2pkhPrefix(TWCoinTypeZcash)) { + return buildPayToPublicKeyHash(data); + } else if (address.bytes[1] == TW::p2shPrefix(TWCoinTypeZcash)) { + return buildPayToScriptHash(data); + } + } + return {}; + + default: + return {}; + } +} + +Script Script::lockScriptForAddress(const std::string& string, enum TWCoinType coin, const Data& blockHash, int64_t blockHeight) { + if (Zen::Address::isValid(string)) { + auto address = Zen::Address(string); auto data = Data(); - data.reserve(Address::size - 2); + data.reserve(Zen::Address::size - 2); std::copy(address.bytes.begin() + 2, address.bytes.end(), std::back_inserter(data)); - if (address.bytes[1] == TW::p2pkhPrefix(TWCoinTypeZcash)) { - return buildPayToPublicKeyHash(data); - } else if (address.bytes[1] == TW::p2shPrefix(TWCoinTypeZcash)) { - return buildPayToScriptHash(data); + if (address.bytes[1] == TW::p2pkhPrefix(TWCoinTypeZen)) { + return buildPayToPublicKeyHashReplay(data, blockHash, blockHeight); + } else if (address.bytes[1] == TW::p2shPrefix(TWCoinTypeZen)) { + return buildPayToScriptHashReplay(data, blockHash, blockHeight); } } - return {}; + + return lockScriptForAddress(string, coin); } + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Script.h b/src/Bitcoin/Script.h index 83de655387c..dc48452604f 100644 --- a/src/Bitcoin/Script.h +++ b/src/Bitcoin/Script.h @@ -1,17 +1,18 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" +#include "rust/bindgen/WalletCoreRSBindgen.h" #include "OpCodes.h" #include +#include "proto/Bitcoin.pb.h" #include +#include #include #include @@ -19,6 +20,9 @@ namespace TW::Bitcoin { class Script { public: + // Maximum length for OP_RETURN data + static const size_t MaxOpReturnLength = 80; + /// Script raw bytes. Data bytes; @@ -29,8 +33,8 @@ class Script { template Script(It begin, It end) : bytes(begin, end) {} - /// Initializaes a script with a collection of raw bytes by moving. - explicit Script(const Data& bytes) : bytes(bytes) {} + /// Initializes a script with a collection of raw bytes by moving. + explicit Script(Data bytes) : bytes(std::move(bytes)) {} /// Whether the script is empty. bool empty() const { return bytes.empty(); } @@ -41,13 +45,17 @@ class Script { /// Determines whether this is a pay-to-script-hash (P2SH) script. bool isPayToScriptHash() const; + /// Determines whether this is a pay-to-script-hash-replay (P2SH) script. + /// Only apply for zen + bool isPayToScriptHashReplay() const; + /// Determines whether this is a pay-to-witness-script-hash (P2WSH) script. bool isPayToWitnessScriptHash() const; /// Determines whether this is a pay-to-witness-public-key-hash (P2WPKH) script. bool isPayToWitnessPublicKeyHash() const; - /// Determines whether this is a witness programm script. + /// Determines whether this is a witness program script. bool isWitnessProgram() const; /// Matches the script to a pay-to-public-key (P2PK) script. @@ -56,9 +64,21 @@ class Script { /// Matches the script to a pay-to-public-key-hash (P2PKH). bool matchPayToPublicKeyHash(Data& keyHash) const; + /// Matches the script to a pay-to-exchange-public-key-hash (P2PKH). + /// Only apply for firo + bool matchPayToExchangePublicKeyHash(Data& keyHash) const; + + /// Matches the script to a pay-to-public-key-hash-replay (P2PKH). + /// Only apply for zen + bool matchPayToPublicKeyHashReplay(Data& keyHash) const; + /// Matches the script to a pay-to-script-hash (P2SH). bool matchPayToScriptHash(Data& scriptHash) const; + /// Matches the script to a pay-to-script-hash-replay (P2SH). + /// Only apply for zen + bool matchPayToScriptHashReplay(Data& scriptHash) const; + /// Matches the script to a pay-to-witness-public-key-hash (P2WPKH). bool matchPayToWitnessPublicKeyHash(Data& keyHash) const; @@ -69,14 +89,26 @@ class Script { bool matchMultisig(std::vector& publicKeys, int& required) const; /// Builds a pay-to-public-key (P2PK) script from a public key. - static Script buildPayToPublicKey(const Data& publickKey); + static Script buildPayToPublicKey(const Data& publicKey); /// Builds a pay-to-public-key-hash (P2PKH) script from a public key hash. static Script buildPayToPublicKeyHash(const Data& hash); + /// Builds a pay-to-exchange-public-key-hash script from a public key hash. + /// This will apply for firo. + static Script buildPayToExchangePublicKeyHash(const Data& hash); + + /// Builds a pay-to-public-key-hash-replay (P2PKH) script from a public key hash. + /// This will apply for zen + static Script buildPayToPublicKeyHashReplay(const Data& hash, const Data& blockHash, int64_t blockHeight); + /// Builds a pay-to-script-hash (P2SH) script from a script hash. static Script buildPayToScriptHash(const Data& scriptHash); + /// Builds a pay-to-script-hash-replay (P2SH) script from a script hash. + /// This will apply for zen + static Script buildPayToScriptHashReplay(const Data& scriptHash, const Data& blockHash, int64_t blockHeight); + /// Builds a pay-to-witness-public-key-hash (P2WPKH) script from a public /// key hash. static Script buildPayToWitnessPublicKeyHash(const Data& hash); @@ -90,13 +122,17 @@ class Script { /// Builds a V1 pay-to-witness-program script, P2TR (from a 32-byte Schnorr public key). static Script buildPayToV1WitnessProgram(const Data& publicKey); - /// Builds an OP_RETURN script with given data + /// Builds an OP_RETURN script with given data. Returns empty script on error, if data is too long (>80). static Script buildOpReturnScript(const Data& data); /// Builds a appropriate lock script for the given /// address. static Script lockScriptForAddress(const std::string& address, enum TWCoinType coin); + /// Builds a appropriate lock script for the given + /// address with blockhash and blockheight. + static Script lockScriptForAddress(const std::string& address, enum TWCoinType coin, const Data& blockHash, int64_t blockHeight); + /// Encodes the script. void encode(Data& data) const; @@ -109,6 +145,9 @@ class Script { return OP_1 + uint8_t(n - 1); } + /// Encodes an integer + static Data encodeNumber(int64_t n); + /// Decodes a small integer static inline int decodeNumber(uint8_t opcode) { if (opcode == OP_0) { diff --git a/src/Bitcoin/SegwitAddress.cpp b/src/Bitcoin/SegwitAddress.cpp index 2a2619aa1f4..70ec462d391 100644 --- a/src/Bitcoin/SegwitAddress.cpp +++ b/src/Bitcoin/SegwitAddress.cpp @@ -1,17 +1,14 @@ // Copyright © 2017 Pieter Wuille -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "SegwitAddress.h" #include "../Bech32.h" #include -#include -using namespace TW::Bitcoin; +namespace TW::Bitcoin { bool SegwitAddress::isValid(const std::string& string) { return std::get<2>(decode(string)); @@ -103,3 +100,5 @@ std::pair SegwitAddress::fromRaw(const std::string& hrp, co return std::make_pair(SegwitAddress(hrp, data[0], conv), true); } + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/SegwitAddress.h b/src/Bitcoin/SegwitAddress.h index 480b6694d49..8d65f7d83fc 100644 --- a/src/Bitcoin/SegwitAddress.h +++ b/src/Bitcoin/SegwitAddress.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../PublicKey.h" -#include "../Data.h" +#include "Data.h" #include #include @@ -31,6 +29,9 @@ class SegwitAddress { /// Witness program. Data witnessProgram; + // Prefix for Bitcoin Testnet Segwit addresses + static constexpr auto TestnetPrefix = "tb"; + /// Determines whether a string makes a valid Bech32 address. static bool isValid(const std::string& string); @@ -47,6 +48,9 @@ class SegwitAddress { /// Taproot (v>=1) is not supported by this method. SegwitAddress(const PublicKey& publicKey, std::string hrp); + /// Create a testnet address + static SegwitAddress createTestnetFromPublicKey(const PublicKey& publicKey) { return SegwitAddress(publicKey, TestnetPrefix); } + /// Decodes a SegWit address. /// /// \returns a tuple with the address, hrp, and a success flag. diff --git a/src/Bitcoin/SigHashType.h b/src/Bitcoin/SigHashType.h index 72329e6a477..34d4ccc4a94 100644 --- a/src/Bitcoin/SigHashType.h +++ b/src/Bitcoin/SigHashType.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Bitcoin/SignatureBuilder.cpp b/src/Bitcoin/SignatureBuilder.cpp index d791ea75a5e..0e1f621a8d0 100644 --- a/src/Bitcoin/SignatureBuilder.cpp +++ b/src/Bitcoin/SignatureBuilder.cpp @@ -1,26 +1,22 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "SignatureBuilder.h" - #include "SigHashType.h" #include "TransactionInput.h" #include "TransactionOutput.h" -#include "InputSelector.h" #include "../BinaryCoding.h" -#include "../Hash.h" #include "../HexCoding.h" +#include "../BitcoinDiamond/Transaction.h" #include "../Groestlcoin/Transaction.h" +#include "../Verge/Transaction.h" #include "../Zcash/Transaction.h" #include "../Zcash/TransactionBuilder.h" -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { template Result SignatureBuilder::sign() { @@ -28,23 +24,23 @@ Result SignatureBuilder:: // plan with error, fail return Result::failure(plan.error); } - if (transaction.inputs.size() == 0 || plan.utxos.size() == 0) { + if (_transaction.inputs.size() == 0 || plan.utxos.size() == 0) { return Result::failure(Common::Proto::Error_missing_input_utxos); } - transactionToSign = transaction; + transactionToSign = _transaction; transactionToSign.inputs.clear(); - std::copy(std::begin(transaction.inputs), std::end(transaction.inputs), + std::copy(std::begin(_transaction.inputs), std::end(_transaction.inputs), std::back_inserter(transactionToSign.inputs)); const auto hashSingle = hashTypeIsSingle(input.hashType); - for (auto i = 0; i < plan.utxos.size(); i++) { + for (auto i = 0ul; i < plan.utxos.size(); i++) { // Only sign TWBitcoinSigHashTypeSingle if there's a corresponding output - if (hashSingle && i >= transaction.outputs.size()) { + if (hashSingle && i >= _transaction.outputs.size()) { continue; } auto& utxo = plan.utxos[i]; - if (i < transaction.inputs.size()) { + if (i < _transaction.inputs.size()) { auto result = sign(utxo.script, i, utxo); if (!result) { return Result::failure(result.error()); @@ -62,8 +58,8 @@ Result SignatureBuilder:: template Result SignatureBuilder::sign(Script script, size_t index, - const UTXO& utxo) { - assert(index < transaction.inputs.size()); + const UTXO& utxo) { + assert(index < _transaction.inputs.size()); Script redeemScript; std::vector results; @@ -71,7 +67,7 @@ Result SignatureBuilder::sign(Sc uint32_t signatureVersion = [this]() { if ((input.hashType & TWBitcoinSigHashTypeFork) != 0) { return WITNESS_V0; - } + } return BASE; }(); auto result = signStep(script, index, utxo, signatureVersion); @@ -80,15 +76,15 @@ Result SignatureBuilder::sign(Sc } results = result.payload(); assert(results.size() >= 1); - auto txin = transaction.inputs[index]; + auto txin = _transaction.inputs[index]; if (script.isPayToScriptHash()) { script = Script(results[0]); - auto result = signStep(script, index, utxo, signatureVersion); - if (!result) { - return Result::failure(result.error()); + auto signStepResult = signStep(script, index, utxo, signatureVersion); + if (!signStepResult) { + return Result::failure(signStepResult.error()); } - results = result.payload(); + results = signStepResult.payload(); results.push_back(script.bytes); redeemScript = script; } @@ -97,19 +93,19 @@ Result SignatureBuilder::sign(Sc Data data; if (script.matchPayToWitnessPublicKeyHash(data)) { auto witnessScript = Script::buildPayToPublicKeyHash(results[0]); - auto result = signStep(witnessScript, index, utxo, WITNESS_V0); - if (!result) { - return Result::failure(result.error()); + auto _result = signStep(witnessScript, index, utxo, WITNESS_V0); + if (!_result) { + return Result::failure(_result.error()); } - witnessStack = result.payload(); + witnessStack = _result.payload(); results.clear(); } else if (script.matchPayToWitnessScriptHash(data)) { auto witnessScript = Script(results[0]); - auto result = signStep(witnessScript, index, utxo, WITNESS_V0); - if (!result) { - return Result::failure(result.error()); + auto _result = signStep(witnessScript, index, utxo, WITNESS_V0); + if (!_result) { + return Result::failure(_result.error()); } - witnessStack = result.payload(); + witnessStack = _result.payload(); witnessStack.push_back(std::move(witnessScript.bytes)); results.clear(); } else if (script.isWitnessProgram()) { @@ -135,7 +131,7 @@ Result, Common::Proto::SigningError> SignatureBuilder keys; int required; - if (script.matchPayToScriptHash(data)) { + if (script.matchPayToScriptHash(data) || script.matchPayToScriptHashReplay(data)) { auto redeemScript = scriptForScriptHash(data); if (redeemScript.empty()) { // Error: Missing redeem script @@ -156,13 +152,13 @@ Result, Common::Proto::SigningError> SignatureBuilder, Common::Proto::SigningError>::success({data}); } if (script.isWitnessProgram()) { - // Error: Invalid sutput script + // Error: Invalid output script return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_script_output); } if (script.matchMultisig(keys, required)) { auto results = std::vector{{}}; // workaround CHECKMULTISIG bug for (auto& pubKey : keys) { - if (results.size() >= required + 1) { + if (results.size() >= required + 1ul) { break; } auto keyHash = Hash::ripemd(Hash::sha256(pubKey)); @@ -195,7 +191,9 @@ Result, Common::Proto::SigningError> SignatureBuilder, Common::Proto::SigningError>::success({signature}); } - if (script.matchPayToPublicKeyHash(data)) { + if (script.matchPayToPublicKeyHash(data) + || script.matchPayToPublicKeyHashReplay(data) + || script.matchPayToExchangePublicKeyHash(data)) { // obtain public key auto pair = keyPairForPubKeyHash(data); Data pubkey; @@ -204,12 +202,12 @@ Result, Common::Proto::SigningError> SignatureBuilder, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); } - pubkey = std::get<1>(externalSignatures.value()[index]); + pubkey = std::get<1>(externalSignatures.value()[_index]); } else { // Error: Missing keys return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); @@ -238,15 +236,14 @@ Data SignatureBuilder::createSignature( const std::optional& pair, size_t index, Amount amount, - uint32_t version -) { + uint32_t version) { if (signingMode == SigningMode_SizeEstimationOnly) { // Don't sign, only estimate signature size. It is 71-72 bytes. Return placeholder. return Data(72); } const Data sighash = transaction.getSignatureHash(script, index, input.hashType, amount, - static_cast(version)); + static_cast(version)); if (signingMode == SigningMode_HashOnly) { // Don't sign, only store hash-to-be-signed + pubkeyhash. Return placeholder. @@ -257,26 +254,26 @@ Data SignatureBuilder::createSignature( if (signingMode == SigningMode_External) { // Use externally-provided signature // Store hash, only for counting - size_t index = hashesForSigning.size(); + size_t _index = hashesForSigning.size(); hashesForSigning.push_back(std::make_pair(sighash, publicKeyHash)); - if (!externalSignatures.has_value() || externalSignatures.value().size() <= index) { + if (!externalSignatures.has_value() || externalSignatures.value().size() <= _index) { // Error: no or not enough signatures provided - return Data(); + return {}; } - Data externalSignature = std::get<0>(externalSignatures.value()[index]); - const Data publicKey = std::get<1>(externalSignatures.value()[index]); + Data externalSignature = std::get<0>(externalSignatures.value()[_index]); + const Data publicKey = std::get<1>(externalSignatures.value()[_index]); // Verify provided signature if (!PublicKey::isValid(publicKey, TWPublicKeyTypeSECP256k1)) { // Error: invalid public key - return Data(); + return {}; } const auto publicKeyObj = PublicKey(publicKey, TWPublicKeyTypeSECP256k1); if (!publicKeyObj.verifyAsDER(externalSignature, sighash)) { // Error: Signature does not match publickey+hash - return Data(); + return {}; } externalSignature.push_back(static_cast(input.hashType)); @@ -286,7 +283,7 @@ Data SignatureBuilder::createSignature( const auto key = std::get<0>(pair.value()); const auto pk = PrivateKey(key); - auto sig = pk.signAsDER(sighash, TWCurveSECP256k1); + auto sig = pk.signAsDER(sighash); if (!sig.empty()) { sig.push_back(static_cast(input.hashType)); } @@ -345,6 +342,10 @@ Data SignatureBuilder::scriptForScriptHash(const Data& hash) const } // Explicitly instantiate a Signers for compatible transactions. -template class Bitcoin::SignatureBuilder; -template class Bitcoin::SignatureBuilder; -template class Bitcoin::SignatureBuilder; +template class SignatureBuilder; +template class SignatureBuilder; +template class SignatureBuilder; +template class SignatureBuilder; +template class SignatureBuilder; + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/SignatureBuilder.h b/src/Bitcoin/SignatureBuilder.h index c42c5dfb1d1..4f6af0b2124 100644 --- a/src/Bitcoin/SignatureBuilder.h +++ b/src/Bitcoin/SignatureBuilder.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -17,6 +15,7 @@ #include "../PublicKey.h" #include "../CoinEntry.h" +#include #include #include #include @@ -42,7 +41,7 @@ class SignatureBuilder { TransactionPlan plan; /// Transaction being signed. - Transaction transaction; + Transaction _transaction; /// Transaction being signed, with list of signed inputs Transaction transactionToSign; @@ -59,13 +58,13 @@ class SignatureBuilder { /// Initializes a transaction signer with signing input. /// estimationMode: is set, no real signing is performed, only as much as needed to get the almost-exact signed size SignatureBuilder( - const SigningInput& input, - const TransactionPlan& plan, + SigningInput input, + TransactionPlan plan, Transaction& transaction, SigningMode signingMode = SigningMode_Normal, std::optional externalSignatures = {} ) - : input(input), plan(plan), transaction(transaction), signingMode(signingMode), externalSignatures(externalSignatures) {} + : input(std::move(input)), plan(std::move(plan)), _transaction(transaction), signingMode(signingMode), externalSignatures(std::move(externalSignatures)) {} /// Signs the transaction. /// diff --git a/src/Bitcoin/SignatureVersion.h b/src/Bitcoin/SignatureVersion.h index fa3363eec3f..fc6fe2817cf 100644 --- a/src/Bitcoin/SignatureVersion.h +++ b/src/Bitcoin/SignatureVersion.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -11,4 +9,4 @@ enum SignatureVersion { BASE, WITNESS_V0 }; -} // TW::Bitcoin namespace +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Signer.cpp b/src/Bitcoin/Signer.cpp index 6e8670f89df..7e90a1fc2a9 100644 --- a/src/Bitcoin/Signer.cpp +++ b/src/Bitcoin/Signer.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "Hash.h" @@ -10,25 +8,56 @@ #include "Transaction.h" #include "TransactionBuilder.h" #include "TransactionSigner.h" +#include "rust/bindgen/WalletCoreRSBindgen.h" #include "proto/Common.pb.h" -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) noexcept { + if (input.has_signing_v2()) { + Proto::TransactionPlan plan; + + // Forward the `Bitcoin.Proto.SigningInput.signing_v2` request to Rust. + auto signingV2Data = data(input.signing_v2().SerializeAsString()); + Rust::TWDataWrapper signingV2DataPtr(signingV2Data); + Rust::TWDataWrapper transactionPlanV2DataPtr = Rust::tw_any_signer_plan(signingV2DataPtr.get(), input.coin_type()); + + auto transactionPlanV2Data = transactionPlanV2DataPtr.toDataOrDefault(); + BitcoinV2::Proto::TransactionPlan transactionPlanV2; + transactionPlanV2.ParseFromArray(transactionPlanV2Data.data(), static_cast(transactionPlanV2Data.size())); + + // Set `Bitcoin.Proto.TransactionPlan.planning_result_v2`. Remain other fields default. + *plan.mutable_planning_result_v2() = transactionPlanV2; + return plan; + } + auto plan = TransactionSigner::plan(input); return plan.proto(); } -Proto::SigningOutput Signer::sign(const Proto::SigningInput &input, std::optional optionalExternalSigs) noexcept { +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input, std::optional optionalExternalSigs) noexcept { Proto::SigningOutput output; - auto result = TransactionSigner::sign(input, false, optionalExternalSigs); + if (input.has_signing_v2()) { + // Forward the `Bitcoin.Proto.SigningInput.signing_v2` request to Rust. + auto signingV2Data = data(input.signing_v2().SerializeAsString()); + Rust::TWDataWrapper signingV2DataPtr(signingV2Data); + Rust::TWDataWrapper signingOutputV2DataPtr = Rust::tw_any_signer_sign(signingV2DataPtr.get(), input.coin_type()); + + auto signingOutputV2Data = signingOutputV2DataPtr.toDataOrDefault(); + BitcoinV2::Proto::SigningOutput signingOutputV2; + signingOutputV2.ParseFromArray(signingOutputV2Data.data(), static_cast(signingOutputV2Data.size())); + + // Set `Bitcoin.Proto.SigningOutput.signing_result_v2`. Remain other fields default. + *output.mutable_signing_result_v2() = signingOutputV2; + return output; + } + + auto result = TransactionSigner::sign(input, false, std::move(optionalExternalSigs)); if (!result) { output.set_error(result.error()); return output; } - const auto& tx = result.payload(); *output.mutable_transaction() = tx.proto(); @@ -49,6 +78,21 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput &input, std::optiona Proto::PreSigningOutput Signer::preImageHashes(const Proto::SigningInput& input) noexcept { Proto::PreSigningOutput output; + if (input.has_signing_v2()) { + // Forward the `Bitcoin.Proto.SigningInput.signing_v2` request to Rust. + auto signingV2Data = data(input.signing_v2().SerializeAsString()); + Rust::TWDataWrapper signingV2DataPtr(signingV2Data); + Rust::TWDataWrapper preOutputV2DataPtr = Rust::tw_transaction_compiler_pre_image_hashes(input.coin_type(), signingV2DataPtr.get()); + + auto preOutputV2Data = preOutputV2DataPtr.toDataOrDefault(); + BitcoinV2::Proto::PreSigningOutput preSigningOutputV2; + preSigningOutputV2.ParseFromArray(preOutputV2Data.data(), static_cast(preOutputV2Data.size())); + + // Set `Bitcoin.Proto.PreSigningOutput.pre_signing_result_v2`. Remain other fields default. + *output.mutable_pre_signing_result_v2() = preSigningOutputV2; + return output; + } + auto result = TransactionSigner::preImageHashes(input); if (!result) { output.set_error(result.error()); @@ -65,3 +109,53 @@ Proto::PreSigningOutput Signer::preImageHashes(const Proto::SigningInput& input) } return output; } + +Proto::SigningOutput Signer::compile(const Proto::SigningInput& input, + const std::vector& signatures, + const std::vector& publicKeys) noexcept { + Proto::SigningOutput output; + if (input.has_signing_v2()) { + Rust::TWDataVectorWrapper signaturesVec = signatures; + Rust::TWDataVectorWrapper publicKeysVec; + for (const auto& pubkey : publicKeys) { + publicKeysVec.push(pubkey.bytes); + } + + // Forward the `Bitcoin.Proto.SigningInput.signing_v2` request to Rust. + auto signingV2Data = data(input.signing_v2().SerializeAsString()); + Rust::TWDataWrapper signingV2DataPtr(signingV2Data); + Rust::TWDataWrapper outputV2DataPtr = Rust::tw_transaction_compiler_compile( + input.coin_type(), signingV2DataPtr.get(), signaturesVec.get(), publicKeysVec.get()); + + auto outputV2Data = outputV2DataPtr.toDataOrDefault(); + BitcoinV2::Proto::SigningOutput outputV2; + outputV2.ParseFromArray(outputV2Data.data(), static_cast(outputV2Data.size())); + + // Set `Bitcoin.Proto.SigningOutput.signing_result_v2`. Remain other fields default. + *output.mutable_signing_result_v2() = outputV2; + return output; + } + + if (signatures.empty() || publicKeys.empty()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return output; + } + + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("signatures size and publickeys size not equal"); + return output; + } + + HashPubkeyList externalSignatures; + auto insertFunctor = [](auto&& signature, auto&& pubkey) noexcept { + return std::make_pair(signature, pubkey.bytes); + }; + transform(begin(signatures), end(signatures), begin(publicKeys), + back_inserter(externalSignatures), insertFunctor); + output = Signer::sign(input, externalSignatures); + return output; +} + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Signer.h b/src/Bitcoin/Signer.h index b990d5b7dd2..4fb9401c1d2 100644 --- a/src/Bitcoin/Signer.h +++ b/src/Bitcoin/Signer.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../proto/Bitcoin.pb.h" @@ -15,8 +13,6 @@ namespace TW::Bitcoin { -typedef std::vector> SignaturePubkeyList; - class Signer { public: Signer() = delete; @@ -29,6 +25,11 @@ class Signer { /// Collect pre-image hashes to be signed static Proto::PreSigningOutput preImageHashes(const Proto::SigningInput& input) noexcept; + + /// Compile a transaction with the given signatures and public keys. + static Proto::SigningOutput compile(const Proto::SigningInput& input, + const std::vector& signatures, + const std::vector& publicKeys) noexcept; }; } // namespace TW::Bitcoin diff --git a/src/Bitcoin/SigningInput.cpp b/src/Bitcoin/SigningInput.cpp index 9929880984a..bd7c86f7378 100644 --- a/src/Bitcoin/SigningInput.cpp +++ b/src/Bitcoin/SigningInput.cpp @@ -1,13 +1,14 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "SigningInput.h" -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { + +SigningInput::SigningInput() + : dustCalculator(std::make_shared(TWCoinTypeBitcoin)) { +} SigningInput::SigningInput(const Proto::SigningInput& input) { hashType = static_cast(input.hash_type()); @@ -15,20 +16,36 @@ SigningInput::SigningInput(const Proto::SigningInput& input) { byteFee = input.byte_fee(); toAddress = input.to_address(); changeAddress = input.change_address(); - for (auto& key: input.private_key()) { - privateKeys.emplace_back(PrivateKey(key)); + for (auto&& key : input.private_key()) { + privateKeys.emplace_back(key); } - for (auto& script: input.scripts()) { + for (auto&& script : input.scripts()) { scripts[script.first] = Script(script.second.begin(), script.second.end()); } - for (auto& u: input.utxo()) { - utxos.push_back(UTXO(u)); + for (auto&& u : input.utxo()) { + utxos.emplace_back(u); } useMaxAmount = input.use_max_amount(); + useMaxUtxo = input.use_max_utxo(); + disableDustFilter = input.disable_dust_filter(); coinType = static_cast(input.coin_type()); if (input.has_plan()) { plan = TransactionPlan(input.plan()); } outputOpReturn = data(input.output_op_return()); + if (input.has_output_op_return_index()) { + outputOpReturnIndex = input.output_op_return_index().index(); + } lockTime = input.lock_time(); + time = input.time(); + + extraOutputsAmount = 0; + for (auto& output: input.extra_outputs()) { + extraOutputsAmount += output.amount(); + extraOutputs.push_back(std::make_pair(output.to_address(), output.amount())); + } + + dustCalculator = getDustCalculator(input); } + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/SigningInput.h b/src/Bitcoin/SigningInput.h index cc365fd5b49..86ced0f1122 100644 --- a/src/Bitcoin/SigningInput.h +++ b/src/Bitcoin/SigningInput.h @@ -1,12 +1,11 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Amount.h" +#include "DustCalculator.h" #include "Transaction.h" #include "UTXO.h" #include @@ -51,6 +50,12 @@ class SigningInput { // If sending max amount bool useMaxAmount = false; + // If all input utxos + bool useMaxUtxo = false; + + // If disable dust filter + bool disableDustFilter = false; + // Coin type (forks) TWCoinType coinType = TWCoinTypeBitcoin; @@ -59,10 +64,24 @@ class SigningInput { Data outputOpReturn; + // Optional index of the OP_RETURN output in the transaction. + // If not set, OP_RETURN output will be pushed as the latest output. + MaybeIndex outputOpReturnIndex; + uint32_t lockTime = 0; + uint32_t time = 0; + + // Besides to_address and change_address, + // we have other outputs that include address and value + std::vector> extraOutputs; + + // Total amount of the `extraOutputs`. + Amount extraOutputsAmount = 0; + + DustCalculatorShared dustCalculator; public: - SigningInput() = default; + SigningInput(); SigningInput(const Proto::SigningInput& input); }; diff --git a/src/Bitcoin/Transaction.cpp b/src/Bitcoin/Transaction.cpp index 8e9b3f5c5c3..06d6467cafe 100644 --- a/src/Bitcoin/Transaction.cpp +++ b/src/Bitcoin/Transaction.cpp @@ -1,22 +1,19 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "SegwitAddress.h" #include "Transaction.h" +#include "SegwitAddress.h" +#include "SignatureVersion.h" #include "SigHashType.h" -#include "../BinaryCoding.h" -#include "../Hash.h" -#include "../Data.h" +#include "rust/bindgen/WalletCoreRSBindgen.h" +#include "rust/Wrapper.h" -#include "SignatureVersion.h" +#include "../BinaryCoding.h" #include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { Data Transaction::getPreImage(const Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount) const { @@ -25,7 +22,7 @@ Data Transaction::getPreImage(const Script& scriptCode, size_t index, Data data; // Version - encode32LE(version, data); + encode32LE(_version, data); // Input prevouts (none/all, depending on flags) if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0) { @@ -36,8 +33,8 @@ Data Transaction::getPreImage(const Script& scriptCode, size_t index, } // Input nSequence (none/all, depending on flags) - if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0 && - !hashTypeIsSingle(hashType) && !hashTypeIsNone(hashType)) { + if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0 && !hashTypeIsSingle(hashType) && + !hashTypeIsNone(hashType)) { auto hashSequence = getSequenceHash(); std::copy(std::begin(hashSequence), std::end(hashSequence), std::back_inserter(data)); } else { @@ -46,7 +43,7 @@ Data Transaction::getPreImage(const Script& scriptCode, size_t index, // The input being signed (replacing the scriptSig with scriptCode + amount) // The prevout may already be contained in hashPrevout, and the nSequence - // may already be contain in hashSequence. + // may already be contained in hashSequence. reinterpret_cast(inputs[index].previousOutput).encode(data); scriptCode.encode(data); @@ -106,12 +103,18 @@ Data Transaction::getOutputsHash() const { void Transaction::encode(Data& data, enum SegwitFormatMode segwitFormat) const { bool useWitnessFormat = true; switch (segwitFormat) { - case NonSegwit: useWitnessFormat = false; break; - case IfHasWitness: useWitnessFormat = hasWitness(); break; - case Segwit: useWitnessFormat = true; break; + case NonSegwit: + useWitnessFormat = false; + break; + case IfHasWitness: + useWitnessFormat = hasWitness(); + break; + case Segwit: + useWitnessFormat = true; + break; } - encode32LE(version, data); + encode32LE(_version, data); if (useWitnessFormat) { // Use extended format in case witnesses are to be serialized. @@ -145,18 +148,17 @@ void Transaction::encodeWitness(Data& data) const { } bool Transaction::hasWitness() const { - return std::any_of(inputs.begin(), inputs.end(), [](auto& input) { return !input.scriptWitness.empty(); }); + return std::any_of(inputs.begin(), inputs.end(), [](auto& input) { return !input.scriptWitness.empty(); }); } Data Transaction::getSignatureHash(const Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount, enum SignatureVersion version) const { - switch (version) { - case BASE: + if (version == BASE) { return getSignatureHashBase(scriptCode, index, hashType); - case WITNESS_V0: - return getSignatureHashWitnessV0(scriptCode, index, hashType, amount); } + // version == WITNESS_V0 + return getSignatureHashWitnessV0(scriptCode, index, hashType, amount); } /// Generates the signature hash for Witness version 0 scripts. @@ -175,12 +177,12 @@ Data Transaction::getSignatureHashBase(const Script& scriptCode, size_t index, Data data; - encode32LE(version, data); + encode32LE(_version, data); auto serializedInputCount = (hashType & TWBitcoinSigHashTypeAnyoneCanPay) != 0 ? 1 : inputs.size(); encodeVarInt(serializedInputCount, data); - for (auto subindex = 0; subindex < serializedInputCount; subindex += 1) { + for (auto subindex = 0ul; subindex < serializedInputCount; subindex += 1) { serializeInput(subindex, scriptCode, index, hashType, data); } @@ -188,7 +190,7 @@ Data Transaction::getSignatureHashBase(const Script& scriptCode, size_t index, auto hashSingle = hashTypeIsSingle(hashType); auto serializedOutputCount = hashNone ? 0 : (hashSingle ? index + 1 : outputs.size()); encodeVarInt(serializedOutputCount, data); - for (auto subindex = 0; subindex < serializedOutputCount; subindex += 1) { + for (auto subindex = 0ul; subindex < serializedOutputCount; subindex += 1) { if (hashSingle && subindex != index) { auto output = TransactionOutput(-1, {}); output.encode(data); @@ -236,7 +238,7 @@ void Transaction::serializeInput(size_t subindex, const Script& scriptCode, size Proto::Transaction Transaction::proto() const { auto protoTx = Proto::Transaction(); - protoTx.set_version(version); + protoTx.set_version(_version); protoTx.set_locktime(lockTime); for (const auto& input : inputs) { @@ -256,3 +258,5 @@ Proto::Transaction Transaction::proto() const { return protoTx; } + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Transaction.h b/src/Bitcoin/Transaction.h index b5ecd454e8b..3f276c9ad58 100644 --- a/src/Bitcoin/Transaction.h +++ b/src/Bitcoin/Transaction.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -14,11 +12,12 @@ #include "UTXO.h" #include "../PrivateKey.h" #include "../Hash.h" -#include "../Data.h" +#include "Data.h" #include "SignatureVersion.h" #include "../proto/Bitcoin.pb.h" #include +#include namespace TW::Bitcoin { @@ -33,7 +32,7 @@ class TransactionOutputs: public std::vector {}; struct Transaction { public: /// Transaction data format version (note, this is signed) - int32_t version = 1; + int32_t _version = 1; /// The block number or timestamp at which this transaction is unlocked /// @@ -62,7 +61,7 @@ struct Transaction { Transaction() = default; Transaction(int32_t version, uint32_t lockTime = 0, TW::Hash::Hasher hasher = TW::Hash::HasherSha256d) - : version(version), lockTime(lockTime), inputs(), outputs(), hasher(hasher) {} + : _version(version), lockTime(lockTime), inputs(), outputs(), hasher(hasher) {} /// Whether the transaction is empty. bool empty() const { return inputs.empty() && outputs.empty(); } diff --git a/src/Bitcoin/TransactionBuilder.cpp b/src/Bitcoin/TransactionBuilder.cpp index 464d651f544..beb0a7d682f 100644 --- a/src/Bitcoin/TransactionBuilder.cpp +++ b/src/Bitcoin/TransactionBuilder.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "TransactionBuilder.h" #include "Script.h" @@ -10,7 +8,6 @@ #include "SignatureBuilder.h" #include "../Coin.h" -#include "../proto/Bitcoin.pb.h" #include #include @@ -80,7 +77,7 @@ int64_t estimateSegwitFee(const FeeCalculator& feeCalculator, const TransactionP int extraOutputCount(const SigningInput& input) { int count = int(input.outputOpReturn.size() > 0); - return count; + return count + int(input.extraOutputs.size()); } TransactionPlan TransactionBuilder::plan(const SigningInput& input) { @@ -88,23 +85,27 @@ TransactionPlan TransactionBuilder::plan(const SigningInput& input) { if (input.outputOpReturn.size() > 0) { plan.outputOpReturn = input.outputOpReturn; } + plan.outputOpReturnIndex = input.outputOpReturnIndex; bool maxAmount = input.useMaxAmount; - if (input.amount == 0 && !maxAmount) { + Amount totalAmount = input.amount + input.extraOutputsAmount; + Amount dustThreshold = input.dustCalculator->dustAmount(input.byteFee); + + if (totalAmount == 0 && !maxAmount) { plan.error = Common::Proto::Error_zero_amount_requested; } else if (input.utxos.empty()) { plan.error = Common::Proto::Error_missing_input_utxos; } else { - const auto& feeCalculator = getFeeCalculator(static_cast(input.coinType)); - auto inputSelector = InputSelector(input.utxos, feeCalculator); + const auto& feeCalculator = getFeeCalculator(static_cast(input.coinType), input.disableDustFilter); + auto inputSelector = InputSelector(input.utxos, feeCalculator, input.dustCalculator); auto inputSum = InputSelector::sum(input.utxos); // select UTXOs plan.amount = input.amount; - // if amount requested is the same or more than available amount, it cannot be satisifed, but + // if amount requested is the same or more than available amount, it cannot be satisfied, but // treat this case as MaxAmount, and send maximum available (which will be less) - if (!maxAmount && input.amount >= inputSum) { + if (!maxAmount && static_cast(totalAmount) >= inputSum) { maxAmount = true; } @@ -112,11 +113,17 @@ TransactionPlan TransactionBuilder::plan(const SigningInput& input) { auto output_size = 2; UTXOs selectedInputs; if (!maxAmount) { + // Please note that there may not be a "change" output if the "change.amount" is less than "dust", + // but we use a max amount of transaction outputs to simplify the algorithm, so the fee can be slightly bigger in rare cases. output_size = 2 + extraOutputs; // output + change - if (input.utxos.size() <= SimpleModeLimit && input.utxos.size() <= MaxUtxosHardLimit) { - selectedInputs = inputSelector.select(plan.amount, input.byteFee, output_size); + if (input.useMaxUtxo) { + selectedInputs = inputSelector.selectMaxAmount(input.byteFee); + } else if (input.utxos.size() <= SimpleModeLimit && + input.utxos.size() <= MaxUtxosHardLimit) { + selectedInputs = inputSelector.select(totalAmount, input.byteFee, output_size); } else { - selectedInputs = inputSelector.selectSimple(plan.amount, input.byteFee, output_size); + selectedInputs = + inputSelector.selectSimple(totalAmount, input.byteFee, output_size); } } else { output_size = 1 + extraOutputs; // output, no change @@ -127,31 +134,51 @@ TransactionPlan TransactionBuilder::plan(const SigningInput& input) { } else { // truncate to limit number of selected UTXOs plan.utxos.clear(); - for (auto i = 0; i < MaxUtxosHardLimit; ++i) { + for (auto i = 0ul; i < MaxUtxosHardLimit; ++i) { plan.utxos.push_back(selectedInputs[i]); } } - if (plan.utxos.size() == 0) { + if (plan.utxos.empty()) { plan.amount = 0; plan.error = Common::Proto::Error_not_enough_utxos; + } else if (maxAmount && !input.extraOutputs.empty()) { + // As of now, we don't support `max` amount **and** extra outputs. + plan.amount = 0; + plan.error = Common::Proto::Error_invalid_params; } else { plan.availableAmount = InputSelector::sum(plan.utxos); + // There can be less UTXOs after Dust filtering. + if (!maxAmount && totalAmount > plan.availableAmount) { + TransactionPlan errorPlan; + errorPlan.error = Common::Proto::Error_not_enough_utxos; + return errorPlan; + } + // Compute fee. // must preliminary set change so that there is a second output if (!maxAmount) { - assert(input.amount <= plan.availableAmount); plan.amount = input.amount; plan.fee = 0; - plan.change = plan.availableAmount - plan.amount; + plan.change = plan.availableAmount - totalAmount; } else { plan.amount = plan.availableAmount; plan.fee = 0; plan.change = 0; } plan.fee = estimateSegwitFee(feeCalculator, plan, output_size, input); - // If fee is larger then availableAmount (can happen in special maxAmount case), we reduce it (and hope it will go through) + + // `InputSelector` has a rough segwit fee estimation algorithm, + // so the fee could be increased or decreased (see `InputSelector::select`). + // We need to make sure if we have enough UTXOs to cover "requested amount + final fee". + if (!maxAmount && plan.availableAmount < plan.fee + plan.amount) { + TransactionPlan errorPlan; + errorPlan.error = Common::Proto::Error_not_enough_utxos; + return errorPlan; + } + + // If fee is larger than availableAmount (can happen in special maxAmount case), we reduce it (and hope it will go through) plan.fee = std::min(plan.availableAmount, plan.fee); assert(plan.fee >= 0 && plan.fee <= plan.availableAmount); @@ -165,14 +192,40 @@ TransactionPlan TransactionBuilder::plan(const SigningInput& input) { } assert(plan.amount >= 0 && plan.amount <= plan.availableAmount); - // compute change - plan.change = plan.availableAmount - plan.amount - plan.fee; + // The total amount that will be spent. + Amount totalSpendAmount = plan.amount + input.extraOutputsAmount + plan.fee; + + // Make sure that the output amount is greater or at least equal to the dust threshold. + if (plan.amount < dustThreshold) { + TransactionPlan errorPlan; + errorPlan.error = maxAmount ? Common::Proto::Error_not_enough_utxos : Common::Proto::Error_dust_amount_requested; + return errorPlan; + } + + // Make sure that we have enough available UTXOs to spend `fee`, `amount` and `extraOutputsAmount`. + if (plan.availableAmount < totalSpendAmount) { + TransactionPlan errorPlan; + errorPlan.error = Common::Proto::Error_not_enough_utxos; + return errorPlan; + } + + auto changeAmount = plan.availableAmount - totalSpendAmount; + // Compute change if it's not dust. + if (changeAmount >= dustThreshold) { + plan.change = changeAmount; + } else { + // Spend the change as tx fee if it's dust, otherwise the transaction won't be mined. + plan.change = 0; + plan.fee += changeAmount; + } } } assert(plan.change >= 0 && plan.change <= plan.availableAmount); assert(!maxAmount || plan.change == 0); // change is 0 in max amount case - assert(plan.amount + plan.change + plan.fee == plan.availableAmount); + assert(plan.error != Common::Proto::OK + // `plan.error` is OK, check if the values are expected. + || plan.amount + input.extraOutputsAmount + plan.change + plan.fee == plan.availableAmount); return plan; } diff --git a/src/Bitcoin/TransactionBuilder.h b/src/Bitcoin/TransactionBuilder.h index 33131160939..667fbe190c7 100644 --- a/src/Bitcoin/TransactionBuilder.h +++ b/src/Bitcoin/TransactionBuilder.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -10,6 +8,7 @@ #include "Transaction.h" #include "TransactionPlan.h" #include "InputSelector.h" +#include "../Result.h" #include "../proto/Bitcoin.pb.h" #include @@ -25,18 +24,21 @@ class TransactionBuilder { /// Builds a transaction with the selected input UTXOs, and one main output and an optional change output. template - static Transaction build(const TransactionPlan& plan, const std::string& toAddress, - const std::string& changeAddress, enum TWCoinType coin, uint32_t lockTime) { + static Result build(const TransactionPlan& plan, const SigningInput& input) { Transaction tx; - tx.lockTime = lockTime; + tx.lockTime = input.lockTime; - auto outputTo = prepareOutputWithScript(toAddress, plan.amount, coin); - if (!outputTo.has_value()) { return {}; } + auto outputTo = prepareOutputWithScript(input.toAddress, plan.amount, input.coinType); + if (!outputTo.has_value()) { + return Result::failure(Common::Proto::Error_invalid_address); + } tx.outputs.push_back(outputTo.value()); if (plan.change > 0) { - auto outputChange = prepareOutputWithScript(changeAddress, plan.change, coin); - if (!outputChange.has_value()) { return {}; } + auto outputChange = prepareOutputWithScript(input.changeAddress, plan.change, input.coinType); + if (!outputChange.has_value()) { + return Result::failure(Common::Proto::Error_invalid_address); + } tx.outputs.push_back(outputChange.value()); } @@ -46,12 +48,31 @@ class TransactionBuilder { } // Optional OP_RETURN output - if (plan.outputOpReturn.size() > 0) { + if (!plan.outputOpReturn.empty()) { auto lockingScriptOpReturn = Script::buildOpReturnScript(plan.outputOpReturn); - tx.outputs.push_back(TransactionOutput(0, lockingScriptOpReturn)); + if (lockingScriptOpReturn.bytes.empty()) { + return Result::failure(Common::Proto::Error_invalid_memo); + } + + auto emplace_at = tx.outputs.end(); + if (plan.outputOpReturnIndex.has_value()) { + emplace_at = tx.outputs.begin(); + std::advance(emplace_at, plan.outputOpReturnIndex.value()); + } + int64_t amount = 0; + tx.outputs.emplace(emplace_at, amount, lockingScriptOpReturn); + } + + // extra outputs (always in the end of the outputs list) + for (auto& o : input.extraOutputs) { + auto output = prepareOutputWithScript(o.first, o.second, input.coinType); + if (!output.has_value()) { + return Result::failure(Common::Proto::Error_invalid_address); + } + tx.outputs.push_back(output.value()); } - return tx; + return Result(tx); } /// Prepares a TransactionOutput with given address and amount, prepares script for it diff --git a/src/Bitcoin/TransactionInput.cpp b/src/Bitcoin/TransactionInput.cpp index 6d59f5536b8..ee2db04a78d 100644 --- a/src/Bitcoin/TransactionInput.cpp +++ b/src/Bitcoin/TransactionInput.cpp @@ -1,14 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "TransactionInput.h" #include "../BinaryCoding.h" -using namespace TW::Bitcoin; +namespace TW::Bitcoin { void TransactionInput::encode(Data& data) const { auto& outpoint = reinterpret_cast(previousOutput); @@ -24,3 +22,5 @@ void TransactionInput::encodeWitness(Data& data) const { std::copy(std::begin(item), std::end(item), std::back_inserter(data)); } } + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/TransactionInput.h b/src/Bitcoin/TransactionInput.h index a7b57df2f9c..c95c89ab07c 100644 --- a/src/Bitcoin/TransactionInput.h +++ b/src/Bitcoin/TransactionInput.h @@ -1,14 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "OutPoint.h" #include "Script.h" -#include "../Data.h" +#include "Data.h" #include diff --git a/src/Bitcoin/TransactionOutput.cpp b/src/Bitcoin/TransactionOutput.cpp index e21b9668737..008cdb3ff26 100644 --- a/src/Bitcoin/TransactionOutput.cpp +++ b/src/Bitcoin/TransactionOutput.cpp @@ -1,16 +1,16 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "TransactionOutput.h" #include "../BinaryCoding.h" -using namespace TW::Bitcoin; +namespace TW::Bitcoin { void TransactionOutput::encode(Data& data) const { encode64LE(value, data); script.encode(data); } + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/TransactionOutput.h b/src/Bitcoin/TransactionOutput.h index 499787d0dbc..4627f5dc899 100644 --- a/src/Bitcoin/TransactionOutput.h +++ b/src/Bitcoin/TransactionOutput.h @@ -1,14 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Amount.h" #include "Script.h" -#include "../Data.h" +#include "Data.h" +#include "PublicKey.h" #include diff --git a/src/Bitcoin/TransactionPlan.h b/src/Bitcoin/TransactionPlan.h index 0b7e4b7309f..5124a301ab1 100644 --- a/src/Bitcoin/TransactionPlan.h +++ b/src/Bitcoin/TransactionPlan.h @@ -1,18 +1,20 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Amount.h" #include "UTXO.h" -#include "../Data.h" +#include "Data.h" #include "../proto/Bitcoin.pb.h" +#include + namespace TW::Bitcoin { +using MaybeIndex = std::optional; + /// Describes a preliminary transaction plan. struct TransactionPlan { /// Amount to be received at the other end. @@ -33,8 +35,18 @@ struct TransactionPlan { /// Zcash branch id Data branchId; + /// zen & bitcoin diamond preblockhash + Data preBlockHash; + + /// zen preblockheight + int64_t preBlockHeight = 0; + Data outputOpReturn; + // Optional index of the OP_RETURN output in the transaction. + // If not set, OP_RETURN output will be pushed as the latest output. + MaybeIndex outputOpReturnIndex; + Common::Proto::SigningError error = Common::Proto::SigningError::OK; TransactionPlan() = default; @@ -46,9 +58,15 @@ struct TransactionPlan { , change(plan.change()) , utxos(std::vector(plan.utxos().begin(), plan.utxos().end())) , branchId(plan.branch_id().begin(), plan.branch_id().end()) + , preBlockHash(plan.preblockhash().begin(), plan.preblockhash().end()) + , preBlockHeight(plan.preblockheight()) , outputOpReturn(plan.output_op_return().begin(), plan.output_op_return().end()) , error(plan.error()) - {} + { + if (plan.has_output_op_return_index()) { + outputOpReturnIndex = plan.output_op_return_index().index(); + } + } Proto::TransactionPlan proto() const { auto plan = Proto::TransactionPlan(); @@ -60,7 +78,12 @@ struct TransactionPlan { *plan.add_utxos() = utxo.proto(); } plan.set_branch_id(branchId.data(), branchId.size()); + plan.set_preblockhash(preBlockHash.data(), preBlockHash.size()); + plan.set_preblockheight(preBlockHeight); plan.set_output_op_return(outputOpReturn.data(), outputOpReturn.size()); + if (outputOpReturnIndex.has_value()) { + plan.mutable_output_op_return_index()->set_index(static_cast(outputOpReturnIndex.value())); + } plan.set_error(error); return plan; } diff --git a/src/Bitcoin/TransactionSigner.cpp b/src/Bitcoin/TransactionSigner.cpp index f48e707d6dc..282117df361 100644 --- a/src/Bitcoin/TransactionSigner.cpp +++ b/src/Bitcoin/TransactionSigner.cpp @@ -1,19 +1,21 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "TransactionSigner.h" #include "SignatureBuilder.h" +#include "../BitcoinDiamond/Transaction.h" +#include "../BitcoinDiamond/TransactionBuilder.h" #include "../Groestlcoin/Transaction.h" +#include "../Verge/Transaction.h" +#include "../Verge/TransactionBuilder.h" #include "../Zcash/Transaction.h" #include "../Zcash/TransactionBuilder.h" +#include "../Zen/TransactionBuilder.h" -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { template TransactionPlan TransactionSigner::plan(const SigningInput& input) { @@ -28,10 +30,14 @@ Result TransactionSigner(plan, input.toAddress, input.changeAddress, input.coinType, input.lockTime); + auto tx_result = TransactionBuilder::template build(plan, input); + if (!tx_result) { + return Result::failure(tx_result.error()); + } + Transaction transaction = tx_result.payload(); SigningMode signingMode = - estimationMode ? SigningMode_SizeEstimationOnly : - optionalExternalSigs.has_value() ? SigningMode_External : SigningMode_Normal; + estimationMode ? SigningMode_SizeEstimationOnly : optionalExternalSigs.has_value() ? SigningMode_External + : SigningMode_Normal; SignatureBuilder signer(std::move(input), plan, transaction, signingMode, optionalExternalSigs); return signer.sign(); } @@ -44,7 +50,11 @@ Result TransactionSigner(plan, input.toAddress, input.changeAddress, input.coinType, input.lockTime); + auto tx_result = TransactionBuilder::template build(plan, input); + if (!tx_result) { + return Result::failure(tx_result.error()); + } + Transaction transaction = tx_result.payload(); SignatureBuilder signer(std::move(input), plan, transaction, SigningMode_HashOnly); auto signResult = signer.sign(); if (!signResult) { @@ -56,4 +66,9 @@ Result TransactionSigner; template class Bitcoin::TransactionSigner; +template class Bitcoin::TransactionSigner; template class Bitcoin::TransactionSigner; +template class Bitcoin::TransactionSigner; +template class Bitcoin::TransactionSigner; + +} // namespace TW::Bitcoin diff --git a/src/Bitcoin/TransactionSigner.h b/src/Bitcoin/TransactionSigner.h index 3803703e046..8f67a93cc5c 100644 --- a/src/Bitcoin/TransactionSigner.h +++ b/src/Bitcoin/TransactionSigner.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -10,7 +8,7 @@ #include "Transaction.h" #include "TransactionBuilder.h" #include "Signer.h" -#include "../Data.h" +#include "Data.h" #include "../KeyPair.h" #include "../Result.h" #include "../proto/Bitcoin.pb.h" diff --git a/src/Bitcoin/UTXO.h b/src/Bitcoin/UTXO.h index 55f534d86b7..02fdf2dffee 100644 --- a/src/Bitcoin/UTXO.h +++ b/src/Bitcoin/UTXO.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/BitcoinDiamond/Entry.cpp b/src/BitcoinDiamond/Entry.cpp new file mode 100644 index 00000000000..b75a9ea6d1b --- /dev/null +++ b/src/BitcoinDiamond/Entry.cpp @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Entry.h" + +#include "Signer.h" +#include "proto/Bitcoin.pb.h" +#include "../Bitcoin/Address.h" +#include "../Bitcoin/SegwitAddress.h" + +using namespace TW; +using namespace std; + +namespace TW::BitcoinDiamond { + +// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, const PrefixVariant& addressPrefix) const { + auto* base58Prefix = std::get_if(&addressPrefix); + auto* hrp = std::get_if(&addressPrefix); + bool isValidBase58 = base58Prefix ? Bitcoin::Address::isValid(address) : false; + bool isValidHrp = hrp ? Bitcoin::SegwitAddress::isValid(address, *hrp) : false; + return isValidBase58 || isValidHrp; +} + +string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const { + byte p2pkh = getFromPrefixPkhOrDefault(addressPrefix, coin); + const char* hrp = getFromPrefixHrpOrDefault(addressPrefix, coin); + + switch (derivation) { + case TWDerivationBitcoinLegacy: + case TWDerivationDefault: + return Bitcoin::Address(publicKey, p2pkh).string(); + default: + return TW::Bitcoin::SegwitAddress(publicKey, hrp).string(); + } +} + +TW::Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto decoded = Bitcoin::SegwitAddress::decode(address); + if (!std::get<2>(decoded)) { + // check if it is a legacy address + if (Bitcoin::Address::isValid(address)) { + const auto addr = Bitcoin::Address(address); + return {addr.bytes.begin() + 1, addr.bytes.end()}; + } + return {Data()}; + } + return std::get<0>(decoded).witnessProgram; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +void Entry::plan([[maybe_unused]] TWCoinType coin, const Data& dataIn, Data& dataOut) const { + planTemplate(dataIn, dataOut); +} + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, + [](const auto& input, auto& output) { output = Signer::preImageHashes(input); }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("signatures size and publickeys size not equal"); + return; + } + + HashPubkeyList externalSignatures; + auto n = signatures.size(); + for (auto i = 0ul; i < n; ++i) { + externalSignatures.push_back(std::make_pair(signatures[i], publicKeys[i].bytes)); + } + + output = Signer::sign(input, externalSignatures); + }); +} + +} // namespace TW::BitcoinDiamond diff --git a/src/BitcoinDiamond/Entry.h b/src/BitcoinDiamond/Entry.h new file mode 100644 index 00000000000..ef8002905e5 --- /dev/null +++ b/src/BitcoinDiamond/Entry.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../CoinEntry.h" + +namespace TW::BitcoinDiamond { + +/// Entry point for implementation of BitcoinDiamond coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final: public CoinEntry { +public: + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + Data addressToData(TWCoinType coin, const std::string& address) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; +}; + +} // namespace TW::BitcoinDiamond diff --git a/src/BitcoinDiamond/Signer.cpp b/src/BitcoinDiamond/Signer.cpp new file mode 100644 index 00000000000..cb7c964e562 --- /dev/null +++ b/src/BitcoinDiamond/Signer.cpp @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Signer.h" +#include "Bitcoin/TransactionSigner.h" +#include "Hash.h" +#include "HexCoding.h" +#include "PublicKey.h" +#include "Transaction.h" +#include "TransactionBuilder.h" + +using namespace TW; +namespace TW::BitcoinDiamond { + +TransactionPlan Signer::plan(const SigningInput& input) noexcept { + auto plan = Bitcoin::TransactionSigner::plan(input); + return plan.proto(); +} + +SigningOutput Signer::sign(const SigningInput& input, std::optional optionalExternalSigs) noexcept { + SigningOutput output; + auto result = Bitcoin::TransactionSigner::sign(input, false, optionalExternalSigs); + if (!result) { + output.set_error(result.error()); + return output; + } + const auto& tx = result.payload(); + *output.mutable_transaction() = tx.proto(); + + Data encoded; + tx.encode(encoded); + output.set_encoded(encoded.data(), encoded.size()); + + Data txHashData = encoded; + if (tx.hasWitness()) { + txHashData.clear(); + tx.encode(txHashData, Transaction::SegwitFormatMode::NonSegwit); + } + auto txHash = Hash::sha256(txHashData.data(), txHashData.size()); + std::reverse(txHash.begin(), txHash.end()); + output.set_transaction_id(hex(txHash)); + return output; +} + +PreSigningOutput Signer::preImageHashes(const SigningInput& input) noexcept { + PreSigningOutput output; + auto result = Bitcoin::TransactionSigner::preImageHashes(input); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + return output; + } + + auto hashList = result.payload(); + auto* hashPubKeys = output.mutable_hash_public_keys(); + for (auto& h : hashList) { + auto* hpk = hashPubKeys->Add(); + hpk->set_data_hash(h.first.data(), h.first.size()); + hpk->set_public_key_hash(h.second.data(), h.second.size()); + } + return output; +} + +} // namespace TW::BitcoinDiamond diff --git a/src/BitcoinDiamond/Signer.h b/src/BitcoinDiamond/Signer.h new file mode 100644 index 00000000000..b5639a1d30d --- /dev/null +++ b/src/BitcoinDiamond/Signer.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../Data.h" +#include "../PrivateKey.h" +#include "../proto/Bitcoin.pb.h" + +#include + +namespace TW::BitcoinDiamond { + +using SigningInput = Bitcoin::Proto::SigningInput; +using SigningOutput = Bitcoin::Proto::SigningOutput; +using TransactionPlan = Bitcoin::Proto::TransactionPlan; +using PreSigningOutput = Bitcoin::Proto::PreSigningOutput; + +/// Helper class that performs BitcoinDiamond transaction signing. +class Signer { +public: + /// Hide default constructor + Signer() = delete; + + /// Returns a transaction plan (utxo selection, fee estimation) + static TransactionPlan plan(const SigningInput& input) noexcept; + + /// Signs a Proto::SigningInput transaction + static SigningOutput sign(const SigningInput& input, std::optional optionalExternalSigs = {}) noexcept; + + /// Collect pre-image hashes to be signed + static PreSigningOutput preImageHashes(const SigningInput& input) noexcept; +}; + +} // namespace TW::BitcoinDiamond diff --git a/src/BitcoinDiamond/Transaction.cpp b/src/BitcoinDiamond/Transaction.cpp new file mode 100644 index 00000000000..6d2135b6d27 --- /dev/null +++ b/src/BitcoinDiamond/Transaction.cpp @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Transaction.h" +#include "../Bitcoin/SigHashType.h" +#include "../BinaryCoding.h" +#include "../Hash.h" +#include "../Data.h" + +#include + +using namespace TW; +namespace TW::BitcoinDiamond { + +Data Transaction::getPreImage(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount) const { + assert(index < inputs.size()); + + Data data; + + // Version + encode32LE(_version, data); + + // see: https://github.com/eveybcd/BitcoinDiamond/blob/master/src/script/interpreter.cpp#L1267 + if (_version == CURRENT_VERSION_FORK) { + std::copy(std::begin(preBlockHash), std::end(preBlockHash), std::back_inserter(data)); + } + + // Input prevouts (none/all, depending on flags) + if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0) { + auto hashPrevouts = getPrevoutHash(); + std::copy(std::begin(hashPrevouts), std::end(hashPrevouts), std::back_inserter(data)); + } else { + std::fill_n(back_inserter(data), 32, 0); + } + + // Input nSequence (none/all, depending on flags) + if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0 && + !Bitcoin::hashTypeIsSingle(hashType) && !Bitcoin::hashTypeIsNone(hashType)) { + auto hashSequence = getSequenceHash(); + std::copy(std::begin(hashSequence), std::end(hashSequence), std::back_inserter(data)); + } else { + std::fill_n(back_inserter(data), 32, 0); + } + + // The input being signed (replacing the scriptSig with scriptCode + amount) + // The prevout may already be contained in hashPrevout, and the nSequence + // may already be contain in hashSequence. + inputs[index].previousOutput.encode(data); + scriptCode.encode(data); + + encode64LE(amount, data); + encode32LE(inputs[index].sequence, data); + + // Outputs (none/one/all, depending on flags) + if (!Bitcoin::hashTypeIsSingle(hashType) && !Bitcoin::hashTypeIsNone(hashType)) { + auto hashOutputs = getOutputsHash(); + copy(begin(hashOutputs), end(hashOutputs), back_inserter(data)); + } else if (Bitcoin::hashTypeIsSingle(hashType) && index < outputs.size()) { + Data outputData; + outputs[index].encode(outputData); + auto hashOutputs = Hash::hash(hasher, outputData); + copy(begin(hashOutputs), end(hashOutputs), back_inserter(data)); + } else { + fill_n(back_inserter(data), 32, 0); + } + + // Locktime + encode32LE(lockTime, data); + + // Sighash type + encode32LE(hashType, data); + + return data; +} + + +void Transaction::encode(Data& data, enum SegwitFormatMode segwitFormat) const { + bool useWitnessFormat = true; + switch (segwitFormat) { + case NonSegwit: useWitnessFormat = false; break; + case IfHasWitness: useWitnessFormat = hasWitness(); break; + case Segwit: useWitnessFormat = true; break; + } + + encode32LE(_version, data); + + // see: https://github.com/eveybcd/BitcoinDiamond/blob/master/src/primitives/transaction.h#L344 + if (_version == CURRENT_VERSION_FORK) { + std::copy(std::begin(preBlockHash), std::end(preBlockHash), std::back_inserter(data)); + } + + if (useWitnessFormat) { + // Use extended format in case witnesses are to be serialized. + data.push_back(0); // marker + data.push_back(1); // flag + } + + // txins + encodeVarInt(inputs.size(), data); + for (auto& input : inputs) { + input.encode(data); + } + + // txouts + encodeVarInt(outputs.size(), data); + for (auto& output : outputs) { + output.encode(data); + } + + if (useWitnessFormat) { + encodeWitness(data); + } + + encode32LE(lockTime, data); // nLockTime +} + + +Data Transaction::getSignatureHash(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount, + enum Bitcoin::SignatureVersion version) const { + switch (version) { + case Bitcoin::BASE: + return getSignatureHashBase(scriptCode, index, hashType); + case Bitcoin::WITNESS_V0: + return getSignatureHashWitnessV0(scriptCode, index, hashType, amount); + } +} + +/// Generates the signature hash for Witness version 0 scripts. +Data Transaction::getSignatureHashWitnessV0(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, + uint64_t amount) const { + auto preimage = getPreImage(scriptCode, index, hashType, amount); + auto hash = Hash::hash(hasher, preimage); + return hash; +} + +/// Generates the signature hash for for scripts other than witness scripts. +Data Transaction::getSignatureHashBase(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType) const { + assert(index < inputs.size()); + + Data data; + + encode32LE(_version, data); + + // see: https://github.com/eveybcd/BitcoinDiamond/blob/master/src/script/interpreter.cpp#L1170 + if (_version == CURRENT_VERSION_FORK) { + std::copy(std::begin(preBlockHash), std::end(preBlockHash), std::back_inserter(data)); + } + + auto serializedInputCount = + (hashType & TWBitcoinSigHashTypeAnyoneCanPay) != 0 ? 1 : inputs.size(); + encodeVarInt(serializedInputCount, data); + for (auto subindex = 0ul; subindex < serializedInputCount; subindex += 1) { + serializeInput(subindex, scriptCode, index, hashType, data); + } + + auto hashNone = Bitcoin::hashTypeIsNone(hashType); + auto hashSingle = Bitcoin::hashTypeIsSingle(hashType); + auto serializedOutputCount = hashNone ? 0 : (hashSingle ? index + 1 : outputs.size()); + encodeVarInt(serializedOutputCount, data); + for (auto subindex = 0ul; subindex < serializedOutputCount; subindex += 1) { + if (hashSingle && subindex != index) { + auto output = Bitcoin::TransactionOutput(-1, {}); + output.encode(data); + } else { + outputs[subindex].encode(data); + } + } + + // Locktime + encode32LE(lockTime, data); + + // Sighash type + encode32LE(hashType, data); + + auto hash = Hash::hash(hasher, data); + return hash; +} + +Bitcoin::Proto::Transaction Transaction::proto() const { + auto protoTx = Bitcoin::Proto::Transaction(); + protoTx.set_version(_version); + protoTx.set_locktime(lockTime); + + for (const auto& input : inputs) { + auto* protoInput = protoTx.add_inputs(); + protoInput->mutable_previousoutput()->set_hash(input.previousOutput.hash.data(), + input.previousOutput.hash.size()); + protoInput->mutable_previousoutput()->set_index(input.previousOutput.index); + protoInput->set_sequence(input.sequence); + protoInput->set_script(input.script.bytes.data(), input.script.bytes.size()); + } + + for (const auto& output : outputs) { + auto* protoOutput = protoTx.add_outputs(); + protoOutput->set_value(output.value); + protoOutput->set_script(output.script.bytes.data(), output.script.bytes.size()); + } + + return protoTx; +} + +} // namespace TW::BitcoinDiamond diff --git a/src/BitcoinDiamond/Transaction.h b/src/BitcoinDiamond/Transaction.h new file mode 100644 index 00000000000..55f68ca1333 --- /dev/null +++ b/src/BitcoinDiamond/Transaction.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include + +#include "../Bitcoin/Script.h" +#include "../Bitcoin/Transaction.h" +#include "../Bitcoin/TransactionInput.h" +#include "../Bitcoin/TransactionOutput.h" +#include "../proto/Bitcoin.pb.h" +#include "Data.h" + +namespace TW::BitcoinDiamond { + +struct Transaction : public Bitcoin::Transaction { +public: + // see: https://github.com/eveybcd/BitcoinDiamond/blob/master/src/primitives/transaction.h#L209 + static const int32_t CURRENT_VERSION_FORK = 12; + static const int32_t CURRENT_VERSION = CURRENT_VERSION_FORK; + + Data preBlockHash; + +public: + Transaction() : Bitcoin::Transaction(CURRENT_VERSION, 0, TW::Hash::HasherSha256d) {} + Transaction(const Data& blockHash, int32_t version = CURRENT_VERSION, uint32_t lockTime = 0) + : Bitcoin::Transaction(version, lockTime, TW::Hash::HasherSha256d) + , preBlockHash(blockHash) {} + + Data getPreImage(const Bitcoin::Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount) const; + + /// Encodes the transaction into the provided buffer. + void encode(Data& data, enum SegwitFormatMode segwitFormat) const; + void encode(Data& data) const { encode(data, SegwitFormatMode::IfHasWitness); } + + Data getSignatureHash(const Bitcoin::Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, + uint64_t amount, enum Bitcoin::SignatureVersion version) const; + /// Converts to Protobuf model + Bitcoin::Proto::Transaction proto() const; + +private: + /// Generates the signature hash for Witness version 0 scripts. + Data getSignatureHashWitnessV0(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount) const; + + /// Generates the signature hash for for scripts other than witness scripts. + Data getSignatureHashBase(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType) const; +}; + +} // namespace TW::BitcoinDiamond diff --git a/src/BitcoinDiamond/TransactionBuilder.h b/src/BitcoinDiamond/TransactionBuilder.h new file mode 100644 index 00000000000..7134ae515fd --- /dev/null +++ b/src/BitcoinDiamond/TransactionBuilder.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Transaction.h" +#include "../Bitcoin/TransactionBuilder.h" +#include "../Bitcoin/TransactionPlan.h" +#include "../proto/Bitcoin.pb.h" +#include "../HexCoding.h" +#include + +#include + +namespace TW::BitcoinDiamond { + +struct TransactionBuilder { + /// Plans a transaction by selecting UTXOs and calculating fees. + static Bitcoin::TransactionPlan plan(const Bitcoin::SigningInput& input) { + return Bitcoin::TransactionBuilder::plan(input); + } + + /// Builds a transaction by selecting UTXOs and calculating fees. + template + static Result build(const Bitcoin::TransactionPlan& plan, + const Bitcoin::SigningInput& input) { + auto tx_result = Bitcoin::TransactionBuilder::build(plan, input); + if (!tx_result) { return Result::failure(tx_result.error()); } + Transaction tx = tx_result.payload(); + std::copy(plan.preBlockHash.begin(), plan.preBlockHash.end(), + std::back_inserter(tx.preBlockHash)); + return Result(tx); + } +}; + +} // namespace TW::BitcoinDiamond diff --git a/src/Cardano/AddressV2.cpp b/src/Cardano/AddressV2.cpp index 2efba5e56f8..695f9451788 100644 --- a/src/Cardano/AddressV2.cpp +++ b/src/Cardano/AddressV2.cpp @@ -1,51 +1,44 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "AddressV2.h" -#include "../Cbor.h" -#include "../Data.h" #include "../Base58.h" +#include "../Cbor.h" #include "../Crc.h" -#include "../HexCoding.h" -#include "../Hash.h" #include -using namespace TW; -using namespace TW::Cardano; -using namespace std; +namespace TW::Cardano { bool AddressV2::parseAndCheck(const std::string& addr, Data& root_out, Data& attrs_out, byte& type_out) { // Decode Bas58, decode payload + crc, decode root, attr - Data base58decoded = Base58::bitcoin.decode(addr); - if (base58decoded.size() == 0) { - throw invalid_argument("Invalid address: could not Base58 decode"); + Data base58decoded = Base58::decode(addr); + if (base58decoded.empty()) { + return false; } auto elems = Cbor::Decode(base58decoded).getArrayElements(); if (elems.size() < 2) { - throw invalid_argument("Could not parse address payload from CBOR data"); + return false; } auto tag = elems[0].getTagValue(); if (tag != PayloadTag) { - throw invalid_argument("wrong tag value"); + return false; } Data payload = elems[0].getTagElement().getBytes(); uint64_t crcPresent = (uint32_t)elems[1].getValue(); uint32_t crcComputed = TW::Crc::crc32(payload); if (crcPresent != crcComputed) { - throw invalid_argument("CRC mismatch"); + return false; } // parse payload, 3 elements auto payloadElems = Cbor::Decode(payload).getArrayElements(); if (payloadElems.size() < 3) { - throw invalid_argument("Could not parse address root and attrs from CBOR data"); + return false; } root_out = payloadElems[0].getBytes(); attrs_out = payloadElems[1].encoded(); // map, but encoded as bytes - type_out = (TW::byte)payloadElems[2].getValue(); + type_out = (byte)payloadElems[2].getValue(); return true; } @@ -54,10 +47,12 @@ bool AddressV2::isValid(const std::string& string) { Data root; Data attrs; byte type = 0; - if (!parseAndCheck(string, root, attrs, type)) { return false; } + if (!parseAndCheck(string, root, attrs, type)) { + return false; + } // valid return true; - } catch (exception& ex) { + } catch (std::exception& ex) { return false; } } @@ -71,7 +66,7 @@ AddressV2::AddressV2(const std::string& string) { AddressV2::AddressV2(const PublicKey& publicKey) { // input is extended pubkey, 64-byte - if (publicKey.type != TWPublicKeyTypeED25519Extended || publicKey.bytes.size() != PublicKey::ed25519DoubleExtendedSize) { + if (publicKey.type != TWPublicKeyTypeED25519Cardano || publicKey.bytes.size() != PublicKey::cardanoKeySize) { throw std::invalid_argument("Invalid public key type"); } type = 0; // public key @@ -82,7 +77,7 @@ AddressV2::AddressV2(const PublicKey& publicKey) { } Data AddressV2::getCborData() const { - // put together string represenatation, CBOR representation + // put together string representation, CBOR representation // inner data: pubkey, attrs, type auto cbor1 = Cbor::Encode::array({ Cbor::Encode::bytes(root), @@ -90,8 +85,8 @@ Data AddressV2::getCborData() const { Cbor::Encode::uint(type), }); auto payloadData = cbor1.encoded(); - - // crc checksum + + // crc checksum auto crc = TW::Crc::crc32(payloadData); // second pack: tag, base, crc auto cbor2 = Cbor::Encode::array({ @@ -101,25 +96,29 @@ Data AddressV2::getCborData() const { return cbor2.encoded(); } -string AddressV2::string() const { +std::string AddressV2::string() const { // Base58 encode the CBOR data - return Base58::bitcoin.encode(getCborData()); + return Base58::encode(getCborData()); } Data AddressV2::keyHash(const TW::Data& xpub) { - if (xpub.size() != 64) { throw invalid_argument("invalid xbub length"); } - // hash of follwoing Cbor-array: [0, [0, xbub], {} ] + if (xpub.size() != 64) { + return {}; + } + // hash of following Cbor-array: [0, [0, xpub], {} ] // 3rd entry map is empty map for V2, contains derivation path for V1 + // clang-format off Data cborData = Cbor::Encode::array({ Cbor::Encode::uint(0), - Cbor::Encode::array({ - Cbor::Encode::uint(0), - Cbor::Encode::bytes(xpub) - }), + Cbor::Encode::array({Cbor::Encode::uint(0), + Cbor::Encode::bytes(xpub)}), Cbor::Encode::map({}), }).encoded(); + // clang-format on // SHA3 hash, then blake Data firstHash = Hash::sha3_256(cborData); Data blake = Hash::blake2b(firstHash, 28); return blake; } + +} // namespace TW::Cardano diff --git a/src/Cardano/AddressV2.h b/src/Cardano/AddressV2.h index 6c03006b4e0..742c3eeaba6 100644 --- a/src/Cardano/AddressV2.h +++ b/src/Cardano/AddressV2.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -20,7 +18,7 @@ namespace TW::Cardano { * Derivation is BIP39, default derivation path is "m/44'/1815'/0'/0/0", with last element being the account number. * Curve is ED25519 with special variations, custom logic in HDWallet and TrezorCrypto lib. * Private key is ED25519: 32-byte PK is extended with 32-byte extra extension, and 32-byte chain code. - * Private key is derived from mnemonic raw entropy (not seed, as in other cases); 96-byte secret is generated (pk, extrension, and chain code). + * Private key is derived from mnemonic raw entropy (not seed, as in other cases); 96-byte secret is generated (pk, extension, and chain code). * Public key is 64-byte: the 32-byte ED25519 public key plus the 32-byte chain code of the PK. * Address derivation: Only V2, type 0 (=public key) addresses are generated. * - CBOR binary scheme is used inside addresses. @@ -46,7 +44,7 @@ class AddressV2 { Data attrs; /// Type; 0: public key. - TW::byte type; + TW::byte type{}; static const TW::byte PayloadTag = 24; diff --git a/src/Cardano/AddressV3.cpp b/src/Cardano/AddressV3.cpp index 20f297bb33f..84e264256a6 100644 --- a/src/Cardano/AddressV3.cpp +++ b/src/Cardano/AddressV3.cpp @@ -1,29 +1,54 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "AddressV3.h" #include "AddressV2.h" #include -#include "../Data.h" #include "../Bech32.h" #include "../Base32.h" -#include "../Crc.h" #include "../HexCoding.h" -#include "../Hash.h" #include -using namespace TW; -using namespace TW::Cardano; -using namespace std; +namespace TW::Cardano { -bool AddressV3::parseAndCheckV3(const std::string& addr, NetworkId& networkId, Kind& kind, Data& raw) { +bool AddressV3::checkLength(Kind kind, size_t length) noexcept { + switch (kind) { + case Kind_Base: + return (length == EncodedSize2); + + case Kind_Enterprise: + case Kind_Reward: + return (length == EncodedSize1); + + default: + // accept other types as well + return true; + } +} + +bool AddressV3::parseAndCheckV3(const Data& raw, NetworkId& networkId, Kind& kind, Data& bytes) noexcept { + if (raw.empty()) { + // too short, cannot extract kind and networkId + return false; + } + kind = kindFromFirstByte(raw[0]); + networkId = networkIdFromFirstByte(raw[0]); + if (networkId != Network_Production) { + return false; + } + + bytes = Data(); + std::copy(cbegin(raw) + 1, cend(raw), std::back_inserter(bytes)); + + return checkLength(kind, raw.size()); +} + +bool AddressV3::parseAndCheckV3(const std::string& addr, NetworkId& networkId, Kind& kind, Data& bytes) noexcept { try { auto bech = Bech32::decode(addr); - if (std::get<1>(bech).size() == 0) { + if (std::get<1>(bech).empty()) { // empty Bech data return false; } @@ -33,14 +58,16 @@ bool AddressV3::parseAndCheckV3(const std::string& addr, NetworkId& networkId, K if (!success) { return false; } - if (conv.size() != EncodedSize) { + + if (!parseAndCheckV3(conv, networkId, kind, bytes)) { + return false; + } + + // check prefix + if (const auto expectedHrp = getHrp(kind); !addr.starts_with(expectedHrp)) { return false; } - kind = kindFromFirstByte(conv[0]); - networkId = networkIdFromFirstByte(conv[0]); - raw = Data(conv.size() - 1); - std::copy(conv.begin() + 1, conv.end(), raw.begin()); return true; } catch (...) { return false; @@ -98,9 +125,20 @@ AddressV3 AddressV3::createBase(NetworkId networkId, const PublicKey& spendingKe return createBase(networkId, hash1, hash2); } +AddressV3 AddressV3::createReward(NetworkId networkId, const TW::Data& stakingKeyHash) { + if (stakingKeyHash.size() != HashSize) { + throw std::invalid_argument("Wrong spending key hash size"); + } + auto addr = AddressV3(); + addr.networkId = networkId; + addr.kind = Kind_Reward; + addr.bytes = stakingKeyHash; + return addr; +} + AddressV3::AddressV3(const std::string& addr) { if (parseAndCheckV3(addr, networkId, kind, bytes)) { - // values stored + // values stored return; } // try legacy @@ -110,27 +148,20 @@ AddressV3::AddressV3(const std::string& addr) { AddressV3::AddressV3(const PublicKey& publicKey) { // input is double extended pubkey - if (publicKey.type != TWPublicKeyTypeED25519Extended || publicKey.bytes.size() != PublicKey::ed25519DoubleExtendedSize) { + if (publicKey.type != TWPublicKeyTypeED25519Cardano || publicKey.bytes.size() != PublicKey::cardanoKeySize) { throw std::invalid_argument("Invalid public key type"); } - kind = Kind_Base; - *this = createBase(Network_Production, PublicKey(subData(publicKey.bytes, 0, 32), TWPublicKeyTypeED25519), PublicKey(subData(publicKey.bytes, 64, 32), TWPublicKeyTypeED25519)); } AddressV3::AddressV3(const Data& data) { - if (data.size() != EncodedSize) { - throw std::invalid_argument("Address data too short"); - } - networkId = networkIdFromFirstByte(data[0]); - kind = kindFromFirstByte(data[0]); - bytes = subData(data, 1); + parseAndCheckV3(data, networkId, kind, bytes); } AddressV3::AddressV3(const AddressV3& other) = default; uint8_t AddressV3::firstByte(NetworkId networkId, Kind kind) { - byte first = (byte)(((byte)kind << 4) + networkId); + auto first = (byte)(((byte)kind << 4) + networkId); return first; } @@ -142,26 +173,23 @@ AddressV3::Kind AddressV3::kindFromFirstByte(uint8_t first) { return (Kind)((first & 0xF0) >> 4); } -void AddressV3::operator=(const AddressV3& other) -{ - networkId = other.networkId; - kind = other.kind; - bytes = other.bytes; - legacyAddressV2 = other.legacyAddressV2; -} - -string AddressV3::string() const { - std::string hrp; +std::string AddressV3::getHrp(Kind kind) noexcept { switch (kind) { - case Kind_Base: - hrp = stringForHRP(TWHRPCardano); break; - default: - hrp = ""; break; + case Kind_Base: + case Kind_Enterprise: + default: + return stringForHRP(TWHRPCardano); + case Kind_Reward: + return "stake"; } +} + +std::string AddressV3::string() const { + const auto hrp = getHrp(kind); return string(hrp); } -string AddressV3::string(const std::string& hrp) const { +std::string AddressV3::string(const std::string& hrp) const { if (legacyAddressV2.has_value()) { return legacyAddressV2->string(); } @@ -175,7 +203,7 @@ string AddressV3::string(const std::string& hrp) const { return Bech32::encode(hrp, bech, Bech32::ChecksumVariant::Bech32); } -Data AddressV3::data() const { +Data AddressV3::data() const noexcept { if (legacyAddressV2.has_value()) { return legacyAddressV2->getCborData(); } @@ -186,3 +214,13 @@ Data AddressV3::data() const { TW::append(raw, bytes); return raw; } + +std::string AddressV3::getStakingAddress() const noexcept { + if (kind != Kind_Base || bytes.size() != (2 * HashSize)) { + return ""; + } + const auto& stakingKeyHash = TW::subData(bytes, HashSize, HashSize); + return createReward(this->networkId, stakingKeyHash).string(); +} + +} // namespace TW::Cardano diff --git a/src/Cardano/AddressV3.h b/src/Cardano/AddressV3.h index eff53d499b4..199d9e03c9f 100644 --- a/src/Cardano/AddressV3.h +++ b/src/Cardano/AddressV3.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -24,17 +22,28 @@ class AddressV3 { }; enum Kind: uint8_t { - Kind_Base = 0, + Kind_Base = 0, // spending + staking key + //Kind_Base_Script_Key = 1, + //Kind_Base_Key_Script_Key = 2, + //Kind_Base_Script_Script_Key = 3, + //Kind_Pointer = 4, + //Kind_Pointer_Script = 5, + Kind_Enterprise = 6, // spending key + //Kind_Enterprise_Script = 7, + //Kind_Bootstrap = 8, // Byron + Kind_Reward = 14, // // staking key + //Kind_Reward_Script = 15, }; static const uint8_t HashSize = 28; // First byte header (kind, netowrkId) and 2 hashes - static const uint8_t EncodedSize = 1 + 2 * HashSize; + static const uint8_t EncodedSize1 = 1 + HashSize; + static const uint8_t EncodedSize2 = 1 + 2 * HashSize; - NetworkId networkId = Network_Production; + NetworkId networkId{Network_Production}; - Kind kind = Kind_Base; + Kind kind{Kind_Base}; /// raw key/hash bytes Data bytes; @@ -54,6 +63,9 @@ class AddressV3 { /// Create a base address, given public keys static AddressV3 createBase(NetworkId networkId, const PublicKey& spendingKey, const PublicKey& stakingKey); + /// Create a staking (reward) address, given a staking key + static AddressV3 createReward(NetworkId networkId, const TW::Data& stakingKeyHash); + /// Initializes a Cardano address with a string representation. Throws if invalid. explicit AddressV3(const std::string& addr); @@ -66,18 +78,26 @@ class AddressV3 { /// Copy constructor AddressV3(const AddressV3& other); - void operator=(const AddressV3& other); + AddressV3& operator=(const AddressV3& other) noexcept = default; /// Returns the Bech string representation of the address, with default HRP. std::string string() const; /// Returns the Bech string representation of the address, with given HRP. std::string string(const std::string& hrp) const; + /// Hrp of kind + static std::string getHrp(Kind kind) noexcept; + /// Check whether data length is correct + static bool checkLength(Kind kind, size_t length) noexcept; + /// Check validity of binary address. + static bool parseAndCheckV3(const TW::Data& raw, NetworkId& networkId, Kind& kind, TW::Data& bytes) noexcept; /// Check validity and parse elements of a string address. Used internally by isValid and ctor. - static bool parseAndCheckV3(const std::string& addr, NetworkId& networkId, Kind& kind, TW::Data& raw); + static bool parseAndCheckV3(const std::string& addr, NetworkId& networkId, Kind& kind, TW::Data& bytes) noexcept; /// Return the binary data representation (keys appended, internal format) - Data data() const; + Data data() const noexcept; + /// Return the staking address associated to (contained in) this address. Must be a Base address. Empty string is returned on error. + std::string getStakingAddress() const noexcept; // First encoded byte, from networkId and Kind static uint8_t firstByte(NetworkId networkId, Kind kind); @@ -85,7 +105,7 @@ class AddressV3 { static Kind kindFromFirstByte(uint8_t first); private: - AddressV3() : networkId(Network_Production), kind(Kind_Base) {} + AddressV3() = default; }; inline bool operator==(const AddressV3& lhs, const AddressV3& rhs) { diff --git a/src/Cardano/Entry.cpp b/src/Cardano/Entry.cpp index 4cac8a06ae4..688ac1ed355 100644 --- a/src/Cardano/Entry.cpp +++ b/src/Cardano/Entry.cpp @@ -1,39 +1,63 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "AddressV3.h" #include "Signer.h" #include "../proto/Cardano.pb.h" +#include "../proto/TransactionCompiler.pb.h" -#include - -using namespace TW::Cardano; -using namespace TW; -using namespace std; +namespace TW::Cardano { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return AddressV3::isValidLegacy(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return AddressV3(publicKey).string(); } -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { return AddressV3(address).data(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -void Entry::plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const { +void Entry::plan([[maybe_unused]] TWCoinType coin, const Data& dataIn, Data& dataOut) const { planTemplate(dataIn, dataOut); } + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + Transaction tx; + const auto buildRet = Signer::buildTx(tx, input); + if (buildRet != Common::Proto::OK) { + output.set_error(buildRet); + output.set_error_message(Common::Proto::SigningError_Name(buildRet)); + return; + } + auto hash = tx.getId(); + auto encoded = tx.encode(); + output.set_data_hash(hash.data(), hash.size()); + output.set_data(encoded.data(), encoded.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + auto encoded = Signer::encodeTransactionWithSig(input, publicKey, signature); + output.set_encoded(encoded.data(), encoded.size()); + return; + }); +} + +} // namespace TW::Cardano diff --git a/src/Cardano/Entry.h b/src/Cardano/Entry.h index 10998d90b6c..ed73e24e5c7 100644 --- a/src/Cardano/Entry.h +++ b/src/Cardano/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,13 +10,16 @@ namespace TW::Cardano { /// Entry point for implementation of Cardano coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Cardano diff --git a/src/Cardano/Signer.cpp b/src/Cardano/Signer.cpp index 3cdb0ebba88..80336de5e94 100644 --- a/src/Cardano/Signer.cpp +++ b/src/Cardano/Signer.cpp @@ -1,26 +1,21 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "AddressV3.h" -#include "PrivateKey.h" #include "Cbor.h" #include "HexCoding.h" +#include "PrivateKey.h" -#include +#include #include #include -#include #include +#include -using namespace TW::Cardano; -using namespace TW; -using namespace std; - +namespace TW::Cardano { static const Data placeholderPrivateKey = parse_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); static const auto PlaceholderFee = 170000; @@ -40,7 +35,7 @@ Proto::SigningOutput Signer::sign() { Common::Proto::SigningError Signer::buildTransactionAux(Transaction& tx, const Proto::SigningInput& input, const TransactionPlan& plan) { tx = Transaction(); - for (const auto& i: plan.utxos) { + for (const auto& i : plan.utxos) { tx.inputs.emplace_back(i.txHash, i.outputIndex); } @@ -50,6 +45,12 @@ Common::Proto::SigningError Signer::buildTransactionAux(Transaction& tx, const P } const auto toAddress = AddressV3(input.transfer_message().to_address()); tx.outputs.emplace_back(toAddress.data(), plan.amount, plan.outputTokens); + + for (auto& output: plan.extraOutputs) { + const auto extraToAddress = AddressV3(output.address); + tx.outputs.emplace_back(extraToAddress.data(), output.amount, output.tokenBundle); + } + // Change bool hasChangeToken = any_of(plan.changeTokens.bundle.begin(), plan.changeTokens.bundle.end(), [](auto&& t) { return t.second.amount > 0; }); if (plan.change > 0 || hasChangeToken) { @@ -62,37 +63,103 @@ Common::Proto::SigningError Signer::buildTransactionAux(Transaction& tx, const P tx.fee = plan.fee; tx.ttl = input.ttl(); + if (input.has_register_staking_key()) { + const auto stakingAddress = AddressV3(input.register_staking_key().staking_address()); + // here we need the bare staking key + const auto key = stakingAddress.bytes; + tx.certificates.emplace_back(Certificate{Certificate::SkatingKeyRegistration, {CertificateKey{CertificateKey::AddressKeyHash, key}}, Data()}); + } + if (input.has_delegate()) { + const auto stakingAddress = AddressV3(input.delegate().staking_address()); + // here we need the bare staking key + const auto key = stakingAddress.bytes; + const auto poolId = data(input.delegate().pool_id()); + tx.certificates.emplace_back(Certificate{Certificate::Delegation, {CertificateKey{CertificateKey::AddressKeyHash, key}}, poolId}); + } + if (input.has_withdraw()) { + const auto stakingAddress = AddressV3(input.withdraw().staking_address()); + const auto key = stakingAddress.data(); + const auto amount = input.withdraw().withdraw_amount(); + tx.withdrawals.emplace_back(Withdrawal{key, amount}); + } + if (input.has_deregister_staking_key()) { + const auto stakingAddress = AddressV3(input.deregister_staking_key().staking_address()); + // here we need the bare staking key + const auto key = stakingAddress.bytes; + tx.certificates.emplace_back(Certificate{Certificate::StakingKeyDeregistration, {CertificateKey{CertificateKey::AddressKeyHash, key}}, Data()}); + } + return Common::Proto::OK; } -Common::Proto::SigningError Signer::assembleSignatures(vector>& signatures, const Proto::SigningInput& input, const TransactionPlan& plan, const Data& txId, bool sizeEstimationOnly) { +Data deriveStakingPrivateKey(const Data& privateKeyData) { + if (privateKeyData.size() != PrivateKey::cardanoKeySize) { + return {}; + } + assert(privateKeyData.size() == PrivateKey::cardanoKeySize); + const auto halfSize = PrivateKey::cardanoKeySize / 2; + auto stakingPrivKeyData = TW::subData(privateKeyData, halfSize); + TW::append(stakingPrivKeyData, TW::Data(halfSize)); + return stakingPrivKeyData; +} + +Common::Proto::SigningError Signer::assembleSignatures(std::vector>& signatures, const Proto::SigningInput& input, const TransactionPlan& plan, const Data& txId, bool sizeEstimationOnly) { signatures.clear(); // Private keys and corresponding addresses - map privateKeys; + std::map privateKeys; for (auto i = 0; i < input.private_key_size(); ++i) { const auto privateKeyData = data(input.private_key(i)); if (!PrivateKey::isValid(privateKeyData)) { return Common::Proto::Error_invalid_private_key; } + + // Add this private key and associated address const auto privateKey = PrivateKey(privateKeyData); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); const auto address = AddressV3(publicKey); privateKeys[address.string()] = privateKeyData; + + const auto legacyAddress = AddressV2(publicKey); + privateKeys[legacyAddress.string()] = privateKeyData; + + // Also add the derived staking private key (the 2nd half) and associated address; because staking keys also need signature + const auto stakingPrivKeyData = deriveStakingPrivateKey(privateKeyData); + if (!stakingPrivKeyData.empty()) { + privateKeys[address.getStakingAddress()] = stakingPrivKeyData; + } } - // collect every unique input UTXO address - vector addresses; - for (auto& u: plan.utxos) { - if (!AddressV3::isValid(u.address)) { + // collect every unique input UTXO address, preserving order + std::vector addresses; + for (auto& u : plan.utxos) { + if (!AddressV3::isValidLegacy(u.address)) { return Common::Proto::Error_invalid_address; } - if (find(addresses.begin(), addresses.end(), u.address) == addresses.end()) { - addresses.push_back(u.address); + addresses.emplace_back(u.address); + } + // Staking key is also an address that needs signature + if (input.has_register_staking_key()) { + addresses.emplace_back(input.register_staking_key().staking_address()); + } + if (input.has_deregister_staking_key()) { + addresses.emplace_back(input.deregister_staking_key().staking_address()); + } + if (input.has_delegate()) { + addresses.emplace_back(input.delegate().staking_address()); + } + if (input.has_withdraw()) { + addresses.emplace_back(input.withdraw().staking_address()); + } + // discard duplicates (std::set, std::copy_if, std::unique does not work well here) + std::vector addressesUnique; + for (auto& a: addresses) { + if (find(addressesUnique.begin(), addressesUnique.end(), a) == addressesUnique.end()) { + addressesUnique.emplace_back(a); } } // create signature for each address - for (auto& a: addresses) { + for (auto& a : addressesUnique) { const auto privKeyFind = privateKeys.find(a); Data privateKeyData; if (privKeyFind != privateKeys.end()) { @@ -106,35 +173,59 @@ Common::Proto::SigningError Signer::assembleSignatures(vector>& } } const auto privateKey = PrivateKey(privateKeyData); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - const auto signature = privateKey.sign(txId, TWCurveED25519Extended); - // public key (first 32 bytes) and signature (64 bytes) - signatures.emplace_back(subData(publicKey.bytes, 0, 32), signature); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto signature = privateKey.sign(txId, TWCurveED25519ExtendedCardano); + signatures.emplace_back(publicKey.bytes, signature); } return Common::Proto::OK; } -Cbor::Encode cborizeSignatures(const vector>& signatures) { +Cbor::Encode cborizeSignatures(const std::vector>& signatures, const bool addByronSignatures) { + std::map cborizeSigs; // signatures as Cbor - vector sigsCbor; - for (auto& s: signatures) { - sigsCbor.emplace_back(Cbor::Encode::array({ - Cbor::Encode::bytes(s.first), + // clang-format off + std::vector sigsShelly; + std::vector sigsByron; + + for (auto& s : signatures) { + sigsShelly.emplace_back(Cbor::Encode::array({ + // public key (first 32 bytes) + Cbor::Encode::bytes(subData(s.first, 0, 32)), Cbor::Encode::bytes(s.second) })); + + if (addByronSignatures) { + sigsByron.emplace_back(Cbor::Encode::array({ + // skey - public key (first 32 bytes) + Cbor::Encode::bytes(subData(s.first, 0, 32)), + Cbor::Encode::bytes(s.second), + // vkey - public key (second 32 bytes started from 32) + Cbor::Encode::bytes(subData(s.first, 32, 32)), + // payload + Cbor::Encode::bytes(parse_hex("A0")) + })); + } + } + + cborizeSigs.emplace( + Cbor::Encode::uint(0), + Cbor::Encode::array(sigsShelly) + ); + + if (!sigsByron.empty()) { + cborizeSigs.emplace( + Cbor::Encode::uint(2), + Cbor::Encode::array(sigsByron) + ); } // Cbor-encode txAux & signatures - return Cbor::Encode::map({ - make_pair( - Cbor::Encode::uint(0), - Cbor::Encode::array(sigsCbor) - ) - }); + return Cbor::Encode::map(cborizeSigs); + // clang-format on } -Proto::SigningOutput Signer::signWithPlan() { +Proto::SigningOutput Signer::signWithPlan() const { auto ret = Proto::SigningOutput(); if (_plan.error != Common::Proto::OK) { // plan has error @@ -150,8 +241,8 @@ Proto::SigningOutput Signer::signWithPlan() { return ret; } - ret.set_encoded(string(encoded.begin(), encoded.end())); - ret.set_tx_id(string(txId.begin(), txId.end())); + ret.set_encoded(std::string(encoded.begin(), encoded.end())); + ret.set_tx_id(std::string(txId.begin(), txId.end())); ret.set_error(Common::Proto::OK); return ret; @@ -169,12 +260,21 @@ Common::Proto::SigningError Signer::encodeTransaction(Data& encoded, Data& txId, } txId = txAux.getId(); - vector> signatures; + std::vector> signatures; const auto sigError = assembleSignatures(signatures, input, plan, txId, sizeEstimationOnly); if (sigError != Common::Proto::OK) { return sigError; } - const auto sigsCbor = cborizeSignatures(signatures); + + bool hasLegacyUtxos = false; + for (const auto& utxo : input.utxos()) { + if (AddressV2::isValid(utxo.address())) { + hasLegacyUtxos = true; + break; + } + } + + const auto sigsCbor = cborizeSignatures(signatures, hasLegacyUtxos); // Cbor-encode txAux & signatures const auto cbor = Cbor::Encode::array({ @@ -189,17 +289,17 @@ Common::Proto::SigningError Signer::encodeTransaction(Data& encoded, Data& txId, return Common::Proto::OK; } -// Select a subset of inputs, to cover desired coin amount. Simple algorithm: pick largest ones. -vector selectInputsSimpleNative(const vector& inputs, Amount amount) { - auto ii = vector(inputs); - sort(ii.begin(), ii.end(), [](TxInput t1, TxInput t2) { +// Select a subset of inputs, to cover desired coin amount. Simple algorithm: pick the largest ones. +std::vector selectInputsSimpleNative(const std::vector& inputs, Amount amount) { + auto ii = std::vector(inputs); + sort(ii.begin(), ii.end(), [](auto&& t1, auto&& t2) { return t1.amount > t2.amount; }); - auto selected = vector(); + auto selected = std::vector(); Amount selectedAmount = 0; - for (const auto& i: ii) { - selected.push_back(i); + for (const auto& i : ii) { + selected.emplace_back(i); selectedAmount += i.amount; if (selectedAmount >= amount) { break; @@ -208,21 +308,22 @@ vector selectInputsSimpleNative(const vector& inputs, Amount a return selected; } -// Select a subset of inputs, to cover desired token amount. Simple algorithm: pick largest ones. -void selectInputsSimpleToken(const vector& inputs, string key, uint256_t amount, vector& selectedInputs) { - uint256_t selectedAmount = std::accumulate(selectedInputs.begin(), selectedInputs.end(), uint256_t(0), [key](uint256_t sum, const TxInput& si){ return si.tokenBundle.getAmount(key); }); +// Select a subset of inputs, to cover desired token amount. Simple algorithm: pick the largest ones. +void selectInputsSimpleToken(const std::vector& inputs, std::string key, const uint256_t& amount, std::vector& selectedInputs) { + auto accumulateFunctor = [key]([[maybe_unused]] auto&& sum, auto&& si) { return si.tokenBundle.getAmount(key); }; + uint256_t selectedAmount = std::accumulate(selectedInputs.begin(), selectedInputs.end(), uint256_t(0), accumulateFunctor); if (selectedAmount >= amount) { return; // already covered } // sort inputs descending - auto ii = vector(inputs); - std::sort(ii.begin(), ii.end(), [key](TxInput t1, TxInput t2) { return t1.tokenBundle.getAmount(key) > t2.tokenBundle.getAmount(key); }); - for (const auto& i: ii) { - if (distance(selectedInputs.begin(), find(selectedInputs.begin(), selectedInputs.end(), i)) < selectedInputs.size()) { + auto ii = std::vector(inputs); + std::sort(ii.begin(), ii.end(), [key](auto&& t1, auto&& t2) { return t1.tokenBundle.getAmount(key) > t2.tokenBundle.getAmount(key); }); + for (const auto& i : ii) { + if (static_cast(distance(selectedInputs.begin(), find(selectedInputs.begin(), selectedInputs.end(), i))) < selectedInputs.size()) { // already selected continue; } - selectedInputs.push_back(i); + selectedInputs.emplace_back(i); selectedAmount += i.amount; if (selectedAmount >= amount) { return; @@ -231,43 +332,41 @@ void selectInputsSimpleToken(const vector& inputs, string key, uint256_ // not enough } -// Select a subset of inputs, to cover desired amount. Simple algorithm: pick largest ones -vector Signer::selectInputsWithTokens(const vector& inputs, Amount amount, const TokenBundle& requestedTokens) { +// Select a subset of inputs, to cover desired amount. Simple algorithm: pick the largest ones +std::vector Signer::selectInputsWithTokens(const std::vector& inputs, Amount amount, const TokenBundle& requestedTokens) { auto selected = selectInputsSimpleNative(inputs, amount); - for (auto iter = requestedTokens.bundle.begin(); iter != requestedTokens.bundle.end(); ++iter) { - const auto& ta = iter->second; - selectInputsSimpleToken(inputs, ta.key(), ta.amount, selected); + for (auto&& [_, curAmount] : requestedTokens.bundle) { + selectInputsSimpleToken(inputs, curAmount.key(), curAmount.amount, selected); } return selected; } // Create a simple plan, used for estimation -TransactionPlan simplePlan(Amount amount, const TokenBundle& requestedTokens, const vector& selectedInputs, bool maxAmount) { - TransactionPlan plan; - plan.amount = amount; - plan.utxos = selectedInputs; +TransactionPlan simplePlan(Amount amount, const TokenBundle& requestedTokens, const std::vector& selectedInputs, bool maxAmount, uint64_t deposit, uint64_t undeposit, const std::vector& extraOutputs) { + TransactionPlan plan{.utxos = selectedInputs, .extraOutputs = extraOutputs, .amount = amount, .deposit = deposit, .undeposit = undeposit}; // Sum availableAmount plan.availableAmount = 0; - for (auto& u: plan.utxos) { + for (auto& u : plan.utxos) { plan.availableAmount += u.amount; - for (auto iter = u.tokenBundle.bundle.begin(); iter != u.tokenBundle.bundle.end(); ++iter) { - plan.availableTokens.add(iter->second); + for (auto && [_, curAmount] : u.tokenBundle.bundle) { + plan.availableTokens.add(curAmount); } } plan.fee = PlaceholderFee; // placeholder value + const auto availAfterDeposit = plan.availableAmount + plan.undeposit - plan.deposit; // adjust/compute output amount and output tokens if (!maxAmount) { // reduce amount if needed - plan.amount = max(Amount(0), min(plan.amount, plan.availableAmount - plan.fee)); + plan.amount = std::max(Amount(0), std::min(plan.amount, availAfterDeposit - plan.fee)); plan.outputTokens = requestedTokens; } else { // max available amount - plan.amount = max(Amount(0), plan.availableAmount - plan.fee); + plan.amount = std::max(Amount(0), availAfterDeposit - plan.fee); plan.outputTokens = plan.availableTokens; // use all } // compute change - plan.change = plan.availableAmount - (plan.amount + plan.fee); + plan.change = availAfterDeposit - (plan.amount + plan.fee); for (auto iter = plan.availableTokens.bundle.begin(); iter != plan.availableTokens.bundle.end(); ++iter) { const auto key = iter->second.key(); const auto changeAmount = iter->second.amount - plan.outputTokens.getAmount(key); @@ -278,13 +377,37 @@ TransactionPlan simplePlan(Amount amount, const TokenBundle& requestedTokens, co return plan; } +uint64_t sumDeposits(const Proto::SigningInput& input) { + uint64_t sum = 0; + if (input.has_register_staking_key()) { + sum += input.register_staking_key().deposit_amount(); + } + if (input.has_delegate()) { + sum += input.delegate().deposit_amount(); + } + return sum; +} + +uint64_t sumUndeposits(const Proto::SigningInput& input) { + uint64_t sum = 0; + if (input.has_deregister_staking_key()) { + sum += input.deregister_staking_key().undeposit_amount(); + } + if (input.has_withdraw()) { + sum += input.withdraw().withdraw_amount(); + } + return sum; +} + // Estimates size of transaction in bytes. -uint64_t estimateTxSize(const Proto::SigningInput& input, Amount amount, const TokenBundle& requestedTokens, const vector& selectedInputs) { - auto inputs = vector(); +uint64_t estimateTxSize(const Proto::SigningInput& input, Amount amount, const TokenBundle& requestedTokens, const std::vector& selectedInputs, const std::vector& extraOutputs) { + auto inputs = std::vector(); for (auto i = 0; i < input.utxos_size(); ++i) { inputs.emplace_back(TxInput::fromProto(input.utxos(i))); } - const auto _simplePlan = simplePlan(amount, requestedTokens, selectedInputs, input.transfer_message().use_max_amount()); + const auto deposits = sumDeposits(input); + const uint64_t undeposits = sumUndeposits(input); + const auto _simplePlan = simplePlan(amount, requestedTokens, selectedInputs, input.transfer_message().use_max_amount(), deposits, undeposits, extraOutputs); Data encoded; Data txId; @@ -301,12 +424,12 @@ Amount txFeeFunction(uint64_t txSizeInBytes) { const double fixedTerm = 155381 + 500; const double linearTerm = 43.946 + 0.1; - const Amount fee = (Amount)(ceil(fixedTerm + (double)txSizeInBytes * linearTerm)); + const auto fee = (Amount)(ceil(fixedTerm + (double)txSizeInBytes * linearTerm)); return fee; } -Amount Signer::estimateFee(const Proto::SigningInput& input, Amount amount, const TokenBundle& requestedTokens, const vector selectedInputs) { - return txFeeFunction(estimateTxSize(input, amount, requestedTokens, selectedInputs)); +Amount Signer::estimateFee(const Proto::SigningInput& input, Amount amount, const TokenBundle& requestedTokens, const std::vector& selectedInputs, const std::vector& extraOutputs) { + return txFeeFunction(estimateTxSize(input, amount, requestedTokens, selectedInputs, extraOutputs)); } TransactionPlan Signer::doPlan() const { @@ -318,7 +441,7 @@ TransactionPlan Signer::doPlan() const { return plan; } // Check input UTXOs, process, sum ADA and token amounts - auto utxos = vector(); + auto utxos = std::vector(); uint64_t inputSum = 0; for (auto i = 0; i < input.utxos_size(); ++i) { const auto& utxo = input.utxos(i); @@ -330,44 +453,58 @@ TransactionPlan Signer::doPlan() const { return plan; } assert(inputSum > 0); + // adjust inputSum with deposited/undeposited amount + plan.deposit = sumDeposits(input); + plan.undeposit = sumUndeposits(input); + const auto inputSumAfterDeposit = inputSum + plan.undeposit - plan.deposit; // Amounts requested plan.amount = input.transfer_message().amount(); + + uint64_t extraAmountSum = 0; + auto extraOutputs = std::vector(); + for (auto& output: input.extra_outputs()) { + const auto extraToAddress = AddressV3(output.address()); + extraOutputs.emplace_back(extraToAddress.data(), output.amount()); + extraAmountSum = extraAmountSum + output.amount(); + } + plan.extraOutputs = extraOutputs; + TokenBundle requestedTokens; for (auto i = 0; i < input.transfer_message().token_amount().token_size(); ++i) { const auto token = TokenAmount::fromProto(input.transfer_message().token_amount().token(i)); requestedTokens.add(token); } - assert(plan.amount > 0 || maxAmount); + assert(plan.amount > 0 || maxAmount || input.transfer_message().token_amount().token_size() > 0); if (requestedTokens.size() > 1) { - // We support transfer of only one coin (for simplicity; inputs may contain more coints which are preserved) + // We support transfer of only one coin (for simplicity; inputs may contain more coins which are preserved) plan.error = Common::Proto::Error_invalid_requested_token_amount; return plan; } - // if amount requested is the same or more than available amount, it cannot be satisifed, but + // if amount requested is the same or more than available amount, it cannot be satisfied, but // treat this case as MaxAmount, and send maximum available (which will be less) - if (!maxAmount && input.transfer_message().amount() >= inputSum) { + if (!maxAmount && input.transfer_message().amount() >= inputSumAfterDeposit) { maxAmount = true; } // select UTXOs if (!maxAmount) { // aim for larger total input, enough for 4/3 of the target amount plus typical fee plus minimal ADA for change plus some extra - auto targetInputAmount = (plan.amount * 4) / 3 + PlaceholderFee + requestedTokens.minAdaAmount() + ExtraInputAmount; + auto targetInputAmount = (plan.amount * 4) / 3 + plan.deposit - plan.undeposit + PlaceholderFee + requestedTokens.minAdaAmount() + ExtraInputAmount; plan.utxos = selectInputsWithTokens(utxos, targetInputAmount, requestedTokens); } else { // maxAmount, select all plan.utxos = utxos; } - assert(plan.utxos.size() > 0); + assert(!plan.utxos.empty()); // Sum availableAmount plan.availableAmount = 0; - for (auto& u: plan.utxos) { + for (auto& u : plan.utxos) { plan.availableAmount += u.amount; - for (auto iter = u.tokenBundle.bundle.begin(); iter != u.tokenBundle.bundle.end(); ++iter) { - plan.availableTokens.add(iter->second); + for (auto && [_, curAmount] : u.tokenBundle.bundle) { + plan.availableTokens.add(curAmount); } } if (plan.availableAmount == 0) { @@ -375,16 +512,18 @@ TransactionPlan Signer::doPlan() const { return plan; } assert(plan.availableAmount > 0); + // adjust availableAmount with deposited/undeposited amount + const auto availableAmountAfterDeposit = plan.availableAmount + plan.undeposit - plan.deposit; // check that there are enough coins in the inputs - if (plan.amount > plan.availableAmount) { + if (plan.amount > availableAmountAfterDeposit) { plan.error = Common::Proto::Error_low_balance; return plan; } - assert(plan.amount <= plan.availableAmount); + assert(plan.amount <= availableAmountAfterDeposit); // check that there are enough tokens in the inputs - for (auto iter = requestedTokens.bundle.begin(); iter != requestedTokens.bundle.end(); ++iter) { - if (iter->second.amount > plan.availableTokens.getAmount(iter->second.key())) { + for (auto && [_, curAmount] : requestedTokens.bundle) { + if (curAmount.amount > plan.availableTokens.getAmount(curAmount.key())) { plan.error = Common::Proto::Error_low_balance; return plan; } @@ -392,28 +531,28 @@ TransactionPlan Signer::doPlan() const { // compute fee if (input.transfer_message().force_fee() == 0) { - plan.fee = estimateFee(input, plan.amount, requestedTokens, plan.utxos); + plan.fee = estimateFee(input, plan.amount, requestedTokens, plan.utxos, plan.extraOutputs); } else { // fee provided, use it (capped) - plan.fee = max(Amount(0), min(plan.availableAmount - plan.amount, input.transfer_message().force_fee())); + plan.fee = std::max(Amount(0), std::min(availableAmountAfterDeposit - plan.amount - extraAmountSum, input.transfer_message().force_fee())); } - assert(plan.fee >= 0 && plan.fee < plan.availableAmount); + assert(plan.fee >= 0 && plan.fee < availableAmountAfterDeposit); // adjust/compute output amount if (!maxAmount) { // reduce amount if needed - plan.amount = max(Amount(0), min(plan.amount, plan.availableAmount - plan.fee)); + plan.amount = std::max(Amount(0), std::min(plan.amount, availableAmountAfterDeposit - plan.fee - extraAmountSum)); } else { // max available amount - plan.amount = max(Amount(0), plan.availableAmount - plan.fee); + plan.amount = std::max(Amount(0), availableAmountAfterDeposit - plan.fee - extraAmountSum); } - assert(plan.amount >= 0 && plan.amount <= plan.availableAmount); + assert(plan.amount >= 0 && plan.amount <= availableAmountAfterDeposit); - if (plan.amount + plan.fee > plan.availableAmount) { + if (plan.amount + extraAmountSum + plan.fee > availableAmountAfterDeposit) { plan.error = Common::Proto::Error_low_balance; return plan; } - assert(plan.amount + plan.fee <= plan.availableAmount); + assert(plan.amount + extraAmountSum + plan.fee <= availableAmountAfterDeposit); // compute output token amounts if (!maxAmount) { @@ -423,7 +562,7 @@ TransactionPlan Signer::doPlan() const { } // compute change - plan.change = plan.availableAmount - (plan.amount + plan.fee); + plan.change = availableAmountAfterDeposit - (plan.amount + extraAmountSum + plan.fee); for (auto iter = plan.availableTokens.bundle.begin(); iter != plan.availableTokens.bundle.end(); ++iter) { const auto key = iter->second.key(); const auto changeAmount = iter->second.amount - plan.outputTokens.getAmount(key); @@ -433,9 +572,51 @@ TransactionPlan Signer::doPlan() const { } } - assert(plan.change >= 0 && plan.change <= plan.availableAmount); + assert(plan.change >= 0 && plan.change <= availableAmountAfterDeposit); assert(!maxAmount || plan.change == 0); // change is 0 in max amount case - assert(plan.amount + plan.change + plan.fee == plan.availableAmount); + assert(plan.amount + extraAmountSum+ plan.change + plan.fee == availableAmountAfterDeposit); + assert(plan.amount + extraAmountSum+ plan.change + plan.fee + plan.deposit == plan.availableAmount + plan.undeposit); return plan; } + + +Data Signer::encodeTransactionWithSig(const Proto::SigningInput &input, const PublicKey &publicKey, const Data &signature) { + Transaction txAux; + auto buildRet = buildTx(txAux, input); + if (buildRet != Common::Proto::OK) { + throw Common::Proto::SigningError(buildRet); + } + + std::vector> signatures; + signatures.emplace_back(publicKey.bytes, signature); + + bool hasLegacyUtxos = false; + for (const auto& utxo : input.utxos()) { + if (AddressV2::isValid(utxo.address())) { + hasLegacyUtxos = true; + break; + } + } + + const auto sigsCbor = cborizeSignatures(signatures, hasLegacyUtxos); + + // Cbor-encode txAux & signatures + const auto cbor = Cbor::Encode::array({ + // txaux + Cbor::Encode::fromRaw(txAux.encode()), + // signatures + sigsCbor, + // aux data + Cbor::Encode::null(), + }); + + return cbor.encoded(); +} + +Common::Proto::SigningError Signer::buildTx(Transaction& tx, const Proto::SigningInput& input) { + auto plan = Signer(input).doPlan(); + return buildTransactionAux(tx, input, plan); +} + +} // namespace TW::Cardano diff --git a/src/Cardano/Signer.h b/src/Cardano/Signer.h index 2daae8b5caa..d6036e8d2d8 100644 --- a/src/Cardano/Signer.h +++ b/src/Cardano/Signer.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,6 +10,7 @@ #include #include +#include namespace TW::Cardano { @@ -27,11 +26,11 @@ class Signer { Proto::SigningInput input; TransactionPlan _plan; - Signer(const Proto::SigningInput& input): input(input) {} + explicit Signer(Proto::SigningInput input): input(std::move(input)) {} Proto::SigningOutput sign(); // Sign using existing plan - Proto::SigningOutput signWithPlan(); + Proto::SigningOutput signWithPlan() const; // Create plan from signing input TransactionPlan doPlan() const; /// Returns a transaction plan (utxo selection, fee estimation) @@ -42,9 +41,11 @@ class Signer { } // Build encoded transaction static Common::Proto::SigningError encodeTransaction(Data& encoded, Data& txId, const Proto::SigningInput& input, const TransactionPlan& plan, bool sizeEstimationOnly = false); + static Data encodeTransactionWithSig(const Proto::SigningInput &input, const PublicKey &publicKey, const Data &signature); // Build aux transaction object, using input and plan static Common::Proto::SigningError buildTransactionAux(Transaction& tx, const Proto::SigningInput& input, const TransactionPlan& plan); - static Amount estimateFee(const Proto::SigningInput& input, Amount amount, const TokenBundle& requestedTokens, const std::vector selectedInputs); + static Common::Proto::SigningError buildTx(Transaction& tx, const Proto::SigningInput& input); + static Amount estimateFee(const Proto::SigningInput& input, Amount amount, const TokenBundle& requestedTokens, const std::vector& selectedInputs, const std::vector& extraOutputs); static std::vector selectInputsWithTokens(const std::vector& inputs, Amount amount, const TokenBundle& requestedTokens); // Build list of public keys + signature static Common::Proto::SigningError assembleSignatures(std::vector>& signatures, const Proto::SigningInput& input, const TransactionPlan& plan, const Data& txId, bool sizeEstimationOnly = false); diff --git a/src/Cardano/Transaction.cpp b/src/Cardano/Transaction.cpp index bfc3d21c2f8..fa8fd157d53 100644 --- a/src/Cardano/Transaction.cpp +++ b/src/Cardano/Transaction.cpp @@ -1,48 +1,51 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Transaction.h" +#include "AddressV3.h" #include "Cbor.h" #include "Hash.h" #include "HexCoding.h" +#include "Numeric.h" -using namespace TW::Cardano; -using namespace TW; -using namespace std; - +namespace TW::Cardano { TokenAmount TokenAmount::fromProto(const Proto::TokenAmount& proto) { - auto ret = TokenAmount(); - ret.policyId = proto.policy_id(); - ret.assetName = proto.asset_name(); - ret.amount = load(proto.amount()); - return ret; + std::string assetName; + if (!proto.asset_name().empty()) { + assetName = proto.asset_name(); + } else if (!proto.asset_name_hex().empty()) { + auto assetNameData = parse_hex(proto.asset_name_hex()); + assetName.assign(assetNameData.data(), assetNameData.data() + assetNameData.size()); + } + + return {proto.policy_id(), std::move(assetName), load(proto.amount())}; } Proto::TokenAmount TokenAmount::toProto() const { + auto assetNameHex = hex(assetName); + Proto::TokenAmount tokenAmount; tokenAmount.set_policy_id(policyId.data(), policyId.size()); tokenAmount.set_asset_name(assetName.data(), assetName.size()); + tokenAmount.set_asset_name_hex(assetNameHex.data(), assetNameHex.size()); const auto amountData = store(amount); tokenAmount.set_amount(amountData.data(), amountData.size()); return tokenAmount; } TokenBundle TokenBundle::fromProto(const Proto::TokenBundle& proto) { - auto ret = TokenBundle(); - for (auto i = 0; i < proto.token_size(); ++i) { - ret.add(TokenAmount::fromProto(proto.token(i))); - } + TokenBundle ret; + const auto addFunctor = [&ret](auto&& cur) { ret.add(TokenAmount::fromProto(cur)); }; + std::for_each(std::cbegin(proto.token()), std::cend(proto.token()), addFunctor); return ret; } Proto::TokenBundle TokenBundle::toProto() const { Proto::TokenBundle proto; - for (const auto& t: bundle) { + for (const auto& t : bundle) { *(proto.add_token()) = t.second.toProto(); } return proto; @@ -50,24 +53,37 @@ Proto::TokenBundle TokenBundle::toProto() const { void TokenBundle::add(const TokenAmount& ta) { const auto key = ta.key(); - if (bundle.find(key) == bundle.end()) { - bundle[key] = ta; - } else { - auto entry = bundle[key]; - entry.amount += ta.amount; - bundle[key] = entry; + if (auto&& [it, inserted] = bundle.try_emplace(key, ta); !inserted) { + it->second.amount += ta.amount; } } uint256_t TokenBundle::getAmount(const std::string& key) const { const auto& findkey = bundle.find(key); - if (findkey == bundle.end()) { - return 0; + return findkey == bundle.end() ? 0 : findkey->second.amount; +} + +std::unordered_set TokenBundle::getPolicyIds() const { + std::unordered_set policyIds; + std::transform(bundle.cbegin(), bundle.cend(), + std::inserter(policyIds, policyIds.begin()), + [](auto&& cur) { return cur.second.policyId; }); + return policyIds; +} + +std::vector TokenBundle::getByPolicyId(const std::string& policyId) const { + std::vector filtered; + for (auto&& t : bundle) { + if (t.second.policyId == policyId) { + filtered.emplace_back(t.second); + } } - return findkey->second.amount; + return filtered; } -uint64_t roundupBytesToWords(uint64_t b) { return ((b + 7) / 8); } +uint64_t roundupBytesToWords(uint64_t b) { + return ((b + 7) / 8); +} const uint64_t TokenBundle::MinUtxoValue = 1000000; @@ -82,7 +98,7 @@ uint64_t TokenBundle::minAdaAmountHelper(uint64_t numPids, uint64_t numAssets, u static const uint64_t pidSize = 28; uint64_t sizeB = 6 + roundupBytesToWords((numAssets * 12) + sumAssetNameLengths + (numPids * pidSize)); - return max(MinUtxoValue, (MinUtxoValue / adaOnlyUTxOSize) * (utxoEntrySizeWithoutVal + sizeB)); + return std::max(MinUtxoValue, (MinUtxoValue / adaOnlyUTxOSize) * (utxoEntrySizeWithoutVal + sizeB)); } uint64_t TokenBundle::minAdaAmount() const { @@ -91,20 +107,20 @@ uint64_t TokenBundle::minAdaAmount() const { return MinUtxoValue; } - unordered_set policyIdRegistry; - unordered_set assetNameRegistry; - uint64_t numPids = 0; - uint64_t numAssets = 0; + std::unordered_set policyIdRegistry; + std::unordered_set assetNameRegistry; uint64_t sumAssetNameLengths = 0; - for (const auto& t: bundle) { + for (const auto& t : bundle) { policyIdRegistry.emplace(t.second.policyId); if (t.second.assetName.length() > 0) { assetNameRegistry.emplace(t.second.assetName); } } - numPids = uint64_t(policyIdRegistry.size()); - numAssets = uint64_t(assetNameRegistry.size()); - for_each(assetNameRegistry.begin(), assetNameRegistry.end(), [&sumAssetNameLengths](string a){ sumAssetNameLengths += a.length(); }); + + auto numPids = uint64_t(policyIdRegistry.size()); + auto numAssets = uint64_t(assetNameRegistry.size()); + for_each(assetNameRegistry.begin(), assetNameRegistry.end(), [&sumAssetNameLengths](auto&& a){ sumAssetNameLengths += a.length(); }); + return minAdaAmountHelper(numPids, numAssets, sumAssetNameLengths); } @@ -127,13 +143,37 @@ Proto::TxInput TxInput::toProto() const { txInput.mutable_out_point()->set_output_index(outputIndex); txInput.set_address(address.data(), address.size()); txInput.set_amount(amount); - for (auto iter = tokenBundle.bundle.begin(); iter != tokenBundle.bundle.end(); ++iter) { - *txInput.add_token_amount() = iter->second.toProto(); + for (const auto& token : tokenBundle.bundle) { + *txInput.add_token_amount() = token.second.toProto(); } return txInput; } -bool TW::Cardano::operator==(const TxInput& i1, const TxInput& i2) { return i1.outputIndex == i2.outputIndex && i1.txHash == i2.txHash; } +TxOutput TxOutput::fromProto(const Cardano::Proto::TxOutput& proto) { + auto ret = TxOutput(); + ret.address = data(proto.address()); + ret.amount = proto.amount(); + for (auto i = 0; i < proto.token_amount_size(); ++i) { + auto ta = TokenAmount::fromProto(proto.token_amount(i)); + ret.tokenBundle.add(ta); + } + return ret; +} + +Proto::TxOutput TxOutput::toProto() const { + Proto::TxOutput txOutput; + const auto toAddress = AddressV3(address); + txOutput.set_address(toAddress.string()); + txOutput.set_amount(amount); + for (const auto& token : tokenBundle.bundle) { + *txOutput.add_token_amount() = token.second.toProto(); + } + return txOutput; +} + +bool operator==(const TxInput& i1, const TxInput& i2) { + return i1.outputIndex == i2.outputIndex && i1.txHash == i2.txHash; +} TransactionPlan TransactionPlan::fromProto(const Proto::TransactionPlan& proto) { auto ret = TransactionPlan(); @@ -141,6 +181,8 @@ TransactionPlan TransactionPlan::fromProto(const Proto::TransactionPlan& proto) ret.amount = proto.amount(); ret.fee = proto.fee(); ret.change = proto.change(); + ret.deposit = proto.deposit(); + ret.undeposit = proto.undeposit(); for (auto i = 0; i < proto.available_tokens_size(); ++i) { ret.availableTokens.add(TokenAmount::fromProto(proto.available_tokens(i))); } @@ -151,7 +193,10 @@ TransactionPlan TransactionPlan::fromProto(const Proto::TransactionPlan& proto) ret.changeTokens.add(TokenAmount::fromProto(proto.change_tokens(i))); } for (auto i = 0; i < proto.utxos_size(); ++i) { - ret.utxos.push_back(TxInput::fromProto(proto.utxos(i))); + ret.utxos.emplace_back(TxInput::fromProto(proto.utxos(i))); + } + for (auto i = 0; i < proto.extra_outputs_size(); ++i) { + ret.extraOutputs.emplace_back(TxOutput::fromProto(proto.extra_outputs(i))); } ret.error = proto.error(); return ret; @@ -163,30 +208,37 @@ Proto::TransactionPlan TransactionPlan::toProto() const { plan.set_amount(amount); plan.set_fee(fee); plan.set_change(change); - for (const auto& t: availableTokens.bundle) { - *plan.add_available_tokens() = t.second.toProto(); + plan.set_deposit(deposit); + plan.set_undeposit(undeposit); + for (const auto& token : availableTokens.bundle) { + *plan.add_available_tokens() = token.second.toProto(); } - for (const auto& t: outputTokens.bundle) { - *plan.add_output_tokens() = t.second.toProto(); + for (const auto& token : outputTokens.bundle) { + *plan.add_output_tokens() = token.second.toProto(); } - for (const auto& t: changeTokens.bundle) { - *plan.add_change_tokens() = t.second.toProto(); + for (const auto& token : changeTokens.bundle) { + *plan.add_change_tokens() = token.second.toProto(); } - for (const auto& u: utxos) { + for (const auto& u : utxos) { *plan.add_utxos() = u.toProto(); } + for (const auto& u : extraOutputs) { + *plan.add_extra_outputs() = u.toProto(); + } plan.set_error(error); return plan; } Cbor::Encode cborizeInputs(const std::vector& inputs) { + // clang-format off std::vector ii; - for (const auto& i: inputs) { - ii.push_back(Cbor::Encode::array({ + for (const auto& i : inputs) { + ii.emplace_back(Cbor::Encode::array({ Cbor::Encode::bytes(i.txHash), Cbor::Encode::uint(i.outputIndex) })); } + // clang-format on return Cbor::Encode::array(ii); } @@ -196,56 +248,138 @@ Cbor::Encode cborizeOutputAmounts(const Amount& amount, const TokenBundle& token return Cbor::Encode::uint(amount); } // native and token amounts - std::vector> tokensMap; - for (auto iter = tokenBundle.bundle.begin(); iter != tokenBundle.bundle.end(); ++iter) { - tokensMap.push_back(make_pair( - Cbor::Encode::bytes(parse_hex(iter->second.policyId)), - Cbor::Encode::map({make_pair( - Cbor::Encode::bytes(data(iter->second.assetName)), - Cbor::Encode::uint(uint64_t(iter->second.amount)) // 64 bits - )}) - )); + // tokens: organized in two levels: by policyId and by assetName + const auto policyIds = tokenBundle.getPolicyIds(); + std::map tokensMap; + for (const auto& policy : policyIds) { + const auto& subTokens = tokenBundle.getByPolicyId(policy); + std::map subTokensMap; + for (const auto& token : subTokens) { + subTokensMap.emplace( + Cbor::Encode::bytes(data(token.assetName)), + Cbor::Encode::uint(uint64_t(token.amount)) // 64 bits + ); + } + tokensMap.emplace( + Cbor::Encode::bytes(parse_hex(policy)), + Cbor::Encode::map(subTokensMap)); } + // clang-format off return Cbor::Encode::array({ Cbor::Encode::uint(amount), Cbor::Encode::map(tokensMap) }); + // clang-format on } Cbor::Encode cborizeOutput(const TxOutput& output) { + // clang-format off return Cbor::Encode::array({ Cbor::Encode::bytes(output.address), cborizeOutputAmounts(output.amount, output.tokenBundle) }); + // clang-format on } Cbor::Encode cborizeOutputs(const std::vector& outputs) { std::vector oo; - for (const auto& o: outputs) { - oo.push_back(cborizeOutput(o)); + for (const auto& o : outputs) { + oo.emplace_back(cborizeOutput(o)); } return Cbor::Encode::array(oo); } +Cbor::Encode cborizeCertificateKey(const CertificateKey& certKey) { + std::vector c; + c.emplace_back(Cbor::Encode::uint(static_cast(certKey.type))); + c.emplace_back(Cbor::Encode::bytes(certKey.key)); + return Cbor::Encode::array(c); +} + +Cbor::Encode cborizeCert(const Certificate& cert) { + std::vector c; + c.emplace_back(Cbor::Encode::uint(static_cast(cert.type))); + c.emplace_back(cborizeCertificateKey(cert.certKey)); + if (!cert.poolId.empty()) { + c.emplace_back(Cbor::Encode::bytes(cert.poolId)); + } + return Cbor::Encode::array(c); +} + +Cbor::Encode cborizeCerts(const std::vector& certs) { + std::vector c; + for (const auto& i : certs) { + c.emplace_back(cborizeCert(i)); + } + return Cbor::Encode::array(c); +} + +Cbor::Encode cborizeWithdrawals(const std::vector& withdrawals) { + std::map mapElems; + for (const auto& w : withdrawals) { + mapElems.emplace(Cbor::Encode::bytes(w.stakingKey), Cbor::Encode::uint(w.amount)); + } + return Cbor::Encode::map(mapElems); +} + Data Transaction::encode() const { const auto ii = cborizeInputs(inputs); const auto oo = cborizeOutputs(outputs); // Encode elements in a map, with fixed numbers as keys - Cbor::Encode encode = Cbor::Encode::map({ - make_pair(Cbor::Encode::uint(0), ii), - make_pair(Cbor::Encode::uint(1), oo), - make_pair(Cbor::Encode::uint(2), Cbor::Encode::uint(fee)), - make_pair(Cbor::Encode::uint(3), Cbor::Encode::uint(ttl)), - }); - // Note: following fields are not included: - // 4 certificates, 5 withdrawals, 7 AUXILIARY_DATA_HASH, 8 VALIDITY_INTERVAL_START + std::map mapElems = { + std::make_pair(Cbor::Encode::uint(0), ii), + std::make_pair(Cbor::Encode::uint(1), oo), + std::make_pair(Cbor::Encode::uint(2), Cbor::Encode::uint(fee)), + std::make_pair(Cbor::Encode::uint(3), Cbor::Encode::uint(ttl)), + }; + + if (!certificates.empty()) { + mapElems.emplace(Cbor::Encode::uint(4), cborizeCerts(certificates)); + } + if (!withdrawals.empty()) { + mapElems.emplace(Cbor::Encode::uint(5), cborizeWithdrawals(withdrawals)); + } + Cbor::Encode encode = Cbor::Encode::map(mapElems); return encode.encoded(); + + // Note: following fields are not included: + // 7 AUXILIARY_DATA_HASH, 8 VALIDITY_INTERVAL_START } Data Transaction::getId() const { const auto encoded = encode(); - const auto hash = Hash::blake2b(encoded, 32); + auto hash = Hash::blake2b(encoded, 32); return hash; } + +/// https://github.com/Emurgo/cardano-serialization-lib/blob/78184e0a2c207c2f8bba57b0d3c437f4c808c125/rust/src/utils.rs#L1415 +std::optional minAdaAmountHelper(const TxOutput& output, uint64_t coinsPerUtxoByte) noexcept { + const size_t outputSize = cborizeOutput(output).encoded().size(); + const auto outputSizeExtended = static_cast(outputSize + 160); + if (checkMulUnsignedOverflow(outputSizeExtended, coinsPerUtxoByte)) { + return std::nullopt; + } + return outputSizeExtended * coinsPerUtxoByte; +} + +/// https://github.com/Emurgo/cardano-serialization-lib/blob/78184e0a2c207c2f8bba57b0d3c437f4c808c125/rust/src/utils.rs#L1388 +std::optional TxOutput::minAdaAmount(uint64_t coinsPerUtxoByte) const noexcept { + // A copy of `this`. + TxOutput output(address, amount, tokenBundle); + + while (true) { + const auto minAmount = minAdaAmountHelper(output, coinsPerUtxoByte); + if (!minAmount) { + return std::nullopt; + } + if (output.amount >= *minAmount) { + return minAmount; + } + // Set the amount to `minAmount` and re-try again. + output.amount = *minAmount; + } +} + +} // namespace TW::Cardano diff --git a/src/Cardano/Transaction.h b/src/Cardano/Transaction.h index 98e591c9cf6..afdd47b4cb4 100644 --- a/src/Cardano/Transaction.h +++ b/src/Cardano/Transaction.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -13,8 +11,12 @@ #include "../proto/Cardano.pb.h" #include "../proto/Common.pb.h" +#include +#include +#include #include #include +#include namespace TW::Cardano { @@ -27,10 +29,12 @@ class TokenAmount { uint256_t amount; TokenAmount() = default; - TokenAmount(const std::string& policyId, const std::string& assetName, uint256_t amount) : policyId(policyId), assetName(assetName), amount(amount) {} + TokenAmount(std::string policyId, std::string assetName, uint256_t amount) + : policyId(std::move(policyId)), assetName(std::move(assetName)), amount(std::move(amount)) {} static TokenAmount fromProto(const Proto::TokenAmount& proto); Proto::TokenAmount toProto() const; + /// Key used in TokenBundle std::string key() const { return policyId + "_" + assetName; } }; @@ -39,7 +43,11 @@ class TokenBundle { std::map bundle; TokenBundle() = default; - TokenBundle(const std::vector& tokens) { for (const auto& t: tokens) { add(t); } } + explicit TokenBundle(const std::vector& tokens) { + for (const auto& t : tokens) { + add(t); + } + } static TokenBundle fromProto(const Proto::TokenBundle& proto); Proto::TokenBundle toProto() const; @@ -47,6 +55,10 @@ class TokenBundle { void add(const TokenAmount& ta); uint256_t getAmount(const std::string& key) const; size_t size() const { return bundle.size(); } + /// Get the unique policyIds, can be the same number as the elements, or less (in case a policyId appears more than once, with different asset names). + std::unordered_set getPolicyIds() const; + /// Filter by policyIds + std::vector getByPolicyId(const std::string& policyId) const; // The minimum ADA amount needed for an ADA-only UTXO static const uint64_t MinUtxoValue; @@ -58,14 +70,14 @@ class TokenBundle { class OutPoint { public: Data txHash; - uint64_t outputIndex; + uint64_t outputIndex{}; OutPoint() = default; - OutPoint(const Data& txHash, uint64_t outputIndex) : txHash(txHash), outputIndex(outputIndex) {} - static OutPoint fromProto(const Proto::OutPoint& proto); + OutPoint(Data txHash, uint64_t outputIndex) + : txHash(std::move(txHash)), outputIndex(outputIndex) {} }; -class TxInput: public OutPoint { +class TxInput : public OutPoint { public: std::string address; @@ -86,38 +98,84 @@ class TxOutput { Data address; /// ADA amount - Amount amount; + Amount amount{}; /// Token amounts (optional) TokenBundle tokenBundle; + /// Returns minimal amount of ADA for the output or `std::nullopt` if there a problem happened. + std::optional minAdaAmount(uint64_t coinsPerUtxoByte) const noexcept; + TxOutput() = default; - TxOutput(const Data& address, Amount amount) : address(address), amount(amount) {} - TxOutput(const Data& address, Amount amount, const TokenBundle& tokenBundle) : address(address), amount(amount), tokenBundle(tokenBundle) {} + TxOutput(Data address, Amount amount) + : address(std::move(address)), amount(amount) {} + TxOutput(Data address, Amount amount, TokenBundle tokenBundle) + : address(std::move(address)), amount(amount), tokenBundle(std::move(tokenBundle)) {} + + static TxOutput fromProto(const Proto::TxOutput& proto); + Proto::TxOutput toProto() const; }; class TransactionPlan { public: std::vector utxos; - Amount availableAmount = 0; // total coins in the utxos - Amount amount = 0; // coins in the output UTXO - Amount fee = 0; // coin amount deducted as fee - Amount change = 0; // coins in the change UTXO - TokenBundle availableTokens; // total tokens in the utxos (optional) - TokenBundle outputTokens; // tokens in the output (optional) - TokenBundle changeTokens; // tokens in the change (optional) + std::vector extraOutputs; + Amount availableAmount = 0; // total coins in the input utxos + Amount amount = 0; // coins in the output UTXO + Amount fee = 0; // coin amount deducted as fee + Amount change = 0; // coins in the change UTXO + Amount deposit = 0; // coins deposited (going to deposit) in this TX + Amount undeposit = 0; // coins undeposited (returned from deposit) in this TX + TokenBundle availableTokens; // total tokens in the utxos (optional) + TokenBundle outputTokens; // tokens in the output (optional) + TokenBundle changeTokens; // tokens in the change (optional) Common::Proto::SigningError error = Common::Proto::SigningError::OK; static TransactionPlan fromProto(const Proto::TransactionPlan& proto); Proto::TransactionPlan toProto() const; }; +/// A key with a type, used in a Certificate +class CertificateKey { +public: + enum KeyType : uint8_t { + AddressKeyHash = 0, + // ScriptHash = 1, + }; + KeyType type; + Data key; +}; + +/// Certificate, mainly used for staking +class Certificate { +public: + enum CertificateType : uint8_t { + SkatingKeyRegistration = 0, + StakingKeyDeregistration = 1, + Delegation = 2, + // StakePoolRegistration = 3, // not supported + }; + CertificateType type; + CertificateKey certKey; + /// Optional PoolId, used in delegation + Data poolId; +}; + +/// Staking withdrawal +class Withdrawal { +public: + Data stakingKey; + Amount amount; +}; + class Transaction { public: std::vector inputs; std::vector outputs; Amount fee; uint64_t ttl; + std::vector certificates; + std::vector withdrawals; // Encode into CBOR binary format Data encode() const; diff --git a/src/Cbor.cpp b/src/Cbor.cpp index add7d0946d4..dd5f3ed06c2 100644 --- a/src/Cbor.cpp +++ b/src/Cbor.cpp @@ -1,11 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Cbor.h" #include "HexCoding.h" +#include "Numeric.h" #include #include @@ -19,7 +18,7 @@ TW::Data Encode::encoded() const { if (openIndefCount > 0) { throw invalid_argument("CBOR Unclosed indefinite length building"); } - return data; + return _data; } Encode Encode::uint(uint64_t value) { @@ -46,21 +45,21 @@ Encode Encode::array(const vector& elems) { Encode e; auto n = elems.size(); e.appendValue(Decode::MT_array, n); - for (int i = 0; i < n; ++i) { + for (auto i = 0ul; i < n; ++i) { e.append(elems[i].encoded()); } return e; } -Encode Encode::map(const vector>& elems) { - Encode e; +Encode Encode::map(const std::map& elems) { + Encode enc; auto n = elems.size(); - e.appendValue(Decode::MT_map, n); - for (int i = 0; i < n; ++i) { - e.append(elems[i].first.encoded()); - e.append(elems[i].second.encoded()); + enc.appendValue(Decode::MT_map, n); + for (const auto& e: elems) { + enc.append(e.first.encoded()); + enc.append(e.second.encoded()); } - return e; + return enc; } Encode Encode::tag(uint64_t value, const Encode& elem) { @@ -96,7 +95,7 @@ Encode Encode::closeIndefArray() { throw invalid_argument("CBOR Not inside indefinite-length array"); } // add closing break command - TW::append(data, 0xFF); + TW::append(_data, 0xFF); // close counter --openIndefCount; return *this; @@ -129,9 +128,9 @@ Encode Encode::appendValue(byte majorType, uint64_t value) { minorType = 27; } // add bytes - TW::append(data, (byte)((majorType << 5) | (minorType & 0x1F))); + TW::append(_data, (byte)((majorType << 5) | (minorType & 0x1F))); Data valBytes = Data(byteCount - 1); - for (int i = 0; i < valBytes.size(); ++i) { + for (auto i = 0ul; i < valBytes.size(); ++i) { valBytes[valBytes.size() - 1 - i] = (byte)(value & 0xFF); value = value >> 8; } @@ -141,7 +140,7 @@ Encode Encode::appendValue(byte majorType, uint64_t value) { void Encode::appendIndefinite(byte majorType) { byte minorType = 31; - TW::append(data, (byte)((majorType << 5) | (minorType & 0x1F))); + TW::append(_data, (byte)((majorType << 5) | (minorType & 0x1F))); } @@ -279,7 +278,7 @@ uint32_t Decode::getCompoundLength(uint32_t countMultiplier) const { uint32_t count = typeDesc.isIndefiniteValue ? 0 : (uint32_t)(typeDesc.value * countMultiplier); // process elements len += typeDesc.byteCount; - for (int i = 0; i < count || typeDesc.isIndefiniteValue; ++i) { + for (auto i = 0ul; i < count || typeDesc.isIndefiniteValue; ++i) { Decode nextElem = skipClone(len); if (typeDesc.isIndefiniteValue && nextElem.isBreak()) { // end of indefinite-length @@ -304,17 +303,20 @@ vector Decode::getCompoundElements(uint32_t countMultiplier, TW::byte ex uint32_t count = typeDesc.isIndefiniteValue ? 0 : (uint32_t)(typeDesc.value * countMultiplier); // process elements uint32_t idx = typeDesc.byteCount; - for (int i = 0; i < count || typeDesc.isIndefiniteValue; ++i) { + for (auto i = 0ul; i < count || typeDesc.isIndefiniteValue; ++i) { Decode nextElem = skipClone(idx); if (typeDesc.isIndefiniteValue && nextElem.isBreak()) { // end of indefinite-length break; } uint32_t elemLen = nextElem.getTotalLen(); + if (elemLen == 0 || checkAddUnsignedOverflow(idx, elemLen)) { + throw std::invalid_argument("CBOR invalid element length"); + } if (idx + elemLen > length()) { - throw std::invalid_argument("CBOR array data too short"); + throw std::invalid_argument("CBOR invalid array data"); } - elems.push_back(Decode(data, subStart + idx, elemLen)); + elems.emplace_back(Decode(data, subStart + idx, elemLen)); idx += elemLen; } return elems; @@ -323,7 +325,7 @@ vector Decode::getCompoundElements(uint32_t countMultiplier, TW::byte ex vector> Decode::getMapElements() const { auto elems = getCompoundElements(2, MT_map); vector> map; - for (int i = 0; i < elems.size(); i += 2) { + for (auto i = 0ul; i < elems.size(); i += 2) { map.emplace_back(make_pair(elems[i], elems[i + 1])); } return map; @@ -369,7 +371,7 @@ bool Decode::isValid() const { if (len > subLen) { return false; } auto count = typeDesc.isIndefiniteValue ? 0 : countMultiplier * typeDesc.value; uint32_t idx = typeDesc.byteCount; - for (int i = 0; i < count || typeDesc.isIndefiniteValue; ++i) + for (auto i = 0ul; i < count || typeDesc.isIndefiniteValue; ++i) { Decode nextElem = skipClone(idx); if (typeDesc.isIndefiniteValue && nextElem.isBreak()) { break; } @@ -416,7 +418,7 @@ string Decode::dumpToStringInternal() const { s << "["; } vector elems = getArrayElements(); - for (int i = 0; i < elems.size(); ++i) { + for (auto i = 0ul; i < elems.size(); ++i) { if (i > 0) s << ", "; s << elems[i].dumpToStringInternal(); } @@ -432,7 +434,7 @@ string Decode::dumpToStringInternal() const { s << "{"; } auto elems = getMapElements(); - for (int i = 0; i < elems.size(); ++i) { + for (auto i = 0ul; i < elems.size(); ++i) { if (i > 0) s << ", "; s << elems[i].first.dumpToStringInternal() << ": " << elems[i].second.dumpToStringInternal(); } diff --git a/src/Cbor.h b/src/Cbor.h index 5433b3e2ec2..43f245611bb 100644 --- a/src/Cbor.h +++ b/src/Cbor.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -11,6 +9,8 @@ #include #include #include +#include +#include namespace TW::Cbor { @@ -38,7 +38,7 @@ class Encode { /// encode an array of elements (of different types) static Encode array(const std::vector& elems); /// encode a map - static Encode map(const std::vector>& elems); + static Encode map(const std::map& elems); /// encode a tag and following element static Encode tag(uint64_t value, const Encode& elem); /// encode a null value (special) @@ -54,22 +54,28 @@ class Encode { /// Create from raw content, must be valid CBOR data, may throw static Encode fromRaw(const TW::Data& rawData); + const Data& getDataInternal() const { return _data; } private: Encode() {} - Encode(const TW::Data& rawData) : data(rawData) {} + Encode(const TW::Data& rawData) : _data(rawData) {} /// Append types + value, on variable number of bytes (1..8). Return object to support chain syntax. Encode appendValue(byte majorType, uint64_t value); - inline Encode append(const TW::Data& data) { TW::append(this->data, data); return *this; } + inline Encode append(const TW::Data& data) { TW::append(_data, data); return *this; } void appendIndefinite(byte majorType); private: /// Encoded data is stored here, always well-formed, but my be partial. - TW::Data data; + TW::Data _data; /// number of currently open indefinite buildingds (0, 1, or more for nested) int openIndefCount = 0; }; +/// Comparator, needed for map keys +inline bool operator<(const Encode& lhs, const Encode& rhs) { + return lhs.getDataInternal() < rhs.getDataInternal(); +} + /// CBOR Decoder and container for data for decoding. Contains reference to read-only CBOR data. /// See CborTests.cpp for usage. class Decode { diff --git a/src/Coin.cpp b/src/Coin.cpp index 8d2b7767ae2..6033666b9ef 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Coin.h" @@ -16,19 +14,23 @@ #include "Aeternity/Entry.h" #include "Aion/Entry.h" #include "Algorand/Entry.h" +#include "Aptos/Entry.h" #include "Binance/Entry.h" #include "Bitcoin/Entry.h" +#include "BitcoinDiamond/Entry.h" #include "Cardano/Entry.h" #include "Cosmos/Entry.h" #include "Decred/Entry.h" #include "EOS/Entry.h" -#include "Elrond/Entry.h" +#include "MultiversX/Entry.h" #include "Ethereum/Entry.h" +#include "Everscale/Entry.h" #include "FIO/Entry.h" #include "Filecoin/Entry.h" #include "Groestlcoin/Entry.h" #include "Harmony/Entry.h" #include "Icon/Entry.h" +#include "IOST/Entry.h" #include "IoTeX/Entry.h" #include "Kusama/Entry.h" #include "NEAR/Entry.h" @@ -36,11 +38,12 @@ #include "NULS/Entry.h" #include "Nano/Entry.h" #include "Nebulas/Entry.h" +#include "Nervos/Entry.h" #include "Nimiq/Entry.h" #include "Oasis/Entry.h" #include "Ontology/Entry.h" #include "Polkadot/Entry.h" -#include "Ripple/Entry.h" +#include "XRP/Entry.h" #include "Ronin/Entry.h" #include "Solana/Entry.h" #include "Stellar/Entry.h" @@ -49,9 +52,20 @@ #include "Theta/Entry.h" #include "Tron/Entry.h" #include "VeChain/Entry.h" +#include "Verge/Entry.h" #include "Waves/Entry.h" +#include "XRP/Entry.h" #include "Zcash/Entry.h" #include "Zilliqa/Entry.h" +#include "Zen/Entry.h" +#include "Everscale/Entry.h" +#include "Hedera/Entry.h" +#include "TheOpenNetwork/Entry.h" +#include "Sui/Entry.h" +#include "Greenfield/Entry.h" +#include "InternetComputer/Entry.h" +#include "NativeEvmos/Entry.h" +#include "NativeInjective/Entry.h" // end_of_coin_includes_marker_do_not_modify using namespace TW; @@ -61,11 +75,12 @@ using namespace std; Aeternity::Entry aeternityDP; Aion::Entry aionDP; Algorand::Entry algorandDP; +Aptos::Entry AptosDP; Binance::Entry binanceDP; Bitcoin::Entry bitcoinDP; Cardano::Entry cardanoDP; Cosmos::Entry cosmosDP; -Elrond::Entry elrondDP; +MultiversX::Entry multiversxDP; EOS::Entry eosDP; Ethereum::Entry ethereumDP; Decred::Entry decredDP; @@ -74,6 +89,7 @@ FIO::Entry fioDP; Groestlcoin::Entry groestlcoinDP; Harmony::Entry harmonyDP; Icon::Entry iconDP; +IOST::Entry iostDP; IoTeX::Entry iotexDP; Kusama::Entry kusamaDP; Nano::Entry nanoDP; @@ -94,9 +110,21 @@ Theta::Entry thetaDP; THORChain::Entry thorchainDP; Tron::Entry tronDP; VeChain::Entry vechainDP; +Verge::Entry vergeDP; Waves::Entry wavesDP; Zcash::Entry zcashDP; Zilliqa::Entry zilliqaDP; +BitcoinDiamond::Entry bcdDP; +Zen::Entry zenDP; +Nervos::Entry NervosDP; +Everscale::Entry EverscaleDP; +Hedera::Entry HederaDP; +TheOpenNetwork::Entry tonDP; +Sui::Entry SuiDP; +Greenfield::Entry GreenfieldDP; +InternetComputer::Entry InternetComputerDP; +NativeEvmos::Entry NativeEvmosDP; +NativeInjective::Entry NativeInjectiveDP; // end_of_coin_dipatcher_declarations_marker_do_not_modify CoinEntry* coinDispatcher(TWCoinType coinType) { @@ -106,6 +134,7 @@ CoinEntry* coinDispatcher(TWCoinType coinType) { switch (blockchain) { // #coin-list# case TWBlockchainBitcoin: entry = &bitcoinDP; break; + case TWBlockchainBitcoinDiamond: entry = &bcdDP; break; case TWBlockchainEthereum: entry = ðereumDP; break; case TWBlockchainVechain: entry = &vechainDP; break; case TWBlockchainTron: entry = &tronDP; break; @@ -136,14 +165,27 @@ CoinEntry* coinDispatcher(TWCoinType coinType) { case TWBlockchainCardano: entry = &cardanoDP; break; case TWBlockchainNEO: entry = &neoDP; break; case TWBlockchainFilecoin: entry = &filecoinDP; break; - case TWBlockchainElrondNetwork: entry = &elrondDP; break; + case TWBlockchainMultiversX: entry = &multiversxDP; break; case TWBlockchainOasisNetwork: entry = &oasisDP; break; case TWBlockchainDecred: entry = &decredDP; break; case TWBlockchainGroestlcoin: entry = &groestlcoinDP; break; case TWBlockchainZcash: entry = &zcashDP; break; + case TWBlockchainZen: entry = &zenDP; break; + case TWBlockchainVerge: entry = &vergeDP; break; + case TWBlockchainIOST: entry = &iostDP; break; case TWBlockchainThorchain: entry = &thorchainDP; break; case TWBlockchainRonin: entry = &roninDP; break; case TWBlockchainKusama: entry = &kusamaDP; break; + case TWBlockchainNervos: entry = &NervosDP; break; + case TWBlockchainEverscale: entry = &EverscaleDP; break; + case TWBlockchainAptos: entry = &AptosDP; break; + case TWBlockchainHedera: entry = &HederaDP; break; + case TWBlockchainTheOpenNetwork: entry = &tonDP; break; + case TWBlockchainSui: entry = &SuiDP; break; + case TWBlockchainGreenfield: entry = &GreenfieldDP; break; + case TWBlockchainInternetComputer: entry = &InternetComputerDP; break; + case TWBlockchainNativeEvmos: entry = &NativeEvmosDP; break; + case TWBlockchainNativeInjective: entry = &NativeInjectiveDP; break; // end_of_coin_dipatcher_switch_marker_do_not_modify default: entry = nullptr; break; @@ -152,39 +194,83 @@ CoinEntry* coinDispatcher(TWCoinType coinType) { return entry; } -const Derivation CoinInfo::derivationByName(TWDerivation name) const { - if (name == TWDerivationDefault && derivation.size() > 0) { +const Derivation CoinInfo::derivationByName(TWDerivation nameIn) const { + if (nameIn == TWDerivationDefault && derivation.size() > 0) { return derivation[0]; } - for (auto deriv: derivation) { - if (deriv.name == name) { + for (auto deriv : derivation) { + if (deriv.name == nameIn) { return deriv; } } return Derivation(); } +bool TW::validateAddress(TWCoinType coin, const string& address, const PrefixVariant& prefix) { + // dispatch + auto* dispatcher = coinDispatcher(coin); + assert(dispatcher != nullptr); + try { + return dispatcher->validateAddress(coin, address, prefix); + } catch (...) { + return false; + } +} + bool TW::validateAddress(TWCoinType coin, const std::string& string) { + const auto* hrp = stringForHRP(TW::hrp(coin)); auto p2pkh = TW::p2pkhPrefix(coin); auto p2sh = TW::p2shPrefix(coin); - const auto* hrp = stringForHRP(TW::hrp(coin)); // dispatch auto* dispatcher = coinDispatcher(coin); assert(dispatcher != nullptr); - return dispatcher->validateAddress(coin, string, p2pkh, p2sh, hrp); + + try { + bool isValid = false; + // First check HRP. + if (hrp != nullptr && !std::string(hrp).empty()) { + isValid = dispatcher->validateAddress(coin, string, Bech32Prefix(hrp)); + } + // Then check UTXO + if ((p2pkh != 0 || p2sh != 0) && !isValid) { + return isValid || dispatcher->validateAddress(coin, string, Base58Prefix{.p2pkh = p2pkh, .p2sh = p2sh}); + } + // Then check normal + if (!isValid) { + isValid = dispatcher->validateAddress(coin, string, std::monostate()); + } + return isValid; + } catch (...) { + return false; + } } -std::string TW::normalizeAddress(TWCoinType coin, const std::string& address) { +namespace TW::internal { + inline std::string normalizeAddress(TWCoinType coin, const string& address) { + // dispatch + auto* dispatcher = coinDispatcher(coin); + assert(dispatcher != nullptr); + return dispatcher->normalizeAddress(coin, address); + } +} // namespace TW::internal + +std::string TW::normalizeAddress(TWCoinType coin, const string& address) {; if (!TW::validateAddress(coin, address)) { // invalid address, not normalizing return ""; } - // dispatch - auto* dispatcher = coinDispatcher(coin); - assert(dispatcher != nullptr); - return dispatcher->normalizeAddress(coin, address); + return internal::normalizeAddress(coin, address); +} + +std::string TW::normalizeAddress(TWCoinType coin, const std::string& address, const PrefixVariant& prefix) { + if (!TW::validateAddress(coin, address, prefix)) { + // invalid address, not normalizing + return ""; + } + + return internal::normalizeAddress(coin, address); } std::string TW::deriveAddress(TWCoinType coin, const PrivateKey& privateKey) { @@ -196,18 +282,10 @@ std::string TW::deriveAddress(TWCoinType coin, const PrivateKey& privateKey, TWD return TW::deriveAddress(coin, privateKey.getPublicKey(keyType), derivation); } -std::string TW::deriveAddress(TWCoinType coin, const PublicKey& publicKey) { - return deriveAddress(coin, publicKey, TWDerivationDefault); -} - -std::string TW::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation) { - auto p2pkh = TW::p2pkhPrefix(coin); - const auto* hrp = stringForHRP(TW::hrp(coin)); - - // dispatch - auto* dispatcher = coinDispatcher(coin); +std::string TW::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) { + auto const* dispatcher = coinDispatcher(coin); assert(dispatcher != nullptr); - return dispatcher->deriveAddress(coin, derivation, publicKey, p2pkh, hrp); + return dispatcher->deriveAddress(coin, publicKey, derivation, addressPrefix); } Data TW::addressToData(TWCoinType coin, const std::string& address) { @@ -252,12 +330,6 @@ void TW::anyCoinCompileWithSignatures(TWCoinType coinType, const Data& txInputDa dispatcher->compile(coinType, txInputData, signatures, publicKeys, txOutputOut); } -Data TW::anyCoinBuildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) { - auto* dispatcher = coinDispatcher(coinType); - assert(dispatcher != nullptr); - return dispatcher->buildTransactionInput(coinType, from, to, amount, asset, memo, chainId); -} - // Coin info accessors extern const CoinInfo getCoinInfo(TWCoinType coin); // in generated CoinInfoData.cpp file @@ -342,6 +414,10 @@ uint32_t TW::slip44Id(TWCoinType coin) { return getCoinInfo(coin).slip44; } +std::uint32_t TW::ss58Prefix(TWCoinType coin) { + return getCoinInfo(coin).ss58Prefix; +} + TWString* _Nullable TWCoinTypeConfigurationGetSymbol(enum TWCoinType coin) { return TWStringCreateWithUTF8Bytes(getCoinInfo(coin).symbol); } diff --git a/src/Coin.h b/src/Coin.h index e18cc7b88ea..35ad5bc1d59 100644 --- a/src/Coin.h +++ b/src/Coin.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -32,8 +30,12 @@ std::vector getCoinTypes(); /// Validates an address for a particular coin. bool validateAddress(TWCoinType coin, const std::string& address); +/// Validates an address for a particular coin. +bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& prefix); + /// Validates and normalizes an address for a particular coin. std::string normalizeAddress(TWCoinType coin, const std::string& address); +std::string normalizeAddress(TWCoinType coin, const std::string& address, const PrefixVariant& prefix); /// Returns the blockchain for a coin type. TWBlockchain blockchain(TWCoinType coin); @@ -74,11 +76,8 @@ std::string deriveAddress(TWCoinType coin, const PrivateKey& privateKey); /// Derives the address for a particular coin from the private key, with given derivation. std::string deriveAddress(TWCoinType coin, const PrivateKey& privateKey, TWDerivation derivation); -/// Derives the address for a particular coin from the public key. -std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey); - -/// Derives the address for a particular coin from the public key, with given derivation. -std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation); +/// Derives the address for a particular coin from the public key, with given derivation and addressPrefix. +std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation = TWDerivationDefault, const PrefixVariant& addressPrefix = std::monostate()); /// Returns the binary representation of a string address Data addressToData(TWCoinType coin, const std::string& address); @@ -104,6 +103,9 @@ byte p2shPrefix(TWCoinType coin); /// Returns human readable part for a coin type. enum TWHRP hrp(TWCoinType coin); +/// Returns the ss58 prefix of a coin type. +std::uint32_t ss58Prefix(TWCoinType coin); + /// Returns chain ID. const char* chainId(TWCoinType coin); @@ -122,8 +124,6 @@ Data anyCoinPreImageHashes(TWCoinType coinType, const Data& txInputData); void anyCoinCompileWithSignatures(TWCoinType coinType, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& txOutputOut); -Data anyCoinBuildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId); - // Describes a derivation: path + optional format + optional name struct Derivation { TWDerivation name = TWDerivationDefault; @@ -155,6 +155,7 @@ struct CoinInfo { const char* explorerTransactionUrl; const char* explorerAccountUrl; uint32_t slip44; + std::uint32_t ss58Prefix; // returns default derivation const Derivation defaultDerivation() const { diff --git a/src/CoinEntry.cpp b/src/CoinEntry.cpp new file mode 100644 index 00000000000..c1748f7ab8d --- /dev/null +++ b/src/CoinEntry.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "CoinEntry.h" +#include "Coin.h" +#include "HexCoding.h" +#include "rust/Wrapper.h" +#include +#include + +namespace TW { + +const char* getFromPrefixHrpOrDefault(const PrefixVariant &prefix, TWCoinType coin) { + if (std::holds_alternative(prefix)) { + const char* fromPrefix = std::get(prefix); + if (fromPrefix != nullptr && *fromPrefix != 0) { + return fromPrefix; + } + } + // Prefix contains no hrp or empty, return coin-default + return stringForHRP(TW::hrp(coin)); +} + +byte getFromPrefixPkhOrDefault(const PrefixVariant &prefix, TWCoinType coin) { + if (std::holds_alternative(prefix)) { + return std::get(prefix).p2pkh; + } + // Prefix contains no base58 prefixes, return coin-default + return TW::p2pkhPrefix(coin); +} + +} // namespace TW diff --git a/src/CoinEntry.h b/src/CoinEntry.h index bff6736752c..569593dac84 100644 --- a/src/CoinEntry.h +++ b/src/CoinEntry.h @@ -1,13 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include #include +#include #include "Data.h" #include "PublicKey.h" @@ -17,47 +16,58 @@ #include #include +#include #include namespace TW { typedef std::vector> HashPubkeyList; +struct Base58Prefix { + TW::byte p2pkh; + TW::byte p2sh; +}; + +using Bech32Prefix = const char *; +using SS58Prefix = uint32_t; + +/// Declare a dummy prefix to notify the entry to derive a delegated address. +struct DelegatedPrefix {}; + +/// Declare a dummy prefix to notify the entry to derive a firo exchange address. +struct ExchangePrefix {}; + +using PrefixVariant = std::variant; + /// Interface for coin-specific entry, used to dispatch calls to coins /// Implement this for all coins. class CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const = 0; + virtual ~CoinEntry() noexcept = default; + virtual bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const = 0; // normalizeAddress is optional, it may leave this default, no-change implementation - virtual std::string normalizeAddress(TWCoinType coin, const std::string& address) const { return address; } - // Address derivation, default derivation - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const = 0; - // Address derivation, by default invoking default - virtual std::string deriveAddress(TWCoinType coin, TWDerivation derivation, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const { - return deriveAddress(coin, publicKey, p2pkh, hrp); - } + virtual std::string normalizeAddress([[maybe_unused]] TWCoinType coin, const std::string& address) const { return address; } + // Address derivation + virtual std::string deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const = 0; // Return the binary representation of a string address, used by AnyAddress // It is optional, if not defined, 'AnyAddress' interface will not support this coin. - virtual Data addressToData(TWCoinType coin, const std::string& address) const { return {}; } + virtual Data addressToData([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const std::string& address) const { return {}; } // Signing virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const = 0; virtual bool supportsJSONSigning() const { return false; } // It is optional, Signing JSON input with private key - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const { return ""; } + virtual std::string signJSON([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const std::string& json, [[maybe_unused]] const Data& key) const { return ""; } // Planning, for UTXO chains, in preparation for signing // It is optional, only UTXO chains need it, default impl. leaves empty result. - virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const { return; } + virtual void plan([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const Data& dataIn, [[maybe_unused]] Data& dataOut) const { } // Optional method for obtaining hash(es) for signing, needed for external signing. // It will return a proto object named `PreSigningOutput` which will include hash. // We provide a default `PreSigningOutput` in TransactionCompiler.proto. // For some special coins, such as bitcoin, we will create a custom `PreSigningOutput` object in its proto file. - virtual Data preImageHashes(TWCoinType coin, const Data& txInputData) const { return Data(); } + virtual Data preImageHashes([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const Data& txInputData) const { return {}; } // Optional method for compiling a transaction with externally-supplied signatures & pubkeys. - virtual void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const {} - // Optional helper to prepare a SigningInput from simple parameters. - // Not suitable for UTXO chains. Some parameters, like chain-specific fee/gas paraemters, may need to be set in the SigningInput. - virtual Data buildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) const { return Data(); } + virtual void compile([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const Data& txInputData, [[maybe_unused]] const std::vector& signatures, [[maybe_unused]] const std::vector& publicKeys, [[maybe_unused]] Data& dataOut) const {} }; // In each coin's Entry.cpp the specific types of the coin are used, this template enforces the Signer implement: @@ -89,7 +99,7 @@ Data txCompilerTemplate(const Data& dataIn, Func&& fnHandler) { if (!input.ParseFromArray(dataIn.data(), (int)dataIn.size())) { output.set_error(Common::Proto::Error_input_parse); output.set_error_message("failed to parse input data"); - return TW::data(output.SerializeAsString());; + return TW::data(output.SerializeAsString()); } try { @@ -102,4 +112,43 @@ Data txCompilerTemplate(const Data& dataIn, Func&& fnHandler) { return TW::data(output.SerializeAsString()); } +// This template will be used for compile in each coin's Entry.cpp. +// It is a helper function to simplify exception handle that validates if there is only one `signatures` and one `publicKeys`. +template +Data txCompilerSingleTemplate(const Data& dataIn, const std::vector& signatures, const std::vector& publicKeys, Func&& fnHandler) { + auto input = Input(); + auto output = Output(); + if (!input.ParseFromArray(dataIn.data(), (int)dataIn.size())) { + output.set_error(Common::Proto::Error_input_parse); + output.set_error_message("failed to parse input data"); + return TW::data(output.SerializeAsString()); + } + + if (signatures.empty() || publicKeys.empty()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return TW::data(output.SerializeAsString()); + } + if (signatures.size() != 1 || publicKeys.size() != 1) { + output.set_error(Common::Proto::Error_no_support_n2n); + output.set_error_message("signatures and publickeys size can only be one"); + return TW::data(output.SerializeAsString()); + } + + try { + // each coin function handler + fnHandler(input, output, signatures[0], publicKeys[0]); + } catch (const std::exception& e) { + output.set_error(Common::Proto::Error_internal); + output.set_error_message(e.what()); + } + return TW::data(output.SerializeAsString()); +} + +// Get the hrp from the prefix variant, or the coin-default if it is empty or it is not an hrp +const char* getFromPrefixHrpOrDefault(const PrefixVariant &prefix, TWCoinType coin); + +// Get the p2pkh prefix from the prefix variant, or the coin-default if it does not contain base58 prefixes +byte getFromPrefixPkhOrDefault(const PrefixVariant &prefix, TWCoinType coin); + } // namespace TW diff --git a/src/Cosmos/Address.cpp b/src/Cosmos/Address.cpp deleted file mode 100644 index 5cfabf5e553..00000000000 --- a/src/Cosmos/Address.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright © 2017 Pieter Wuille -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Address.h" diff --git a/src/Cosmos/Address.h b/src/Cosmos/Address.h index 36e5399bbf6..ed4c7bf61eb 100644 --- a/src/Cosmos/Address.h +++ b/src/Cosmos/Address.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../Bech32Address.h" -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include "../Coin.h" #include @@ -22,9 +20,6 @@ class Address: public Bech32Address { public: Address() : Bech32Address("") {} - /// Initializes an address with a key hash, with prefix of the given coin. - Address(TWCoinType coin, const Data& keyHash) : Bech32Address(stringForHRP(TW::hrp(coin)), keyHash) {} - /// Initializes an address with a key hash, with given prefix. Address(const std::string& hrp, const Data& keyHash) : Bech32Address(hrp, keyHash) {} @@ -40,6 +35,11 @@ class Address: public Bech32Address { return Bech32Address::isValid(addr, hrp); } + /// Determines whether a string makes a valid Bech32 address with the given hrp. + static bool isValid(const std::string& addr, const std::string& hrp) { + return Bech32Address::isValid(addr, hrp); + } + /// Creates an address object from the given string, if valid. Returns success. static bool decode(const std::string& addr, Address& obj_out) { return Bech32Address::decode(addr, obj_out, ""); diff --git a/src/Cosmos/Entry.cpp b/src/Cosmos/Entry.cpp index 686ebe06557..69a4b4c2707 100644 --- a/src/Cosmos/Entry.cpp +++ b/src/Cosmos/Entry.cpp @@ -1,43 +1,26 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" - #include "Address.h" -#include "Signer.h" -using namespace TW::Cosmos; +#include +#include +#include + using namespace TW; using namespace std; -// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. +namespace TW::Cosmos { -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char* hrp) const { - return Address::isValid(coin, address); +string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { + return signJSONHelper( + coin, + json, + key, + [](const Proto::SigningOutput& output) { return output.json(); } + ); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char* hrp) const { - return Address(coin, publicKey).string(); -} - -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { - Address addr; - if (!Address::decode(address, addr)) { - return Data(); - } - return addr.getKeyHash(); -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - auto input = Proto::SigningInput(); - input.ParseFromArray(dataIn.data(), (int)dataIn.size()); - auto serializedOut = Signer::sign(input, coin).SerializeAsString(); - dataOut.insert(dataOut.end(), serializedOut.begin(), serializedOut.end()); -} - -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { - return Signer::signJSON(json, key, coin); -} +} // namespace TW::Cosmos diff --git a/src/Cosmos/Entry.h b/src/Cosmos/Entry.h index f31825ca167..77d8efec01e 100644 --- a/src/Cosmos/Entry.h +++ b/src/Cosmos/Entry.h @@ -1,25 +1,20 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../CoinEntry.h" +#include "rust/RustCoinEntry.h" namespace TW::Cosmos { /// Entry point for implementation of Cosmos coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry : public Rust::RustCoinEntryWithSignJSON { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + ~Entry() override = default; + bool supportsJSONSigning() const final { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const final; }; } // namespace TW::Cosmos diff --git a/src/Cosmos/JsonSerialization.cpp b/src/Cosmos/JsonSerialization.cpp deleted file mode 100644 index cf77b344a5d..00000000000 --- a/src/Cosmos/JsonSerialization.cpp +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "JsonSerialization.h" -#include "ProtobufSerialization.h" - -#include "../Cosmos/Address.h" -#include "../proto/Cosmos.pb.h" -#include "Base64.h" -#include "PrivateKey.h" - -using namespace TW; -using namespace TW::Cosmos; - -using json = nlohmann::json; -using string = std::string; - -namespace TW::Cosmos { - -const string TYPE_PREFIX_MSG_SEND = "cosmos-sdk/MsgSend"; -const string TYPE_PREFIX_MSG_DELEGATE = "cosmos-sdk/MsgDelegate"; -const string TYPE_PREFIX_MSG_UNDELEGATE = "cosmos-sdk/MsgUndelegate"; -const string TYPE_PREFIX_MSG_REDELEGATE = "cosmos-sdk/MsgBeginRedelegate"; -const string TYPE_PREFIX_MSG_WITHDRAW_REWARD = "cosmos-sdk/MsgWithdrawDelegationReward"; -const string TYPE_PREFIX_PUBLIC_KEY = "tendermint/PubKeySecp256k1"; -const string TYPE_PREFIX_WASM_MSG_EXECUTE = "wasm/MsgExecuteContract"; - -static string broadcastMode(Proto::BroadcastMode mode) { - switch (mode) { - case Proto::BroadcastMode::BLOCK: - return "block"; - case Proto::BroadcastMode::ASYNC: - return "async"; - default: return "sync"; - } -} - -static json broadcastJSON(json& j, Proto::BroadcastMode mode) { - return { - {"tx", j}, - {"mode", broadcastMode(mode)} - }; -} - -static json amountJSON(const Proto::Amount& amount) { - return { - {"amount", amount.amount()}, - {"denom", amount.denom()} - }; -} - -static json amountsJSON(const ::google::protobuf::RepeatedPtrField& amounts) { - json j = json::array(); - for (auto& amount : amounts) { - j.push_back(amountJSON(amount)); - } - return j; -} - -static json feeJSON(const Proto::Fee& fee) { - json js = json::array(); - - for (auto& amount : fee.amounts()) { - js.push_back(amountJSON(amount)); - } - - return { - {"amount", js}, - {"gas", std::to_string(fee.gas())} - }; -} - -static json messageSend(const Proto::Message_Send& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_SEND : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"amount", amountsJSON(message.amounts())}, - {"from_address", message.from_address()}, - {"to_address", message.to_address()} - }} - }; -} - -static json messageDelegate(const Proto::Message_Delegate& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_DELEGATE : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"amount", amountJSON(message.amount())}, - {"delegator_address", message.delegator_address()}, - {"validator_address", message.validator_address()} - }} - }; -} - -static json messageUndelegate(const Proto::Message_Undelegate& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_UNDELEGATE : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"amount", amountJSON(message.amount())}, - {"delegator_address", message.delegator_address()}, - {"validator_address", message.validator_address()} - }} - }; -} - -static json messageRedelegate(const Proto::Message_BeginRedelegate& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_REDELEGATE : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"amount", amountJSON(message.amount())}, - {"delegator_address", message.delegator_address()}, - {"validator_src_address", message.validator_src_address()}, - {"validator_dst_address", message.validator_dst_address()}, - }} - }; -} - -static json messageWithdrawReward(const Proto::Message_WithdrawDelegationReward& message) { - auto typePrefix = message.type_prefix().empty() ? TYPE_PREFIX_MSG_WITHDRAW_REWARD : message.type_prefix(); - - return { - {"type", typePrefix}, - {"value", { - {"delegator_address", message.delegator_address()}, - {"validator_address", message.validator_address()} - }} - }; -} - -json messageWasmTerraTransfer(const Proto::Message_WasmTerraExecuteContractTransfer& msg) { - return { - {"type", TYPE_PREFIX_WASM_MSG_EXECUTE}, - {"value", - { - {"sender", msg.sender_address()}, - {"contract", msg.contract_address()}, - {"execute_msg", wasmTerraExecuteTransferPayload(msg)}, - {"coins", json::array()} // used in case you are sending native tokens along with this message - } - } - }; -} - -static json messageRawJSON(const Proto::Message_RawJSON& message) { - return { - {"type", message.type()}, - {"value", json::parse(message.value())}, - }; -} - -static json messagesJSON(const Proto::SigningInput& input) { - json j = json::array(); - for (auto& msg : input.messages()) { - if (msg.has_send_coins_message()) { - j.push_back(messageSend(msg.send_coins_message())); - } else if (msg.has_stake_message()) { - j.push_back(messageDelegate(msg.stake_message())); - } else if (msg.has_unstake_message()) { - j.push_back(messageUndelegate(msg.unstake_message())); - } else if (msg.has_withdraw_stake_reward_message()) { - j.push_back(messageWithdrawReward(msg.withdraw_stake_reward_message())); - } else if (msg.has_restake_message()) { - j.push_back(messageRedelegate(msg.restake_message())); - } else if (msg.has_raw_json_message()) { - j.push_back(messageRawJSON(msg.raw_json_message())); - } else if ((msg.has_wasm_terra_execute_contract_transfer_message())) { - j.push_back(messageWasmTerraTransfer(msg.wasm_terra_execute_contract_transfer_message())); - } else if (msg.has_transfer_tokens_message() || msg.has_wasm_terra_execute_contract_generic()) { - assert(false); // not supported, use protobuf serialization - return json::array(); - } - } - return j; -} - -static json signatureJSON(const Data& signature, const Data& pubkey) { - return { - {"pub_key", { - {"type", TYPE_PREFIX_PUBLIC_KEY}, - {"value", Base64::encode(pubkey)} - }}, - {"signature", Base64::encode(signature)} - }; -} - -json signaturePreimageJSON(const Proto::SigningInput& input) { - return { - {"account_number", std::to_string(input.account_number())}, - {"chain_id", input.chain_id()}, - {"fee", feeJSON(input.fee())}, - {"memo", input.memo()}, - {"msgs", messagesJSON(input)}, - {"sequence", std::to_string(input.sequence())} - }; -} - -json transactionJSON(const Proto::SigningInput& input, const Data& signature) { - auto privateKey = PrivateKey(input.private_key()); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - json tx = { - {"fee", feeJSON(input.fee())}, - {"memo", input.memo()}, - {"msg", messagesJSON(input)}, - {"signatures", json::array({ - signatureJSON(signature, Data(publicKey.bytes)) - })} - }; - return broadcastJSON(tx, input.mode()); -} - -} // namespace diff --git a/src/Cosmos/JsonSerialization.h b/src/Cosmos/JsonSerialization.h deleted file mode 100644 index 61b63e92dd8..00000000000 --- a/src/Cosmos/JsonSerialization.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "../proto/Cosmos.pb.h" -#include "Data.h" -#include - -using string = std::string; -using json = nlohmann::json; - -extern const string TYPE_PREFIX_MSG_SEND; -extern const string TYPE_PREFIX_MSG_TRANSFER; -extern const string TYPE_PREFIX_MSG_DELEGATE; -extern const string TYPE_PREFIX_MSG_UNDELEGATE; -extern const string TYPE_PREFIX_MSG_REDELEGATE; -extern const string TYPE_PREFIX_MSG_WITHDRAW_REWARD; -extern const string TYPE_PREFIX_PUBLIC_KEY; - -namespace TW::Cosmos { - -json signaturePreimageJSON(const Proto::SigningInput& input); -json transactionJSON(const Proto::SigningInput& input, const Data& signature); - -} // namespace diff --git a/src/Cosmos/Protobuf/distribution_tx.proto b/src/Cosmos/Protobuf/distribution_tx.proto deleted file mode 100644 index 1182c562140..00000000000 --- a/src/Cosmos/Protobuf/distribution_tx.proto +++ /dev/null @@ -1,11 +0,0 @@ -syntax = "proto3"; -package cosmos.distribution.v1beta1; - -// Src: https://github.com/cosmos/cosmos-sdk/blob/master/proto/cosmos/distribution/v1beta1/tx.proto - -// MsgWithdrawDelegatorReward represents delegation withdrawal to a delegator -// from a single validator. -message MsgWithdrawDelegatorReward { - string delegator_address = 1; - string validator_address = 2; -} diff --git a/src/Cosmos/Protobuf/thorchain_bank_tx.proto b/src/Cosmos/Protobuf/thorchain_bank_tx.proto deleted file mode 100644 index 0e5870ab162..00000000000 --- a/src/Cosmos/Protobuf/thorchain_bank_tx.proto +++ /dev/null @@ -1,14 +0,0 @@ -syntax = "proto3"; -package types; - -// Src: https://gitlab.com/thorchain/thornode/-/blob/develop/proto/thorchain/v1/x/thorchain/types/msg_send.proto -// Cosmos original: https://github.com/cosmos/cosmos-sdk/blob/master/proto/cosmos/bank/v1beta1/tx.proto - -import "coin.proto"; - -// MsgSend represents a message to send coins from one account to another. -message MsgSend { - bytes from_address = 1; - bytes to_address = 2; - repeated cosmos.base.v1beta1.Coin amount = 3; -} diff --git a/src/Cosmos/ProtobufSerialization.cpp b/src/Cosmos/ProtobufSerialization.cpp deleted file mode 100644 index 0ceb5bff9d2..00000000000 --- a/src/Cosmos/ProtobufSerialization.cpp +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "ProtobufSerialization.h" -#include "JsonSerialization.h" -#include "../proto/Cosmos.pb.h" -#include "Protobuf/coin.pb.h" -#include "Protobuf/bank_tx.pb.h" -#include "Protobuf/distribution_tx.pb.h" -#include "Protobuf/staking_tx.pb.h" -#include "Protobuf/tx.pb.h" -#include "Protobuf/crypto_secp256k1_keys.pb.h" -#include "Protobuf/ibc_applications_transfer_tx.pb.h" -#include "Protobuf/terra_wasm_v1beta1_tx.pb.h" -#include "Protobuf/thorchain_bank_tx.pb.h" -#include "Protobuf/ethermint_keys.pb.h" - -#include "PrivateKey.h" -#include "Data.h" -#include "Hash.h" -#include "Base64.h" -#include "uint256.h" - -#include - -using namespace TW; -using namespace TW::Cosmos; - -namespace TW::Cosmos { - -const auto ProtobufAnyNamespacePrefix = ""; // to override default 'type.googleapis.com' - -cosmos::base::v1beta1::Coin convertCoin(const Proto::Amount& amount) { - cosmos::base::v1beta1::Coin coin; - coin.set_denom(amount.denom()); - coin.set_amount(amount.amount()); - return coin; -} - -// Convert messages from external protobuf to internal protobuf -google::protobuf::Any convertMessage(const Proto::Message& msg) { - google::protobuf::Any any; - switch (msg.message_oneof_case()) { - case Proto::Message::kSendCoinsMessage: - { - assert(msg.has_send_coins_message()); - const auto& send = msg.send_coins_message(); - auto msgSend = cosmos::bank::v1beta1::MsgSend(); - msgSend.set_from_address(send.from_address()); - msgSend.set_to_address(send.to_address()); - for (auto i = 0; i < send.amounts_size(); ++i) { - *msgSend.add_amount() = convertCoin(send.amounts(i)); - } - any.PackFrom(msgSend, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kTransferTokensMessage: - { - assert(msg.has_transfer_tokens_message()); - const auto& transfer = msg.transfer_tokens_message(); - auto msgTransfer = ibc::applications::transfer::v1::MsgTransfer(); - msgTransfer.set_source_port(transfer.source_port()); - msgTransfer.set_source_channel(transfer.source_channel()); - *msgTransfer.mutable_token() = convertCoin(transfer.token()); - msgTransfer.set_sender(transfer.sender()); - msgTransfer.set_receiver(transfer.receiver()); - msgTransfer.mutable_timeout_height()->set_revision_number(transfer.timeout_height().revision_number()); - msgTransfer.mutable_timeout_height()->set_revision_height(transfer.timeout_height().revision_height()); - msgTransfer.set_timeout_timestamp(transfer.timeout_timestamp()); - any.PackFrom(msgTransfer, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kStakeMessage: - { - assert(msg.has_stake_message()); - const auto& stake = msg.stake_message(); - auto msgDelegate = cosmos::staking::v1beta1::MsgDelegate(); - msgDelegate.set_delegator_address(stake.delegator_address()); - msgDelegate.set_validator_address(stake.validator_address()); - *msgDelegate.mutable_amount() = convertCoin(stake.amount()); - any.PackFrom(msgDelegate, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kUnstakeMessage: - { - assert(msg.has_unstake_message()); - const auto& unstake = msg.unstake_message(); - auto msgUndelegate = cosmos::staking::v1beta1::MsgUndelegate(); - msgUndelegate.set_delegator_address(unstake.delegator_address()); - msgUndelegate.set_validator_address(unstake.validator_address()); - *msgUndelegate.mutable_amount() = convertCoin(unstake.amount()); - any.PackFrom(msgUndelegate, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kRestakeMessage: - { - assert(msg.has_restake_message()); - const auto& restake = msg.restake_message(); - auto msgRedelegate = cosmos::staking::v1beta1::MsgBeginRedelegate(); - msgRedelegate.set_delegator_address(restake.delegator_address()); - msgRedelegate.set_validator_src_address(restake.validator_src_address()); - msgRedelegate.set_validator_dst_address(restake.validator_dst_address()); - *msgRedelegate.mutable_amount() = convertCoin(restake.amount()); - any.PackFrom(msgRedelegate, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWithdrawStakeRewardMessage: - { - assert(msg.has_withdraw_stake_reward_message()); - const auto& withdraw = msg.withdraw_stake_reward_message(); - auto msgWithdraw = cosmos::distribution::v1beta1::MsgWithdrawDelegatorReward(); - msgWithdraw.set_delegator_address(withdraw.delegator_address()); - msgWithdraw.set_validator_address(withdraw.validator_address()); - any.PackFrom(msgWithdraw, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWasmTerraExecuteContractTransferMessage: - { - assert(msg.has_wasm_terra_execute_contract_transfer_message()); - const auto& wasmExecute = msg.wasm_terra_execute_contract_transfer_message(); - auto msgExecute = terra::wasm::v1beta1::MsgExecuteContract(); - msgExecute.set_sender(wasmExecute.sender_address()); - msgExecute.set_contract(wasmExecute.contract_address()); - const std::string payload = Cosmos::wasmTerraExecuteTransferPayload(wasmExecute).dump(); - msgExecute.set_execute_msg(payload); - any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kWasmTerraExecuteContractSendMessage: - { - assert(msg.has_wasm_terra_execute_contract_send_message()); - const auto& wasmExecute = msg.wasm_terra_execute_contract_send_message(); - auto msgExecute = terra::wasm::v1beta1::MsgExecuteContract(); - msgExecute.set_sender(wasmExecute.sender_address()); - msgExecute.set_contract(wasmExecute.contract_address()); - const std::string payload = Cosmos::wasmTerraExecuteSendPayload(wasmExecute).dump(); - msgExecute.set_execute_msg(payload); - any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); - return any; - } - - case Proto::Message::kThorchainSendMessage: - { - assert(msg.has_thorchain_send_message()); - const auto& send = msg.thorchain_send_message(); - auto msgSend =types::MsgSend(); - msgSend.set_from_address(send.from_address()); - msgSend.set_to_address(send.to_address()); - for (auto i = 0; i < send.amounts_size(); ++i) { - *msgSend.add_amount() = convertCoin(send.amounts(i)); - } - any.PackFrom(msgSend, ProtobufAnyNamespacePrefix); - return any; - } - case Proto::Message::kWasmTerraExecuteContractGeneric: { - assert(msg.has_wasm_terra_execute_contract_generic()); - const auto& wasmExecute = msg.wasm_terra_execute_contract_generic(); - auto msgExecute = terra::wasm::v1beta1::MsgExecuteContract(); - msgExecute.set_sender(wasmExecute.sender_address()); - msgExecute.set_contract(wasmExecute.contract_address()); - msgExecute.set_execute_msg(wasmExecute.execute_msg()); - - for (auto i = 0; i < wasmExecute.coins_size(); ++i) { - *msgExecute.add_coins() = convertCoin(wasmExecute.coins(i)); - } - any.PackFrom(msgExecute, ProtobufAnyNamespacePrefix); - return any; - } - - default: - throw std::invalid_argument(std::string("Message not supported ") + std::to_string(msg.message_oneof_case())); - } -} - -std::string buildProtoTxBody(const Proto::SigningInput& input) { - if (input.messages_size() < 1) { - throw std::invalid_argument("No message found"); - } - assert(input.messages_size() >= 1); - auto txBody = cosmos::TxBody(); - for (auto i = 0; i < input.messages_size(); ++i) { - const auto msgAny = convertMessage(input.messages(i)); - *txBody.add_messages() = msgAny; - } - txBody.set_memo(input.memo()); - txBody.set_timeout_height(0); - - return txBody.SerializeAsString(); -} - -std::string buildAuthInfo(const Proto::SigningInput& input, TWCoinType coin) { - // AuthInfo - const auto privateKey = PrivateKey(input.private_key()); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - auto authInfo = cosmos::AuthInfo(); - auto* signerInfo = authInfo.add_signer_infos(); - - signerInfo->mutable_mode_info()->mutable_single()->set_mode(cosmos::signing::v1beta1::SIGN_MODE_DIRECT); - signerInfo->set_sequence(input.sequence()); - switch(coin) { - case TWCoinTypeNativeEvmos: { - auto pubKey = ethermint::crypto::v1::ethsecp256k1::PubKey(); - pubKey.set_key(publicKey.bytes.data(), publicKey.bytes.size()); - signerInfo->mutable_public_key()->PackFrom(pubKey, ProtobufAnyNamespacePrefix); - break; - } - default: { - auto pubKey = cosmos::crypto::secp256k1::PubKey(); - pubKey.set_key(publicKey.bytes.data(), publicKey.bytes.size()); - signerInfo->mutable_public_key()->PackFrom(pubKey, ProtobufAnyNamespacePrefix); - } - } - - auto* fee = authInfo.mutable_fee(); - for (auto i = 0; i < input.fee().amounts_size(); ++i) { - *fee->add_amount() = convertCoin(input.fee().amounts(i)); - } - - fee->set_gas_limit(input.fee().gas()); - fee->set_payer(""); - fee->set_granter(""); - // tip is omitted - return authInfo.SerializeAsString(); -} - -Data buildSignature(const Proto::SigningInput& input, const std::string& serializedTxBody, const std::string& serializedAuthInfo, TWCoinType coin) { - // SignDoc Preimage - auto signDoc = cosmos::SignDoc(); - signDoc.set_body_bytes(serializedTxBody); - signDoc.set_auth_info_bytes(serializedAuthInfo); - signDoc.set_chain_id(input.chain_id()); - signDoc.set_account_number(input.account_number()); - const auto serializedSignDoc = signDoc.SerializeAsString(); - - Data hashToSign; - switch(coin) { - case TWCoinTypeNativeEvmos: { - hashToSign = Hash::keccak256(serializedSignDoc); - break; - } - default: { - hashToSign = Hash::sha256(serializedSignDoc); - } - } - - const auto privateKey = PrivateKey(input.private_key()); - auto signedHash = privateKey.sign(hashToSign, TWCurveSECP256k1); - auto signature = Data(signedHash.begin(), signedHash.end() - 1); - return signature; -} - -std::string buildProtoTxRaw(const Proto::SigningInput& input, const std::string& serializedTxBody, const std::string& serializedAuthInfo, const Data& signature) { - auto txRaw = cosmos::TxRaw(); - txRaw.set_body_bytes(serializedTxBody); - txRaw.set_auth_info_bytes(serializedAuthInfo); - *txRaw.add_signatures() = std::string(signature.begin(), signature.end()); - return txRaw.SerializeAsString(); -} - -static string broadcastMode(Proto::BroadcastMode mode) { - switch (mode) { - case Proto::BroadcastMode::BLOCK: - return "BROADCAST_MODE_BLOCK"; - case Proto::BroadcastMode::ASYNC: - return "BROADCAST_MODE_ASYNC"; - case Proto::BroadcastMode::SYNC: - default: return "BROADCAST_MODE_SYNC"; - } -} - -std::string buildProtoTxJson(const Proto::SigningInput& input, const std::string& serializedTx) { - const string serializedBase64 = Base64::encode(TW::data(serializedTx)); - const json jsonSerialized = { - {"tx_bytes", serializedBase64}, - {"mode", broadcastMode(input.mode())} - }; - return jsonSerialized.dump(); -} - -json wasmTerraExecuteTransferPayload(const Proto::Message_WasmTerraExecuteContractTransfer& msg) { - return { - {"transfer", - { - {"amount", toString(load(data(msg.amount())))}, - {"recipient", msg.recipient_address()} - } - } - }; -} - -json wasmTerraExecuteSendPayload(const Proto::Message_WasmTerraExecuteContractSend& msg) { - return { - {"send", - { - {"amount", toString(load(data(msg.amount())))}, - {"contract", msg.recipient_contract_address()}, - {"msg", msg.msg()} - } - } - }; -} - -} // namespace diff --git a/src/Cosmos/ProtobufSerialization.h b/src/Cosmos/ProtobufSerialization.h deleted file mode 100644 index 2e48685685a..00000000000 --- a/src/Cosmos/ProtobufSerialization.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "../proto/Cosmos.pb.h" -#include "Data.h" - -#include -#include - -#include - -namespace TW::Cosmos { - -std::string buildProtoTxBody(const Proto::SigningInput& input); - -std::string buildAuthInfo(const Proto::SigningInput& input, TWCoinType coin); - -Data buildSignature(const Proto::SigningInput& input, const std::string& serializedTxBody, const std::string& serializedAuthInfo, TWCoinType coin); - -std::string buildProtoTxRaw(const Proto::SigningInput& input, const std::string& serializedTxBody, const std::string& serializedAuthInfo, const Data& signature); - -std::string buildProtoTxJson(const Proto::SigningInput& input, const std::string& serializedTx); - -nlohmann::json wasmTerraExecuteTransferPayload(const Proto::Message_WasmTerraExecuteContractTransfer& msg); - -nlohmann::json wasmTerraExecuteSendPayload(const Proto::Message_WasmTerraExecuteContractSend& msg); - -} // namespace diff --git a/src/Cosmos/Signer.cpp b/src/Cosmos/Signer.cpp deleted file mode 100644 index f8f109b683d..00000000000 --- a/src/Cosmos/Signer.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Signer.h" -#include "../proto/Cosmos.pb.h" -#include "JsonSerialization.h" -#include "ProtobufSerialization.h" - -#include "PrivateKey.h" -#include "Data.h" -#include - -using namespace TW; -using namespace TW::Cosmos; - -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input, TWCoinType coin) noexcept { - switch (input.signing_mode()) { - case Proto::JSON: - return signJsonSerialized(input); - - case Proto::Protobuf: - default: - return signProtobuf(input, coin); - } -} - -Proto::SigningOutput Signer::signJsonSerialized(const Proto::SigningInput& input) noexcept { - auto key = PrivateKey(input.private_key()); - auto preimage = signaturePreimageJSON(input).dump(); - auto hash = Hash::sha256(preimage); - auto signedHash = key.sign(hash, TWCurveSECP256k1); - - auto output = Proto::SigningOutput(); - auto signature = Data(signedHash.begin(), signedHash.end() - 1); - auto txJson = transactionJSON(input, signature); - output.set_json(txJson.dump()); - output.set_signature(signature.data(), signature.size()); - output.set_serialized(""); - output.set_error(""); - return output; -} - -Proto::SigningOutput Signer::signProtobuf(const Proto::SigningInput& input, TWCoinType coin) noexcept { - try { - const auto serializedTxBody = buildProtoTxBody(input); - const auto serializedAuthInfo = buildAuthInfo(input, coin); - const auto signature = buildSignature(input, serializedTxBody, serializedAuthInfo, coin); - auto serializedTxRaw = buildProtoTxRaw(input, serializedTxBody, serializedAuthInfo, signature); - - auto output = Proto::SigningOutput(); - const string jsonSerialized = buildProtoTxJson(input, serializedTxRaw); - output.set_serialized(jsonSerialized); - output.set_signature(signature.data(), signature.size()); - output.set_json(""); - output.set_error(""); - return output; - } catch (const std::exception& ex) { - auto output = Proto::SigningOutput(); - output.set_error(std::string("Error: ") + ex.what()); - return output; - } -} - -std::string Signer::signJSON(const std::string& json, const Data& key, TWCoinType coin) { - auto input = Proto::SigningInput(); - google::protobuf::util::JsonStringToMessage(json, &input); - input.set_private_key(key.data(), key.size()); - auto output = Signer::sign(input, coin); - return output.json(); -} diff --git a/src/Cosmos/Signer.h b/src/Cosmos/Signer.h deleted file mode 100644 index b22a4fd2d48..00000000000 --- a/src/Cosmos/Signer.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "../Data.h" -#include "../proto/Cosmos.pb.h" - -#include - -namespace TW::Cosmos { - -/// Helper class that performs Cosmos transaction signing. -class Signer { - public: - /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input, TWCoinType coin) noexcept; - - /// Signs a Proto::SigningInput transaction, using Json serialization - static Proto::SigningOutput signJsonSerialized(const Proto::SigningInput& input) noexcept; - - /// Signs a Proto::SigningInput transaction, using binary Protobuf serialization - static Proto::SigningOutput signProtobuf(const Proto::SigningInput& input, TWCoinType coin) noexcept; - - /// Signs a json Proto::SigningInput with private key - static std::string signJSON(const std::string& json, const Data& key, TWCoinType coin); -}; - -} // namespace TW::Cosmos diff --git a/src/Crc.cpp b/src/Crc.cpp index 7a388a7c03c..38fc2c84d9c 100644 --- a/src/Crc.cpp +++ b/src/Crc.cpp @@ -1,13 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Crc.h" -#include // for boost::crc_32_type - +#include #include using namespace TW; @@ -17,7 +14,7 @@ uint16_t Crc::crc16(uint8_t* bytes, uint32_t length) { uint16_t crc = 0x0000; const uint16_t polynomial = 0x1021; - for (auto i = 0; i < length; i++) { + for (auto i = 0ul; i < length; i++) { const auto byte = bytes[i]; for (auto bitidx = 0; bitidx < 8; bitidx++) { const auto bit = ((byte >> (7 - bitidx) & 1) == 1); @@ -32,17 +29,12 @@ uint16_t Crc::crc16(uint8_t* bytes, uint32_t length) { return crc & 0xffff; } -uint32_t Crc::crc32(const Data& data) -{ - boost::crc_32_type result; - result.process_bytes((const void*)data.data(), data.size()); - return (uint32_t)result.checksum(); -} - -uint32_t Crc::crc32C(const Data& data) -{ - using crc_32c_type = boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true>; - crc_32c_type result; - result.process_bytes((const void*)data.data(), data.size()); - return (uint32_t)result.checksum(); +// Algorithm inspired by this old-style C implementation: +// https://web.mit.edu/freebsd/head/sys/libkern/crc32.c (Public Domain code) +uint32_t Crc::crc32(const Data& data) { + uint32_t c = std::numeric_limits::max(); + for (const auto byte : data) { + c = crc32_table[(c ^ byte) & 0xFF] ^ (c >> 8); + } + return ~c; } diff --git a/src/Crc.h b/src/Crc.h index 88bb3b28854..5268f186d13 100644 --- a/src/Crc.h +++ b/src/Crc.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -17,6 +15,50 @@ uint16_t crc16(uint8_t* bytes, uint32_t length); uint32_t crc32(const TW::Data& data); -uint32_t crc32C(const TW::Data& data); - +// Table taken from https://web.mit.edu/freebsd/head/sys/libkern/crc32.c (Public Domain code) +// This table is used to speed up the crc calculation. +static constexpr uint32_t crc32_table[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d}; } // namespace TW::Crc diff --git a/src/CryptoBox.cpp b/src/CryptoBox.cpp new file mode 100644 index 00000000000..f0d5218559f --- /dev/null +++ b/src/CryptoBox.cpp @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "CryptoBox.h" + +namespace TW::CryptoBox { + +bool PublicKey::isValid(const Data& bytes) { + Rust::TWDataWrapper data = bytes; + return Rust::tw_crypto_box_public_key_is_valid(data.get()); +} + +std::optional PublicKey::fromBytes(const Data& bytes) { + Rust::TWDataWrapper data = bytes; + if (!Rust::tw_crypto_box_public_key_is_valid(data.get())) { + return std::nullopt; + } + auto* publicKey = Rust::tw_crypto_box_public_key_create_with_data(data.get()); + return PublicKey(PublicKeyPtr(publicKey, Rust::tw_crypto_box_public_key_delete)); +} + +Data PublicKey::getData() const { + Rust::TWDataWrapper data = Rust::tw_crypto_box_public_key_data(impl.get()); + return data.toDataOrDefault(); +} + +SecretKey::SecretKey() { + auto* secretKey = Rust::tw_crypto_box_secret_key_create(); + impl = SecretKeyPtr(secretKey, Rust::tw_crypto_box_secret_key_delete); +} + +bool SecretKey::isValid(const Data& bytes) { + Rust::TWDataWrapper data = bytes; + return Rust::tw_crypto_box_secret_key_is_valid(data.get()); +} + +std::optional SecretKey::fromBytes(const Data& bytes) { + Rust::TWDataWrapper data = bytes; + if (!Rust::tw_crypto_box_secret_key_is_valid(data.get())) { + return std::nullopt; + } + auto* secretKey = Rust::tw_crypto_box_secret_key_create_with_data(data.get()); + return SecretKey(SecretKeyPtr(secretKey, Rust::tw_crypto_box_secret_key_delete)); +} + +PublicKey SecretKey::getPublicKey() const noexcept { + auto* publicKey = Rust::tw_crypto_box_secret_key_get_public_key(impl.get()); + return PublicKey(PublicKeyPtr(publicKey, Rust::tw_crypto_box_public_key_delete)); +} + +Data SecretKey::getData() const { + Rust::TWDataWrapper data = Rust::tw_crypto_box_secret_key_data(impl.get()); + return data.toDataOrDefault(); +} + +Data encryptEasy(const SecretKey& mySecret, const PublicKey& otherPubkey, const Data& message) { + Rust::TWDataWrapper messageData = message; + Rust::TWDataWrapper encrypted = Rust::tw_crypto_box_encrypt_easy(mySecret.impl.get(), otherPubkey.impl.get(), messageData.get()); + return encrypted.toDataOrDefault(); +} + +std::optional decryptEasy(const SecretKey& mySecret, const PublicKey& otherPubkey, const Data& encrypted) { + Rust::TWDataWrapper encryptedData = encrypted; + Rust::TWDataWrapper decryptedData = Rust::tw_crypto_box_decrypt_easy(mySecret.impl.get(), otherPubkey.impl.get(), encryptedData.get()); + if (!decryptedData.ptr) { + return std::nullopt; + } + return decryptedData.toDataOrDefault(); +} + +} // namespace TW::CryptoBox \ No newline at end of file diff --git a/src/CryptoBox.h b/src/CryptoBox.h new file mode 100644 index 00000000000..ce00f9c2404 --- /dev/null +++ b/src/CryptoBox.h @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "rust/Wrapper.h" + +namespace TW::CryptoBox { + +using PublicKeyPtr = std::shared_ptr; +using SecretKeyPtr = std::shared_ptr; + +/// Public key used in `crypto_box` cryptography. +struct PublicKey { + explicit PublicKey(PublicKeyPtr ptr): impl(std::move(ptr)) { + } + + /// Determines if the given public key is valid or not. + static bool isValid(const Data& bytes); + + /// Create a `crypto_box` public key with the given block of data. + static std::optional fromBytes(const Data& bytes); + + /// Returns the raw data of the given public-key. + Data getData() const; + + PublicKeyPtr impl; +}; + +/// Secret key used in `crypto_box` cryptography. +class SecretKey { +public: + /// Create a random secret key. + SecretKey(); + + explicit SecretKey(SecretKeyPtr ptr): impl(std::move(ptr)) { + } + + /// Determines if the given secret key is valid or not. + static bool isValid(const Data& bytes); + + /// Create a `crypto_box` secret key with the given block of data. + static std::optional fromBytes(const Data& bytes); + + /// Returns the public key associated with the given `key`. + PublicKey getPublicKey() const noexcept; + + /// Returns the raw data of the given secret-key. + Data getData() const; + + SecretKeyPtr impl; +}; + +/// Encrypts message using `my_secret` and `other_pubkey`. +/// The output will have a randomly generated nonce prepended to it. +/// The output will be Overhead + 24 bytes longer than the original. +Data encryptEasy(const SecretKey& mySecret, const PublicKey& otherPubkey, const Data& message); + +/// Decrypts box produced by `TWCryptoBoxEncryptEasy`. +/// We assume a 24-byte nonce is prepended to the encrypted text in box. +std::optional decryptEasy(const SecretKey& mySecret, const PublicKey& otherPubkey, const Data& encrypted); + +} // namespace TW::CryptoBox + +/// Wrapper for C interface. +struct TWCryptoBoxSecretKey { + TW::CryptoBox::SecretKey impl; +}; + +/// Wrapper for C interface. +struct TWCryptoBoxPublicKey { + TW::CryptoBox::PublicKey impl; +}; diff --git a/src/Data.cpp b/src/Data.cpp index 8eea5816ce5..67b2427b6b8 100644 --- a/src/Data.cpp +++ b/src/Data.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Data.h" diff --git a/src/Data.h b/src/Data.h index 1c43fdc68b0..0d853173f32 100644 --- a/src/Data.h +++ b/src/Data.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -16,10 +14,28 @@ namespace TW { using byte = std::uint8_t; using Data = std::vector; +typedef std::vector> HashPubkeyList; +typedef std::vector> SignaturePubkeyList; + inline void pad_left(Data& data, const uint32_t size) { data.insert(data.begin(), size - data.size(), 0); } +template +inline Data data(It&& begin, It&& end) { + return Data(begin, end); +} + +template +inline Data data_from(const Collection& collection) { + Data out; + out.reserve(collection.size()); + for (auto&& cur : collection) { + out.emplace_back(uint8_t(cur)); + } + return out; +} + inline Data data(const std::string& data) { return Data(data.begin(), data.end()); } @@ -32,6 +48,12 @@ inline void append(Data& data, const Data& suffix) { data.insert(data.end(), suffix.begin(), suffix.end()); } +inline Data concat(const Data& data, const Data& suffix) { + Data out = data; + append(out, suffix); + return out; +} + inline void append(Data& data, const byte suffix) { data.push_back(suffix); } diff --git a/src/DataVector.h b/src/DataVector.h new file mode 100644 index 00000000000..3e3a071807a --- /dev/null +++ b/src/DataVector.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TrustWalletCore/TWDataVector.h" +#include "Data.h" + +namespace TW { + +static std::vector createFromTWDataVector(const struct TWDataVector* _Nonnull dataVector) { + std::vector ret; + const auto n = TWDataVectorSize(dataVector); + for (auto i = 0uL; i < n; ++i) { + const auto* const elem = TWDataVectorGet(dataVector, i); + if (const auto* const data = reinterpret_cast(elem); data) { + ret.emplace_back(*data); + TWDataDelete(elem); + } + } + return ret; +} + +} // namespace TW diff --git a/src/Decred/Address.cpp b/src/Decred/Address.cpp index 0d0d8c666b3..b330145ca69 100644 --- a/src/Decred/Address.cpp +++ b/src/Decred/Address.cpp @@ -1,25 +1,22 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" #include "../Base58.h" -#include "../Hash.h" #include "../Coin.h" +#include "../Hash.h" #include -using namespace TW; -using namespace TW::Decred; +namespace TW::Decred { static const auto keyhashSize = Hash::ripemdSize; static const auto addressDataSize = keyhashSize + 2; bool Address::isValid(const std::string& string) noexcept { - const auto data = Base58::bitcoin.decodeCheck(string, Hash::HasherBlake256d); + const auto data = Base58::decodeCheck(string, Rust::Base58Alphabet::Bitcoin, Hash::HasherBlake256d); if (data.size() != addressDataSize) { return false; } @@ -27,12 +24,12 @@ bool Address::isValid(const std::string& string) noexcept { return false; } - return (data[1] == TW::p2pkhPrefix(TWCoinTypeDecred) || + return (data[1] == TW::p2pkhPrefix(TWCoinTypeDecred) || data[1] == TW::p2shPrefix(TWCoinTypeDecred)); } Address::Address(const std::string& string) { - const auto data = Base58::bitcoin.decodeCheck(string, Hash::HasherBlake256d); + const auto data = Base58::decodeCheck(string, Rust::Base58Alphabet::Bitcoin, Hash::HasherBlake256d); if (data.size() != addressDataSize) { throw std::invalid_argument("Invalid address string"); } @@ -42,7 +39,7 @@ Address::Address(const std::string& string) { Address::Address(const PublicKey& publicKey) { if (publicKey.type != TWPublicKeyTypeSECP256k1) { - throw std::invalid_argument("Invalid publid key type"); + throw std::invalid_argument("Invalid public key type"); } const auto hash = Hash::ripemd(Hash::blake256(publicKey.bytes)); std::copy(hash.begin(), hash.end(), bytes.begin() + 2); @@ -51,5 +48,7 @@ Address::Address(const PublicKey& publicKey) { } std::string Address::string() const { - return Base58::bitcoin.encodeCheck(bytes, Hash::HasherBlake256d); + return Base58::encodeCheck(bytes, Rust::Base58Alphabet::Bitcoin, Hash::HasherBlake256d); } + +} // namespace TW::Decred diff --git a/src/Decred/Address.h b/src/Decred/Address.h index d86bfcba2bf..4403d7da12a 100644 --- a/src/Decred/Address.h +++ b/src/Decred/Address.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/Decred/Entry.cpp b/src/Decred/Entry.cpp index 31faf5c04ac..4e6c17f60a6 100644 --- a/src/Decred/Entry.cpp +++ b/src/Decred/Entry.cpp @@ -1,35 +1,65 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" #include "Signer.h" -using namespace TW::Decred; -using namespace TW; -using namespace std; +namespace TW::Decred { -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { const auto addr = Address(address); return {addr.bytes.begin() + 2, addr.bytes.end()}; } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -void Entry::plan(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::plan([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { planTemplate(dataIn, dataOut); } + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + output = Signer::preImageHashes(input); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("signatures size and publickeys size not equal"); + return; + } + + HashPubkeyList externalSignatures; + auto n = signatures.size(); + for (auto i = 0ul; i < n; ++i) { + externalSignatures.push_back(std::make_pair(signatures[i], publicKeys[i].bytes)); + } + + output = Signer::sign(input, externalSignatures); + }); +} + +} // namespace TW::Decred diff --git a/src/Decred/Entry.h b/src/Decred/Entry.h index a744a63021d..9f207c67c63 100644 --- a/src/Decred/Entry.h +++ b/src/Decred/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,13 +10,16 @@ namespace TW::Decred { /// Decred entry dispatcher. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Decred diff --git a/src/Decred/OutPoint.cpp b/src/Decred/OutPoint.cpp index 27cb836c50a..88a74321101 100644 --- a/src/Decred/OutPoint.cpp +++ b/src/Decred/OutPoint.cpp @@ -1,17 +1,17 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "OutPoint.h" #include "../BinaryCoding.h" -using namespace TW::Decred; +namespace TW::Decred { void OutPoint::encode(Data& data) const { std::copy(std::begin(hash), std::end(hash), std::back_inserter(data)); encode32LE(index, data); data.push_back(static_cast(tree)); } + +} // namespace TW::Decred diff --git a/src/Decred/OutPoint.h b/src/Decred/OutPoint.h index 3ce3b5b169a..8ebd58a643b 100644 --- a/src/Decred/OutPoint.h +++ b/src/Decred/OutPoint.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../Bitcoin/OutPoint.h" #include "../proto/Bitcoin.pb.h" @@ -44,14 +42,14 @@ class OutPoint { OutPoint(const Bitcoin::Proto::OutPoint& other) { std::copy(other.hash().begin(), other.hash().begin() + hash.size(), hash.begin()); index = other.index(); - tree = 0; + tree = int8_t(other.tree()); } /// Initializes an out-point from a Protobuf out-point. OutPoint(const Bitcoin::OutPoint& other) { hash = other.hash; index = other.index; - tree = 0; + tree = other.tree; } /// Encodes the out-point into the provided buffer. diff --git a/src/Decred/Signer.cpp b/src/Decred/Signer.cpp index 6105aa5938c..f968cfe0239 100644 --- a/src/Decred/Signer.cpp +++ b/src/Decred/Signer.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" @@ -10,27 +8,24 @@ #include "TransactionOutput.h" #include "../Bitcoin/SigHashType.h" #include "../Bitcoin/SignatureBuilder.h" - #include "../BinaryCoding.h" -#include "../Hash.h" #include "../HexCoding.h" -#include "Bitcoin/OpCodes.h" - -using namespace TW; -using namespace TW::Decred; +namespace TW::Decred { Bitcoin::Proto::TransactionPlan Signer::plan(const Bitcoin::Proto::SigningInput& input) noexcept { - auto signer = Signer(std::move(input)); + auto signer = Signer(input); return signer.txPlan.proto(); } -Proto::SigningOutput Signer::sign(const Bitcoin::Proto::SigningInput& input) noexcept { - auto signer = Signer(std::move(input)); +Proto::SigningOutput Signer::sign(const Bitcoin::Proto::SigningInput& input, std::optional optionalExternalSigs) noexcept { + SigningMode signingMode = optionalExternalSigs.has_value() ? SigningMode_External : SigningMode_Normal; + auto signer = Signer(std::move(input), signingMode, optionalExternalSigs); auto result = signer.sign(); auto output = Proto::SigningOutput(); if (!result) { output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); return output; } @@ -46,19 +41,46 @@ Proto::SigningOutput Signer::sign(const Bitcoin::Proto::SigningInput& input) noe return output; } +Bitcoin::Proto::PreSigningOutput Signer::preImageHashes(const Bitcoin::Proto::SigningInput& input) noexcept { + Bitcoin::Proto::PreSigningOutput output; + + auto signer = Signer(std::move(input), SigningMode_HashOnly); + auto result = signer.sign(); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + return output; + } + + auto hashes = signer.getHashesForSigning(); + if (hashes.size() == 0) { + output.set_error(Common::Proto::Error_signing); + output.set_error_message("got empty preImage hashes"); + return output; + } + + auto* hashPubKeys = output.mutable_hash_public_keys(); + for (auto& h : hashes) { + auto* hpk = hashPubKeys->Add(); + hpk->set_data_hash(h.first.data(), h.first.size()); + hpk->set_public_key_hash(h.second.data(), h.second.size()); + } + return output; +} + Result Signer::sign() { - if (txPlan.utxos.size() == 0 || transaction.inputs.size() == 0) { + if (txPlan.utxos.empty() || _transaction.inputs.empty()) { return Result::failure(Common::Proto::Error_missing_input_utxos); } - signedInputs = transaction.inputs; + signedInputs = _transaction.inputs; const auto hashSingle = Bitcoin::hashTypeIsSingle(static_cast(input.hash_type())); - for (auto i = 0; i < txPlan.utxos.size(); i += 1) { + for (auto i = 0ul; i < txPlan.utxos.size(); i += 1) { auto& utxo = txPlan.utxos[i]; // Only sign TWBitcoinSigHashTypeSingle if there's a corresponding output - if (hashSingle && i >= transaction.outputs.size()) { + if (hashSingle && i >= _transaction.outputs.size()) { continue; } auto result = sign(utxo.script, i); @@ -68,14 +90,14 @@ Result Signer::sign() { signedInputs[i].script = result.payload(); } - Transaction tx(transaction); + Transaction tx(_transaction); tx.inputs = std::move(signedInputs); - tx.outputs = transaction.outputs; + tx.outputs = _transaction.outputs; return Result::success(std::move(tx)); } Result Signer::sign(Bitcoin::Script script, size_t index) { - assert(index < transaction.inputs.size()); + assert(index < _transaction.inputs.size()); Bitcoin::Script redeemScript; std::vector results; @@ -86,15 +108,15 @@ Result Signer::sign(Bitcoin::Scrip } else { return Result::failure(result.error()); } - auto txin = transaction.inputs[index]; + auto txin = _transaction.inputs[index]; if (script.isPayToScriptHash()) { script = Bitcoin::Script(results.front().begin(), results.front().end()); - auto result = signStep(script, index); - if (!result) { - return Result::failure(result.error()); + auto result_ = signStep(script, index); + if (!result_) { + return Result::failure(result_.error()); } - results = result.payload(); + results = result_.payload(); results.push_back(script.bytes); redeemScript = script; results.push_back(redeemScript.bytes); @@ -104,9 +126,9 @@ Result Signer::sign(Bitcoin::Scrip } Result, Common::Proto::SigningError> Signer::signStep(Bitcoin::Script script, size_t index) { - Transaction transactionToSign(transaction); + Transaction transactionToSign(_transaction); transactionToSign.inputs = signedInputs; - transactionToSign.outputs = transaction.outputs; + transactionToSign.outputs = _transaction.outputs; Data data; std::vector keys; @@ -115,11 +137,11 @@ Result, Common::Proto::SigningError> Signer::signStep(Bitcoin: if (script.matchPayToPublicKey(data)) { auto keyHash = TW::Hash::ripemd(TW::Hash::blake256(data)); auto key = keyForPublicKeyHash(keyHash); - if (key.empty()) { + if (key.empty() && signingMode == SigningMode_Normal) { // Error: Missing key return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); } - auto signature = createSignature(transactionToSign, script, key, index); + auto signature = createSignature(transactionToSign, script, key, keyHash, index); if (signature.empty()) { // Error: Failed to sign return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); @@ -127,18 +149,32 @@ Result, Common::Proto::SigningError> Signer::signStep(Bitcoin: return Result, Common::Proto::SigningError>::success({signature}); } else if (script.matchPayToPublicKeyHash(data)) { auto key = keyForPublicKeyHash(data); + Data pubkey; if (key.empty()) { - // Error: Missing keyxs - return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); + if (signingMode == SigningMode_HashOnly) { + // estimation mode, key is missing: use placeholder for public key + pubkey = Data(PublicKey::secp256k1Size); + } else if (signingMode == SigningMode_External) { + size_t hashSize = hashesForSigning.size(); + if (!externalSignatures.has_value() || externalSignatures.value().size() <= hashSize) { + // Error: no or not enough signatures provided + return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); + } + pubkey = std::get<1>(externalSignatures.value()[hashSize]); + } else { + // Error: Missing keyxs + return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); + } + } else { + pubkey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1).bytes; } - auto pubkey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); - auto signature = createSignature(transactionToSign, script, key, index); + auto signature = createSignature(transactionToSign, script, key, data, index); if (signature.empty()) { // Error: Failed to sign return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); } - return Result, Common::Proto::SigningError>::success({signature, pubkey.bytes}); + return Result, Common::Proto::SigningError>::success({signature, pubkey}); } else if (script.matchPayToScriptHash(data)) { auto redeemScript = scriptForScriptHash(data); if (redeemScript.empty()) { @@ -149,16 +185,16 @@ Result, Common::Proto::SigningError> Signer::signStep(Bitcoin: } else if (script.matchMultisig(keys, required)) { auto results = std::vector{{}}; for (auto& pubKey : keys) { - if (results.size() >= required + 1) { + if (results.size() >= required + 1ul) { break; } auto keyHash = TW::Hash::ripemd(TW::Hash::blake256(pubKey)); auto key = keyForPublicKeyHash(keyHash); - if (key.empty()) { + if (key.empty() && signingMode == SigningMode_Normal) { // Error: missing key return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); } - auto signature = createSignature(transactionToSign, script, key, index); + auto signature = createSignature(transactionToSign, script, key, keyHash, index); if (signature.empty()) { // Error: Failed to sign return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_signing); @@ -174,10 +210,46 @@ Result, Common::Proto::SigningError> Signer::signStep(Bitcoin: } Data Signer::createSignature(const Transaction& transaction, const Bitcoin::Script& script, - const Data& key, size_t index) { + const Data& key, const Data& publicKeyHash, size_t index) { auto sighash = transaction.computeSignatureHash(script, index, static_cast(input.hash_type())); + + if (signingMode == SigningMode_HashOnly) { + // Don't sign, only store hash-to-be-signed + pubkeyhash. Return placeholder. + hashesForSigning.push_back(std::make_pair(sighash, publicKeyHash)); + return Data(72); + } + + if (signingMode == SigningMode_External) { + // Use externally-provided signature + // Store hash, only for counting + size_t hashSize = hashesForSigning.size(); + hashesForSigning.push_back(std::make_pair(sighash, publicKeyHash)); + + if (!externalSignatures.has_value() || externalSignatures.value().size() <= hashSize) { + // Error: no or not enough signatures provided + return Data(); + } + + Data externalSignature = std::get<0>(externalSignatures.value()[hashSize]); + const Data publicKey = std::get<1>(externalSignatures.value()[hashSize]); + + // Verify provided signature + if (!PublicKey::isValid(publicKey, TWPublicKeyTypeSECP256k1)) { + // Error: invalid public key + return Data(); + } + const auto publicKeyObj = PublicKey(publicKey, TWPublicKeyTypeSECP256k1); + if (!publicKeyObj.verifyAsDER(externalSignature, sighash)) { + // Error: Signature does not match publickey+hash + return Data(); + } + externalSignature.push_back(static_cast(input.hash_type())); + + return externalSignature; + } + auto pk = PrivateKey(key); - auto signature = pk.signAsDER(Data(begin(sighash), end(sighash)), TWCurveSECP256k1); + auto signature = pk.signAsDER(Data(begin(sighash), end(sighash))); if (script.empty()) { return {}; } @@ -198,7 +270,7 @@ Data Signer::keyForPublicKeyHash(const Data& hash) const { } Data Signer::scriptForScriptHash(const Data& hash) const { - auto hashString = hex(hash.begin(), hash.end()); + auto hashString = hex(hash); auto it = input.scripts().find(hashString); if (it == input.scripts().end()) { // Error: Missing redeem script @@ -206,3 +278,5 @@ Data Signer::scriptForScriptHash(const Data& hash) const { } return Data(it->second.begin(), it->second.end()); } + +} // namespace TW::Decred diff --git a/src/Decred/Signer.h b/src/Decred/Signer.h index 6e299fa65d6..9208c744ee3 100644 --- a/src/Decred/Signer.h +++ b/src/Decred/Signer.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -24,6 +22,13 @@ namespace TW::Decred { +/// Normal and special signature modes +enum SigningMode { + SigningMode_Normal = 0, // normal signing + SigningMode_HashOnly, // no signing, only collect hash to be signed + SigningMode_External, // no signing, signatures are provided +}; + /// Helper class that performs Decred transaction signing. class Signer { public: @@ -31,17 +36,30 @@ class Signer { static Bitcoin::Proto::TransactionPlan plan(const Bitcoin::Proto::SigningInput& input) noexcept; /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Bitcoin::Proto::SigningInput& input) noexcept; + static Proto::SigningOutput sign(const Bitcoin::Proto::SigningInput& input, std::optional optionalExternalSigs = {}) noexcept; + + /// Collect pre-image hashes to be signed + static Bitcoin::Proto::PreSigningOutput preImageHashes(const Bitcoin::Proto::SigningInput& input) noexcept; + private: /// Private key and redeem script provider for signing. Bitcoin::Proto::SigningInput input; + /// Determine sign strategy + SigningMode signingMode = SigningMode_Normal; + + /// For SigningMode_HashOnly, collect hashes (plus corresponding publickey hashes) here + HashPubkeyList hashesForSigning; + + /// For SigningMode_External, signatures are provided here + std::optional externalSignatures; + public: /// Transaction plan. Bitcoin::TransactionPlan txPlan; /// Transaction being signed. - Transaction transaction; + Transaction _transaction; private: /// List of signed inputs. @@ -52,14 +70,16 @@ class Signer { Signer() = default; /// Initializes a transaction signer with signing input. - explicit Signer(const Bitcoin::Proto::SigningInput& input) - : input(input) { + explicit Signer(const Bitcoin::Proto::SigningInput& input, + SigningMode mode = SigningMode_Normal, + std::optional externalSignatures = {}) + : input(input), signingMode(mode), externalSignatures(externalSignatures) { if (input.has_plan()) { - txPlan = Bitcoin::TransactionPlan(input.plan()); + txPlan = Bitcoin::TransactionPlan(input.plan()); } else { - txPlan = TransactionBuilder::plan(input); + txPlan = TransactionBuilder::plan(input); } - transaction = TransactionBuilder::build(txPlan, input.to_address(), input.change_address()); + _transaction = TransactionBuilder::build(txPlan, input); } /// Signs the transaction. @@ -72,10 +92,12 @@ class Signer { /// \returns the signed transaction script. Result sign(Bitcoin::Script script, size_t index); + HashPubkeyList getHashesForSigning() const { return hashesForSigning; } + private: Result, Common::Proto::SigningError> signStep(Bitcoin::Script script, size_t index); Data createSignature(const Transaction& transaction, const Bitcoin::Script& script, - const Data& key, size_t index); + const Data& key, const Data& publicKeyHash, size_t index); /// Returns the private key for the given public key hash. Data keyForPublicKeyHash(const Data& hash) const; diff --git a/src/Decred/Transaction.cpp b/src/Decred/Transaction.cpp index 7f924d59204..d04950afbcd 100644 --- a/src/Decred/Transaction.cpp +++ b/src/Decred/Transaction.cpp @@ -1,21 +1,15 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Transaction.h" #include "../Bitcoin/SigHashType.h" #include "../BinaryCoding.h" -#include "../Hash.h" - -#include "Bitcoin/SignatureVersion.h" #include -using namespace TW; -using namespace TW::Decred; +namespace TW::Decred { namespace { // Indicates the serialization does not include any witness data. @@ -52,7 +46,7 @@ Data Transaction::computeSignatureHash(const Bitcoin::Script& prevOutScript, siz break; case TWBitcoinSigHashTypeSingle: outputsToSign.clear(); - std::copy(outputs.begin(), outputs.begin() + index + 1, outputsToSign.end()); + std::copy(outputs.begin(), outputs.begin() + index + 1, std::back_inserter(outputsToSign)); break; default: // Keep all outputs @@ -86,7 +80,7 @@ Data Transaction::computePrefixHash(const std::vector& inputsT // Commit to the relevant transaction inputs. encodeVarInt(inputsToSign.size(), preimage); - for (auto i = 0; i < inputsToSign.size(); i += 1) { + for (auto i = 0ul; i < inputsToSign.size(); i += 1) { auto& input = inputsToSign[i]; input.previousOutput.encode(preimage); @@ -100,7 +94,7 @@ Data Transaction::computePrefixHash(const std::vector& inputsT // Commit to the relevant transaction outputs. encodeVarInt(outputsToSign.size(), preimage); - for (auto i = 0; i < outputsToSign.size(); i += 1) { + for (auto i = 0ul; i < outputsToSign.size(); i += 1) { auto& output = outputsToSign[i]; auto value = output.value; auto pkScript = output.script; @@ -133,7 +127,7 @@ Data Transaction::computeWitnessHash(const std::vector& inputs // Commit to the relevant transaction inputs. encodeVarInt(inputsToSign.size(), witnessBuf); - for (auto i = 0; i < inputsToSign.size(); i += 1) { + for (auto i = 0ul; i < inputsToSign.size(); i += 1) { if (i == signIndex) { signScript.encode(witnessBuf); } else { @@ -239,3 +233,5 @@ std::size_t sigHashWitnessSize(const std::vector& inputs, signScript.bytes.size(); } } // namespace + +} // namespace TW::Decred diff --git a/src/Decred/Transaction.h b/src/Decred/Transaction.h index ba53dcba98f..eb2cb9983e2 100644 --- a/src/Decred/Transaction.h +++ b/src/Decred/Transaction.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -11,7 +9,7 @@ #include "TransactionOutput.h" #include "Bitcoin/Transaction.h" #include "Bitcoin/Script.h" -#include "../Data.h" +#include "Data.h" #include "../proto/Decred.pb.h" #include "Bitcoin/SignatureVersion.h" diff --git a/src/Decred/TransactionBuilder.h b/src/Decred/TransactionBuilder.h index ac81a3e9480..35dd958dc5b 100644 --- a/src/Decred/TransactionBuilder.h +++ b/src/Decred/TransactionBuilder.h @@ -1,12 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Transaction.h" +#include "../Bitcoin/SigningInput.h" #include "../Bitcoin/TransactionPlan.h" #include "../Bitcoin/TransactionBuilder.h" #include "../proto/Bitcoin.pb.h" @@ -25,10 +24,10 @@ struct TransactionBuilder { } /// Builds a transaction by selecting UTXOs and calculating fees. - static Transaction build(const Bitcoin::TransactionPlan& plan, const std::string& toAddress, - const std::string& changeAddress) { - auto coin = TWCoinTypeDecred; - auto lockingScriptTo = Bitcoin::Script::lockScriptForAddress(toAddress, coin); + static Transaction build(const Bitcoin::TransactionPlan& plan, const Bitcoin::SigningInput& input) { + auto coin = TWCoinTypeDecred; + + auto lockingScriptTo = Bitcoin::Script::lockScriptForAddress(input.toAddress, coin); if (lockingScriptTo.empty()) { return {}; } @@ -37,17 +36,24 @@ struct TransactionBuilder { tx.outputs.emplace_back(TransactionOutput(plan.amount, /* version: */ 0, lockingScriptTo)); if (plan.change > 0) { - auto lockingScriptChange = Bitcoin::Script::lockScriptForAddress(changeAddress, coin); + auto lockingScriptChange = Bitcoin::Script::lockScriptForAddress(input.changeAddress, coin); tx.outputs.emplace_back( TransactionOutput(plan.change, /* version: */ 0, lockingScriptChange)); } const auto emptyScript = Bitcoin::Script(); for (auto& utxo : plan.utxos) { - auto input = TransactionInput(); - input.previousOutput = utxo.outPoint; - input.sequence = utxo.outPoint.sequence; - tx.inputs.push_back(std::move(input)); + auto txInput = TransactionInput(); + txInput.previousOutput = utxo.outPoint; + txInput.sequence = utxo.outPoint.sequence; + tx.inputs.push_back(std::move(txInput)); + } + + // extra outputs + for (auto& o : input.extraOutputs) { + auto lockingScriptOther = Bitcoin::Script::lockScriptForAddress(o.first, coin); + tx.outputs.emplace_back( + TransactionOutput(o.second, /* version: */ 0, lockingScriptOther)); } return tx; diff --git a/src/Decred/TransactionInput.cpp b/src/Decred/TransactionInput.cpp index 0cf57aba6d8..8a9c0bca09e 100644 --- a/src/Decred/TransactionInput.cpp +++ b/src/Decred/TransactionInput.cpp @@ -1,14 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "TransactionInput.h" #include "../BinaryCoding.h" -using namespace TW::Decred; +namespace TW::Decred { void TransactionInput::encode(Data& data) const { previousOutput.encode(data); @@ -21,3 +19,5 @@ void TransactionInput::encodeWitness(Data& data) const { encode32LE(blockIndex, data); script.encode(data); } + +} // namespace TW::Decred diff --git a/src/Decred/TransactionInput.h b/src/Decred/TransactionInput.h index 98e1a2933ac..0b916106367 100644 --- a/src/Decred/TransactionInput.h +++ b/src/Decred/TransactionInput.h @@ -1,14 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "OutPoint.h" #include "../Bitcoin/Script.h" -#include "../Data.h" +#include "Data.h" #include #include @@ -27,13 +25,19 @@ class TransactionInput { /// before inclusion into a block. uint32_t sequence = std::numeric_limits::max(); - int64_t valueIn; - uint32_t blockHeight; + int64_t valueIn = 0; + uint32_t blockHeight = 0; uint32_t blockIndex = std::numeric_limits::max(); /// Computational Script for confirming transaction authorization. Bitcoin::Script script; + TransactionInput() = default; + /// Initializes a transaction input with a previous output, a script and a + /// sequence number. + TransactionInput(OutPoint previousOutput, Bitcoin::Script script, uint32_t sequence) + : previousOutput(std::move(previousOutput)), sequence(sequence), script(std::move(script)) {} + /// Encodes the transaction into the provided buffer. void encode(Data& data) const; diff --git a/src/Decred/TransactionOutput.cpp b/src/Decred/TransactionOutput.cpp index 9bc46efde7d..33aced75827 100644 --- a/src/Decred/TransactionOutput.cpp +++ b/src/Decred/TransactionOutput.cpp @@ -1,17 +1,17 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "TransactionOutput.h" #include "../BinaryCoding.h" -using namespace TW::Decred; +namespace TW::Decred { void TransactionOutput::encode(Data& data) const { encode64LE(value, data); encode16LE(version, data); script.encode(data); } + +} // namespace TW::Decred \ No newline at end of file diff --git a/src/Decred/TransactionOutput.h b/src/Decred/TransactionOutput.h index 7d5cf89b30f..4a2978e8c64 100644 --- a/src/Decred/TransactionOutput.h +++ b/src/Decred/TransactionOutput.h @@ -1,14 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../Bitcoin/Amount.h" #include "../Bitcoin/Script.h" -#include "../Data.h" +#include "Data.h" namespace TW::Decred { diff --git a/src/Defer.h b/src/Defer.h index e8c70316733..d3d3665f636 100644 --- a/src/Defer.h +++ b/src/Defer.h @@ -1,8 +1,8 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. + +#pragma once #ifndef defer // https://stackoverflow.com/a/42060129/411431 diff --git a/src/DerivationPath.cpp b/src/DerivationPath.cpp index aa7ac40c82d..dd54e28efbc 100644 --- a/src/DerivationPath.cpp +++ b/src/DerivationPath.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "DerivationPath.h" diff --git a/src/DerivationPath.h b/src/DerivationPath.h index be6b6602d07..cf3f1f7ba7b 100644 --- a/src/DerivationPath.h +++ b/src/DerivationPath.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -21,7 +19,8 @@ struct DerivationPathIndex { bool hardened = true; DerivationPathIndex() = default; - DerivationPathIndex(uint32_t value, bool hardened = true) : value(value), hardened(hardened) {} + DerivationPathIndex(uint32_t value, bool hardened = true) + : value(value), hardened(hardened) {} /// The derivation index. uint32_t derivationIndex() const { @@ -46,63 +45,85 @@ struct DerivationPath { std::vector indices; TWPurpose purpose() const { - if (indices.size() == 0) { return TWPurposeBIP44; } + if (indices.size() == 0) { + return TWPurposeBIP44; + } return static_cast(indices[0].value); } void setPurpose(TWPurpose v) { - if (indices.size() == 0) { return; } + if (indices.size() == 0) { + return; + } indices[0] = DerivationPathIndex(v, /* hardened: */ true); } uint32_t coin() const { - if (indices.size() <= 1) { return TWCoinTypeBitcoin; } + if (indices.size() <= 1) { + return TWCoinTypeBitcoin; + } return indices[1].value; } void setCoin(uint32_t v) { - if (indices.size() <= 1) { return; } + if (indices.size() <= 1) { + return; + } indices[1] = DerivationPathIndex(v, /* hardened: */ true); } uint32_t account() const { - if (indices.size() <= 2) { return 0; } + if (indices.size() <= 2) { + return 0; + } return indices[2].value; } void setAccount(uint32_t v) { - if (indices.size() <= 2) { return; } + if (indices.size() <= 2) { + return; + } indices[2] = DerivationPathIndex(v, /* hardened: */ true); } uint32_t change() const { - if (indices.size() <= 3) { return 0; } + if (indices.size() <= 3) { + return 0; + } return indices[3].value; } void setChange(uint32_t v) { - if (indices.size() <= 3) { return; } + if (indices.size() <= 3) { + return; + } indices[3] = DerivationPathIndex(v, /* hardened: */ false); } uint32_t address() const { - if (indices.size() <= 4) { return 0; } + if (indices.size() <= 4) { + return 0; + } return indices[4].value; } void setAddress(uint32_t v) { - if (indices.size() <= 4) { return; } + if (indices.size() <= 4) { + return; + } indices[4] = DerivationPathIndex(v, /* hardened: */ false); } DerivationPath() = default; - explicit DerivationPath(std::initializer_list l) : indices(l) {} - explicit DerivationPath(std::vector indices) : indices(std::move(indices)) {} + explicit DerivationPath(std::initializer_list l) + : indices(l) {} + explicit DerivationPath(std::vector indices) + : indices(std::move(indices)) {} /// Creates a `DerivationPath` by BIP44 components. DerivationPath(TWPurpose purpose, uint32_t coin, uint32_t account, uint32_t change, - uint32_t address) - : indices(std::vector(5)) { + uint32_t address) + : indices(std::vector(5)) { setPurpose(purpose); setCoin(coin); setAccount(account); @@ -112,7 +133,7 @@ struct DerivationPath { /// Creates a derivation path with a string description like `m/10/0/2'/3` /// - /// @throws std::invalid_argument if the string is not a valid derivation + /// \throws std::invalid_argument if the string is not a valid derivation /// path. explicit DerivationPath(const std::string& string); @@ -130,3 +151,12 @@ inline bool operator==(const DerivationPath& lhs, const DerivationPath& rhs) { } } // namespace TW + +/// Wrapper for C interface. +struct TWDerivationPath { + TW::DerivationPath impl; +}; + +struct TWDerivationPathIndex { + TW::DerivationPathIndex impl; +}; diff --git a/src/EOS/Action.cpp b/src/EOS/Action.cpp index dae38bbabf7..40fa3c7798c 100644 --- a/src/EOS/Action.cpp +++ b/src/EOS/Action.cpp @@ -1,16 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Action.h" #include "../HexCoding.h" -#include "../EOS/Serialization.h" -using namespace TW; -using namespace TW::EOS; -using json = nlohmann::json; +namespace TW::EOS { void PermissionLevel::serialize(Data& o) const { actor.serialize(o); @@ -41,11 +36,11 @@ json Action::serialize() const noexcept { return obj; } -TransferAction::TransferAction( const std::string& currency, - const std::string& from, - const std::string& to, - const Asset& asset, - const std::string& memo) { +TransferAction::TransferAction(const std::string& currency, + const std::string& from, + const std::string& to, + const Asset& asset, + const std::string& memo) { account = Name(currency); name = Name("transfer"); authorization.emplace_back(PermissionLevel(Name(from), Name("active"))); @@ -63,3 +58,5 @@ void TransferAction::setData(const std::string& from, const std::string& to, con asset.serialize(data); encodeString(memo, data); } + +} // namespace TW::EOS diff --git a/src/EOS/Action.h b/src/EOS/Action.h index cd55cb47fb5..26006cc3efa 100644 --- a/src/EOS/Action.h +++ b/src/EOS/Action.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,8 +10,6 @@ #include #include -using Data = TW::Data; - namespace TW::EOS { class PermissionLevel { diff --git a/src/EOS/Address.cpp b/src/EOS/Address.cpp index 7d8d1a6d8fa..72e6d52d2ed 100644 --- a/src/EOS/Address.cpp +++ b/src/EOS/Address.cpp @@ -1,19 +1,16 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. +#include "Address.h" #include "../Base58.h" #include "../BinaryCoding.h" -#include "Address.h" #include #include -using namespace TW; -using namespace TW::EOS; +namespace TW::EOS { bool Address::isValid(const std::string& string) { return extractKeyData(string); @@ -22,11 +19,15 @@ bool Address::isValid(const std::string& string) { /// Determines whether the given byte vector is a valid keyBuffer /// Verifies the buffer's size and it's checksum bytes bool Address::isValid(const Data& bytes, EOS::Type type) { - if (bytes.size() != KeyDataSize) return false; + if (bytes.size() != KeyDataSize) { + return false; + } // last Address::ChecksumSize bytes are a checksum uint32_t checksum = decode32LE(bytes.data() + PublicKeyDataSize); - if (createChecksum(bytes, type) != checksum) return false; + if (createChecksum(bytes, type) != checksum) { + return false; + } return true; } @@ -48,15 +49,15 @@ uint32_t Address::createChecksum(const Data& bytes, Type type) { break; case Type::ModernK1: - ripemd160_Update(&ctx, - (const uint8_t *) Modern::K1::prefix.c_str(), - static_cast(Modern::K1::prefix.size())); + ripemd160_Update(&ctx, + (const uint8_t*)Modern::K1::prefix.c_str(), + static_cast(Modern::K1::prefix.size())); break; case Type::ModernR1: - ripemd160_Update(&ctx, - (const uint8_t *) Modern::R1::prefix.c_str(), - static_cast(Modern::R1::prefix.size())); + ripemd160_Update(&ctx, + (const uint8_t*)Modern::R1::prefix.c_str(), + static_cast(Modern::R1::prefix.size())); break; } @@ -67,9 +68,9 @@ uint32_t Address::createChecksum(const Data& bytes, Type type) { } /// Extracts and verifies the key data from a base58 string. -/// If the second arg is provided, the keyData and isTestNet +/// If the second arg is provided, the keyData and isTestNet /// properties of that object are set from the extracted data. -bool Address::extractKeyData(const std::string& string, Address *address) { +bool Address::extractKeyData(const std::string& string, Address* address) { // verify if the string has one of the valid prefixes Type type; size_t prefixSize; @@ -86,7 +87,7 @@ bool Address::extractKeyData(const std::string& string, Address *address) { return false; } - const Data& decodedBytes = Base58::bitcoin.decode(string.substr(prefixSize)); + const Data& decodedBytes = Base58::decode(string.substr(prefixSize)); if (decodedBytes.size() != KeyDataSize) { return false; } @@ -111,14 +112,16 @@ Address::Address(const std::string& string) { } /// Initializes a EOS address from raw bytes -Address::Address(const Data& data, Type type) : keyData(data), type(type) { +Address::Address(const Data& data, Type type) + : keyData(data), type(type) { if (!isValid(data, type)) { throw std::invalid_argument("Invalid byte size!"); } } /// Initializes a EOS address from a public key. -Address::Address(const PublicKey& publicKey, Type type) : type(type) { +Address::Address(const PublicKey& publicKey, Type type) + : type(type) { assert(PublicKeyDataSize == TW::PublicKey::secp256k1Size); // copy the raw, compressed key data @@ -134,5 +137,7 @@ Address::Address(const PublicKey& publicKey, Type type) : type(type) { /// Returns a string representation of the EOS address. std::string Address::string() const { - return prefix() + Base58::bitcoin.encode(keyData); + return prefix() + Base58::encode(keyData); } + +} // namespace TW::EOS diff --git a/src/EOS/Address.h b/src/EOS/Address.h index f0484c046c1..9edbc41c816 100644 --- a/src/EOS/Address.h +++ b/src/EOS/Address.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include "Prefixes.h" diff --git a/src/EOS/Asset.cpp b/src/EOS/Asset.cpp index 17d03be7976..7dbb5a87d11 100644 --- a/src/EOS/Asset.cpp +++ b/src/EOS/Asset.cpp @@ -1,16 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Asset.h" +#include "algorithm/string.hpp" -#include -#include #include -using namespace TW::EOS; +namespace TW::EOS { static const int64_t Precision = 1000; static const uint8_t MaxDecimals = 18; @@ -21,12 +18,12 @@ Asset::Asset(int64_t amount, uint8_t decimals, const std::string& symbol) { } this->symbol |= decimals; - if (symbol.size() < 1 || symbol.size() > 7) { + if (symbol.empty() || symbol.size() > 7) { throw std::invalid_argument("Symbol size invalid!"); } - for (int i = 0; i < symbol.size(); i++) { - uint64_t c = symbol[i]; + for (std::size_t i = 0; i < symbol.size(); i++) { + uint64_t c = (unsigned char) symbol[i]; if (c < 'A' || c > 'Z') { throw std::invalid_argument("Invalid symbol " + symbol + ".\n Symbol can only have upper case alphabets!"); } @@ -40,7 +37,7 @@ Asset::Asset(int64_t amount, uint8_t decimals, const std::string& symbol) { Asset Asset::fromString(std::string assetString) { using namespace std; - boost::algorithm::trim(assetString); + trim(assetString); // Find space in order to split amount and symbol auto spacePosition = assetString.find(' '); @@ -48,7 +45,7 @@ Asset Asset::fromString(std::string assetString) { throw std::invalid_argument("Asset's amount and symbol should be separated with space"); } - auto symbolString = boost::algorithm::trim_copy(assetString.substr(spacePosition + 1)); + auto symbolString = trim_copy(assetString.substr(spacePosition + 1)); auto amountString = assetString.substr(0, spacePosition); // Ensure that if decimal point is used (.), decimal fraction is specified @@ -61,19 +58,19 @@ Asset Asset::fromString(std::string assetString) { if (dotPosition != string::npos) { decimals = static_cast(amountString.size() - dotPosition - 1); } - - int64_t precision = static_cast(pow(10, static_cast(decimals))); + + auto precision = static_cast(pow(10, static_cast(decimals))); // Parse amount int64_t intPart, fractPart = 0; if (dotPosition != string::npos) { - intPart = boost::lexical_cast(amountString.data(), dotPosition); - fractPart = boost::lexical_cast(amountString.data() + dotPosition + 1, decimals); + intPart = std::stoll(amountString.substr(0, dotPosition)); + fractPart = std::stoll(amountString.substr(dotPosition + 1, decimals)); if (amountString[0] == '-') { fractPart *= -1; } } else { - intPart = boost::lexical_cast(amountString); + intPart = std::stoll(amountString); } int64_t amount = intPart; @@ -110,10 +107,10 @@ std::string Asset::string() const { auto decimals = getDecimals(); - int charsWritten = snprintf(buffer, maxBufferSize, "%.*f %s", - decimals, - static_cast(amount) / Precision, - getSymbol().c_str()); + int charsWritten = snprintf(buffer, maxBufferSize, "%.*f %s", + decimals, + static_cast(amount) / Precision, + getSymbol().c_str()); if (charsWritten < 0 || charsWritten > maxBufferSize) { throw std::runtime_error("Failed to create string representation of asset!"); @@ -133,3 +130,5 @@ std::string Asset::getSymbol() const noexcept { return str; } + +} // namespace TW::EOS diff --git a/src/EOS/Asset.h b/src/EOS/Asset.h index 58e611a1469..5f91f3e6e23 100644 --- a/src/EOS/Asset.h +++ b/src/EOS/Asset.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/EOS/Entry.cpp b/src/EOS/Entry.cpp index 37de7a06c4c..23611107634 100644 --- a/src/EOS/Entry.cpp +++ b/src/EOS/Entry.cpp @@ -1,27 +1,54 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" +#include "../proto/EOS.pb.h" +#include #include "Address.h" #include "Signer.h" -using namespace TW::EOS; -using namespace std; +namespace TW::EOS { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const TW::Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto chainId = Data(input.chain_id().begin(), input.chain_id().end()); + auto unsignedTxBytes = Signer(chainId).buildUnsignedTx(input); + auto imageHash = Hash::sha256(unsignedTxBytes); + output.set_data(unsignedTxBytes.data(), unsignedTxBytes.size()); + output.set_data_hash(imageHash.data(), imageHash.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, [[maybe_unused]] const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() != 1) { + output.set_error(Common::Proto::Error_signatures_count); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_signatures_count)); + return; + } + + auto chainId = Data(input.chain_id().begin(), input.chain_id().end()); + auto signedTx = Signer(chainId).buildSignedTx(input, signatures[0]); + output.set_json_encoded(signedTx.data(), signedTx.size()); + }); +} + +} // namespace TW::EOS diff --git a/src/EOS/Entry.h b/src/EOS/Entry.h index 586ec9526a3..dedf0d4c76d 100644 --- a/src/EOS/Entry.h +++ b/src/EOS/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,11 +10,14 @@ namespace TW::EOS { /// Entry point for implementation of EOS coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::EOS diff --git a/src/EOS/KeyType.h b/src/EOS/KeyType.h index 9b7c259e545..c21d82eb00b 100644 --- a/src/EOS/KeyType.h +++ b/src/EOS/KeyType.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/EOS/Name.cpp b/src/EOS/Name.cpp index 07ec3a131e9..6ce388d800f 100644 --- a/src/EOS/Name.cpp +++ b/src/EOS/Name.cpp @@ -1,24 +1,21 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "../BinaryCoding.h" #include "Name.h" +#include "algorithm/string.hpp" -#include #include -using namespace TW; -using namespace TW::EOS; +namespace TW::EOS { Name::Name(const std::string& str) { if (str.size() > 13) { throw std::invalid_argument(str + ": size too long!"); } - int i = 0; + std::size_t i = 0; while (i < std::min(size_t(12), str.size())) { value |= (toSymbol(str[i]) & 0x1f) << (64 - (5 * (i + 1))); i++; @@ -28,7 +25,7 @@ Name::Name(const std::string& str) { value |= (toSymbol(str[i]) & 0x0f); } -uint64_t Name::toSymbol(char c) const noexcept { +uint64_t Name::toSymbol(char c) noexcept { if (c >= 'a' && c <= 'z') return c - 'a' + 6; @@ -41,22 +38,24 @@ uint64_t Name::toSymbol(char c) const noexcept { std::string Name::string() const noexcept { static const char* charMap = ".12345abcdefghijklmnopqrstuvwxyz"; - std::string str(13,'.'); + std::string str(13, '.'); uint64_t tmp = value; str[12] = charMap[tmp & 0x0f]; tmp >>= 4; - for( uint32_t i = 1; i <= 12; ++i ) { + for (uint32_t i = 1; i <= 12; ++i) { char c = charMap[tmp & 0x1f]; - str[12-i] = c; + str[12 - i] = c; tmp >>= 5; } - boost::algorithm::trim_right_if( str, []( char c ){ return c == '.'; } ); + trim_right(str, "."); return str; } -void Name::serialize(Data& o) const noexcept { +void Name::serialize(Data& o) const noexcept { encode64LE(value, o); -} \ No newline at end of file +} + +} // namespace TW::EOS diff --git a/src/EOS/Name.h b/src/EOS/Name.h index 3d3d3721a47..5280975535f 100644 --- a/src/EOS/Name.h +++ b/src/EOS/Name.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" namespace TW::EOS { @@ -14,9 +12,9 @@ class Name { public: uint64_t value = 0; - Name() { } + Name() = default; Name(const std::string& str); - uint64_t toSymbol(char c) const noexcept; + static uint64_t toSymbol(char c) noexcept; std::string string() const noexcept; void serialize(TW::Data& o) const noexcept; diff --git a/src/EOS/PackedTransaction.cpp b/src/EOS/PackedTransaction.cpp index 56708474227..4c884f8c815 100644 --- a/src/EOS/PackedTransaction.cpp +++ b/src/EOS/PackedTransaction.cpp @@ -1,22 +1,19 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "PackedTransaction.h" #include "../HexCoding.h" -using namespace TW; -using namespace TW::EOS; -using json = nlohmann::json; +namespace TW::EOS { -PackedTransaction::PackedTransaction(const Transaction& transaction, CompressionType type) noexcept : compression(type) { +PackedTransaction::PackedTransaction(const Transaction& transaction, CompressionType type) noexcept + : compression(type) { transaction.serialize(packedTrx); const Data& cfd = transaction.contextFreeData; - if (cfd.size()) { + if (!cfd.empty()) { packedCFD.push_back(1); encodeVarInt64(cfd.size(), packedCFD); append(packedCFD, cfd); @@ -49,4 +46,6 @@ json PackedTransaction::serialize() const noexcept { obj["packed_trx"] = hex(packedTrx); return obj; -} \ No newline at end of file +} + +} // namespace TW::EOS diff --git a/src/EOS/PackedTransaction.h b/src/EOS/PackedTransaction.h index f9d6ca337b7..e88a5a97703 100644 --- a/src/EOS/PackedTransaction.h +++ b/src/EOS/PackedTransaction.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/EOS/Prefixes.h b/src/EOS/Prefixes.h index b6594c8a147..d3e2335d7f5 100644 --- a/src/EOS/Prefixes.h +++ b/src/EOS/Prefixes.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/EOS/Serialization.h b/src/EOS/Serialization.h index af6f3820fa4..f6aa5c83680 100644 --- a/src/EOS/Serialization.h +++ b/src/EOS/Serialization.h @@ -2,7 +2,7 @@ #include -#include "../Data.h" +#include "Data.h" #include "../BinaryCoding.h" #include diff --git a/src/EOS/Signer.cpp b/src/EOS/Signer.cpp index c268761018b..dc8019fbeed 100644 --- a/src/EOS/Signer.cpp +++ b/src/EOS/Signer.cpp @@ -1,38 +1,21 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "Asset.h" #include "PackedTransaction.h" -#include "../proto/Common.pb.h" -#include "../HexCoding.h" #include -#include -#include -using namespace TW; -using namespace TW::EOS; +namespace TW::EOS { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Proto::SigningOutput output; try { - // create an asset object - auto assetData = input.asset(); - auto asset = Asset(assetData.amount(), static_cast(assetData.decimals()), - assetData.symbol()); - - // create a transfer action - auto action = TransferAction(input.currency(), input.sender(), input.recipient(), asset, - input.memo()); - - // create a Transaction and add the transfer action - auto tx = Transaction(Data(input.reference_block_id().begin(), input.reference_block_id().end()), - input.reference_block_time()); - tx.actions.push_back(action); + auto chainId = Data(input.chain_id().begin(), input.chain_id().end()); + auto signer = Signer(chainId); + auto tx = signer.buildTx(input); // get key type EOS::Type type = Type::Legacy; @@ -54,8 +37,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { // sign the transaction with a Signer auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto chainId = Data(input.chain_id().begin(), input.chain_id().end()); - Signer(chainId).sign(key, type, tx); + signer.sign(key, type, tx); // Pack the transaction and add the json encoding to Signing outputput PackedTransaction ptx{tx, CompressionType::None}; @@ -89,22 +71,83 @@ void Signer::sign(const PrivateKey& privateKey, Type type, Transaction& transact } TW::Data Signer::hash(const Transaction& transaction) const noexcept { + return Hash::sha256(serializeTx(transaction)); +} + +TW::Data Signer::serializeTx(const Transaction& transaction) const noexcept { Data hashInput(chainID); transaction.serialize(hashInput); Data cfdHash(Hash::sha256Size); // default value for empty cfd - if (transaction.contextFreeData.size()) { + if (!transaction.contextFreeData.empty()) { cfdHash = Hash::sha256(transaction.contextFreeData); } append(hashInput, cfdHash); - return Hash::sha256(hashInput); + return hashInput; } // canonical check for EOS -int Signer::isCanonical(uint8_t by, uint8_t sig[64]) { +int Signer::isCanonical([[maybe_unused]] uint8_t by, uint8_t sig[64]) { + // clang-format off return !(sig[0] & 0x80) && !(sig[0] == 0 && !(sig[1] & 0x80)) && !(sig[32] & 0x80) && !(sig[32] == 0 && !(sig[33] & 0x80)); + // clang-format on +} + +Transaction Signer::buildTx(const Proto::SigningInput& input) const { + // create an asset object + auto assetData = input.asset(); + auto asset = + Asset(assetData.amount(), static_cast(assetData.decimals()), assetData.symbol()); + + // create a transfer action + auto action = + TransferAction(input.currency(), input.sender(), input.recipient(), asset, input.memo()); + + // create a Transaction and add the transfer action + auto tx = + Transaction(Data(input.reference_block_id().begin(), input.reference_block_id().end()), + input.reference_block_time()); + if (input.expiration() > 0) { + tx.expiration = input.expiration(); + } + tx.actions.push_back(action); + return tx; +} + +Data Signer::buildUnsignedTx(const Proto::SigningInput& input) noexcept { + auto tx = buildTx(input); + return serializeTx(tx); +} + +std::string Signer::buildSignedTx(const Proto::SigningInput& input, const Data& signature) noexcept { + auto tx = buildTx(input); + + // get key type + EOS::Type type = Type::Legacy; + switch (input.private_key_type()) { + case Proto::KeyType::LEGACY: + type = Type::Legacy; + break; + + case Proto::KeyType::MODERNK1: + type = Type::ModernK1; + break; + + case Proto::KeyType::MODERNR1: + type = Type::ModernR1; + break; + default: + break; + } + + tx.signatures.emplace_back(Signature(signature, type)); + PackedTransaction ptx{tx, CompressionType::None}; + auto stx = ptx.serialize().dump(); + return stx; } + +} // namespace TW::EOS diff --git a/src/EOS/Signer.h b/src/EOS/Signer.h index 1aee6d97364..0eef641f9a2 100644 --- a/src/EOS/Signer.h +++ b/src/EOS/Signer.h @@ -1,14 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Prefixes.h" #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/EOS.pb.h" @@ -35,7 +33,14 @@ class Signer { /// Computes the transaction hash. Data hash(const Transaction& transaction) const noexcept; + /// Serialize the transaction. + Data serializeTx(const Transaction& transaction) const noexcept; + static int isCanonical(uint8_t by, uint8_t sig[64]); + + Transaction buildTx(const Proto::SigningInput& input) const; + Data buildUnsignedTx(const Proto::SigningInput& input) noexcept; + std::string buildSignedTx(const Proto::SigningInput& input, const Data& signature) noexcept; }; } // namespace TW::EOS diff --git a/src/EOS/Transaction.cpp b/src/EOS/Transaction.cpp index d30d6b670ae..f5ae6608f46 100644 --- a/src/EOS/Transaction.cpp +++ b/src/EOS/Transaction.cpp @@ -1,11 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "../Base58.h" -#include "../Hash.h" #include "../HexCoding.h" #include "Transaction.h" @@ -15,11 +12,10 @@ #include #include -using namespace TW; -using namespace TW::EOS; -using json = nlohmann::json; +namespace TW::EOS { -Signature::Signature(const Data& sig, Type type) : data(sig), type(type) { +Signature::Signature(const Data& sig, Type type) + : data(sig), type(type) { if (sig.size() != DataSize) { throw std::invalid_argument("Invalid signature size!"); } @@ -55,11 +51,11 @@ std::string Signature::string() const noexcept { // drop the subPrefix and append the checksum to the bufer buffer.resize(DataSize); - for(size_t i = 0; i < ChecksumSize; i++) { + for (size_t i = 0; i < ChecksumSize; i++) { buffer.push_back(hash[i]); } - return prefix + TW::Base58::bitcoin.encode(buffer); + return prefix + TW::Base58::encode(buffer); } void Extension::serialize(Data& os) const noexcept { @@ -86,7 +82,7 @@ void Transaction::setReferenceBlock(const Data& refBlockId) { refBlockPrefix = decode32LE(refBlockId.data() + 8); } -void Transaction::serialize(Data& os) const noexcept{ +void Transaction::serialize(Data& os) const noexcept { encode32LE(expiration, os); encode16LE(refBlockNumber, os); @@ -105,10 +101,10 @@ std::string Transaction::formatDate(int32_t date) { constexpr size_t DateSize = 19; constexpr size_t BufferSize = DateSize + 1; char formattedDate[BufferSize]; - time_t time = static_cast(date); + auto time = static_cast(date); const size_t len = strftime(formattedDate, BufferSize, "%FT%T", std::gmtime(&time)); assert(len == DateSize); - return std::string(formattedDate, len); + return {formattedDate, len}; } json Transaction::serialize() const { @@ -136,3 +132,5 @@ json Transaction::serialize() const { return obj; } + +} // namespace TW::EOS diff --git a/src/EOS/Transaction.h b/src/EOS/Transaction.h index 1d31ee9da36..420f6e7bf73 100644 --- a/src/EOS/Transaction.h +++ b/src/EOS/Transaction.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "Action.h" #include "Prefixes.h" @@ -67,7 +65,7 @@ class Transaction { void setReferenceBlock(const Data& referenceBlockId); - static const int32_t ExpirySeconds = 30; + static const int32_t ExpirySeconds = 3600; /// Get formatted date static std::string formatDate(int32_t date); }; diff --git a/src/Elrond/Address.cpp b/src/Elrond/Address.cpp deleted file mode 100644 index 1af84ace200..00000000000 --- a/src/Elrond/Address.cpp +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include - -#include "Address.h" - -using namespace TW::Elrond; - -const std::string Address::hrp = HRP_ELROND; - -bool Address::isValid(const std::string& string) { - return Bech32Address::isValid(string, hrp); -} diff --git a/src/Elrond/Address.h b/src/Elrond/Address.h deleted file mode 100644 index 13e722ab193..00000000000 --- a/src/Elrond/Address.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "../Data.h" -#include "../PublicKey.h" -#include "../Bech32Address.h" - -#include - -namespace TW::Elrond { - -class Address : public Bech32Address { - public: - /// The human-readable part of the address, as defined in "registry.json" - static const std::string hrp; // HRP_ELROND - - /// Determines whether a string makes a valid address. - static bool isValid(const std::string& string); - - Address() : Bech32Address(hrp) {} - - /// Initializes an address with a key hash. - Address(Data keyHash) : Bech32Address(hrp, keyHash) {} - - /// Initializes an address with a public key. - Address(const PublicKey& publicKey) : Bech32Address(hrp, publicKey.bytes) {} - - static bool decode(const std::string& addr, Address& obj_out) { - return Bech32Address::decode(addr, obj_out, hrp); - } -}; - -} // namespace TW::Elrond diff --git a/src/Elrond/Codec.cpp b/src/Elrond/Codec.cpp deleted file mode 100644 index ccf524c46a4..00000000000 --- a/src/Elrond/Codec.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Codec.h" - -#include "HexCoding.h" -#include "uint256.h" - -using namespace TW; -using namespace TW::Elrond; - -std::string Codec::encodeString(const std::string& value) { - std::string encoded = hex(TW::data(value)); - return encoded; -} - -std::string Codec::encodeUint64(uint64_t value) { - std::string encoded = hex(store(uint256_t(value))); - return encoded; -} - -std::string Codec::encodeBigInt(const std::string& value) { - return encodeBigInt(uint256_t(value)); -} - -// For reference, see https://docs.elrond.com/developers/developer-reference/elrond-serialization-format/#arbitrary-width-big-numbers. -std::string Codec::encodeBigInt(uint256_t value) { - std::string encoded = hex(store(value)); - return encoded; -} - -std::string Codec::encodeAddress(const std::string& bech32Address) { - Address address; - Address::decode(bech32Address, address); - return encodeAddress(address); -} - -std::string Codec::encodeAddress(const Address& address) { - std::string encoded = hex(address.getKeyHash()); - return encoded; -} diff --git a/src/Elrond/Codec.h b/src/Elrond/Codec.h deleted file mode 100644 index b2ffd73405b..00000000000 --- a/src/Elrond/Codec.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Address.h" -#include "uint256.h" - -namespace TW::Elrond { - -/// A stripped-down variant of the Elrond codec. -/// For reference, see: -/// - https://docs.elrond.com/developers/developer-reference/elrond-serialization-format -/// - https://github.com/ElrondNetwork/elrond-sdk-erdjs/tree/main/src/smartcontracts/codec -/// - https://github.com/ElrondNetwork/elrond-wasm-rs/tree/master/elrond-codec -class Codec { -public: - static std::string encodeString(const std::string& value); - static std::string encodeUint64(uint64_t value); - static std::string encodeBigInt(const std::string& value); - static std::string encodeBigInt(TW::uint256_t value); - static std::string encodeAddress(const std::string& bech32Address); - static std::string encodeAddress(const Address& address); -}; - -} // namespace diff --git a/src/Elrond/Entry.cpp b/src/Elrond/Entry.cpp deleted file mode 100644 index e97e307d91d..00000000000 --- a/src/Elrond/Entry.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Entry.h" - -#include "Address.h" -#include "Signer.h" - -using namespace TW::Elrond; -using namespace TW; -using namespace std; - -// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. - -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { - return Address::isValid(address); -} - -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { - return Address(publicKey).string(); -} - -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { - Address addr; - if (!Elrond::Address::decode(address, addr)) { - return Data(); - } - return addr.getKeyHash(); -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); -} - -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { - return Signer::signJSON(json, key); -} diff --git a/src/Elrond/Entry.h b/src/Elrond/Entry.h deleted file mode 100644 index 8723a972bcb..00000000000 --- a/src/Elrond/Entry.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "../CoinEntry.h" - -namespace TW::Elrond { - -/// Entry point for implementation of Elrond coin. -/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { -public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; -}; - -} // namespace TW::Elrond diff --git a/src/Elrond/GasEstimator.cpp b/src/Elrond/GasEstimator.cpp deleted file mode 100644 index 2445b64dd45..00000000000 --- a/src/Elrond/GasEstimator.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "GasEstimator.h" - -#include "../proto/Elrond.pb.h" - -using namespace TW; -using namespace TW::Elrond; - -// Additional gas to account for eventual increases in gas requirements (thus avoid breaking changes in clients of TW-core). -const uint64_t ADDITIONAL_GAS_FOR_ESDT_TRANSFER = 100000; - -// Additional gas to account for extra blockchain operations (e.g. data movement (between accounts) for NFTs), -// and for eventual increases in gas requirements (thus avoid breaking changes in clients of TW-core). -const uint64_t ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER = 500000; - -GasEstimator::GasEstimator(const NetworkConfig& networkConfig) { - this->networkConfig = networkConfig; -} - -uint64_t GasEstimator::forEGLDTransfer(size_t dataLength) const { - uint64_t gasLimit = - this->networkConfig.getMinGasLimit() + - this->networkConfig.getGasPerDataByte() * dataLength; - - return gasLimit; -} - -uint64_t GasEstimator::forESDTTransfer(size_t dataLength) const { - uint64_t gasLimit = - this->networkConfig.getMinGasLimit() + - this->networkConfig.getGasCostESDTTransfer() + - this->networkConfig.getGasPerDataByte() * dataLength + - ADDITIONAL_GAS_FOR_ESDT_TRANSFER; - - return gasLimit; -} - -uint64_t GasEstimator::forESDTNFTTransfer(size_t dataLength) const { - uint64_t gasLimit = - this->networkConfig.getMinGasLimit() + - this->networkConfig.getGasCostESDTNFTTransfer() + - this->networkConfig.getGasPerDataByte() * dataLength + - ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER; - - return gasLimit; -} diff --git a/src/Elrond/GasEstimator.h b/src/Elrond/GasEstimator.h deleted file mode 100644 index 4c14ed434d6..00000000000 --- a/src/Elrond/GasEstimator.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include -#include "NetworkConfig.h" - -namespace TW::Elrond { - -class GasEstimator { - NetworkConfig networkConfig; -public: - GasEstimator(const NetworkConfig& networkConfig); - - uint64_t forEGLDTransfer(size_t dataLength) const; - uint64_t forESDTTransfer(size_t dataLength) const; - uint64_t forESDTNFTTransfer(size_t dataLength) const; -}; - -} // namespace diff --git a/src/Elrond/NetworkConfig.cpp b/src/Elrond/NetworkConfig.cpp deleted file mode 100644 index 98f5f6295a9..00000000000 --- a/src/Elrond/NetworkConfig.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "NetworkConfig.h" - -#include - -using namespace TW; -using namespace TW::Elrond; -using namespace std::chrono; - -NetworkConfig::NetworkConfig() : - chainId("1") /* Mainnet */ { -} - -const std::string& NetworkConfig::getChainId() const { - return this->chainId; -} - -void NetworkConfig::setChainId(const std::string& value) { - this->chainId = value; -} - -uint32_t NetworkConfig::getGasPerDataByte() const { - return this->gasPerDataByte; -} - -void NetworkConfig::setGasPerDataByte(uint32_t value) { - this->gasPerDataByte = value; -} - -uint32_t NetworkConfig::getMinGasLimit() const { - return this->minGasLimit; -} - -void NetworkConfig::setMinGasLimit(uint32_t value) { - this->minGasLimit = value; -} - -uint64_t NetworkConfig::getMinGasPrice() const { - return this->minGasPrice; -} - -void NetworkConfig::setMinGasPrice(uint64_t value) { - this->minGasPrice = value; -} - -uint32_t NetworkConfig::getGasCostESDTTransfer() const { - return this->gasCostESDTTransfer; -} - -void NetworkConfig::setGasCostESDTTransfer(uint32_t value) { - this->gasCostESDTTransfer = value; -} - -uint32_t NetworkConfig::getGasCostESDTNFTTransfer() const { - return this->gasCostESDTNFTTransfer; -} - -void NetworkConfig::setGasCostESDTNFTTransfer(uint32_t value) { - this->gasCostESDTNFTTransfer = value; -} - -NetworkConfig NetworkConfig::GetDefault() { - const uint64_t timestamp = duration_cast(system_clock::now().time_since_epoch()).count(); - return GetByTimestamp(timestamp); -} - -NetworkConfig NetworkConfig::GetByTimestamp(uint64_t timestamp) { - NetworkConfig networkConfig; - - // Mainnet values at the time of defining the "NetworkConfig" component (December 2021). - if (timestamp > 0) { - networkConfig.setGasPerDataByte(1500); - networkConfig.setMinGasLimit(50000); - networkConfig.setMinGasPrice(1000000000); - networkConfig.setGasCostESDTTransfer(200000); - networkConfig.setGasCostESDTNFTTransfer(200000); - } - - return networkConfig; -} diff --git a/src/Elrond/NetworkConfig.h b/src/Elrond/NetworkConfig.h deleted file mode 100644 index 79b5ebf61d0..00000000000 --- a/src/Elrond/NetworkConfig.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include - -namespace TW::Elrond { - -/// A "NetworkConfig" object holds the network parameters relevant to creating transactions (e.g. minimum gas limit, minimum gas price). -class NetworkConfig { - /// The following fields can (should) be fetched from https://api.elrond.com/network/config. - /// However, a "NetworkConfig" object is initialized with proper default values for Elrond Mainnet (as of December 2021). - std::string chainId; - uint32_t gasPerDataByte; - uint32_t minGasLimit; - uint64_t minGasPrice; - - /// GasSchedule entries of interest (only one at this moment), according to: https://github.com/ElrondNetwork/elrond-config-mainnet/blob/master/gasSchedules. - /// Here, for the sake of simplicity, we define the gas costs of interest directly in the class "NetworkConfig" - /// (that is, without defining extra nested structures such as "GasSchedule" and "BuiltInCosts"). - uint32_t gasCostESDTTransfer; - uint32_t gasCostESDTNFTTransfer; -public: - NetworkConfig(); - - const std::string& getChainId() const; - void setChainId(const std::string& value); - - uint32_t getGasPerDataByte() const; - void setGasPerDataByte(uint32_t value); - - uint32_t getMinGasLimit() const; - void setMinGasLimit(uint32_t value); - - uint64_t getMinGasPrice() const; - void setMinGasPrice(uint64_t value); - - uint32_t getGasCostESDTTransfer() const; - void setGasCostESDTTransfer(uint32_t value); - - uint32_t getGasCostESDTNFTTransfer() const; - void setGasCostESDTNFTTransfer(uint32_t value); - - static NetworkConfig GetDefault(); - - /// Useful to implement upwards-compatible changes of the network configuration (a TWCore client can receive planned configuration updates, in advance). - static NetworkConfig GetByTimestamp(uint64_t timestamp); -}; - -} // namespace diff --git a/src/Elrond/Serialization.cpp b/src/Elrond/Serialization.cpp deleted file mode 100644 index 192cf935d66..00000000000 --- a/src/Elrond/Serialization.cpp +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Serialization.h" - -#include "Address.h" -#include "Base64.h" - -using namespace TW; - -std::map fields_order { - {"nonce", 1}, - {"value", 2}, - {"receiver", 3}, - {"sender", 4}, - {"senderUsername", 5}, - {"receiverUsername", 6}, - {"gasPrice", 7}, - {"gasLimit", 8}, - {"data", 9}, - {"chainID", 10}, - {"version", 11}, - {"options", 12}, - {"signature", 13} -}; - -struct FieldsSorter { - bool operator() (const string& lhs, const string& rhs) const { - return fields_order[lhs] < fields_order[rhs]; - } -}; - -template -using sorted_map = std::map; -using sorted_json = nlohmann::basic_json; - -sorted_json preparePayload(const Elrond::Transaction& transaction) { - sorted_json payload { - {"nonce", json(transaction.nonce)}, - {"value", json(transaction.value)}, - {"receiver", json(transaction.receiver)}, - {"sender", json(transaction.sender)}, - {"gasPrice", json(transaction.gasPrice)}, - {"gasLimit", json(transaction.gasLimit)}, - }; - - if (!transaction.senderUsername.empty()) { - payload["senderUsername"] = json(Base64::encode(data(transaction.senderUsername))); - } - - if (!transaction.receiverUsername.empty()) { - payload["receiverUsername"] = json(Base64::encode(data(transaction.receiverUsername))); - } - - if (!transaction.data.empty()) { - payload["data"] = json(Base64::encode(data(transaction.data))); - } - - payload["chainID"] = json(transaction.chainID); - payload["version"] = json(transaction.version); - - if (transaction.options != 0) { - payload["options"] = json(transaction.options); - } - - return payload; -} - -string Elrond::serializeTransaction(const Elrond::Transaction& transaction) { - sorted_json payload = preparePayload(transaction); - return payload.dump(); -} - -string Elrond::serializeSignedTransaction(const Elrond::Transaction& transaction, string signature) { - sorted_json payload = preparePayload(transaction); - payload["signature"] = json(signature); - return payload.dump(); -} diff --git a/src/Elrond/Serialization.h b/src/Elrond/Serialization.h deleted file mode 100644 index dd0d57cb894..00000000000 --- a/src/Elrond/Serialization.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Data.h" -#include "Transaction.h" -#include - -using string = std::string; -using json = nlohmann::json; - -namespace TW::Elrond { - -string serializeTransaction(const Transaction& transaction); -string serializeSignedTransaction(const Transaction& transaction, string encodedSignature); - -} // namespace diff --git a/src/Elrond/Signer.cpp b/src/Elrond/Signer.cpp deleted file mode 100644 index dcbd5d0144b..00000000000 --- a/src/Elrond/Signer.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Signer.h" -#include "Address.h" -#include "Serialization.h" -#include "TransactionFactory.h" -#include "../PublicKey.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Elrond; -using namespace TW::Elrond::Proto; - -SigningOutput Signer::sign(const SigningInput &input) noexcept { - TransactionFactory factory; - - auto transaction = factory.create(input); - auto privateKey = PrivateKey(input.private_key()); - auto signableAsString = serializeTransaction(transaction); - auto signableAsData = TW::data(signableAsString); - auto signature = privateKey.sign(signableAsData, TWCurveED25519); - auto encodedSignature = hex(signature); - auto encoded = serializeSignedTransaction(transaction, encodedSignature); - - auto protoOutput = Proto::SigningOutput(); - protoOutput.set_signature(encodedSignature); - protoOutput.set_encoded(encoded); - return protoOutput; -} - -std::string Signer::signJSON(const std::string& json, const Data& key) { - auto input = Proto::SigningInput(); - google::protobuf::util::JsonStringToMessage(json, &input); - input.set_private_key(key.data(), key.size()); - auto output = sign(input); - return output.encoded(); -} diff --git a/src/Elrond/Signer.h b/src/Elrond/Signer.h deleted file mode 100644 index 046f5e52d67..00000000000 --- a/src/Elrond/Signer.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "../Data.h" -#include "../PrivateKey.h" -#include "../proto/Elrond.pb.h" - -namespace TW::Elrond { - -/// Helper class that performs Elrond transaction signing. -class Signer { -public: - /// Hide default constructor - Signer() = delete; - - /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; - - /// Signs a json Proto::SigningInput with private key - static std::string signJSON(const std::string& json, const Data& key); -}; - -} // namespace TW::Elrond diff --git a/src/Elrond/Transaction.cpp b/src/Elrond/Transaction.cpp deleted file mode 100644 index 9345d50e143..00000000000 --- a/src/Elrond/Transaction.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Transaction.h" - -using namespace TW; -using namespace TW::Elrond; - -Transaction::Transaction() : - nonce(0), - sender(""), - senderUsername(""), - receiver(""), - receiverUsername(""), - value("0"), - data(""), - gasPrice(0), - gasLimit(0), - chainID(""), - version(0), - options(0) { -} - diff --git a/src/Elrond/Transaction.h b/src/Elrond/Transaction.h deleted file mode 100644 index a98e7435360..00000000000 --- a/src/Elrond/Transaction.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include - -namespace TW::Elrond { - -class Transaction { -public: - uint64_t nonce; - std::string sender; - std::string senderUsername; - std::string receiver; - std::string receiverUsername; - std::string value; - std::string data; - uint64_t gasPrice; - uint64_t gasLimit; - std::string chainID; - uint32_t version; - uint32_t options; - - Transaction(); -}; - -} // namespace diff --git a/src/Elrond/TransactionFactory.cpp b/src/Elrond/TransactionFactory.cpp deleted file mode 100644 index d454bec8e1e..00000000000 --- a/src/Elrond/TransactionFactory.cpp +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "TransactionFactory.h" - -#include "Codec.h" - -using namespace TW; -using namespace TW::Elrond; - -const int TX_VERSION = 1; - -TransactionFactory::TransactionFactory() : - TransactionFactory(NetworkConfig::GetDefault()) { -} - -TransactionFactory::TransactionFactory(const NetworkConfig& networkConfig) : - networkConfig(networkConfig), - gasEstimator(networkConfig) { -} - -Transaction TransactionFactory::create(const Proto::SigningInput &input) { - if (input.has_egld_transfer()) { - return fromEGLDTransfer(input); - } else if (input.has_esdt_transfer()) { - return fromESDTTransfer(input); - } else if (input.has_esdtnft_transfer()) { - return fromESDTNFTTransfer(input); - } else { - return fromGenericAction(input); - } -} - -/// Copies the input fields into a transaction object, without any other logic. -Transaction TransactionFactory::fromGenericAction(const Proto::SigningInput &input) { - auto action = input.generic_action(); - - Transaction transaction; - transaction.nonce = action.accounts().sender_nonce(); - transaction.sender = action.accounts().sender(); - transaction.senderUsername = action.accounts().sender_username(); - transaction.receiver = action.accounts().receiver(); - transaction.receiverUsername = action.accounts().receiver_username(); - transaction.value = action.value(); - transaction.data = action.data(); - transaction.gasLimit = input.gas_limit(); - transaction.gasPrice = input.gas_price(); - transaction.chainID = input.chain_id(); - transaction.version = action.version(); - transaction.options = action.options(); - - return transaction; -} - -Transaction TransactionFactory::fromEGLDTransfer(const Proto::SigningInput &input) { - auto transfer = input.egld_transfer(); - - uint64_t estimatedGasLimit = this->gasEstimator.forEGLDTransfer(0); - - Transaction transaction; - transaction.nonce = transfer.accounts().sender_nonce(); - transaction.sender = transfer.accounts().sender(); - transaction.senderUsername = transfer.accounts().sender_username(); - transaction.receiver = transfer.accounts().receiver(); - transaction.receiverUsername = transfer.accounts().receiver_username(); - transaction.value = transfer.amount(); - transaction.gasLimit = coalesceGasLimit(input.gas_limit(), estimatedGasLimit); - transaction.gasPrice = coalesceGasPrice(input.gas_price()); - transaction.chainID = coalesceChainId(input.chain_id()); - transaction.version = TX_VERSION; - - return transaction; -} - -Transaction TransactionFactory::fromESDTTransfer(const Proto::SigningInput &input) { - auto transfer = input.esdt_transfer(); - - std::string encodedTokenIdentifier = Codec::encodeString(transfer.token_identifier()); - std::string encodedAmount = Codec::encodeBigInt(transfer.amount()); - std::string data = prepareFunctionCall("ESDTTransfer", { encodedTokenIdentifier, encodedAmount }); - uint64_t estimatedGasLimit = this->gasEstimator.forESDTTransfer(data.size()); - - Transaction transaction; - transaction.nonce = transfer.accounts().sender_nonce(); - transaction.sender = transfer.accounts().sender(); - transaction.senderUsername = transfer.accounts().sender_username(); - transaction.receiver = transfer.accounts().receiver(); - transaction.receiverUsername = transfer.accounts().receiver_username(); - transaction.value = "0"; - transaction.data = data; - transaction.gasLimit = coalesceGasLimit(input.gas_limit(), estimatedGasLimit); - transaction.gasPrice = coalesceGasPrice(input.gas_price()); - transaction.chainID = coalesceChainId(input.chain_id()); - transaction.version = TX_VERSION; - - return transaction; -} - -Transaction TransactionFactory::fromESDTNFTTransfer(const Proto::SigningInput &input) { - auto transfer = input.esdtnft_transfer(); - - std::string encodedCollection = Codec::encodeString(transfer.token_collection()); - std::string encodedNonce = Codec::encodeUint64(transfer.token_nonce()); - std::string encodedQuantity = Codec::encodeBigInt(transfer.amount()); - std::string encodedReceiver = Codec::encodeAddress(transfer.accounts().receiver()); - std::string data = prepareFunctionCall("ESDTNFTTransfer", { encodedCollection, encodedNonce, encodedQuantity, encodedReceiver }); - uint64_t estimatedGasLimit = this->gasEstimator.forESDTNFTTransfer(data.size()); - - Transaction transaction; - transaction.nonce = transfer.accounts().sender_nonce(); - // For NFT, SFT and MetaESDT, transaction.sender == transaction.receiver. - transaction.sender = transfer.accounts().sender(); - transaction.receiver = transfer.accounts().sender(); - transaction.value = "0"; - transaction.data = data; - transaction.gasLimit = coalesceGasLimit(input.gas_limit(), estimatedGasLimit); - transaction.gasPrice = coalesceGasPrice(input.gas_price()); - transaction.chainID = coalesceChainId(input.chain_id()); - transaction.version = TX_VERSION; - - return transaction; -} - -uint64_t TransactionFactory::coalesceGasLimit(uint64_t providedGasLimit, uint64_t estimatedGasLimit) { - return providedGasLimit > 0 ? providedGasLimit : estimatedGasLimit; -} - -uint64_t TransactionFactory::coalesceGasPrice(uint64_t gasPrice) { - return gasPrice > 0 ? gasPrice : this->networkConfig.getMinGasPrice(); -} - -std::string TransactionFactory::coalesceChainId(std::string chainID) { - return chainID.empty() ? this->networkConfig.getChainId() : chainID; -} - -std::string TransactionFactory::prepareFunctionCall(const std::string& function, std::initializer_list arguments) { - const auto ARGUMENTS_SEPARATOR = "@"; - std::string result; - - result.append(function); - - for (auto argument : arguments) { - result.append(ARGUMENTS_SEPARATOR); - result.append(argument); - } - - return result; -} diff --git a/src/Elrond/TransactionFactory.h b/src/Elrond/TransactionFactory.h deleted file mode 100644 index db3b3058b9d..00000000000 --- a/src/Elrond/TransactionFactory.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "../proto/Elrond.pb.h" -#include "Address.h" -#include "NetworkConfig.h" -#include "GasEstimator.h" -#include "Transaction.h" -#include "uint256.h" - -namespace TW::Elrond { - -/// Creates specific transaction objects, wrt. the provided "NetworkConfig". -class TransactionFactory { -private: - NetworkConfig networkConfig; - GasEstimator gasEstimator; -public: - TransactionFactory(); - TransactionFactory(const NetworkConfig& networkConfig); - - /// Creates the appropriate transaction object, with respect to the "oneof" field (substructure) of Proto::SigningInput. - Transaction create(const Proto::SigningInput &input); - - Transaction fromGenericAction(const Proto::SigningInput& input); - - /// This should be used to transfer EGLD. - /// For reference, see: https://docs.elrond.com/developers/signing-transactions/signing-transactions/#general-structure. - Transaction fromEGLDTransfer(const Proto::SigningInput& input); - - /// This should be used to transfer regular ESDTs (fungible tokens). - /// For reference, see: https://docs.elrond.com/developers/esdt-tokens/#transfers - /// - /// The "regular" ESDT tokens held by an account can be fetched from https://api.elrond.com/accounts/{address}/tokens. - Transaction fromESDTTransfer(const Proto::SigningInput& input); - - /// This should be used to transfer NFTs, SFTs and Meta ESDTs. - /// For reference, see: https://docs.elrond.com/developers/nft-tokens/#transfers - /// - /// The semi-fungible and non-fungible tokens held by an account can be fetched from https://api.elrond.com/accounts/{address}/nfts?type=SemiFungibleESDT,NonFungibleESDT. - /// The Meta ESDTs (a special kind of SFTs) held by an account can be fetched from https://api.elrond.com/accounts/{address}/nfts?type=MetaESDT. - /// - /// The fields "token_collection" and "token_nonce" are found as well in the HTTP response of the API call (as "collection" and "nonce", respectively). - Transaction fromESDTNFTTransfer(const Proto::SigningInput& input); -private: - uint64_t coalesceGasLimit(uint64_t providedGasLimit, uint64_t estimatedGasLimit); - uint64_t coalesceGasPrice(uint64_t gasPrice); - std::string coalesceChainId(std::string chainID); - std::string prepareFunctionCall(const std::string& function, std::initializer_list arguments); -}; - -} // namespace diff --git a/src/Encrypt.cpp b/src/Encrypt.cpp index 9ed2a643eea..a3a3c8a78a0 100644 --- a/src/Encrypt.cpp +++ b/src/Encrypt.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Encrypt.h" #include "Data.h" diff --git a/src/Encrypt.h b/src/Encrypt.h index e024186f653..2cada9f9cc3 100644 --- a/src/Encrypt.h +++ b/src/Encrypt.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Ethereum/ABI.h b/src/Ethereum/ABI.h deleted file mode 100644 index e0c3a9f1b90..00000000000 --- a/src/Ethereum/ABI.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "ABI/Array.h" -#include "ABI/Bytes.h" -#include "ABI/Function.h" -#include "ABI/ParamAddress.h" -#include "ABI/ParamBase.h" -#include "ABI/Parameters.h" -#include "ABI/ParamFactory.h" -#include "ABI/ParamNumber.h" -#include "ABI/ParamStruct.h" -#include "ABI/Tuple.h" diff --git a/src/Ethereum/ABI/Array.cpp b/src/Ethereum/ABI/Array.cpp deleted file mode 100644 index 4f79e816ed7..00000000000 --- a/src/Ethereum/ABI/Array.cpp +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Array.h" -#include "ParamFactory.h" -#include "ValueEncoder.h" -#include - -#include - -#include - -using namespace TW::Ethereum::ABI; -using namespace TW; -using json = nlohmann::json; - -int ParamArray::addParam(const std::shared_ptr& param) { - assert(param != nullptr); - if (param == nullptr) { return -1; } - if (_params.getCount() >= 1 && param->getType() != getProtoType()) { return -2; } // do not add different types - return _params.addParam(param); -} - -void ParamArray::addParams(const std::vector>& params) { - for (auto p: params) { addParam(p); } -} - -std::shared_ptr ParamArray::getProtoElem() const { - if (_params.getCount() >= 1) { - return _params.getParamUnsafe(0); - } - return _proto; -} - -std::string ParamArray::getProtoType() const { - const auto proto = getProtoElem(); - return (proto != nullptr) ? proto->getType() : "__empty__"; -} - -size_t ParamArray::getSize() const -{ - return 32 + _params.getSize(); -} - -void ParamArray::encode(Data& data) const { - size_t n = _params.getCount(); - ValueEncoder::encodeUInt256(uint256_t(n), data); - _params.encode(data); -} - -bool ParamArray::decode(const Data& encoded, size_t& offset_inout) { - size_t origOffset = offset_inout; - // read length - uint256_t len256; - if (!ABI::decode(encoded, len256, offset_inout)) { - return false; - } - // check if length is in the size_t range - auto len = static_cast(len256); - if (len256 != uint256_t(len)) { - return false; - } - // check number of values - auto n = _params.getCount(); - if (n == 0 || n > len) { - // Encoded length is less than params count, unsafe to continue decoding - return false; - } - if (n < len) { - // pad with first type - auto first = _params.getParamUnsafe(0); - for (size_t i = 0; i < len - n; i++) { - _params.addParam(ParamFactory::make(first->getType())); - } - } - - // read values - auto res = _params.decode(encoded, offset_inout); - - // padding - offset_inout = origOffset + ValueEncoder::paddedTo32(offset_inout - origOffset); - return res; -} - -bool ParamArray::setValueJson(const std::string& value) { - if (_params.getCount() < 1) { - // no single element - return false; - } - auto valuesJson = json::parse(value, nullptr, false); - if (valuesJson.is_discarded()) { - return false; - } - if (!valuesJson.is_array()) { - return false; - } - // make sure enough elements are in the array - while (_params.getCount() < valuesJson.size()) { - addParam(ParamFactory::make(getProtoType())); - } - int cnt = 0; - for (const auto& e: valuesJson) { - std::string eString = e.is_string() ? e.get() : e.dump(); - _params.getParamUnsafe(cnt)->setValueJson(eString); - ++cnt; - } - return true; -} - -Data ParamArray::hashStruct() const { - if (_params.getCount() == 0) { - return Hash::keccak256(Data()); - } - Data hash(32); - Data hashes = _params.encodeHashes(); - if (hashes.size() > 0) { - hash = Hash::keccak256(hashes); - } - return hash; -} - -std::string ParamArray::getExtraTypes(std::vector& ignoreList) const { - const auto& proto = getProtoElem(); - return (proto != nullptr) ? proto->getExtraTypes(ignoreList) : ""; -} diff --git a/src/Ethereum/ABI/Array.h b/src/Ethereum/ABI/Array.h deleted file mode 100644 index c4a8c8e80dd..00000000000 --- a/src/Ethereum/ABI/Array.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "ParamBase.h" -#include "ParamNumber.h" -#include "Parameters.h" - -namespace TW::Ethereum::ABI { - -/// Dynamic array of the same types, "[]" -/// Normally has at least one element. Empty array can have prototype set so its type is known. -/// Empty with no prototype is possible, but should be avoided. -class ParamArray: public ParamCollection -{ -private: - ParamSet _params; - std::shared_ptr _proto; // an optional prototype element, determines the array type, useful in empty array case - -private: - std::shared_ptr getProtoElem() const; // the first element if exists, otherwise the proto element - std::string getProtoType() const; - -public: - ParamArray() = default; - ParamArray(const std::shared_ptr& param1) : ParamCollection() { addParam(param1); } - ParamArray(const std::vector>& params) : ParamCollection() { setVal(params); } - void setVal(const std::vector>& params) { addParams(params); } - std::vector> const& getVal() const { return _params.getParams(); } - int addParam(const std::shared_ptr& param); - void addParams(const std::vector>& params); - void setProto(const std::shared_ptr& proto) { _proto = proto; } - std::shared_ptr getParam(int paramIndex) { return _params.getParamUnsafe(paramIndex); } - virtual std::string getType() const { return getProtoType() + "[]"; } - virtual size_t getSize() const; - virtual bool isDynamic() const { return true; } - virtual size_t getCount() const { return _params.getCount(); } - virtual void encode(Data& data) const; - virtual bool decode(const Data& encoded, size_t& offset_inout); - virtual bool setValueJson(const std::string& value); - virtual Data hashStruct() const; - virtual std::string getExtraTypes(std::vector& ignoreList) const; -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Bytes.cpp b/src/Ethereum/ABI/Bytes.cpp deleted file mode 100644 index 986bfa66216..00000000000 --- a/src/Ethereum/ABI/Bytes.cpp +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Bytes.h" -#include "ParamNumber.h" -#include "ValueEncoder.h" -#include -#include - -#include - -using namespace TW::Ethereum::ABI; -using namespace TW; - -void ParamByteArray::encodeBytes(const Data& bytes, Data& data) { - ValueEncoder::encodeUInt256(uint256_t(bytes.size()), data); - - const auto count = bytes.size(); - const auto padding = ValueEncoder::padNeeded32(count); - data.insert(data.end(), bytes.begin(), bytes.begin() + count); - append(data, Data(padding)); -} - -bool ParamByteArray::decodeBytes(const Data& encoded, Data& decoded, size_t& offset_inout) { - size_t origOffset = offset_inout; - // read len - uint256_t len256; - if (!ABI::decode(encoded, len256, offset_inout)) { - return false; - } - // check if length is in the size_t range - auto len = static_cast(len256); - if (len256 != uint256_t(len)) { - return false; - } - // check if there is enough data - if (encoded.size() < offset_inout + len) { - return false; - } - // read data - decoded = Data(encoded.begin() + offset_inout, encoded.begin() + offset_inout + len); - offset_inout += len; - // padding - offset_inout = origOffset + ValueEncoder::paddedTo32(offset_inout - origOffset); - return true; -} - -bool ParamByteArray::setValueJson(const std::string& value) { - setVal(parse_hex(value)); - return true; -} - -Data ParamByteArray::hashStruct() const { - return Hash::keccak256(_bytes); -} - -void ParamByteArrayFix::setVal(const Data& val) { - if (val.size() > _n) { // crop right - _bytes = subData(val, 0, _n); - } else { - _bytes = val; - if (_bytes.size() < _n) { // pad on right - append(_bytes, Data(_n - _bytes.size())); - } - } - assert(_bytes.size() == _n); -} - -void ParamByteArrayFix::encode(Data& data) const { - const auto count = _bytes.size(); - const auto padding = ValueEncoder::padNeeded32(count); - data.insert(data.end(), _bytes.begin(), _bytes.begin() + count); - append(data, Data(padding)); -} - -bool ParamByteArrayFix::decodeBytesFix(const Data& encoded, size_t n, Data& decoded, - size_t& offset_inout) { - size_t origOffset = offset_inout; - if (encoded.size() < offset_inout + n) { - // not enough data - return false; - } - if (decoded.size() < n) { - append(decoded, Data(n - decoded.size())); - } - std::copy(encoded.begin() + offset_inout, encoded.begin() + (offset_inout + n), - decoded.begin()); - offset_inout += n; - // padding - offset_inout = origOffset + ValueEncoder::paddedTo32(offset_inout - origOffset); - return true; -} - -bool ParamByteArrayFix::setValueJson(const std::string& value) { - setVal(parse_hex(value)); - return true; -} - -Data ParamByteArrayFix::hashStruct() const { - if (_bytes.size() > 32) { - return Hash::keccak256(_bytes); - } - return ParamBase::hashStruct(); -} - -void ParamString::encodeString(const std::string& decoded, Data& data) { - auto bytes = Data(decoded.begin(), decoded.end()); - ParamByteArray::encodeBytes(bytes, data); -} - -bool ParamString::decodeString(const Data& encoded, std::string& decoded, size_t& offset_inout) { - Data decodedData; - if (!ParamByteArray::decodeBytes(encoded, decodedData, offset_inout)) { - return false; - } - decoded = std::string(decodedData.begin(), decodedData.end()); - return true; -} - -Data ParamString::hashStruct() const { - Data hash(32); - Data encoded = data(_str); - hash = Hash::keccak256(encoded); - return hash; -} diff --git a/src/Ethereum/ABI/Bytes.h b/src/Ethereum/ABI/Bytes.h deleted file mode 100644 index b823ec2752b..00000000000 --- a/src/Ethereum/ABI/Bytes.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "ParamBase.h" -#include "ValueEncoder.h" -#include - -namespace TW::Ethereum::ABI { - -/// Dynamic array of bytes "bytes" -class ParamByteArray: public ParamCollection -{ -private: - Data _bytes; -public: - ParamByteArray() = default; - ParamByteArray(const Data& val) : ParamCollection() { setVal(val); } - void setVal(const Data& val) { _bytes = val; } - const Data& getVal() const { return _bytes; } - virtual std::string getType() const { return "bytes"; }; - virtual size_t getSize() const { return 32 + ValueEncoder::paddedTo32(_bytes.size()); } - virtual bool isDynamic() const { return true; } - virtual size_t getCount() const { return _bytes.size(); } - static void encodeBytes(const Data& bytes, Data& data); - virtual void encode(Data& data) const { encodeBytes(_bytes, data); } - static bool decodeBytes(const Data& encoded, Data& decoded, size_t& offset_inout); - virtual bool decode(const Data& encoded, size_t& offset_inout) { - return decodeBytes(encoded, _bytes, offset_inout); - } - virtual bool setValueJson(const std::string& value); - virtual Data hashStruct() const; -}; - -/// Fixed-size array of bytes, "bytes" -class ParamByteArrayFix: public ParamCollection -{ -private: - size_t _n; - Data _bytes; -public: - ParamByteArrayFix(size_t n): ParamCollection(), _n(n), _bytes(Data(_n)) {} - ParamByteArrayFix(size_t n, const Data& val): ParamCollection(), _n(n), _bytes(Data(_n)) { setVal(val); } - void setVal(const Data& val); - const std::vector& getVal() const { return _bytes; } - virtual std::string getType() const { return "bytes" + std::to_string(_n); }; - virtual size_t getSize() const { return ValueEncoder::paddedTo32(_bytes.size()); } - virtual bool isDynamic() const { return false; } - virtual size_t getCount() const { return _bytes.size(); } - virtual void encode(Data& data) const; - static bool decodeBytesFix(const Data& encoded, size_t n, Data& decoded, size_t& offset_inout); - virtual bool decode(const Data& encoded, size_t& offset_inout) { - return decodeBytesFix(encoded, _n, _bytes, offset_inout); - } - virtual bool setValueJson(const std::string& value); - virtual Data hashStruct() const; -}; - -/// Var-length string parameter -class ParamString: public ParamCollection -{ -private: - std::string _str; -public: - ParamString() = default; - ParamString(std::string val): ParamCollection() { setVal(val); } - void setVal(const std::string& val) { _str = val; } - const std::string& getVal() const { return _str; } - virtual std::string getType() const { return "string"; }; - virtual size_t getSize() const { return 32 + ValueEncoder::paddedTo32(_str.size()); } - virtual bool isDynamic() const { return true; } - virtual size_t getCount() const { return _str.size(); } - static void encodeString(const std::string& decoded, Data& data); - virtual void encode(Data& data) const { ParamString::encodeString(_str, data); } - static bool decodeString(const Data& encoded, std::string& decoded, size_t& offset_inout); - virtual bool decode(const Data& encoded, size_t& offset_inout) { - return decodeString(encoded, _str, offset_inout); - } - virtual bool setValueJson(const std::string& value) { _str = value; return true; } - virtual Data hashStruct() const; -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Function.cpp b/src/Ethereum/ABI/Function.cpp index b2c067fcb26..be1007516fe 100644 --- a/src/Ethereum/ABI/Function.cpp +++ b/src/Ethereum/ABI/Function.cpp @@ -1,51 +1,210 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Function.h" -#include "../../Data.h" +#include "rust/Wrapper.h" +#include "TrustWalletCore/TWCoinType.h" -#include +namespace TW::Ethereum::ABI { -using namespace TW; -using namespace TW::Ethereum::ABI; +static constexpr std::size_t FUNCTION_SIGNATURE_LEN = 4; -Data Function::getSignature() const { - auto typ = getType(); - auto hash = Hash::keccak256(Data(typ.begin(), typ.end())); - auto signature = Data(hash.begin(), hash.begin() + 4); - return signature; +int Function::addParam(AbiProto::Param paramType, AbiProto::Token paramValue, bool isOutput) { + if (isOutput) { + auto idx = outputValues.size(); + *outputs.add_params() = std::move(paramType); + outputValues.emplace_back(std::move(paramValue)); + return static_cast(idx); + } + + auto idx = inputValues.size(); + *inputs.add_params() = std::move(paramType); + inputValues.emplace_back(std::move(paramValue)); + return static_cast(idx); } -void Function::encode(Data& data) const { - Data signature = getSignature(); - append(data, signature); - _inParams.encode(data); +int Function::addUintParam(uint32_t bits, const Data& encodedValue, bool isOutput) { + AbiProto::Param paramType; + paramType.mutable_param()->mutable_number_uint()->set_bits(bits); + + AbiProto::Token paramValue; + auto* numValue = paramValue.mutable_number_uint(); + numValue->set_bits(bits); + numValue->set_value(encodedValue.data(), encodedValue.size()); + + return addParam(std::move(paramType), std::move(paramValue), isOutput); } -bool Function::decodeOutput(const Data& encoded, size_t& offset_inout) { - // read parameter values - if (!_outParams.decode(encoded, offset_inout)) { return false; } - return true; +int Function::addIntParam(uint32_t bits, const Data& encodedValue, bool isOutput) { + AbiProto::Param paramType; + paramType.mutable_param()->mutable_number_int()->set_bits(bits); + + AbiProto::Token paramValue; + auto* numValue = paramValue.mutable_number_int(); + numValue->set_bits(bits); + numValue->set_value(encodedValue.data(), encodedValue.size()); + + return addParam(std::move(paramType), std::move(paramValue), isOutput); +} + +int Function::addInArrayParam(int idx, AbiProto::ParamType paramType, AbiProto::Token paramValue) { + if (idx < 0) { + return -1; + } + + auto idxSize = static_cast(idx); + if (idxSize >= inputValues.size()) { + return -1; + } + + auto& arrayToken = inputValues[idxSize]; + auto& arrayType = *inputs.mutable_params(idx)->mutable_param(); + + if (!arrayToken.has_array() || !arrayType.has_array()) { + return -1; + } + + auto arrayInElementIdx = arrayToken.array().elements_size(); + + *arrayToken.mutable_array()->add_elements() = std::move(paramValue); + *arrayToken.mutable_array()->mutable_element_type() = paramType; + // Override the element type. + *arrayType.mutable_array()->mutable_element_type() = std::move(paramType); + + return arrayInElementIdx; +} + +int Function::addInArrayUintParam(int idx, uint32_t bits, const Data& encodedValue) { + AbiProto::ParamType paramType; + paramType.mutable_number_uint()->set_bits(bits); + + AbiProto::Token paramValue; + auto* numValue = paramValue.mutable_number_uint(); + numValue->set_bits(bits); + numValue->set_value(encodedValue.data(), encodedValue.size()); + + return addInArrayParam(idx, std::move(paramType), std::move(paramValue)); +} + +int Function::addInArrayIntParam(int idx, uint32_t bits, const Data& encodedValue) { + AbiProto::ParamType paramType; + paramType.mutable_number_int()->set_bits(bits); + + AbiProto::Token paramValue; + auto* numValue = paramValue.mutable_number_int(); + numValue->set_bits(bits); + numValue->set_value(encodedValue.data(), encodedValue.size()); + + return addInArrayParam(idx, std::move(paramType), std::move(paramValue)); } -bool Function::decodeInput(const Data& encoded, size_t& offset_inout) { - // read 4-byte hash - auto p = ParamByteArrayFix(4); - if (!p.decode(encoded, offset_inout)) { return false; } - std::vector hash = p.getVal(); - // adjust offset; hash is NOT padded to 32 bytes - offset_inout = offset_inout - 32 + 4; - // verify hash - Data hashExpect = getSignature(); - if (hash != hashExpect) { - // invalid hash +MaybeToken Function::getParam(int idx, bool isOutput) const { + const auto& values = isOutput ? outputValues : inputValues; + + if (idx < 0) { + return {}; + } + + auto idxSize = static_cast(idx); + if (idxSize >= values.size()) { + return {}; + } + + return values[idxSize]; +} + +bool Function::decode(const Data& encoded, bool isOutput) { + AbiProto::ParamsDecodingInput input; + + input.set_encoded(encoded.data(), encoded.size()); + if (isOutput) { + *input.mutable_abi_params() = outputs; + } else { + *input.mutable_abi_params() = inputs; + } + + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWDataWrapper outputPtr = Rust::tw_ethereum_abi_decode_params(TWCoinTypeEthereum, inputData.get()); + + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { return false; } - // read parameters - if (!_inParams.decode(encoded, offset_inout)) { return false; } + + AbiProto::ParamsDecodingOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + if (output.error() != AbiProto::AbiError::OK) { + return false; + } + + std::vector decoded; + for (const auto ¶m : output.tokens()) { + decoded.emplace_back(param); + } + + if (isOutput) { + outputValues = decoded; + } else { + inputValues = decoded; + } return true; } + +std::string Function::getType() const { + AbiProto::FunctionGetTypeInput input; + input.set_function_name(name); + *input.mutable_inputs() = inputs.params(); + + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWStringWrapper outputPtr = Rust::tw_ethereum_abi_function_get_signature(TWCoinTypeEthereum, inputData.get()); + + return outputPtr.toStringOrDefault(); +} + +MaybeData Function::encodeFunctionCall(const std::string& functionName, const Tokens& tokens) { + AbiProto::FunctionEncodingInput input; + input.set_function_name(functionName); + for (const auto& token : tokens) { + *input.add_tokens() = token; + } + + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWDataWrapper outputPtr = Rust::tw_ethereum_abi_encode_function(TWCoinTypeEthereum, inputData.get()); + + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { + return {}; + } + + AbiProto::FunctionEncodingOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + if (output.error() != AbiProto::AbiError::OK) { + return {}; + } + + return data(output.encoded()); +} + +MaybeData Function::encodeFunctionCall(const std::string& functionName, const BaseParams& params) { + Tokens namedParams; + for (const auto& param : params) { + namedParams.push_back(param->toToken()); + } + return encodeFunctionCall(functionName, namedParams); +} + +MaybeData Function::encodeParams(const BaseParams& params) { + auto encoded = encodeFunctionCall("", params); + if (!encoded.has_value() || encoded.value().size() < FUNCTION_SIGNATURE_LEN) { + return {}; + } + + // The encoded data includes the function call signature (4 bytes). Erase it. + return subData(encoded.value(), FUNCTION_SIGNATURE_LEN); +} + +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Function.h b/src/Ethereum/ABI/Function.h index f85af88bdcf..2bd474fadc0 100644 --- a/src/Ethereum/ABI/Function.h +++ b/src/Ethereum/ABI/Function.h @@ -1,77 +1,100 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "ParamBase.h" -#include "Parameters.h" -#include "Bytes.h" +#include "ProtoParam.h" +#include "proto/EthereumAbi.pb.h" +#include "../../HexCoding.h" #include "../../uint256.h" -#include "../../Hash.h" +#include +#include #include namespace TW::Ethereum::ABI { -/// Non-generic version of Function, templated version is impossible to pass around to and back over C interface -/// (void* looses the template parameters). +namespace AbiProto = EthereumAbi::Proto; + +using MaybeToken = std::optional; +using MaybeData = std::optional; +using Tokens = std::vector; + class Function { public: - std::string name; - ParamSet _inParams; - ParamSet _outParams; - - Function(std::string name) : name(std::move(name)) {} - Function(std::string name, const std::vector>& inParams) - : name(std::move(name)), _inParams(ParamSet(inParams)) {} - virtual ~Function() {} - /// Add an input parameter. Returns the index of the parameter. - int addInParam(std::shared_ptr param) { - return _inParams.addParam(param); - } - /// Add an output parameter. Returns the index of the parameter. - int addOutParam(std::shared_ptr param) { - return _outParams.addParam(param); - } - /// Add an input or output parameter. Returns the index of the parameter. - int addParam(std::shared_ptr param, bool isOutput = false) { - return isOutput ? _outParams.addParam(param) : _inParams.addParam(param); - } - /// Get an input parameter. - bool getInParam(int paramIndex, std::shared_ptr& param_out) { - return _inParams.getParam(paramIndex, param_out); - } - /// Get an output parameter. - bool getOutParam(int paramIndex, std::shared_ptr& param_out) { - return _outParams.getParam(paramIndex, param_out); + explicit Function(std::string name): name(std::move(name)) {} + + /// Adds an input or output parameter. Returns the index of the parameter. + int addParam(AbiProto::Param paramType, AbiProto::Token paramValue, bool isOutput = false); + + /// Adds an input or output uint parameter. Returns the index of the parameter. + int addUintParam(uint32_t bits, const Data& encodedValue, bool isOutput = false); + + /// Adds an input or output int parameter. Returns the index of the parameter. + int addIntParam(uint32_t bits, const Data& encodedValue, bool isOutput = false); + + /// Adds a parameter to the input array. + /// Please note the array should be present at `inputValues[idx]`. + int addInArrayParam(int idx, AbiProto::ParamType paramType, AbiProto::Token paramValue); + + /// Adds a uint parameter to the input array. + /// Please note the array should be present at `inputValues[idx]`. + int addInArrayUintParam(int idx, uint32_t bits, const Data& encodedValue); + + /// Adds an int parameter to the input array. + /// Please note the array should be present at `inputValues[idx]`. + int addInArrayIntParam(int idx, uint32_t bits, const Data& encodedValue); + + /// Returns an input or output parameter. + MaybeToken getParam(int idx, bool isOutput = false) const; + + /// Returns the data of an input or output uint parameter. + Data getUintParamData(int idx, uint32_t bits, bool isOutput = false) const { + auto param = getParam(idx, isOutput); + if (!param.has_value() || !param->has_number_uint() || param->number_uint().bits() != bits) { + return store(0); + } + return data(param->number_uint().value()); } - /// Get an input or output parameter. - bool getParam(int paramIndex, std::shared_ptr& param_out, bool isOutput = false) { - return isOutput ? _outParams.getParam(paramIndex, param_out) : _inParams.getParam(paramIndex, param_out); + + /// Returns an input or output uint parameter. + template + T getUintParam(int idx, uint32_t bits, bool isOutput = false) const { + auto valueData = getUintParamData(idx, bits, isOutput); + auto val256 = load(valueData); + return static_cast(val256); } - /// Return the function type signature, of the form "baz(int32,uint256)" - std::string getType() const { - return name + _inParams.getType(); + + /// Encodes a function call to Eth ABI binary. + MaybeData encodeInput() const { + return encodeFunctionCall(name, inputValues); } - /// Return the 4-byte function signature - Data getSignature() const; + /// Decode binary, fill input or output parameters. + bool decode(const Data& encoded, bool isOutput = false); - virtual void encode(Data& data) const; + /// Returns the function type signature, of the form "baz(int32,uint256)". + std::string getType() const; - /// Decode binary, fill output parameters - bool decodeOutput(const Data& encoded, size_t& offset_inout); - /// Decode binary, fill input parameters - bool decodeInput(const Data& encoded, size_t& offset_inout); -}; + /// Encodes a function call to Eth ABI binary. + static MaybeData encodeFunctionCall(const std::string& functionName, const Tokens& params); -inline void encode(const Function& func, Data& data) { - func.encode(data); -} + /// Encodes a function call to Eth ABI binary. + static MaybeData encodeFunctionCall(const std::string& functionName, const BaseParams& params); + + /// Encodes params to Eth ABI binary. + static MaybeData encodeParams(const BaseParams& params); + +private: + std::string name; + AbiProto::AbiParams inputs; + AbiProto::AbiParams outputs; + + Tokens inputValues; + Tokens outputValues; +}; } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamAddress.cpp b/src/Ethereum/ABI/ParamAddress.cpp deleted file mode 100644 index 43491026210..00000000000 --- a/src/Ethereum/ABI/ParamAddress.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "ParamAddress.h" -#include -#include - -using namespace TW::Ethereum::ABI; -using namespace TW; - -Data ParamAddress::getData() const { - Data data = store(getVal(), bytes); - return data; -} - -bool ParamAddress::setValueJson(const std::string& value) { - setVal(load(parse_hex(value))); - return true; -} diff --git a/src/Ethereum/ABI/ParamAddress.h b/src/Ethereum/ABI/ParamAddress.h deleted file mode 100644 index ea9faef9ed9..00000000000 --- a/src/Ethereum/ABI/ParamAddress.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "ParamNumber.h" -#include - -namespace TW::Ethereum::ABI { - -/// 160-bit Address parameter, "address". Padded to the right, treated like ParamUInt160 -class ParamAddress: public ParamUIntN -{ -public: - static const size_t bytes = 20; - ParamAddress(): ParamUIntN(bytes * 8) {} - ParamAddress(const Data& val): ParamUIntN(bytes * 8, load(val)) {} - virtual std::string getType() const { return "address"; }; - // get the value as (20-byte) byte array (as opposed to uint256_t) - Data getData() const; - virtual bool setValueJson(const std::string& value); -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamBase.cpp b/src/Ethereum/ABI/ParamBase.cpp deleted file mode 100644 index 4fde27f0410..00000000000 --- a/src/Ethereum/ABI/ParamBase.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "ParamBase.h" - -namespace TW::Ethereum::ABI { - -// Default implementation for simple types: return encoded value (32 bytes) -Data ParamBase::hashStruct() const { - Data encoded; - encode(encoded); - return encoded; -} - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamBase.h b/src/Ethereum/ABI/ParamBase.h deleted file mode 100644 index 464fa92c109..00000000000 --- a/src/Ethereum/ABI/ParamBase.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Data.h" - -#include - -namespace TW::Ethereum::ABI { - -/// Abstract base class for parameters. -class ParamBase -{ -public: - virtual ~ParamBase() = default; - virtual std::string getType() const = 0; - virtual size_t getSize() const = 0; - virtual bool isDynamic() const = 0; - virtual void encode(Data& data) const = 0; - virtual bool decode(const Data& encoded, size_t& offset_inout) = 0; - virtual bool setValueJson(const std::string& value) = 0; - // EIP712-style hash of the value (used for signing); default implementation - virtual Data hashStruct() const; - // Helper for EIP712 encoding; provide full type of all used types (recursively). Default is empty implementation. - virtual std::string getExtraTypes(std::vector& ignoreList) const { return ""; } -}; - -/// Collection of parameters base class -class ParamCollection: public ParamBase -{ -public: - virtual size_t getCount() const = 0; -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamFactory.cpp b/src/Ethereum/ABI/ParamFactory.cpp deleted file mode 100644 index 49f98bdf63d..00000000000 --- a/src/Ethereum/ABI/ParamFactory.cpp +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "ParamFactory.h" -#include "ParamAddress.h" -#include "HexCoding.h" - -#include -#include - -using namespace std; -using namespace boost::algorithm; -using json = nlohmann::json; - -namespace TW::Ethereum::ABI { - -static int parseBitSize(const std::string& type) { - int size = stoi(type); - if (size < 8 || size > 256 || size % 8 != 0 || - size == 8 || size == 16 || size == 32 || size == 64 || size == 256) { - throw invalid_argument("invalid bit size"); - } - return size; -} - -static std::shared_ptr makeUInt(const std::string& type) { - auto bits = parseBitSize(type); - return make_shared(bits); -} - -static std::shared_ptr makeInt(const std::string& type) { - auto bits = parseBitSize(type); - return make_shared(bits); -} - -static bool isArrayType(const std::string& type) { - return ends_with(type, "[]") && type.length() >= 3; -} - -static std::string getArrayElemType(const std::string& arrayType) { - if (isArrayType(arrayType)) { - return arrayType.substr(0, arrayType.length() - 2); - } - return ""; -} - -std::shared_ptr ParamFactory::make(const std::string& type) { - shared_ptr param; - if (isArrayType(type)) { - auto elemType = getArrayElemType(type); - auto elemParam = make(elemType); - if (!elemParam) { - return param; - } - param = make_shared(elemParam); - } else if (type == "address") { - param = make_shared(); - } else if (type == "uint8") { - param = make_shared(); - } else if (type == "uint16") { - param = make_shared(); - } else if (type == "uint32") { - param = make_shared(); - } else if (type == "uint64") { - param = make_shared(); - } else if (type == "uint256" || type == "uint") { - param = make_shared(); - } else if (type == "int8") { - param = make_shared(); - } else if (type == "int16") { - param = make_shared(); - } else if (type == "int32") { - param = make_shared(); - } else if (type == "int64") { - param = make_shared(); - } else if (type == "int256" || type == "int") { - param = make_shared(); - } else if (starts_with(type, "uint")) { - param = makeUInt(type.substr(4, type.size() - 1)); - } else if (starts_with(type, "int")) { - param = makeInt(type.substr(3, type.size() - 1)); - } else if (type == "bool") { - param = make_shared(); - } else if (type == "bytes") { - param = make_shared(); - } else if (starts_with(type, "bytes")) { - auto bits = stoi(type.substr(5, type.size() - 1)); - param = make_shared(bits); - } else if (type == "string") { - param = make_shared(); - } - return param; -} - -std::string joinArrayElems(const std::vector& strings) { - auto array = json::array(); - for (const auto& string : strings) { - // parse to prevent quotes on simple values - auto value = json::parse(string, nullptr, false); - if (value.is_discarded()) { - // fallback - value = json(string); - } - array.push_back(value); - } - return array.dump(); -} - -std::shared_ptr ParamFactory::makeNamed(const std::string& name, const std::string& type) { - auto param = make(type); - if (!param) { - return nullptr; - } - return std::make_shared(name, param); -} - -std::string ParamFactory::getValue(const std::shared_ptr& param, const std::string& type) { - std::string result = ""; - if (isArrayType(type)) { - auto values = getArrayValue(param, type); - result = joinArrayElems(values); - } else if (type == "address") { - auto value = dynamic_pointer_cast(param); - result = hexEncoded(value->getData()); - } else if (type == "uint8") { - result = boost::lexical_cast((uint)dynamic_pointer_cast(param)->getVal()); - } else if (type == "uint16") { - result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); - } else if (type == "uint32") { - result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); - } else if (type == "uint64") { - result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); - } else if (type == "uint256" || type == "uint") { - result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); - } else if (type == "int8") { - result = boost::lexical_cast((int)dynamic_pointer_cast(param)->getVal()); - } else if (type == "int16") { - result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); - } else if (type == "int32") { - result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); - } else if (type == "int64") { - result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); - } else if (type == "int256" || type == "int") { - result = boost::lexical_cast(dynamic_pointer_cast(param)->getVal()); - } else if (starts_with(type, "uint")) { - auto value = dynamic_pointer_cast(param); - result = boost::lexical_cast(value->getVal()); - } else if (starts_with(type, "int")) { - auto value = dynamic_pointer_cast(param); - result = boost::lexical_cast(value->getVal()); - } else if (type == "bool") { - auto value = dynamic_pointer_cast(param); - result = value->getVal() ? "true" : "false"; - } else if (type == "bytes") { - auto value = dynamic_pointer_cast(param); - result = hexEncoded(value->getVal()); - } else if (starts_with(type, "bytes")) { - auto value = dynamic_pointer_cast(param); - result = hexEncoded(value->getVal()); - } else if (type == "string") { - auto value = dynamic_pointer_cast(param); - result = value->getVal(); - } - return result; -} - -std::vector ParamFactory::getArrayValue(const std::shared_ptr& param, const std::string& type) { - if (!isArrayType(type)) { - return std::vector(); - } - auto array = dynamic_pointer_cast(param); - if (!array) { - return std::vector(); - } - auto elemType = getArrayElemType(type); - auto elems = array->getVal(); - std::vector values(elems.size()); - for (auto i = 0; i < elems.size(); ++i) { - values[i] = getValue(elems[i], elemType); - } - return values; -} - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamFactory.h b/src/Ethereum/ABI/ParamFactory.h deleted file mode 100644 index 0133b6f1c8e..00000000000 --- a/src/Ethereum/ABI/ParamFactory.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Array.h" -#include "Bytes.h" -#include "ParamBase.h" -#include "ParamStruct.h" - -#include -#include - -#include "Wasm.h" - -namespace TW::Ethereum::ABI { - -/// Factory creates concrete ParamBase class from string type. -class ParamFactory -{ -public: - /// Create a param of given type - static std::shared_ptr make(const std::string& type); - /// Create a named param, with given name and type - static std::shared_ptr makeNamed(const std::string& name, const std::string& type); - - static std::string getValue(const std::shared_ptr& param, const std::string& type); - static std::vector getArrayValue(const std::shared_ptr& param, const std::string& type); -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamNumber.cpp b/src/Ethereum/ABI/ParamNumber.cpp deleted file mode 100644 index ba4308cc985..00000000000 --- a/src/Ethereum/ABI/ParamNumber.cpp +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "ParamNumber.h" - -#include -#include -#include - -#include - -#include -#include - -using namespace TW; -using namespace TW::Ethereum::ABI; - - -bool ParamUInt256::setUInt256FromValueJson(uint256_t& dest, const std::string& value) { - // try hex string or number - if (value.length() >= 3 && value.substr(0, 2) == "0x") { - dest = load(parse_hex(value)); - return true; - } - return boost::conversion::detail::try_lexical_convert(value, dest); -} - -bool ParamInt256::setInt256FromValueJson(int256_t& dest, const std::string& value) { - // try hex string or number - if (value.length() >= 3 && value.substr(0, 2) == "0x") { - dest = ValueEncoder::int256FromUint256(load(parse_hex(value))); - return true; - } - return boost::conversion::detail::try_lexical_convert(value, dest); -} - -bool ParamBool::setValueJson(const std::string& value) { - if (value == "true" || value == "1") { setVal(true); return true; } - if (value == "false" || value == "0") { setVal(false); return true; } - return false; -} - -bool ParamUInt8::setValueJson(const std::string& value) { - uint16_t val; - if (!boost::conversion::detail::try_lexical_convert(value, val)) { return false; } - setVal(static_cast(val)); - return true; -} - -bool ParamInt8::setValueJson(const std::string& value) { - int16_t val; - if (!boost::conversion::detail::try_lexical_convert(value, val)) { return false; } - setVal(static_cast(val)); - return true; -} - -void ParamUIntN::setVal(uint256_t val) { - // mask it to the given bits - _val = val & _mask; -} - -bool ParamUIntN::decode(const Data& encoded, size_t& offset_inout) { - uint256_t temp; - auto res = decodeNumber(encoded, temp, offset_inout); - setVal(temp); - return res; -} - -void ParamUIntN::init() { - _mask = maskForBits(bits); -} - -uint256_t ParamUIntN::maskForBits(size_t bits) { - assert(bits >= 8 && bits <= 256 && (bits % 8) == 0); - // exclude predefined sizes - assert(bits != 8 && bits != 16 && bits != 32 && bits != 64 && bits != 256); - return (uint256_t(1) << bits) - 1; -} - -void ParamIntN::setVal(int256_t val) { - // mask it to the given bits - if (val < 0) { - _val = ValueEncoder::int256FromUint256(~((~((uint256_t)val)) & _mask)); - } else { - _val = ValueEncoder::int256FromUint256(((uint256_t)val) & _mask); - } -} - -bool ParamIntN::decodeNumber(const Data& encoded, int256_t& decoded, size_t& offset_inout) { - uint256_t valU; - auto res = ABI::decode(encoded, valU, offset_inout); - decoded = ValueEncoder::int256FromUint256(valU); - return res; -} - -bool ParamIntN::decode(const Data& encoded, size_t& offset_inout) { - int256_t temp; - auto res = decodeNumber(encoded, temp, offset_inout); - setVal(temp); - return res; -} - -void ParamIntN::init() -{ - _mask = ParamUIntN::maskForBits(bits); -} diff --git a/src/Ethereum/ABI/ParamNumber.h b/src/Ethereum/ABI/ParamNumber.h deleted file mode 100644 index 7b9beb3c80c..00000000000 --- a/src/Ethereum/ABI/ParamNumber.h +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "ParamBase.h" -#include "ValueEncoder.h" - -#include -#include - -#include - -#include - -namespace TW::Ethereum::ABI { - - -inline bool decode(const Data& encoded, uint256_t& decoded, size_t& offset_inout) -{ - decoded = 0u; - if (encoded.empty() || (encoded.size() < (ValueEncoder::encodedIntSize + offset_inout))) { - return false; - } - decoded = loadWithOffset(encoded, offset_inout); - offset_inout += ValueEncoder::encodedIntSize; - return true; -} - -/// Generic parameter class for numeric types, like bool, uint32, int64, etc. All are stored on 256 bits. -template -class ParamNumberType : public ParamBase -{ -public: - ParamNumberType() = default; - ParamNumberType(T val) { _val = val; } - void setVal(T val) { _val = val; } - T getVal() const { return _val; } - virtual std::string getType() const = 0; - virtual size_t getSize() const { return ValueEncoder::encodedIntSize; } - virtual bool isDynamic() const { return false; } - virtual void encode(Data& data) const { - // cast up - ValueEncoder::encodeUInt256(static_cast(_val), data); - } - static bool decodeNumber(const Data& encoded, T& decoded, size_t& offset_inout) { - uint256_t val256; - if (!ABI::decode(encoded, val256, offset_inout)) { return false; } - // cast down - decoded = static_cast(val256); - return true; - } - virtual bool decode(const Data& encoded, size_t& offset_inout) { - return decodeNumber(encoded, _val, offset_inout); - } - virtual bool setValueJson(const std::string& value) { - return boost::conversion::detail::try_lexical_convert(value, _val); - } -protected: - T _val; -}; - -class ParamUInt256 : public ParamNumberType -{ -public: - ParamUInt256() : ParamNumberType(uint256_t(0)) {} - ParamUInt256(uint256_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "uint256"; } - uint256_t getVal() const { return ParamNumberType::getVal(); } - virtual bool setValueJson(const std::string& value) { return setUInt256FromValueJson(_val, value); } - static bool setUInt256FromValueJson(uint256_t& dest, const std::string& value); -}; - -class ParamInt256 : public ParamNumberType -{ -public: - ParamInt256() : ParamNumberType(int256_t(0)) {} - ParamInt256(int256_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "int256"; } - int256_t getVal() const { return ParamNumberType::getVal(); } - virtual bool setValueJson(const std::string& value) { return setInt256FromValueJson(_val, value); } - static bool setInt256FromValueJson(int256_t& dest, const std::string& value); -}; - -class ParamBool : public ParamNumberType -{ -public: - ParamBool() : ParamNumberType(false) {} - ParamBool(bool val) : ParamNumberType(val) {} - virtual std::string getType() const { return "bool"; } - bool getVal() const { return ParamNumberType::getVal(); } - virtual bool setValueJson(const std::string& value); -}; - -class ParamUInt8 : public ParamNumberType -{ -public: - ParamUInt8() : ParamNumberType(0) {} - ParamUInt8(uint8_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "uint8"; } - virtual bool setValueJson(const std::string& value); -}; - -class ParamInt8 : public ParamNumberType -{ -public: - ParamInt8() : ParamNumberType(0) {} - ParamInt8(int8_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "int8"; } - virtual bool setValueJson(const std::string& value); -}; - -class ParamUInt16 : public ParamNumberType -{ -public: - ParamUInt16() : ParamNumberType(0) {} - ParamUInt16(uint16_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "uint16"; } -}; - -class ParamInt16 : public ParamNumberType -{ -public: - ParamInt16() : ParamNumberType(0) {} - ParamInt16(int16_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "int16"; } -}; - -class ParamUInt32 : public ParamNumberType -{ -public: - ParamUInt32() : ParamNumberType(0) {} - ParamUInt32(uint32_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "uint32"; } -}; - -class ParamInt32 : public ParamNumberType -{ -public: - ParamInt32() : ParamNumberType(0) {} - ParamInt32(int32_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "int32"; } -}; - -class ParamUInt64 : public ParamNumberType -{ -public: - ParamUInt64() : ParamNumberType(0) {} - ParamUInt64(uint64_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "uint64"; } -}; - -class ParamInt64 : public ParamNumberType -{ -public: - ParamInt64() : ParamNumberType(0) {} - ParamInt64(int64_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "int64"; } -}; - -/// Generic parameter class for all other bit sizes, like UInt24, 40, 48, ... 248. -/// For predefined sizes (8, 16, 32, 64, 256) use the sepcial types like UInt32. -/// Stored on 256 bits. -class ParamUIntN : public ParamBase -{ -public: - const size_t bits; - ParamUIntN(size_t bits_in) : bits(bits_in) { init(); } - ParamUIntN(size_t bits_in, uint256_t val) : bits(bits_in) { init(); setVal(val); } - void setVal(uint256_t val); - uint256_t getVal() const { return _val; } - virtual std::string getType() const { return "uint" + std::to_string(bits); } - virtual size_t getSize() const { return ValueEncoder::encodedIntSize; } - virtual bool isDynamic() const { return false; } - virtual void encode(Data& data) const { ValueEncoder::encodeUInt256(_val, data); } - static bool decodeNumber(const Data& encoded, uint256_t& decoded, size_t& offset_inout) { - return ABI::decode(encoded, decoded, offset_inout); - } - virtual bool decode(const Data& encoded, size_t& offset_inout); - static uint256_t maskForBits(size_t bits); - virtual bool setValueJson(const std::string& value) { return ParamUInt256::setUInt256FromValueJson(_val, value); } - -private: - void init(); - uint256_t _val; - uint256_t _mask; -}; - -/// Generic parameter class for all other bit sizes, like Int24, 40, 48, ... 248. -/// For predefined sizes (8, 16, 32, 64, 256) use the sepcial types like Int32. -/// Stored on 256 bits. -class ParamIntN : public ParamBase -{ -public: - const size_t bits; - ParamIntN(size_t bits_in) : bits(bits_in) { init(); } - ParamIntN(size_t bits_in, int256_t val) : bits(bits_in) { init(); setVal(val); } - void setVal(int256_t val); - int256_t getVal() const { return _val; } - virtual std::string getType() const { return "int" + std::to_string(bits); } - virtual size_t getSize() const { return ValueEncoder::encodedIntSize; } - virtual bool isDynamic() const { return false; } - virtual void encode(Data& data) const { ValueEncoder::encodeUInt256((uint256_t)_val, data); } - static bool decodeNumber(const Data& encoded, int256_t& decoded, size_t& offset_inout); - virtual bool decode(const Data& encoded, size_t& offset_inout); - virtual bool setValueJson(const std::string& value) { return ParamInt256::setInt256FromValueJson(_val, value); } - -private: - void init(); - int256_t _val; - uint256_t _mask; -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamStruct.cpp b/src/Ethereum/ABI/ParamStruct.cpp deleted file mode 100644 index c8d76d36029..00000000000 --- a/src/Ethereum/ABI/ParamStruct.cpp +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "ParamStruct.h" -#include "ValueEncoder.h" -#include "ParamFactory.h" -#include "ParamAddress.h" -#include -#include - -#include - -#include -#include - -using namespace TW::Ethereum::ABI; -using namespace TW; -using json = nlohmann::json; - -static const Data EipStructPrefix = parse_hex("1901"); -static const auto Eip712Domain = "EIP712Domain"; - - -std::string ParamNamed::getType() const { - return _param->getType() + " " + _name; -} - -ParamSetNamed::~ParamSetNamed() { - _params.clear(); -} - -/// Returns the index of the parameter -int ParamSetNamed::addParam(const std::shared_ptr& param) { - if (param.get() == nullptr) { - return -1; - } - assert(param.get() != nullptr); - _params.push_back(param); - return static_cast(_params.size() - 1); -} - -void ParamSetNamed::addParams(const std::vector>& params) { - for (auto p : params) { - addParam(p); - } -} - -std::string ParamSetNamed::getType() const { - std::string t = "("; - int cnt = 0; - for (auto p : _params) { - if (cnt++ > 0) { - t += ","; - } - t += p->getType(); - } - t += ")"; - return t; -} - -Data ParamSetNamed::encodeHashes() const { - Data hashes; - for (auto p: _params) { - append(hashes, p->hashStruct()); - } - return hashes; -} - -std::string ParamSetNamed::getExtraTypes(std::vector& ignoreList) const { - std::string types; - for (auto& p: _params) { - auto pType = p->_param->getType(); - if (std::find(ignoreList.begin(), ignoreList.end(), pType) == ignoreList.end()) { - types += p->getExtraTypes(ignoreList); - ignoreList.push_back(pType); - } - } - return types; -} - -std::shared_ptr ParamSetNamed::findParamByName(const std::string& name) const { - for (auto& p: _params) { - if (p->_name == name) { - return p; - } - } - return nullptr; -} - -Data ParamStruct::hashType() const { - return Hash::keccak256(TW::data(encodeType())); -} - -Data ParamStruct::encodeHashes() const { - Data hashes; - Data paramsHashes = _params.encodeHashes(); - if (paramsHashes.size() > 0) { - auto fullType = encodeType(); - hashes = Hash::keccak256(TW::data(fullType)); - append(hashes, paramsHashes); - } - return hashes; -} - -Data ParamStruct::hashStruct() const { - Data hash(32); - Data hashes = encodeHashes(); - if (hashes.size() > 0) { - hash = Hash::keccak256(hashes); - } - return hash; -} - -std::string ParamStruct::getExtraTypes(std::vector& ignoreList) const { - std::string types; - if (std::find(ignoreList.begin(), ignoreList.end(), _name) == ignoreList.end()) { - types += _name + _params.getType(); - ignoreList.push_back(_name); - } - types += _params.getExtraTypes(ignoreList); - return types; -} - -Data ParamStruct::hashStructJson(const std::string& messageJson) { - auto message = json::parse(messageJson, nullptr, false); - if (message.is_discarded()) { - throw std::invalid_argument("Could not parse Json"); - } - if (!message.is_object()) { - throw std::invalid_argument("Expecting Json object"); - } - if (!message.contains("primaryType") || !message["primaryType"].is_string()) { - throw std::invalid_argument("Top-level string field 'primaryType' missing"); - } - if (!message.contains("domain") || !message["domain"].is_object()) { - throw std::invalid_argument("Top-level object field 'domain' missing"); - } - if (!message.contains("message") || !message["message"].is_object()) { - throw std::invalid_argument("Top-level object field 'message' missing"); - } - if (!message.contains("types") || !message["types"].is_object()) { - throw std::invalid_argument("Top-level object field 'types' missing"); - } - - // concatenate hashes - Data hashes = EipStructPrefix; - - auto domainStruct = makeStruct( - Eip712Domain, - message["domain"].dump(), - message["types"].dump()); - if (domainStruct) { - TW::append(hashes, domainStruct->hashStruct()); - - auto messageStruct = makeStruct( - message["primaryType"].get(), - message["message"].dump(), - message["types"].dump()); - if (messageStruct) { - TW::append(hashes, messageStruct->hashStruct()); - return Hash::keccak256(hashes); - } - } - return {}; // fallback -} - -std::shared_ptr findType(const std::string& typeName, const std::vector>& types) { - for (auto& t: types) { - if (t->getType() == typeName) { - return t; - } - } - return nullptr; -} - -std::shared_ptr ParamStruct::makeStruct(const std::string& structType, const std::string& valueJson, const std::string& typesJson) { - try { - // parse types - auto types = makeTypes(typesJson); - // find type info - auto typeInfo = findType(structType, types); - if (!typeInfo) { - throw std::invalid_argument("Type not found, " + structType); - } - auto values = json::parse(valueJson, nullptr, false); - if (values.is_discarded()) { - throw std::invalid_argument("Could not parse value Json"); - } - if (!values.is_object()) { - throw std::invalid_argument("Expecting object"); - } - std::vector> params; - const auto& typeParams = typeInfo->getParams(); - // iterate through the type; order is important and field order in the value json is not defined - for (int i = 0; i < typeParams.getCount(); ++i) { - auto name = typeParams.getParam(i)->getName(); - auto type = typeParams.getParam(i)->getParam()->getType(); - // look for it in value (may throw) - auto value = values[name]; - // first try simple params - auto paramVal = ParamFactory::make(type); - if (paramVal) { - if (!values.is_null()) { - std::string valueString = value.is_string() ? value.get() : value.dump(); - if (!paramVal->setValueJson(valueString)) { - throw std::invalid_argument("Could not set type for param " + name); - } - } - params.push_back(std::make_shared(name, paramVal)); - } else if (type.length() >= 2 && type.substr(type.length() - 2, 2) == "[]") { - // array of struct - auto arrayType = type.substr(0, type.length() - 2); - auto subTypeInfo = findType(arrayType, types); - if (!subTypeInfo) { - throw std::invalid_argument("Could not find type for array sub-struct " + arrayType); - } - if (!value.is_array()) { - throw std::invalid_argument("Value must be array for type " + type); - } - std::vector> paramsArray; - if (value.size() == 0) { - // empty array - auto subStruct = makeStruct(arrayType, "{}", typesJson); - if (!subStruct) { - throw std::invalid_argument("Could not process array sub-struct " + arrayType + " " + "{}"); - } - assert(subStruct); - auto tmp = std::make_shared(paramsArray); - tmp->setProto(subStruct); - params.push_back(std::make_shared(name, tmp)); - } else { - for (const auto& e: value) { - auto subStruct = makeStruct(arrayType, e.dump(), typesJson); - if (!subStruct) { - throw std::invalid_argument("Could not process array sub-struct " + arrayType + " " + e.dump()); - } - assert(subStruct); - paramsArray.push_back(subStruct); - } - params.push_back(std::make_shared(name, std::make_shared(paramsArray))); - } - } else { - // try if sub struct - auto subTypeInfo = findType(type, types); - if (!subTypeInfo) { - throw std::invalid_argument("Could not find type for sub-struct " + type); - } - if (value.is_null()) { - params.push_back(std::make_shared(name, std::make_shared(type, std::vector>{}))); - } else { - auto subStruct = makeStruct(type, value.dump(), typesJson); - if (!subStruct) { - throw std::invalid_argument("Could not process sub-struct " + type); - } - assert(subStruct); - params.push_back(std::make_shared(name, subStruct)); - } - } - } - return std::make_shared(structType, params); - } catch (const std::invalid_argument& ex) { - throw; - } catch (const std::exception& ex) { - throw std::invalid_argument(std::string("Could not process Json: ") + ex.what()); - } catch (...) { - throw std::invalid_argument("Could not process Json"); - } -} - -std::shared_ptr ParamStruct::makeType(const std::string& structName, const std::string& structJson, const std::vector>& extraTypes, bool ignoreMissingType) { - try { - if (structName.empty()) { - throw std::invalid_argument("Missing type name"); - } - auto jsonValue = json::parse(structJson, nullptr, false); - if (jsonValue.is_discarded()) { - throw std::invalid_argument("Could not parse type Json"); - } - if (!jsonValue.is_array()) { - throw std::invalid_argument("Expecting array"); - } - std::vector> params; - for(auto& p2: jsonValue) { - auto name = p2["name"].get(); - auto type = p2["type"].get(); - if (name.empty() || type.empty()) { - throw std::invalid_argument("Expecting 'name' and 'type', in " + structName); - } - auto named = ParamFactory::makeNamed(name, type); - if (named) { - // simple type (incl. array of simple type) - params.push_back(named); - } else if (type == structName) { - // recursive to self - params.push_back(std::make_shared(name, std::make_shared(type, std::vector>{}))); - } else if (type.length() >= 2 && type.substr(type.length() - 2, 2) == "[]") { - // array of struct - auto arrayType = type.substr(0, type.length() - 2); - if (ignoreMissingType) { - params.push_back(std::make_shared(name, std::make_shared(std::make_shared(arrayType, std::vector>{})))); - } else { - // try array struct from extra types - auto p2struct = findType(arrayType, extraTypes); - if (!p2struct) { - throw std::invalid_argument("Unknown struct array type " + arrayType); - } - params.push_back(std::make_shared(name, std::make_shared(p2struct))); - } - } else { - if (ignoreMissingType) { - params.push_back(std::make_shared(name, std::make_shared(type, std::vector>{}))); - } else { - // try struct from extra types - auto p2struct = findType(type, extraTypes); - if (!p2struct) { - throw std::invalid_argument("Unknown type " + type); - } - params.push_back(std::make_shared(name, p2struct)); - } - } - } - if (params.size() == 0) { - throw std::invalid_argument("No valid params found"); - } - return std::make_shared(structName, params); - } catch (const std::invalid_argument& ex) { - throw; - } catch (const std::exception& ex) { - throw std::invalid_argument(std::string("Could not process Json: ") + ex.what()); - } catch (...) { - throw std::invalid_argument("Could not process Json"); - } -} - -std::vector> ParamStruct::makeTypes(const std::string& structTypes) { - try { - auto jsonValue = json::parse(structTypes, nullptr, false); - if (jsonValue.is_discarded()) { - throw std::invalid_argument("Could not parse types Json"); - } - if (!jsonValue.is_object()) { - throw std::invalid_argument("Expecting object"); - } - // do it in 2 passes, as type order may be undefined - std::vector> types1; - for (json::iterator it = jsonValue.begin(); it != jsonValue.end(); it++) { - // may throw - auto struct1 = makeType(it.key(), it.value().dump(), {}, true); - types1.push_back(struct1); - } - std::vector> types2; - for (json::iterator it = jsonValue.begin(); it != jsonValue.end(); it++) { - // may throw - auto struct1 = makeType(it.key(), it.value().dump(), types1, false); - types2.push_back(struct1); - } - return types2; - } catch (const std::invalid_argument& ex) { - throw; - } catch (const std::exception& ex) { - throw std::invalid_argument(std::string("Could not process Json: ") + ex.what()); - } catch (...) { - throw std::invalid_argument("Could not process Json"); - } -} diff --git a/src/Ethereum/ABI/ParamStruct.h b/src/Ethereum/ABI/ParamStruct.h deleted file mode 100644 index 53eb8d42711..00000000000 --- a/src/Ethereum/ABI/ParamStruct.h +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "ParamBase.h" - -#include -#include -#include - -namespace TW::Ethereum::ABI { - -/// A named parameter. -class ParamNamed: public ParamBase -{ -public: - std::string _name; - std::shared_ptr _param; - -public: - ParamNamed(const std::string& name, std::shared_ptr param): _name(name), _param(param) {} - - virtual std::string getName() const { return _name; } - virtual std::shared_ptr getParam() const { return _param; } - virtual std::string getType() const; - virtual size_t getSize() const { return _param->getSize(); } - virtual bool isDynamic() const { return _param->isDynamic(); } - virtual void encode(Data& data) const { return _param->encode(data); } - virtual bool decode(const Data& encoded, size_t& offset_inout) { return _param->decode(encoded, offset_inout); } - virtual bool setValueJson(const std::string& value) { return _param->setValueJson(value); } - virtual Data hashStruct() const { return _param->hashStruct(); } - virtual std::string getExtraTypes(std::vector& ignoreList) const { return _param->getExtraTypes(ignoreList); } -}; - -/// A collection of named parameters. See also: ParamStruct -class ParamSetNamed { -private: - std::vector> _params; - -public: - ParamSetNamed() = default; - ParamSetNamed(const std::shared_ptr& param1) { addParam(param1); } - ParamSetNamed(const std::vector>& params) { addParams(params); } - virtual ~ParamSetNamed(); - - /// Returns the index of the parameter - int addParam(const std::shared_ptr& param); - void addParams(const std::vector>& params); - size_t getCount() const { return _params.size(); } - std::shared_ptr getParam(int idx) const { return _params[idx]; } - std::string getType() const; - Data encodeHashes() const; - std::string getExtraTypes(std::vector& ignoreList) const; - std::shared_ptr findParamByName(const std::string& name) const; -}; - -/// A named structure (set of parameters plus a type name). -class ParamStruct: public ParamCollection -{ -private: - std::string _name; - ParamSetNamed _params; - -public: - ParamStruct() = default; - ParamStruct(const std::string& name, const std::vector>& params) : ParamCollection(), _name(name), _params(ParamSetNamed(params)) {} - - std::string getType() const { return _name; } - const ParamSetNamed& getParams() const { return _params; } - - /// Compute the hash of a struct, used for signing, according to EIP712 - virtual Data hashStruct() const; - /// Get full type, extended by used sub-types, of the form 'Mail(Person from,Person to,string contents)Person(string name,address wallet)' - std::string encodeType() const { - std::vector ignoreList; - return getExtraTypes(ignoreList); - } - /// Get the hash of the full type. - Data hashType() const; - - virtual size_t getSize() const { return _params.getCount(); } - virtual bool isDynamic() const { return true; } - virtual size_t getCount() const { return _params.getCount(); } - virtual void encode(Data& data) const {} - virtual bool decode(const Data& encoded, size_t& offset_inout) { return true; } - virtual bool setValueJson(const std::string& value) { return false; } // see makeStruct - Data encodeHashes() const; - virtual std::string getExtraTypes(std::vector& ignoreList) const; - std::shared_ptr findParamByName(const std::string& name) const { return _params.findParamByName(name); } - - /// Compute the hash of a struct, used for signing, according to EIP712 ("v4"). - /// Input is a Json object (as string), with following fields: - /// - types: map of used struct types (see makeTypes()) - /// - primaryType: the type of the message (string) - /// - domain: EIP712 domain specifier values - /// - message: the message (object). - /// Throws on error. - /// Example input: - /// R"({ - /// "types": { - /// "EIP712Domain": [ - /// {"name": "name", "type": "string"}, - /// {"name": "version", "type": "string"}, - /// {"name": "chainId", "type": "uint256"}, - /// {"name": "verifyingContract", "type": "address"} - /// ], - /// "Person": [ - /// {"name": "name", "type": "string"}, - /// {"name": "wallet", "type": "address"} - /// ] - /// }, - /// "primaryType": "Person", - /// "domain": { - /// "name": "Ether Person", - /// "version": "1", - /// "chainId": 1, - /// "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - /// }, - /// "message": { - /// "name": "Cow", - /// "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - /// } - /// })"); - static Data hashStructJson(const std::string& messageJson); - - /// Make a named struct, described by a json string (with values), and its type info (may contain type info of sub-types also). - /// Throws on error. - static std::shared_ptr makeStruct(const std::string& structType, const std::string& valueJson, const std::string& typesJson); - - /// Parse a json with a list of types, and build a vector of named structs. Structs params have the given name and type, and empty value. - /// Ex. input: R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}], "Mail": [{"name": "from", "type": "Person"}, {"name": "to", "type": "Person"}, {"name": "contents", "type": "string"}]})" - /// Order does not matter. Note the quote delimiters. - /// Throws on error. - static std::vector> makeTypes(const std::string& structTypes); - - /// Make a named struct, with the given types, with empty values. - /// Similar to makeTypes, but works with only one type. - /// Ex. input: "Person", R"([{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}])" - /// Throws on error. - static std::shared_ptr makeType(const std::string& structName, const std::string& structJson, const std::vector>& extraTypes = {}, bool ignoreMissingType = false); -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Parameters.cpp b/src/Ethereum/ABI/Parameters.cpp deleted file mode 100644 index ed734d78257..00000000000 --- a/src/Ethereum/ABI/Parameters.cpp +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Parameters.h" -#include "ValueEncoder.h" -#include - -#include -#include - -using namespace TW::Ethereum::ABI; -using namespace TW; - -ParamSet::~ParamSet() { - _params.clear(); -} - -/// Returns the index of the parameter -int ParamSet::addParam(const std::shared_ptr& param) { - if (param.get() == nullptr) { - return -1; - } - assert(param.get() != nullptr); - _params.push_back(param); - return static_cast(_params.size() - 1); -} - -void ParamSet::addParams(const std::vector>& params) { - for (auto p : params) { - addParam(p); - } -} - -bool ParamSet::getParam(int paramIndex, std::shared_ptr& param_out) const { - if (paramIndex >= _params.size() || paramIndex < 0) { - return false; - } - param_out = _params[paramIndex]; - return true; -} - -std::shared_ptr ParamSet::getParamUnsafe(int paramIndex) const { - if (_params.size() == 0) { - // zero parameter, nothing to return. This may cause trouble (segfault) - return nullptr; - } - if (paramIndex >= _params.size() || paramIndex < 0) { - // invalid index, return the first instead of nullptr - return _params[0]; - } - return _params[paramIndex]; -} - -/// Return the function type signature, of the form "baz(int32,uint256)" -std::string ParamSet::getType() const { - std::string t = "("; - int cnt = 0; - for (auto p : _params) { - if (cnt++ > 0) { - t += ","; - } - t += p->getType(); - } - t += ")"; - return t; -} - -bool ParamSet::isDynamic() const { - for (const auto& p: _params) { - if (p->isDynamic()) { - return true; - } - } - return false; -} - -size_t ParamSet::getSize() const { - // 2-pass encoding - size_t s = 0; - for (auto p: _params) { - if (p->isDynamic() || p->getSize() > ValueEncoder::encodedIntSize) { - // offset used - s += 32; - } - s += p->getSize(); - } - return ValueEncoder::paddedTo32(s); -} - -size_t ParamSet::getHeadSize() const { - size_t s = 0; - for (auto p : _params) { - if (p->isDynamic()) { - s += 32; - } else { - s += p->getSize(); - } - } - return s; -} - -void ParamSet::encode(Data& data) const { - // 2-pass encoding - size_t headSize = getHeadSize(); - size_t dynamicOffset = 0; - - // pass 1: small values or indices - for (auto p : _params) { - if (p->isDynamic() || p->getSize() > ValueEncoder::encodedIntSize) { - // include only offset - ValueEncoder::encodeUInt256(uint256_t(headSize + dynamicOffset), data); - dynamicOffset += p->getSize(); - } else { - // encode small data - p->encode(data); - } - } - - // pass 2: dynamic values - for (auto p : _params) { - if (p->isDynamic() || p->getSize() > ValueEncoder::encodedIntSize) { - // encode large data - p->encode(data); - } - } -} - -bool ParamSet::decode(const Data& encoded, size_t& offset_inout) { - // pass 1: small values - for (auto p : _params) { - if (p->isDynamic()) { - uint256_t index; - if (!ABI::decode(encoded, index, offset_inout)) { - return false; - } - // index is read but not used - } else { - if (!p->decode(encoded, offset_inout)) { - return false; - } - } - } - // pass2: large values - for (auto p : _params) { - if (p->isDynamic()) { - if (!p->decode(encoded, offset_inout)) { - return false; - } - } - } - return true; -} - -Data ParamSet::encodeHashes() const { - Data hashes; - for (auto p: _params) { - append(hashes, p->hashStruct()); - } - return hashes; -} - -Data Parameters::hashStruct() const { - Data hash(32); - Data hashes = _params.encodeHashes(); - if (hashes.size() > 0) { - hash = Hash::keccak256(hashes); - } - return hash; -} diff --git a/src/Ethereum/ABI/Parameters.h b/src/Ethereum/ABI/Parameters.h deleted file mode 100644 index 4f62ff8f326..00000000000 --- a/src/Ethereum/ABI/Parameters.h +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "ParamBase.h" -#include "ParamNumber.h" - -#include -#include -#include - -namespace TW::Ethereum::ABI { - -/// A set of parameters -class ParamSet { -private: - std::vector> _params; - -public: - ParamSet() = default; - ParamSet(const std::shared_ptr& param1) { addParam(param1); } - ParamSet(const std::vector>& params) { addParams(params); } - virtual ~ParamSet(); - - /// Returns the index of the parameter - int addParam(const std::shared_ptr& param); - void addParams(const std::vector>& params); - bool getParam(int paramIndex, std::shared_ptr& param_out) const; - std::shared_ptr getParamUnsafe(int paramIndex) const; - size_t getCount() const { return _params.size(); } - std::vector> const& getParams() const { return _params; } - /// Return the function type signature, of the form "baz(int32,uint256)" - std::string getType() const; - bool isDynamic() const; - size_t getSize() const; - virtual void encode(Data& data) const; - virtual bool decode(const Data& encoded, size_t& offset_inout); - Data encodeHashes() const; - -private: - size_t getHeadSize() const; -}; - -/// Collection of different parameters, dynamic length, "(,,...)". -class Parameters: public ParamCollection -{ -private: - ParamSet _params; - -public: - Parameters() = default; - Parameters(const std::vector>& params) : ParamCollection(), _params(ParamSet(params)) {} - void addParam(const std::shared_ptr& param) { _params.addParam(param); } - void addParams(const std::vector>& params) { _params.addParams(params); } - std::shared_ptr getParam(int paramIndex) const { return _params.getParamUnsafe(paramIndex); } - virtual std::string getType() const { return _params.getType(); } - virtual size_t getSize() const { return _params.getSize(); } - virtual bool isDynamic() const { return true; } - virtual size_t getCount() const { return _params.getCount(); } - virtual void encode(Data& data) const { _params.encode(data); } - virtual bool decode(const Data& encoded, size_t& offset_inout) { return _params.decode(encoded, offset_inout); } - virtual bool setValueJson(const std::string& value) { return false; } - virtual Data hashStruct() const; -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ProtoParam.h b/src/Ethereum/ABI/ProtoParam.h new file mode 100644 index 00000000000..8391ee94a26 --- /dev/null +++ b/src/Ethereum/ABI/ProtoParam.h @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "proto/EthereumAbi.pb.h" +#include "uint256.h" + +namespace TW::Ethereum::ABI { + +namespace AbiProto = EthereumAbi::Proto; + +struct BaseProtoParam { + virtual ~BaseProtoParam() noexcept = default; + + virtual AbiProto::Token toToken() const = 0; +}; + +using BaseParams = std::vector>; + +class ProtoBool final: public BaseProtoParam { +public: + explicit ProtoBool(bool val): m_value(val) { + } + + ~ProtoBool() override = default; + + AbiProto::Token toToken() const override { + AbiProto::Token proto; + proto.set_boolean(m_value); + return proto; + } + +private: + bool m_value = false; +}; + +class ProtoUInt256 final: public BaseProtoParam { +public: + explicit ProtoUInt256(const uint256_t &num): m_number(store(num)) { + } + + explicit ProtoUInt256(Data numData): m_number(std::move(numData)) { + } + + ~ProtoUInt256() override = default; + + AbiProto::Token toToken() const override { + AbiProto::Token proto; + proto.mutable_number_uint()->set_bits(256); + proto.mutable_number_uint()->set_value(m_number.data(), m_number.size()); + return proto; + } + +private: + Data m_number; +}; + +class ProtoByteArray final: public BaseProtoParam { +public: + explicit ProtoByteArray(Data data): m_data(std::move(data)) { + } + + ~ProtoByteArray() override = default; + + AbiProto::Token toToken() const override { + AbiProto::Token proto; + proto.set_byte_array(m_data.data(), m_data.size()); + return proto; + } + +private: + Data m_data; +}; + +class ProtoBytes32 final: public BaseProtoParam { +public: + explicit ProtoBytes32(const Data& data): m_data(data) { + if (data.size() != 32) { + throw std::invalid_argument("Data must be exactly 32 bytes long"); + } + } + + ~ProtoBytes32() override = default; + + AbiProto::Token toToken() const override { + AbiProto::Token proto; + proto.set_byte_array_fix(m_data.data(), m_data.size()); + return proto; + } + +private: + Data m_data; +}; + +class ProtoString final: public BaseProtoParam { +public: + explicit ProtoString(std::string str): m_string(std::move(str)) { + } + + ~ProtoString() override = default; + + AbiProto::Token toToken() const override { + AbiProto::Token proto; + proto.set_string_value(m_string.data(), m_string.size()); + return proto; + } + +private: + std::string m_string; +}; + +class ProtoAddress final: public BaseProtoParam { +public: + ProtoAddress() = default; + + explicit ProtoAddress(std::string addr): m_address(std::move(addr)) { + } + + ~ProtoAddress() override = default; + + AbiProto::Token toToken() const override { + AbiProto::Token proto; + proto.set_address(m_address); + return proto; + } + +private: + std::string m_address {"0x0000000000000000000000000000000000000000"}; +}; + +} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Tuple.cpp b/src/Ethereum/ABI/Tuple.cpp deleted file mode 100644 index 72c6a6a66d2..00000000000 --- a/src/Ethereum/ABI/Tuple.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Tuple.h" - -#include "Data.h" - -#include - -using namespace TW; -using namespace TW::Ethereum::ABI; - - -int ParamTuple::addParam(std::shared_ptr param) { - return _params.addParam(param); -} diff --git a/src/Ethereum/ABI/Tuple.h b/src/Ethereum/ABI/Tuple.h deleted file mode 100644 index b9aca129e28..00000000000 --- a/src/Ethereum/ABI/Tuple.h +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "ParamBase.h" -#include "Parameters.h" -#include "Data.h" - -namespace TW::Ethereum::ABI { - -/// A Tuple is a collection of parameters -class ParamTuple: public ParamCollection -{ -public: - ParamSet _params; - - ParamTuple() {} - ParamTuple(const std::vector>& params) : _params(ParamSet(params)) {} - - /// Add a parameter. Returns the index of the parameter. - int addParam(std::shared_ptr param); - /// Get a parameter. - bool getParam(int paramIndex, std::shared_ptr& param_out) { - return _params.getParam(paramIndex, param_out); - } - /// Return the type signature, of the form "(int32,uint256)" - std::string getType() const { return _params.getType(); } - - virtual size_t getSize() const { return _params.getSize(); } - virtual bool isDynamic() const { return _params.isDynamic(); } - virtual void encode(Data& data) const { return _params.encode(data); } - virtual bool decode(const Data& encoded, size_t& offset_inout) { return _params.decode(encoded, offset_inout); } - virtual bool setValueJson(const std::string& value) { return false; } - virtual size_t getCount() const { return _params.getCount(); } -}; - -} // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ValueDecoder.cpp b/src/Ethereum/ABI/ValueDecoder.cpp index 0bf29705d3f..425f2ce7918 100644 --- a/src/Ethereum/ABI/ValueDecoder.cpp +++ b/src/Ethereum/ABI/ValueDecoder.cpp @@ -1,12 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "ValueDecoder.h" -#include "Array.h" -#include "ParamFactory.h" +#include "proto/EthereumAbi.pb.h" +#include "rust/Wrapper.h" +#include "TrustWalletCore/TWCoinType.h" namespace TW::Ethereum::ABI { @@ -17,29 +16,27 @@ uint256_t ValueDecoder::decodeUInt256(const Data& data) { return load(data); } -std::string ValueDecoder::decodeValue(const Data& data, const std::string& type) { - auto param = ParamFactory::make(type); - if (!param) { +std::string ValueDecoder::decodeValue(const Data& encoded, const std::string& type) { + EthereumAbi::Proto::ValueDecodingInput input; + input.set_encoded(encoded.data(), encoded.size()); + input.set_param_type(type); + + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWDataWrapper outputPtr = Rust::tw_ethereum_abi_decode_value(TWCoinTypeEthereum, inputData.get()); + + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { return ""; } - size_t offset = 0; - if (!param->decode(data, offset)) { + + EthereumAbi::Proto::ValueDecodingOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + if (output.error() != EthereumAbi::Proto::AbiError::OK) { return ""; } - return ParamFactory::getValue(param, param->getType()); -} -std::vector ValueDecoder::decodeArray(const Data& data, const std::string& type) { - auto param = ParamFactory::make(type); - if (!param) { - return std::vector{}; - } - size_t offset = 0; - if (!param->decode(data, offset)) { - return std::vector{}; - } - auto values = ParamFactory::getArrayValue(param, type); - return values; + return output.param_str(); } } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ValueDecoder.h b/src/Ethereum/ABI/ValueDecoder.h index b27a9ecd935..02edf2a254c 100644 --- a/src/Ethereum/ABI/ValueDecoder.h +++ b/src/Ethereum/ABI/ValueDecoder.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -19,7 +17,5 @@ class ValueDecoder { static uint256_t decodeUInt256(const Data& data); // Decode an arbitrary type, return value as string static std::string decodeValue(const Data& data, const std::string& type); - // Decode an array of given simple types; return each element as a string in a vector - static std::vector decodeArray(const Data& data, const std::string& elementType); }; } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ValueEncoder.cpp b/src/Ethereum/ABI/ValueEncoder.cpp index d244a5acbf0..c4db5470c49 100644 --- a/src/Ethereum/ABI/ValueEncoder.cpp +++ b/src/Ethereum/ABI/ValueEncoder.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "ValueEncoder.h" diff --git a/src/Ethereum/ABI/ValueEncoder.h b/src/Ethereum/ABI/ValueEncoder.h index 8463c7bdbbb..178ae693f43 100644 --- a/src/Ethereum/ABI/ValueEncoder.h +++ b/src/Ethereum/ABI/ValueEncoder.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Ethereum/Address.cpp b/src/Ethereum/Address.cpp index 890c180fe74..47a804ea0b5 100644 --- a/src/Ethereum/Address.cpp +++ b/src/Ethereum/Address.cpp @@ -1,15 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" #include "AddressChecksum.h" -#include "../Hash.h" #include "../HexCoding.h" -using namespace TW::Ethereum; +namespace TW::Ethereum { bool Address::isValid(const std::string& string) { if (string.size() != 42 || string[0] != '0' || string[1] != 'x') { @@ -43,5 +40,7 @@ Address::Address(const PublicKey& publicKey) { } std::string Address::string() const { - return checksumed(*this, ChecksumType::eip55); + return checksumed(*this); } + +} // namespace TW::Ethereum diff --git a/src/Ethereum/Address.h b/src/Ethereum/Address.h index c14a56146f1..afac9e1a4de 100644 --- a/src/Ethereum/Address.h +++ b/src/Ethereum/Address.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Ethereum/AddressChecksum.cpp b/src/Ethereum/AddressChecksum.cpp index 65fa5ba458f..e927c272633 100644 --- a/src/Ethereum/AddressChecksum.cpp +++ b/src/Ethereum/AddressChecksum.cpp @@ -1,24 +1,20 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "AddressChecksum.h" -#include "../Hash.h" #include "../HexCoding.h" #include -using namespace TW; -using namespace TW::Ethereum; +namespace TW::Ethereum { -std::string Ethereum::checksumed(const Address& address, enum ChecksumType type) { +std::string checksumed(const Address& address) { const auto addressString = hex(address.bytes); const auto hash = hex(Hash::keccak256(addressString)); std::string string = "0x"; - for (auto i = 0; i < std::min(addressString.size(), hash.size()); i += 1) { + for (auto i = 0ul; i < std::min(addressString.size(), hash.size()); i += 1) { const auto a = addressString[i]; const auto h = hash[i]; if (a >= '0' && a <= '9') { @@ -32,3 +28,5 @@ std::string Ethereum::checksumed(const Address& address, enum ChecksumType type) return string; } + +} // namespace TW::Ethereum diff --git a/src/Ethereum/AddressChecksum.h b/src/Ethereum/AddressChecksum.h index 2aa11e989d4..4d2b78c96c1 100644 --- a/src/Ethereum/AddressChecksum.h +++ b/src/Ethereum/AddressChecksum.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -11,12 +9,6 @@ namespace TW::Ethereum { -/// Checksum types for Ethereum-based blockchains. -enum ChecksumType { - eip55 = 0, - wanchain = 1, -}; - -std::string checksumed(const Address& address, enum ChecksumType type); +std::string checksumed(const Address& address); } // namespace TW::Ethereum diff --git a/src/Ethereum/Barz.cpp b/src/Ethereum/Barz.cpp new file mode 100644 index 00000000000..6002af3692b --- /dev/null +++ b/src/Ethereum/Barz.cpp @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "ABI/ValueEncoder.h" +#include "ABI/Function.h" +#include "AddressChecksum.h" +#include "EIP1014.h" +#include "Hash.h" +#include "HexCoding.h" +#include "../proto/Barz.pb.h" +#include "AsnParser.h" +#include "Base64.h" + +namespace TW::Barz { + +std::string getCounterfactualAddress(const Proto::ContractAddressInput input) { + auto encodedData = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { + std::make_shared(input.account_facet()), + std::make_shared(input.verification_facet()), + std::make_shared(input.entry_point()), + std::make_shared(input.facet_registry()), + std::make_shared(input.default_fallback()), + std::make_shared(parse_hex(input.public_key())), + }); + if (!encodedData.has_value()) { + return {}; + } + + Data initCode = parse_hex(input.bytecode()); + append(initCode, encodedData.value()); + + const Data initCodeHash = Hash::keccak256(initCode); + Data salt = store(input.salt(), 32); + return Ethereum::checksumed(Ethereum::Address(hexEncoded(Ethereum::create2Address(input.factory(), salt, initCodeHash)))); +} + +Data getInitCode(const std::string& factoryAddress, const PublicKey& publicKey, const std::string& verificationFacet, const uint32_t salt) { + auto createAccountFuncEncoded = Ethereum::ABI::Function::encodeFunctionCall("createAccount", Ethereum::ABI::BaseParams { + std::make_shared(verificationFacet), + std::make_shared(publicKey.bytes), + std::make_shared(salt), + }); + if (!createAccountFuncEncoded.has_value()) { + return {}; + } + + Data envelope; + append(envelope, parse_hex(factoryAddress)); + append(envelope, createAccountFuncEncoded.value()); + return envelope; +} + +Data getFormattedSignature(const Data& signature, const Data challenge, const Data& authenticatorData, const std::string& clientDataJSON) { + std::string challengeBase64 = TW::Base64::encodeBase64Url(challenge); + while (challengeBase64.back() == '=') { + challengeBase64.pop_back(); + } + size_t challengePos = clientDataJSON.find(challengeBase64); + if (challengePos == std::string::npos) { + return Data(); + } + + const std::string clientDataJSONPre = clientDataJSON.substr(0, challengePos); + const std::string clientDataJSONPost = clientDataJSON.substr(challengePos + challengeBase64.size()); + + const auto parsedSignatureOptional = ASN::AsnParser::ecdsa_signature_from_der(signature); + if (!parsedSignatureOptional.has_value()) { + return {}; + } + const Data parsedSignature = parsedSignatureOptional.value(); + const Data rValue = subData(parsedSignature, 0, 32); + const Data sValue = subData(parsedSignature, 32, 64); + + auto encoded = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { + std::make_shared(rValue), + std::make_shared(sValue), + std::make_shared(authenticatorData), + std::make_shared(clientDataJSONPre), + std::make_shared(clientDataJSONPost), + }); + + if (encoded.has_value()) { + return encoded.value(); + } + return {}; +} + +Data getPrefixedMsgHash(const Data msgHash, const std::string& barzAddress, const uint32_t chainId) { + // keccak256("EIP712Domain(uint256 chainId,address verifyingContract)"); + const Data& domainSeparatorTypeHashData = parse_hex("0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218"); + // keccak256("BarzMessage(bytes message)") + const Data& barzMsgHashData = parse_hex("0xb1bcb804a4a3a1af3ee7920d949bdfd417ea1b736c3552c8d6563a229a619100"); + const auto signedDataPrefix = "0x1901"; + + auto encodedDomainSeparatorData = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { + std::make_shared(domainSeparatorTypeHashData), + std::make_shared(chainId), + std::make_shared(barzAddress) + }); + if (!encodedDomainSeparatorData.has_value()) { + return {}; + } + + Data domainSeparator = encodedDomainSeparatorData.value(); + const Data domainSeparatorHash = Hash::keccak256(domainSeparator); + + auto encodedRawMessageData = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { + std::make_shared(barzMsgHashData), + std::make_shared(Hash::keccak256(msgHash)), + }); + + Data rawMessageData = encodedRawMessageData.value(); + + auto encodedMsg = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { + std::make_shared(domainSeparatorHash), + std::make_shared(Hash::keccak256(rawMessageData)) + }); + auto encodedMsgData = signedDataPrefix + hex(encodedMsg.value()); + + Data finalEncodedMsgData = parse_hex(encodedMsgData); + + const Data encodedMsgHash = Hash::keccak256(finalEncodedMsgData); + + Data envelope; + append(envelope, encodedMsgHash); + + return envelope; +} + +// Function to encode the diamondCut function call using protobuf message as input +Data getDiamondCutCode(const Proto::DiamondCutInput& input) { + const auto diamondCutSelector = "1f931c1c"; + const auto dataLocationChunk = "60"; + const char defaultPadding = '0'; + Data encoded; + + // function diamondCut( + // FacetCut[] calldata diamondCut, + // address init, + // bytes calldata _calldata // Note that Barz does not use the _calldata for initialization. + // ) + Data encodedSignature = parse_hex(diamondCutSelector); // diamondCut() function selector + encoded.insert(encoded.end(), encodedSignature.begin(), encodedSignature.end()); + + // First argument Data Location `diamondCut` + Data dataLocation = parse_hex(dataLocationChunk); + pad_left(dataLocation, 32); + append(encoded, dataLocation); + + // Encode second Parameter `init` + Data initAddress = parse_hex(input.init_address()); + pad_left(initAddress, 32); + append(encoded, initAddress); + + // Third Argument Data location `_calldata` + auto callDataDataLocation = int(hex(encoded).size()) / 2; + + Ethereum::ABI::ValueEncoder::encodeUInt256(input.facet_cuts_size(), encoded); + + // Prepend the function selector for the diamondCut function + int instructChunk = 0; + int totalInstructChunk = 0; + int prevDataPosition = 0; + const auto encodingChunk = 32; + const auto bytesChunkLine = 5; + int chunkLocation; + Data dataPosition; + // Encode each FacetCut from the input + for (const auto& facetCut : input.facet_cuts()) { + if (instructChunk == 0) { + prevDataPosition = input.facet_cuts_size() * encodingChunk; + Ethereum::ABI::ValueEncoder::encodeUInt256(prevDataPosition, encoded); + chunkLocation = int(hex(encoded).size()) / 2; + } else { + prevDataPosition = prevDataPosition + (instructChunk * encodingChunk); + Ethereum::ABI::ValueEncoder::encodeUInt256(prevDataPosition, dataPosition); + instructChunk = 0; + + encoded.insert(encoded.begin() + chunkLocation, dataPosition.begin(), dataPosition.end()); + ++instructChunk; + } + Ethereum::ABI::ValueEncoder::encodeAddress(parse_hex(facetCut.facet_address()), encoded); // facet address + ++instructChunk; + Ethereum::ABI::ValueEncoder::encodeUInt256(facetCut.action(), encoded); // FacetAction enum + ++instructChunk; + append(encoded, dataLocation); // adding 0x60 DataStorage position + ++instructChunk; + Ethereum::ABI::ValueEncoder::encodeUInt256(facetCut.function_selectors_size(), encoded); // Number of FacetSelector + ++instructChunk; + // Encode and append function selectors + for (const auto& selector : facetCut.function_selectors()) { + Ethereum::ABI::ValueEncoder::encodeBytes(parse_hex(hex(selector)), encoded); + ++instructChunk; + } + totalInstructChunk += instructChunk; + } + + Data calldataLength; + Ethereum::ABI::ValueEncoder::encodeUInt256((totalInstructChunk * encodingChunk) + (bytesChunkLine * encodingChunk), calldataLength); + + encoded.insert(encoded.begin() + callDataDataLocation, calldataLength.begin(), calldataLength.end()); + + auto initDataSize = int(hex(parse_hex(input.init_data())).size()); + if (initDataSize == 0 || initDataSize % 2 != 0) + return {}; + + auto initDataLength = initDataSize / 2; // 1 byte is encoded into 2 char + Ethereum::ABI::ValueEncoder::encodeUInt256(initDataLength, encoded); + + append(encoded, parse_hex(input.init_data())); + + const int paddingLength = (encodingChunk * 2) - (initDataSize % (encodingChunk * 2)); + const std::string padding(paddingLength, defaultPadding); + append(encoded, parse_hex(padding)); + + return encoded; +} + +} // namespace TW::Barz diff --git a/src/Ethereum/Barz.h b/src/Ethereum/Barz.h new file mode 100644 index 00000000000..f29afc4eeed --- /dev/null +++ b/src/Ethereum/Barz.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "uint256.h" +#include "PublicKey.h" +#include "../proto/Barz.pb.h" + +namespace TW::Barz { + +std::string getCounterfactualAddress(const Proto::ContractAddressInput input); +Data getInitCode(const std::string& factoryAddress, const PublicKey& publicKey, const std::string& verificationFacet, const uint32_t salt); +Data getFormattedSignature(const Data& signature, const Data challenge, const Data& authenticatorData, const std::string& clientDataJSON); +Data getPrefixedMsgHash(const Data msgHash, const std::string& barzAddress, const uint32_t chainId); +Data getDiamondCutCode(const Proto::DiamondCutInput& input); // action should be one of 0, 1, 2. 0 = Add, 1 = Remove, 2 = Replace + +} diff --git a/src/Ethereum/ContractCall.cpp b/src/Ethereum/ContractCall.cpp index 722ca4b68aa..727a21d192b 100644 --- a/src/Ethereum/ContractCall.cpp +++ b/src/Ethereum/ContractCall.cpp @@ -1,131 +1,38 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "ContractCall.h" -#include "ABI.h" #include "HexCoding.h" -#include "uint256.h" -#include +#include "proto/EthereumAbi.pb.h" +#include "TrustWalletCore/TWCoinType.h" using namespace std; using json = nlohmann::json; namespace TW::Ethereum::ABI { -static void fillArray(ParamSet& paramSet, const string& type) { - auto baseType = string(type.begin(), type.end() - 2); - auto value = ParamFactory::make(baseType); - auto param = make_shared(value); - paramSet.addParam(param); -} - -static void fill(ParamSet& paramSet, const string& type) { - if (boost::algorithm::ends_with(type, "[]")) { - fillArray(paramSet, type); - } else { - auto param = ParamFactory::make(type); - paramSet.addParam(param); - } -} - -static vector getArrayValue(ParamSet& paramSet, const string& type, int idx) { - shared_ptr param; - paramSet.getParam(idx, param); - return ParamFactory::getArrayValue(param, type); -} - -static json buildInputs(ParamSet& paramSet, const json& registry); // forward - -static json getTupleValue(ParamSet& paramSet, const string& type, int idx, const json& typeInfo) { - shared_ptr param; - paramSet.getParam(idx, param); - auto paramTuple = dynamic_pointer_cast(param); - if (!paramTuple.get()) { - return {}; - } - return buildInputs(paramTuple->_params, typeInfo["components"]); -} - -static string getValue(ParamSet& paramSet, const string& type, int idx) { - shared_ptr param; - paramSet.getParam(idx, param); - return ParamFactory::getValue(param, type); -} - -static json buildInputs(ParamSet& paramSet, const json& registry) { - auto inputs = json::array(); - for (int i = 0; i < registry.size(); i++) { - auto info = registry[i]; - auto type = info["type"]; - auto input = json{ - {"name", info["name"]}, - {"type", type} - }; - if (boost::algorithm::ends_with(type.get(), "[]")) { - input["value"] = json(getArrayValue(paramSet, type, i)); - } else if (type == "tuple") { - input["components"] = getTupleValue(paramSet, type, i, info); - } else if (type == "bool") { - input["value"] = getValue(paramSet, type, i) == "true" ? json(true) : json(false); - } else { - input["value"] = getValue(paramSet, type, i); - } - inputs.push_back(input); - } - return inputs; -} - -void fillTuple(ParamSet& paramSet, const json& jsonSet); // forward - -void decodeParamSet(ParamSet& paramSet, const json& jsonSet) { - for (auto& comp : jsonSet) { - if (comp["type"] == "tuple") { - fillTuple(paramSet, comp["components"]); - } else { - fill(paramSet, comp["type"]); - } - } -} - -void fillTuple(ParamSet& paramSet, const json& jsonSet) { - std::shared_ptr param = make_shared(); - decodeParamSet(param->_params, jsonSet); - paramSet.addParam(param); -} - -optional decodeCall(const Data& call, const json& abi) { - // check bytes length - if (call.size() <= 4) { - return {}; - } +optional decodeCall(const Data& call, const std::string& abi) { + EthereumAbi::Proto::ContractCallDecodingInput input; + input.set_encoded(call.data(), call.size()); + input.set_smart_contract_abi_json(abi); - auto methodId = hex(Data(call.begin(), call.begin() + 4)); + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWDataWrapper outputPtr = Rust::tw_ethereum_abi_decode_contract_call(TWCoinTypeEthereum, inputData.get()); - if (abi.find(methodId) == abi.end()) { + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { return {}; } - // build Function with types - const auto registry = abi[methodId]; - auto func = Function(registry["name"]); - decodeParamSet(func._inParams, registry["inputs"]); + EthereumAbi::Proto::ContractCallDecodingOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); - // decode inputs - size_t offset = 0; - auto success = func.decodeInput(call, offset); - if (!success) { + if (output.error() != EthereumAbi::Proto::AbiError::OK) { return {}; } - // build output json - auto decoded = json{ - {"function", func.getType()}, - {"inputs", buildInputs(func._inParams, registry["inputs"])}, - }; - return decoded.dump(); + return output.decoded_json(); } } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ContractCall.h b/src/Ethereum/ContractCall.h index ca7b5c44718..abbb0a0fce5 100644 --- a/src/Ethereum/ContractCall.h +++ b/src/Ethereum/ContractCall.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,5 +10,5 @@ #include namespace TW::Ethereum::ABI { - std::optional decodeCall(const Data& call, const nlohmann::json& abi); + std::optional decodeCall(const Data& call, const std::string& abi); } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/EIP1014.cpp b/src/Ethereum/EIP1014.cpp new file mode 100644 index 00000000000..7a72b3e0e51 --- /dev/null +++ b/src/Ethereum/EIP1014.cpp @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "EIP1014.h" +#include "AddressChecksum.h" +#include "Hash.h" +#include "HexCoding.h" + +namespace TW::Ethereum { + +Data create2Address(const std::string& from, const Data& salt, const Data& initCodeHash) { + if (salt.size() != 32) { + throw std::runtime_error("Error: salt must be 32 bytes."); + } + if (initCodeHash.size() != 32) { + throw std::runtime_error("Error: initCodeHash must be 32 bytes."); + } + Data input = {0xff}; + append(input, parse_hex(from)); + append(input, salt); + append(input, initCodeHash); + auto hash = Hash::keccak256(input); + return Data(hash.end() - 20, hash.end()); +} + +} // namespace TW::Ethereum diff --git a/src/Ethereum/EIP1014.h b/src/Ethereum/EIP1014.h new file mode 100644 index 00000000000..7471a9052e8 --- /dev/null +++ b/src/Ethereum/EIP1014.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" + +namespace TW::Ethereum { + +Data create2Address(const std::string& from, const Data& salt, const Data& initCodeHash); + +} diff --git a/src/Ethereum/EIP1967.cpp b/src/Ethereum/EIP1967.cpp new file mode 100644 index 00000000000..4fe8d527b08 --- /dev/null +++ b/src/Ethereum/EIP1967.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "EIP1014.h" +#include "ABI/Function.h" +#include "Hash.h" +#include "HexCoding.h" + +namespace TW::Ethereum { + +static const std::string eip1967ProxyBytecodeHex = R"(0x608060405260405162000c5138038062000c51833981810160405281019062000029919062000580565b6200003d828260006200004560201b60201c565b5050620007d7565b62000056836200008860201b60201c565b600082511180620000645750805b156200008357620000818383620000df60201b620000371760201c565b505b505050565b62000099816200011560201b60201c565b8073ffffffffffffffffffffffffffffffffffffffff167fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b60405160405180910390a250565b60606200010d838360405180606001604052806027815260200162000c2a60279139620001eb60201b60201c565b905092915050565b6200012b816200027d60201b620000641760201c565b6200016d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000164906200066d565b60405180910390fd5b80620001a77f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b620002a060201b620000871760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60606000808573ffffffffffffffffffffffffffffffffffffffff1685604051620002179190620006dc565b600060405180830381855af49150503d806000811462000254576040519150601f19603f3d011682016040523d82523d6000602084013e62000259565b606091505b50915091506200027286838387620002aa60201b60201c565b925050509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b606083156200031a5760008351036200031157620002ce856200027d60201b60201c565b62000310576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620003079062000745565b60405180910390fd5b5b8290506200032d565b6200032c83836200033560201b60201c565b5b949350505050565b600082511115620003495781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200037f9190620007b3565b60405180910390fd5b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620003c9826200039c565b9050919050565b620003db81620003bc565b8114620003e757600080fd5b50565b600081519050620003fb81620003d0565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b62000456826200040b565b810181811067ffffffffffffffff821117156200047857620004776200041c565b5b80604052505050565b60006200048d62000388565b90506200049b82826200044b565b919050565b600067ffffffffffffffff821115620004be57620004bd6200041c565b5b620004c9826200040b565b9050602081019050919050565b60005b83811015620004f6578082015181840152602081019050620004d9565b60008484015250505050565b6000620005196200051384620004a0565b62000481565b90508281526020810184848401111562000538576200053762000406565b5b62000545848285620004d6565b509392505050565b600082601f83011262000565576200056462000401565b5b81516200057784826020860162000502565b91505092915050565b600080604083850312156200059a576200059962000392565b5b6000620005aa85828601620003ea565b925050602083015167ffffffffffffffff811115620005ce57620005cd62000397565b5b620005dc858286016200054d565b9150509250929050565b600082825260208201905092915050565b7f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60008201527f6f74206120636f6e747261637400000000000000000000000000000000000000602082015250565b600062000655602d83620005e6565b91506200066282620005f7565b604082019050919050565b60006020820190508181036000830152620006888162000646565b9050919050565b600081519050919050565b600081905092915050565b6000620006b2826200068f565b620006be81856200069a565b9350620006d0818560208601620004d6565b80840191505092915050565b6000620006ea8284620006a5565b915081905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b60006200072d601d83620005e6565b91506200073a82620006f5565b602082019050919050565b6000602082019050818103600083015262000760816200071e565b9050919050565b600081519050919050565b60006200077f8262000767565b6200078b8185620005e6565b93506200079d818560208601620004d6565b620007a8816200040b565b840191505092915050565b60006020820190508181036000830152620007cf818462000772565b905092915050565b61044380620007e76000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b610025610091565b610035610030610093565b6100a2565b565b606061005c83836040518060600160405280602781526020016103e7602791396100c8565b905092915050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b565b600061009d61014e565b905090565b3660008037600080366000845af43d6000803e80600081146100c3573d6000f35b3d6000fd5b60606000808573ffffffffffffffffffffffffffffffffffffffff16856040516100f291906102db565b600060405180830381855af49150503d806000811461012d576040519150601f19603f3d011682016040523d82523d6000602084013e610132565b606091505b5091509150610143868383876101a5565b925050509392505050565b600061017c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b610087565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606083156102075760008351036101ff576101bf85610064565b6101fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101f59061034f565b60405180910390fd5b5b829050610212565b610211838361021a565b5b949350505050565b60008251111561022d5781518083602001fd5b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161026191906103c4565b60405180910390fd5b600081519050919050565b600081905092915050565b60005b8381101561029e578082015181840152602081019050610283565b60008484015250505050565b60006102b58261026a565b6102bf8185610275565b93506102cf818560208601610280565b80840191505092915050565b60006102e782846102aa565b915081905092915050565b600082825260208201905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b6000610339601d836102f2565b915061034482610303565b602082019050919050565b600060208201905081810360008301526103688161032c565b9050919050565b600081519050919050565b6000601f19601f8301169050919050565b60006103968261036f565b6103a081856102f2565b93506103b0818560208601610280565b6103b98161037a565b840191505092915050565b600060208201905081810360008301526103de818461038b565b90509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e57dd3eafc9985be746025b6d82d4f011b9a7bb3db56f9a1eb7eadfddd376b6064736f6c63430008110033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564)"; + +Data getEIP1967ProxyInitCode(const std::string& logicAddress, const Data& data) { + Data initCode = parse_hex(eip1967ProxyBytecodeHex); + + auto proxyConstructorParamsEncoded = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { + std::make_shared(logicAddress), + std::make_shared(data), + }); + + if (!proxyConstructorParamsEncoded.has_value()) { + return {}; + } + + append(initCode, proxyConstructorParamsEncoded.value()); + return initCode; +} + +} // namespace TW::Ethereum diff --git a/src/Ethereum/EIP1967.h b/src/Ethereum/EIP1967.h new file mode 100644 index 00000000000..04e37fffdf8 --- /dev/null +++ b/src/Ethereum/EIP1967.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" + +namespace TW::Ethereum { + +Data getEIP1967ProxyInitCode(const std::string& logicAddress, const Data& data); + +} diff --git a/src/Ethereum/EIP2645.cpp b/src/Ethereum/EIP2645.cpp new file mode 100644 index 00000000000..c9ae7aa9bde --- /dev/null +++ b/src/Ethereum/EIP2645.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include + +#include +#include + +namespace TW::Ethereum::internal { + +static std::string getIntFromBits(const std::string& hexString, std::size_t from, std::optional length = std::nullopt) { + const auto data = hex_str_to_bin_str(hexString); + const auto sub = data.substr(data.size() - from, length.value_or(std::string::npos)); + return std::to_string(std::stoll(sub, nullptr, 2)); +} + +} // namespace TW::Ethereum::internal + +namespace TW::Ethereum { + +// https://docs.starkware.co/starkex/key-derivation.html +std::string accountPathFromAddress(const std::string& ethAddress, const std::string& layer, const std::string& application, const std::string& index) noexcept { + using namespace internal; + std::stringstream out; + const auto layerHash = getIntFromBits(hex(Hash::sha256(data(layer))), 31); + const auto applicationHash = getIntFromBits(hex(Hash::sha256(data(application))), 31); + const auto ethAddress1 = getIntFromBits(ethAddress.substr(2), 31); + const auto ethAddress2 = getIntFromBits(ethAddress.substr(2), 62, 31); + out << "m/2645'/" << layerHash << "'/" << applicationHash << "'/" << ethAddress1 << "'/" << ethAddress2 << "'/" << index; + return out.str(); +} + +} // namespace TW::Ethereum diff --git a/src/Ethereum/EIP2645.h b/src/Ethereum/EIP2645.h new file mode 100644 index 00000000000..e5d51a85f24 --- /dev/null +++ b/src/Ethereum/EIP2645.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include + +namespace TW::Ethereum { + +std::string accountPathFromAddress(const std::string& ethAddress, const std::string& layer, const std::string& application, const std::string& index) noexcept;; + +} // namespace TW::Ethereum diff --git a/src/Ethereum/Entry.cpp b/src/Ethereum/Entry.cpp index 1382f420d9d..6560b3969f7 100644 --- a/src/Ethereum/Entry.cpp +++ b/src/Ethereum/Entry.cpp @@ -1,92 +1,22 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" -#include "Signer.h" +#include "HexCoding.h" +#include "proto/Ethereum.pb.h" -#include "proto/TransactionCompiler.pb.h" +namespace TW::Ethereum { -using namespace TW::Ethereum; -using namespace TW; -using namespace std; - -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { - return Address::isValid(address); -} - -string Entry::normalizeAddress(TWCoinType coin, const string& address) const { - // normalized with EIP55 checksum - return Address(address).string(); -} - -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { - return Address(publicKey).string(); -} - -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { - const auto addr = Address(address); - return {addr.bytes.begin(), addr.bytes.end()}; -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); -} - -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { - return Signer::signJSON(json, key); +std::string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { + return signJSONHelper( + coin, + json, + key, + [](const Proto::SigningOutput& output) { return hex(output.encoded()); } + ); } -Data Entry::preImageHashes(TWCoinType coin, const Data& txInputData) const { - return txCompilerTemplate( - txInputData, [](const auto& input, auto& output) { - const auto transaction = Signer::build(input); - const auto chainId = load(data(input.chain_id())); // retrieve chainId from input - auto preHash = transaction->preHash(chainId); - auto preImage = transaction->serialize(chainId); - output.set_data_hash(preHash.data(), preHash.size()); - output.set_data(preImage.data(), preImage.size()); - }); -} - -void Entry::compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { - dataOut = txCompilerTemplate( - txInputData, [&](const auto& input, auto& output) { - if (signatures.size() != 1) { - output.set_error(Common::Proto::Error_signatures_count); - output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_signatures_count)); - return; - } - output = Signer::compile(input, signatures[0]); - }); -} - -Data Entry::buildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) const { - Proto::SigningInput input; - - auto chainIdData = store(uint256_t(1)); - if (chainId.length() > 0) { - // parse amount - uint256_t chainIdUint256 { chainId }; - chainIdData = store(chainIdUint256); - } - input.set_chain_id(chainIdData.data(), chainIdData.size()); - - if (!Address::isValid(to)) { - throw std::invalid_argument("Invalid to address"); - } - input.set_to_address(to); - - auto& transfer = *input.mutable_transaction()->mutable_transfer(); - const auto amountData = store(amount); - transfer.set_amount(amountData.data(), amountData.size()); - - // not set: nonce, gasPrice, gasLimit, tx_mode (need to be set afterwards) - - const auto txInputData = data(input.SerializeAsString()); - return txInputData; -} +} // namespace TW::Ethereum diff --git a/src/Ethereum/Entry.h b/src/Ethereum/Entry.h index 110a7ed1671..69d67808596 100644 --- a/src/Ethereum/Entry.h +++ b/src/Ethereum/Entry.h @@ -1,30 +1,19 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../CoinEntry.h" +#include "rust/RustCoinEntry.h" namespace TW::Ethereum { /// Entry point for Ethereum and Ethereum-fork coins. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry : public Rust::RustCoinEntryWithSignJSON { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string normalizeAddress(TWCoinType coin, const std::string& address) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; - - virtual Data preImageHashes(TWCoinType coin, const Data& txInputData) const; - virtual void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; - virtual Data buildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) const; + bool supportsJSONSigning() const final { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const final; }; } // namespace TW::Ethereum diff --git a/src/Ethereum/MessageSigner.cpp b/src/Ethereum/MessageSigner.cpp new file mode 100644 index 00000000000..a50863c9d15 --- /dev/null +++ b/src/Ethereum/MessageSigner.cpp @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "MessageSigner.h" +#include +#include +#include "rust/Wrapper.h" +#include "TrustWalletCore/TWCoinType.h" + +namespace TW::Ethereum { + +std::string signMessageRust(const PrivateKey& privateKey, const std::string& message, Proto::MessageType msgType, MessageSigner::MaybeChainId chainId) { + Proto::MessageSigningInput input; + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_message(message); + input.set_message_type(msgType); + + if (chainId.has_value()) { + input.mutable_chain_id()->set_chain_id(static_cast(chainId.value())); + } + + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWDataWrapper outputPtr = Rust::tw_message_signer_sign(inputData.get(), TWCoinTypeEthereum); + + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { + return {}; + } + + Proto::MessageSigningOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + if (output.error() != Common::Proto::SigningError::OK) { + return {}; + } + return output.signature(); +} + +Data messagePreImageHashRust(const std::string& message, Proto::MessageType msgType) { + Proto::MessageSigningInput input; + input.set_message(message); + input.set_message_type(msgType); + + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWDataWrapper outputPtr = Rust::tw_message_signer_pre_image_hashes(inputData.get(), TWCoinTypeEthereum); + + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { + return {}; + } + + TxCompiler::Proto::PreSigningOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + if (output.error() != Common::Proto::SigningError::OK) { + return {}; + } + return data(output.data_hash()); +} + +bool verifyMessageRust(const PublicKey& publicKey, const std::string& message, const std::string& signature) { + Proto::MessageVerifyingInput input; + input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + input.set_message(message); + input.set_signature(signature); + + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + return Rust::tw_message_signer_verify(inputData.get(), TWCoinTypeEthereum); +} + +std::string MessageSigner::signMessage(const PrivateKey& privateKey, const std::string& message, MessageType msgType, MaybeChainId chainId) { + auto protoMsgType = Proto::MessageType::MessageType_legacy; + switch (msgType) { + case MessageType::Eip155: { + protoMsgType = Proto::MessageType::MessageType_eip155; + break; + } + case MessageType::ImmutableX: { + protoMsgType = Proto::MessageType::MessageType_immutable_x; + break; + } + default: { + break; + } + } + + return signMessageRust(privateKey, message, protoMsgType, chainId); +} + +std::string MessageSigner::signTypedData(const PrivateKey& privateKey, const std::string& data, MessageType msgType, MessageSigner::MaybeChainId chainId) { + auto protoMsgType = Proto::MessageType::MessageType_typed; + switch (msgType) { + case MessageType::Eip155: { + protoMsgType = Proto::MessageType::MessageType_typed_eip155; + break; + } + default: { + break; + } + } + + return signMessageRust(privateKey, data, protoMsgType, chainId); +} + +bool MessageSigner::verifyMessage(const PublicKey& publicKey, const std::string& message, const std::string& signature) noexcept { + return verifyMessageRust(publicKey, message, signature); +} + +Data MessageSigner::messagePreImageHash(const std::string& message) noexcept { + return messagePreImageHashRust(message, Proto::MessageType::MessageType_legacy); +} + +Data MessageSigner::typedDataPreImageHash(const std::string& data) noexcept { + return messagePreImageHashRust(data, Proto::MessageType::MessageType_typed); +} + +void MessageSigner::prepareSignature(Data& signature, MessageType msgType, TW::Ethereum::MessageSigner::MaybeChainId chainId) noexcept { + switch (msgType) { + case MessageType::ImmutableX: { + break; + } + case MessageType::Legacy: { + signature[64] += 27; + break; + } + case MessageType::Eip155: { + auto id = chainId.value_or(0); + signature[64] += 35 + id * 2; + break; + } + default: + break; + } +} + +} // namespace TW::Ethereum diff --git a/src/Ethereum/MessageSigner.h b/src/Ethereum/MessageSigner.h new file mode 100644 index 00000000000..fc45e49c152 --- /dev/null +++ b/src/Ethereum/MessageSigner.h @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include +#include + +namespace TW::Ethereum { + +enum class MessageType { + Legacy = 1, + Eip155 = 2, + ImmutableX = 3 +}; + +class MessageSigner { +public: + using MaybeChainId = std::optional; + /// Sign a message following EIP-191 + /// \param privateKey the private key to sign with + /// \param message message to sign + /// \param msgType message type to sign + /// \param chainId optional chainId if msgType is eip155 + /// \return hex signed message + static std::string signMessage(const PrivateKey& privateKey, const std::string& message, MessageType msgType, MaybeChainId chainId = std::nullopt); + + /// Sign typed data according to EIP-712 V4 + /// \param privateKey the private key to sign with + /// \param data json data + /// \param msgType message type to sign + /// \param chainId optional chainId if msgType is eip155 + /// \return hex signed message + static std::string signTypedData(const PrivateKey& privateKey, const std::string& data, MessageType msgType, MaybeChainId chainId = std::nullopt); + + /// Verify a message following EIP-191 + /// \param publicKey publickey to verify the signed message + /// \param message message to be verified as a string + /// \param signature signature to verify the message against + /// \return true if the message match the signature, false otherwise + static bool verifyMessage(const PublicKey& publicKey, const std::string& message, const std::string& signature) noexcept; + + /// Computes a hash of the message following EIP-191. + /// \param message message to hash + /// \return hash of the tuped data. + static Data messagePreImageHash(const std::string& message) noexcept; + + /// Computes a hash of the typed data according to EIP-712 V4. + /// \param data json data + /// \return hash of the tuped data. + static Data typedDataPreImageHash(const std::string& data) noexcept; + + static void prepareSignature(Data& signature, MessageType msgType, MaybeChainId chainId = std::nullopt) noexcept; +}; + +} // namespace TW::Ethereum diff --git a/src/Ethereum/RLP.cpp b/src/Ethereum/RLP.cpp index 4a4b7bcba6c..8a543c1e8de 100644 --- a/src/Ethereum/RLP.cpp +++ b/src/Ethereum/RLP.cpp @@ -1,195 +1,44 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "RLP.h" -#include "../Data.h" -#include "../uint256.h" -#include "../BinaryCoding.h" +#include "BinaryCoding.h" +#include "TrustWalletCore/TWCoinType.h" +#include "rust/Wrapper.h" #include -using namespace TW; -using namespace TW::Ethereum; +namespace TW::Ethereum { -Data RLP::encode(const uint256_t& value) noexcept { - using boost::multiprecision::cpp_int; +Data RLP::encode(const EthereumRlp::Proto::EncodingInput& input) { + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWDataWrapper outputPtr = Rust::tw_ethereum_rlp_encode(TWCoinTypeEthereum, inputData.get()); - Data bytes; - export_bits(value, std::back_inserter(bytes), 8); - - if (bytes.empty() || (bytes.size() == 1 && bytes[0] == 0)) { - return {0x80}; + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { + return {}; } - return encode(bytes); -} + EthereumRlp::Proto::EncodingOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); -Data RLP::encodeList(const Data& encoded) noexcept { - auto result = encodeHeader(encoded.size(), 0xc0, 0xf7); - result.reserve(result.size() + encoded.size()); - result.insert(result.end(), encoded.begin(), encoded.end()); - return result; + return data(output.encoded()); } -Data RLP::encode(const Data& data) noexcept { - if (data.size() == 1 && data[0] <= 0x7f) { - // Fits in single byte, no header - return data; - } - - auto encoded = encodeHeader(data.size(), 0x80, 0xb7); - encoded.insert(encoded.end(), data.begin(), data.end()); - return encoded; +Data RLP::encodeString(const std::string& s) { + EthereumRlp::Proto::EncodingInput input; + input.mutable_item()->set_string_item(s); + return encode(input); } -Data RLP::encodeHeader(uint64_t size, uint8_t smallTag, uint8_t largeTag) noexcept { - if (size < 56) { - return {static_cast(smallTag + size)}; - } - - const auto sizeData = putVarInt(size); - - auto header = Data(); - header.reserve(1 + sizeData.size()); - header.push_back(largeTag + static_cast(sizeData.size())); - header.insert(header.end(), sizeData.begin(), sizeData.end()); - return header; -} +Data RLP::encodeU256(const uint256_t & num) { + auto numData = store(num); -Data RLP::putVarInt(uint64_t i) noexcept { - Data bytes; // accumulate bytes here, in reverse order - do { - // take LSB byte, append - bytes.push_back(i & 0xff); - i = i >> 8; - } while (i); - assert(bytes.size() >= 1 && bytes.size() <= 8); - std::reverse(bytes.begin(), bytes.end()); - return bytes; + EthereumRlp::Proto::EncodingInput input; + input.mutable_item()->set_number_u256(numData.data(), numData.size()); + return encode(input); } -uint64_t RLP::parseVarInt(size_t size, const Data& data, size_t index) { - if (size < 1 || size > 8) { - throw std::invalid_argument("invalid length length"); - } - if (data.size() - index < size) { - throw std::invalid_argument("Not enough data for varInt"); - } - if (size >= 2 && data[index] == 0) { - throw std::invalid_argument("multi-byte length must have no leading zero"); - } - uint64_t val = 0; - for (auto i = 0; i < size; ++i) { - val = val << 8; - val += data[index + i]; - } - return static_cast(val); -} - -RLP::DecodedItem RLP::decodeList(const Data& input) { - RLP::DecodedItem item; - auto remainder = input; - while(true) { - auto listItem = RLP::decode(remainder); - item.decoded.push_back(listItem.decoded[0]); - if (listItem.remainder.size() == 0) { - break; - } else { - remainder = listItem.remainder; - } - } - return item; -} - -RLP::DecodedItem RLP::decode(const Data& input) { - if (input.size() == 0) { - throw std::invalid_argument("can't decode empty rlp data"); - } - RLP::DecodedItem item; - auto inputLen = input.size(); - auto prefix = input[0]; - if (prefix <= 0x7f) { - // 00--7f: a single byte whose value is in the [0x00, 0x7f] range, that byte is its own RLP encoding. - item.decoded.push_back(Data{input[0]}); - item.remainder = subData(input, 1); - return item; - } - if (prefix <= 0xb7) { - // 80--b7: short string - // string is 0-55 bytes long. A single byte with value 0x80 plus the length of the string followed by the string - // The range of the first byte is [0x80, 0xb7] - - // empty string - if (prefix == 0x80) { - item.decoded.emplace_back(Data()); - item.remainder = subData(input, 1); - return item; - } - - auto strLen = prefix - 0x80; - if (strLen == 1 && input[1] <= 0x7f) { - throw std::invalid_argument("single byte below 128 must be encoded as itself"); - } - - if (inputLen < (1 + strLen)) { - throw std::invalid_argument(std::string("invalid short string, length ") + std::to_string(strLen)); - } - item.decoded.push_back(subData(input, 1, strLen)); - item.remainder = subData(input, 1 + strLen); - - return item; - } - if (prefix <= 0xbf) { - // b8--bf: long string - auto lenOfStrLen = size_t(prefix - 0xb7); - auto strLen = static_cast(parseVarInt(lenOfStrLen, input, 1)); - if (inputLen < lenOfStrLen || inputLen < (1 + lenOfStrLen + strLen)) { - throw std::invalid_argument(std::string("Invalid rlp encoding length, length ") + std::to_string(strLen)); - } - auto data = subData(input, 1 + lenOfStrLen, strLen); - item.decoded.push_back(data); - item.remainder = subData(input, 1 + lenOfStrLen + strLen); - return item; - } - if (prefix <= 0xf7) { - // c0--f7: a list between 0-55 bytes long - auto listLen = size_t(prefix - 0xc0); - if (inputLen < (1 + listLen)) { - throw std::invalid_argument(std::string("Invalid rlp string length, length ") + std::to_string(listLen)); - } - // empty list - if (listLen == 0) { - item.remainder = subData(input, 1); - return item; - } - - // decode list - auto listItem = decodeList(subData(input, 1, listLen)); - for (auto& data : listItem.decoded) { - item.decoded.push_back(data); - } - item.remainder = subData(input, 1 + listLen); - return item; - } - // f8--ff - auto lenOfListLen = size_t(prefix - 0xf7); - auto listLen = static_cast(parseVarInt(lenOfListLen, input, 1)); - if (listLen < 56) { - throw std::invalid_argument("length below 56 must be encoded in one byte"); - } - if (inputLen < lenOfListLen || inputLen < (1 + lenOfListLen + listLen)) { - throw std::invalid_argument(std::string("Invalid rlp list length, length ") + std::to_string(listLen)); - } - - // decode list - auto listItem = decodeList(subData(input, 1 + lenOfListLen, listLen)); - for (auto& data : listItem.decoded) { - item.decoded.push_back(data); - } - item.remainder = subData(input, 1 + lenOfListLen + listLen); - return item; -} +} // namespace TW::Ethereum diff --git a/src/Ethereum/RLP.h b/src/Ethereum/RLP.h index 7bdc17c60ed..5163d339514 100644 --- a/src/Ethereum/RLP.h +++ b/src/Ethereum/RLP.h @@ -1,14 +1,12 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "Transaction.h" -#include "../Data.h" -#include "../uint256.h" +#include "Data.h" +#include "proto/EthereumRlp.pb.h" +#include "uint256.h" #include #include @@ -20,87 +18,11 @@ namespace TW::Ethereum { /// /// - SeeAlso: https://github.com/ethereum/wiki/wiki/RLP struct RLP { - /// Encodes a string; - static Data encode(const std::string& string) noexcept { - return encode(Data(string.begin(), string.end())); - } + static Data encode(const EthereumRlp::Proto::EncodingInput& input); - static Data encode(uint8_t number) noexcept { return encode(uint256_t(number)); } + static Data encodeString(const std::string& s); - static Data encode(uint16_t number) noexcept { return encode(uint256_t(number)); } - - static Data encode(int32_t number) noexcept { - if (number < 0) { - return {}; // RLP cannot encode negative numbers - } - return encode(static_cast(number)); - } - - static Data encode(uint32_t number) noexcept { return encode(uint256_t(number)); } - - static Data encode(int64_t number) noexcept { - if (number < 0) { - return {}; // RLP cannot encode negative numbers - } - return encode(static_cast(number)); - } - - static Data encode(uint64_t number) noexcept { return encode(uint256_t(number)); } - - static Data encode(const uint256_t& number) noexcept; - - /// Wraps encoded data as a list. - static Data encodeList(const Data& encoded) noexcept; - - /// Encodes a block of data. - static Data encode(const Data& data) noexcept; - - /// Encodes a static array. - template - static Data encode(const std::array& data) noexcept { - if (N == 1 && data[0] <= 0x7f) { - // Fits in single byte, no header - return Data(data.begin(), data.end()); - } - - auto encoded = encodeHeader(data.size(), 0x80, 0xb7); - encoded.insert(encoded.end(), data.begin(), data.end()); - return encoded; - } - - /// Encodes a list of elements. - template - static Data encodeList(T elements) noexcept { - auto encodedData = Data(); - for (const auto& el : elements) { - auto encoded = encode(el); - if (encoded.empty()) { - return {}; - } - encodedData.insert(encodedData.end(), encoded.begin(), encoded.end()); - } - - auto encoded = encodeHeader(encodedData.size(), 0xc0, 0xf7); - encoded.insert(encoded.end(), encodedData.begin(), encodedData.end()); - return encoded; - } - - /// Encodes a list header. - static Data encodeHeader(uint64_t size, uint8_t smallTag, uint8_t largeTag) noexcept; - - struct DecodedItem { - std::vector decoded; - Data remainder; - }; - - static DecodedItem decodeList(const Data& input); - /// Decodes data, remainder from RLP encoded data - static DecodedItem decode(const Data& data); - - /// Returns the representation of an integer using the least number of bytes needed, between 1 and 8 bytes, big endian - static Data putVarInt(uint64_t i) noexcept; - /// Parses an integer of given size, between 1 and 8 bytes, big endian - static uint64_t parseVarInt(size_t size, const Data& data, size_t index); + static Data encodeU256(const uint256_t& num); }; } // namespace TW::Ethereum diff --git a/src/Ethereum/Signer.cpp b/src/Ethereum/Signer.cpp deleted file mode 100644 index 6e743fcf1cb..00000000000 --- a/src/Ethereum/Signer.cpp +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Signer.h" -#include "HexCoding.h" -#include "Coin.h" -#include -#include - -using namespace TW; -using namespace TW::Ethereum; - -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - try { - uint256_t chainID = load(input.chain_id()); - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto transaction = Signer::build(input); - - auto signature = sign(key, chainID, transaction); - - auto output = Proto::SigningOutput(); - - auto encoded = transaction->encoded(signature, chainID); - output.set_encoded(encoded.data(), encoded.size()); - - auto v = store(signature.v, 1); - output.set_v(v.data(), v.size()); - auto r = store(signature.r, 32); - output.set_r(r.data(), r.size()); - auto s = store(signature.s, 32); - output.set_s(s.data(), s.size()); - - output.set_data(transaction->payload.data(), transaction->payload.size()); - - return output; - } catch (std::exception&) { - return Proto::SigningOutput(); - } -} - -std::string Signer::signJSON(const std::string& json, const Data& key) { - auto input = Proto::SigningInput(); - google::protobuf::util::JsonStringToMessage(json, &input); - input.set_private_key(key.data(), key.size()); - auto output = Signer::sign(input); - return hex(output.encoded()); -} - -Proto::SigningOutput Signer::compile(const Proto::SigningInput& input, const Data& signature) noexcept { - try { - uint256_t chainID = load(input.chain_id()); - auto transaction = Signer::build(input); - - // prepare Signature - const Signature sigStruct = signatureDataToStruct(signature, transaction->usesReplayProtection(), chainID); - - auto output = Proto::SigningOutput(); - - auto encoded = transaction->encoded(sigStruct, chainID); - output.set_encoded(encoded.data(), encoded.size()); - - auto v = store(sigStruct.v, 1); - output.set_v(v.data(), v.size()); - auto r = store(sigStruct.r, 32); - output.set_r(r.data(), r.size()); - auto s = store(sigStruct.s, 32); - output.set_s(s.data(), s.size()); - - output.set_data(transaction->payload.data(), transaction->payload.size()); - return output; - } catch (std::exception&) { - return Proto::SigningOutput(); - } -} - -Signature Signer::signatureDataToStruct(const Data& signature, bool includeEip155, const uint256_t& chainID) noexcept { - if (!includeEip155) { - return signatureDataToStructSimple(signature); - } - return signatureDataToStructWithEip155(chainID, signature); -} - -Signature Signer::signatureDataToStructSimple(const Data& signature) noexcept { - boost::multiprecision::uint256_t r, s, v; - import_bits(r, signature.begin(), signature.begin() + 32); - import_bits(s, signature.begin() + 32, signature.begin() + 64); - import_bits(v, signature.begin() + 64, signature.begin() + 65); - return Signature{r, s, v}; -} - -Signature Signer::signatureDataToStructWithEip155(const uint256_t& chainID, const Data& signature) noexcept { - Signature rsv = signatureDataToStructSimple(signature); - // Embed chainID in V param, for replay protection, legacy (EIP155) - if (chainID != 0) { - rsv.v += 35 + chainID + chainID; - } else { - rsv.v += 27; - } - return rsv; -} - -Signature Signer::sign(const PrivateKey& privateKey, const Data& hash, bool includeEip155, const uint256_t& chainID) noexcept { - auto signature = privateKey.sign(hash, TWCurveSECP256k1); - return signatureDataToStruct(signature, includeEip155, chainID); -} - -// May throw -Data addressStringToData(const std::string& asString) { - if (asString.empty()) { - return {}; - } - // only ronin address prefix is not 0x - if (asString.compare(0, 2, "0x") != 0) { - return TW::addressToData(TWCoinTypeRonin, asString); - } - return TW::addressToData(TWCoinTypeEthereum, asString); -} - -std::shared_ptr Signer::build(const Proto::SigningInput& input) { - Data toAddress = addressStringToData(input.to_address()); - uint256_t nonce = load(input.nonce()); - uint256_t gasPrice = load(input.gas_price()); - uint256_t gasLimit = load(input.gas_limit()); - uint256_t maxInclusionFeePerGas = load(input.max_inclusion_fee_per_gas()); - uint256_t maxFeePerGas = load(input.max_fee_per_gas()); - switch (input.transaction().transaction_oneof_case()) { - case Proto::Transaction::kTransfer: - { - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildNativeTransfer( - nonce, gasPrice, gasLimit, - /* to: */ toAddress, - /* amount: */ load(input.transaction().transfer().amount()), - /* optional data: */ Data(input.transaction().transfer().data().begin(), input.transaction().transfer().data().end())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildNativeTransfer( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* to: */ toAddress, - /* amount: */ load(input.transaction().transfer().amount()), - /* optional data: */ Data(input.transaction().transfer().data().begin(), input.transaction().transfer().data().end())); - } - } - - case Proto::Transaction::kErc20Transfer: - { - Data tokenToAddress = addressStringToData(input.transaction().erc20_transfer().to()); - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildERC20Transfer( - nonce, gasPrice, gasLimit, - /* tokenContract: */ toAddress, - /* toAddress */ tokenToAddress, - /* amount: */ load(input.transaction().erc20_transfer().amount())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildERC20Transfer( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* tokenContract: */ toAddress, - /* toAddress */ tokenToAddress, - /* amount: */ load(input.transaction().erc20_transfer().amount())); - } - } - - case Proto::Transaction::kErc20Approve: - { - Data spenderAddress = addressStringToData(input.transaction().erc20_approve().spender()); - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildERC20Approve( - nonce, gasPrice, gasLimit, - /* tokenContract: */ toAddress, - /* toAddress */ spenderAddress, - /* amount: */ load(input.transaction().erc20_approve().amount())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildERC20Approve( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* tokenContract: */ toAddress, - /* toAddress */ spenderAddress, - /* amount: */ load(input.transaction().erc20_approve().amount())); - } - } - - case Proto::Transaction::kErc721Transfer: - { - Data tokenToAddress = addressStringToData(input.transaction().erc721_transfer().to()); - Data tokenFromAddress = addressStringToData(input.transaction().erc721_transfer().from()); - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildERC721Transfer( - nonce, gasPrice, gasLimit, - /* tokenContract: */ toAddress, - /* fromAddress: */ tokenFromAddress, - /* toAddress */ tokenToAddress, - /* tokenId: */ load(input.transaction().erc721_transfer().token_id())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildERC721Transfer( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* tokenContract: */ toAddress, - /* fromAddress: */ tokenFromAddress, - /* toAddress */ tokenToAddress, - /* tokenId: */ load(input.transaction().erc721_transfer().token_id())); - } - } - - case Proto::Transaction::kErc1155Transfer: - { - Data tokenToAddress = addressStringToData(input.transaction().erc1155_transfer().to()); - Data tokenFromAddress = addressStringToData(input.transaction().erc1155_transfer().from()); - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildERC1155Transfer( - nonce, gasPrice, gasLimit, - /* tokenContract: */ toAddress, - /* fromAddress: */ tokenFromAddress, - /* toAddress */ tokenToAddress, - /* tokenId: */ load(input.transaction().erc1155_transfer().token_id()), - /* value */ load(input.transaction().erc1155_transfer().value()), - /* data */ Data(input.transaction().erc1155_transfer().data().begin(), input.transaction().erc1155_transfer().data().end())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildERC1155Transfer( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* tokenContract: */ toAddress, - /* fromAddress: */ tokenFromAddress, - /* toAddress */ tokenToAddress, - /* tokenId: */ load(input.transaction().erc1155_transfer().token_id()), - /* value */ load(input.transaction().erc1155_transfer().value()), - /* data */ Data(input.transaction().erc1155_transfer().data().begin(), input.transaction().erc1155_transfer().data().end())); - } - } - - case Proto::Transaction::kContractGeneric: - default: - { - switch (input.tx_mode()) { - case Proto::TransactionMode::Legacy: - default: - return TransactionNonTyped::buildNativeTransfer( - nonce, gasPrice, gasLimit, - /* to: */ toAddress, - /* amount: */ load(input.transaction().contract_generic().amount()), - /* transaction: */ Data(input.transaction().contract_generic().data().begin(), input.transaction().contract_generic().data().end())); - - case Proto::TransactionMode::Enveloped: // Eip1559 - return TransactionEip1559::buildNativeTransfer( - nonce, maxInclusionFeePerGas, maxFeePerGas, gasLimit, - /* to: */ toAddress, - /* amount: */ load(input.transaction().contract_generic().amount()), - /* transaction: */ Data(input.transaction().contract_generic().data().begin(), input.transaction().contract_generic().data().end())); - } - } - } -} - -Signature Signer::sign(const PrivateKey& privateKey, const uint256_t& chainID, std::shared_ptr transaction) noexcept { - auto preHash = transaction->preHash(chainID); - return Signer::sign(privateKey, preHash, transaction->usesReplayProtection(), chainID); -} diff --git a/src/Ethereum/Signer.h b/src/Ethereum/Signer.h deleted file mode 100644 index fb13a9b36c3..00000000000 --- a/src/Ethereum/Signer.h +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "RLP.h" -#include "Transaction.h" -#include "../Data.h" -#include "../Hash.h" -#include "../PrivateKey.h" -#include "../proto/Ethereum.pb.h" -#include "../uint256.h" - -#include -#include -#include -#include - -namespace TW::Ethereum { - -/// Helper class that performs Ethereum transaction signing. -class Signer { - public: - /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; - /// Signs a json Proto::SigningInput with private key - static std::string signJSON(const std::string& json, const Data& key); - - public: - Signer() = delete; - - /// Signs the given transaction. - static Signature sign(const PrivateKey& privateKey, const uint256_t& chainID, std::shared_ptr transaction) noexcept; - /// Compiles a Proto::SigningInput transaction, with external signature - static Proto::SigningOutput compile(const Proto::SigningInput& input, const Data& signature) noexcept; - - public: - /// build Transaction from signing input - static std::shared_ptr build(const Proto::SigningInput& input); - - /// Signs a hash with the given private key for the given chain identifier. - /// - /// @returns the r, s, and v values of the transaction signature - static Signature sign(const PrivateKey& privateKey, const Data& hash, bool includeEip155, const uint256_t& chainID) noexcept; - - /// Break up the signature into the R, S, and V values. - /// @returns the r, s, and v values of the transaction signature - static Signature signatureDataToStruct(const Data& signature, bool includeEip155, const uint256_t& chainID) noexcept; - - /// Break up the signature into the R, S, and V values, with no replay protection. - /// @returns the r, s, and v values of the transaction signature - static Signature signatureDataToStructSimple(const Data& signature) noexcept; - - /// Break up the signature into the R, S, and V values, and include chainID in V for replay protection (Eip155) - /// @returns the r, s, and v values of the transaction signature - static Signature signatureDataToStructWithEip155(const uint256_t& chainID, const Data& signature) noexcept; -}; - -} // namespace TW::Ethereum diff --git a/src/Ethereum/Transaction.cpp b/src/Ethereum/Transaction.cpp deleted file mode 100644 index f8e91580a44..00000000000 --- a/src/Ethereum/Transaction.cpp +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Transaction.h" -#include "ABI/Function.h" -#include "ABI/ParamBase.h" -#include "ABI/ParamAddress.h" -#include "RLP.h" -#include "HexCoding.h" - -using namespace TW::Ethereum::ABI; -using namespace TW::Ethereum; -using namespace TW; - - -static const Data EmptyListEncoded = parse_hex("c0"); - -std::shared_ptr TransactionNonTyped::buildNativeTransfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& toAddress, const uint256_t& amount, const Data& data) { - return std::make_shared(nonce, gasPrice, gasLimit, toAddress, amount, data); -} - -std::shared_ptr TransactionNonTyped::buildERC20Transfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& toAddress, const uint256_t& amount) { - return std::make_shared(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC20TransferCall(toAddress, amount)); -} - -std::shared_ptr TransactionNonTyped::buildERC20Approve(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& spenderAddress, const uint256_t& amount) { - return std::make_shared(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC20ApproveCall(spenderAddress, amount)); -} - -std::shared_ptr TransactionNonTyped::buildERC721Transfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId) { - return std::make_shared(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC721TransferFromCall(from, to, tokenId)); -} - -std::shared_ptr TransactionNonTyped::buildERC1155Transfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data) { - return std::make_shared(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC1155TransferFromCall(from, to, tokenId, value, data)); -} - -Data TransactionNonTyped::preHash(const uint256_t chainID) const { - return Hash::keccak256(serialize(chainID)); -} - -Data TransactionNonTyped::serialize(const uint256_t chainID) const { - Data encoded; - append(encoded, RLP::encode(nonce)); - append(encoded, RLP::encode(gasPrice)); - append(encoded, RLP::encode(gasLimit)); - append(encoded, RLP::encode(to)); - append(encoded, RLP::encode(amount)); - append(encoded, RLP::encode(payload)); - append(encoded, RLP::encode(chainID)); - append(encoded, RLP::encode(0)); - append(encoded, RLP::encode(0)); - return RLP::encodeList(encoded); -} - -Data TransactionNonTyped::encoded(const Signature& signature, const uint256_t chainID) const { - Data encoded; - append(encoded, RLP::encode(nonce)); - append(encoded, RLP::encode(gasPrice)); - append(encoded, RLP::encode(gasLimit)); - append(encoded, RLP::encode(to)); - append(encoded, RLP::encode(amount)); - append(encoded, RLP::encode(payload)); - append(encoded, RLP::encode(signature.v)); - append(encoded, RLP::encode(signature.r)); - append(encoded, RLP::encode(signature.s)); - return RLP::encodeList(encoded); -} - -Data TransactionNonTyped::buildERC20TransferCall(const Data& to, const uint256_t& amount) { - auto func = Function("transfer", std::vector>{ - std::make_shared(to), - std::make_shared(amount) - }); - Data payload; - func.encode(payload); - return payload; -} - -Data TransactionNonTyped::buildERC20ApproveCall(const Data& spender, const uint256_t& amount) { - auto func = Function("approve", std::vector>{ - std::make_shared(spender), - std::make_shared(amount) - }); - Data payload; - func.encode(payload); - return payload; -} - -Data TransactionNonTyped::buildERC721TransferFromCall(const Data& from, const Data& to, const uint256_t& tokenId) { - auto func = Function("transferFrom", std::vector>{ - std::make_shared(from), - std::make_shared(to), - std::make_shared(tokenId) - }); - Data payload; - func.encode(payload); - return payload; -} - -Data TransactionNonTyped::buildERC1155TransferFromCall(const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data) { - auto func = Function("safeTransferFrom", std::vector>{ - std::make_shared(from), - std::make_shared(to), - std::make_shared(tokenId), - std::make_shared(value), - std::make_shared(data) - }); - Data payload; - func.encode(payload); - return payload; -} - -Data TransactionEip1559::preHash(const uint256_t chainID) const { - return Hash::keccak256(serialize(chainID)); -} - -Data TransactionEip1559::serialize(const uint256_t chainID) const { - Data encoded; - append(encoded, RLP::encode(chainID)); - append(encoded, RLP::encode(nonce)); - append(encoded, RLP::encode(maxInclusionFeePerGas)); - append(encoded, RLP::encode(maxFeePerGas)); - append(encoded, RLP::encode(gasLimit)); - append(encoded, RLP::encode(to)); - append(encoded, RLP::encode(amount)); - append(encoded, RLP::encode(payload)); - append(encoded, EmptyListEncoded); // empty accessList - - Data envelope; - append(envelope, static_cast(type)); - append(envelope, RLP::encodeList(encoded)); - return envelope; -} - -Data TransactionEip1559::encoded(const Signature& signature, const uint256_t chainID) const { - Data encoded; - append(encoded, RLP::encode(chainID)); - append(encoded, RLP::encode(nonce)); - append(encoded, RLP::encode(maxInclusionFeePerGas)); - append(encoded, RLP::encode(maxFeePerGas)); - append(encoded, RLP::encode(gasLimit)); - append(encoded, RLP::encode(to)); - append(encoded, RLP::encode(amount)); - append(encoded, RLP::encode(payload)); - append(encoded, EmptyListEncoded); // empty accessList - append(encoded, RLP::encode(signature.v)); - append(encoded, RLP::encode(signature.r)); - append(encoded, RLP::encode(signature.s)); - - Data envelope; - append(envelope, static_cast(type)); - append(envelope, RLP::encodeList(encoded)); - return envelope; -} - -std::shared_ptr TransactionEip1559::buildNativeTransfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& toAddress, const uint256_t& amount, const Data& data) { - return std::make_shared(nonce, maxInclusionFeePerGas, maxFeePerGas, gasPrice, toAddress, amount, data); -} - -std::shared_ptr TransactionEip1559::buildERC20Transfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& toAddress, const uint256_t& amount) { - return std::make_shared(nonce, maxInclusionFeePerGas, maxFeePerGas, gasPrice, tokenContract, 0, TransactionNonTyped::buildERC20TransferCall(toAddress, amount)); -} - -std::shared_ptr TransactionEip1559::buildERC20Approve(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& spenderAddress, const uint256_t& amount) { - return std::make_shared(nonce, maxInclusionFeePerGas, maxFeePerGas, gasPrice, tokenContract, 0, TransactionNonTyped::buildERC20ApproveCall(spenderAddress, amount)); -} - -std::shared_ptr TransactionEip1559::buildERC721Transfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId) { - return std::make_shared(nonce, maxInclusionFeePerGas, maxFeePerGas, gasPrice, tokenContract, 0, TransactionNonTyped::buildERC721TransferFromCall(from, to, tokenId)); -} - -std::shared_ptr TransactionEip1559::buildERC1155Transfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data) { - return std::make_shared(nonce, maxInclusionFeePerGas, maxFeePerGas, gasPrice, tokenContract, 0, TransactionNonTyped::buildERC1155TransferFromCall(from, to, tokenId, value, data)); -} diff --git a/src/Ethereum/Transaction.h b/src/Ethereum/Transaction.h deleted file mode 100644 index d43fd77aa10..00000000000 --- a/src/Ethereum/Transaction.h +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Address.h" -#include "../uint256.h" - -#include - -namespace TW::Ethereum { - -// Transactions can be: -// - Non-typed (legacy, pre-EIP2718) transactions: -// -- simple ETH transfer -// -- others with payload, function call, e.g. ERC20 transfer -// - Typed transactions (enveloped, EIP2718), with specific type and transaction payload - -/// R-S-V Signature values -struct Signature { -public: - uint256_t r, s, v; -}; - -/// Base class for all transactions. -/// Non-typed and various typed transactions derive from this. -class TransactionBase { -public: - uint256_t nonce; - Data payload; - -public: - TransactionBase(const uint256_t& nonce, const Data& payload): nonce(nonce), payload(payload) {} - virtual ~TransactionBase() {} - // pre-sign hash of the tx, for signing - virtual Data preHash(const uint256_t chainID) const = 0; - // pre-sign image of tx - virtual Data serialize(const uint256_t chainID) const = 0; - // encoded tx (signed) - virtual Data encoded(const Signature& signature, const uint256_t chainID) const = 0; - // Signals wether this tx type uses Eip155-style replay protection in the signature - virtual bool usesReplayProtection() const = 0; - -protected: - TransactionBase() {} -}; - -/// Original transaction format, with no explicit type, legacy as pre-EIP2718 -class TransactionNonTyped: public TransactionBase { -public: - uint256_t gasPrice; - uint256_t gasLimit; - // Public key hash (Address.bytes) - Data to; - uint256_t amount; - - // Factory methods - // Create a native transfer transaction - static std::shared_ptr buildNativeTransfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& toAddress, const uint256_t& amount, const Data& data = {}); - - // Create an ERC20 token transfer transaction - static std::shared_ptr buildERC20Transfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& toAddress, const uint256_t& amount); - - // Create an ERC20 approve transaction - static std::shared_ptr buildERC20Approve(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& spenderAddress, const uint256_t& amount); - - // Create an ERC721 NFT transfer transaction - static std::shared_ptr buildERC721Transfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId); - - // Create an ERC1155 NFT transfer transaction - static std::shared_ptr buildERC1155Transfer(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data); - - // Helpers for building contract calls - static Data buildERC20TransferCall(const Data& to, const uint256_t& amount); - static Data buildERC20ApproveCall(const Data& spender, const uint256_t& amount); - static Data buildERC721TransferFromCall(const Data& from, const Data& to, const uint256_t& tokenId); - static Data buildERC1155TransferFromCall(const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data); - - virtual Data preHash(const uint256_t chainID) const; - virtual Data serialize(const uint256_t chainID) const; - virtual Data encoded(const Signature& signature, const uint256_t chainID) const; - virtual bool usesReplayProtection() const { return true; } - -public: - TransactionNonTyped(const uint256_t& nonce, - const uint256_t& gasPrice, const uint256_t& gasLimit, - const Data& to, const uint256_t& amount, const Data& payload = {}) - : TransactionBase(nonce, payload) - , gasPrice(std::move(gasPrice)) - , gasLimit(std::move(gasLimit)) - , to(std::move(to)) - , amount(std::move(amount)) {} -}; - -enum TransactionType: uint8_t { - TxType_OptionalAccessList = 0x01, - TxType_Eip1559 = 0x02, -}; - -/// Base class for various typed transactions. -class TransactionTyped: public TransactionBase { -public: - // transaction type - TransactionType type; - - TransactionTyped(TransactionType type, const uint256_t& nonce, const Data& payload) - : TransactionBase(nonce, payload), type(type) {} - virtual bool usesReplayProtection() const { return false; } -}; - -/// EIP1559 transaction -class TransactionEip1559: public TransactionTyped { -public: - uint256_t maxInclusionFeePerGas; - uint256_t maxFeePerGas; - uint256_t gasLimit; - // Public key hash (Address.bytes) - Data to; - uint256_t amount; - - // Factory methods - // Create a native transfer transaction - static std::shared_ptr buildNativeTransfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& toAddress, const uint256_t& amount, const Data& data = {}); - // Create an ERC20 token transfer transaction - static std::shared_ptr buildERC20Transfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& toAddress, const uint256_t& amount); - // Create an ERC20 approve transaction - static std::shared_ptr buildERC20Approve(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& spenderAddress, const uint256_t& amount); - // Create an ERC721 NFT transfer transaction - static std::shared_ptr buildERC721Transfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId); - // Create an ERC1155 NFT transfer transaction - static std::shared_ptr buildERC1155Transfer(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasPrice, - const Data& tokenContract, const Data& from, const Data& to, const uint256_t& tokenId, const uint256_t& value, const Data& data); - - virtual Data preHash(const uint256_t chainID) const; - virtual Data serialize(const uint256_t chainID) const; - virtual Data encoded(const Signature& signature, const uint256_t chainID) const; - -public: - TransactionEip1559(const uint256_t& nonce, - const uint256_t& maxInclusionFeePerGas, const uint256_t& maxFeePerGas, const uint256_t& gasLimit, - const Data& to, const uint256_t& amount, const Data& payload = {}) - : TransactionTyped(TxType_Eip1559, nonce, payload) - , maxInclusionFeePerGas(std::move(maxInclusionFeePerGas)) - , maxFeePerGas(std::move(maxFeePerGas)) - , gasLimit(std::move(gasLimit)) - , to(std::move(to)) - , amount(std::move(amount)) {} -}; - -} // namespace TW::Ethereum diff --git a/src/Everscale/Address.cpp b/src/Everscale/Address.cpp new file mode 100644 index 00000000000..dee503e837c --- /dev/null +++ b/src/Everscale/Address.cpp @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include + +#include "Address.h" +#include "HexCoding.h" +#include "Wallet.h" + +using namespace TW; + +namespace TW::Everscale { + +using AddressImpl = TW::CommonTON::RawAddress; + +bool Address::isValid(const std::string& string) noexcept { + return AddressImpl::isValid(string); +} + +Address::Address(const std::string& string) { + if (!Address::isValid(string)) { + throw std::invalid_argument("Invalid address string!"); + } + + addressData = AddressImpl::splitAddress(string); +} + +Address::Address(const PublicKey& publicKey, int8_t workchainId) + : Address(InitData(publicKey).computeAddr(workchainId)) { +} + +std::string Address::string() const { + return AddressImpl::to_string(addressData); +} + +} // namespace TW::Everscale diff --git a/src/Everscale/Address.h b/src/Everscale/Address.h new file mode 100644 index 00000000000..ff114a8ce78 --- /dev/null +++ b/src/Everscale/Address.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "PublicKey.h" + +#include "CommonTON/RawAddress.h" + +#include +#include + +namespace TW::Everscale { + +using AddressData = CommonTON::AddressData; + +class Address { +public: + AddressData addressData; + + /// Determines whether a string makes a valid address. + [[nodiscard]] static bool isValid(const std::string& string) noexcept; + + /// Initializes an Everscale address with a string representation. + explicit Address(const std::string& string); + + /// Initializes an Everscale address with a public key and a workchain id. + explicit Address(const PublicKey& publicKey, int8_t workchainId); + + /// Initializes an Everscale address with its parts + explicit Address(int8_t workchainId, std::array hash) + : addressData(workchainId, hash) {} + + /// Initializes an Everscale address with AddressData + explicit Address(AddressData addressData) + : addressData(addressData) {} + + /// Returns a string representation of the address. + [[nodiscard]] std::string string() const; +}; + +inline bool operator==(const Address& lhs, const Address& rhs) { + return lhs.addressData.workchainId == rhs.addressData.workchainId && lhs.addressData.hash == rhs.addressData.hash; +} + +} // namespace TW::Everscale diff --git a/src/Everscale/CommonTON/BitReader.cpp b/src/Everscale/CommonTON/BitReader.cpp new file mode 100644 index 00000000000..44220144801 --- /dev/null +++ b/src/Everscale/CommonTON/BitReader.cpp @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "BitReader.h" + +namespace TW::CommonTON { + +std::optional BitReader::createExact(const TW::Data &buffer, uint64_t bitLen) { + Rust::TWDataWrapper twData(buffer); + auto *readerPtr = Rust::tw_bit_reader_create(twData.get(), bitLen); + if (!readerPtr) { + return std::nullopt; + } + + return BitReader(std::shared_ptr(readerPtr, Rust::tw_bit_reader_delete)); +} + +std::optional BitReader::readU8(uint8_t bitCount) { + Rust::CUInt8ResultWrapper res = Rust::tw_bit_reader_read_u8(reader.get(), bitCount); + if (res.isErr()) { + return std::nullopt; + } + return res.unwrap().value; +} + +std::optional BitReader::readU8Slice(uint64_t byteCount) { + Rust::CByteArrayResultWrapper res = Rust::tw_bit_reader_read_u8_slice(reader.get(), byteCount); + if (res.isErr()) { + return std::nullopt; + } + return res.unwrap().data; +} + +bool BitReader::finished() const { + return Rust::tw_bit_reader_finished(reader.get()); +} + +} // namespace TW::CommonTON diff --git a/src/Everscale/CommonTON/BitReader.h b/src/Everscale/CommonTON/BitReader.h new file mode 100644 index 00000000000..51177f25b09 --- /dev/null +++ b/src/Everscale/CommonTON/BitReader.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "rust/Wrapper.h" + +namespace TW::CommonTON { + +class BitReader { +public: + // Tries to create a bit reader with exact `bitLen` number of bits. + static std::optional createExact(const Data& buffer, uint64_t bitLen); + + // Read at most 8 bits into a u8. + std::optional readU8(uint8_t bitCount); + + // Reads an entire slice of `byteCount` bytes. If there aren't enough bits remaining + // after the internal cursor's current position, returns none. + std::optional readU8Slice(uint64_t byteCount); + + bool finished() const; + +private: + explicit BitReader(std::shared_ptr reader): reader(std::move(reader)) {} + + std::shared_ptr reader; +}; + +} // namespace TW::CommonTON diff --git a/src/Everscale/CommonTON/Cell.cpp b/src/Everscale/CommonTON/Cell.cpp new file mode 100644 index 00000000000..b3be8774405 --- /dev/null +++ b/src/Everscale/CommonTON/Cell.cpp @@ -0,0 +1,441 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Cell.h" + +#include +#include +#include +#include +#include + +#include "Base64.h" +#include "BinaryCoding.h" +#include "BitReader.h" + +using namespace TW; + +namespace TW::CommonTON { + +constexpr static uint32_t BOC_MAGIC = 0xb5ee9c72; + +uint16_t computeBitLen(const Data& data, bool aligned) { + auto bitLen = static_cast(data.size() * 8); + if (aligned) { + return bitLen; + } + + for (auto i = static_cast(data.size() - 1); i >= 0; --i) { + if (data[i] == 0) { + bitLen -= 8; + } else { + auto skip = 1; + uint8_t mask = 1; + while ((data[i] & mask) == 0) { + skip += 1; + mask <<= 1; + } + bitLen -= skip; + break; + } + } + return bitLen; +} + +struct Reader { + const uint8_t* _Nonnull buffer; + size_t bufferLen; + size_t offset = 0; + + explicit Reader(const uint8_t* _Nonnull buffer, size_t len) noexcept + : buffer(buffer), bufferLen(len) { + } + + void require(size_t bytes) const { + if (offset + bytes > bufferLen) { + throw std::runtime_error("unexpected eof"); + } + } + + void advance(size_t bytes) { + offset += bytes; + } + + const uint8_t* _Nonnull data() const { + return buffer + offset; + } + + size_t readNextUint(uint8_t len) { + const auto* _Nonnull p = data(); + advance(len); + + switch (len) { + case 1: + return static_cast(*p); + case 2: + return static_cast(decode16BE(p)); + case 3: + return static_cast(p[2]) | (static_cast(p[1]) << 8) | (static_cast(p[0]) << 16); + case 4: + return static_cast(decode32BE(p)); + default: + // Unreachable in valid cells + return 0; + } + } +}; + +std::shared_ptr Cell::fromBase64(const std::string& encoded) { + auto boc = Base64::decode(encoded); + return Cell::deserialize(boc.data(), boc.size()); +} + +std::shared_ptr Cell::deserialize(const uint8_t* _Nonnull data, size_t len) { + Reader reader(data, len); + + // Parse header + reader.require(6); + // 1. Magic + if (reader.readNextUint(sizeof(BOC_MAGIC)) != BOC_MAGIC) { + throw std::runtime_error("unknown magic"); + } + // 2. Flags + struct Flags { + uint8_t refSize : 3; + uint8_t : 2; // unused + bool hasCacheBits : 1; + bool hasCrc : 1; + bool indexIncluded : 1; + }; + + static_assert(sizeof(Flags) == 1, "flags must be represented as 1 byte"); + const auto flags = reinterpret_cast(reader.data())[0]; + const auto refSize = flags.refSize; + const auto offsetSize = reader.data()[1]; + reader.advance(2); + + // 3. Counters and root index + reader.require(refSize * 3 + offsetSize + refSize); + const auto cellCount = reader.readNextUint(refSize); + const auto rootCount = reader.readNextUint(refSize); + if (rootCount != 1) { + throw std::runtime_error("unsupported root count"); + } + if (rootCount > cellCount) { + throw std::runtime_error("root count is greater than cell count"); + } + const auto absent_count = reader.readNextUint(refSize); + if (absent_count > 0) { + throw std::runtime_error("absent cells are not supported"); + } + + reader.readNextUint(offsetSize); // total cell size + + const auto rootIndex = reader.readNextUint(refSize); + + // 4. Cell offsets (skip if specified) + if (flags.indexIncluded) { + reader.advance(cellCount * offsetSize); + } + + // 5. Deserialize cells + struct IntermediateCell { + uint16_t bitLen; + Data data; + std::vector references; + }; + + std::vector intermediate{}; + intermediate.reserve(cellCount); + + for (size_t i = 0; i < cellCount; ++i) { + struct Descriptor { + uint8_t refCount : 3; + bool exotic : 1; + bool storeHashes : 1; + uint8_t level : 3; + }; + + static_assert(sizeof(Descriptor) == 1, "cell descriptor must be represented as 1 byte"); + + reader.require(2); + const auto d1 = reinterpret_cast(reader.data())[0]; + if (d1.level != 0) { + throw std::runtime_error("non-zero level is not supported"); + } + if (d1.exotic) { + throw std::runtime_error("exotic cells are not supported"); + } + if (d1.refCount == 7 && d1.storeHashes) { + throw std::runtime_error("absent cells are not supported"); + } + if (d1.refCount > 4) { + throw std::runtime_error("invalid ref count"); + } + const auto d2 = reader.data()[1]; + const auto byteLen = (d2 >> 1) + (d2 & 0b1); + reader.advance(2); + + // Skip stored hashes + if (d1.storeHashes) { + reader.advance(sizeof(uint16_t) + Hash::sha256Size); + } + + reader.require(byteLen); + Data cellData(byteLen); + std::memcpy(cellData.data(), reader.data(), byteLen); + reader.advance(byteLen); + + std::vector references{}; + references.reserve(refSize * d1.refCount); + reader.require(refSize * d1.refCount); + for (size_t r = 0; r < d1.refCount; ++r) { + const auto index = reader.readNextUint(refSize); + if (index > cellCount || index <= i) { + throw std::runtime_error("invalid child index"); + } + + references.push_back(index); + } + + const auto bitLen = computeBitLen(cellData, (d2 & 0b1) == 0); + intermediate.emplace_back( + IntermediateCell{ + .bitLen = bitLen, + .data = std::move(cellData), + .references = std::move(references), + }); + } + + std::unordered_map doneCells{}; + + size_t index = cellCount; + for (auto it = intermediate.rbegin(); it != intermediate.rend(); ++it, --index) { + auto& raw = *it; + + Cell::Refs references{}; + for (size_t r = 0; r < raw.references.size(); ++r) { + const auto child = doneCells.find(raw.references[r]); + if (child == doneCells.end()) { + throw std::runtime_error("child cell not found"); + } + references[r] = child->second; + } + + auto cell = std::make_shared(raw.bitLen, std::move(raw.data), raw.references.size(), std::move(references)); + cell->finalize(); + doneCells.emplace(index - 1, cell); + } + + const auto root = doneCells.find(rootIndex); + if (root == doneCells.end()) { + throw std::runtime_error("root cell not found"); + } + return std::move(root->second); +} + +class SerializationContext { +public: + static SerializationContext build(const Cell& cell) { + SerializationContext ctx{}; + fillContext(cell, ctx); + return ctx; + } + + void encode(Data& os) const { + os.reserve(os.size() + HEADER_SIZE + cellsSize); + + const auto cellCount = static_cast(reversedCells.size()); + + // Write header + encode32BE(BOC_MAGIC, os); + os.push_back(REF_SIZE); + os.push_back(OFFSET_SIZE); + encode16BE(static_cast(cellCount), os); + encode16BE(1, os); // root count + encode16BE(0, os); // absent cell count + encode16BE(static_cast(cellsSize), os); + encode16BE(0, os); // root cell index + + // Write cells + size_t i = cellCount - 1; + while (true) { + const auto& cell = *reversedCells[i]; + + // Write cell data + const auto [d1, d2] = cell.getDescriptorBytes(); + os.push_back(d1); + os.push_back(d2); + os.insert(os.end(), cell.data.begin(), cell.data.end()); + + // Write cell references + for (const auto& child : cell.references) { + if (child == nullptr) { + break; + } + + // Map cell hash to index (which must be presented) + const auto it = indices.find(child->hash); + assert(it != indices.end()); + + encode16BE(cellCount - it->second - 1, os); + } + + if (i == 0) { + break; + } else { + --i; + } + } + } + +private: + // uint16_t will be enough for wallet transactions (e.g. 64k is the size of the whole elector) + using ref_t = uint16_t; + using offset_t = uint16_t; + + constexpr static uint8_t REF_SIZE = sizeof(ref_t); + constexpr static uint8_t OFFSET_SIZE = sizeof(offset_t); + constexpr static size_t HEADER_SIZE = + /*magic*/ sizeof(BOC_MAGIC) + + /*ref_size*/ 1 + + /*offset_size*/ 1 + + /*cell_count*/ REF_SIZE + + /*root_count*/ REF_SIZE + + /*absent_count*/ REF_SIZE + + /*data_size*/ OFFSET_SIZE + + /*root_cell_index*/ REF_SIZE; + + size_t cellsSize = 0; + ref_t index = 0; + std::map indices{}; + std::vector reversedCells{}; + + static void fillContext(const Cell& cell, SerializationContext& ctx) { + if (ctx.indices.find(cell.hash) != ctx.indices.end()) { + return; + } + + for (const auto& ref : cell.references) { + if (ref == nullptr) { + break; + } + fillContext(*ref, ctx); + } + + ctx.indices.insert(std::make_pair(cell.hash, ctx.index++)); + ctx.reversedCells.emplace_back(&cell); + ctx.cellsSize += cell.serializedSize(REF_SIZE); + } +}; + +void Cell::serialize(Data& os) const { + assert(finalized); + const auto ctx = SerializationContext::build(*this); + ctx.encode(os); +} + +void Cell::finalize() { + if (finalized) { + return; + } + + if (bitLen > Cell::MAX_BITS || refCount > Cell::MAX_REFS) { + throw std::invalid_argument("invalid cell"); + } + + // Finalize child cells and update current cell depth + // NOTE: Use before context creation to reduce stack size + for (const auto& ref : references) { + if (ref == nullptr) { + break; + } + ref->finalize(); + depth = std::max(depth, static_cast(ref->depth + 1)); + } + + // Compute cell hash + const auto dataSize = std::min(data.size(), static_cast((bitLen + 7) / 8)); + + Data normalized{}; + normalized.reserve(/* descriptor bytes */ 2 + dataSize + refCount * (sizeof(uint16_t) + Hash::sha256Size)); + + // Write descriptor bytes + const auto [d1, d2] = getDescriptorBytes(); + normalized.push_back(d1); + normalized.push_back(d2); + std::copy(data.begin(), std::next(data.begin(), static_cast(dataSize)), std::back_inserter(normalized)); + + // Write all children depths + for (const auto& ref : references) { + if (ref == nullptr) { + break; + } + encode16BE(ref->depth, normalized); + } + + // Write all children hashes + for (const auto& ref : references) { + if (ref == nullptr) { + break; + } + std::copy(ref->hash.begin(), ref->hash.end(), std::back_inserter(normalized)); + } + + // Done + const auto computedHash = Hash::sha256(normalized); + assert(computedHash.size() == Hash::sha256Size); + + std::copy(computedHash.begin(), computedHash.end(), hash.begin()); + finalized = true; +} + +std::optional Cell::parseAddress() const { + auto reader = BitReader::createExact(data, static_cast(bitLen)); + if (!reader) { + return std::nullopt; + } + + auto tp = reader->readU8(2); + if (!tp) { + return std::nullopt; + } + + if (tp.value() == 0) { + // Hole address (default). Check if the Cell does not contain more bits. + if (!reader->finished()) { + return std::nullopt; + } + return AddressData(); + } + + // We expect type=0 or type=2 addresses only. + if (tp.value() != 2) { + return std::nullopt; + } + + // Ignore res1 value. + reader->readU8(1); + + auto workchain = reader->readU8(8); + if (!workchain) { + return std::nullopt; + } + + auto hashPart = reader->readU8Slice(AddressData::size); + if (!hashPart) { + return std::nullopt; + } + + if (!reader->finished()) { + return std::nullopt; + } + + std::array parsedHash {}; + std::copy(begin(hashPart.value()), end(hashPart.value()), begin(parsedHash)); + + return AddressData(static_cast(workchain.value()), parsedHash); +} + +} // namespace TW::CommonTON diff --git a/src/Everscale/CommonTON/Cell.h b/src/Everscale/CommonTON/Cell.h new file mode 100644 index 00000000000..2341fd778db --- /dev/null +++ b/src/Everscale/CommonTON/Cell.h @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include +#include + +#include "Data.h" +#include "Hash.h" +#include "RawAddress.h" + +namespace TW::CommonTON { + +class Cell { +public: + constexpr static uint16_t MAX_BITS = 1023; + constexpr static uint8_t MAX_REFS = 4; + + using Ref = std::shared_ptr; + using Refs = std::array; + using CellHash = std::array; + + bool finalized = false; + uint16_t bitLen = 0; + std::vector data{}; + uint8_t refCount = 0; + Refs references{}; + + uint16_t depth = 0; + CellHash hash{}; + + Cell() = default; + + Cell(uint16_t bitLen, std::vector data, uint8_t refCount, Refs references) + : bitLen(bitLen), data(std::move(data)), refCount(refCount), references(std::move(references)) {} + + // Deserialize from Base64 + static std::shared_ptr fromBase64(const std::string& encoded); + + // Deserialize from BOC representation + static std::shared_ptr deserialize(const uint8_t* _Nonnull data, size_t len); + + // Serialize to binary stream + void serialize(Data& os) const; + + // Compute cell depth and hash + void finalize(); + + [[nodiscard]] inline std::pair getDescriptorBytes() const noexcept { + const uint8_t d1 = refCount; + const uint8_t d2 = (static_cast(bitLen >> 2) & 0b11111110) | (bitLen % 8 != 0); + return std::pair{d1, d2}; + } + + [[nodiscard]] inline size_t serializedSize(uint8_t refSize) const noexcept { + return 2 + (bitLen + 7) / 8 + refCount * refSize; + } + + // Tries to parse an address from the Cell. + std::optional parseAddress() const; +}; + +} // namespace TW::CommonTON diff --git a/src/Everscale/CommonTON/CellBuilder.cpp b/src/Everscale/CommonTON/CellBuilder.cpp new file mode 100644 index 00000000000..b03a7891067 --- /dev/null +++ b/src/Everscale/CommonTON/CellBuilder.cpp @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "CellBuilder.h" +#include "Cell.h" + +#include +#include +#include + +#include "BinaryCoding.h" + +using namespace TW; + +namespace TW::CommonTON { + +CellBuilder::CellBuilder(Data& appendedData, uint16_t bits) { + assert(bits <= appendedData.size() * 8); + assert(bits < Cell::MAX_BITS); + assert(bits % 8 == 0); + + appendedData.resize(bits / 8); + + data = appendedData; + bitLen = bits; + references = {}; +} + +void CellBuilder::appendBitZero() { + Data appendedData{0x00}; + appendRaw(appendedData, 1); +} + +void CellBuilder::appendBitOne() { + Data appendedData{0xFF}; + appendRaw(appendedData, 1); +} + +void CellBuilder::appendBitBool(bool bit) { + auto getAppendedData = [](bool bit) { + if (bit) { + return Data{0xFF}; + } else { + return Data{0x00}; + } + }; + auto appendedData = getAppendedData(bit); + appendRaw(appendedData, 1); +} + +void CellBuilder::appendU8(uint8_t value) { + Data appendedData{value}; + appendRaw(appendedData, 8); +} + +void CellBuilder::appendU32(uint32_t value) { + Data appendedData; + encode32BE(value, appendedData); + appendRaw(appendedData, 32); +} + +void CellBuilder::appendU64(uint64_t value) { + Data appendedData; + encode64BE(value, appendedData); + appendRaw(appendedData, 64); +} + +void CellBuilder::appendU128(const uint128_t& value) { + uint8_t bits = 4; + uint16_t bytes = 16 - clzU128(value) / 8; + + appendBits(bytes, bits); + + Data encodedValue; + encode128BE(value, encodedValue); + + auto offset = static_cast(encodedValue.size() - bytes); + + Data appendedData(encodedValue.begin() + offset, encodedValue.end()); + appendRaw(appendedData, bytes * 8); +} + +void CellBuilder::appendI8(int8_t value) { + Data appendedData{static_cast(value)}; + appendRaw(appendedData, 8); +} + +void CellBuilder::appendBits(uint64_t value, uint8_t bits) { + assert(bits >= 1 && bits <= 7); + + Data appendedData; + + auto val = static_cast(value << (8 - bits)); + appendedData.push_back(val); + + appendRaw(appendedData, bits); +} + +void CellBuilder::appendRaw(const Data& appendedData, uint16_t bits) { + if (appendedData.size() * 8 < bits) { + throw std::invalid_argument("invalid builder data"); + } else if (bitLen + bits > Cell::MAX_BITS) { + throw std::runtime_error("cell data overflow"); + } else if (bits != 0) { + if ((bitLen % 8) == 0) { + if ((bits % 8) == 0) { + appendWithoutShifting(appendedData, bits); + } else { + appendWithSliceShifting(appendedData, bits); + } + } else { + appendWithDoubleShifting(appendedData, bits); + } + } + assert(bitLen <= Cell::MAX_BITS); + assert(data.size() * 8 <= Cell::MAX_BITS + 1); +} + +void CellBuilder::prependRaw(Data& appendedData, uint16_t bits) { + if (bits != 0) { + auto buffer = CellBuilder(appendedData, bits); + buffer.appendRaw(data, bitLen); + + data = buffer.data; + bitLen = buffer.bitLen; + } +} + +void CellBuilder::appendReferenceCell(std::shared_ptr child) { + if (child) { + if (references.size() + 1 > Cell::MAX_REFS) { + throw std::runtime_error("cell refs overflow"); + } + references.emplace_back(std::move(child)); + } +} + +void CellBuilder::appendBuilder(const CellBuilder& builder) { + appendRaw(builder.data, builder.bitLen); + for (const auto& reference : builder.references) { + appendReferenceCell(reference); + } +} + +void CellBuilder::appendCellSlice(const CellSlice& other) { + Data appendedData(other.cell->data); + appendRaw(appendedData, other.cell->bitLen); + + for (const auto& cell : other.cell->references) { + appendReferenceCell(cell); + } +} + +Cell::Ref CellBuilder::intoCell() { + // Append tag + if (bitLen & 7) { + const auto mask = static_cast(0x80 >> (bitLen & 7)); + const auto l = bitLen / 8; + data[l] = static_cast((data[l] & ~mask) | mask); + } + + auto refCount = references.size(); + + Cell::Refs refs; + std::move(references.begin(), references.end(), refs.begin()); + + auto cell = std::make_shared(bitLen, std::move(data), refCount, std::move(refs)); + cell->finalize(); + + bitLen = 0; + refCount = 0; + + return cell; +} + +void CellBuilder::appendWithoutShifting(const Data& appendedData, uint16_t bits) { + assert(bits % 8 == 0); + assert(bitLen % 8 == 0); + + data.resize(bitLen / 8); + data.insert(data.end(), appendedData.begin(), appendedData.end()); + bitLen += bits; + data.resize(bitLen / 8); +} + +void CellBuilder::appendWithSliceShifting(const Data& appendedData, uint16_t bits) { + assert(bits % 8 != 0); + assert(bitLen % 8 == 0); + + data.resize(bitLen / 8); + data.insert(data.end(), appendedData.begin(), appendedData.end()); + bitLen += bits; + data.resize(1 + bitLen / 8); + + data.back() &= ~static_cast(0xff >> (bits % 8)); +} + +void CellBuilder::appendWithDoubleShifting(const Data& appendedData, uint16_t bits) { + auto selfShift = bitLen % 8; + data.resize(1 + bitLen / 8); + bitLen += bits; + + // yyyyy000 -> 00000000 000yyyyy + auto y = static_cast(data.back() >> (8 - selfShift)); + data.pop_back(); + + for (const auto x : appendedData) { + // 00000000 000yyyyy -> 000yyyyy xxxxxxxx + y = static_cast(y << 8) | x; + // 000yyyyy xxxxxxxx -> 00000000 yyyyyxxx + data.push_back(static_cast(y >> selfShift)); + } + // 00000000 yyyyyxxx + data.push_back(static_cast(y << (8 - selfShift))); + + auto shift = bitLen % 8; + if (shift == 0) { + data.resize(bitLen / 8); + } else { + data.resize(bitLen / 8 + 1); + data.back() &= ~static_cast(0xff >> (bitLen % 8)); + } +} + +void CellBuilder::appendAddress(const AddressData& addressData) { + Data rawData(addressData.hash.begin(), addressData.hash.end()); + Data prefix{0x80}; + appendRaw(prefix, 2); + appendBitZero(); + appendI8(addressData.workchainId); + appendRaw(rawData, 256); +} + +uint8_t CellBuilder::clzU128(const uint128_t& u) { + auto hi = static_cast(u >> 64); + auto lo = static_cast(u); + + if (lo == 0 && hi == 0) { + return 128; + } else if (hi == 0) { + return static_cast(std::countl_zero(lo) + 64); + } else { + return static_cast(std::countl_zero(hi)); + } +} + +void CellBuilder::encode128BE(const uint128_t& val, Data& data) { + data.emplace_back(static_cast((val >> 120))); + data.emplace_back(static_cast((val >> 112))); + data.emplace_back(static_cast((val >> 104))); + data.emplace_back(static_cast((val >> 96))); + data.emplace_back(static_cast((val >> 88))); + data.emplace_back(static_cast((val >> 80))); + data.emplace_back(static_cast((val >> 72))); + data.emplace_back(static_cast((val >> 64))); + data.emplace_back(static_cast((val >> 56))); + data.emplace_back(static_cast((val >> 48))); + data.emplace_back(static_cast((val >> 40))); + data.emplace_back(static_cast((val >> 32))); + data.emplace_back(static_cast((val >> 24))); + data.emplace_back(static_cast((val >> 16))); + data.emplace_back(static_cast((val >> 8))); + data.emplace_back(static_cast(val)); +} + +} // namespace TW::CommonTON diff --git a/src/Everscale/CommonTON/CellBuilder.h b/src/Everscale/CommonTON/CellBuilder.h new file mode 100644 index 00000000000..6054e8d23be --- /dev/null +++ b/src/Everscale/CommonTON/CellBuilder.h @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include +#include + +#include "Cell.h" +#include "CellSlice.h" +#include "RawAddress.h" +#include "uint256.h" + +namespace TW::CommonTON { + +class CellBuilder { + uint16_t bitLen = 0; + std::vector data{}; + std::vector references{}; + +public: + CellBuilder() = default; + CellBuilder(Data& appendedData, uint16_t bits); + + void appendBitZero(); + void appendBitOne(); + void appendU8(uint8_t value); + void appendBitBool(bool bit); + void appendU32(uint32_t value); + void appendU64(uint64_t value); + void appendU128(const uint128_t& value); + void appendI8(int8_t value); + void appendBits(uint64_t value, uint8_t bits); + void appendRaw(const Data& appendedData, uint16_t bits); + void prependRaw(Data& appendedData, uint16_t bits); + void appendReferenceCell(Cell::Ref child); + void appendBuilder(const CellBuilder& builder); + void appendCellSlice(const CellSlice& other); + void appendAddress(const AddressData& addressData); + + Cell::Ref intoCell(); + +private: + void appendWithoutShifting(const Data& data, uint16_t bits); + void appendWithSliceShifting(const Data& data, uint16_t bits); + void appendWithDoubleShifting(const Data& data, uint16_t bits); + + static uint8_t clzU128(const uint128_t& u); + static void encode128BE(const uint128_t& value, Data& data); +}; + +} // namespace TW::CommonTON diff --git a/src/Everscale/CommonTON/CellSlice.cpp b/src/Everscale/CommonTON/CellSlice.cpp new file mode 100644 index 00000000000..08089d155d4 --- /dev/null +++ b/src/Everscale/CommonTON/CellSlice.cpp @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "CellSlice.h" + +#include + +#include "BinaryCoding.h" + +using namespace TW; + +namespace TW::CommonTON { + +uint32_t CellSlice::getNextU32() { + const auto bytes = getNextBytes(sizeof(uint32_t)); + return decode32BE(bytes.data()); +} + +Data CellSlice::getNextBytes(uint8_t bytes) { + if (bytes == 0) { + return Data{}; + } + require(bytes * 8); + Data result{}; + result.reserve(bytes); + + const size_t q = dataOffset / 8; + const auto r = dataOffset % 8; + const auto invR = 8 - r; + + dataOffset += bytes * 8; + + if (r == 0) { + const auto begin = cell->data.begin() + q; + const auto end = begin + bytes; + std::copy(begin, end, std::back_inserter(result)); + return result; + } + + for (size_t byte = q; byte < q + bytes; ++byte) { + auto bits = static_cast(static_cast(cell->data[byte]) << 8); + if (byte + 1 < cell->data.size()) { + bits |= static_cast(cell->data[byte + 1]); + } + result.push_back(static_cast(bits >> invR)); + } + return result; +} + +void CellSlice::require(uint16_t bits) const { + if (dataOffset + bits > cell->bitLen) { + throw std::runtime_error("cell data underflow"); + } +} + +} // namespace TW::CommonTON diff --git a/src/Everscale/CommonTON/CellSlice.h b/src/Everscale/CommonTON/CellSlice.h new file mode 100644 index 00000000000..be98c2f185a --- /dev/null +++ b/src/Everscale/CommonTON/CellSlice.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include +#include + +#include "Cell.h" + +namespace TW::CommonTON { + +class CellSlice { +public: + const Cell* _Nonnull cell; + uint16_t dataOffset = 0; + + explicit CellSlice(const Cell* _Nonnull cell) noexcept + : cell(cell) {} + + uint32_t getNextU32(); + Data getNextBytes(uint8_t bytes); + +private: + void require(uint16_t bits) const; +}; + +} // namespace TW::CommonTON diff --git a/src/Everscale/CommonTON/Messages.cpp b/src/Everscale/CommonTON/Messages.cpp new file mode 100644 index 00000000000..ae79a75111f --- /dev/null +++ b/src/Everscale/CommonTON/Messages.cpp @@ -0,0 +1,76 @@ +#include "Messages.h" + +namespace TW::CommonTON { + +void ExternalInboundMessageHeader::writeTo(CellBuilder& builder) const { + builder.appendBitOne(); + builder.appendBitZero(); + + // addr src (none) + builder.appendRaw(Data{0x00}, 2); + + // addr dst + Data dstAddr(_dst.hash.begin(), _dst.hash.end()); + + Data prefix{0x80}; + builder.appendRaw(prefix, 2); + + builder.appendBitZero(); + builder.appendI8(_dst.workchainId); + builder.appendRaw(dstAddr, 256); + + // fee + builder.appendU128(_importFee); +} + +void InternalMessageHeader::writeTo(CellBuilder& builder) const { + // tag + builder.appendBitZero(); + + builder.appendBitBool(_ihrDisabled); + builder.appendBitBool(_bounce); + builder.appendBitBool(_bounced); + + // addr src (none) + builder.appendRaw(Data{0x00}, 2); + + // addr dst + Data dstAddr(_dst.hash.begin(), _dst.hash.end()); + + Data prefix{0x80}; + builder.appendRaw(prefix, 2); + + builder.appendBitZero(); + builder.appendI8(_dst.workchainId); + builder.appendRaw(dstAddr, 256); + + // value + builder.appendU128(_value); + + // currency collections + builder.appendBitZero(); + + // fee + builder.appendU128(_ihrFee); + builder.appendU128(_fwdFee); + + // created + builder.appendU64(_createdLt); + builder.appendU32(_createdAt); +} + +CellBuilder StateInit::writeTo() const { + CellBuilder builder; + + builder.appendBitZero(); // split_depth + builder.appendBitZero(); // special + builder.appendBitOne(); // code + builder.appendReferenceCell(code); + builder.appendBitOne(); // data + builder.appendReferenceCell(data); + builder.appendBitZero(); // library + + return builder; +} + +} // namespace TW::CommonTON \ No newline at end of file diff --git a/src/Everscale/CommonTON/Messages.h b/src/Everscale/CommonTON/Messages.h new file mode 100644 index 00000000000..232f92664d0 --- /dev/null +++ b/src/Everscale/CommonTON/Messages.h @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include + +#include "Cell.h" +#include "CellBuilder.h" +#include "CellSlice.h" +#include "RawAddress.h" + +#include "PrivateKey.h" + +namespace TW::CommonTON { + +class CommonMsgInfo { +public: + virtual void writeTo(CellBuilder& builder) const = 0; + + virtual ~CommonMsgInfo() = default; +}; + +class ExternalInboundMessageHeader : public CommonMsgInfo { + AddressData _dst; + uint128_t _importFee{}; + +public: + explicit ExternalInboundMessageHeader(AddressData dst) + : _dst(dst) {} + + void writeTo(CellBuilder& builder) const override; +}; + +class InternalMessageHeader : public CommonMsgInfo { + bool _ihrDisabled; + bool _bounce; + AddressData _dst; + uint128_t _value; + + bool _bounced{}; + uint128_t _ihrFee{}; + uint128_t _fwdFee{}; + uint64_t _createdLt{}; + uint32_t _createdAt{}; + +public: + InternalMessageHeader(bool ihrDisabled, bool bounce, AddressData dst, uint64_t value) + : _ihrDisabled(ihrDisabled), _bounce(bounce), _dst(dst), _value(static_cast(value)) {} + + void writeTo(CellBuilder& builder) const override; +}; + +class StateInit { +public: + Cell::Ref code; + Cell::Ref data; + + [[nodiscard]] CellBuilder writeTo() const; +}; + +struct MessageData { + std::shared_ptr header; + std::optional init{}; + std::optional body{}; + + using HeaderRef = std::shared_ptr; + + explicit MessageData(HeaderRef header) : header(std::move(header)) { } +}; + +} // namespace TW::CommonTON diff --git a/src/Everscale/CommonTON/RawAddress.cpp b/src/Everscale/CommonTON/RawAddress.cpp new file mode 100644 index 00000000000..5f01bf7906c --- /dev/null +++ b/src/Everscale/CommonTON/RawAddress.cpp @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include + +#include "RawAddress.h" + +#include "HexCoding.h" +#include "WorkchainType.h" + +using MaybeWorkchain = std::optional>; + +namespace TW::CommonTON { + +static std::optional parse_long(char const *s) +{ + char *end; + auto previous_errno = errno; + errno = 0; + long l = strtol(s, &end, 10); + if (errno == ERANGE && l == LONG_MAX) { + errno = previous_errno; + return {}; // Overflow + } + if (errno == ERANGE && l == LONG_MIN) { + errno = previous_errno; + return {}; // Underflow + } + if (*s == '\0' || *end != '\0') { + errno = previous_errno; + return {}; // Inconvertible + } + errno = previous_errno; + return l; +} + +static std::optional parse_int8(char const *s) { + auto value = parse_long(s); + if (!value.has_value()) { + return std::nullopt; + } + if (value.value() <= static_cast(std::numeric_limits::max()) && value.value() >= static_cast(std::numeric_limits::min())) { + return value; + } else { + return std::nullopt; + } +} + +static MaybeWorkchain parseWorkchainId(const std::string& string) { + if (auto pos = string.find(':'); pos != std::string::npos) { + std::string workchain_string = string.substr(0, pos); + auto workchain_id = parse_int8(workchain_string.c_str()); + if (workchain_id.has_value()) { + return std::make_pair(workchain_id.value(), pos + 1); + } + } + return {}; +} + +bool RawAddress::isValid(const std::string& string) { + auto parsed = parseWorkchainId(string); + if (!parsed.has_value()) { + return false; + } + + auto [workchainId, pos] = *parsed; + + if (workchainId != WorkchainType::Basechain && workchainId != WorkchainType::Masterchain) { + return false; + } + + if (string.size() != pos + AddressData::hexAddrLen) { + return false; + } + + std::string addr = string.substr(pos); + if (!is_hex_encoded(addr)) { + return false; + } + return parse_hex(addr).size() == AddressData::size; +} + +AddressData RawAddress::splitAddress(const std::string& address) { + auto parsed = parseWorkchainId(address); + auto [parsedWorkchainId, pos] = *parsed; + + auto workchainId = parsedWorkchainId; + + const auto parsedHash = parse_hex(address.substr(pos)); + + std::array hash{}; + std::copy(begin(parsedHash), end(parsedHash), begin(hash)); + + return AddressData(workchainId, hash); +} + +std::string RawAddress::to_string(const AddressData& addressData) { + std::string address = std::to_string(addressData.workchainId) + ":" + hex(addressData.hash); + return address; +} + +} // namespace TW::CommonTON diff --git a/src/Everscale/CommonTON/RawAddress.h b/src/Everscale/CommonTON/RawAddress.h new file mode 100644 index 00000000000..f628aefb018 --- /dev/null +++ b/src/Everscale/CommonTON/RawAddress.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include +#include + +#include "Hash.h" + +namespace TW::CommonTON { + +struct AddressData { + /// Number of bytes in an address + static const size_t size = Hash::sha256Size; + + /// Hex address length + static const size_t hexAddrLen = size * 2; + + /// Workchain ID (-1 for masterchain, 0 for base workchain) + std::int8_t workchainId{}; + + /// StateInit hash + std::array hash{}; + + explicit AddressData(int8_t workchainId, std::array hash) + : workchainId(workchainId), hash(hash) {} + + explicit AddressData() = default; +}; + +struct RawAddress { + static bool isValid(const std::string& string); + static AddressData splitAddress(const std::string& address); + static std::string to_string(const AddressData& addressData); +}; + +} // namespace TW::CommonTON diff --git a/src/Everscale/CommonTON/WorkchainType.h b/src/Everscale/CommonTON/WorkchainType.h new file mode 100644 index 00000000000..2a50e086ede --- /dev/null +++ b/src/Everscale/CommonTON/WorkchainType.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +namespace TW::CommonTON { + +enum WorkchainType { + Masterchain = -1, + Basechain = 0, +}; + +} // namespace TW::CommonTON diff --git a/src/Everscale/Entry.cpp b/src/Everscale/Entry.cpp new file mode 100644 index 00000000000..d0291c7826d --- /dev/null +++ b/src/Everscale/Entry.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Entry.h" + +#include "Address.h" +#include "Signer.h" +#include "WorkchainType.h" + +using namespace TW; +using namespace std; + +namespace TW::Everscale { + +// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Address::isValid(address); +} + +string Entry::normalizeAddress([[maybe_unused]] TWCoinType coin, const string& address) const { + return Address(address).string(); +} + +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Address(publicKey, WorkchainType::Basechain).string(); +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +} // namespace TW::Everscale diff --git a/src/Everscale/Entry.h b/src/Everscale/Entry.h new file mode 100644 index 00000000000..00bbd1f1b23 --- /dev/null +++ b/src/Everscale/Entry.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../CoinEntry.h" + +namespace TW::Everscale { + +/// Entry point for implementation of Everscale coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final : public CoinEntry { +public: + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; +}; + +} // namespace TW::Everscale diff --git a/src/Everscale/Messages.cpp b/src/Everscale/Messages.cpp new file mode 100644 index 00000000000..47d2b4d72a2 --- /dev/null +++ b/src/Everscale/Messages.cpp @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Messages.h" + +#include "Wallet.h" +#include "WorkchainType.h" + +using namespace TW; + +namespace TW::Everscale { + +Cell::Ref Message::intoCell() const { + CellBuilder builder; + + // write Header + _messageData.header->writeTo(builder); + + // write StateInit + if (_messageData.init.has_value()) { + auto initBuilder = _messageData.init.value().writeTo(); + + builder.appendBitOne(); + builder.appendBitZero(); + builder.appendBuilder(initBuilder); + } else { + builder.appendBitZero(); + } + + // write Body + if (_messageData.body.has_value()) { + builder.appendBitZero(); + builder.appendCellSlice(CellSlice(_messageData.body.value().get())); + } else { + builder.appendBitZero(); + } + + return builder.intoCell(); +} + +MessageData createSignedMessage(PublicKey& publicKey, PrivateKey& key, bool bounce, uint32_t flags, uint64_t amount, uint32_t expiredAt, + Address to, const Cell::Ref& contractData) { + auto getInitData = [](const PublicKey& publicKey, const Cell::Ref& contractData) { + if (contractData != nullptr) { + auto cellSlice = CellSlice(contractData.get()); + return std::make_pair(InitData(cellSlice), /* withInitState */ false); + } else { + return std::make_pair(InitData(publicKey), /* withInitState */ true); + } + }; + + auto [initData, withInitState] = getInitData(publicKey, contractData); + + auto gift = Wallet::Gift{ + .bounce = bounce, + .amount = amount, + .to = to, + .flags = static_cast(flags), + }; + + auto payload = initData.makeTransferPayload(expiredAt, gift); + + auto payloadCopy = payload; + auto payloadCell = payloadCopy.intoCell(); + + Data data(payloadCell->hash.begin(), payloadCell->hash.end()); + auto signature = key.sign(data, TWCurveED25519); + payload.prependRaw(signature, static_cast(signature.size()) * 8); + + auto header = std::make_shared(InitData(publicKey).computeAddr(WorkchainType::Basechain)); + auto message = MessageData(header); + + if (withInitState) { + message.init = initData.makeStateInit(); + } + + message.body = payload.intoCell(); + + return message; +} + +} // namespace TW::Everscale diff --git a/src/Everscale/Messages.h b/src/Everscale/Messages.h new file mode 100644 index 00000000000..bd32b7f1286 --- /dev/null +++ b/src/Everscale/Messages.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Address.h" +#include "CommonTON/Messages.h" + +using namespace TW::CommonTON; + +namespace TW::Everscale { + +class Message { +private: + MessageData _messageData; + +public: + explicit Message(MessageData messageData) : _messageData(std::move(messageData)) {} + [[nodiscard]] Cell::Ref intoCell() const; +}; + +MessageData createSignedMessage(PublicKey& publicKey, PrivateKey& key, bool bounce, uint32_t flags, uint64_t amount, + uint32_t expiredAt, Address destination, const Cell::Ref& contractData); + +} // namespace TW::Everscale diff --git a/src/Everscale/Signer.cpp b/src/Everscale/Signer.cpp new file mode 100644 index 00000000000..33e5ee368b0 --- /dev/null +++ b/src/Everscale/Signer.cpp @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Signer.h" +#include "Address.h" +#include "Messages.h" +#include "Wallet.h" + +#include "Base64.h" + +namespace TW::Everscale { + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { + auto key = PrivateKey(input.private_key()); + auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); + + auto protoOutput = Proto::SigningOutput(); + + switch (input.action_oneof_case()) { + case Proto::SigningInput::ActionOneofCase::kTransfer: { + const auto& transfer = input.transfer(); + + uint8_t flags; + switch (transfer.behavior()) { + case Proto::MessageBehavior::SendAllBalance: { + flags = Wallet::sendAllBalanceFlags; + break; + } + default: { + flags = Wallet::simpleTransferFlags; + break; + } + } + + Cell::Ref contractData{}; + switch (transfer.account_state_oneof_case()) { + case Proto::Transfer::AccountStateOneofCase::kEncodedContractData: { + contractData = Cell::fromBase64(transfer.encoded_contract_data()); + break; + } + default: + break; + } + + auto signedMessage = createSignedMessage( + publicKey, + key, + transfer.bounce(), + flags, + transfer.amount(), + transfer.expired_at(), + Address(transfer.to()), + contractData); + Data result{}; + Message(signedMessage).intoCell()->serialize(result); + + protoOutput.set_encoded(TW::Base64::encode(result)); + break; + } + default: + break; + } + + return protoOutput; +} + +} // namespace TW::Everscale diff --git a/src/Everscale/Signer.h b/src/Everscale/Signer.h new file mode 100644 index 00000000000..4e14e19e8e0 --- /dev/null +++ b/src/Everscale/Signer.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "../PrivateKey.h" +#include "../proto/Everscale.pb.h" + +namespace TW::Everscale { + +/// Helper class that performs Everscale transaction signing. +class Signer { +public: + /// Hide default constructor + Signer() = delete; + + /// Signs a Proto::SigningInput transaction + static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; +}; + +} // namespace TW::Everscale diff --git a/src/Everscale/Wallet.cpp b/src/Everscale/Wallet.cpp new file mode 100644 index 00000000000..73fa3c39885 --- /dev/null +++ b/src/Everscale/Wallet.cpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Wallet.h" +#include "Messages.h" + +#include "HexCoding.h" + +namespace TW::Everscale { + +// WalletV3 contract https://github.com/tonlabs/ton-1/blob/master/crypto/smartcont/wallet3-code.fc +const Data Wallet::code = parse_hex("b5ee9c720101010100710000deff0020dd2082014c97ba218201339cbab19f71b0ed44d0d31fd31f31d70bffe304e0a4f2608308d71820d31fd31fd31ff82313bbf263ed44d0d31fd31fd3ffd15132baf2a15144baf2a204f901541055f910f2a3f8009320d74a96d307d402fb00e8d101a4c8cb1fcb1fcbffc9ed54"); + +CellBuilder InitData::writeTo() const { + CellBuilder builder; + + builder.appendU32(_seqno); + builder.appendU32(_walletId); + builder.appendRaw(_publicKey.bytes, 256); + + return builder; +} + +AddressData InitData::computeAddr(int8_t workchainId) const { + auto builder = this->writeTo(); + + StateInit stateInit{ + .code = Cell::deserialize(Wallet::code.data(), Wallet::code.size()), + .data = builder.intoCell(), + }; + return AddressData(workchainId, stateInit.writeTo().intoCell()->hash); +} + +StateInit InitData::makeStateInit() const { + auto builder = this->writeTo(); + + return StateInit{ + .code = Cell::deserialize(Wallet::code.data(), Wallet::code.size()), + .data = builder.intoCell(), + }; +} + +CellBuilder InitData::makeTransferPayload(uint32_t expireAt, const Wallet::Gift& gift) const { + CellBuilder payload; + + // insert prefix + payload.appendU32(_walletId); + payload.appendU32(expireAt); + payload.appendU32(_seqno); + + // create internal message + MessageData::HeaderRef header = std::make_shared(true, gift.bounce, gift.to.addressData, gift.amount); + auto message = Message(MessageData(header)); + + // append it to the body + payload.appendU8(gift.flags); + payload.appendReferenceCell(message.intoCell()); + + return payload; +} + +} // namespace TW::Everscale diff --git a/src/Everscale/Wallet.h b/src/Everscale/Wallet.h new file mode 100644 index 00000000000..7efd68b7778 --- /dev/null +++ b/src/Everscale/Wallet.h @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include + +#include "PublicKey.h" + +#include "Address.h" +#include "CommonTON/Cell.h" +#include "CommonTON/CellBuilder.h" +#include "CommonTON/CellSlice.h" +#include "CommonTON/Messages.h" + +const uint32_t WALLET_ID = 0x4BA92D8A; + +using namespace TW::CommonTON; + +namespace TW::Everscale { + +class Wallet { +public: + enum MessageFlags : uint8_t { + // Sender wants to pay transfer fees separately + // (from account balance instead of message balance) + FeesFromAccountBalance = (1 << 0), + + // If there are some errors during the action phase it should be ignored + IgnoreActionPhaseErrors = (1 << 1), + + // Message will carry all the remaining balance + AttachAllBalance = (1 << 7), + }; + + struct Gift { + bool bounce; + uint64_t amount; + Address to; + uint8_t flags; + }; + + static constexpr uint8_t simpleTransferFlags = + MessageFlags::FeesFromAccountBalance | MessageFlags::IgnoreActionPhaseErrors; + static constexpr uint8_t sendAllBalanceFlags = + MessageFlags::AttachAllBalance | MessageFlags::IgnoreActionPhaseErrors; + + static const Data code; +}; + +class InitData { + uint32_t _seqno; + uint32_t _walletId; + PublicKey _publicKey; + +public: + explicit InitData(PublicKey publicKey) + : _seqno(0), _walletId(WALLET_ID), _publicKey(std::move(publicKey)) {} + explicit InitData(CellSlice cs) + : _seqno(cs.getNextU32()), _walletId(cs.getNextU32()), _publicKey(PublicKey(cs.getNextBytes(32), TWPublicKeyTypeED25519)) {} + + [[nodiscard]] CellBuilder writeTo() const; + [[nodiscard]] StateInit makeStateInit() const; + [[nodiscard]] AddressData computeAddr(int8_t workchainId) const; + [[nodiscard]] CellBuilder makeTransferPayload(uint32_t expireAt, const Wallet::Gift& gift) const; +}; + +} // namespace TW::Everscale diff --git a/src/Everscale/WorkchainType.h b/src/Everscale/WorkchainType.h new file mode 100644 index 00000000000..10a6cab39a3 --- /dev/null +++ b/src/Everscale/WorkchainType.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "CommonTON/WorkchainType.h" + +namespace TW::Everscale { + using WorkchainType = CommonTON::WorkchainType; +} // namespace TW::Everscale diff --git a/src/FIO/Action.cpp b/src/FIO/Action.cpp index 9a64137e7bb..34e1a8455cd 100644 --- a/src/FIO/Action.cpp +++ b/src/FIO/Action.cpp @@ -1,11 +1,9 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Action.h" -#include "../Data.h" +#include "Data.h" namespace TW::FIO { @@ -43,7 +41,7 @@ void Action::serialize(Data& out) const { append(out, 0); // 00 } -void AddPubAddressData::serialize(Data& out) const { +void PubAddressActionData::serialize(Data& out) const { encodeString(fioAddress, out); addresses.serialize(out); encode64LE(fee, out); @@ -51,6 +49,13 @@ void AddPubAddressData::serialize(Data& out) const { encodeString(tpid, out); } +void RemoveAllPubAddressActionData::serialize(Data& out) const { + encodeString(fioAddress, out); + encode64LE(fee, out); + EOS::Name(actor).serialize(out); + encodeString(tpid, out); +} + void RegisterFioAddressData::serialize(Data& out) const { encodeString(fioAddress, out); encodeString(ownerPublicKey, out); @@ -83,4 +88,12 @@ void NewFundsRequestData::serialize(Data& out) const { encodeString(tpid, out); } +void AddBundledTransactionsActionData::serialize(Data& out) const { + encodeString(fioAddress, out); + encode64LE(bundledSets, out); + encode64LE(fee, out); + encodeString(tpid, out); + EOS::Name(actor).serialize(out); +} + } // namespace TW::FIO diff --git a/src/FIO/Action.h b/src/FIO/Action.h index c79ef65a500..a5c325d3341 100644 --- a/src/FIO/Action.h +++ b/src/FIO/Action.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "EOS/Name.h" // Name is reused -#include "../Data.h" +#include "Data.h" #include "../BinaryCoding.h" #include @@ -16,7 +14,7 @@ namespace TW::FIO { /// Encodes a value as a variable-length integer. -/// @returns the number of bytes written. +/// \returns the number of bytes written. uint8_t encodeVarInt(uint64_t num, Data& data); /// Encodes an ASCII string prefixed by the length (varInt) @@ -83,8 +81,11 @@ class Action { void serialize(Data& out) const; }; -/// AddPubAddress action data part. -class AddPubAddressData { +/// A public address action data part. +/// Can be used for `addaddress`, `remaddress`, `remalladdr` (addresses must be empty) actions. +/// https://dev.fio.net/reference/add_pub_address +/// https://dev.fio.net/reference/remove_pub_address +class PubAddressActionData { public: std::string fioAddress; PublicAddresses addresses; @@ -92,13 +93,27 @@ class AddPubAddressData { std::string tpid; std::string actor; - AddPubAddressData(const std::string& fioAddress, const std::vector& addresses, + PubAddressActionData(const std::string& fioAddress, const std::vector& addresses, uint64_t fee, const std::string& tpid, const std::string& actor) : fioAddress(fioAddress), addresses(addresses), fee(fee), tpid(tpid), actor(actor) {} void serialize(Data& out) const; }; +/// RemoveAllPubAddress action data part. +/// https://dev.fio.net/reference/remove_all_pub_address +class RemoveAllPubAddressActionData { +public: + std::string fioAddress; + uint64_t fee; + std::string tpid; + std::string actor; + + RemoveAllPubAddressActionData(const std::string& fioAddress, uint64_t fee, const std::string& tpid, const std::string& actor) : + fioAddress(fioAddress), fee(fee), tpid(tpid), actor(actor) {} + void serialize(Data& out) const; +}; + /// RegisterFioAddress action data part. class RegisterFioAddressData { public: @@ -157,4 +172,19 @@ class NewFundsRequestData { void serialize(Data& out) const; }; +/// AddBundledTransactions action data part. +/// https://dev.fio.net/reference/add_bundled_transactions +class AddBundledTransactionsActionData { +public: + std::string fioAddress; + uint64_t bundledSets; + uint64_t fee; + std::string tpid; + std::string actor; + + AddBundledTransactionsActionData(const std::string& fioAddress, uint64_t bundledSets, uint64_t fee, const std::string& tpid, const std::string& actor) : + fioAddress(fioAddress), bundledSets(bundledSets), fee(fee), tpid(tpid), actor(actor) {} + void serialize(Data& out) const; +}; + } // namespace TW::FIO diff --git a/src/FIO/Actor.cpp b/src/FIO/Actor.cpp index 550b52489bd..5463da1cda1 100644 --- a/src/FIO/Actor.cpp +++ b/src/FIO/Actor.cpp @@ -1,56 +1,55 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Actor.h" #include -using namespace TW::FIO; -using namespace std; +namespace TW::FIO { -static const auto pattern = regex(R"(\b([a-z1-5]{3,})[.@]?\b)"); -string Actor::actor(const Address& addr) -{ +static const auto pattern = std::regex(R"(\b([a-z1-5]{3,})[.@]?\b)"); +std::string Actor::actor(const Address& addr) { uint64_t shortenedKey = shortenKey(addr.bytes); - string name13 = name(shortenedKey); + std::string name13 = name(shortenedKey); // trim to 12 chracters return name13.substr(0, 12); } bool Actor::validate(const std::string& addr) { - smatch match; + std::smatch match; return regex_search(addr, match, pattern); } -uint64_t Actor::shortenKey(const array& addrKey) -{ +uint64_t Actor::shortenKey(const std::array& addrKey) { uint64_t res = 0; int i = 1; // Ignore key head int len = 0; while (len <= 12) { assert(i < 33); // Means the key has > 20 bytes with trailing zeroes... - + auto trimmed_char = uint64_t(addrKey[i] & (len == 12 ? 0x0f : 0x1f)); - if (trimmed_char == 0) { i++; continue; } // Skip a zero and move to next + if (trimmed_char == 0) { + i++; + continue; + } // Skip a zero and move to next auto shuffle = len == 12 ? 0 : 5 * (12 - len) - 1; res |= trimmed_char << shuffle; - len++; i++; + len++; + i++; } return res; } -string Actor::name(uint64_t shortKey) { +std::string Actor::name(uint64_t shortKey) { static const char* charmap = ".12345abcdefghijklmnopqrstuvwxyz"; - string str(13,'.'); //We are forcing the string to be 13 characters + std::string str(13, '.'); // We are forcing the string to be 13 characters uint64_t tmp = shortKey; - for(uint32_t i = 0; i <= 12; i++ ) { + for (uint32_t i = 0; i <= 12; i++) { char c = charmap[tmp & (i == 0 ? 0x0f : 0x1f)]; str[12 - i] = c; tmp >>= (i == 0 ? 4 : 5); @@ -58,3 +57,5 @@ string Actor::name(uint64_t shortKey) { return str; } + +} // namespace TW::FIO diff --git a/src/FIO/Actor.h b/src/FIO/Actor.h index 2024d756c05..dd5157890c0 100644 --- a/src/FIO/Actor.h +++ b/src/FIO/Actor.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/FIO/Address.cpp b/src/FIO/Address.cpp index 362cd7c6a9d..abfc1046846 100644 --- a/src/FIO/Address.cpp +++ b/src/FIO/Address.cpp @@ -1,19 +1,18 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. +#include "Address.h" #include "../Base58.h" #include "../BinaryCoding.h" -#include "Address.h" #include #include using namespace TW; -using namespace TW::FIO; + +namespace TW::FIO { bool Address::isValid(const std::string& string) { return decodeKeyData(string).has_value(); @@ -56,7 +55,7 @@ std::optional Address::decodeKeyData(const std::string& string) { return {}; } - const Data& decodedBytes = Base58::bitcoin.decode(string.substr(prefixSize)); + const Data& decodedBytes = Base58::decode(string.substr(prefixSize)); if (decodedBytes.size() != size) { return {}; } @@ -93,7 +92,7 @@ Address::Address(const PublicKey& publicKey) { /// Returns a string representation of the FIO address. std::string Address::string() const { - return prefix() + Base58::bitcoin.encode(bytes); + return prefix() + Base58::encode(bytes); } PublicKey Address::publicKey() const { @@ -101,3 +100,5 @@ PublicKey Address::publicKey() const { const Data keyData = TW::data(bytes.data(), PublicKey::secp256k1Size); return PublicKey(keyData, TWPublicKeyTypeSECP256k1); } + +} // namespace TW::FIO diff --git a/src/FIO/Address.h b/src/FIO/Address.h index 25dba8e7bf5..049a72d2b9e 100644 --- a/src/FIO/Address.h +++ b/src/FIO/Address.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/FIO/Encryption.cpp b/src/FIO/Encryption.cpp index 0a73a45a022..ee2c2b54c6c 100644 --- a/src/FIO/Encryption.cpp +++ b/src/FIO/Encryption.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Encryption.h" diff --git a/src/FIO/Encryption.h b/src/FIO/Encryption.h index ecc7106f8b8..431a685fdd3 100644 --- a/src/FIO/Encryption.h +++ b/src/FIO/Encryption.h @@ -1,40 +1,38 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../PublicKey.h" namespace TW::FIO { /// Payload message encryption/decryption. -/// See also https://github.com/fioprotocol/fiojs/blob/master/src/encryption-check.ts +/// \see https://github.com/fioprotocol/fiojs/blob/master/src/encryption-check.ts class Encryption { public: /** * Provides AES-256-CBC encryption and message authentication. * The CBC cipher is used for good platform native compatability. - * @see https://security.stackexchange.com/a/63134 - * @see https://security.stackexchange.com/a/20493 - * @arg secret - Shared secret (64-bytes). - * @arg message - Plaintext message (arbitrary length). - * @arg iv - An unpredictable strong random value (16 bytes) is required + * \see https://security.stackexchange.com/a/63134 + * \see https://security.stackexchange.com/a/20493 + * \param secret - Shared secret (64-bytes). + * \param message - Plaintext message (arbitrary length). + * \param iv - An unpredictable strong random value (16 bytes) is required * and supplied by default. Unit tests may provide a static value to achieve predictable results. - * @throws {Error} invalid IV size + * \throws {Error} invalid IV size */ static Data checkEncrypt(const Data& secret, const Data& message, Data& iv); /** * Provides AES-256-CBC message authentication then decryption. - * @arg secret - Shared secret (64-bytes). - * @arg message - Ciphertext (from checkEncrypt()) - * @throws {Error} Message too short - * @throws {Error} Decrypt failed, HMAC mismatch + * \param secret - Shared secret (64-bytes). + * \param message - Ciphertext (from checkEncrypt()) + * \throws {Error} Message too short + * \throws {Error} Decrypt failed, HMAC mismatch */ static Data checkDecrypt(const Data& secret, const Data& message); diff --git a/src/FIO/Entry.cpp b/src/FIO/Entry.cpp index 1caec6f5a68..5cc91faefba 100644 --- a/src/FIO/Entry.cpp +++ b/src/FIO/Entry.cpp @@ -1,27 +1,51 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" #include "Signer.h" +#include "../proto/TransactionCompiler.pb.h" +#include "TransactionBuilder.h" -using namespace TW::FIO; -using namespace std; +namespace TW::FIO { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data &txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto &input, auto &output) { + + Proto::SigningInput in = input; + auto unsignedTxSerialize = TransactionBuilder::buildUnsignedTxBytes(input); + auto preSignData = TransactionBuilder::buildPreSignTxData(TW::data(in.chain_params().chain_id()), unsignedTxSerialize); + auto imageHash = Hash::sha256(preSignData); + output.set_data(preSignData.data(), preSignData.size()); + output.set_data_hash(imageHash.data(), imageHash.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, [[maybe_unused]] const auto& publicKey) { + output = Signer::compile(input, signature); + }); +} + + +} // namespace TW::FIO diff --git a/src/FIO/Entry.h b/src/FIO/Entry.h index 1d7abb7386b..ccfbd5b4c91 100644 --- a/src/FIO/Entry.h +++ b/src/FIO/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,11 +10,14 @@ namespace TW::FIO { /// Entry point for implementation of FIO coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::FIO diff --git a/src/FIO/NewFundsRequest.cpp b/src/FIO/NewFundsRequest.cpp index 257003f15f6..3c71dc3d5a8 100644 --- a/src/FIO/NewFundsRequest.cpp +++ b/src/FIO/NewFundsRequest.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "NewFundsRequest.h" #include "../BinaryCoding.h" diff --git a/src/FIO/NewFundsRequest.h b/src/FIO/NewFundsRequest.h index e3602fd54b1..822f38a0656 100644 --- a/src/FIO/NewFundsRequest.h +++ b/src/FIO/NewFundsRequest.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include diff --git a/src/FIO/Signer.cpp b/src/FIO/Signer.cpp index f3a96ad0954..0e90e84df30 100644 --- a/src/FIO/Signer.cpp +++ b/src/FIO/Signer.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "Address.h" @@ -25,29 +23,37 @@ using namespace std; Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { FIO::Proto::SigningOutput output; - try { + try { + const string actionName = TransactionBuilder::actionName(input); const string json = TransactionBuilder::sign(input); output.set_json(json); + output.set_action_name(actionName); } catch(const std::exception& e) { output.set_error(Common::Proto::Error_internal); } return output; } +Proto::SigningOutput Signer::compile(const Proto::SigningInput& input, const Data& signature) noexcept { + FIO::Proto::SigningOutput output; + output = TransactionBuilder::buildSigningOutput(input, signature); + return output; +} + Data Signer::signData(const PrivateKey& privKey, const Data& data) { Data hash = Hash::sha256(data); Data signature = privKey.sign(hash, TWCurveSECP256k1, isCanonical); return signature; } -std::string Signer::signatureToBsase58(const Data& sig) { +std::string Signer::signatureToBase58(const Data& sig) { Data sigWithSuffix(sig); append(sigWithSuffix, TW::data(SignatureSuffix)); // take hash, ripemd, first 4 bytes Data hash = Hash::ripemd(sigWithSuffix); Data sigWithChecksum(sig); append(sigWithChecksum, TW::data(hash.data(), 4)); - string s = SignaturePrefix + Base58::bitcoin.encode(sigWithChecksum); + string s = SignaturePrefix + Base58::encode(sigWithChecksum); return s; } @@ -55,8 +61,8 @@ bool Signer::verify(const PublicKey& pubKey, const Data& data, const Data& signa return pubKey.verify(TW::data(signature.data() + 1, signature.size() - 1), data); } -// canonical check for FIO, both R and S lenght is 32 -int Signer::isCanonical(uint8_t by, uint8_t sig[64]) { +// canonical check for FIO, both R and S length is 32 +int Signer::isCanonical([[maybe_unused]] uint8_t by, uint8_t sig[64]) { return !(sig[0] & 0x80) && !(sig[0] == 0 && !(sig[1] & 0x80)) && !(sig[32] & 0x80) diff --git a/src/FIO/Signer.h b/src/FIO/Signer.h index 7604ca4f953..72b3d7b7098 100644 --- a/src/FIO/Signer.h +++ b/src/FIO/Signer.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Address.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../PublicKey.h" #include "../proto/FIO.pb.h" @@ -21,7 +19,8 @@ class Signer { public: /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; - + /// Build the compile output + static Proto::SigningOutput compile(const Proto::SigningInput& input, const Data& signature) noexcept; public: static constexpr auto SignatureSuffix = "K1"; static constexpr auto SignaturePrefix = "SIG_K1_"; @@ -30,7 +29,7 @@ class Signer { static Data signData(const PrivateKey& privKey, const Data& data); /// Used internally, encode signature to base58 with prefix. Ex.: "SIG_K1_K54CA1jmhgWrSdvrNrkokPyvqh7dwsSoQHNU9xgD3Ezf6cJySzhKeUubVRqmpYdnjoP1DM6SorroVAgrCu3qqvJ9coAQ6u" - static std::string signatureToBsase58(const Data& sig); + static std::string signatureToBase58(const Data& sig); /// Verify a signature, used in testing static bool verify(const PublicKey& pubKey, const Data& data, const Data& signature); diff --git a/src/FIO/Transaction.cpp b/src/FIO/Transaction.cpp index c05e210066d..d0d7bb31340 100644 --- a/src/FIO/Transaction.cpp +++ b/src/FIO/Transaction.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Transaction.h" #include "TransactionBuilder.h" diff --git a/src/FIO/Transaction.h b/src/FIO/Transaction.h index 67f70939ff7..0204b2d0acf 100644 --- a/src/FIO/Transaction.h +++ b/src/FIO/Transaction.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Action.h" -#include "../Data.h" +#include "Data.h" #include diff --git a/src/FIO/TransactionBuilder.cpp b/src/FIO/TransactionBuilder.cpp index c0203ec9911..4a890d81705 100644 --- a/src/FIO/TransactionBuilder.cpp +++ b/src/FIO/TransactionBuilder.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "TransactionBuilder.h" @@ -23,6 +21,14 @@ using namespace TW; using namespace std; using json = nlohmann::json; +static constexpr auto gRegisterFioAddress = "regaddress"; +static constexpr auto gAddPubAddress = "addaddress"; +static constexpr auto gRemoveAddress = "remaddress"; +static constexpr auto gRemoveAllPubAddresses = "remalladdr"; +static constexpr auto gTransferFIOPubkey = "trnsfiopubky"; +static constexpr auto gRenewFIOAddress = "renewaddress"; +static constexpr auto gNewFundsRequest = "newfundsreq"; +static constexpr auto gAddBundledTransactions = "addbundles"; /// Internal helper ChainParams getChainParams(const Proto::SigningInput& input) { @@ -40,6 +46,29 @@ bool TransactionBuilder::expirySetDefaultIfNeeded(uint32_t& expiryTime) { return true; } +string TransactionBuilder::actionName(const Proto::SigningInput& input) { + switch (input.action().message_oneof_case()) { + case Proto::Action::MessageOneofCase::kRegisterFioAddressMessage: + return gRegisterFioAddress; + case Proto::Action::MessageOneofCase::kAddPubAddressMessage: + return gAddPubAddress; + case Proto::Action::MessageOneofCase::kTransferMessage: + return gTransferFIOPubkey; + case Proto::Action::MessageOneofCase::kRenewFioAddressMessage: + return gRenewFIOAddress; + case Proto::Action::MessageOneofCase::kNewFundsRequestMessage: + return gNewFundsRequest; + case Proto::Action::MessageOneofCase::kRemovePubAddressMessage: + return gRemoveAddress; + case Proto::Action::MessageOneofCase::kRemoveAllPubAddressesMessage: + return gRemoveAllPubAddresses; + case Proto::Action::MessageOneofCase::kAddBundledTransactionsMessage: + return gAddBundledTransactions; + default: + return {}; + } +} + string TransactionBuilder::sign(Proto::SigningInput in) { PrivateKey privateKey(in.private_key()); PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); @@ -78,6 +107,24 @@ string TransactionBuilder::sign(Proto::SigningInput in) { action.payer_fio_name(), action.payer_fio_address(), action.payee_fio_name(), content.payee_public_address(), content.amount(), content.coin_symbol(), content.memo(), content.hash(), content.offline_url(), getChainParams(in), action.fee(), in.tpid(), in.expiry(), Data()); + } else if (in.action().has_remove_pub_address_message()) { + const auto action = in.action().remove_pub_address_message(); + // process addresses + std::vector> addresses; + for (int i = 0; i < action.public_addresses_size(); ++i) { + addresses.emplace_back(std::make_pair(action.public_addresses(i).coin_symbol(), action.public_addresses(i).address())); + } + json = TransactionBuilder::createRemovePubAddress(owner, privateKey, + action.fio_address(), addresses, + getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } else if (in.action().has_remove_all_pub_addresses_message()) { + const auto action = in.action().remove_all_pub_addresses_message(); + json = TransactionBuilder::createRemoveAllPubAddresses(owner, privateKey, + action.fio_address(), getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } else if (in.action().has_add_bundled_transactions_message()) { + const auto action = in.action().add_bundled_transactions_message(); + json = TransactionBuilder::createAddBundledTransactions(owner, privateKey, action.fio_address(), + action.bundle_sets(), getChainParams(in), action.fee(), in.tpid(), in.expiry()); } return json; } @@ -86,16 +133,109 @@ string TransactionBuilder::createRegisterFioAddress(const Address& address, cons const string& fioName, const ChainParams& chainParams, uint64_t fee, const string& walletTpId, uint32_t expiryTime) { - const auto* const apiName = "regaddress"; + Transaction transaction = TransactionBuilder::buildUnsignedRegisterFioAddress(address, fioName, chainParams, fee, walletTpId, expiryTime); + + Data serTx; + transaction.serialize(serTx); + + return signAndBuildTx(chainParams.chainId, serTx, privateKey); +} + +string TransactionBuilder::createAddPubAddress(const Address& address, const PrivateKey& privateKey, const string& fioName, + const vector>& pubAddresses, + const ChainParams& chainParams, uint64_t fee, const string& walletTpId, uint32_t expiryTime) { + + Transaction transaction = TransactionBuilder::buildUnsignedPubAddressAction(gAddPubAddress, address, fioName, pubAddresses, chainParams, fee, walletTpId, expiryTime); + + Data serTx; + transaction.serialize(serTx); + + return signAndBuildTx(chainParams.chainId, serTx, privateKey); +} + +string TransactionBuilder::createRemovePubAddress(const Address& address, const PrivateKey& privateKey, const string& fioName, + const vector>& pubAddresses, + const ChainParams& chainParams, uint64_t fee, const string& walletTpId, uint32_t expiryTime) { + + Transaction transaction = TransactionBuilder::buildUnsignedPubAddressAction(gRemoveAddress, address, fioName, pubAddresses, chainParams, fee, walletTpId, expiryTime); + + Data serTx; + transaction.serialize(serTx); + + return signAndBuildTx(chainParams.chainId, serTx, privateKey); +} + +std::string TransactionBuilder::createRemoveAllPubAddresses(const Address& address, const PrivateKey& privateKey, const std::string& fioName, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime) { + + Transaction transaction = TransactionBuilder::buildUnsignedRemoveAllAddressesAction(address, fioName, chainParams, fee, walletTpId, expiryTime); + + Data serTx; + transaction.serialize(serTx); + + return signAndBuildTx(chainParams.chainId, serTx, privateKey); +} + +std::string TransactionBuilder::createAddBundledTransactions(const Address& address, const PrivateKey& privateKey, const std::string& fioName, + uint64_t bundleSets, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime) { + + Transaction transaction = TransactionBuilder::buildUnsignedAddBundledTransactions(address, fioName, bundleSets, chainParams, fee, walletTpId, expiryTime); + + Data serTx; + transaction.serialize(serTx); + + return signAndBuildTx(chainParams.chainId, serTx, privateKey); +} + +string TransactionBuilder::createTransfer(const Address& address, const PrivateKey& privateKey, + const string& payeePublicKey, uint64_t amount, + const ChainParams& chainParams, uint64_t fee, const string& walletTpId, uint32_t expiryTime) { + + Transaction transaction = TransactionBuilder::buildUnsignedTransfer(address, payeePublicKey, amount, chainParams, fee, walletTpId, expiryTime); + + Data serTx; + transaction.serialize(serTx); + + return signAndBuildTx(chainParams.chainId, serTx, privateKey); +} + +string TransactionBuilder::createRenewFioAddress(const Address& address, const PrivateKey& privateKey, + const string& fioName, + const ChainParams& chainParams, uint64_t fee, const string& walletTpId, uint32_t expiryTime) { + + Transaction transaction = TransactionBuilder::buildUnsignedRenewFioAddress(address, fioName, chainParams, fee, walletTpId, expiryTime); + + Data serTx; + transaction.serialize(serTx); + + return signAndBuildTx(chainParams.chainId, serTx, privateKey); +} + +string TransactionBuilder::createNewFundsRequest(const Address& address, const PrivateKey& privateKey, + const string& payerFioName, const string& payerFioAddress, const string& payeeFioName, const string& payeePublicAddress, + const string& amount, const string& coinSymbol, const string& memo, const string& hash, const string& offlineUrl, + const ChainParams& chainParams, uint64_t fee, const string& walletTpId, uint32_t expiryTime, + const Data& iv) { + + // use coinSymbol for chainCode as well + NewFundsContent newFundsContent { payeePublicAddress, amount, coinSymbol, coinSymbol, memo, hash, offlineUrl }; + // serialize and encrypt + Data serContent; + newFundsContent.serialize(serContent); + + Address payerAddress(payerFioAddress); + PublicKey payerPublicKey = payerAddress.publicKey(); + // encrypt and encode + const string encodedEncryptedContent = Encryption::encode(Encryption::encrypt(privateKey, payerPublicKey, serContent, iv)); string actor = Actor::actor(address); - RegisterFioAddressData raData(fioName, address.string(), fee, walletTpId, actor); + NewFundsRequestData nfData(payerFioName, payeeFioName, encodedEncryptedContent, fee, walletTpId, actor); Data serData; - raData.serialize(serData); + nfData.serialize(serData); Action action; - action.account = ContractAddress; - action.name = apiName; + action.account = ContractPayRequest; + action.name = gNewFundsRequest; action.actionDataSer = serData; action.auth.authArray.push_back(Authorization{actor, AuthrizationActive}); @@ -106,14 +246,123 @@ string TransactionBuilder::createRegisterFioAddress(const Address& address, cons Data serTx; tx.serialize(serTx); - return signAdnBuildTx(chainParams.chainId, serTx, privateKey); + return signAndBuildTx(chainParams.chainId, serTx, privateKey); } -string TransactionBuilder::createAddPubAddress(const Address& address, const PrivateKey& privateKey, const string& fioName, - const vector>& pubAddresses, - const ChainParams& chainParams, uint64_t fee, const string& walletTpId, uint32_t expiryTime) { +string TransactionBuilder::signAndBuildTx(const Data& chainId, const Data& packedTx, const PrivateKey& privateKey) { + // create signature + Data sigBuf = buildPreSignTxData(chainId, packedTx); + string signature = Signer::signatureToBase58(Signer::signData(privateKey, sigBuf)); + + // Build json + json tx = { + {"signatures", json::array({signature})}, + {"compression", "none"}, + {"packed_context_free_data", ""}, + {"packed_trx", hex(packedTx)} + }; + return tx.dump(); +} + +Data TransactionBuilder::buildPreSignTxData(const Data& chainId, const Data& packedTx) { + // create signature + Data sigBuf(chainId); + append(sigBuf, packedTx); + append(sigBuf, TW::Data(32)); // context_free + return sigBuf; +} + +Data TransactionBuilder::buildUnsignedTxBytes(const Proto::SigningInput& in) { + Address owner(in.owner_public_key()); + + Transaction transaction; + if (in.action().has_register_fio_address_message()) { + const auto action = in.action().register_fio_address_message(); + transaction = TransactionBuilder::buildUnsignedRegisterFioAddress(owner, + in.action().register_fio_address_message().fio_address(), + getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } else if (in.action().has_add_pub_address_message()) { + const auto action = in.action().add_pub_address_message(); + // process addresses + std::vector> addresses; + for (int i = 0; i < action.public_addresses_size(); ++i) { + addresses.emplace_back(std::make_pair(action.public_addresses(i).coin_symbol(), action.public_addresses(i).address())); + } + transaction = TransactionBuilder::buildUnsignedPubAddressAction(gAddPubAddress, owner, action.fio_address(), addresses, + getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } else if (in.action().has_transfer_message()) { + const auto action = in.action().transfer_message(); + transaction = TransactionBuilder::buildUnsignedTransfer(owner, action.payee_public_key(), action.amount(), + getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } else if (in.action().has_renew_fio_address_message()) { + const auto action = in.action().renew_fio_address_message(); + transaction = TransactionBuilder::buildUnsignedRenewFioAddress(owner, action.fio_address(), + getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } else if (in.action().has_remove_pub_address_message()) { + const auto action = in.action().remove_pub_address_message(); + // process addresses + std::vector> addresses; + for (int i = 0; i < action.public_addresses_size(); ++i) { + addresses.emplace_back(std::make_pair(action.public_addresses(i).coin_symbol(), action.public_addresses(i).address())); + } + transaction = TransactionBuilder::buildUnsignedPubAddressAction(gRemoveAddress, owner, action.fio_address(), addresses, + getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } else if (in.action().has_remove_all_pub_addresses_message()) { + const auto action = in.action().remove_all_pub_addresses_message(); + transaction = TransactionBuilder::buildUnsignedRemoveAllAddressesAction(owner, action.fio_address(), + getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } else if (in.action().has_add_bundled_transactions_message()) { + const auto action = in.action().add_bundled_transactions_message(); + transaction = TransactionBuilder::buildUnsignedAddBundledTransactions(owner, action.fio_address(), action.bundle_sets(), + getChainParams(in), action.fee(), in.tpid(), in.expiry()); + } - const auto* const apiName = "addaddress"; + Data serTx; + transaction.serialize(serTx); + + return serTx; +} + +Proto::SigningOutput TransactionBuilder::buildSigningOutput(const Proto::SigningInput &input, const Data &signature) { + FIO::Proto::SigningOutput output; + Data serTx = buildUnsignedTxBytes(input); + + string signatureString = Signer::signatureToBase58(signature); + // Build json + json tx = { + {"signatures", json::array({signatureString})}, + {"compression", "none"}, + {"packed_context_free_data", ""}, + {"packed_trx", hex(serTx)} + }; + + output.set_json(tx.dump()); + output.set_action_name(actionName(input)); + return output; +} + +Transaction TransactionBuilder::buildUnsignedRegisterFioAddress(const Address& address, const std::string& fioName, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime){ + string actor = Actor::actor(address); + RegisterFioAddressData raData(fioName, address.string(), fee, walletTpId, actor); + Data serData; + raData.serialize(serData); + + Action action; + action.account = ContractAddress; + action.name = gRegisterFioAddress; + action.actionDataSer = serData; + action.auth.authArray.push_back(Authorization{actor, AuthrizationActive}); + + Transaction tx; + expirySetDefaultIfNeeded(expiryTime); + tx.set(expiryTime, chainParams); + tx.actions.push_back(action); + return tx; +} + +Transaction TransactionBuilder::buildUnsignedPubAddressAction(const std::string& apiName, const Address& address, + const std::string& fioName, const std::vector>& pubAddresses, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime) { string actor = Actor::actor(address); // convert addresses to add chainCode -- set it equal to coinSymbol @@ -121,10 +370,10 @@ string TransactionBuilder::createAddPubAddress(const Address& address, const Pri for (const auto& a: pubAddresses) { pubAddresses2.push_back(PublicAddress{a.first, a.first, a.second}); } - AddPubAddressData aaData(fioName, pubAddresses2, fee, walletTpId, actor); + PubAddressActionData actionData(fioName, pubAddresses2, fee, walletTpId, actor); Data serData; - aaData.serialize(serData); - + actionData.serialize(serData); + Action action; action.account = ContractAddress; action.name = apiName; @@ -135,18 +384,10 @@ string TransactionBuilder::createAddPubAddress(const Address& address, const Pri expirySetDefaultIfNeeded(expiryTime); tx.set(expiryTime, chainParams); tx.actions.push_back(action); - Data serTx; - tx.serialize(serTx); - - return signAdnBuildTx(chainParams.chainId, serTx, privateKey); + return tx; } -string TransactionBuilder::createTransfer(const Address& address, const PrivateKey& privateKey, - const string& payeePublicKey, uint64_t amount, - const ChainParams& chainParams, uint64_t fee, const string& walletTpId, uint32_t expiryTime) { - - const auto* const apiName = "trnsfiopubky"; - +Transaction TransactionBuilder::buildUnsignedTransfer(const Address& address, const std::string& payeePublicKey, uint64_t amount, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime) { string actor = Actor::actor(address); TransferData ttData(payeePublicKey, amount, fee, walletTpId, actor); Data serData; @@ -154,7 +395,7 @@ string TransactionBuilder::createTransfer(const Address& address, const PrivateK Action action; action.account = ContractToken; - action.name = apiName; + action.name = gTransferFIOPubkey; action.actionDataSer = serData; action.auth.authArray.push_back(Authorization{actor, AuthrizationActive}); @@ -162,18 +403,10 @@ string TransactionBuilder::createTransfer(const Address& address, const PrivateK expirySetDefaultIfNeeded(expiryTime); tx.set(expiryTime, chainParams); tx.actions.push_back(action); - Data serTx; - tx.serialize(serTx); - - return signAdnBuildTx(chainParams.chainId, serTx, privateKey); + return tx; } -string TransactionBuilder::createRenewFioAddress(const Address& address, const PrivateKey& privateKey, - const string& fioName, - const ChainParams& chainParams, uint64_t fee, const string& walletTpId, uint32_t expiryTime) { - - const auto* const apiName = "renewaddress"; - +Transaction TransactionBuilder::buildUnsignedRenewFioAddress(const Address& address, const std::string& fioName, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime) { string actor = Actor::actor(address); RenewFioAddressData raData(fioName, fee, walletTpId, actor); Data serData; @@ -181,7 +414,7 @@ string TransactionBuilder::createRenewFioAddress(const Address& address, const P Action action; action.account = ContractAddress; - action.name = apiName; + action.name = gRenewFIOAddress; action.actionDataSer = serData; action.auth.authArray.push_back(Authorization{actor, AuthrizationActive}); @@ -189,39 +422,20 @@ string TransactionBuilder::createRenewFioAddress(const Address& address, const P expirySetDefaultIfNeeded(expiryTime); tx.set(expiryTime, chainParams); tx.actions.push_back(action); - Data serTx; - tx.serialize(serTx); - - return signAdnBuildTx(chainParams.chainId, serTx, privateKey); + return tx; } -string TransactionBuilder::createNewFundsRequest(const Address& address, const PrivateKey& privateKey, - const string& payerFioName, const string& payerFioAddress, const string& payeeFioName, const string& payeePublicAddress, - const string& amount, const string& coinSymbol, const string& memo, const string& hash, const string& offlineUrl, - const ChainParams& chainParams, uint64_t fee, const string& walletTpId, uint32_t expiryTime, - const Data& iv) { - - const auto* const apiName = "newfundsreq"; - - // use coinSymbol for chainCode as well - NewFundsContent newFundsContent { payeePublicAddress, amount, coinSymbol, coinSymbol, memo, hash, offlineUrl }; - // serialize and encrypt - Data serContent; - newFundsContent.serialize(serContent); - - Address payerAddress(payerFioAddress); - PublicKey payerPublicKey = payerAddress.publicKey(); - // encrypt and encode - const string encodedEncryptedContent = Encryption::encode(Encryption::encrypt(privateKey, payerPublicKey, serContent, iv)); +Transaction TransactionBuilder::buildUnsignedRemoveAllAddressesAction(const Address& address, const std::string& fioName, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime) { string actor = Actor::actor(address); - NewFundsRequestData nfData(payerFioName, payeeFioName, encodedEncryptedContent, fee, walletTpId, actor); + RemoveAllPubAddressActionData actionData(fioName, fee, walletTpId, actor); Data serData; - nfData.serialize(serData); - + actionData.serialize(serData); + Action action; - action.account = ContractPayRequest; - action.name = apiName; + action.account = ContractAddress; + action.name = gRemoveAllPubAddresses; action.actionDataSer = serData; action.auth.authArray.push_back(Authorization{actor, AuthrizationActive}); @@ -229,27 +443,28 @@ string TransactionBuilder::createNewFundsRequest(const Address& address, const P expirySetDefaultIfNeeded(expiryTime); tx.set(expiryTime, chainParams); tx.actions.push_back(action); - Data serTx; - tx.serialize(serTx); - - return signAdnBuildTx(chainParams.chainId, serTx, privateKey); + return tx; } -string TransactionBuilder::signAdnBuildTx(const Data& chainId, const Data& packedTx, const PrivateKey& privateKey) { - // create signature - Data sigBuf(chainId); - append(sigBuf, packedTx); - append(sigBuf, TW::Data(32)); // context_free - string signature = Signer::signatureToBsase58(Signer::signData(privateKey, sigBuf)); +Transaction TransactionBuilder::buildUnsignedAddBundledTransactions(const Address& address, const std::string& fioName, + uint64_t bundleSets, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime) { - // Build json - json tx = { - {"signatures", json::array({signature})}, - {"compression", "none"}, - {"packed_context_free_data", ""}, - {"packed_trx", hex(packedTx)} - }; - return tx.dump(); + string actor = Actor::actor(address); + AddBundledTransactionsActionData actionData(fioName, bundleSets, fee, walletTpId, actor); + Data serData; + actionData.serialize(serData); + + Action action; + action.account = ContractAddress; + action.name = gAddBundledTransactions; + action.actionDataSer = serData; + action.auth.authArray.push_back(Authorization{actor, AuthrizationActive}); + + Transaction tx; + expirySetDefaultIfNeeded(expiryTime); + tx.set(expiryTime, chainParams); + tx.actions.push_back(action); + return tx; } } // namespace TW::FIO diff --git a/src/FIO/TransactionBuilder.h b/src/FIO/TransactionBuilder.h index eede5d91ca2..844a58850b1 100644 --- a/src/FIO/TransactionBuilder.h +++ b/src/FIO/TransactionBuilder.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -10,7 +8,7 @@ #include "Address.h" #include "../proto/FIO.pb.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include @@ -40,6 +38,9 @@ class TransactionBuilder { /// Generic transaction signer: Build a signed transaction, in Json, from the specific SigningInput messages. static std::string sign(Proto::SigningInput in); + /// Returns an action name according to the given signing input. + static std::string actionName(const Proto::SigningInput& input); + /// Create a signed RegisterFioAddress transaction, returned as json string (double quote delimited), suitable for register_fio_address RPC call /// @address The owners' FIO address. Ex.: "FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf" /// @privateKey The private key matching the address, needed for signing. @@ -66,6 +67,32 @@ class TransactionBuilder { const std::vector>& pubAddresses, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + /// Create a signed `remaddress` transaction, returned as json string (double quote delimited), suitable for remove_pub_address RPC call + /// @address The owners' FIO address + /// @privateKey The private key matching the address, needed for signing. + /// @fioName The FIO name already registered to the owner. Ex.: "dmitry@trust" + /// @addressess List of public addresses to be registered, ex. {{"BTC", "bc1qv...7v"},{"BNB", "bnb1ts3...9s"}} + /// @chainParams Current parameters from the FIO chain, must be obtained recently using get_info and get_block calls. + /// @fee Max fee to spend, can be obtained using get_fee API. + /// @walletTpId The FIO name of the originating wallet (project-wide constant) + /// @expiryTime Expiry for this message, can be 0, then it is taken from current time with default expiry + /// Note: fee is usually 0 for remove_pub_address. + static std::string createRemovePubAddress(const Address& address, const PrivateKey& privateKey, const std::string& fioName, + const std::vector>& pubAddresses, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + + /// Create a signed `remalladdr` transaction, returned as json string (double quote delimited), suitable for remove_all_pub_address RPC call + /// @address The owners' FIO address + /// @privateKey The private key matching the address, needed for signing. + /// @fioName The FIO name already registered to the owner. Ex.: "dmitry@trust" + /// @chainParams Current parameters from the FIO chain, must be obtained recently using get_info and get_block calls. + /// @fee Max fee to spend, can be obtained using get_fee API. + /// @walletTpId The FIO name of the originating wallet (project-wide constant) + /// @expiryTime Expiry for this message, can be 0, then it is taken from current time with default expiry + /// Note: fee is usually 0 for remove_all_pub_address. + static std::string createRemoveAllPubAddresses(const Address& address, const PrivateKey& privateKey, const std::string& fioName, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + /// Create a signed TransferTokens transaction, returned as json string (double quote delimited), suitable for transfer_tokens_pub_key RPC call /// @address The owners' FIO address /// @privateKey The private key matching the address, needed for signing. @@ -115,11 +142,60 @@ class TransactionBuilder { const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime, const Data& iv); + /// Create a signed `addbundles` transaction, returned as json string (double quote delimited), suitable for add_bundled_transactions RPC call + /// @address The owners' FIO address + /// @privateKey The private key matching the address, needed for signing. + /// @fioName The FIO name already registered to the owner. Ex.: "dmitry@trust" + /// @bundleSets Number of bundled sets. One set is 100 bundled transactions. + /// @chainParams Current parameters from the FIO chain, must be obtained recently using get_info and get_block calls. + /// @fee Max fee to spend, can be obtained using get_fee API. + /// @walletTpId The FIO name of the originating wallet (project-wide constant) + /// @expiryTime Expiry for this message, can be 0, then it is taken from current time with default expiry + static std::string createAddBundledTransactions(const Address& address, const PrivateKey& privateKey, const std::string& fioName, + uint64_t bundleSets, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + /// Used internally. Creates signatures and json with transaction. - static std::string signAdnBuildTx(const Data& chainId, const Data& packedTx, const PrivateKey& privateKey); + static std::string signAndBuildTx(const Data& chainId, const Data& packedTx, const PrivateKey& privateKey); /// Used internally. If expiry is 0, fill it based on current time. Return true if value has been changed. static bool expirySetDefaultIfNeeded(uint32_t& expiryTime); + + /// Build an unsigned transaction, from the specific SigningInput messages. + static Data buildUnsignedTxBytes(const Proto::SigningInput &in); + + /// Build signing output + static Proto::SigningOutput buildSigningOutput(const Proto::SigningInput &input, const Data &signature); + + /// Build presign TxData + static Data buildPreSignTxData(const Data& chainId, const Data& packedTx); +private: + static Transaction buildUnsignedRegisterFioAddress(const Address& address, const std::string& fioName, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + + /// Builds an unsigned transaction to perform an action over public addresses, e.g. adding or removing public addresses. + /// @apiName The action API name, ex. "addaddress", "remaddress". + /// @address The owners' FIO address. + /// @fioName The FIO name already registered to the owner. Ex.: "dmitry@trust" + /// @pubAddresses List of public addresses to be registered, ex. {{"BTC", "bc1qv...7v"},{"BNB", "bnb1ts3...9s"}} + /// @chainParams Current parameters from the FIO chain, must be obtained recently using get_info and get_block calls. + /// @fee Max fee to spend, can be obtained using get_fee API. + /// @walletTpId The FIO name of the originating wallet (project-wide constant) + /// @expiryTime Expiry for this message, can be 0, then it is taken from current time with default expiry + static Transaction buildUnsignedPubAddressAction(const std::string& apiName, const Address& address, + const std::string& fioName, const std::vector>& pubAddresses, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + + static Transaction buildUnsignedTransfer(const Address& address, const std::string& payeePublicKey, uint64_t amount, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + + static Transaction buildUnsignedRenewFioAddress(const Address& address, const std::string& fioName, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + + static Transaction buildUnsignedRemoveAllAddressesAction(const Address& address, const std::string& fioName, + const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); + + static Transaction buildUnsignedAddBundledTransactions(const Address& address, const std::string& fioName, + uint64_t bundleSets, const ChainParams& chainParams, uint64_t fee, const std::string& walletTpId, uint32_t expiryTime); }; } // namespace TW::FIO diff --git a/src/Filecoin/Address.cpp b/src/Filecoin/Address.cpp index 28ca9daf774..07dc7d7577c 100644 --- a/src/Filecoin/Address.cpp +++ b/src/Filecoin/Address.cpp @@ -1,143 +1,282 @@ -// Copyright © 2017-2020 Trust. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" -#include +#include #include "../Base32.h" -#include "../Data.h" -using namespace TW; -using namespace TW::Filecoin; +namespace TW::Filecoin { static const char BASE32_ALPHABET_FILECOIN[] = "abcdefghijklmnopqrstuvwxyz234567"; -static constexpr size_t checksumSize = 4; +static constexpr std::size_t checksumSize = 4; -bool Address::isValid(const Data& data) { - if (data.size() < 2) { +static constexpr uint64_t charMask = 0x80; + +/// Parses the given `string` as an ActorID. +/// Please note `string` must not contain any prefixes. +static bool parseActorID(const std::string& string, uint64_t& actorID) { + // `uint64_t` may contain up to 20 decimal digits. + static constexpr std::size_t maxDigits = 20; + + if (string.length() > maxDigits) { return false; } - Type type = getType(data[0]); - if (type == Type::Invalid) { + const bool onlyDigits = std::all_of( + string.begin(), + string.end(), + [](unsigned char c)->bool { return std::isdigit(c); } + ); + if (!onlyDigits) { return false; - } else if (type == Type::ID) { - // Verify varuint encoding - if (data.size() > 11) { - return false; - } - if (data.size() == 11 && data[10] > 0x01) { - return false; - } - int i; - for (i = 1; i < data.size(); i++) { - if ((data[i] & 0x80) == 0) { - break; - } - } - return i == data.size() - 1; - } else { - return data.size() == (1 + Address::payloadSize(type)); } -} -static bool isValidID(const std::string& string) { - if (string.length() > 22) - return false; - for (int i = 2; i < string.length(); i++) { - if (string[i] < '0' || string[i] > '9') { - return false; - } - } try { - size_t chars; - [[maybe_unused]] uint64_t id = std::stoull(string.substr(2), &chars); + std::size_t chars; + actorID = std::stoull(string, &chars); return chars > 0; } catch (...) { return false; } } -static bool isValidBase32(const std::string& string, Address::Type type) { - // Check if valid Base32. - uint8_t size = Address::payloadSize(type); - Data decoded; - if (!Base32::decode(string.substr(2), decoded, BASE32_ALPHABET_FILECOIN)) { - return false; +/// Decodes the given `string` as an ActorID. +/// Please note `bytes` must not contain any prefixes. +/// https://github.com/paritytech/unsigned-varint/blob/a3a5b8f2bee1f44270629e96541adf805a53d32c/src/decode.rs#L66-L86 +static bool decodeActorID(const Data& bytes, uint64_t& actorID, std::size_t& remainingPos) { + static constexpr std::size_t maxBytes = 9; + + actorID = 0; + remainingPos = 0; + for (remainingPos = 0; remainingPos < bytes.size() && remainingPos <= maxBytes; ++remainingPos) { + auto byte = bytes[remainingPos]; + auto k = byte & SCHAR_MAX; + actorID |= k << (remainingPos * 7); + + // Check if last. + if ((byte & charMask) == 0) { + if (byte == 0 && remainingPos > 0) { + // If last byte is zero, it could have been "more minimally" encoded by dropping that trailing zero. + return false; + } + ++remainingPos; + return true; + } } - // Check size - if (decoded.size() != size + checksumSize) { - return false; + // Couldn't find the last byte so `(byte & 0x80) == 0`. + return false; +} + +static void writeActorID(uint64_t actorID, Data& dest) { + static constexpr uint64_t shift = 7; + + while (actorID >= charMask) { + dest.emplace_back(actorID | charMask); + actorID >>= shift; + } + dest.emplace_back(actorID); +} + +/// Returns encoded bytes of Address including the protocol byte and actorID (if required) +/// without the checksum. +static Data addressToBytes(Address::Type type, uint64_t actorID, const Data& payload) { + Data encoded; + encoded.push_back(static_cast(type)); + if (type == Address::Type::ID || type == Address::Type::DELEGATED) { + writeActorID(actorID, encoded); + } + append(encoded, payload); + return encoded; +} + +static Data calculateChecksum(Address::Type type, uint64_t actorID, const Data& payload) { + Data bytesVec(addressToBytes(type, actorID, payload)); + Data sum = Hash::blake2b(bytesVec, checksumSize); + assert(sum.size() == checksumSize); + return sum; +} + +MaybeAddress Address::fromBytes(const Data& encoded) { + // Should contain at least one byte (address type). + if (encoded.empty()) { + return std::nullopt; } - // Extract raw address. - Data address; - address.push_back(static_cast(type)); - address.insert(address.end(), decoded.data(), decoded.data() + size); + // Get address type. + Type type = Address::getType(encoded[0]); + Data withoutPrefix(encoded.begin() + 1, encoded.end()); + + switch (type) { + case Address::Type::ID: { + std::size_t remainingPos = 0; + uint64_t actorID = 0; - // Verify checksum. - Data should_sum = Hash::blake2b(address, checksumSize); - return std::memcmp(should_sum.data(), decoded.data() + size, checksumSize) == 0; + if (!decodeActorID(withoutPrefix, actorID, remainingPos)) { + return std::nullopt; + } + // Check if there are no remaining bytes. + if (remainingPos != withoutPrefix.size()) { + return std::nullopt; + } + + return Address(type, actorID, Data()); + } + case Address::Type::SECP256K1: + case Address::Type::ACTOR: + case Address::Type::BLS: { + if (!Address::isValidPayloadSize(type, withoutPrefix.size())) { + return std::nullopt; + } + return Address(type, 0, std::move(withoutPrefix)); + } + case Address::Type::DELEGATED: { + std::size_t remainingPos = 0; + uint64_t actorID = 0; + + if (!decodeActorID(withoutPrefix, actorID, remainingPos)) { + return std::nullopt; + } + if (!Address::isValidPayloadSize(type, withoutPrefix.size() - remainingPos)) { + return std::nullopt; + } + + return Address(type, actorID, subData(withoutPrefix, remainingPos)); + } + default: + return std::nullopt; + } } -bool Address::isValid(const std::string& string) { - if (string.length() < 3) { - return false; +MaybeAddress Address::fromString(const std::string& string) { + // An address must contain at least 'f' prefix and the address type. + static constexpr std::size_t minLength = 2; + + if (string.length() < minLength) { + return std::nullopt; } // Only main net addresses supported. - if (string[0] != PREFIX) { - return false; + if (string[0] != Address::PREFIX) { + return std::nullopt; } + // Get address type. - auto type = parseType(string[1]); - if (type == Type::Invalid) { - return false; + Type type = Address::parseType(string[1]); + if (type == Address::Type::Invalid) { + return std::nullopt; } - // ID addresses are special, they are just numbers. - return type == Type::ID ? isValidID(string) : isValidBase32(string, type); + uint64_t actorID = 0; + if (type == Address::Type::ID) { + if (!parseActorID(string.substr(2), actorID)) { + return std::nullopt; + } + return Address(type, actorID, Data()); + } + + // For `SECP256K1`, `ACTOR`, `BLS`, the payload starts after the Protocol Type. + // For `DELEGATED`, the payload starts after an ActorID and the 'f' separator. + std::size_t payloadPos = 2; + if (type == Address::Type::DELEGATED) { + std::size_t actorIDEnd = string.find('f', 2); + // Delegated address must contain the 'f' separator. + if (actorIDEnd == std::string::npos || actorIDEnd <= 2) { + return std::nullopt; + } + if (!parseActorID(string.substr(2, actorIDEnd - 2), actorID)) { + return std::nullopt; + } + // Address payload starts after 'f'. + payloadPos = actorIDEnd + 1; + } + + Data decoded; + if (!Base32::decode(string.substr(payloadPos), decoded, BASE32_ALPHABET_FILECOIN)) { + return std::nullopt; + } + if (decoded.size() < checksumSize) { + return std::nullopt; + } + + Data payload(decoded.begin(), decoded.end() - checksumSize); + if (!Address::isValidPayloadSize(type, payload.size())) { + return std::nullopt; + } + + Data actualChecksum(decoded.end() - checksumSize, decoded.end()); + Data expectedChecksum = calculateChecksum(type, actorID, payload); + if (actualChecksum != expectedChecksum) { + return std::nullopt; + } + + return Address(type, actorID, std::move(payload)); +} + +bool Address::isValid(const std::string& string) { + return Address::fromString(string).has_value(); +} + +bool Address::isValid(const Data& encoded) { + return Address::fromBytes(encoded).has_value(); +} + +Address Address::secp256k1Address(const PublicKey& publicKey) { + Data payload = Hash::blake2b(publicKey.bytes, 20); + return Address(Type::SECP256K1, 0, std::move(payload)); +} + +Address Address::delegatedAddress(const PublicKey& publicKey) { + if (publicKey.type != TWPublicKeyTypeSECP256k1Extended) { + throw std::invalid_argument("Ethereum::Address needs an extended SECP256k1 public key."); + } + + const auto data = publicKey.hash({}, Hash::HasherKeccak256, true); + Data payload(data.end() - 20, data.end()); + + return Address(Type::DELEGATED, ETHEREUM_ADDRESS_MANAGER_ACTOR_ID, std::move(payload)); +} + +Address Address::delegatedAddress(uint64_t actorID, Data&& payload) { + return Address(Type::DELEGATED, actorID, std::move(payload)); } Address::Address(const std::string& string) { - if (!isValid(string)) + auto addr = Address::fromString(string); + if (!addr) { throw std::invalid_argument("Invalid address data"); - - Type type = parseType(string[1]); - // First byte is type - bytes.push_back(static_cast(type)); - if (type == Type::ID) { - uint64_t id = std::stoull(string.substr(2)); - while (id >= 0x80) { - bytes.push_back(((uint8_t)id) | 0x80); - id >>= 7; - } - bytes.push_back((uint8_t)id); - return; } - Data decoded; - if (!Base32::decode(string.substr(2), decoded, BASE32_ALPHABET_FILECOIN)) + assign(std::move(*addr)); +} + +Address::Address(const Data& encoded) { + auto addr = Address::fromBytes(encoded); + if (!addr) { throw std::invalid_argument("Invalid address data"); - uint8_t payloadSize = Address::payloadSize(type); + } - bytes.insert(bytes.end(), decoded.data(), decoded.data() + payloadSize); + assign(std::move(*addr)); } -Address::Address(const Data& data) { - if (!isValid(data)) { +Address::Address(Type type, uint64_t actorID, Data&& payload): + type(type), + actorID(actorID), + payload(std::move(payload)) { + if (!isValidPayloadSize(this->type, this->payload.size())) { throw std::invalid_argument("Invalid address data"); } - bytes = data; } -Address::Address(const PublicKey& publicKey) { - bytes.push_back(static_cast(Type::SECP256K1)); - Data hash = Hash::blake2b(publicKey.bytes, payloadSize(Type::SECP256K1)); - bytes.insert(bytes.end(), hash.begin(), hash.end()); +void Address::assign(Address&& other) { + type = other.type; + actorID = other.actorID; + payload = std::move(other.payload); +} + +Data Address::toBytes() const { + return addressToBytes(type, actorID, payload); } std::string Address::string() const { @@ -145,36 +284,26 @@ std::string Address::string() const { // Main net address prefix s.push_back(PREFIX); // Address type prefix - s.push_back(typeAscii(type())); - - if (type() == Type::ID) { - uint64_t id = 0; - unsigned shift = 0; - for (int i = 1; i < bytes.size(); i++) { - if (bytes[i] < 0x80) { - id |= bytes[i] << shift; - break; - } else { - id |= ((uint64_t)(bytes[i] & 0x7F)) << shift; - shift += 7; - } - } - s.append(std::to_string(id)); + s.push_back(typeAscii(type)); + + if (type == Type::ID) { + s.append(std::to_string(actorID)); return s; } - uint8_t payloadSize = Address::payloadSize(type()); - // Base32 encoded body - Data toEncode(payloadSize + checksumSize); - // Copy address payload without prefix - std::copy(bytes.data() + 1, bytes.data() + payloadSize + 1, toEncode.data()); + if (type == Type::DELEGATED) { + s.append(std::to_string(actorID)); + s.push_back('f'); + } + // Append Blake2b checksum - Data bytesVec; - bytesVec.assign(std::begin(bytes), std::end(bytes)); - Data sum = Hash::blake2b(bytesVec, checksumSize); - assert(sum.size() == checksumSize); - std::copy(sum.begin(), sum.end(), toEncode.data() + payloadSize); - s.append(Base32::encode(toEncode, BASE32_ALPHABET_FILECOIN)); + Data checksum = calculateChecksum(type, actorID, payload); + Data toEncode = payload; + append(toEncode, checksum); + + s.append(Base32::encode(toEncode, BASE32_ALPHABET_FILECOIN)); return s; } + +} // namespace TW::Filecoin diff --git a/src/Filecoin/Address.h b/src/Filecoin/Address.h index cb8909000e3..ea5cc9cf190 100644 --- a/src/Filecoin/Address.h +++ b/src/Filecoin/Address.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -10,49 +8,89 @@ #include #include -#include +#include #include +#include namespace TW::Filecoin { +class Address; + +using MaybeAddress = std::optional
; + class Address { public: + /// The actor ID of the Ethereum Address Manager singleton. + static constexpr uint64_t ETHEREUM_ADDRESS_MANAGER_ACTOR_ID = 10; + enum class Type : uint8_t { ID = 0, SECP256K1 = 1, ACTOR = 2, BLS = 3, + DELEGATED = 4, Invalid, }; - /// Address data with address type prefix. - Data bytes; + /// Type of the Address. + Type type{Type::Invalid}; - /// Determines whether a collection of bytes makes a valid address. - static bool isValid(const Data& data); + /// Actor ID. + /// This is used if `type` is either `ID` or `DELEGATED`. + uint64_t actorID{0}; + + /// Address data payload (without prefixes and checksum). + Data payload; + + /// Decodes `encoded` as a Filecoin address. + /// Returns `std::nullopt` on fail. + static MaybeAddress fromBytes(const Data& encoded); + + /// Parses `string` as a Filecoin address and validates the checksum. + /// Returns `std::nullopt` if `string` is not a valid address. + static MaybeAddress fromString(const std::string& string); /// Determines whether a string makes a valid encoded address. static bool isValid(const std::string& string); + /// Determines whether a collection of bytes makes a valid address. + static bool isValid(const Data& encoded); + + /// Initializes a Secp256k1 address with a secp256k1 public key. + static Address secp256k1Address(const PublicKey& publicKey); + + /// Initializes a Delegated address with a secp256k1 public key. + static Address delegatedAddress(const PublicKey& publicKey); + + /// Initializes a Delegated address with a secp256k1 public key. + static Address delegatedAddress(uint64_t actorID, Data&& payload); + /// Initializes an address with a string representation. explicit Address(const std::string& string); /// Initializes an address with a collection of bytes. - explicit Address(const Data& data); - - /// Initializes an address with a secp256k1 public key. - explicit Address(const PublicKey& publicKey); + explicit Address(const Data& encoded); /// Returns a string representation of the address. [[nodiscard]] std::string string() const; - /// Returns the type of an address. - Type type() const { return getType(bytes[0]); } + /// Returns encoded bytes of Address including the protocol byte and actorID (if required) + /// without the checksum. + Data toBytes() const; /// Address prefix static constexpr char PREFIX = 'f'; - public: +private: + static constexpr char F0_TYPE_CHAR = '0'; + static constexpr char F4_TYPE_CHAR = '4'; + + /// Initializes an address with a type, actorID and payload. + explicit Address(Type type, uint64_t actorID, Data&& payload); + + /// Assigns the address to the `other` value. + void assign(Address&& other); + /// Attempts to get the type by number. static Type getType(uint8_t raw) { switch (raw) { @@ -64,6 +102,8 @@ class Address { return Type::ACTOR; case 3: return Type::BLS; + case 4: + return Type::DELEGATED; default: return Type::Invalid; } @@ -71,34 +111,38 @@ class Address { /// Attempts to get the type by ASCII. static Type parseType(char c) { - if (c >= '0' && c <= '3') { - return static_cast(c - '0'); + if (c >= F0_TYPE_CHAR && c <= F4_TYPE_CHAR) { + return static_cast(c - F0_TYPE_CHAR); } else { return Type::Invalid; } } /// Returns ASCII character of type - static char typeAscii(Type t) { return '0' + static_cast(t); } + static char typeAscii(Type t) { return F0_TYPE_CHAR + static_cast(t); } - // Returns the payload size (excluding any prefixes) of an address type. - // If the payload size is undefined/variable (e.g. ID) - // or the type is unknown, it returns zero. - static uint8_t payloadSize(Type t) { + /// Validates if the payload size (excluding any prefixes and checksum) of an address type has an expected value. + static bool isValidPayloadSize(Type t, std::size_t payloadSize) { switch (t) { - case Type::SECP256K1: - case Type::ACTOR: - return 20; - case Type::BLS: - return 48; - default: - return 0; + case Type::ID: + return payloadSize == 0; + case Type::SECP256K1: + case Type::ACTOR: + return payloadSize == 20; + case Type::BLS: + return payloadSize == 48; + case Type::DELEGATED: + return payloadSize <= 54; + default: + return false; } } }; inline bool operator==(const Address& lhs, const Address& rhs) { - return lhs.bytes == rhs.bytes; + return lhs.type == rhs.type + && lhs.actorID == rhs.actorID + && lhs.payload == rhs.payload; } } // namespace TW::Filecoin diff --git a/src/Filecoin/AddressConverter.cpp b/src/Filecoin/AddressConverter.cpp new file mode 100644 index 00000000000..b805bb6d3f0 --- /dev/null +++ b/src/Filecoin/AddressConverter.cpp @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "AddressConverter.h" + +#include "BinaryCoding.h" +#include "Ethereum/Address.h" + +namespace TW::Filecoin { + +static constexpr std::size_t ACTOR_ID_ENCODED_LEN = sizeof(uint64_t); + +/// https://github.com/filecoin-project/lotus/blob/61f29a84b5a61060c4ac8aabe9b9360a2cdf5e7e/chain/types/ethtypes/eth_types.go#L279 +MaybeAddressString AddressConverter::convertToEthereumString(const std::string& filecoinAddress) { + // This may throw an exception if the given address is invalid. + Address addr(filecoinAddress); + if (auto &ð_opt = convertToEthereum(addr); eth_opt) { + return eth_opt->string(); + } + return std::nullopt; +} + +MaybeEthAddress AddressConverter::convertToEthereum(const Address& filecoinAddress) { + switch (filecoinAddress.type) { + case Address::Type::ID: { + Data payload(Ethereum::Address::size - ACTOR_ID_ENCODED_LEN, 0); + payload[0] = 0xFF; + + Data encodedActorID; + encodedActorID.reserve(ACTOR_ID_ENCODED_LEN); + encode64BE(filecoinAddress.actorID, encodedActorID); + + append(payload, encodedActorID); + + Ethereum::Address ethAddr(payload); + return ethAddr; + } + case Address::Type::DELEGATED: { + if (filecoinAddress.actorID != Address::ETHEREUM_ADDRESS_MANAGER_ACTOR_ID) { + return std::nullopt; + } + + if (filecoinAddress.payload.size() != Ethereum::Address::size) { + return std::nullopt; + } + + Ethereum::Address ethAddr(filecoinAddress.payload); + return ethAddr; + } + default: + return std::nullopt; + } +} + +std::string AddressConverter::convertFromEthereumString(const std::string& ethereumAddress) { + // This may throw an exception if the given address is invalid. + Ethereum::Address addr(ethereumAddress); + + return convertFromEthereum(addr).string(); +} + +Address AddressConverter::convertFromEthereum(const Ethereum::Address& ethereumAddress) noexcept { + Data addrPayload(ethereumAddress.bytes.begin(), ethereumAddress.bytes.end()); + return Address::delegatedAddress(Address::ETHEREUM_ADDRESS_MANAGER_ACTOR_ID, std::move(addrPayload)); +} + +} diff --git a/src/Filecoin/AddressConverter.h b/src/Filecoin/AddressConverter.h new file mode 100644 index 00000000000..2d64406baa4 --- /dev/null +++ b/src/Filecoin/AddressConverter.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include + +#include "Address.h" +#include "Ethereum/Address.h" + +namespace TW::Filecoin { + +using MaybeAddressString = std::optional; +using MaybeEthAddress = std::optional; + +class AddressConverter { +public: + /// Converts a Filecoin address to Ethereum. + static MaybeAddressString convertToEthereumString(const std::string& filecoinAddress); + + /// Converts a Filecoin address to Ethereum. + static MaybeEthAddress convertToEthereum(const Address& filecoinAddress); + + /// Converts an Ethereum address to Filecoin. + static std::string convertFromEthereumString(const std::string& ethereumAddress); + + /// Converts an Ethereum address to Filecoin. + static Address convertFromEthereum(const Ethereum::Address& ethereumAddress) noexcept; +}; + +} diff --git a/src/Filecoin/Entry.cpp b/src/Filecoin/Entry.cpp index 39db5385ded..7e28c1fb901 100644 --- a/src/Filecoin/Entry.cpp +++ b/src/Filecoin/Entry.cpp @@ -1,33 +1,61 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" +#include "proto/TransactionCompiler.pb.h" #include "Signer.h" -using namespace TW::Filecoin; -using namespace std; +namespace TW::Filecoin { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, - const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, - const char*) const { - return Address(publicKey).string(); +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, const PrefixVariant& addressPrefix) const { + if (std::get_if(&addressPrefix)) { + return Address::delegatedAddress(publicKey).string(); + } + return Address::secp256k1Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto preImage = Signer::signaturePreimage(input); + auto preImageHash = Hash::blake2b(preImage, 32); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + if (signatures.size() > 1 || publicKeys.size() > 1) { + output.set_error(Common::Proto::Error_no_support_n2n); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_no_support_n2n)); + return; + } + output = Signer::compile(signatures[0], publicKeys[0], input); + }); +} + +} // namespace TW::Filecoin diff --git a/src/Filecoin/Entry.h b/src/Filecoin/Entry.h index de38cf654dd..cfd0977881b 100644 --- a/src/Filecoin/Entry.h +++ b/src/Filecoin/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -13,15 +11,16 @@ namespace TW::Filecoin { /// Entry point for implementation of Filecoin coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific /// includes in this file -class Entry : public CoinEntry { - public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, - TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, - const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; +class Entry final : public CoinEntry { +public: + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Filecoin diff --git a/src/Filecoin/Signer.cpp b/src/Filecoin/Signer.cpp index 88f48a7e740..d0d72f4dd2c 100644 --- a/src/Filecoin/Signer.cpp +++ b/src/Filecoin/Signer.cpp @@ -1,42 +1,50 @@ -// Copyright © 2017-2020 Trust. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "Signer.h" -#include "HexCoding.h" #include -using namespace TW; -using namespace TW::Filecoin; +#include "AddressConverter.h" +#include "Ethereum/Entry.h" +#include "Result.h" +#include "Signer.h" +#include "proto/Ethereum.pb.h" +#include "proto/TransactionCompiler.pb.h" -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - // Load private key and transaction from Protobuf input. - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto pubkey = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended); - Address from_address(pubkey); - Address to_address(input.to()); - Transaction transaction( - /* to */ to_address, - /* from */ from_address, - /* nonce */ input.nonce(), - /* value */ load(input.value()), - /* gasLimit */ input.gas_limit(), - /* gasFeeCap */ load(input.gas_fee_cap()), - /* gasPremium */ load(input.gas_premium()) - ); +namespace TW::Filecoin { - // Sign transaction. - auto signature = sign(key, transaction); - const auto json = transaction.serialize(signature); +Proto::SigningOutput signingOutputError(Common::Proto::SigningError error) { + Proto::SigningOutput outputErr; + outputErr.set_error(error); + outputErr.set_error_message(Common::Proto::SigningError_Name(error)); + return outputErr; +} - // Return Protobuf output. +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +static constexpr uint256_t FILECOIN_EIP155_CHAIN_ID = 314; + +static Proto::SigningOutput errorOutput(const char* error) { Proto::SigningOutput output; - output.set_json(json.data(), json.size()); + output.set_error_message(error); return output; } +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { + try { + switch (input.derivation()) { + case Proto::DerivationType::SECP256K1: + return signSecp256k1(input); + case Proto::DerivationType::DELEGATED: + return signDelegated(input); + default: + return errorOutput("Unknown derivation type"); + } + } catch (const std::exception& exp) { + return errorOutput(exp.what()); + } +} + Data Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexcept { Data toSign = Hash::blake2b(transaction.cid(), 32); auto signature = privateKey.sign(toSign, TWCurveSECP256k1); @@ -50,3 +58,140 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { auto output = Signer::sign(input); return output.json(); } + +TW::Data Signer::signaturePreimage(const Proto::SigningInput& input) noexcept { + auto pubkey = PublicKey(Data(input.public_key().begin(), input.public_key().end()), TWPublicKeyTypeSECP256k1Extended); + auto tx = Signer::buildTx(pubkey, input); + return tx.cid(); +} + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey, const Proto::SigningInput& input) noexcept { + auto tx = Signer::buildTx(publicKey, input); + const auto json = tx.serialize(Transaction::SignatureType::SECP256K1, signature); + + // Return Protobuf output. + Proto::SigningOutput output; + output.set_json(json.data(), json.size()); + return output; +} + +Proto::SigningOutput Signer::signSecp256k1(const Proto::SigningInput& input) { + // Load private key and transaction from Protobuf input. + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto pubkey = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + + auto transaction = Signer::buildTx(pubkey, input); + + // Sign transaction. + auto signature = sign(key, transaction); + const auto json = transaction.serialize(Transaction::SignatureType::SECP256K1, signature); + + // Return Protobuf output. + Proto::SigningOutput output; + output.set_json(json.data(), json.size()); + return output; +} + +Transaction Signer::buildTx(const PublicKey& publicKey, const Proto::SigningInput& input) noexcept { + Address from_address = Address::secp256k1Address(publicKey); + Address to_address(input.to()); + + // Load the transaction params. + Data params(input.params().begin(), input.params().end()); + + // Simple transfer by default. + Transaction::MethodType method = Transaction::MethodType::SEND; + if (to_address.type == Address::Type::DELEGATED) { + method = Transaction::MethodType::INVOKE_EVM; + } + + return { + Transaction( + /* to */ to_address, + /* from */ from_address, + /* nonce */ input.nonce(), + /* value */ load(input.value()), + /* gasLimit */ input.gas_limit(), + /* gasFeeCap */ load(input.gas_fee_cap()), + /* gasPremium */ load(input.gas_premium()), + /* method */ method, + /* params */ params + ) + }; +} + +/// https://github.com/filecoin-project/lotus/blob/ce17546a762eef311069e13410d15465d832a45e/chain/messagesigner/messagesigner.go#L197-L211 +Proto::SigningOutput Signer::signDelegated(const Proto::SigningInput& input) { + // Load private key from Protobuf input. + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + + // Load the transaction params. + Data params(input.params().begin(), input.params().end()); + + // Generate a Delegated `f4` address. + Address fromAddress = Address::delegatedAddress(pubkey); + + // Parse `to` address. Expects either ID `f1` or Delegated `f4` address. + Address toAddress(input.to()); + auto toEth = AddressConverter::convertToEthereum(toAddress); + if (!toEth) { + throw std::invalid_argument("Expected a Delegated recipient"); + } + Data toBytes(toEth->bytes.begin(), toEth->bytes.end()); + + Ethereum::Proto::SigningInput ethInput; + + auto chainId = store(FILECOIN_EIP155_CHAIN_ID); + auto nonce = store(uint256_t(input.nonce())); + auto gasLimit = store(uint256_t(input.gas_limit())); + + ethInput.set_chain_id(chainId.data(), chainId.size()); + ethInput.set_nonce(nonce.data(), nonce.size()); + ethInput.set_tx_mode(Ethereum::Proto::Enveloped); + ethInput.set_gas_limit(gasLimit.data(), gasLimit.size()); + ethInput.set_max_inclusion_fee_per_gas(input.gas_premium()); + ethInput.set_max_fee_per_gas(input.gas_fee_cap()); + ethInput.set_to_address(toEth->string()); + + auto* transfer = ethInput.mutable_transaction()->mutable_transfer(); + transfer->set_amount(input.value()); + transfer->set_data(params.data(), params.size()); + + // Get an Ethereum EIP1559 native transfer preHash to sign. + auto ethOutputData = Ethereum::Entry().preImageHashes(TWCoinTypeEthereum, data(ethInput.SerializeAsString())); + if (ethOutputData.empty()) { + return signingOutputError(Common::Proto::SigningError::Error_internal); + } + + TxCompiler::Proto::PreSigningOutput ethOutput; + ethOutput.ParseFromArray(ethOutputData.data(), static_cast(ethOutputData.size())); + if (ethOutput.error() != Common::Proto::SigningError::OK) { + return signingOutputError(ethOutput.error()); + } + + auto preHash = data(ethOutput.data_hash()); + // Sign transaction as an Ethereum EIP1559 native transfer. + Data signature = privateKey.sign(preHash, TWCurveSECP256k1); + + // Generate a Filecoin signed message. + Transaction filecoinTransaction( + /* to */ toAddress, + /* from */ fromAddress, + /* nonce */ input.nonce(), + /* value */ load(input.value()), + /* gasLimit */ input.gas_limit(), + /* gasFeeCap */ load(input.gas_fee_cap()), + /* gasPremium */ load(input.gas_premium()), + /* method */ Transaction::MethodType::INVOKE_EVM, + /* params */ params + ); + const auto json = filecoinTransaction.serialize(Transaction::SignatureType::DELEGATED, signature); + + // Return Protobuf output. + Proto::SigningOutput output; + output.set_json(json.data(), json.size()); + return output; +} + +} // namespace TW::Filecoin diff --git a/src/Filecoin/Signer.h b/src/Filecoin/Signer.h index 523abc45d6b..db3c68ed0a1 100644 --- a/src/Filecoin/Signer.h +++ b/src/Filecoin/Signer.h @@ -1,14 +1,12 @@ -// Copyright © 2017-2020 Trust. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/Filecoin.pb.h" @@ -28,6 +26,22 @@ class Signer { /// Signs the given transaction. static Data sign(const PrivateKey& privateKey, Transaction& transaction) noexcept; + + /// Get transaction data to be signed + static TW::Data signaturePreimage(const Proto::SigningInput& input) noexcept; + + /// build transaction with signature + static Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey, const Proto::SigningInput& input) noexcept; + + private: + /// Get transaction data for secp256k1 to be signed + static Transaction buildTx(const PublicKey& publicKey, const Proto::SigningInput& input) noexcept; + + /// Signs a Proto::SigningInput transaction. + static Proto::SigningOutput signSecp256k1(const Proto::SigningInput& input); + + /// Signs a Proto::SigningInput transaction. + static Proto::SigningOutput signDelegated(const Proto::SigningInput& input); }; } // namespace TW::Filecoin diff --git a/src/Filecoin/Transaction.cpp b/src/Filecoin/Transaction.cpp index ff9a2960fcc..3ac1db7f6f1 100644 --- a/src/Filecoin/Transaction.cpp +++ b/src/Filecoin/Transaction.cpp @@ -1,19 +1,17 @@ -// Copyright © 2017-2020 Trust. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Transaction.h" #include #include "Base64.h" +namespace TW::Filecoin { + using json = nlohmann::json; -using namespace TW; -using namespace TW::Filecoin; // encodeBigInt encodes a Filecoin BigInt to CBOR. -Data TW::Filecoin::encodeBigInt(const uint256_t& value) { +Data encodeBigInt(const uint256_t& value) { if (value.is_zero()) { return {}; } @@ -39,16 +37,16 @@ Cbor::Encode Transaction::message() const { Cbor::Encode cborGasLimit = gasLimit >= 0 ? Cbor::Encode::uint((uint64_t)gasLimit) : Cbor::Encode::negInt((uint64_t)(-gasLimit - 1)); return Cbor::Encode::array({ - Cbor::Encode::uint(0), // version - Cbor::Encode::bytes(to.bytes), // to address - Cbor::Encode::bytes(from.bytes), // from address - Cbor::Encode::uint(nonce), // nonce - Cbor::Encode::bytes(encodeBigInt(value)), // value - cborGasLimit, // gas limit + Cbor::Encode::uint(0), // version + Cbor::Encode::bytes(to.toBytes()), // to address + Cbor::Encode::bytes(from.toBytes()), // from address + Cbor::Encode::uint(nonce), // nonce + Cbor::Encode::bytes(encodeBigInt(value)), // value + cborGasLimit, // gas limit Cbor::Encode::bytes(encodeBigInt(gasFeeCap)), // gas fee cap Cbor::Encode::bytes(encodeBigInt(gasPremium)), // gas premium - Cbor::Encode::uint(0), // abi.MethodNum (0 => send) - Cbor::Encode::bytes(Data()) // data (empty) + Cbor::Encode::uint(method), // abi.MethodNum + Cbor::Encode::bytes(params) // params }); } @@ -60,23 +58,33 @@ Data Transaction::cid() const { cid.insert(cid.end(), hash.begin(), hash.end()); return cid; } -std::string Transaction::serialize(Data& signature) const { + +std::string Transaction::serialize(SignatureType signatureType, const Data& signature) const { + // clang-format off + json message = { + {"To", to.string()}, + {"From", from.string()}, + {"Nonce", nonce}, + {"Value", toString(value)}, + {"GasPremium", toString(gasPremium)}, + {"GasFeeCap", toString(gasFeeCap)}, + {"GasLimit", gasLimit}, + {"Method", method}, + }; + if (!params.empty()) { + message["Params"] = Base64::encode(params); + } + json tx = { - {"Message", json{ - {"To", to.string()}, - {"From", from.string()}, - {"Nonce", nonce}, - {"Value", toString(value)}, - {"GasPremium", toString(gasPremium)}, - {"GasFeeCap", toString(gasFeeCap)}, - {"GasLimit", gasLimit}, - } - }, + {"Message", message}, {"Signature", json{ - {"Type", 1}, + {"Type", static_cast(signatureType)}, {"Data", Base64::encode(signature)}, } }, }; + // clang-format on return tx.dump(); } + +} // namespace TW::Filecoin diff --git a/src/Filecoin/Transaction.h b/src/Filecoin/Transaction.h index 6ae7dfa1e99..54615507a4c 100644 --- a/src/Filecoin/Transaction.h +++ b/src/Filecoin/Transaction.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -18,6 +16,18 @@ Data encodeBigInt(const uint256_t& value); class Transaction { public: + enum class MethodType: uint64_t { + /// Simple transfers. + SEND = 0, + /// InvokeEVM method. + INVOKE_EVM = 3844450837, + }; + + enum class SignatureType: uint8_t { + SECP256K1 = 1, + DELEGATED = 3, + }; + // Transaction version uint64_t version; // Recipient address @@ -32,13 +42,13 @@ class Transaction { int64_t gasLimit; uint256_t gasFeeCap; uint256_t gasPremium; - // Transaction type; 0 for simple transfers + // Transaction type uint64_t method; - // Transaction data; empty for simple transfers + // Transaction data Data params; Transaction(Address to, Address from, uint64_t nonce, uint256_t value, int64_t gasLimit, - uint256_t gasFeeCap, uint256_t gasPremium) + uint256_t gasFeeCap, uint256_t gasPremium, MethodType method, Data params) : version(0) , to(std::move(to)) , from(std::move(from)) @@ -47,7 +57,8 @@ class Transaction { , gasLimit(gasLimit) , gasFeeCap(std::move(gasFeeCap)) , gasPremium(std::move(gasPremium)) - , method(0) {} + , method(static_cast(method)) + , params(std::move(params)) {} public: // message returns the CBOR encoding of the Filecoin Message to be signed. @@ -57,7 +68,7 @@ class Transaction { Data cid() const; // serialize returns json ready for MpoolPush rpc - std::string serialize(Data& signature) const; + std::string serialize(SignatureType signatureType, const Data& signature) const; }; } // namespace TW::Filecoin diff --git a/src/FullSS58Address.h b/src/FullSS58Address.h new file mode 100644 index 00000000000..04644660204 --- /dev/null +++ b/src/FullSS58Address.h @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Base58.h" +#include "Data.h" +#include "PublicKey.h" + +#include +#include + +const std::string FullSS58Prefix = "SS58PRE"; + +namespace TW { + +class FullSS58Address { + public: + static const size_t expectPublicKeySize = 32; + + /// Number of bytes in an address. + static const size_t simpleFormatSize = 33; + static const size_t fullFormatSize = 34; + + /// see https://docs.substrate.io/v3/advanced/ss58/#checksum-types + static const size_t checksumSize = 2; + + /// Address data consisting of a network byte followed by the public key. + std::vector bytes; + + + /// Determines whether a string makes a valid address + static bool isValid(const std::string& string, int32_t network) { + const auto decoded = Base58::decode(string); + if (decoded.size() != (simpleFormatSize + checksumSize) && decoded.size() != (fullFormatSize + checksumSize)) { + return false; + } + + if (decoded.size() == (simpleFormatSize + checksumSize)) { + // check network + if (decoded[0] != network) { + return false; + } + } else { + int32_t ss58Decoded = ((decoded[0] & 0b00111111) << 2) | (decoded[1] >> 6) | ((decoded[1] & 0b00111111) << 8); + if (ss58Decoded != network) { + return false; + } + } + + auto checksum = computeChecksum(Data(decoded.begin(), decoded.end() - checksumSize)); + // compare checksum + if (!std::equal(decoded.end() - checksumSize, decoded.end(), checksum.begin())) { + return false; + } + return true; + } + + template + static Data computeChecksum(const T& data) { + auto prefix = Data(FullSS58Prefix.begin(), FullSS58Prefix.end()); + append(prefix, Data(data.begin(), data.end())); + auto hash = Hash::blake2b(prefix, 64); + auto checksum = Data(checksumSize); + std::copy(hash.begin(), hash.begin() + checksumSize, checksum.data()); + return checksum; + } + + FullSS58Address() = default; + + /// Initializes an address with a string representation. + FullSS58Address(const std::string& string, int32_t network) { + if (!isValid(string, network)) { + throw std::invalid_argument("Invalid address string"); + } + const auto decoded = Base58::decode(string); + bytes.resize(decoded.size() - checksumSize); + std::copy(decoded.begin(), decoded.end() - checksumSize, bytes.begin()); + } + + /// Initializes an address with a public key and network. + /// see https://docs.substrate.io/v3/advanced/ss58/#format-in-detail + FullSS58Address(const PublicKey& publicKey, int32_t network) { + if (publicKey.type != TWPublicKeyTypeED25519) { + throw std::invalid_argument("SS58Address expects an ed25519 public key."); + } + if (network < 0 || network > 16383) { + throw std::invalid_argument("network out of range"); + } + if (network < 64) { + // Simple account/address/network + bytes.resize(simpleFormatSize); + bytes[0] = byte(network); + std::copy(publicKey.bytes.begin(), publicKey.bytes.end(), bytes.begin() + 1); + } else { + // Full address/address/network identifier. + byte byte0 = (network & 0b0000000011111100) >> 2 | 0b01000000; + byte byte1 = byte(network >> 8 | (network & 0b0000000000000011) << 6); + + bytes.resize(fullFormatSize); + bytes[0] = byte0; + bytes[1] = byte1; + std::copy(publicKey.bytes.begin(), publicKey.bytes.end(), bytes.begin() + 2); + } + } + + /// Returns a string representation of the address. + std::string string() const { + auto result = Data(bytes.begin(), bytes.end()); + auto checksum = computeChecksum(bytes); + append(result, checksum); + return Base58::encode(result); + } + + /// Returns public key bytes + Data keyBytes() const { + Data bz; + if (bytes.size() == simpleFormatSize) { + bz = Data(bytes.begin() + 1, bytes.end()); + } else if (bytes.size() == fullFormatSize) { + bz = Data(bytes.begin() + 2, bytes.end()); + } else { + throw std::length_error("invalid address bytes length"); + } + + if (bz.size() != expectPublicKeySize) { + throw std::length_error("invalid public key bytes length"); + } + + return bz; + } +}; + +inline bool operator==(const FullSS58Address& lhs, const FullSS58Address& rhs) { + return lhs.bytes == rhs.bytes; +} + +} // namespace TW diff --git a/src/Generated/.clang-tidy b/src/Generated/.clang-tidy new file mode 100644 index 00000000000..2c22f7387dd --- /dev/null +++ b/src/Generated/.clang-tidy @@ -0,0 +1,6 @@ +--- +InheritParentConfig: false +Checks: '-*,misc-definitions-in-headers' +CheckOptions: + - { key: HeaderFileExtensions, value: "x" } +... diff --git a/src/Greenfield/Entry.h b/src/Greenfield/Entry.h new file mode 100644 index 00000000000..9ab2416863d --- /dev/null +++ b/src/Greenfield/Entry.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "rust/RustCoinEntry.h" + +namespace TW::Greenfield { + +/// Entry point for implementation of Greenfield coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +struct Entry final : public Rust::RustCoinEntry { +}; + +} // namespace TW::Greenfield diff --git a/src/Groestlcoin/Address.cpp b/src/Groestlcoin/Address.cpp index 85a4258842f..7871353d279 100644 --- a/src/Groestlcoin/Address.cpp +++ b/src/Groestlcoin/Address.cpp @@ -1,29 +1,24 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" #include "../Base58.h" #include -#include - -using namespace TW::Groestlcoin; +namespace TW::Groestlcoin { bool Address::isValid(const std::string& string) { - const auto decoded = Base58::bitcoin.decodeCheck(string, Hash::HasherGroestl512d); + const auto decoded = Base58::decodeCheck(string, Rust::Base58Alphabet::Bitcoin, Hash::HasherGroestl512d); if (decoded.size() != Address::size) { return false; } return true; - // return isValid(string, std::vector{36, 5}); } bool Address::isValid(const std::string& string, const std::vector& validPrefixes) { - const auto decoded = Base58::bitcoin.decodeCheck(string, Hash::HasherGroestl512d); + const auto decoded = Base58::decodeCheck(string, Rust::Base58Alphabet::Bitcoin, Hash::HasherGroestl512d); if (decoded.size() != Address::size) { return false; } @@ -34,7 +29,7 @@ bool Address::isValid(const std::string& string, const std::vector& validP } Address::Address(const std::string& string) { - const auto decoded = Base58::bitcoin.decodeCheck(string, Hash::HasherGroestl512d); + const auto decoded = Base58::decodeCheck(string, Rust::Base58Alphabet::Bitcoin, Hash::HasherGroestl512d); if (decoded.size() != Address::size) { throw std::invalid_argument("Invalid address string"); } @@ -58,5 +53,7 @@ Address::Address(const PublicKey& publicKey, uint8_t prefix) { } std::string Address::string() const { - return Base58::bitcoin.encodeCheck(bytes, Hash::HasherGroestl512d); + return Base58::encodeCheck(bytes, Rust::Base58Alphabet::Bitcoin, Hash::HasherGroestl512d); } + +} // namespace TW::Groestlcoin diff --git a/src/Groestlcoin/Address.h b/src/Groestlcoin/Address.h index 2084cea1b9f..2aad25dfe14 100644 --- a/src/Groestlcoin/Address.h +++ b/src/Groestlcoin/Address.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/Groestlcoin/Entry.cpp b/src/Groestlcoin/Entry.cpp index c5e77bf3b2f..89254b29e9a 100644 --- a/src/Groestlcoin/Entry.cpp +++ b/src/Groestlcoin/Entry.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" @@ -10,22 +8,78 @@ #include "../Bitcoin/SegwitAddress.h" #include "Signer.h" -using namespace TW::Groestlcoin; -using namespace std; +namespace TW::Groestlcoin { -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const { - return TW::Bitcoin::SegwitAddress::isValid(address, hrp) - || Address::isValid(address, {p2pkh, p2sh}); +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const { + if (auto* prefix = std::get_if(&addressPrefix); prefix) { + return Address::isValid(address, {prefix->p2pkh, prefix->p2sh}); + } + return TW::Bitcoin::SegwitAddress::isValid(address, std::get(addressPrefix)); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const { - return TW::Bitcoin::SegwitAddress(publicKey, hrp).string(); +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const { + auto hrp = getFromPrefixHrpOrDefault(addressPrefix, coin); + auto p2pkh = getFromPrefixPkhOrDefault(addressPrefix, coin); + + switch (derivation) { + case TWDerivationBitcoinLegacy: + return Address(publicKey, p2pkh).string(); + default: + return TW::Bitcoin::SegwitAddress(publicKey, hrp).string(); + } } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +TW::Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto decoded = Bitcoin::SegwitAddress::decode(address); + if (!std::get<2>(decoded)) { + // check if it is a legacy address + if (Address::isValid(address)) { + const auto addr = Address(address); + return {addr.bytes.begin() + 1, addr.bytes.end()}; + } + return {Data()}; + } + return std::get<0>(decoded).witnessProgram; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -void Entry::plan(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::plan([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { planTemplate(dataIn, dataOut); } + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + output = Signer::preImageHashes(input); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("signatures size and publickeys size not equal"); + return; + } + + HashPubkeyList externalSignatures; + auto n = signatures.size(); + for (auto i = 0ul; i < n; ++i) { + externalSignatures.push_back(std::make_pair(signatures[i], publicKeys[i].bytes)); + } + + output = Signer::sign(input, externalSignatures); + }); +} + +} // namespace TW::Groestlcoin diff --git a/src/Groestlcoin/Entry.h b/src/Groestlcoin/Entry.h index 96eb4d83a4e..b9d0ca454e3 100644 --- a/src/Groestlcoin/Entry.h +++ b/src/Groestlcoin/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,12 +10,16 @@ namespace TW::Groestlcoin { /// Groestlcoin entry dispatcher. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Groestlcoin diff --git a/src/Groestlcoin/Signer.cpp b/src/Groestlcoin/Signer.cpp index d395fadc901..772e816d8bd 100644 --- a/src/Groestlcoin/Signer.cpp +++ b/src/Groestlcoin/Signer.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "Bitcoin/TransactionBuilder.h" @@ -12,8 +10,7 @@ #include "HexCoding.h" #include "Transaction.h" -using namespace TW; -using namespace TW::Groestlcoin; +namespace TW::Groestlcoin { using TransactionBuilder = Bitcoin::TransactionBuilder; @@ -22,9 +19,9 @@ TransactionPlan Signer::plan(const SigningInput& input) noexcept { return plan.proto(); } -SigningOutput Signer::sign(const SigningInput& input) noexcept { +SigningOutput Signer::sign(const SigningInput& input, std::optional optionalExternalSigs) noexcept { SigningOutput output; - auto result = Bitcoin::TransactionSigner::sign(input); + auto result = Bitcoin::TransactionSigner::sign(input, false, optionalExternalSigs); if (!result) { output.set_error(result.error()); return output; @@ -46,3 +43,24 @@ SigningOutput Signer::sign(const SigningInput& input) noexcept { output.set_transaction_id(hex(txHash)); return output; } + +PreSigningOutput Signer::preImageHashes(const SigningInput& input) noexcept { + PreSigningOutput output; + auto result = Bitcoin::TransactionSigner::preImageHashes(input); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + return output; + } + + auto hashList = result.payload(); + auto* hashPubKeys = output.mutable_hash_public_keys(); + for (auto& h : hashList) { + auto* hpk = hashPubKeys->Add(); + hpk->set_data_hash(h.first.data(), h.first.size()); + hpk->set_public_key_hash(h.second.data(), h.second.size()); + } + return output; +} + +} // namespace TW::Groestlcoin diff --git a/src/Groestlcoin/Signer.h b/src/Groestlcoin/Signer.h index 6b6979105d4..1a90b0c7ce3 100644 --- a/src/Groestlcoin/Signer.h +++ b/src/Groestlcoin/Signer.h @@ -1,17 +1,21 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../proto/Bitcoin.pb.h" +#include "Data.h" + +#include +#include +#include namespace TW::Groestlcoin { using SigningInput = Bitcoin::Proto::SigningInput; using SigningOutput = Bitcoin::Proto::SigningOutput; using TransactionPlan = Bitcoin::Proto::TransactionPlan; +using PreSigningOutput = Bitcoin::Proto::PreSigningOutput; class Signer { public: @@ -20,8 +24,11 @@ class Signer { /// Returns a transaction plan (utxo selection, fee estimation) static TransactionPlan plan(const SigningInput& input) noexcept; - /// Signs a Proto::SigningInput transaction - static SigningOutput sign(const SigningInput& input) noexcept; + /// Signs a SigningInput transaction + static SigningOutput sign(const SigningInput& input, std::optional optionalExternalSigs = {}) noexcept; + + /// Collect pre-image hashes to be signed + static PreSigningOutput preImageHashes(const SigningInput& input) noexcept; }; } // namespace TW::Groestlcoin diff --git a/src/Groestlcoin/Transaction.h b/src/Groestlcoin/Transaction.h index 10e678c7de3..e9d34458a5e 100644 --- a/src/Groestlcoin/Transaction.h +++ b/src/Groestlcoin/Transaction.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/HDWallet.cpp b/src/HDWallet.cpp index b119517f7ac..23f01790c7f 100644 --- a/src/HDWallet.cpp +++ b/src/HDWallet.cpp @@ -1,25 +1,27 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "HDWallet.h" #include "Base58.h" #include "BinaryCoding.h" -#include "Bitcoin/SegwitAddress.h" #include "Bitcoin/CashAddress.h" +#include "Bitcoin/SegwitAddress.h" #include "Coin.h" +#include "ImmutableX/StarkKey.h" #include "Mnemonic.h" +#include "memory/memzero_wrapper.h" #include #include +#include + #include #include +#include #include -#include #include #include @@ -28,18 +30,37 @@ using namespace TW; namespace { -uint32_t fingerprint(HDNode *node, Hash::Hasher hasher); -std::string serialize(const HDNode *node, uint32_t fingerprint, uint32_t version, bool use_public, Hash::Hasher hasher); -bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher, HDNode *node); -HDNode getNode(const HDWallet& wallet, TWCurve curve, const DerivationPath& derivationPath); -HDNode getMasterNode(const HDWallet& wallet, TWCurve curve); - +uint32_t fingerprint(HDNode* node, Hash::Hasher hasher); +std::string serialize(const HDNode* node, uint32_t fingerprint, uint32_t version, bool use_public, Hash::Hasher hasher); +bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher, HDNode* node); const char* curveName(TWCurve curve); } // namespace const int MnemonicBufLength = Mnemonic::MaxWords * (BIP39_MAX_WORD_LENGTH + 3) + 20; // some extra slack -HDWallet::HDWallet(int strength, const std::string& passphrase) +template +HDWallet::HDWallet(const Data& seed) { + std::copy_n(seed.begin(), seedSize, this->seed.begin()); +} + +template +void HDWallet::updateSeedAndEntropy(bool check) { + assert(!check || Mnemonic::isValid(mnemonic)); // precondition + + // generate seed from mnemonic + mnemonic_to_seed(mnemonic.c_str(), passphrase.c_str(), seed.data(), nullptr); + + // generate entropy bits from mnemonic + Data entropyRaw((Mnemonic::MaxWords * Mnemonic::BitsPerWord) / 8); + // entropy is truncated to fully bytes, 4 bytes for each 3 words (=33 bits) + auto entropyBytes = mnemonic_to_bits(mnemonic.c_str(), entropyRaw.data()) / 33 * 4; + // copy to truncate + entropy = data(entropyRaw.data(), entropyBytes); + assert(!check || entropy.size() > 10); +} + +template +HDWallet::HDWallet(int strength, const std::string& passphrase) : passphrase(passphrase) { char buf[MnemonicBufLength]; const char* mnemonic_chars = mnemonic_generate(strength, buf, MnemonicBufLength); @@ -47,11 +68,12 @@ HDWallet::HDWallet(int strength, const std::string& passphrase) throw std::invalid_argument("Invalid strength"); } mnemonic = mnemonic_chars; - memzero(buf, MnemonicBufLength); + TW::memzero(buf, MnemonicBufLength); updateSeedAndEntropy(); } -HDWallet::HDWallet(const std::string& mnemonic, const std::string& passphrase, const bool check) +template +HDWallet::HDWallet(const std::string& mnemonic, const std::string& passphrase, const bool check) : mnemonic(mnemonic), passphrase(passphrase) { if (mnemonic.length() == 0 || (check && !Mnemonic::isValid(mnemonic))) { @@ -60,7 +82,8 @@ HDWallet::HDWallet(const std::string& mnemonic, const std::string& passphrase, c updateSeedAndEntropy(check); } -HDWallet::HDWallet(const Data& entropy, const std::string& passphrase) +template +HDWallet::HDWallet(const Data& entropy, const std::string& passphrase) : passphrase(passphrase) { char buf[MnemonicBufLength]; const char* mnemonic_chars = mnemonic_from_data(entropy.data(), static_cast(entropy.size()), buf, MnemonicBufLength); @@ -68,124 +91,171 @@ HDWallet::HDWallet(const Data& entropy, const std::string& passphrase) throw std::invalid_argument("Invalid mnemonic data"); } mnemonic = mnemonic_chars; - memzero(buf, MnemonicBufLength); + TW::memzero(buf, MnemonicBufLength); updateSeedAndEntropy(); } -HDWallet::~HDWallet() { - std::fill(seed.begin(), seed.end(), 0); - std::fill(mnemonic.begin(), mnemonic.end(), 0); - std::fill(passphrase.begin(), passphrase.end(), 0); +template +HDWallet::~HDWallet() { + memzero(seed.data(), seed.size()); + memzero(mnemonic.data(), mnemonic.size()); + memzero(passphrase.data(), passphrase.size()); } -void HDWallet::updateSeedAndEntropy(bool check) { - assert(!check || Mnemonic::isValid(mnemonic)); // precondition - - // generate seed from mnemonic - mnemonic_to_seed(mnemonic.c_str(), passphrase.c_str(), seed.data(), nullptr); +template +static HDNode getMasterNode(const HDWallet& wallet, TWCurve curve) { + const auto privateKeyType = PrivateKey::getType(curve); + HDNode node; + switch (privateKeyType) { + case TWPrivateKeyTypeCardano: { + // Derives the root Cardano HDNode from a passphrase and the entropy encoded in + // a BIP-0039 mnemonic using the Icarus derivation (V2) scheme + const auto entropy = wallet.getEntropy(); + uint8_t secret[CARDANO_SECRET_LENGTH]; + secret_from_entropy_cardano_icarus((const uint8_t*)"", 0, entropy.data(), int(entropy.size()), secret, nullptr); + hdnode_from_secret_cardano(secret, &node); + TW::memzero(secret, CARDANO_SECRET_LENGTH); + break; + } + case TWPrivateKeyTypeDefault: + default: + hdnode_from_seed(wallet.getSeed().data(), HDWallet::mSeedSize, curveName(curve), &node); + break; + } + return node; +} - // generate entropy bits from mnemonic - Data entropyRaw((Mnemonic::MaxWords * Mnemonic::BitsPerWord) / 8); - // entropy is truncated to fully bytes, 4 bytes for each 3 words (=33 bits) - auto entropyBytes = mnemonic_to_bits(mnemonic.c_str(), entropyRaw.data()) / 33 * 4; - // copy to truncate - entropy = data(entropyRaw.data(), entropyBytes); - assert(!check || entropy.size() > 10); +template +static HDNode getNode(const HDWallet& wallet, TWCurve curve, const DerivationPath& derivationPath) { + const auto privateKeyType = PrivateKey::getType(curve); + auto node = getMasterNode(wallet, curve); + for (auto& index : derivationPath.indices) { + switch (privateKeyType) { + case TWPrivateKeyTypeCardano: + hdnode_private_ckd_cardano(&node, index.derivationIndex()); + break; + case TWPrivateKeyTypeDefault: + default: + hdnode_private_ckd(&node, index.derivationIndex()); + break; + } + } + return node; } -PrivateKey HDWallet::getMasterKey(TWCurve curve) const { +template +PrivateKey HDWallet::getMasterKey(TWCurve curve) const { auto node = getMasterNode(*this, curve); - auto data = Data(node.private_key, node.private_key + PrivateKey::size); + auto data = Data(node.private_key, node.private_key + PrivateKey::_size); return PrivateKey(data); } -PrivateKey HDWallet::getMasterKeyExtension(TWCurve curve) const { +template +PrivateKey HDWallet::getMasterKeyExtension(TWCurve curve) const { auto node = getMasterNode(*this, curve); - auto data = Data(node.private_key_extension, node.private_key_extension + PrivateKey::size); + auto data = Data(node.private_key_extension, node.private_key_extension + PrivateKey::_size); return PrivateKey(data); } -PrivateKey HDWallet::getKey(TWCoinType coin, TWDerivation derivation) const { - const auto path = TW::derivationPath(coin, derivation); - return getKey(coin, path); -} - -DerivationPath HDWallet::cardanoStakingDerivationPath(const DerivationPath& path) { +template +DerivationPath HDWallet::cardanoStakingDerivationPath(const DerivationPath& path) { DerivationPath stakingPath = path; stakingPath.indices[3].value = 2; stakingPath.indices[4].value = 0; return stakingPath; } -PrivateKey HDWallet::getKey(TWCoinType coin, const DerivationPath& derivationPath) const { - const auto curve = TWCoinTypeCurve(coin); - const auto privateKeyType = getPrivateKeyType(curve); - const auto node = getNode(*this, curve, derivationPath); +template +PrivateKey HDWallet::getKeyByCurve(TWCurve curve, const DerivationPath& derivationPath) const { + const auto privateKeyType = PrivateKey::getType(curve); + auto node = getNode(*this, curve, derivationPath); switch (privateKeyType) { - case PrivateKeyTypeDoubleExtended: // special handling for Cardano - { - if (derivationPath.indices.size() < 4 || derivationPath.indices[3].value > 1) { - // invalid derivation path - return PrivateKey(Data(192)); - } - const DerivationPath stakingPath = cardanoStakingDerivationPath(derivationPath); - - auto pkData = Data(node.private_key, node.private_key + PrivateKey::size); - auto extData = Data(node.private_key_extension, node.private_key_extension + PrivateKey::size); - auto chainCode = Data(node.chain_code, node.chain_code + PrivateKey::size); - - // repeat with staking path - const auto node2 = getNode(*this, curve, stakingPath); - auto pkData2 = Data(node2.private_key, node2.private_key + PrivateKey::size); - auto extData2 = Data(node2.private_key_extension, node2.private_key_extension + PrivateKey::size); - auto chainCode2 = Data(node2.chain_code, node2.chain_code + PrivateKey::size); - - return PrivateKey(pkData, extData, chainCode, pkData2, extData2, chainCode2); - } - - case PrivateKeyTypeDefault32: - default: - // default path - auto data = Data(node.private_key, node.private_key + PrivateKey::size); - return PrivateKey(data); + case TWPrivateKeyTypeCardano: { + if (derivationPath.indices.size() < 4 || derivationPath.indices[3].value > 1) { + // invalid derivation path + return PrivateKey(Data(PrivateKey::cardanoKeySize)); + } + const DerivationPath stakingPath = cardanoStakingDerivationPath(derivationPath); + + auto pkData = Data(node.private_key, node.private_key + PrivateKey::_size); + auto extData = Data(node.private_key_extension, node.private_key_extension + PrivateKey::_size); + auto chainCode = Data(node.chain_code, node.chain_code + PrivateKey::_size); + + // repeat with staking path + const auto node2 = getNode(*this, curve, stakingPath); + auto pkData2 = Data(node2.private_key, node2.private_key + PrivateKey::_size); + auto extData2 = Data(node2.private_key_extension, node2.private_key_extension + PrivateKey::_size); + auto chainCode2 = Data(node2.chain_code, node2.chain_code + PrivateKey::_size); + + TW::memzero(&node); + return PrivateKey(pkData, extData, chainCode, pkData2, extData2, chainCode2); + } + case TWPrivateKeyTypeDefault: + default: + // default path + auto data = Data(node.private_key, node.private_key + PrivateKey::_size); + TW::memzero(&node); + if (curve == TWCurveStarkex) { + return ImmutableX::getPrivateKeyFromEthPrivKey(PrivateKey(data)); + } + return PrivateKey(data); } } -std::string HDWallet::getRootKey(TWCoinType coin, TWHDVersion version) const { +template +PrivateKey HDWallet::getKey(TWCoinType coin, const DerivationPath& derivationPath) const { const auto curve = TWCoinTypeCurve(coin); - auto node = getMasterNode(*this, curve); - return serialize(&node, 0, version, false, base58Hasher(coin)); + return getKeyByCurve(curve, derivationPath); } -std::string HDWallet::deriveAddress(TWCoinType coin) const { - return deriveAddress(coin, TWDerivationDefault); +template +PrivateKey HDWallet::getKey(TWCoinType coin, TWDerivation derivation) const { + const auto path = TW::derivationPath(coin, derivation); + return getKey(coin, path); +} + +template +std::string HDWallet::getRootKey(TWCoinType coin, TWHDVersion version) const { + const auto curve = TWCoinTypeCurve(coin); + auto node = getMasterNode(*this, curve); + return serialize(&node, 0, version, false, base58Hasher(coin)); } -std::string HDWallet::deriveAddress(TWCoinType coin, TWDerivation derivation) const { +template +std::string HDWallet::deriveAddress(TWCoinType coin, TWDerivation derivation) const { const auto derivationPath = TW::derivationPath(coin, derivation); return TW::deriveAddress(coin, getKey(coin, derivationPath), derivation); } -std::string HDWallet::getExtendedPrivateKeyAccount(TWPurpose purpose, TWCoinType coin, TWHDVersion version, uint32_t account) const { +template +std::string HDWallet::deriveAddress(TWCoinType coin) const { + return deriveAddress(coin, TWDerivationDefault); +} + +template +std::string HDWallet::getExtendedPrivateKeyAccount(TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version, uint32_t account) const { if (version == TWHDVersionNone) { return ""; } - + const auto curve = TWCoinTypeCurve(coin); - auto derivationPath = TW::DerivationPath({DerivationPathIndex(purpose, true), DerivationPathIndex(coin, true)}); + const auto path = TW::derivationPath(coin, derivation); + auto derivationPath = DerivationPath({DerivationPathIndex(purpose, true), DerivationPathIndex(path.coin(), true)}); auto node = getNode(*this, curve, derivationPath); auto fingerprintValue = fingerprint(&node, publicKeyHasher(coin)); hdnode_private_ckd(&node, account + 0x80000000); return serialize(&node, fingerprintValue, version, false, base58Hasher(coin)); } -std::string HDWallet::getExtendedPublicKeyAccount(TWPurpose purpose, TWCoinType coin, TWHDVersion version, uint32_t account) const { +template +std::string HDWallet::getExtendedPublicKeyAccount(TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version, uint32_t account) const { if (version == TWHDVersionNone) { return ""; } - + const auto curve = TWCoinTypeCurve(coin); - auto derivationPath = TW::DerivationPath({DerivationPathIndex(purpose, true), DerivationPathIndex(coin, true)}); + const auto path = TW::derivationPath(coin, derivation); + auto derivationPath = DerivationPath({DerivationPathIndex(purpose, true), DerivationPathIndex(path.coin(), true)}); auto node = getNode(*this, curve, derivationPath); auto fingerprintValue = fingerprint(&node, publicKeyHasher(coin)); hdnode_private_ckd(&node, account + 0x80000000); @@ -193,7 +263,8 @@ std::string HDWallet::getExtendedPublicKeyAccount(TWPurpose purpose, TWCoinType return serialize(&node, fingerprintValue, version, true, base58Hasher(coin)); } -std::optional HDWallet::getPublicKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path) { +template +std::optional HDWallet::getPublicKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path) { const auto curve = TW::curve(coin); const auto hasher = TW::base58Hasher(coin); @@ -209,7 +280,7 @@ std::optional HDWallet::getPublicKeyFromExtended(const std::string& e hdnode_fill_public_key(&node); // These public key type are not applicable. Handled above, as node.curve->params is null - assert(curve != TWCurveED25519 && curve != TWCurveED25519Blake2bNano && curve != TWCurveED25519Extended && curve != TWCurveCurve25519); + assert(curve != TWCurveED25519 && curve != TWCurveED25519Blake2bNano && curve != TWCurveED25519ExtendedCardano && curve != TWCurveCurve25519); TWPublicKeyType keyType = TW::publicKeyType(coin); if (curve == TWCurveSECP256k1) { auto pubkey = PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeSECP256k1); @@ -229,7 +300,8 @@ std::optional HDWallet::getPublicKeyFromExtended(const std::string& e return {}; } -std::optional HDWallet::getPrivateKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path) { +template +std::optional HDWallet::getPrivateKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path) { const auto curve = TW::curve(coin); const auto hasher = TW::base58Hasher(coin); @@ -243,26 +315,22 @@ std::optional HDWallet::getPrivateKeyFromExtended(const std::string& return PrivateKey(Data(node.private_key, node.private_key + 32)); } -HDWallet::PrivateKeyType HDWallet::getPrivateKeyType(TWCurve curve) { - switch (curve) { - case TWCurve::TWCurveED25519Extended: - // used by Cardano - return PrivateKeyTypeDoubleExtended; - default: - // default - return PrivateKeyTypeDefault32; - } +template +PrivateKey HDWallet::bip32DeriveRawSeed(TWCoinType coin, const Data& seed, const DerivationPath& path) { + const auto curve = TWCoinTypeCurve(coin); + auto wallet = HDWallet(seed); + return wallet.getKeyByCurve(curve, path); } namespace { -uint32_t fingerprint(HDNode *node, Hash::Hasher hasher) { +uint32_t fingerprint(HDNode* node, Hash::Hasher hasher) { hdnode_fill_public_key(node); auto digest = Hash::hash(hasher, node->public_key, 33); - return ((uint32_t) digest[0] << 24) + (digest[1] << 16) + (digest[2] << 8) + digest[3]; + return ((uint32_t)digest[0] << 24) + (digest[1] << 16) + (digest[2] << 8) + digest[3]; } -std::string serialize(const HDNode *node, uint32_t fingerprint, uint32_t version, bool use_public, Hash::Hasher hasher) { +std::string serialize(const HDNode* node, uint32_t fingerprint, uint32_t version, bool use_public, Hash::Hasher hasher) { Data node_data; node_data.reserve(78); @@ -278,19 +346,19 @@ std::string serialize(const HDNode *node, uint32_t fingerprint, uint32_t version node_data.insert(node_data.end(), node->private_key, node->private_key + 32); } - return Base58::bitcoin.encodeCheck(node_data, hasher); + return Base58::encodeCheck(node_data, Rust::Base58Alphabet::Bitcoin, hasher); } bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher, HDNode* node) { - memset(node, 0, sizeof(HDNode)); + TW::memzero(node); const char* curveNameStr = curveName(curve); - if (curveNameStr == nullptr || ::strlen(curveNameStr) == 0) { + if (curveNameStr == nullptr || std::string(curveNameStr).empty()) { return false; } node->curve = get_curve_by_name(curveNameStr); assert(node->curve != nullptr); - const auto node_data = Base58::bitcoin.decodeCheck(extended, hasher); + const auto node_data = Base58::decodeCheck(extended, Rust::Base58Alphabet::Bitcoin, hasher); if (node_data.size() != 78) { return false; } @@ -312,49 +380,16 @@ bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher return true; } -HDNode getNode(const HDWallet& wallet, TWCurve curve, const DerivationPath& derivationPath) { - const auto privateKeyType = HDWallet::getPrivateKeyType(curve); - auto node = getMasterNode(wallet, curve); - for (auto& index : derivationPath.indices) { - switch (privateKeyType) { - case HDWallet::PrivateKeyTypeDoubleExtended: // used by Cardano, special handling - hdnode_private_ckd_cardano(&node, index.derivationIndex()); - break; - case HDWallet::PrivateKeyTypeDefault32: - default: - hdnode_private_ckd(&node, index.derivationIndex()); - break; - } - } - return node; -} - -HDNode getMasterNode(const HDWallet& wallet, TWCurve curve) { - const auto privateKeyType = HDWallet::getPrivateKeyType(curve); - auto node = HDNode(); - switch (privateKeyType) { - case HDWallet::PrivateKeyTypeDoubleExtended: // used by Cardano - // special handling for extended, use entropy (not seed) - hdnode_from_entropy_cardano_icarus((const uint8_t*)"", 0, wallet.getEntropy().data(), (int)wallet.getEntropy().size(), &node); - break; - - case HDWallet::PrivateKeyTypeDefault32: - default: - hdnode_from_seed(wallet.getSeed().data(), HDWallet::seedSize, curveName(curve), &node); - break; - } - return node; -} - const char* curveName(TWCurve curve) { switch (curve) { + case TWCurveStarkex: case TWCurveSECP256k1: return SECP256K1_NAME; case TWCurveED25519: return ED25519_NAME; case TWCurveED25519Blake2bNano: return ED25519_BLAKE2B_NANO_NAME; - case TWCurveED25519Extended: + case TWCurveED25519ExtendedCardano: return ED25519_CARDANO_NAME; case TWCurveNIST256p1: return NIST256P1_NAME; @@ -367,3 +402,8 @@ const char* curveName(TWCurve curve) { } } // namespace + +namespace TW { +template class HDWallet<32>; +template class HDWallet<64>; +} // namespace TW diff --git a/src/HDWallet.h b/src/HDWallet.h index 2ef846bcace..7cff0fbcb2e 100644 --- a/src/HDWallet.h +++ b/src/HDWallet.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -24,9 +22,10 @@ namespace TW { +template class HDWallet { public: - static constexpr size_t seedSize = 64; + static constexpr size_t mSeedSize = seedSize; static constexpr size_t maxMnemomincSize = 240; static constexpr size_t maxExtendedKeySize = 128; @@ -43,13 +42,16 @@ class HDWallet { /// Entropy is the binary 1-to-1 representation of the mnemonic (11 bits from each word) TW::Data entropy; - public: +public: const std::array& getSeed() const { return seed; } const std::string& getMnemonic() const { return mnemonic; } const std::string& getPassphrase() const { return passphrase; } const TW::Data& getEntropy() const { return entropy; } public: + /// Initializes an HDWallet from given seed. + HDWallet(const Data& seed); + /// Initializes a new random HDWallet with the provided strength in bits. /// Throws on invalid strength. HDWallet(int strength, const std::string& passphrase); @@ -81,23 +83,40 @@ class HDWallet { /// Returns the private key at the given derivation path. PrivateKey getKey(const TWCoinType coin, const DerivationPath& derivationPath) const; + /// Returns the private key at the given derivation path and curve. + PrivateKey getKeyByCurve(TWCurve curve, const DerivationPath& derivationPath) const; + /// Derives the address for a coin (default derivation). std::string deriveAddress(TWCoinType coin) const; /// Derives the address for a coin with given derivation. std::string deriveAddress(TWCoinType coin, TWDerivation derivation) const; + /// Returns the extended private key for default 0 account with the given derivation. + std::string getExtendedPrivateKeyDerivation(TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version) const { + return getExtendedPrivateKeyAccount(purpose, coin, derivation, version, 0); + } + + /// Returns the extended public key for default 0 account with the given derivation. + std::string getExtendedPublicKeyDerivation(TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version) const { + return getExtendedPublicKeyAccount(purpose, coin, derivation, version, 0); + } + /// Returns the extended private key for default 0 account; derivation path used is "m/purpose'/coin'/0'". - std::string getExtendedPrivateKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const { return getExtendedPrivateKeyAccount(purpose, coin, version, 0); } + std::string getExtendedPrivateKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const { + return getExtendedPrivateKeyAccount(purpose, coin, TWDerivationDefault, version, 0); + } /// Returns the extended public key for default 0 account; derivation path used is "m/purpose'/coin'/0'". - std::string getExtendedPublicKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const { return getExtendedPublicKeyAccount(purpose, coin, version, 0); } + std::string getExtendedPublicKey(TWPurpose purpose, TWCoinType coin, TWHDVersion version) const { + return getExtendedPublicKeyAccount(purpose, coin, TWDerivationDefault, version, 0); + } /// Returns the extended private key for a custom account; derivation path used is "m/purpose'/coin'/account'". - std::string getExtendedPrivateKeyAccount(TWPurpose purpose, TWCoinType coin, TWHDVersion version, uint32_t account) const; + std::string getExtendedPrivateKeyAccount(TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version, uint32_t account) const; /// Returns the extended public key for a custom account; derivation path used is "m/purpose'/coin'/account'". - std::string getExtendedPublicKeyAccount(TWPurpose purpose, TWCoinType coin, TWHDVersion version, uint32_t account) const; + std::string getExtendedPublicKeyAccount(TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version, uint32_t account) const; /// Returns the BIP32 Root Key (private) std::string getRootKey(TWCoinType coin, TWHDVersion version) const; @@ -108,20 +127,17 @@ class HDWallet { /// Computes the private key from an extended private key representation. static std::optional getPrivateKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path); - public: - // Private key type (later could be moved out of HDWallet) - enum PrivateKeyType { - PrivateKeyTypeDefault32 = 0, // 32-byte private key - PrivateKeyTypeDoubleExtended = 1, // used by Cardano - }; - - // obtain privateKeyType used by the coin/curve - static PrivateKeyType getPrivateKeyType(TWCurve curve); + /// Derive the given seed for the given coin, with the given Derivation path + /// \param coin Coin to be used in order to retrieve the curve type + /// \param seed Custom seed to be used for the derivation, can be a mnemonic seed as well as an ethereum signature seed + /// \param path The derivation path to use + /// \return The computed private key + static PrivateKey bip32DeriveRawSeed(TWCoinType coin, const Data& seed, const DerivationPath& path); private: void updateSeedAndEntropy(bool check = true); - // For Cardano, derive 2nd, staking derivation path from the primary one + // For Cardano, derive 2nd staking derivation path from the primary one static DerivationPath cardanoStakingDerivationPath(const DerivationPath& path); }; @@ -129,5 +145,5 @@ class HDWallet { /// Wrapper for C interface. struct TWHDWallet { - TW::HDWallet impl; + TW::HDWallet<> impl; }; diff --git a/src/Harmony/Address.cpp b/src/Harmony/Address.cpp index 69a68fab06a..9da95d15d66 100644 --- a/src/Harmony/Address.cpp +++ b/src/Harmony/Address.cpp @@ -1,16 +1,15 @@ // Copyright © 2017 Pieter Wuille -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" #include -using namespace TW::Harmony; +namespace TW::Harmony { const std::string Address::hrp = HRP_HARMONY; +} diff --git a/src/Harmony/Address.h b/src/Harmony/Address.h index da4748f7a15..2fda40b406d 100644 --- a/src/Harmony/Address.h +++ b/src/Harmony/Address.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Harmony/Entry.cpp b/src/Harmony/Entry.cpp index 408351678ff..e9ad9fdd624 100644 --- a/src/Harmony/Entry.cpp +++ b/src/Harmony/Entry.cpp @@ -1,29 +1,28 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" - #include "Address.h" #include "Signer.h" +#include -using namespace TW::Harmony; using namespace TW; using namespace std; +namespace TW::Harmony { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { Address addr; if (!Address::decode(address, addr)) { return Data(); @@ -31,10 +30,31 @@ Data Entry::addressToData(TWCoinType coin, const std::string& address) const { return addr.getKeyHash(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data &txInputData) const { + return txCompilerTemplate( + txInputData, [=](const auto &input, auto &output) { + Signer signer(uint256_t(load(input.chain_id()))); + auto unsignedTxBytes = signer.buildUnsignedTxBytes(input); + auto imageHash = Hash::keccak256(unsignedTxBytes); + output.set_data(unsignedTxBytes.data(), unsignedTxBytes.size()); + output.set_data_hash(imageHash.data(), imageHash.size()); }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data &txInputData, const std::vector &signatures, + const std::vector &publicKeys, Data &dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto &input, auto &output, const auto& signature, [[maybe_unused]] const auto& publicKey) { + Signer signer(uint256_t(load(input.chain_id()))); + output = signer.buildSigningOutput(input, signature); }); +} + +} // namespace TW::Harmony diff --git a/src/Harmony/Entry.h b/src/Harmony/Entry.h index e39eaf82d84..bc1c3ee7062 100644 --- a/src/Harmony/Entry.h +++ b/src/Harmony/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,14 +10,17 @@ namespace TW::Harmony { /// Entry point for implementation of Harmony coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Harmony diff --git a/src/Harmony/Signer.cpp b/src/Harmony/Signer.cpp index 160d2cc926b..5870b4769ec 100644 --- a/src/Harmony/Signer.cpp +++ b/src/Harmony/Signer.cpp @@ -1,19 +1,18 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "../Ethereum/RLP.h" #include "../HexCoding.h" #include +namespace TW::Harmony { -using namespace TW; -using namespace TW::Harmony; +using INVALID_ENUM = std::integral_constant; +using RLP = TW::Ethereum::RLP; -std::tuple Signer::values(const uint256_t &chainID, +std::tuple Signer::values(const uint256_t& chainID, const Data& signature) noexcept { auto r = load(Data(signature.begin(), signature.begin() + 32)); auto s = load(Data(signature.begin() + 32, signature.begin() + 64)); @@ -23,13 +22,13 @@ std::tuple Signer::values(const uint256_t &chai } std::tuple -Signer::sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data& hash) noexcept { +Signer::sign(const uint256_t& chainID, const PrivateKey& privateKey, const Data& hash) noexcept { auto signature = privateKey.sign(hash, TWCurveSECP256k1); return values(chainID, signature); } template -Proto::SigningOutput Signer::prepareOutput(const Data& encoded, const T &transaction) noexcept { +Proto::SigningOutput Signer::prepareOutput(const Data& encoded, const T& transaction) noexcept { auto protoOutput = Proto::SigningOutput(); auto v = store(transaction.v, 1); @@ -45,28 +44,38 @@ Proto::SigningOutput Signer::prepareOutput(const Data& encoded, const T &transac } Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { - if (input.has_transaction_message()) { - return signTransaction(input); - } - if (input.has_staking_message()) { - Harmony::Proto::StakingMessage stakingMessage = input.staking_message(); - if (stakingMessage.has_create_validator_message()) { - return signCreateValidator(input); - } - if (stakingMessage.has_edit_validator_message()) { - return signEditValidator(input); - } - if (stakingMessage.has_delegate_message()) { - return signDelegate(input); - } - if (stakingMessage.has_undelegate_message()) { - return signUndelegate(input); + auto output = Proto::SigningOutput(); + try { + + if (input.has_transaction_message()) { + return signTransaction(input); } - if (stakingMessage.has_collect_rewards()) { - return signCollectRewards(input); + + if (input.has_staking_message()) { + Harmony::Proto::StakingMessage stakingMessage = input.staking_message(); + if (stakingMessage.has_create_validator_message()) { + return Signer::signStaking(input, &Signer::buildUnsignedCreateValidator); + } + if (stakingMessage.has_edit_validator_message()) { + return Signer::signStaking(input, &Signer::buildUnsignedEditValidator); + } + if (stakingMessage.has_delegate_message()) { + return Signer::signStaking(input, &Signer::buildUnsignedDelegate); + } + if (stakingMessage.has_undelegate_message()) { + return Signer::signStaking(input, &Signer::buildUnsignedUndelegate); + } + if (stakingMessage.has_collect_rewards()) { + return Signer::signStaking(input, &Signer::buildUnsignedCollectRewards); + } } + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("Invalid message"); + } catch (const std::exception &e) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message(e.what()); } - return Proto::SigningOutput(); + return output; } std::string Signer::signJSON(const std::string& json, const Data& key) { @@ -76,24 +85,10 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { return hex(Signer::sign(input).encoded()); } -Proto::SigningOutput Signer::signTransaction(const Proto::SigningInput &input) noexcept { +Proto::SigningOutput Signer::signTransaction(const Proto::SigningInput& input) { auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - Address toAddr; - if (!Address::decode(input.transaction_message().to_address(), toAddr)) { - // invalid to address - return Proto::SigningOutput(); - } - auto transaction = Transaction( - /* nonce: */ load(input.transaction_message().nonce()), - /* gasPrice: */ load(input.transaction_message().gas_price()), - /* gasLimit: */ load(input.transaction_message().gas_limit()), - /* fromShardID */ load(input.transaction_message().from_shard_id()), - /* toShardID */ load(input.transaction_message().to_shard_id()), - /* to: */ toAddr, - /* amount: */ load(input.transaction_message().amount()), - /* payload: */ - Data(input.transaction_message().payload().begin(), - input.transaction_message().payload().end())); + + auto transaction = Signer::buildUnsignedTransaction(input); auto signer = Signer(uint256_t(load(input.chain_id()))); auto hash = signer.hash(transaction); @@ -104,8 +99,56 @@ Proto::SigningOutput Signer::signTransaction(const Proto::SigningInput &input) n return prepareOutput(encoded, transaction); } -Proto::SigningOutput Signer::signCreateValidator(const Proto::SigningInput &input) noexcept { +template +uint8_t Signer::getEnum() noexcept { + return INVALID_ENUM::value; +} + +template <> +uint8_t Signer::getEnum() noexcept { + return DirectiveCreateValidator; +} + +template <> +uint8_t Signer::getEnum() noexcept { + return DirectiveEditValidator; +} + +template <> +uint8_t Signer::getEnum() noexcept { + return DirectiveDelegate; +} + +template <> +uint8_t Signer::getEnum() noexcept { + return DirectiveUndelegate; +} + +template <> +uint8_t Signer::getEnum() noexcept { + return DirectiveCollectRewards; +} + +template +Proto::SigningOutput Signer::signStaking(const Proto::SigningInput &input, function func) { auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto stakingTx = buildUnsignedStakingTransaction(input, func); + + auto signer = Signer(uint256_t(load(input.chain_id()))); + auto hash = signer.hash(stakingTx); + signer.sign(key, hash, stakingTx); + auto encoded = signer.rlpNoHash(stakingTx, true); + + return prepareOutput>(encoded, stakingTx); +} + +CreateValidator Signer::buildUnsignedCreateValidator(const Proto::SigningInput &input) { + Address validatorAddr; + if (!Address::decode(input.staking_message().create_validator_message().validator_address(), + validatorAddr)) { + throw std::invalid_argument("Invalid address"); + } + auto description = Description( /* name */ input.staking_message().create_validator_message().description().name(), /* identity */ input.staking_message().create_validator_message().description().identity(), @@ -155,13 +198,8 @@ Proto::SigningOutput Signer::signCreateValidator(const Proto::SigningInput &inpu for (auto sig : input.staking_message().create_validator_message().slot_key_sigs()) { slotKeySigs.emplace_back(Data(sig.begin(), sig.end())); } - Address validatorAddr; - if (!Address::decode(input.staking_message().create_validator_message().validator_address(), - validatorAddr)) { - // invalid to address - return Proto::SigningOutput(); - } - auto createValidator = CreateValidator( + + return CreateValidator( /* ValidatorAddress */ validatorAddr, /* Description */ description, /* Commission */ commissionRates, @@ -172,22 +210,15 @@ Proto::SigningOutput Signer::signCreateValidator(const Proto::SigningInput &inpu /* PubKey */ slotPubKeys, /* BlsSig */ slotKeySigs, /* Amount */ load(input.staking_message().create_validator_message().amount())); - - auto stakingTx = Staking( - DirectiveCreateValidator, createValidator, load(input.staking_message().nonce()), - load(input.staking_message().gas_price()), load(input.staking_message().gas_limit()), - load(input.chain_id()), 0, 0); - - auto signer = Signer(uint256_t(load(input.chain_id()))); - auto hash = signer.hash(stakingTx); - signer.sign(key, hash, stakingTx); - auto encoded = signer.rlpNoHash(stakingTx, true); - - return prepareOutput>(encoded, stakingTx); } -Proto::SigningOutput Signer::signEditValidator(const Proto::SigningInput &input) noexcept { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); +EditValidator Signer::buildUnsignedEditValidator(const Proto::SigningInput &input) { + + Address validatorAddr; + if (!Address::decode(input.staking_message().edit_validator_message().validator_address(), + validatorAddr)) { + throw std::invalid_argument("Invalid address"); + } auto description = Description( /* name */ input.staking_message().edit_validator_message().description().name(), @@ -206,13 +237,7 @@ Proto::SigningOutput Signer::signEditValidator(const Proto::SigningInput &input) load(input.staking_message().edit_validator_message().commission_rate().precision())); } - Address validatorAddr; - if (!Address::decode(input.staking_message().edit_validator_message().validator_address(), - validatorAddr)) { - // invalid to address - return Proto::SigningOutput(); - } - auto editValidator = EditValidator( + return EditValidator( /* ValidatorAddress */ validatorAddr, /* Description */ description, /* CommissionRate */ commissionRate, @@ -231,280 +256,409 @@ Proto::SigningOutput Signer::signEditValidator(const Proto::SigningInput &input) input.staking_message().edit_validator_message().slot_key_to_add_sig().end()), /* Active */ load(input.staking_message().edit_validator_message().active())); - - auto stakingTx = Staking( - DirectiveEditValidator, editValidator, load(input.staking_message().nonce()), - load(input.staking_message().gas_price()), load(input.staking_message().gas_limit()), - load(input.chain_id()), 0, 0); - - auto signer = Signer(uint256_t(load(input.chain_id()))); - auto hash = signer.hash(stakingTx); - signer.sign(key, hash, stakingTx); - auto encoded = signer.rlpNoHash(stakingTx, true); - - return prepareOutput>(encoded, stakingTx); } -Proto::SigningOutput Signer::signDelegate(const Proto::SigningInput &input) noexcept { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - +Delegate Signer::buildUnsignedDelegate(const Proto::SigningInput &input) { Address delegatorAddr; if (!Address::decode(input.staking_message().delegate_message().delegator_address(), delegatorAddr)) { - // invalid to address - return Proto::SigningOutput(); + throw std::invalid_argument("Invalid address"); } Address validatorAddr; if (!Address::decode(input.staking_message().delegate_message().validator_address(), validatorAddr)) { - // invalid to address - return Proto::SigningOutput(); + throw std::invalid_argument("Invalid address"); } - auto delegate = Delegate(delegatorAddr, validatorAddr, - load(input.staking_message().delegate_message().amount())); - auto stakingTx = - Staking(DirectiveDelegate, delegate, load(input.staking_message().nonce()), - load(input.staking_message().gas_price()), - load(input.staking_message().gas_limit()), load(input.chain_id()), 0, 0); - - auto signer = Signer(uint256_t(load(input.chain_id()))); - auto hash = signer.hash(stakingTx); - signer.sign(key, hash, stakingTx); - auto encoded = signer.rlpNoHash(stakingTx, true); - - return prepareOutput>(encoded, stakingTx); + return Delegate(delegatorAddr, validatorAddr, + load(input.staking_message().delegate_message().amount())); } -Proto::SigningOutput Signer::signUndelegate(const Proto::SigningInput &input) noexcept { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - +Undelegate Signer::buildUnsignedUndelegate(const Proto::SigningInput &input) { Address delegatorAddr; if (!Address::decode(input.staking_message().undelegate_message().delegator_address(), delegatorAddr)) { - // invalid to address - return Proto::SigningOutput(); + throw std::invalid_argument("Invalid address"); } Address validatorAddr; if (!Address::decode(input.staking_message().undelegate_message().validator_address(), validatorAddr)) { - // invalid to address - return Proto::SigningOutput(); + throw std::invalid_argument("Invalid address"); } - auto undelegate = Undelegate(delegatorAddr, validatorAddr, - load(input.staking_message().undelegate_message().amount())); - auto stakingTx = Staking( - DirectiveUndelegate, undelegate, load(input.staking_message().nonce()), - load(input.staking_message().gas_price()), load(input.staking_message().gas_limit()), - load(input.chain_id()), 0, 0); - - auto signer = Signer(uint256_t(load(input.chain_id()))); - auto hash = signer.hash(stakingTx); - signer.sign(key, hash, stakingTx); - auto encoded = signer.rlpNoHash(stakingTx, true); - - return prepareOutput>(encoded, stakingTx); + return Undelegate(delegatorAddr, validatorAddr, + load(input.staking_message().undelegate_message().amount())); } -Proto::SigningOutput Signer::signCollectRewards(const Proto::SigningInput &input) noexcept { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - +CollectRewards Signer::buildUnsignedCollectRewards(const Proto::SigningInput &input) { Address delegatorAddr; if (!Address::decode(input.staking_message().collect_rewards().delegator_address(), delegatorAddr)) { - // invalid to address - return Proto::SigningOutput(); + throw std::invalid_argument("Invalid address"); } - auto collectRewards = CollectRewards(delegatorAddr); - auto stakingTx = Staking( - DirectiveCollectRewards, collectRewards, load(input.staking_message().nonce()), - load(input.staking_message().gas_price()), load(input.staking_message().gas_limit()), - load(input.chain_id()), 0, 0); - - auto signer = Signer(uint256_t(load(input.chain_id()))); - auto hash = signer.hash(stakingTx); - signer.sign(key, hash, stakingTx); - auto encoded = signer.rlpNoHash(stakingTx, true); - - return prepareOutput>(encoded, stakingTx); + return CollectRewards(delegatorAddr); } template -void Signer::sign(const PrivateKey &privateKey, const Data& hash, T &transaction) const noexcept { +void Signer::sign(const PrivateKey& privateKey, const Data& hash, T& transaction) const noexcept { auto tuple = sign(chainID, privateKey, hash); transaction.r = std::get<0>(tuple); transaction.s = std::get<1>(tuple); transaction.v = std::get<2>(tuple); } -Data Signer::rlpNoHash(const Transaction &transaction, const bool include_vrs) const noexcept { - auto encoded = Data(); - using namespace TW::Ethereum; - append(encoded, RLP::encode(transaction.nonce)); - append(encoded, RLP::encode(transaction.gasPrice)); - append(encoded, RLP::encode(transaction.gasLimit)); - append(encoded, RLP::encode(transaction.fromShardID)); - append(encoded, RLP::encode(transaction.toShardID)); - append(encoded, RLP::encode(transaction.to.getKeyHash())); - append(encoded, RLP::encode(transaction.amount)); - append(encoded, RLP::encode(transaction.payload)); +Data Signer::rlpNoHash(const Transaction& transaction, const bool include_vrs) const noexcept { + auto nonce = store(transaction.nonce); + auto gasPrice = store(transaction.gasPrice); + auto gasLimit = store(transaction.gasLimit); + auto fromShardID = store(transaction.fromShardID); + auto toShardID = store(transaction.toShardID); + auto toKeyHash = transaction.to.getKeyHash(); + auto amount = store(transaction.amount); + + Data v; + Data r; + Data s; if (include_vrs) { - append(encoded, RLP::encode(transaction.v)); - append(encoded, RLP::encode(transaction.r)); - append(encoded, RLP::encode(transaction.s)); + v = store(transaction.v); + r = store(transaction.r); + s = store(transaction.s); } else { - append(encoded, RLP::encode(chainID)); - append(encoded, RLP::encode(0)); - append(encoded, RLP::encode(0)); + v = store(chainID); + r = store(0); + s = store(0); } - return RLP::encodeList(encoded); + + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); + + rlpList->add_items()->set_number_u256(nonce.data(), nonce.size()); + rlpList->add_items()->set_number_u256(gasPrice.data(), gasPrice.size()); + rlpList->add_items()->set_number_u256(gasLimit.data(), gasLimit.size()); + rlpList->add_items()->set_number_u256(fromShardID.data(), fromShardID.size()); + rlpList->add_items()->set_number_u256(toShardID.data(), toShardID.size()); + rlpList->add_items()->set_data(toKeyHash.data(), toKeyHash.size()); + rlpList->add_items()->set_number_u256(amount.data(), amount.size()); + rlpList->add_items()->set_data(transaction.payload.data(), transaction.payload.size()); + + rlpList->add_items()->set_number_u256(v.data(), v.size()); + rlpList->add_items()->set_number_u256(r.data(), r.size()); + rlpList->add_items()->set_number_u256(s.data(), s.size()); + + return RLP::encode(input); } template -Data Signer::rlpNoHash(const Staking &transaction, const bool include_vrs) const - noexcept { - auto encoded = Data(); - using namespace TW::Ethereum; - append(encoded, RLP::encode(transaction.directive)); - append(encoded, rlpNoHashDirective(transaction)); - - append(encoded, RLP::encode(transaction.nonce)); - append(encoded, RLP::encode(transaction.gasPrice)); - append(encoded, RLP::encode(transaction.gasLimit)); +Data Signer::rlpNoHash(const Staking& transaction, const bool include_vrs) const noexcept { + Data v; + Data r; + Data s; if (include_vrs) { - append(encoded, RLP::encode(transaction.v)); - append(encoded, RLP::encode(transaction.r)); - append(encoded, RLP::encode(transaction.s)); + v = store(transaction.v); + r = store(transaction.r); + s = store(transaction.s); } else { - append(encoded, RLP::encode(chainID)); - append(encoded, RLP::encode(0)); - append(encoded, RLP::encode(0)); + v = store(chainID); + r = store(0); + s = store(0); } - return RLP::encodeList(encoded); + + auto nonce = store(transaction.nonce); + auto gasPrice = store(transaction.gasPrice); + auto gasLimit = store(transaction.gasLimit); + + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); + + rlpList->add_items()->set_number_u64(transaction.directive); + *rlpList->add_items() = rlpNoHashDirective(transaction); + + rlpList->add_items()->set_number_u256(nonce.data(), nonce.size()); + rlpList->add_items()->set_number_u256(gasPrice.data(), gasPrice.size()); + rlpList->add_items()->set_number_u256(gasLimit.data(), gasLimit.size()); + + rlpList->add_items()->set_number_u256(v.data(), v.size()); + rlpList->add_items()->set_number_u256(r.data(), r.size()); + rlpList->add_items()->set_number_u256(s.data(), s.size()); + + return RLP::encode(input); } -Data Signer::rlpNoHashDirective(const Staking &transaction) const noexcept { - auto encoded = Data(); - using namespace TW::Ethereum; +template +EthereumRlp::Proto::RlpItem Signer::rlpPrepareDescription(const Staking& transaction) const noexcept { + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_string_item(transaction.stakeMsg.description.name); + rlpList->add_items()->set_string_item(transaction.stakeMsg.description.identity); + rlpList->add_items()->set_string_item(transaction.stakeMsg.description.website); + rlpList->add_items()->set_string_item(transaction.stakeMsg.description.securityContact); + rlpList->add_items()->set_string_item(transaction.stakeMsg.description.details); + + return item; +} + +EthereumRlp::Proto::RlpItem Signer::rlpPrepareCommissionRates(const Staking &transaction) noexcept { + auto rateValue = store(transaction.stakeMsg.commissionRates.rate.value); + auto maxRateValue = store(transaction.stakeMsg.commissionRates.maxRate.value); + auto maxChangeRateValue = store(transaction.stakeMsg.commissionRates.maxChangeRate.value); - append(encoded, RLP::encode(transaction.stakeMsg.validatorAddress.getKeyHash())); + EthereumRlp::Proto::RlpItem item; + auto* commissionList = item.mutable_list(); - auto descriptionEncoded = Data(); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.name)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.identity)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.website)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.securityContact)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.details)); - append(encoded, RLP::encodeList(descriptionEncoded)); + // Append `commission::rate` properties list with a single item. + auto* rateList = commissionList->add_items()->mutable_list(); + rateList->add_items()->set_number_u256(rateValue.data(), rateValue.size()); - auto commissionEncoded = Data(); + // Append `commission::maxRate` properties list. + auto* maxRateList = commissionList->add_items()->mutable_list(); + maxRateList->add_items()->set_number_u256(maxRateValue.data(), maxRateValue.size()); - auto rateEncoded = Data(); - append(rateEncoded, RLP::encode(transaction.stakeMsg.commissionRates.rate.value)); - append(commissionEncoded, RLP::encodeList(rateEncoded)); + // Append `commission::maxChangeRate` properties list. + auto* maxChangeRateList = commissionList->add_items()->mutable_list(); + maxChangeRateList->add_items()->set_number_u256(maxChangeRateValue.data(), maxChangeRateValue.size()); - auto maxRateEncoded = Data(); - append(maxRateEncoded, RLP::encode(transaction.stakeMsg.commissionRates.maxRate.value)); - append(commissionEncoded, RLP::encodeList(maxRateEncoded)); + return item; +} - auto maxChangeRateEncoded = Data(); - append(maxChangeRateEncoded, - RLP::encode(transaction.stakeMsg.commissionRates.maxChangeRate.value)); - append(commissionEncoded, RLP::encodeList(maxChangeRateEncoded)); +EthereumRlp::Proto::RlpItem Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { + auto validatorKeyHash = transaction.stakeMsg.validatorAddress.getKeyHash(); + auto minSelfDelegation = store(transaction.stakeMsg.minSelfDelegation); + auto maxTotalDelegation = store(transaction.stakeMsg.maxTotalDelegation); + auto amount = store(transaction.stakeMsg.amount); - append(encoded, RLP::encodeList(commissionEncoded)); + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); - append(encoded, RLP::encode(transaction.stakeMsg.minSelfDelegation)); - append(encoded, RLP::encode(transaction.stakeMsg.maxTotalDelegation)); + rlpList->add_items()->set_data(validatorKeyHash.data(), validatorKeyHash.size()); + *rlpList->add_items() = rlpPrepareDescription(transaction); - auto slotPubKeysEncoded = Data(); - for (auto pk : transaction.stakeMsg.slotPubKeys) { - append(slotPubKeysEncoded, RLP::encode(pk)); + // Append `commission` properties list of `rate`, `maxRate`, `maxChangeRate` sublists. + *rlpList->add_items() = rlpPrepareCommissionRates(transaction); + + rlpList->add_items()->set_number_u256(minSelfDelegation.data(), minSelfDelegation.size()); + rlpList->add_items()->set_number_u256(maxTotalDelegation.data(), maxTotalDelegation.size()); + + // Append a list of slot public keys. + auto* slotPubkeysList = rlpList->add_items()->mutable_list(); + for (const auto& pk : transaction.stakeMsg.slotPubKeys) { + slotPubkeysList->add_items()->set_data(pk.data(), pk.size()); } - append(encoded, RLP::encodeList(slotPubKeysEncoded)); - auto slotBlsSigsEncoded = Data(); - for (auto sig : transaction.stakeMsg.slotKeySigs) { - append(slotBlsSigsEncoded, RLP::encode(sig)); + // Append a list of slot key signatures. + auto* slotKeySigsList = rlpList->add_items()->mutable_list(); + for (const auto& sign : transaction.stakeMsg.slotKeySigs) { + slotKeySigsList->add_items()->set_data(sign.data(), sign.size()); } - append(encoded, RLP::encodeList(slotBlsSigsEncoded)); - append(encoded, RLP::encode(transaction.stakeMsg.amount)); + rlpList->add_items()->set_number_u256(amount.data(), amount.size()); - return RLP::encodeList(encoded); + return item; } -Data Signer::rlpNoHashDirective(const Staking &transaction) const noexcept { - auto encoded = Data(); - using namespace TW::Ethereum; +EthereumRlp::Proto::RlpItem Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { + auto validatorKeyHash = transaction.stakeMsg.validatorAddress.getKeyHash(); + auto minSelfDelegation = store(transaction.stakeMsg.minSelfDelegation); + auto maxTotalDelegation = store(transaction.stakeMsg.maxTotalDelegation); + auto active = store(transaction.stakeMsg.active); - append(encoded, RLP::encode(transaction.stakeMsg.validatorAddress.getKeyHash())); + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); - auto descriptionEncoded = Data(); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.name)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.identity)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.website)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.securityContact)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.details)); - append(encoded, RLP::encodeList(descriptionEncoded)); + rlpList->add_items()->set_data(validatorKeyHash.data(), validatorKeyHash.size()); + *rlpList->add_items() = rlpPrepareDescription(transaction); - auto decEncoded = Data(); + auto* commissionRateList = rlpList->add_items()->mutable_list(); if (transaction.stakeMsg.commissionRate.has_value()) { // Note: std::optional.value() is not available in XCode with target < iOS 12; using '*' - append(decEncoded, RLP::encode((*transaction.stakeMsg.commissionRate).value)); + auto commissionRateValue = store((*transaction.stakeMsg.commissionRate).value); + commissionRateList->add_items()->set_number_u256(commissionRateValue.data(), commissionRateValue.size()); } - append(encoded, RLP::encodeList(decEncoded)); - append(encoded, RLP::encode(transaction.stakeMsg.minSelfDelegation)); - append(encoded, RLP::encode(transaction.stakeMsg.maxTotalDelegation)); + rlpList->add_items()->set_number_u256(minSelfDelegation.data(), minSelfDelegation.size()); + rlpList->add_items()->set_number_u256(maxTotalDelegation.data(), maxTotalDelegation.size()); - append(encoded, RLP::encode(transaction.stakeMsg.slotKeyToRemove)); - append(encoded, RLP::encode(transaction.stakeMsg.slotKeyToAdd)); - append(encoded, RLP::encode(transaction.stakeMsg.slotKeyToAddSig)); + rlpList->add_items()->set_data(transaction.stakeMsg.slotKeyToRemove.data(), transaction.stakeMsg.slotKeyToRemove.size()); + rlpList->add_items()->set_data(transaction.stakeMsg.slotKeyToAdd.data(), transaction.stakeMsg.slotKeyToAdd.size()); + rlpList->add_items()->set_data(transaction.stakeMsg.slotKeyToAddSig.data(), transaction.stakeMsg.slotKeyToAddSig.size()); - append(encoded, RLP::encode(transaction.stakeMsg.active)); + rlpList->add_items()->set_number_u256(active.data(), active.size()); - return RLP::encodeList(encoded); + return item; } -Data Signer::rlpNoHashDirective(const Staking &transaction) const noexcept { - auto encoded = Data(); - using namespace TW::Ethereum; - append(encoded, RLP::encode(transaction.stakeMsg.delegatorAddress.getKeyHash())); - append(encoded, RLP::encode(transaction.stakeMsg.validatorAddress.getKeyHash())); - append(encoded, RLP::encode(transaction.stakeMsg.amount)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { + auto delegatorKeyHash = transaction.stakeMsg.delegatorAddress.getKeyHash(); + auto validatorKeyHash = transaction.stakeMsg.validatorAddress.getKeyHash(); + auto amount = store(transaction.stakeMsg.amount); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(delegatorKeyHash.data(), delegatorKeyHash.size()); + rlpList->add_items()->set_data(validatorKeyHash.data(), validatorKeyHash.size()); + rlpList->add_items()->set_number_u256(amount.data(), amount.size()); + + return item; } -Data Signer::rlpNoHashDirective(const Staking &transaction) const noexcept { - auto encoded = Data(); - using namespace TW::Ethereum; - append(encoded, RLP::encode(transaction.stakeMsg.delegatorAddress.getKeyHash())); - append(encoded, RLP::encode(transaction.stakeMsg.validatorAddress.getKeyHash())); - append(encoded, RLP::encode(transaction.stakeMsg.amount)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { + auto delegatorKeyHash = transaction.stakeMsg.delegatorAddress.getKeyHash(); + auto validatorKeyHash = transaction.stakeMsg.validatorAddress.getKeyHash(); + auto amount = store(transaction.stakeMsg.amount); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(delegatorKeyHash.data(), delegatorKeyHash.size()); + rlpList->add_items()->set_data(validatorKeyHash.data(), validatorKeyHash.size()); + rlpList->add_items()->set_number_u256(amount.data(), amount.size()); + + return item; } -Data Signer::rlpNoHashDirective(const Staking &transaction) const noexcept { - auto encoded = Data(); - using namespace TW::Ethereum; - append(encoded, RLP::encode(transaction.stakeMsg.delegatorAddress.getKeyHash())); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { + auto delegatorKeyHash = transaction.stakeMsg.delegatorAddress.getKeyHash(); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(delegatorKeyHash.data(), delegatorKeyHash.size()); + + return item; } -std::string Signer::txnAsRLPHex(Transaction &transaction) const noexcept { +std::string Signer::txnAsRLPHex(Transaction& transaction) const noexcept { return TW::hex(rlpNoHash(transaction, false)); } template -std::string Signer::txnAsRLPHex(Staking &transaction) const noexcept { +std::string Signer::txnAsRLPHex(Staking& transaction) const noexcept { return TW::hex(rlpNoHash(transaction, false)); } -Data Signer::hash(const Transaction &transaction) const noexcept { +Data Signer::hash(const Transaction& transaction) const noexcept { return Hash::keccak256(rlpNoHash(transaction, false)); } template -Data Signer::hash(const Staking &transaction) const noexcept { +Data Signer::hash(const Staking& transaction) const noexcept { return Hash::keccak256(rlpNoHash(transaction, false)); } + +Data Signer::buildUnsignedTxBytes(const Proto::SigningInput &input) { + if (input.has_transaction_message()) { + Transaction transaction = Signer::buildUnsignedTransaction(input); + return rlpNoHash(transaction, false); + } + + if (input.has_staking_message()) { + auto stakingMessage = input.staking_message(); + + if (stakingMessage.has_create_validator_message()) { + auto tx = Signer::buildUnsignedStakingTransaction(input, &Signer::buildUnsignedCreateValidator); + return rlpNoHash(tx, false); + } + if (stakingMessage.has_edit_validator_message()) { + auto tx = Signer::buildUnsignedStakingTransaction(input, &Signer::buildUnsignedEditValidator); + return rlpNoHash(tx, false); + } + if (stakingMessage.has_delegate_message()) { + auto tx = Signer::buildUnsignedStakingTransaction(input, &Signer::buildUnsignedDelegate); + return rlpNoHash(tx, false); + } + if (stakingMessage.has_undelegate_message()) { + auto tx = Signer::buildUnsignedStakingTransaction(input, &Signer::buildUnsignedUndelegate); + return rlpNoHash(tx, false); + } + if (stakingMessage.has_collect_rewards()) { + auto tx = Signer::buildUnsignedStakingTransaction(input, &Signer::buildUnsignedCollectRewards); + return rlpNoHash(tx, false); + } + } + + throw std::invalid_argument("Invalid message"); +} + +Proto::SigningOutput Signer::buildSigningOutput(const Proto::SigningInput &input, const Data &signature) { + if (input.has_transaction_message()) { + Transaction transaction = Signer::buildUnsignedTransaction(input); + + auto tuple = values(chainID, signature); + transaction.r = std::get<0>(tuple); + transaction.s = std::get<1>(tuple); + transaction.v = std::get<2>(tuple); + + auto encoded = rlpNoHash(transaction, true); + return prepareOutput(encoded, transaction); + } + + if (input.has_staking_message()) { + auto stakingMessage = input.staking_message(); + + if (stakingMessage.has_create_validator_message()) { + return buildStakingSigningOutput(input, signature, &Signer::buildUnsignedCreateValidator); + } + if (stakingMessage.has_edit_validator_message()) { + return buildStakingSigningOutput(input, signature, &Signer::buildUnsignedEditValidator); + + } + if (stakingMessage.has_delegate_message()) { + return buildStakingSigningOutput(input, signature, &Signer::buildUnsignedDelegate); + + } + if (stakingMessage.has_undelegate_message()) { + return buildStakingSigningOutput(input, signature, &Signer::buildUnsignedUndelegate); + } + if (stakingMessage.has_collect_rewards()) { + return buildStakingSigningOutput(input, signature, &Signer::buildUnsignedCollectRewards); + } + } + + throw std::invalid_argument("Invalid message"); +} + +Transaction Signer::buildUnsignedTransaction(const Proto::SigningInput &input) { + if (input.message_oneof_case() != Proto::SigningInput::kTransactionMessage) { + throw std::invalid_argument("Invalid message"); + } + + auto transactionMessage = input.transaction_message(); + + Transaction transaction; + + Address toAddr; + if (!Address::decode(transactionMessage.to_address(), transaction.to)) { + throw std::invalid_argument("Invalid address"); + } + + transaction.nonce = load(transactionMessage.nonce()); + transaction.gasPrice = load(transactionMessage.gas_price()); + transaction.gasLimit = load(transactionMessage.gas_limit()); + transaction.amount = load(transactionMessage.amount()); + transaction.fromShardID = load(transactionMessage.from_shard_id()); + transaction.toShardID = load(transactionMessage.to_shard_id()); + transaction.payload = Data(transactionMessage.payload().begin(), transactionMessage.payload().end()); + return transaction; +} + +template +Staking Signer::buildUnsignedStakingTransaction(const Proto::SigningInput &input, function func) { + auto tx = func(input); + return Staking( + getEnum(), tx, load(input.staking_message().nonce()), + load(input.staking_message().gas_price()), load(input.staking_message().gas_limit()), + load(input.chain_id()), 0, 0); +} + +template +Proto::SigningOutput Signer::buildStakingSigningOutput(const Proto::SigningInput &input, const Data &signature, function func) { + auto tx = Signer::buildUnsignedStakingTransaction(input, func); + auto tuple = values(chainID, signature); + + tx.r = std::get<0>(tuple); + tx.s = std::get<1>(tuple); + tx.v = std::get<2>(tuple); + auto encoded = rlpNoHash(tx, true); + return prepareOutput>(encoded, tx); +} + +} // namespace TW::Harmony diff --git a/src/Harmony/Signer.h b/src/Harmony/Signer.h index a4c80685d0f..0c8b073a196 100644 --- a/src/Harmony/Signer.h +++ b/src/Harmony/Signer.h @@ -1,19 +1,17 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Staking.h" #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/Harmony.pb.h" +#include "../proto/EthereumRlp.pb.h" -#include #include #include #include @@ -30,22 +28,26 @@ class Signer { private: static Proto::SigningOutput - signTransaction(const Proto::SigningInput &input) noexcept; + signTransaction(const Proto::SigningInput &input); - static Proto::SigningOutput - signCreateValidator(const Proto::SigningInput &input) noexcept; - - static Proto::SigningOutput - signEditValidator(const Proto::SigningInput &input) noexcept; + template + static Proto::SigningOutput signStaking(const Proto::SigningInput &input, function func); - static Proto::SigningOutput - signDelegate(const Proto::SigningInput &input) noexcept; + template + static uint8_t getEnum() noexcept; - static Proto::SigningOutput - signUndelegate(const Proto::SigningInput &input) noexcept; + template + static Staking buildUnsignedStakingTransaction(const Proto::SigningInput &input, function func); + + template + Proto::SigningOutput buildStakingSigningOutput(const Proto::SigningInput &input, const Data &signature, function func); - static Proto::SigningOutput - signCollectRewards(const Proto::SigningInput &input) noexcept; + static Transaction buildUnsignedTransaction(const Proto::SigningInput &input); + static CreateValidator buildUnsignedCreateValidator(const Proto::SigningInput &input); + static EditValidator buildUnsignedEditValidator(const Proto::SigningInput &input); + static Delegate buildUnsignedDelegate(const Proto::SigningInput &input); + static Undelegate buildUnsignedUndelegate(const Proto::SigningInput &input); + static CollectRewards buildUnsignedCollectRewards(const Proto::SigningInput &input); public: uint256_t chainID; @@ -54,29 +56,32 @@ class Signer { explicit Signer(uint256_t chainID) : chainID(std::move(chainID)) {} template - static Proto::SigningOutput prepareOutput(const Data& encoded, const T &transaction) noexcept; + static Proto::SigningOutput prepareOutput(const Data &encoded, const T &transaction) noexcept; /// Signs the given transaction. template - void sign(const PrivateKey &privateKey, const Data& hash, T &transaction) const noexcept; + void sign(const PrivateKey &privateKey, const Data &hash, T &transaction) const noexcept; /// Signs a hash with the given private key for the given chain identifier. /// - /// @returns the r, s, and v values of the transaction signature + /// \returns the r, s, and v values of the transaction signature static std::tuple - sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data& hash) noexcept; + sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data &hash) noexcept; /// R, S, and V values for the given chain identifier and signature. /// - /// @returns the r, s, and v values of the transaction signature + /// \returns the r, s, and v values of the transaction signature static std::tuple values(const uint256_t &chainID, - const Data& signature) noexcept; + const Data &signature) noexcept; std::string txnAsRLPHex(Transaction &transaction) const noexcept; template std::string txnAsRLPHex(Staking &transaction) const noexcept; + Data buildUnsignedTxBytes(const Proto::SigningInput &input); + Proto::SigningOutput buildSigningOutput(const Proto::SigningInput &input, const Data &signature); + protected: /// Computes the transaction hash. Data hash(const Transaction &transaction) const noexcept; @@ -89,11 +94,16 @@ class Signer { template Data rlpNoHash(const Staking &transaction, const bool) const noexcept; - Data rlpNoHashDirective(const Staking &transaction) const noexcept; - Data rlpNoHashDirective(const Staking &transaction) const noexcept; - Data rlpNoHashDirective(const Staking &transaction) const noexcept; - Data rlpNoHashDirective(const Staking &transaction) const noexcept; - Data rlpNoHashDirective(const Staking &transaction) const noexcept; + EthereumRlp::Proto::RlpItem rlpNoHashDirective(const Staking &transaction) const noexcept; + EthereumRlp::Proto::RlpItem rlpNoHashDirective(const Staking &transaction) const noexcept; + EthereumRlp::Proto::RlpItem rlpNoHashDirective(const Staking &transaction) const noexcept; + EthereumRlp::Proto::RlpItem rlpNoHashDirective(const Staking &transaction) const noexcept; + EthereumRlp::Proto::RlpItem rlpNoHashDirective(const Staking &transaction) const noexcept; + + template + EthereumRlp::Proto::RlpItem rlpPrepareDescription(const Staking& transaction) const noexcept; + + static EthereumRlp::Proto::RlpItem rlpPrepareCommissionRates(const Staking &transaction) noexcept; }; } // namespace TW::Harmony diff --git a/src/Harmony/Staking.cpp b/src/Harmony/Staking.cpp index a3f6291bbd8..7632dd9561b 100644 --- a/src/Harmony/Staking.cpp +++ b/src/Harmony/Staking.cpp @@ -1,9 +1,3 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Staking.h" - -using namespace TW::Harmony; +// Copyright © 2017 Trust Wallet. diff --git a/src/Harmony/Staking.h b/src/Harmony/Staking.h index bf08cb0d199..eff3ba8b37f 100644 --- a/src/Harmony/Staking.h +++ b/src/Harmony/Staking.h @@ -1,12 +1,9 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include #include #include #include diff --git a/src/Harmony/Transaction.cpp b/src/Harmony/Transaction.cpp deleted file mode 100644 index b63874ff7fb..00000000000 --- a/src/Harmony/Transaction.cpp +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Transaction.h" - -using namespace TW::Harmony; diff --git a/src/Harmony/Transaction.h b/src/Harmony/Transaction.h index ac43aa253f0..fc4a33e5158 100644 --- a/src/Harmony/Transaction.h +++ b/src/Harmony/Transaction.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -31,6 +29,8 @@ class Transaction { uint256_t r = uint256_t(); uint256_t s = uint256_t(); + Transaction() = default; + Transaction(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, uint256_t fromShardID, uint256_t toShardID, Address to, uint256_t amount, const Data& payload) : nonce(std::move(nonce)) diff --git a/src/Hash.cpp b/src/Hash.cpp index aaf4c5c1339..80a2a7d2b6a 100644 --- a/src/Hash.cpp +++ b/src/Hash.cpp @@ -1,129 +1,125 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Hash.h" -#include "BinaryCoding.h" - -#include -#include -#include -#include -#include -#include -#include +#include "rust/bindgen/WalletCoreRSBindgen.h" +#include "rust/Wrapper.h" #include using namespace TW; TW::Hash::HasherSimpleType Hash::functionPointerFromEnum(TW::Hash::Hasher hasher) { switch (hasher) { - case Hash::HasherSha1: return Hash::sha1; - default: case Hash::HasherSha256: return Hash::sha256; - case Hash::HasherSha512: return Hash::sha512; - case Hash::HasherSha512_256: return Hash::sha512_256; - case Hash::HasherKeccak256: return Hash::keccak256; - case Hash::HasherKeccak512: return Hash::keccak512; - case Hash::HasherSha3_256: return Hash::sha3_256; - case Hash::HasherSha3_512: return Hash::sha3_512; - case Hash::HasherRipemd: return Hash::ripemd; - case Hash::HasherBlake256: return Hash::blake256; - case Hash::HasherGroestl512: return Hash::groestl512; - case Hash::HasherSha256d: return Hash::sha256d; - case Hash::HasherSha256ripemd: return Hash::sha256ripemd; - case Hash::HasherSha3_256ripemd: return Hash::sha3_256ripemd; - case Hash::HasherBlake256d: return Hash::blake256d; - case Hash::HasherBlake256ripemd: return Hash::blake256ripemd; - case Hash::HasherGroestl512d: return Hash::groestl512d; + case Hash::HasherSha1: + return Hash::sha1; + case Hash::HasherSha256: + return Hash::sha256; + case Hash::HasherSha512: + return Hash::sha512; + case Hash::HasherSha512_256: + return Hash::sha512_256; + case Hash::HasherKeccak256: + return Hash::keccak256; + case Hash::HasherKeccak512: + return Hash::keccak512; + case Hash::HasherSha3_256: + return Hash::sha3_256; + case Hash::HasherSha3_512: + return Hash::sha3_512; + case Hash::HasherRipemd: + return Hash::ripemd; + case Hash::HasherBlake256: + return Hash::blake256; + case Hash::HasherGroestl512: + return Hash::groestl512; + case Hash::HasherSha256d: + return Hash::sha256d; + case Hash::HasherSha256ripemd: + return Hash::sha256ripemd; + case Hash::HasherSha3_256ripemd: + return Hash::sha3_256ripemd; + case Hash::HasherBlake2b: + return Hash::blake2b; + case Hash::HasherBlake256d: + return Hash::blake256d; + case Hash::HasherBlake256ripemd: + return Hash::blake256ripemd; + case Hash::HasherGroestl512d: + return Hash::groestl512d; } } Data Hash::sha1(const byte* data, size_t size) { - Data result(sha1Size); - sha1_Raw(data, size, result.data()); - return result; + return Rust::CByteArrayWrapper(Rust::sha1(data, size)).data; } Data Hash::sha256(const byte* data, size_t size) { - Data result(sha256Size); - sha256_Raw(data, size, result.data()); - return result; + return Rust::CByteArrayWrapper(Rust::sha256(data, size)).data; } Data Hash::sha512(const byte* data, size_t size) { - Data result(sha512Size); - sha512_Raw(data, size, result.data()); - return result; + return Rust::CByteArrayWrapper(Rust::sha512(data, size)).data; } Data Hash::sha512_256(const byte* data, size_t size) { - Data result(sha256Size); - sha512_256_Raw(data, size, result.data()); - return result; + return Rust::CByteArrayWrapper(Rust::sha512_256(data, size)).data; } Data Hash::keccak256(const byte* data, size_t size) { - Data result(sha256Size); - keccak_256(data, size, result.data()); - return result; + return Rust::CByteArrayWrapper(Rust::keccak256(data, size)).data; } Data Hash::keccak512(const byte* data, size_t size) { - Data result(sha512Size); - keccak_512(data, size, result.data()); - return result; + return Rust::CByteArrayWrapper(Rust::keccak512(data, size)).data; } Data Hash::sha3_256(const byte* data, size_t size) { - Data result(sha256Size); - ::sha3_256(data, size, result.data()); - return result; + return Rust::CByteArrayWrapper(Rust::sha3__256(data, size)).data; } Data Hash::sha3_512(const byte* data, size_t size) { - Data result(sha512Size); - ::sha3_512(data, size, result.data()); - return result; + return Rust::CByteArrayWrapper(Rust::sha3__512(data, size)).data; } Data Hash::ripemd(const byte* data, size_t size) { - Data result(ripemdSize); - ::ripemd160(data, static_cast(size), result.data()); - return result; + return Rust::CByteArrayWrapper(Rust::ripemd_160(data, size)).data; } Data Hash::blake256(const byte* data, size_t size) { - Data result(sha256Size); - ::blake256(data, size, result.data()); - return result; + return Rust::CByteArrayWrapper(Rust::blake_256(data, size)).data; +} + +Data Hash::blake2b(const byte* data, size_t dataSize) { + Rust::CByteArrayResultWrapper res = Rust::blake2_b(data, dataSize, 32); + if (res.isErr()) { + throw std::runtime_error("Error 'blake2_b' hashing"); + } + return res.unwrap().data; } Data Hash::blake2b(const byte* data, size_t dataSize, size_t hashSize) { - Data result(hashSize); - ::blake2b(data, static_cast(dataSize), result.data(), hashSize); - return result; + Rust::CByteArrayResultWrapper res = Rust::blake2_b(data, dataSize, hashSize); + if (res.isErr()) { + throw std::runtime_error("Error 'blake2_b' hashing"); + } + return res.unwrap().data; } Data Hash::blake2b(const byte* data, size_t dataSize, size_t hashSize, const Data& personal) { - Data result(hashSize); - ::blake2b_Personal(data, static_cast(dataSize), personal.data(), personal.size(), result.data(), hashSize); - return result; + Rust::CByteArrayResultWrapper res = Rust::blake2_b_personal(data, dataSize, hashSize, personal.data(), personal.size()); + if (res.isErr()) { + throw std::runtime_error("Error 'blake2_b_personal' hashing"); + } + return res.unwrap().data; } Data Hash::groestl512(const byte* data, size_t size) { - GROESTL512_CTX ctx; - Data result(sha512Size); - groestl512_Init(&ctx); - groestl512_Update(&ctx, data, size); - groestl512_Final(&ctx, result.data()); - return result; + return Rust::CByteArrayWrapper(Rust::groestl_512(data, size)).data; } Data Hash::hmac256(const Data& key, const Data& message) { - Data hmac(SHA256_DIGEST_LENGTH); - hmac_sha256(key.data(), static_cast(key.size()), message.data(), static_cast(message.size()), hmac.data()); - return hmac; + Rust::CByteArrayWrapper res = Rust::hmac__sha256(key.data(), key.size(), message.data(), message.size()); + return res.data; } diff --git a/src/Hash.h b/src/Hash.h index 7d16544976e..bcc46a756bf 100644 --- a/src/Hash.h +++ b/src/Hash.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -23,6 +21,7 @@ enum Hasher { HasherSha3_256, // version 3 SHA256 HasherSha3_512, // version 3 SHA512 HasherRipemd, // RIPEMD160 + HasherBlake2b, // Blake2b HasherBlake256, // Blake256 HasherGroestl512, // Groestl 512 HasherSha256d, // SHA256 hash of the SHA256 hash @@ -82,6 +81,9 @@ Data ripemd(const byte* data, size_t size); /// Computes the Blake256 hash. Data blake256(const byte* data, size_t size); +/// Computes the Blake2b hash with default size (32). +Data blake2b(const byte* data, size_t dataSize); + /// Computes the Blake2b hash. Data blake2b(const byte* data, size_t dataSize, size_t hashSize); diff --git a/src/Hedera/Address.cpp b/src/Hedera/Address.cpp new file mode 100644 index 00000000000..cb05e2d212f --- /dev/null +++ b/src/Hedera/Address.cpp @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Address.h" +#include "HexCoding.h" +#include "DER.h" +#include "algorithm/string.hpp" + +#include +#include + +namespace TW::Hedera::internal { + static const std::regex gEntityIDRegex{"(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-([a-z]{5}))?$"}; +} + +namespace TW::Hedera { + + +Alias::Alias(std::optional alias) noexcept : mPubKey(std::move(alias)) { + +} + +std::string Alias::string() const noexcept { + std::string pubkeyBytes = ""; + if (mPubKey.has_value()) { + pubkeyBytes = hex(mPubKey.value().bytes); + } + return gHederaDerPrefixPublic + pubkeyBytes; +} + +bool Address::isValid(const std::string& string) { + using namespace internal; + std::smatch match; + auto isValid = std::regex_match(string, match, gEntityIDRegex); + if (!isValid) { + auto parts = TW::ssplit(string, '.'); + if (parts.size() != 3) { + return false; + } + auto isNumberFunctor = [](std::string_view input) { + return input.find_first_not_of("0123456789") == std::string::npos; + }; + if (!isNumberFunctor(parts[0]) || !isNumberFunctor(parts[1])) { + return false; + } + isValid = hasDerPrefix(parts[2]); + } + return isValid; +} + +Address::Address(const std::string& string) { + if (!isValid(string)) { + throw std::invalid_argument("Invalid address string"); + } + + auto toInt = [](std::string_view s) -> std::optional { + if (std::size_t value = 0; std::from_chars(s.begin(), s.end(), value).ec == std::errc{}) { + return value; + } else { + return std::nullopt; + } + }; + + // When creating an Address by string - we assume to only sent to 0.0.1 format, alias is internal. + auto parts = TW::ssplit(string, '.'); + mShard = *toInt(parts[0]); + mRealm = *toInt(parts[1]); + mNum = *toInt(parts[2]); +} + +Address::Address(const PublicKey& publicKey) + : Address(0, 0, 0, publicKey) { +} + +std::string Address::string() const { + std::string out = std::to_string(mShard) + "." + std::to_string(mRealm) + "."; + if (mAlias.mPubKey.has_value()) { + return out + mAlias.string(); + } + return out + std::to_string(mNum); +} + +Address::Address(std::size_t shard, std::size_t realm, std::size_t num, std::optional alias) + : mShard(shard), mRealm(realm), mNum(num), mAlias(std::move(alias)) { +} + +} // namespace TW::Hedera diff --git a/src/Hedera/Address.h b/src/Hedera/Address.h new file mode 100644 index 00000000000..076cbeb47f3 --- /dev/null +++ b/src/Hedera/Address.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "PublicKey.h" + +#include +#include + +namespace TW::Hedera { + +struct Alias { + explicit Alias(std::optional alias = std::nullopt) noexcept; + std::string string() const noexcept; + std::optional mPubKey{std::nullopt}; +}; + +class Address { +public: + /// Determines whether a string makes a valid address. + static bool isValid(const std::string& string); + + /// Initializes a Hedera address with a string representation. + explicit Address(const std::string& string); + + /// Initializes a Hedera address with a public key. + explicit Address(const PublicKey& publicKey); + + /// Initializes a Hedera address with a shard, realm, num and optional alias + explicit Address(std::size_t shard, std::size_t realm, std::size_t num, std::optional alias = std::nullopt); + + /// Returns a string representation of the address. + std::string string() const; + + std::size_t shard() const { return mShard; } + std::size_t realm() const { return mRealm; } + std::size_t num() const { return mNum; } + const Alias& alias() const {return mAlias;} + +private: + std::size_t mShard{0}; + std::size_t mRealm{0}; + std::size_t mNum; + Alias mAlias; +}; + +} // namespace TW::Hedera diff --git a/src/Hedera/DER.cpp b/src/Hedera/DER.cpp new file mode 100644 index 00000000000..6f72051fd01 --- /dev/null +++ b/src/Hedera/DER.cpp @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "DER.h" +#include "PublicKey.h" +#include "HexCoding.h" + +namespace TW::Hedera { + +bool hasDerPrefix(const std::string& input) noexcept { + if (std::size_t pos = input.find(gHederaDerPrefixPublic); pos != std::string::npos) { + return PublicKey::isValid(parse_hex(input.substr(pos + std::string(gHederaDerPrefixPublic).size())), TWPublicKeyTypeED25519); + } + return false; +} + +} // namespace TW::Hedera diff --git a/src/Hedera/DER.h b/src/Hedera/DER.h new file mode 100644 index 00000000000..8918fceae4a --- /dev/null +++ b/src/Hedera/DER.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include + +namespace TW::Hedera { + // Hedera DER prefix: https://github.com/hashgraph/hedera-sdk-js/blob/e0cd39c84ab189d59a6bcedcf16e4102d7bb8beb/packages/cryptography/src/Ed25519PrivateKey.js#L8 + inline constexpr const char* const gHederaDerPrefixPrivate = "302e020100300506032b657004220420"; + // Hedera DER prefix: https://github.com/hashgraph/hedera-sdk-js/blob/e0cd39c84ab189d59a6bcedcf16e4102d7bb8beb/packages/cryptography/src/Ed25519PublicKey.js#L7 + inline constexpr const char* const gHederaDerPrefixPublic = "302a300506032b6570032100"; + + bool hasDerPrefix(const std::string& input) noexcept; +} diff --git a/src/Hedera/Entry.cpp b/src/Hedera/Entry.cpp new file mode 100644 index 00000000000..b939e0d170d --- /dev/null +++ b/src/Hedera/Entry.cpp @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Entry.h" + +#include "Address.h" +#include "Signer.h" + +namespace TW::Hedera { + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Address::isValid(address); +} + +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Address(publicKey).string(); +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +} // namespace TW::Hedera diff --git a/src/Hedera/Entry.h b/src/Hedera/Entry.h new file mode 100644 index 00000000000..e9632c3a308 --- /dev/null +++ b/src/Hedera/Entry.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../CoinEntry.h" + +namespace TW::Hedera { + +/// Entry point for implementation of Hedera coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final : public CoinEntry { +public: + virtual bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; +}; + +} // namespace TW::Hedera diff --git a/src/Cosmos/Protobuf/.gitignore b/src/Hedera/Protobuf/.gitignore similarity index 100% rename from src/Cosmos/Protobuf/.gitignore rename to src/Hedera/Protobuf/.gitignore diff --git a/src/Hedera/Protobuf/basic_types.proto b/src/Hedera/Protobuf/basic_types.proto new file mode 100644 index 00000000000..40bb309ce34 --- /dev/null +++ b/src/Hedera/Protobuf/basic_types.proto @@ -0,0 +1,1596 @@ +// Taken from https://github.com/hashgraph/hedera-protobufs/tree/main/services + +syntax = "proto3"; + +package proto; + +/*- + * ‌ + * Hedera Network Services Protobuf + * ​ + * Copyright (C) 2018 - 2022 Hedera Hashgraph, LLC + * ​ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ‍ + */ + +import "timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +option java_package = "com.hederahashgraph.api.proto.java"; +option java_multiple_files = true; + +/** + * Each shard has a nonnegative shard number. Each realm within a given shard has a nonnegative + * realm number (that number might be reused in other shards). And each account, file, and smart + * contract instance within a given realm has a nonnegative number (which might be reused in other + * realms). Every account, file, and smart contract instance is within exactly one realm. So a + * FileID is a triplet of numbers, like 0.1.2 for entity number 2 within realm 1 within shard 0. + * Each realm maintains a single counter for assigning numbers, so if there is a file with ID + * 0.1.2, then there won't be an account or smart contract instance with ID 0.1.2. + * + * Everything is partitioned into realms so that each Solidity smart contract can access everything + * in just a single realm, locking all those entities while it's running, but other smart contracts + * could potentially run in other realms in parallel. So realms allow Solidity to be parallelized + * somewhat, even though the language itself assumes everything is serial. + */ +message ShardID { + /** + * the shard number (nonnegative) + */ + int64 shardNum = 1; +} + +/** + * The ID for a realm. Within a given shard, every realm has a unique ID. Each account, file, and + * contract instance belongs to exactly one realm. + */ +message RealmID { + /** + * The shard number (nonnegative) + */ + int64 shardNum = 1; + + /** + * The realm number (nonnegative) + */ + int64 realmNum = 2; +} + +/** + * The ID for an a cryptocurrency account + */ +message AccountID { + /** + * The shard number (nonnegative) + */ + int64 shardNum = 1; + + /** + * The realm number (nonnegative) + */ + int64 realmNum = 2; + + /** + * The account number unique within its realm which can be either a non-negative integer or an alias public key. + * For any AccountID fields in the query response, transaction record or transaction receipt only accountNum will + * be populated. + */ + oneof account { + /** + * A non-negative account number unique within its realm + */ + int64 accountNum = 3; + + /** + * The public key bytes to be used as the account's alias. The public key bytes are the result of serializing + * a protobuf Key message for any primitive key type. Currently only primitive key bytes are supported as an alias + * (ThresholdKey, KeyList, ContractID, and delegatable_contract_id are not supported) + * + * May also be the ethereum account 20-byte EVM address to be used initially in place of the public key bytes. This EVM + * address may be either the encoded form of the shard.realm.num or the keccak-256 hash of a ECDSA_SECP256K1 primitive key. + * + * At most one account can ever have a given alias and it is used for account creation if it + * was automatically created using a crypto transfer. It will be null if an account is created normally. + * It is immutable once it is set for an account. + * + * If a transaction auto-creates the account, any further transfers to that alias will simply be deposited + * in that account, without creating anything, and with no creation fee being charged. + * + * If a transaction lazily-creates this account, a subsequent transaction will be required containing the public key bytes + * that map to the EVM address bytes. The provided public key bytes will then serve as the final alias bytes. + */ + bytes alias = 4; + + } +} + +/** + * The ID for a file + */ +message FileID { + /** + * The shard number (nonnegative) + */ + int64 shardNum = 1; + + /** + * The realm number (nonnegative) + */ + int64 realmNum = 2; + + /** + * A nonnegative File number unique within its realm + */ + int64 fileNum = 3; +} + +/** + * The ID for a smart contract instance + */ +message ContractID { + /** + * The shard number (nonnegative) + */ + int64 shardNum = 1; + + /** + * The realm number (nonnegative) + */ + int64 realmNum = 2; + + oneof contract { + /** + * A nonnegative number unique within a given shard and realm + */ + int64 contractNum = 3; + + /** + * The 20-byte EVM address of the contract to call. + * + * Every contract has an EVM address determined by its shard.realm.num id. + * This address is as follows: + *
    + *
  1. The first 4 bytes are the big-endian representation of the shard.
  2. + *
  3. The next 8 bytes are the big-endian representation of the realm.
  4. + *
  5. The final 8 bytes are the big-endian representation of the number.
  6. + *
+ * + * Contracts created via CREATE2 have an additional, primary address that is + * derived from the EIP-1014 + * specification, and does not have a simple relation to a shard.realm.num id. + * + * (Please do note that CREATE2 contracts can also be referenced by the three-part + * EVM address described above.) + */ + bytes evm_address = 4; + } +} + +/** + * The ID for a transaction. This is used for retrieving receipts and records for a transaction, for + * appending to a file right after creating it, for instantiating a smart contract with bytecode in + * a file just created, and internally by the network for detecting when duplicate transactions are + * submitted. A user might get a transaction processed faster by submitting it to N nodes, each with + * a different node account, but all with the same TransactionID. Then, the transaction will take + * effect when the first of all those nodes submits the transaction and it reaches consensus. The + * other transactions will not take effect. So this could make the transaction take effect faster, + * if any given node might be slow. However, the full transaction fee is charged for each + * transaction, so the total fee is N times as much if the transaction is sent to N nodes. + * + * Applicable to Scheduled Transactions: + * - The ID of a Scheduled Transaction has transactionValidStart and accountIDs inherited from the + * ScheduleCreate transaction that created it. That is to say that they are equal + * - The scheduled property is true for Scheduled Transactions + * - transactionValidStart, accountID and scheduled properties should be omitted + */ +message TransactionID { + /** + * The transaction is invalid if consensusTimestamp < transactionID.transactionStartValid + */ + Timestamp transactionValidStart = 1; + + /** + * The Account ID that paid for this transaction + */ + AccountID accountID = 2; + + /** + * Whether the Transaction is of type Scheduled or no + */ + bool scheduled = 3; + + /** + * The identifier for an internal transaction that was spawned as part + * of handling a user transaction. (These internal transactions share the + * transactionValidStart and accountID of the user transaction, so a + * nonce is necessary to give them a unique TransactionID.) + * + * An example is when a "parent" ContractCreate or ContractCall transaction + * calls one or more HTS precompiled contracts; each of the "child" + * transactions spawned for a precompile has a id with a different nonce. + */ + int32 nonce = 4; +} + +/** + * An account, and the amount that it sends or receives during a cryptocurrency or token transfer. + */ +message AccountAmount { + /** + * The Account ID that sends/receives cryptocurrency or tokens + */ + AccountID accountID = 1; + + /** + * The amount of tinybars (for Crypto transfers) or in the lowest + * denomination (for Token transfers) that the account sends(negative) or + * receives(positive) + */ + sint64 amount = 2; + + /** + * If true then the transfer is expected to be an approved allowance and the + * accountID is expected to be the owner. The default is false (omitted). + */ + bool is_approval = 3; +} + +/** + * A list of accounts and amounts to transfer out of each account (negative) or into it (positive). + */ +message TransferList { + /** + * Multiple list of AccountAmount pairs, each of which has an account and + * an amount to transfer into it (positive) or out of it (negative) + */ + repeated AccountAmount accountAmounts = 1; +} + +/** + * A sender account, a receiver account, and the serial number of an NFT of a Token with + * NON_FUNGIBLE_UNIQUE type. When minting NFTs the sender will be the default AccountID instance + * (0.0.0) and when burning NFTs, the receiver will be the default AccountID instance. + */ +message NftTransfer { + /** + * The accountID of the sender + */ + AccountID senderAccountID = 1; + + /** + * The accountID of the receiver + */ + AccountID receiverAccountID = 2; + + /** + * The serial number of the NFT + */ + int64 serialNumber = 3; + + /** + * If true then the transfer is expected to be an approved allowance and the + * senderAccountID is expected to be the owner. The default is false (omitted). + */ + bool is_approval = 4; +} + +/** + * A list of token IDs and amounts representing the transferred out (negative) or into (positive) + * amounts, represented in the lowest denomination of the token + */ +message TokenTransferList { + /** + * The ID of the token + */ + TokenID token = 1; + + /** + * Applicable to tokens of type FUNGIBLE_COMMON. Multiple list of AccountAmounts, each of which + * has an account and amount + */ + repeated AccountAmount transfers = 2; + + /** + * Applicable to tokens of type NON_FUNGIBLE_UNIQUE. Multiple list of NftTransfers, each of + * which has a sender and receiver account, including the serial number of the NFT + */ + repeated NftTransfer nftTransfers = 3; + + /** + * If present, the number of decimals this fungible token type is expected to have. The transfer + * will fail with UNEXPECTED_TOKEN_DECIMALS if the actual decimals differ. + */ + google.protobuf.UInt32Value expected_decimals = 4; +} + +/** + * A rational number, used to set the amount of a value transfer to collect as a custom fee + */ +message Fraction { + /** + * The rational's numerator + */ + int64 numerator = 1; + + /** + * The rational's denominator; a zero value will result in FRACTION_DIVIDES_BY_ZERO + */ + int64 denominator = 2; +} + +/** + * Unique identifier for a topic (used by the consensus service) + */ +message TopicID { + /** + * The shard number (nonnegative) + */ + int64 shardNum = 1; + + /** + * The realm number (nonnegative) + */ + int64 realmNum = 2; + + /** + * Unique topic identifier within a realm (nonnegative). + */ + int64 topicNum = 3; +} + +/** + * Unique identifier for a token + */ +message TokenID { + /** + * A nonnegative shard number + */ + int64 shardNum = 1; + + /** + * A nonnegative realm number + */ + int64 realmNum = 2; + + /** + * A nonnegative token number + */ + int64 tokenNum = 3; +} + +/** + * Unique identifier for a Schedule + */ +message ScheduleID { + /** + * A nonnegative shard number + */ + int64 shardNum = 1; + + /** + * A nonnegative realm number + */ + int64 realmNum = 2; + + /** + * A nonnegative schedule number + */ + int64 scheduleNum = 3; +} + +/** + * Possible Token Types (IWA Compatibility). + * Apart from fungible and non-fungible, Tokens can have either a common or unique representation. + * This distinction might seem subtle, but it is important when considering how tokens can be traced + * and if they can have isolated and unique properties. + */ +enum TokenType { + /** + * Interchangeable value with one another, where any quantity of them has the same value as + * another equal quantity if they are in the same class. Share a single set of properties, not + * distinct from one another. Simply represented as a balance or quantity to a given Hedera + * account. + */ + FUNGIBLE_COMMON = 0; + + /** + * Unique, not interchangeable with other tokens of the same type as they typically have + * different values. Individually traced and can carry unique properties (e.g. serial number). + */ + NON_FUNGIBLE_UNIQUE = 1; +} + +/** + * Allows a set of resource prices to be scoped to a certain type of a HAPI operation. + * + * For example, the resource prices for a TokenMint operation are different between minting fungible + * and non-fungible tokens. This enum allows us to "mark" a set of prices as applying to one or the + * other. + * + * Similarly, the resource prices for a basic TokenCreate without a custom fee schedule yield a + * total price of $1. The resource prices for a TokenCreate with a custom fee schedule are different + * and yield a total base price of $2. + */ +enum SubType { + /** + * The resource prices have no special scope + */ + DEFAULT = 0; + + /** + * The resource prices are scoped to an operation on a fungible common token + */ + TOKEN_FUNGIBLE_COMMON = 1; + + /** + * The resource prices are scoped to an operation on a non-fungible unique token + */ + TOKEN_NON_FUNGIBLE_UNIQUE = 2; + + /** + * The resource prices are scoped to an operation on a fungible common + * token with a custom fee schedule + */ + TOKEN_FUNGIBLE_COMMON_WITH_CUSTOM_FEES = 3; + + /** + * The resource prices are scoped to an operation on a non-fungible unique + * token with a custom fee schedule + */ + TOKEN_NON_FUNGIBLE_UNIQUE_WITH_CUSTOM_FEES = 4; + + /** + * The resource prices are scoped to a ScheduleCreate containing a ContractCall. + */ + SCHEDULE_CREATE_CONTRACT_CALL = 5; +} + +/** + * Possible Token Supply Types (IWA Compatibility). + * Indicates how many tokens can have during its lifetime. + */ +enum TokenSupplyType { + /** + * Indicates that tokens of that type have an upper bound of Long.MAX_VALUE. + */ + INFINITE = 0; + + /** + * Indicates that tokens of that type have an upper bound of maxSupply, + * provided on token creation. + */ + FINITE = 1; +} + +/** + * Possible Freeze statuses returned on TokenGetInfoQuery or CryptoGetInfoResponse in + * TokenRelationship + */ +enum TokenFreezeStatus { + /** + * UNDOCUMENTED + */ + FreezeNotApplicable = 0; + + /** + * UNDOCUMENTED + */ + Frozen = 1; + + /** + * UNDOCUMENTED + */ + Unfrozen = 2; +} + +/** + * Possible KYC statuses returned on TokenGetInfoQuery or CryptoGetInfoResponse in TokenRelationship + */ +enum TokenKycStatus { + /** + * UNDOCUMENTED + */ + KycNotApplicable = 0; + + /** + * UNDOCUMENTED + */ + Granted = 1; + + /** + * UNDOCUMENTED + */ + Revoked = 2; +} + +/** + * Possible Pause statuses returned on TokenGetInfoQuery + */ +enum TokenPauseStatus { + /** + * Indicates that a Token has no pauseKey + */ + PauseNotApplicable = 0; + + /** + * Indicates that a Token is Paused + */ + Paused = 1; + + /** + * Indicates that a Token is Unpaused. + */ + Unpaused = 2; +} + +/** + * A Key can be a public key from either the Ed25519 or ECDSA(secp256k1) signature schemes, where + * in the ECDSA(secp256k1) case we require the 33-byte compressed form of the public key. We call + * these public keys primitive keys. + * + * If an account has primitive key associated to it, then the corresponding private key must sign + * any transaction to transfer cryptocurrency out of it. + * + * A Key can also be the ID of a smart contract instance, which is then authorized to perform any + * precompiled contract action that requires this key to sign. + * + * Note that when a Key is a smart contract ID, it doesn't mean the contract with that ID + * will actually create a cryptographic signature. It only means that when the contract calls a + * precompiled contract, the resulting "child transaction" will be authorized to perform any action + * controlled by the Key. + * + * A Key can be a "threshold key", which means a list of M keys, any N of which must sign in order + * for the threshold signature to be considered valid. The keys within a threshold signature may + * themselves be threshold signatures, to allow complex signature requirements. + * + * A Key can be a "key list" where all keys in the list must sign unless specified otherwise in the + * documentation for a specific transaction type (e.g. FileDeleteTransactionBody). Their use is + * dependent on context. For example, a Hedera file is created with a list of keys, where all of + * them must sign a transaction to create or modify the file, but only one of them is needed to sign + * a transaction to delete the file. So it's a single list that sometimes acts as a 1-of-M threshold + * key, and sometimes acts as an M-of-M threshold key. A key list is always an M-of-M, unless + * specified otherwise in documentation. A key list can have nested key lists or threshold keys. + * Nested key lists are always M-of-M. A key list can have repeated primitive public keys, but all + * repeated keys are only required to sign once. + * + * A Key can contain a ThresholdKey or KeyList, which in turn contain a Key, so this mutual + * recursion would allow nesting arbitrarily deep. A ThresholdKey which contains a list of primitive + * keys has 3 levels: ThresholdKey -> KeyList -> Key. A KeyList which contains several primitive + * keys has 2 levels: KeyList -> Key. A Key with 2 levels of nested ThresholdKeys has 7 levels: + * Key -> ThresholdKey -> KeyList -> Key -> ThresholdKey -> KeyList -> Key. + * + * Each Key should not have more than 46 levels, which implies 15 levels of nested ThresholdKeys. + */ +message Key { + oneof key { + /** + * smart contract instance that is authorized as if it had signed with a key + */ + ContractID contractID = 1; + + /** + * Ed25519 public key bytes + */ + bytes ed25519 = 2; + + /** + * (NOT SUPPORTED) RSA-3072 public key bytes + */ + bytes RSA_3072 = 3; + + /** + * (NOT SUPPORTED) ECDSA with the p-384 curve public key bytes + */ + bytes ECDSA_384 = 4; + + /** + * a threshold N followed by a list of M keys, any N of which are required to form a valid + * signature + */ + ThresholdKey thresholdKey = 5; + + /** + * A list of Keys of the Key type. + */ + KeyList keyList = 6; + + /** + * Compressed ECDSA(secp256k1) public key bytes + */ + bytes ECDSA_secp256k1 = 7; + + /** + * A smart contract that, if the recipient of the active message frame, should be treated + * as having signed. (Note this does not mean the code being executed in the frame + * will belong to the given contract, since it could be running another contract's code via + * delegatecall. So setting this key is a more permissive version of setting the + * contractID key, which also requires the code in the active message frame belong to the + * the contract with the given id.) + */ + ContractID delegatable_contract_id = 8; + } +} + +/** + * A set of public keys that are used together to form a threshold signature. If the threshold is N + * and there are M keys, then this is an N of M threshold signature. If an account is associated + * with ThresholdKeys, then a transaction to move cryptocurrency out of it must be signed by a list + * of M signatures, where at most M-N of them are blank, and the other at least N of them are valid + * signatures corresponding to at least N of the public keys listed here. + */ +message ThresholdKey { + /** + * A valid signature set must have at least this many signatures + */ + uint32 threshold = 1; + + /** + * List of all the keys that can sign + */ + KeyList keys = 2; +} + +/** + * A list of keys that requires all keys (M-of-M) to sign unless otherwise specified in + * documentation. A KeyList may contain repeated keys, but all repeated keys are only required to + * sign once. + */ +message KeyList { + /** + * list of keys + */ + repeated Key keys = 1; +} + +/** + * This message is DEPRECATED and UNUSABLE with network nodes. It is retained + * here only for historical reasons. + * + * Please use the SignaturePair and SignatureMap messages. + */ +message Signature { + option deprecated = true; + + oneof signature { + /** + * smart contract virtual signature (always length zero) + */ + bytes contract = 1; + + /** + * ed25519 signature bytes + */ + bytes ed25519 = 2; + + /** + * RSA-3072 signature bytes + */ + bytes RSA_3072 = 3; + + /** + * ECDSA p-384 signature bytes + */ + bytes ECDSA_384 = 4; + + /** + * A list of signatures for a single N-of-M threshold Key. This must be a list of exactly M + * signatures, at least N of which are non-null. + */ + ThresholdSignature thresholdSignature = 5; + + /** + * A list of M signatures, each corresponding to a Key in a KeyList of the same length. + */ + SignatureList signatureList = 6; + } +} + +/** + * This message is DEPRECATED and UNUSABLE with network nodes. It is retained + * here only for historical reasons. + * + * Please use the SignaturePair and SignatureMap messages. + */ +message ThresholdSignature { + option deprecated = true; + + /** + * for an N-of-M threshold key, this is a list of M signatures, at least N of which must be + * non-null + */ + SignatureList sigs = 2; +} + +/** + * This message is DEPRECATED and UNUSABLE with network nodes. It is retained + * here only for historical reasons. + * + * Please use the SignaturePair and SignatureMap messages. + */ +message SignatureList { + option deprecated = true; + + /** + * each signature corresponds to a Key in the KeyList + */ + repeated Signature sigs = 2; +} + +/** + * The client may use any number of bytes from zero to the whole length of the public key for + * pubKeyPrefix. If zero bytes are used, then it must be that only one primitive key is required + * to sign the linked transaction; it will surely resolve to INVALID_SIGNATURE otherwise. + * + * IMPORTANT: In the special case that a signature is being provided for a key used to + * authorize a precompiled contract, the pubKeyPrefix must contain the entire public + * key! That is, if the key is a Ed25519 key, the pubKeyPrefix should be 32 bytes + * long. If the key is a ECDSA(secp256k1) key, the pubKeyPrefix should be 33 bytes long, + * since we require the compressed form of the public key. + * + * Only Ed25519 and ECDSA(secp256k1) keys and hence signatures are currently supported. + */ +message SignaturePair { + /** + * First few bytes of the public key + */ + bytes pubKeyPrefix = 1; + + oneof signature { + /** + * smart contract virtual signature (always length zero) + */ + bytes contract = 2; + + /** + * ed25519 signature + */ + bytes ed25519 = 3; + + /** + * RSA-3072 signature + */ + bytes RSA_3072 = 4; + + /** + * ECDSA p-384 signature + */ + bytes ECDSA_384 = 5; + + /** + * ECDSA(secp256k1) signature + */ + bytes ECDSA_secp256k1 = 6; + } +} + +/** + * A set of signatures corresponding to every unique public key used to sign a given transaction. If + * one public key matches more than one prefixes on the signature map, the transaction containing + * the map will fail immediately with the response code KEY_PREFIX_MISMATCH. + */ +message SignatureMap { + /** + * Each signature pair corresponds to a unique Key required to sign the transaction. + */ + repeated SignaturePair sigPair = 1; +} + +/** + * The transactions and queries supported by Hedera Hashgraph. + */ +enum HederaFunctionality { + /** + * UNSPECIFIED - Need to keep first value as unspecified because first element is ignored and + * not parsed (0 is ignored by parser) + */ + NONE = 0; + + /** + * crypto transfer + */ + CryptoTransfer = 1; + + /** + * crypto update account + */ + CryptoUpdate = 2; + + /** + * crypto delete account + */ + CryptoDelete = 3; + + /** + * Add a livehash to a crypto account + */ + CryptoAddLiveHash = 4; + + /** + * Delete a livehash from a crypto account + */ + CryptoDeleteLiveHash = 5; + + /** + * Smart Contract Call + */ + ContractCall = 6; + + /** + * Smart Contract Create Contract + */ + ContractCreate = 7; + + /** + * Smart Contract update contract + */ + ContractUpdate = 8; + + /** + * File Operation create file + */ + FileCreate = 9; + + /** + * File Operation append file + */ + FileAppend = 10; + + /** + * File Operation update file + */ + FileUpdate = 11; + + /** + * File Operation delete file + */ + FileDelete = 12; + + /** + * crypto get account balance + */ + CryptoGetAccountBalance = 13; + + /** + * crypto get account record + */ + CryptoGetAccountRecords = 14; + + /** + * Crypto get info + */ + CryptoGetInfo = 15; + + /** + * Smart Contract Call + */ + ContractCallLocal = 16; + + /** + * Smart Contract get info + */ + ContractGetInfo = 17; + + /** + * Smart Contract, get the runtime code + */ + ContractGetBytecode = 18; + + /** + * Smart Contract, get by solidity ID + */ + GetBySolidityID = 19; + + /** + * Smart Contract, get by key + */ + GetByKey = 20; + + /** + * Get a live hash from a crypto account + */ + CryptoGetLiveHash = 21; + + /** + * Crypto, get the stakers for the node + */ + CryptoGetStakers = 22; + + /** + * File Operations get file contents + */ + FileGetContents = 23; + + /** + * File Operations get the info of the file + */ + FileGetInfo = 24; + + /** + * Crypto get the transaction records + */ + TransactionGetRecord = 25; + + /** + * Contract get the transaction records + */ + ContractGetRecords = 26; + + /** + * crypto create account + */ + CryptoCreate = 27; + + /** + * system delete file + */ + SystemDelete = 28; + + /** + * system undelete file + */ + SystemUndelete = 29; + + /** + * delete contract + */ + ContractDelete = 30; + + /** + * freeze + */ + Freeze = 31; + + /** + * Create Tx Record + */ + CreateTransactionRecord = 32; + + /** + * Crypto Auto Renew + */ + CryptoAccountAutoRenew = 33; + + /** + * Contract Auto Renew + */ + ContractAutoRenew = 34; + + /** + * Get Version + */ + GetVersionInfo = 35; + + /** + * Transaction Get Receipt + */ + TransactionGetReceipt = 36; + + /** + * Create Topic + */ + ConsensusCreateTopic = 50; + + /** + * Update Topic + */ + ConsensusUpdateTopic = 51; + + /** + * Delete Topic + */ + ConsensusDeleteTopic = 52; + + /** + * Get Topic information + */ + ConsensusGetTopicInfo = 53; + + /** + * Submit message to topic + */ + ConsensusSubmitMessage = 54; + + UncheckedSubmit = 55; + /** + * Create Token + */ + TokenCreate = 56; + + /** + * Get Token information + */ + TokenGetInfo = 58; + + /** + * Freeze Account + */ + TokenFreezeAccount = 59; + + /** + * Unfreeze Account + */ + TokenUnfreezeAccount = 60; + + /** + * Grant KYC to Account + */ + TokenGrantKycToAccount = 61; + + /** + * Revoke KYC from Account + */ + TokenRevokeKycFromAccount = 62; + + /** + * Delete Token + */ + TokenDelete = 63; + + /** + * Update Token + */ + TokenUpdate = 64; + + /** + * Mint tokens to treasury + */ + TokenMint = 65; + + /** + * Burn tokens from treasury + */ + TokenBurn = 66; + + /** + * Wipe token amount from Account holder + */ + TokenAccountWipe = 67; + + /** + * Associate tokens to an account + */ + TokenAssociateToAccount = 68; + + /** + * Dissociate tokens from an account + */ + TokenDissociateFromAccount = 69; + + /** + * Create Scheduled Transaction + */ + ScheduleCreate = 70; + + /** + * Delete Scheduled Transaction + */ + ScheduleDelete = 71; + + /** + * Sign Scheduled Transaction + */ + ScheduleSign = 72; + + /** + * Get Scheduled Transaction Information + */ + ScheduleGetInfo = 73; + + /** + * Get Token Account Nft Information + */ + TokenGetAccountNftInfos = 74; + + /** + * Get Token Nft Information + */ + TokenGetNftInfo = 75; + + /** + * Get Token Nft List Information + */ + TokenGetNftInfos = 76; + + /** + * Update a token's custom fee schedule, if permissible + */ + TokenFeeScheduleUpdate = 77; + + /** + * Get execution time(s) by TransactionID, if available + */ + NetworkGetExecutionTime = 78; + + /** + * Pause the Token + */ + TokenPause = 79; + + /** + * Unpause the Token + */ + TokenUnpause = 80; + + /** + * Approve allowance for a spender relative to the owner account + */ + CryptoApproveAllowance = 81; + + /** + * Deletes granted allowances on owner account + */ + CryptoDeleteAllowance = 82; + + /** + * Gets all the information about an account, including balance and allowances. This does not get the list of + * account records. + */ + GetAccountDetails = 83; + + /** + * Ethereum Transaction + */ + EthereumTransaction = 84; + + /** + * Updates the staking info at the end of staking period to indicate new staking period has started. + */ + NodeStakeUpdate = 85; + + /** + * Generates a pseudorandom number. + */ + UtilPrng = 86; +} + +/** + * A set of prices the nodes use in determining transaction and query fees, and constants involved + * in fee calculations. Nodes multiply the amount of resources consumed by a transaction or query + * by the corresponding price to calculate the appropriate fee. Units are one-thousandth of a + * tinyCent. + */ +message FeeComponents { + /** + * A minimum, the calculated fee must be greater than this value + */ + int64 min = 1; + + /** + * A maximum, the calculated fee must be less than this value + */ + int64 max = 2; + + /** + * A constant contribution to the fee + */ + int64 constant = 3; + + /** + * The price of bandwidth consumed by a transaction, measured in bytes + */ + int64 bpt = 4; + + /** + * The price per signature verification for a transaction + */ + int64 vpt = 5; + + /** + * The price of RAM consumed by a transaction, measured in byte-hours + */ + int64 rbh = 6; + + /** + * The price of storage consumed by a transaction, measured in byte-hours + */ + int64 sbh = 7; + + /** + * The price of computation for a smart contract transaction, measured in gas + */ + int64 gas = 8; + + /** + * The price per hbar transferred for a transfer + */ + int64 tv = 9; + + /** + * The price of bandwidth for data retrieved from memory for a response, measured in bytes + */ + int64 bpr = 10; + + /** + * The price of bandwidth for data retrieved from disk for a response, measured in bytes + */ + int64 sbpr = 11; +} + +/** + * The fees for a specific transaction or query based on the fee data. + */ +message TransactionFeeSchedule { + /** + * A particular transaction or query + */ + HederaFunctionality hederaFunctionality = 1; + + /** + * Resource price coefficients + */ + FeeData feeData = 2 [deprecated=true]; + + /** + * Resource price coefficients. Supports subtype price definition. + */ + repeated FeeData fees = 3; +} + +/** + * The total fee charged for a transaction. It is composed of three components – a node fee that + * compensates the specific node that submitted the transaction, a network fee that compensates the + * network for assigning the transaction a consensus timestamp, and a service fee that compensates + * the network for the ongoing maintenance of the consequences of the transaction. + */ +message FeeData { + /** + * Fee paid to the submitting node + */ + FeeComponents nodedata = 1; + + /** + * Fee paid to the network for processing a transaction into consensus + */ + FeeComponents networkdata = 2; + + /** + * Fee paid to the network for providing the service associated with the + * transaction; for instance, storing a file + */ + FeeComponents servicedata = 3; + + /** + * SubType distinguishing between different types of FeeData, correlating + * to the same HederaFunctionality + */ + SubType subType = 4; +} + +/** + * A list of resource prices fee for different transactions and queries and the time period at which + * this fee schedule will expire. Nodes use the prices to determine the fees for all transactions + * based on how much of those resources each transaction uses. + */ +message FeeSchedule { + /** + * List of price coefficients for network resources + */ + repeated TransactionFeeSchedule transactionFeeSchedule = 1; + + /** + * FeeSchedule expiry time + */ + TimestampSeconds expiryTime = 2; +} + +/** + * This contains two Fee Schedules with expiry timestamp. + */ +message CurrentAndNextFeeSchedule { + /** + * Contains current Fee Schedule + */ + FeeSchedule currentFeeSchedule = 1; + + /** + * Contains next Fee Schedule + */ + FeeSchedule nextFeeSchedule = 2; +} + +/** + * Contains the IP address and the port representing a service endpoint of a Node in a network. Used + * to reach the Hedera API and submit transactions to the network. + */ +message ServiceEndpoint { + /** + * The 32-bit IPv4 address of the node encoded in left to right order (e.g. 127.0.0.1 has 127 + * as its first byte) + */ + bytes ipAddressV4 = 1; + + /** + * The port of the node + */ + int32 port = 2; +} + +/** + * The data about a node, including its service endpoints and the Hedera account to be paid for + * services provided by the node (that is, queries answered and transactions submitted.) + * + * If the serviceEndpoint list is not set, or empty, then the endpoint given by the + * (deprecated) ipAddress and portno fields should be used. + * + * All fields are populated in the 0.0.102 address book file while only fields that start with # are + * populated in the 0.0.101 address book file. + */ +message NodeAddress { + /** + * The IP address of the Node with separator & octets encoded in UTF-8. Usage is deprecated, + * ServiceEndpoint is preferred to retrieve a node's list of IP addresses and ports + */ + bytes ipAddress = 1 [deprecated=true]; + + /** + * The port number of the grpc server for the node. Usage is deprecated, ServiceEndpoint is + * preferred to retrieve a node's list of IP addresses and ports + */ + int32 portno = 2 [deprecated=true]; + + /** + * Usage is deprecated, nodeAccountId is preferred to retrieve a node's account ID + */ + bytes memo = 3 [deprecated=true]; + + /** + * The node's X509 RSA public key used to sign stream files (e.g., record stream + * files). Precisely, this field is a string of hexadecimal characters which, + * translated to binary, are the public key's DER encoding. + */ + string RSA_PubKey = 4; + + /** + * # A non-sequential identifier for the node + */ + int64 nodeId = 5; + + /** + * # The account to be paid for queries and transactions sent to this node + */ + AccountID nodeAccountId = 6; + + /** + * # Hash of the node's TLS certificate. Precisely, this field is a string of + * hexadecimal characters which, translated to binary, are the SHA-384 hash of + * the UTF-8 NFKD encoding of the node's TLS cert in PEM format. Its value can be + * used to verify the node's certificate it presents during TLS negotiations. + */ + bytes nodeCertHash = 7; + + /** + * # A node's service IP addresses and ports + */ + repeated ServiceEndpoint serviceEndpoint = 8; + + /** + * A description of the node, with UTF-8 encoding up to 100 bytes + */ + string description = 9; + + /** + * [Deprecated] The amount of tinybars staked to the node + */ + int64 stake = 10 [deprecated = true]; +} + +/** + * A list of nodes and their metadata that contains all details of the nodes for the network. Used + * to parse the contents of system files 0.0.101 and 0.0.102. + */ +message NodeAddressBook { + /** + * Metadata of all nodes in the network + */ + repeated NodeAddress nodeAddress = 1; +} + +/** + * Hedera follows semantic versioning (https://semver.org/) for both the HAPI protobufs and the + * Services software. This type allows the getVersionInfo query in the + * NetworkService to return the deployed versions of both protobufs and software on the + * node answering the query. + */ +message SemanticVersion { + /** + * Increases with incompatible API changes + */ + int32 major = 1; + + /** + * Increases with backwards-compatible new functionality + */ + int32 minor = 2; + + /** + * Increases with backwards-compatible bug fixes + */ + int32 patch = 3; + + /** + * A pre-release version MAY be denoted by appending a hyphen and a series of dot separated + * identifiers (https://semver.org/#spec-item-9); so given a semver 0.14.0-alpha.1+21AF26D3, + * this field would contain 'alpha.1' + */ + string pre = 4; + + /** + * Build metadata MAY be denoted by appending a plus sign and a series of dot separated + * identifiers immediately following the patch or pre-release version + * (https://semver.org/#spec-item-10); so given a semver 0.14.0-alpha.1+21AF26D3, this field + * would contain '21AF26D3' + */ + string build = 5; +} + +/** + * UNDOCUMENTED + */ +message Setting { + /** + * name of the property + */ + string name = 1; + + /** + * value of the property + */ + string value = 2; + + /** + * any data associated with property + */ + bytes data = 3; +} + +/** + * UNDOCUMENTED + */ +message ServicesConfigurationList { + /** + * list of name value pairs of the application properties + */ + repeated Setting nameValue = 1; +} + +/** + * Token's information related to the given Account + */ +message TokenRelationship { + /** + * The ID of the token + */ + TokenID tokenId = 1; + + /** + * The Symbol of the token + */ + string symbol = 2; + + /** + * For token of type FUNGIBLE_COMMON - the balance that the Account holds in the smallest + * denomination. For token of type NON_FUNGIBLE_UNIQUE - the number of NFTs held by the account + */ + uint64 balance = 3; + + /** + * The KYC status of the account (KycNotApplicable, Granted or Revoked). If the token does not + * have KYC key, KycNotApplicable is returned + */ + TokenKycStatus kycStatus = 4; + + /** + * The Freeze status of the account (FreezeNotApplicable, Frozen or Unfrozen). If the token does + * not have Freeze key, FreezeNotApplicable is returned + */ + TokenFreezeStatus freezeStatus = 5; + + /** + * Tokens divide into 10decimals pieces + */ + uint32 decimals = 6; + + /** + * Specifies if the relationship is created implicitly. False : explicitly associated, True : + * implicitly associated. + */ + bool automatic_association = 7; +} + +/** + * A number of transferable units of a certain token. + * + * The transferable unit of a token is its smallest denomination, as given by the token's + * decimals property---each minted token contains 10decimals + * transferable units. For example, we could think of the cent as the transferable unit of the US + * dollar (decimals=2); and the tinybar as the transferable unit of hbar + * (decimals=8). + * + * Transferable units are not directly comparable across different tokens. + */ +message TokenBalance { + /** + * A unique token id + */ + TokenID tokenId = 1; + + /** + * Number of transferable units of the identified token. For token of type FUNGIBLE_COMMON - + * balance in the smallest denomination. For token of type NON_FUNGIBLE_UNIQUE - the number of + * NFTs held by the account + */ + uint64 balance = 2; + + /** + * Tokens divide into 10decimals pieces + */ + uint32 decimals = 3; +} + +/** + * A sequence of token balances + */ +message TokenBalances { + repeated TokenBalance tokenBalances = 1; +} + +/* A token - account association */ +message TokenAssociation { + TokenID token_id = 1; // The token involved in the association + AccountID account_id = 2; // The account involved in the association +} + +/** + * Staking metadata for an account or a contract returned in CryptoGetInfo or ContractGetInfo queries + */ +message StakingInfo { + + /** + * If true, this account or contract declined to receive a staking reward. + */ + bool decline_reward = 1; + + /** + * The staking period during which either the staking settings for this account or contract changed (such as starting + * staking or changing staked_node_id) or the most recent reward was earned, whichever is later. If this account or contract + * is not currently staked to a node, then this field is not set. + */ + Timestamp stake_period_start = 2; + + /** + * The amount in tinybars that will be received in the next reward situation. + */ + int64 pending_reward = 3; + + /** + * The total of balance of all accounts staked to this account or contract. + */ + int64 staked_to_me = 4; + + /** + * ID of the account or node to which this account or contract is staking. + */ + oneof staked_id { + + /** + * The account to which this account or contract is staking. + */ + AccountID staked_account_id = 5; + + /** + * The ID of the node this account or contract is staked to. + */ + int64 staked_node_id = 6; + } +} diff --git a/src/Hedera/Protobuf/crypto_transfer.proto b/src/Hedera/Protobuf/crypto_transfer.proto new file mode 100644 index 00000000000..0c6c0b1b23e --- /dev/null +++ b/src/Hedera/Protobuf/crypto_transfer.proto @@ -0,0 +1,55 @@ +// Taken from https://github.com/hashgraph/hedera-protobufs/tree/main/services + +syntax = "proto3"; + +package proto; + +/*- + * ‌ + * Hedera Network Services Protobuf + * ​ + * Copyright (C) 2018 - 2021 Hedera Hashgraph, LLC + * ​ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ‍ + */ + +option java_package = "com.hederahashgraph.api.proto.java"; +option java_multiple_files = true; + +import "basic_types.proto"; + +/** + * Transfers cryptocurrency among two or more accounts by making the desired adjustments to their + * balances. Each transfer list can specify up to 10 adjustments. Each negative amount is withdrawn + * from the corresponding account (a sender), and each positive one is added to the corresponding + * account (a receiver). The amounts list must sum to zero. Each amount is a number of tinybars + * (there are 100,000,000 tinybars in one hbar). If any sender account fails to have sufficient + * hbars, then the entire transaction fails, and none of those transfers occur, though the + * transaction fee is still charged. This transaction must be signed by the keys for all the sending + * accounts, and for any receiving accounts that have receiverSigRequired == true. The signatures + * are in the same order as the accounts, skipping those accounts that don't need a signature. + */ +message CryptoTransferTransactionBody { + /** + * The desired hbar balance adjustments + */ + TransferList transfers = 1; + + /** + * The desired token unit balance adjustments; if any custom fees are assessed, the ledger will + * try to deduct them from the payer of this CryptoTransfer, resolving the transaction to + * INSUFFICIENT_PAYER_BALANCE_FOR_CUSTOM_FEE if this is not possible + */ + repeated TokenTransferList tokenTransfers = 2; +} diff --git a/src/Hedera/Protobuf/duration.proto b/src/Hedera/Protobuf/duration.proto new file mode 100644 index 00000000000..2b096068c8c --- /dev/null +++ b/src/Hedera/Protobuf/duration.proto @@ -0,0 +1,38 @@ +// Taken from https://github.com/hashgraph/hedera-protobufs/tree/main/services + +syntax = "proto3"; + +package proto; + +/*- + * ‌ + * Hedera Network Services Protobuf + * ​ + * Copyright (C) 2018 - 2021 Hedera Hashgraph, LLC + * ​ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ‍ + */ + +option java_package = "com.hederahashgraph.api.proto.java"; +option java_multiple_files = true; + +/** + * A length of time in seconds. + */ +message Duration { + /** + * The number of seconds + */ + int64 seconds = 1; +} diff --git a/src/Hedera/Protobuf/timestamp.proto b/src/Hedera/Protobuf/timestamp.proto new file mode 100644 index 00000000000..b7e8e9a8831 --- /dev/null +++ b/src/Hedera/Protobuf/timestamp.proto @@ -0,0 +1,54 @@ +// Taken from https://github.com/hashgraph/hedera-protobufs/tree/main/services + +syntax = "proto3"; + +package proto; + +/*- + * ‌ + * Hedera Network Services Protobuf + * ​ + * Copyright (C) 2018 - 2021 Hedera Hashgraph, LLC + * ​ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ‍ + */ + +option java_package = "com.hederahashgraph.api.proto.java"; +option java_multiple_files = true; + +/** + * An exact date and time. This is the same data structure as the protobuf Timestamp.proto (see the + * comments in https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto) + */ +message Timestamp { + /** + * Number of complete seconds since the start of the epoch + */ + int64 seconds = 1; + + /** + * Number of nanoseconds since the start of the last second + */ + int32 nanos = 2; +} + +/** + * An exact date and time, with a resolution of one second (no nanoseconds). + */ +message TimestampSeconds { + /** + * Number of complete seconds since the start of the epoch + */ + int64 seconds = 1; +} diff --git a/src/Hedera/Protobuf/transaction_body.proto b/src/Hedera/Protobuf/transaction_body.proto new file mode 100644 index 00000000000..0687b6f851d --- /dev/null +++ b/src/Hedera/Protobuf/transaction_body.proto @@ -0,0 +1,76 @@ +// Taken from https://github.com/hashgraph/hedera-protobufs/tree/main/services + +syntax = "proto3"; + +package proto; + +/*- + * ‌ + * Hedera Network Services Protobuf + * ​ + * Copyright (C) 2018 - 2021 Hedera Hashgraph, LLC + * ​ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ‍ + */ + +option java_package = "com.hederahashgraph.api.proto.java"; +option java_multiple_files = true; + +import "crypto_transfer.proto"; +import "duration.proto"; +import "basic_types.proto"; + +/** + * A single transaction. All transaction types are possible here. + */ +message TransactionBody { + /** + * The ID for this transaction, which includes the payer's account (the account paying the + * transaction fee). If two transactions have the same transactionID, they won't both have an + * effect + */ + TransactionID transactionID = 1; + + /** + * The account of the node that submits the client's transaction to the network + */ + AccountID nodeAccountID = 2; + + /** + * The maximum transaction fee the client is willing to pay + */ + uint64 transactionFee = 3; + + /** + * The transaction is invalid if consensusTimestamp > transactionID.transactionValidStart + + * transactionValidDuration + */ + Duration transactionValidDuration = 4; + + /** + * Any notes or descriptions that should be put into the record (max length 100) + */ + string memo = 6; + + /** + * The choices here are arranged by service in roughly lexicographical order. The field ordinals are non-sequential, and a result of the historical order of implementation. + */ + oneof data { + + /** + * Transfer amount between accounts + */ + CryptoTransferTransactionBody cryptoTransfer = 14; + } +} diff --git a/src/Hedera/Protobuf/transaction_contents.proto b/src/Hedera/Protobuf/transaction_contents.proto new file mode 100644 index 00000000000..2af320a2220 --- /dev/null +++ b/src/Hedera/Protobuf/transaction_contents.proto @@ -0,0 +1,42 @@ +// Taken from https://github.com/hashgraph/hedera-protobufs/tree/main/services + +syntax = "proto3"; + +package proto; + +/*- + * ‌ + * Hedera Network Services Protobuf + * ​ + * Copyright (C) 2018 - 2021 Hedera Hashgraph, LLC + * ​ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ‍ + */ + +option java_package = "com.hederahashgraph.api.proto.java"; +option java_multiple_files = true; + +import "basic_types.proto"; + +message SignedTransaction { + /** + * TransactionBody serialized into bytes, which needs to be signed + */ + bytes bodyBytes = 1; + + /** + * The signatures on the body with the new format, to authorize the transaction + */ + SignatureMap sigMap = 2; +} diff --git a/src/Hedera/Signer.cpp b/src/Hedera/Signer.cpp new file mode 100644 index 00000000000..47628377551 --- /dev/null +++ b/src/Hedera/Signer.cpp @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Signer.h" +#include "Address.h" +#include "HexCoding.h" +#include "Protobuf/transaction_body.pb.h" +#include "Protobuf/transaction_contents.pb.h" +#include "../PublicKey.h" + +namespace TW::Hedera::internals { +static inline proto::AccountID accountIDfromStr(const std::string& input) { + const auto hederaAccount = Address(input); + auto accountID = proto::AccountID(); + accountID.set_accountnum(static_cast(hederaAccount.num())); + accountID.set_realmnum(static_cast(hederaAccount.realm())); + accountID.set_shardnum(static_cast(hederaAccount.shard())); + return accountID; +} + +static inline proto::Timestamp timestampFromTWProto(const Proto::Timestamp& input) { + auto timestamp = proto::Timestamp(); + timestamp.set_seconds(input.seconds()); + timestamp.set_nanos(input.nanos()); + return timestamp; +} + +static inline proto::TransactionID transactionIDFromSigningInput(const Proto::SigningInput& input) { + auto transactionID = proto::TransactionID(); + *transactionID.mutable_transactionvalidstart() = timestampFromTWProto(input.body().transactionid().transactionvalidstart()); + *transactionID.mutable_accountid() = accountIDfromStr(input.body().transactionid().accountid()); + return transactionID; +} + +static inline proto::TransactionBody transactionBodyPrerequisites(const Proto::SigningInput& input) { + auto body = proto::TransactionBody(); + body.set_memo(input.body().memo()); + body.set_transactionfee(input.body().transactionfee()); + *body.mutable_nodeaccountid() = accountIDfromStr(input.body().nodeaccountid()); + body.mutable_transactionvalidduration()->set_seconds(input.body().transactionvalidduration()); + *body.mutable_transactionid() = transactionIDFromSigningInput(input); + return body; +} + +static inline proto::TransferList transferListFromInput(const Proto::SigningInput& input) { + auto transferList = proto::TransferList(); + auto fromAccountID = accountIDfromStr(input.body().transfer().from()); + auto amount = input.body().transfer().amount(); + auto* to = transferList.add_accountamounts(); + to->set_amount(amount); + *to->mutable_accountid() = accountIDfromStr(input.body().transfer().to()); + auto* from = transferList.add_accountamounts(); + from->set_amount(-amount); + *from->mutable_accountid() = fromAccountID; + return transferList; +} + +static inline proto::CryptoTransferTransactionBody cryptoTransferFromInput(const Proto::SigningInput& input) { + auto transferList = transferListFromInput(input); + auto cryptoTransfer = proto::CryptoTransferTransactionBody(); + *cryptoTransfer.mutable_transfers() = transferList; + return cryptoTransfer; +} + +static inline Proto::SigningOutput sign(const proto::TransactionBody& body, const PrivateKey& privateKey) { + auto protoOutput = Proto::SigningOutput(); + Data encoded; + auto encodedBody = data(body.SerializeAsString()); + auto signedBody = privateKey.sign(encodedBody, TWCurveED25519); + auto sigMap = proto::SignatureMap(); + auto* sigPair = sigMap.add_sigpair(); + sigPair->set_ed25519(signedBody.data(), signedBody.size()); + auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; + sigPair->set_pubkeyprefix(pubKey.data(), pubKey.size()); + auto signedTx = proto::SignedTransaction(); + signedTx.set_bodybytes(encodedBody.data(), encodedBody.size()); + *signedTx.mutable_sigmap() = sigMap; + encoded = data(signedTx.SerializeAsString()); + protoOutput.set_encoded(encoded.data(), encoded.size()); + return protoOutput; +} + +} // namespace TW::Hedera::internals + +namespace TW::Hedera { + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto body = internals::transactionBodyPrerequisites(input); + + switch (input.body().data_case()) { + case Proto::TransactionBody::kTransfer: { + *body.mutable_cryptotransfer() = internals::cryptoTransferFromInput(input); + break; + } + case Proto::TransactionBody::DATA_NOT_SET: + break; + default: + break; + } + return internals::sign(body, privateKey); +} + +} // namespace TW::Hedera diff --git a/src/Hedera/Signer.h b/src/Hedera/Signer.h new file mode 100644 index 00000000000..c76eaad764e --- /dev/null +++ b/src/Hedera/Signer.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "../PrivateKey.h" +#include "../proto/Hedera.pb.h" + +namespace TW::Hedera { + +/// Helper class that performs Hedera transaction signing. +class Signer { +public: + /// Hide default constructor + Signer() = delete; + + /// Signs a Proto::SigningInput transaction + static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; +}; + +} // namespace TW::Hedera diff --git a/src/HexCoding.h b/src/HexCoding.h index 6a12f0e2456..581da5e08cb 100644 --- a/src/HexCoding.h +++ b/src/HexCoding.h @@ -1,81 +1,79 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Data.h" +#include "rust/bindgen/WalletCoreRSBindgen.h" +#include "rust/Wrapper.h" -#include - +#include #include +#include #include #include -namespace TW { - -std::tuple value(uint8_t c); -/// Converts a range of bytes to a hexadecimal string representation. -template -inline std::string hex(const Iter begin, const Iter end) { - static constexpr std::array hexmap = { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' - }; +namespace TW::internal { +/// Parses a string of hexadecimal values. +/// +/// \returns the array or parsed bytes or an empty array if the string is not +/// valid hexadecimal. +inline Data parse_hex(const std::string& input) { + if (input.empty()) { + return {}; + } - std::string result; - result.reserve((end - begin) * 2); + Rust::CByteArrayResultWrapper res = Rust::decode_hex(input.c_str()); + return res.unwrap_or_default().data; +} +} - for (auto it = begin; it < end; ++it) { - auto val = static_cast(*it); - result.push_back(hexmap[val >> 4]); - result.push_back(hexmap[val & 0x0f]); - } +namespace TW { - return result; +inline bool is_hex_encoded(const std::string& s) +{ + bool with_0x = s.compare(0, 2, "0x") == 0 + && s.size() > 2 + && s.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos; + bool without_0x = s.find_first_not_of("0123456789abcdefABCDEF") == std::string::npos; + return with_0x || without_0x; } /// Converts a collection of bytes to a hexadecimal string representation. template -inline std::string hex(const T& collection) { - return hex(std::begin(collection), std::end(collection)); +inline std::string hex(const T& collection, bool prefixed = false) { + auto rust_functor = [prefixed](auto&& collection){ + auto res = Rust::encode_hex(collection.data(), collection.size(), prefixed); + std::string encoded_str(res); + Rust::free_string(res); + return encoded_str; + }; + if constexpr (std::is_same_v) { + return rust_functor(collection); + } + else if constexpr (std::is_same_v) { + return rust_functor(data(collection)); + } + else { + return rust_functor(data_from(collection)); + } } /// same as hex, with 0x prefix template -inline std::string hexEncoded(const T& collection) { - return hex(std::begin(collection), std::end(collection)).insert(0, "0x"); +inline std::string hexEncoded(T&& collection) { + return hex(std::forward(collection), true); } /// Converts a `uint64_t` value to a hexadecimal string. inline std::string hex(uint64_t value) { - auto bytes = reinterpret_cast(&value); - return hex(std::reverse_iterator(bytes + sizeof(value)), - std::reverse_iterator(bytes)); -} - -/// Parses a string of hexadecimal values. -/// -/// \returns the array or parsed bytes or an empty array if the string is not -/// valid hexadecimal. -template -inline Data parse_hex(const Iter begin, const Iter end) { - auto it = begin; - - // Skip `0x` - if (end - begin >= 2 && *begin == '0' && *(begin + 1) == 'x') { - it += 2; - } - try { - std::string temp; - boost::algorithm::unhex(it, end, std::back_inserter(temp)); - return Data(temp.begin(), temp.end()); - } catch (...) { - return {}; - } + const uint8_t* begin = reinterpret_cast(&value); + const uint8_t* end = begin + sizeof(value); + Data v(begin, end); + std::reverse(v.begin(), v.end()); + return hex(v); } /// Parses a string of hexadecimal values. @@ -89,9 +87,56 @@ inline Data parse_hex(const std::string& string, bool padLeft = false) { temp.erase(0, 2); } temp.insert(0, 1, '0'); - return parse_hex(temp.begin(), temp.end()); + return internal::parse_hex(temp); + } + return internal::parse_hex(string); +} + +inline const char* hex_char_to_bin(char c) { + switch (toupper(c)) { + case '0': + return "0000"; + case '1': + return "0001"; + case '2': + return "0010"; + case '3': + return "0011"; + case '4': + return "0100"; + case '5': + return "0101"; + case '6': + return "0110"; + case '7': + return "0111"; + case '8': + return "1000"; + case '9': + return "1001"; + case 'A': + return "1010"; + case 'B': + return "1011"; + case 'C': + return "1100"; + case 'D': + return "1101"; + case 'E': + return "1110"; + case 'F': + return "1111"; + default: + return ""; + } +} + +inline std::string hex_str_to_bin_str(const std::string& hex) { + std::stringstream ss; + for (auto&& c: hex) { + ss << hex_char_to_bin(c); } - return parse_hex(string.begin(), string.end()); + return ss.str(); } } // namespace TW diff --git a/src/IOST/Account.cpp b/src/IOST/Account.cpp new file mode 100644 index 00000000000..1aa3c91f5b7 --- /dev/null +++ b/src/IOST/Account.cpp @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Account.h" +#include "../Base58.h" +#include "../Base58Address.h" + +using namespace TW; +using namespace TW::IOST; + +bool isAccountValid(const std::string& account) { + if (account.size() < 5 || account.size() > 11) { + return false; + } + for (char ch : account) { + if ((ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '_') { + return false; + } + } + return true; +} + +bool isBase58AddressValid(const std::string& address) { + auto decoded = Base58::decode(address); + return decoded.size() == Base58Address<32>::size; +} + +bool Account::isValid(const std::string& s) { + return isAccountValid(s) ? true : isBase58AddressValid(s); +} + +std::string Account::encodePubKey(const PublicKey& publicKey) { + return Base58::encode(publicKey.bytes); +} + +Account::Account(const Proto::AccountInfo& account) { + name = account.name(); + if (account.active_key() != "") { + auto activeKeyBytesPtr = account.active_key().begin(); + activeKey = Data(activeKeyBytesPtr, activeKeyBytesPtr + PrivateKey::_size); + } + if (account.owner_key() != "") { + auto ownerKeyBytesPtr = account.owner_key().begin(); + ownerKey = Data(ownerKeyBytesPtr, ownerKeyBytesPtr + PrivateKey::_size); + } +} + +Data Account::sign(const Data& digest, TWCurve curve) const { + return PrivateKey(activeKey).sign(digest, curve); +} + +Data Account::publicActiveKey() const { + return PrivateKey(activeKey).getPublicKey(TWPublicKeyTypeED25519).bytes; +} + +Data Account::publicOwnerKey() const { + return PrivateKey(ownerKey).getPublicKey(TWPublicKeyTypeED25519).bytes; +} + +std::string Account::address(const std::string& publickey) { + auto publicKeyData = TW::data(publickey); + return Base58::encode(publicKeyData); +} diff --git a/src/IOST/Account.h b/src/IOST/Account.h new file mode 100644 index 00000000000..bc93493d2bb --- /dev/null +++ b/src/IOST/Account.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../PublicKey.h" +#include "../PrivateKey.h" +#include "../proto/IOST.pb.h" + +#include + +namespace TW::IOST { + +class Account { +public: + static bool isValid(const std::string& name); + static std::string encodePubKey(const PublicKey& publicKey); + static std::string address(const std::string& string); + Account(std::string name): name(std::move(name)) {} + Account([[maybe_unused]] const PublicKey& publicKey) {} + Account(const Proto::AccountInfo& account); + std::string string() const { return name; } + Data sign(const Data& digest, TWCurve curve) const; + Data publicActiveKey() const; + Data publicOwnerKey() const; + std::string name; + Data activeKey; + Data ownerKey; +}; + +inline bool operator==(const Account& lhs, const Account& rhs) { + return lhs.name == rhs.name; +} + +} // namespace TW::IOST + +/// Wrapper for C interface. +struct TWIOSTAccount { + TW::IOST::Account impl; +}; diff --git a/src/IOST/Entry.cpp b/src/IOST/Entry.cpp new file mode 100644 index 00000000000..c5f8d59dff6 --- /dev/null +++ b/src/IOST/Entry.cpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Entry.h" +#include "Account.h" +#include "Signer.h" +#include "../Base58.h" +#include "../proto/Common.pb.h" +#include "../proto/TransactionCompiler.pb.h" + +using namespace TW; +using namespace std; + +namespace TW::IOST { + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, [[maybe_unused]] const PrefixVariant& addressPrefixp) const { + return Account::isValid(address); +} + +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Account::encodePubKey(publicKey); +} + +TW::Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& str) const { + return {Base58::decode(str)}; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + const auto signer = Signer(input); + auto preImage = signer.signaturePreimage(); + auto preImageHash = Hash::sha3_256(preImage); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + const auto signer = Signer(input); + output = signer.compile(signature, publicKey); + }); +} + +} // namespace TW::IOST diff --git a/src/IOST/Entry.h b/src/IOST/Entry.h new file mode 100644 index 00000000000..3b187c6ed68 --- /dev/null +++ b/src/IOST/Entry.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../CoinEntry.h" + +namespace TW::IOST { + +/// Entry point for implementation of IOST coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific +/// includes in this file. +class Entry final : public CoinEntry { + public: + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefixp) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + Data addressToData(TWCoinType coin, const std::string& address) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, + const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const override; +}; + +} // namespace TW::IOST diff --git a/src/IOST/Signer.cpp b/src/IOST/Signer.cpp new file mode 100644 index 00000000000..645be9f610f --- /dev/null +++ b/src/IOST/Signer.cpp @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Signer.h" +#include "Account.h" +#include "../BinaryCoding.h" +#include +#include +#include + +using namespace TW; +using namespace TW::IOST; + +class IOSTEncoder { + public: + IOSTEncoder() = default; + void WriteByte(uint8_t b) { buffer << b; } + void WriteInt32(uint32_t i) { + std::vector data; + encode32BE(i, data); + for (auto b : data) { + buffer << b; + } + } + + void WriteInt64(uint64_t i) { + std::vector data; + encode64BE(i, data); + for (auto b : data) { + buffer << b; + } + } + + void WriteString(const std::string& s) { + WriteInt32(static_cast(s.size())); + buffer << s; + } + + void WriteStringSlice(const std::vector v) { + WriteInt32(static_cast(v.size())); + for (auto& s : v) { + WriteString(s); + } + } + + std::string AsString() { return buffer.str(); } + + private: + std::stringstream buffer; +}; + +std::string Signer::encodeTransaction(const Proto::Transaction& t) noexcept { + IOSTEncoder se; + se.WriteInt64(t.time()); + se.WriteInt64(t.expiration()); + se.WriteInt64(int64_t(t.gas_ratio() * 100)); + se.WriteInt64(int64_t(t.gas_limit() * 100)); + se.WriteInt64(t.delay()); + se.WriteInt32(int32_t(t.chain_id())); + se.WriteString(""); + + std::vector svec; + for (auto& item : t.signers()) { + svec.push_back(item); + } + se.WriteStringSlice(svec); + + se.WriteInt32(t.actions_size()); + for (auto& a : t.actions()) { + IOSTEncoder s; + s.WriteString(a.contract()); + s.WriteString(a.action_name()); + s.WriteString(a.data()); + se.WriteString(s.AsString()); + } + + se.WriteInt32(t.amount_limit_size()); + for (auto& a : t.amount_limit()) { + IOSTEncoder s; + s.WriteString(a.token()); + s.WriteString(a.value()); + se.WriteString(s.AsString()); + } + + se.WriteInt32(t.signatures_size()); + for (auto& sig : t.signatures()) { + IOSTEncoder s; + s.WriteByte(static_cast(sig.algorithm())); + s.WriteString(sig.signature()); + s.WriteString(sig.public_key()); + se.WriteString(s.AsString()); + } + + return se.AsString(); +} + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { + auto t = input.transaction_template(); + + if (t.actions_size() == 0) { + t.add_actions(); + auto* action = t.mutable_actions(0); + action->set_contract("token.iost"); + action->set_action_name("transfer"); + + std::string token = "iost"; + std::string src = input.account().name(); + std::string dst = input.transfer_destination(); + std::string amount = input.transfer_amount(); + std::string memo = input.transfer_memo(); + + nlohmann::json j; + j.push_back(token); + j.push_back(src); + j.push_back(dst); + j.push_back(amount); + j.push_back(memo); + std::string data = j.dump(); + action->set_data(data); + } + + Account acc(input.account()); + auto pubkey = acc.publicActiveKey(); + std::string pubkeyStr(pubkey.begin(), pubkey.end()); + + t.add_publisher_sigs(); + auto* sig = t.mutable_publisher_sigs(0); + sig->set_algorithm(Proto::Algorithm::ED25519); + sig->set_public_key(pubkeyStr); + auto signature = acc.sign(Hash::sha3_256(encodeTransaction(t)), TWCurveED25519); + std::string signatureStr(signature.begin(), signature.end()); + sig->set_signature(signatureStr); + + Proto::SigningOutput protoOutput; + protoOutput.mutable_transaction()->CopyFrom(t); + std::string transactionJson; + google::protobuf::util::JsonOptions jsonOptions; + jsonOptions.always_print_enums_as_ints = true; + jsonOptions.preserve_proto_field_names = true; + google::protobuf::util::MessageToJsonString(t, &transactionJson, jsonOptions); + protoOutput.set_encoded(transactionJson); + return protoOutput; +} + +Data Signer::signaturePreimage() const { + return data(encodeTransaction(input.transaction_template())); +} + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey) const { + Proto::SigningOutput output; + // validate public key + if (publicKey.type != TWPublicKeyTypeED25519) { + throw std::invalid_argument("Invalid public key"); + } + { + // validate correctness of signature + const auto hash = Hash::sha3_256(this->signaturePreimage()); + if (!publicKey.verify(signature, hash)) { + throw std::invalid_argument("Invalid signature/hash/publickey combination"); + } + } + auto t = input.transaction_template(); + + std::string pubkeyStr(publicKey.bytes.begin(), publicKey.bytes.end()); + + t.add_publisher_sigs(); + auto* sig = t.mutable_publisher_sigs(0); + sig->set_algorithm(Proto::Algorithm(Proto::ED25519)); + sig->set_public_key(pubkeyStr); + std::string signatureStr(signature.begin(), signature.end()); + sig->set_signature(signatureStr); + + std::string transactionJson; + google::protobuf::util::JsonOptions jsonOptions; + jsonOptions.always_print_enums_as_ints = true; + jsonOptions.preserve_proto_field_names = true; + google::protobuf::util::MessageToJsonString(t, &transactionJson, jsonOptions); + + output.mutable_transaction()->CopyFrom(t); + output.set_encoded(transactionJson); + return output; +} diff --git a/src/IOST/Signer.h b/src/IOST/Signer.h new file mode 100644 index 00000000000..229a9227160 --- /dev/null +++ b/src/IOST/Signer.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../PrivateKey.h" +#include "../proto/IOST.pb.h" + +namespace TW::IOST { +class Signer { + public: + Proto::SigningInput input; + + /// Initializes a transaction signer. + explicit Signer(const Proto::SigningInput& input) : input(input) {} + + static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + static std::string encodeTransaction(const Proto::Transaction& t) noexcept; + + Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) const; + Data signaturePreimage() const; +}; +} // namespace TW::IOST diff --git a/src/Icon/Address.cpp b/src/Icon/Address.cpp index 4c86ae521d9..15eaf1d7a02 100644 --- a/src/Icon/Address.cpp +++ b/src/Icon/Address.cpp @@ -1,28 +1,23 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" -#include "../Hash.h" #include "../HexCoding.h" -#include "../PrivateKey.h" #include -using namespace TW; -using namespace TW::Icon; +namespace TW::Icon { -static const std::string addressPrefix = "hx"; +static const std::string gAddressPrefix = "hx"; static const std::string contractPrefix = "cx"; bool Address::isValid(const std::string& string) { if (string.size() != Address::size * 2 + 2) { return false; } - if (!std::equal(addressPrefix.begin(), addressPrefix.end(), string.begin()) && + if (!std::equal(gAddressPrefix.begin(), gAddressPrefix.end(), string.begin()) && !std::equal(contractPrefix.begin(), contractPrefix.end(), string.begin())) { return false; } @@ -34,7 +29,7 @@ Address::Address(const std::string& string) { throw std::invalid_argument("Invalid address data"); } - if (std::equal(addressPrefix.begin(), addressPrefix.end(), string.begin())) { + if (std::equal(gAddressPrefix.begin(), gAddressPrefix.end(), string.begin())) { type = TypeAddress; } else if (std::equal(contractPrefix.begin(), contractPrefix.end(), string.begin())) { type = TypeContract; @@ -42,11 +37,12 @@ Address::Address(const std::string& string) { throw std::invalid_argument("Invalid address prefix"); } - const auto data = parse_hex(string.begin() + 2, string.end()); + const auto data = parse_hex(string.substr(2)); std::copy(data.begin(), data.end(), bytes.begin()); } -Address::Address(const PublicKey& publicKey, enum AddressType type) : type(type) { +Address::Address(const PublicKey& publicKey, enum AddressType type) + : type(type) { auto hash = std::array(); sha3_256(publicKey.bytes.data() + 1, publicKey.bytes.size() - 1, hash.data()); std::copy(hash.end() - Address::size, hash.end(), bytes.begin()); @@ -55,10 +51,12 @@ Address::Address(const PublicKey& publicKey, enum AddressType type) : type(type) std::string Address::string() const { switch (type) { case TypeAddress: - return addressPrefix + hex(bytes); + return gAddressPrefix + hex(bytes); case TypeContract: return contractPrefix + hex(bytes); default: return ""; } } + +} // namespace TW::Icon diff --git a/src/Icon/Address.h b/src/Icon/Address.h index 1c2a8645f1c..19da238ac78 100644 --- a/src/Icon/Address.h +++ b/src/Icon/Address.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -23,7 +21,7 @@ class Address { /// Address data consisting of a prefix byte followed by the public key /// hash. - std::array bytes; + std::array bytes{}; /// Address type. enum AddressType type; diff --git a/src/Icon/AddressType.h b/src/Icon/AddressType.h index aea709cbabf..9823182cde5 100644 --- a/src/Icon/AddressType.h +++ b/src/Icon/AddressType.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Icon/Entry.cpp b/src/Icon/Entry.cpp index 2786ebd14b1..d9e415dbd5b 100644 --- a/src/Icon/Entry.cpp +++ b/src/Icon/Entry.cpp @@ -1,27 +1,56 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" +#include "../proto/TransactionCompiler.pb.h" #include "Address.h" #include "Signer.h" -using namespace TW::Icon; -using namespace std; +namespace TW::Icon { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey, Icon::TypeAddress).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + Signer signer(input); + auto preImage = signer.preImage(); + auto preImageHash = signer.hashImage(Data(preImage.begin(), preImage.end())); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, [[maybe_unused]] const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + if (signatures.size() > 1) { + output.set_error(Common::Proto::Error_no_support_n2n); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_no_support_n2n)); + return; + } + auto signedTx = Signer(input).encode(signatures[0]); + output.set_encoded(signedTx.data(), signedTx.size()); + }); +} + +} // namespace TW::Icon diff --git a/src/Icon/Entry.h b/src/Icon/Entry.h index 79ad0bd89f8..3983aea1b5f 100644 --- a/src/Icon/Entry.h +++ b/src/Icon/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,11 +10,14 @@ namespace TW::Icon { /// Entry point for implementation of ICON coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Icon diff --git a/src/Icon/Signer.cpp b/src/Icon/Signer.cpp index 9028f327d3c..e537a72740d 100644 --- a/src/Icon/Signer.cpp +++ b/src/Icon/Signer.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" @@ -10,17 +8,13 @@ #include "../Hash.h" #include "../HexCoding.h" #include "../PrivateKey.h" -#include "../uint256.h" -#include #include #include -#include #include -using namespace TW; -using namespace TW::Icon; +namespace TW::Icon { std::string to_hex(int64_t i) { std::stringstream ss; @@ -59,6 +53,10 @@ std::string Signer::preImage() const noexcept { return txHash; } +TW::Data Signer::hashImage(const Data& image) const { + return Hash::sha3_256(image); +} + std::string Signer::encode(const Data& signature) const noexcept { auto json = nlohmann::json(); json["from"] = input.from_address(); @@ -92,3 +90,5 @@ Proto::SigningOutput Signer::sign() const noexcept { return output; } + +} // namespace TW::Icon diff --git a/src/Icon/Signer.h b/src/Icon/Signer.h index 987ae90e546..73c6d518d88 100644 --- a/src/Icon/Signer.h +++ b/src/Icon/Signer.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../proto/Icon.pb.h" #include @@ -32,6 +30,8 @@ class Signer { /// Encodes a signed transaction as JSON. std::string encode(const Data& signature) const noexcept; + TW::Data hashImage(const Data& image) const; + private: std::map parameters() const noexcept; }; diff --git a/src/ImmutableX/Constants.h b/src/ImmutableX/Constants.h new file mode 100644 index 00000000000..f1b1e485643 --- /dev/null +++ b/src/ImmutableX/Constants.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "uint256.h" + +namespace TW::ImmutableX { + +namespace internal { +inline constexpr const char* gLayer = "starkex"; +inline constexpr const char* gApplication = "immutablex"; +inline constexpr const char* gIndex = "1"; +inline const int256_t gStarkCurveA(1); +inline const int256_t gStarkCurveB("3141592653589793238462643383279502884197169399375105820974944592307816406665"); +inline const int256_t gStarkCurveN("3618502788666131213697322783095070105526743751716087489154079457884512865583"); +inline const int256_t gStarkCurveP("3618502788666131213697322783095070105623107215331596699973092056135872020481"); +inline const int256_t gStarkDeriveBias("112173586448650067624617006275947173271329056303198712163776463194419898833073"); +} // namespace internal + +} // namespace TW::ImmutableX diff --git a/src/ImmutableX/StarkKey.cpp b/src/ImmutableX/StarkKey.cpp new file mode 100644 index 00000000000..51dec1d669f --- /dev/null +++ b/src/ImmutableX/StarkKey.cpp @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include +#include +#include +#include + +namespace TW::ImmutableX { + +uint256_t hashKeyWithIndex(const Data& seed, std::size_t index) { + auto data = seed; + data.push_back(static_cast(index)); + auto out = Hash::sha256(data); + return load(out); +} + +std::string grindKey(const Data& seed) { + std::size_t index{0}; + int256_t key = hashKeyWithIndex(seed, index); + while (key >= internal::gStarkDeriveBias) { + key = hashKeyWithIndex(store(uint256_t(key)), index); + index += 1; + } + auto finalKey = key % internal::gStarkCurveN; + std::stringstream ss; + ss << std::hex << finalKey; + return ss.str(); +} + +PrivateKey getPrivateKeyFromSeed(const Data& seed, const DerivationPath& path) { + auto key = HDWallet<32>::bip32DeriveRawSeed(TWCoinTypeEthereum, seed, path); + auto data = parse_hex(grindKey(key.bytes), true); + return PrivateKey(data); +} + +PrivateKey getPrivateKeyFromEthPrivKey(const PrivateKey& ethPrivKey) { + return PrivateKey(parse_hex(ImmutableX::grindKey(ethPrivKey.bytes), true)); +} + +PrivateKey getPrivateKeyFromRawSignature(const Data& signature, const DerivationPath& derivationPath) { + using namespace internal; + // The signature is `rsv`, where `s` starts at 32 and is 32 long. + auto seed = subData(signature, 32, 32); + return getPrivateKeyFromSeed(seed, derivationPath); +} + +} // namespace TW::ImmutableX diff --git a/src/ImmutableX/StarkKey.h b/src/ImmutableX/StarkKey.h new file mode 100644 index 00000000000..d5702ede3a1 --- /dev/null +++ b/src/ImmutableX/StarkKey.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "uint256.h" +#include +#include +#include + +namespace TW::ImmutableX { + +uint256_t hashKeyWithIndex(const Data& seed, std::size_t index); + +std::string grindKey(const Data& seed); + +PrivateKey getPrivateKeyFromSeed(const std::string& seed, const DerivationPath& path); + +PrivateKey getPrivateKeyFromEthPrivKey(const PrivateKey& ethPrivKey); + +PrivateKey getPrivateKeyFromRawSignature(const Data& signature, const DerivationPath& derivationPath); + +} // namespace TW::ImmutableX diff --git a/src/InternetComputer/Entry.h b/src/InternetComputer/Entry.h new file mode 100644 index 00000000000..bf879432460 --- /dev/null +++ b/src/InternetComputer/Entry.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "rust/RustCoinEntry.h" + +namespace TW::InternetComputer { + +/// Entry point for implementation of InternetComputer coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final : public Rust::RustCoinEntry { +}; + +} // namespace TW::InternetComputer diff --git a/src/IoTeX/Address.cpp b/src/IoTeX/Address.cpp index 606af156391..8e387be97c8 100644 --- a/src/IoTeX/Address.cpp +++ b/src/IoTeX/Address.cpp @@ -1,13 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" #include -using namespace TW::IoTeX; +namespace TW::IoTeX { const std::string Address::hrp = HRP_IOTEX; + +} diff --git a/src/IoTeX/Address.h b/src/IoTeX/Address.h index 5dbf03b4a39..355c63e917c 100644 --- a/src/IoTeX/Address.h +++ b/src/IoTeX/Address.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/IoTeX/Entry.cpp b/src/IoTeX/Entry.cpp index dc4c73dcc9d..2f25cebbd00 100644 --- a/src/IoTeX/Entry.cpp +++ b/src/IoTeX/Entry.cpp @@ -1,27 +1,48 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" #include "Signer.h" +#include "../proto/TransactionCompiler.pb.h" -using namespace TW::IoTeX; using namespace std; +namespace TW::IoTeX { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + Signer signer(input); + auto signHash = signer.hash(); + auto preImage = signer.signaturePreimage(); + output.set_data(preImage.data(), preImage.size()); + output.set_data_hash(signHash.data(), signHash.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + output = Signer::compile(input, signature, publicKey); + }); +} +} // namespace TW::IoTeX diff --git a/src/IoTeX/Entry.h b/src/IoTeX/Entry.h index f5a5453fb7f..3b56c8da833 100644 --- a/src/IoTeX/Entry.h +++ b/src/IoTeX/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,11 +10,14 @@ namespace TW::IoTeX { /// Entry point for implementation of IoTeX coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::IoTeX diff --git a/src/IoTeX/Signer.cpp b/src/IoTeX/Signer.cpp index 6b0e6d6ec1a..75c0d2cf7a6 100644 --- a/src/IoTeX/Signer.cpp +++ b/src/IoTeX/Signer.cpp @@ -1,17 +1,14 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "Hash.h" -#include "HexCoding.h" -#include "IoTeX/Staking.h" #include "PrivateKey.h" using namespace TW; -using namespace TW::IoTeX; + +namespace TW::IoTeX { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(input); @@ -40,11 +37,34 @@ Proto::SigningOutput Signer::build() const { return output; } +Proto::SigningOutput Signer::compile(const Proto::SigningInput& input, const Data& signature, + const TW::PublicKey& pubKey) noexcept { + auto signer = Signer(input); + auto signedAction = Proto::Action(); + signedAction.mutable_core()->MergeFrom(signer.action); + + signedAction.set_senderpubkey(pubKey.bytes.data(), pubKey.bytes.size()); + signedAction.set_signature(signature.data(), signature.size()); + // build output + auto output = Proto::SigningOutput(); + auto serialized = signedAction.SerializeAsString(); + output.set_encoded(serialized); + auto h = Hash::keccak256(serialized); + output.set_hash(h.data(), h.size()); + return output; +} + Data Signer::hash() const { return Hash::keccak256(action.SerializeAsString()); } +std::string Signer::signaturePreimage() const { + return action.SerializeAsString(); +} + void Signer::toActionCore() { action.ParseFromString(input.SerializeAsString()); action.DiscardUnknownFields(); } + +} // namespace TW::IoTeX diff --git a/src/IoTeX/Signer.h b/src/IoTeX/Signer.h index 31fc2c8a15f..217c676a1c5 100644 --- a/src/IoTeX/Signer.h +++ b/src/IoTeX/Signer.h @@ -1,14 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Data.h" - #include "proto/IoTeX.pb.h" +#include "../PrivateKey.h" namespace TW::IoTeX { @@ -17,6 +15,8 @@ class Signer { public: /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + /// Build the compile output + static Proto::SigningOutput compile(const Proto::SigningInput& input, const Data& signature, const TW::PublicKey& pubKey) noexcept; public: Proto::SigningInput input; Proto::ActionCore action; @@ -36,7 +36,8 @@ class Signer { /// Computes the transaction hash Data hash() const; - + /// Get PreImage transaction data + std::string signaturePreimage() const; protected: /// Converts to proto ActionCore from transaction input void toActionCore(); diff --git a/src/IoTeX/Staking.cpp b/src/IoTeX/Staking.cpp index a508c859c1e..4675d899e44 100644 --- a/src/IoTeX/Staking.cpp +++ b/src/IoTeX/Staking.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Staking.h" #include "Data.h" diff --git a/src/IoTeX/Staking.h b/src/IoTeX/Staking.h index 73a5e737473..0e716648925 100644 --- a/src/IoTeX/Staking.h +++ b/src/IoTeX/Staking.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/KeyPair.h b/src/KeyPair.h index db5627b88b6..d7387c3db2d 100644 --- a/src/KeyPair.h +++ b/src/KeyPair.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Keystore/AESParameters.cpp b/src/Keystore/AESParameters.cpp index 146e1a12727..4d8a47eef09 100644 --- a/src/Keystore/AESParameters.cpp +++ b/src/Keystore/AESParameters.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "AESParameters.h" @@ -11,20 +9,45 @@ #include using namespace TW; -using namespace TW::Keystore; -AESParameters::AESParameters() { - iv = Data(blockSize, 0); +namespace { + +Data generateIv(std::size_t blockSize = TW::Keystore::gBlockSize) { + auto iv = Data(blockSize, 0); random_buffer(iv.data(), blockSize); + return iv; +} + +static TWStoredKeyEncryption getCipher(const std::string& cipher) { + if (cipher == Keystore::gAes128Ctr) { + return TWStoredKeyEncryption::TWStoredKeyEncryptionAes128Ctr; + } else if (cipher == Keystore::gAes192Ctr) { + return TWStoredKeyEncryption::TWStoredKeyEncryptionAes192Ctr; + } else if (cipher == Keystore::gAes256Ctr) { + return TWStoredKeyEncryption::TWStoredKeyEncryptionAes256Ctr; + } + return TWStoredKeyEncryptionAes128Ctr; } +const std::unordered_map gEncryptionRegistry{ + {TWStoredKeyEncryptionAes128Ctr, Keystore::AESParameters{.mKeyLength = Keystore::A128, .mCipher = Keystore::gAes128Ctr, .mCipherEncryption = TWStoredKeyEncryptionAes128Ctr}}, + {TWStoredKeyEncryptionAes128Cbc, Keystore::AESParameters{.mKeyLength = Keystore::A128, .mCipher = Keystore::gAes128Cbc, .mCipherEncryption = TWStoredKeyEncryptionAes128Cbc}}, + {TWStoredKeyEncryptionAes192Ctr, Keystore::AESParameters{.mKeyLength = Keystore::A192, .mCipher = Keystore::gAes192Ctr, .mCipherEncryption = TWStoredKeyEncryptionAes192Ctr}}, + {TWStoredKeyEncryptionAes256Ctr, Keystore::AESParameters{.mKeyLength = Keystore::A256, .mCipher = Keystore::gAes256Ctr, .mCipherEncryption = TWStoredKeyEncryptionAes256Ctr}} +}; +} // namespace + +namespace TW::Keystore { + namespace CodingKeys { static const auto iv = "iv"; } // namespace CodingKeys /// Initializes `AESParameters` with a JSON object. -AESParameters::AESParameters(const nlohmann::json& json) { - iv = parse_hex(json[CodingKeys::iv].get()); +AESParameters AESParameters::AESParametersFromJson(const nlohmann::json& json, const std::string& cipher) { + auto parameters = AESParameters::AESParametersFromEncryption(getCipher(cipher)); + parameters.iv = parse_hex(json[CodingKeys::iv].get()); + return parameters; } /// Saves `this` as a JSON object. @@ -33,3 +56,12 @@ nlohmann::json AESParameters::json() const { j[CodingKeys::iv] = hex(iv); return j; } + +AESParameters AESParameters::AESParametersFromEncryption(TWStoredKeyEncryption encryption) { + auto parameters = gEncryptionRegistry.at(encryption); + // be sure to regenerate an iv. + parameters.iv = generateIv(); + return parameters; +} + +} // namespace TW::Keystore diff --git a/src/Keystore/AESParameters.h b/src/Keystore/AESParameters.h index 5f6b994157c..46b49f9ee6c 100644 --- a/src/Keystore/AESParameters.h +++ b/src/Keystore/AESParameters.h @@ -1,28 +1,43 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include +#include namespace TW::Keystore { -// AES128 parameters. -struct AESParameters { - static const std::size_t blockSize = 128 / 8; +enum AESKeySize : std::int32_t { + Uninitialized = 0, + A128 = 16, + A192 = 24, + A256 = 32, +}; +inline constexpr std::size_t gBlockSize{16}; +inline constexpr const char* gAes128Ctr{"aes-128-ctr"}; +inline constexpr const char* gAes128Cbc{"aes-128-cbc"}; +inline constexpr const char* gAes192Ctr{"aes-192-ctr"}; +inline constexpr const char* gAes256Ctr{"aes-256-ctr"}; + +// AES128/192/256 parameters. +struct AESParameters { + // For AES, your block length is always going to be 128 bits/16 bytes + std::int32_t mBlockSize{gBlockSize}; + std::int32_t mKeyLength{A128}; + std::string mCipher{gAes128Ctr}; + TWStoredKeyEncryption mCipherEncryption{TWStoredKeyEncryptionAes128Ctr}; Data iv; - /// Initializes `AESParameters` with a random `iv` for AES 128. - AESParameters(); + /// Initializes `AESParameters` with a encryption cipher. + static AESParameters AESParametersFromEncryption(TWStoredKeyEncryption encryption);; /// Initializes `AESParameters` with a JSON object. - AESParameters(const nlohmann::json& json); + static AESParameters AESParametersFromJson(const nlohmann::json& json, const std::string& cipher); /// Saves `this` as a JSON object. nlohmann::json json() const; diff --git a/src/Keystore/Account.cpp b/src/Keystore/Account.cpp index 05fe5e427e8..c97b8eb66d1 100644 --- a/src/Keystore/Account.cpp +++ b/src/Keystore/Account.cpp @@ -1,28 +1,26 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Account.h" #include "../Base64.h" #include "../Coin.h" -#include "../HexCoding.h" using namespace TW; -using namespace TW::Keystore; + +namespace TW::Keystore { namespace CodingKeys { - static const auto address = "address"; - static const auto derivation = "derivation"; - static const auto derivationPath = "derivationPath"; - static const auto extendedPublicKey = "extendedPublicKey"; - static const auto indices = "indices"; - static const auto value = "value"; - static const auto hardened = "hardened"; - static const auto coin = "coin"; - static const auto publicKey = "publicKey"; +static const auto address = "address"; +static const auto derivation = "derivation"; +static const auto derivationPath = "derivationPath"; +static const auto extendedPublicKey = "extendedPublicKey"; +static const auto indices = "indices"; +static const auto value = "value"; +static const auto hardened = "hardened"; +static const auto coin = "coin"; +static const auto publicKey = "publicKey"; } // namespace CodingKeys Account::Account(const nlohmann::json& json) { @@ -80,3 +78,5 @@ nlohmann::json Account::json() const { } return j; } + +} // namespace TW::Keystore diff --git a/src/Keystore/Account.h b/src/Keystore/Account.h index 6c48ec8723f..22a55c5d48d 100644 --- a/src/Keystore/Account.h +++ b/src/Keystore/Account.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Keystore/EncryptionParameters.cpp b/src/Keystore/EncryptionParameters.cpp index 406474e638a..db34105c024 100644 --- a/src/Keystore/EncryptionParameters.cpp +++ b/src/Keystore/EncryptionParameters.cpp @@ -1,23 +1,19 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "EncryptionParameters.h" #include "../Hash.h" -#include "../HexCoding.h" #include #include #include - -#include #include using namespace TW; -using namespace TW::Keystore; + +namespace TW::Keystore { template static Data computeMAC(Iter begin, Iter end, const Data& key) { @@ -42,8 +38,8 @@ static const auto mac = "mac"; } // namespace CodingKeys EncryptionParameters::EncryptionParameters(const nlohmann::json& json) { - cipher = json[CodingKeys::cipher].get(); - cipherParams = AESParameters(json[CodingKeys::cipherParams]); + auto cipher = json[CodingKeys::cipher].get(); + cipherParams = AESParameters::AESParametersFromJson(json[CodingKeys::cipherParams], cipher); auto kdf = json[CodingKeys::kdf].get(); if (kdf == "scrypt") { @@ -55,89 +51,97 @@ EncryptionParameters::EncryptionParameters(const nlohmann::json& json) { nlohmann::json EncryptionParameters::json() const { nlohmann::json j; - j[CodingKeys::cipher] = cipher; + j[CodingKeys::cipher] = cipher(); j[CodingKeys::cipherParams] = cipherParams.json(); - if (kdfParams.which() == 0) { - auto scryptParams = boost::get(kdfParams); + if (auto* scryptParams = std::get_if(&kdfParams); scryptParams) { j[CodingKeys::kdf] = "scrypt"; - j[CodingKeys::kdfParams] = scryptParams.json(); - } else if (kdfParams.which() == 1) { - auto pbkdf2Params = boost::get(kdfParams); + j[CodingKeys::kdfParams] = scryptParams->json(); + } else if (auto* pbkdf2Params = std::get_if(&kdfParams); pbkdf2Params) { j[CodingKeys::kdf] = "pbkdf2"; - j[CodingKeys::kdfParams] = pbkdf2Params.json(); + j[CodingKeys::kdfParams] = pbkdf2Params->json(); } return j; } -EncryptedPayload::EncryptedPayload(const Data& password, const Data& data, const EncryptionParameters& params) : - params(std::move(params)), mac() { - auto scryptParams = boost::get(params.kdfParams); +EncryptedPayload::EncryptedPayload(const Data& password, const Data& data, const EncryptionParameters& params) + : params(std::move(params)), _mac() { + auto scryptParams = std::get(this->params.kdfParams); auto derivedKey = Data(scryptParams.desiredKeyLength); scrypt(reinterpret_cast(password.data()), password.size(), scryptParams.salt.data(), scryptParams.salt.size(), scryptParams.n, scryptParams.r, scryptParams.p, derivedKey.data(), scryptParams.desiredKeyLength); aes_encrypt_ctx ctx; - auto result = aes_encrypt_key128(derivedKey.data(), &ctx); + auto result = 0; + switch(this->params.cipherParams.mCipherEncryption) { + case TWStoredKeyEncryptionAes128Ctr: + case TWStoredKeyEncryptionAes128Cbc: + result = aes_encrypt_key128(derivedKey.data(), &ctx); + break; + case TWStoredKeyEncryptionAes192Ctr: + result = aes_encrypt_key192(derivedKey.data(), &ctx); + break; + case TWStoredKeyEncryptionAes256Ctr: + result = aes_encrypt_key256(derivedKey.data(), &ctx); + break; + } assert(result == EXIT_SUCCESS); if (result == EXIT_SUCCESS) { - Data iv = params.cipherParams.iv; + Data iv = this->params.cipherParams.iv; encrypted = Data(data.size()); aes_ctr_encrypt(data.data(), encrypted.data(), static_cast(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); - - mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); + _mac = computeMAC(derivedKey.end() - params.getKeyBytesSize(), derivedKey.end(), encrypted); } } EncryptedPayload::~EncryptedPayload() { std::fill(encrypted.begin(), encrypted.end(), 0); - std::fill(mac.begin(), mac.end(), 0); + std::fill(_mac.begin(), _mac.end(), 0); } Data EncryptedPayload::decrypt(const Data& password) const { auto derivedKey = Data(); auto mac = Data(); - if (params.kdfParams.which() == 0) { - auto scryptParams = boost::get(params.kdfParams); - derivedKey.resize(scryptParams.defaultDesiredKeyLength); - scrypt(password.data(), password.size(), scryptParams.salt.data(), - scryptParams.salt.size(), scryptParams.n, scryptParams.r, scryptParams.p, derivedKey.data(), - scryptParams.defaultDesiredKeyLength); - mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); - } else if (params.kdfParams.which() == 1) { - auto pbkdf2Params = boost::get(params.kdfParams); - derivedKey.resize(pbkdf2Params.defaultDesiredKeyLength); - pbkdf2_hmac_sha256(password.data(), static_cast(password.size()), pbkdf2Params.salt.data(), - static_cast(pbkdf2Params.salt.size()), pbkdf2Params.iterations, derivedKey.data(), - pbkdf2Params.defaultDesiredKeyLength); - mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); + if (auto* scryptParams = std::get_if(¶ms.kdfParams); scryptParams) { + derivedKey.resize(scryptParams->defaultDesiredKeyLength); + scrypt(password.data(), password.size(), scryptParams->salt.data(), + scryptParams->salt.size(), scryptParams->n, scryptParams->r, scryptParams->p, derivedKey.data(), + scryptParams->defaultDesiredKeyLength); + mac = computeMAC(derivedKey.end() - params.getKeyBytesSize(), derivedKey.end(), encrypted); + } else if (auto* pbkdf2Params = std::get_if(¶ms.kdfParams); pbkdf2Params) { + derivedKey.resize(pbkdf2Params->defaultDesiredKeyLength); + pbkdf2_hmac_sha256(password.data(), static_cast(password.size()), pbkdf2Params->salt.data(), + static_cast(pbkdf2Params->salt.size()), pbkdf2Params->iterations, derivedKey.data(), + pbkdf2Params->defaultDesiredKeyLength); + mac = computeMAC(derivedKey.end() - params.getKeyBytesSize(), derivedKey.end(), encrypted); } else { throw DecryptionError::unsupportedKDF; } - if (mac != this->mac) { + if (mac != _mac) { throw DecryptionError::invalidPassword; } Data decrypted(encrypted.size()); Data iv = params.cipherParams.iv; - if (params.cipher == "aes-128-ctr") { + const auto encryption = params.cipherParams.mCipherEncryption; + if (encryption == TWStoredKeyEncryptionAes128Ctr || encryption == TWStoredKeyEncryptionAes256Ctr) { aes_encrypt_ctx ctx; - auto __attribute__((unused)) result = aes_encrypt_key(derivedKey.data(), 16, &ctx); + [[maybe_unused]] auto result = aes_encrypt_key(derivedKey.data(), params.getKeyBytesSize(), &ctx); assert(result != EXIT_FAILURE); aes_ctr_decrypt(encrypted.data(), decrypted.data(), static_cast(encrypted.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); - } else if (params.cipher == "aes-128-cbc") { + } else if (encryption == TWStoredKeyEncryptionAes128Cbc) { aes_decrypt_ctx ctx; - auto __attribute__((unused)) result = aes_decrypt_key(derivedKey.data(), 16, &ctx); + [[maybe_unused]] auto result = aes_decrypt_key(derivedKey.data(), params.getKeyBytesSize(), &ctx); assert(result != EXIT_FAILURE); - for (auto i = 0; i < encrypted.size(); i += 16) { - aes_cbc_decrypt(encrypted.data() + i, decrypted.data() + i, 16, iv.data(), &ctx); + for (auto i = 0ul; i < encrypted.size(); i += params.getKeyBytesSize()) { + aes_cbc_decrypt(encrypted.data() + i, decrypted.data() + i, params.getKeyBytesSize(), iv.data(), &ctx); } } else { throw DecryptionError::unsupportedCipher; @@ -149,12 +153,14 @@ Data EncryptedPayload::decrypt(const Data& password) const { EncryptedPayload::EncryptedPayload(const nlohmann::json& json) { params = EncryptionParameters(json); encrypted = parse_hex(json[CodingKeys::encrypted].get()); - mac = parse_hex(json[CodingKeys::mac].get()); + _mac = parse_hex(json[CodingKeys::mac].get()); } nlohmann::json EncryptedPayload::json() const { nlohmann::json j = params.json(); j[CodingKeys::encrypted] = hex(encrypted); - j[CodingKeys::mac] = hex(mac); + j[CodingKeys::mac] = hex(_mac); return j; } + +} // namespace TW::Keystore diff --git a/src/Keystore/EncryptionParameters.h b/src/Keystore/EncryptionParameters.h index 2f7463ff598..711700d7aec 100644 --- a/src/Keystore/EncryptionParameters.h +++ b/src/Keystore/EncryptionParameters.h @@ -1,53 +1,57 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "AESParameters.h" +#include "Data.h" #include "PBKDF2Parameters.h" #include "ScryptParameters.h" -#include "../Data.h" +#include #include -#include #include #include +#include namespace TW::Keystore { /// Set of parameters used when encoding struct EncryptionParameters { - static EncryptionParameters getPreset(enum TWStoredKeyEncryptionLevel preset) { + static EncryptionParameters getPreset(enum TWStoredKeyEncryptionLevel preset, enum TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr) { switch (preset) { - case TWStoredKeyEncryptionLevelMinimal: - return EncryptionParameters(AESParameters(), ScryptParameters::Minimal); - case TWStoredKeyEncryptionLevelWeak: - case TWStoredKeyEncryptionLevelDefault: - default: - return EncryptionParameters(AESParameters(), ScryptParameters::Weak); - case TWStoredKeyEncryptionLevelStandard: - return EncryptionParameters(AESParameters(), ScryptParameters::Standard); + case TWStoredKeyEncryptionLevelMinimal: + return EncryptionParameters(AESParameters::AESParametersFromEncryption(encryption), ScryptParameters::Minimal); + case TWStoredKeyEncryptionLevelWeak: + case TWStoredKeyEncryptionLevelDefault: + default: + return EncryptionParameters(AESParameters::AESParametersFromEncryption(encryption), ScryptParameters::Weak); + case TWStoredKeyEncryptionLevelStandard: + return EncryptionParameters(AESParameters::AESParametersFromEncryption(encryption), ScryptParameters::Standard); } } - /// Cipher algorithm. - std::string cipher = "aes-128-ctr"; + std::int32_t getKeyBytesSize() const noexcept { + return cipherParams.mKeyLength; + } + + std::string cipher() const noexcept { + return cipherParams.mCipher; + } /// Cipher parameters. AESParameters cipherParams = AESParameters(); /// Key derivation function parameters. - boost::variant kdfParams = ScryptParameters(); + std::variant kdfParams = ScryptParameters(); EncryptionParameters() = default; /// Initializes with standard values. - EncryptionParameters(AESParameters cipherParams, boost::variant kdfParams) - : cipherParams(std::move(cipherParams)) - , kdfParams(std::move(kdfParams)) {} + EncryptionParameters(AESParameters cipherParams, std::variant kdfParams) + : cipherParams(std::move(cipherParams)), kdfParams(std::move(kdfParams)) { + } /// Initializes with a JSON object. EncryptionParameters(const nlohmann::json& json); @@ -82,7 +86,7 @@ struct EncryptedPayload { Data encrypted; /// Message authentication code. - Data mac; + Data _mac; EncryptedPayload() = default; @@ -90,7 +94,7 @@ struct EncryptedPayload { EncryptedPayload(const EncryptionParameters& params, const Data& encrypted, const Data& mac) : params(std::move(params)) , encrypted(std::move(encrypted)) - , mac(std::move(mac)) {} + , _mac(std::move(mac)) {} /// Initializes by encrypting data with a password /// using standard values. diff --git a/src/Keystore/PBKDF2Parameters.cpp b/src/Keystore/PBKDF2Parameters.cpp index fef2ebe5197..e965e7bba16 100644 --- a/src/Keystore/PBKDF2Parameters.cpp +++ b/src/Keystore/PBKDF2Parameters.cpp @@ -1,18 +1,17 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "PBKDF2Parameters.h" #include -#include using namespace TW; -using namespace TW::Keystore; -PBKDF2Parameters::PBKDF2Parameters() : salt(32) { +namespace TW::Keystore { + +PBKDF2Parameters::PBKDF2Parameters() + : salt(32) { random_buffer(salt.data(), salt.size()); } @@ -41,3 +40,5 @@ nlohmann::json PBKDF2Parameters::json() const { j[CodingKeys::iterations] = iterations; return j; } + +} // namespace TW::Keystore diff --git a/src/Keystore/PBKDF2Parameters.h b/src/Keystore/PBKDF2Parameters.h index 0d473b5615b..1af9ade0ca9 100644 --- a/src/Keystore/PBKDF2Parameters.h +++ b/src/Keystore/PBKDF2Parameters.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../HexCoding.h" #include diff --git a/src/Keystore/ScryptParameters.cpp b/src/Keystore/ScryptParameters.cpp index defc8788bb9..6f4bedf9a4d 100644 --- a/src/Keystore/ScryptParameters.cpp +++ b/src/Keystore/ScryptParameters.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "ScryptParameters.h" @@ -10,20 +8,22 @@ #include using namespace TW; -using namespace TW::Keystore; + +namespace TW::Keystore { ScryptParameters ScryptParameters::Minimal = ScryptParameters(Data(), minimalN, defaultR, minimalP, defaultDesiredKeyLength); ScryptParameters ScryptParameters::Weak = ScryptParameters(Data(), weakN, defaultR, weakP, defaultDesiredKeyLength); ScryptParameters ScryptParameters::Standard = ScryptParameters(Data(), standardN, defaultR, standardP, defaultDesiredKeyLength); -ScryptParameters::ScryptParameters() : salt(32) { +ScryptParameters::ScryptParameters() + : salt(32) { random_buffer(salt.data(), salt.size()); } #pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" std::optional ScryptParameters::validate() const { - if (desiredKeyLength > ((1ULL << 32) - 1) * 32) { // depending on size_t size on platform, may be always false + if (desiredKeyLength > ((1ULL << 32) - 1) * 32) { // depending on size_t size on platform, may be always false return ScryptValidationError::desiredKeyLengthTooLarge; } if (static_cast(r) * static_cast(p) >= (1 << 30)) { @@ -43,32 +43,36 @@ std::optional ScryptParameters::validate() const { // Encoding/Decoding // ----------------- -namespace CodingKeys { +namespace CodingKeys::SP { + static const auto salt = "salt"; static const auto desiredKeyLength = "dklen"; static const auto n = "n"; static const auto p = "p"; static const auto r = "r"; -} // namespace CodingKeys + +} // namespace CodingKeys::SP ScryptParameters::ScryptParameters(const nlohmann::json& json) { - salt = parse_hex(json[CodingKeys::salt].get()); - desiredKeyLength = json[CodingKeys::desiredKeyLength]; - if (json.count(CodingKeys::n) != 0) - n = json[CodingKeys::n]; - if (json.count(CodingKeys::n) != 0) - p = json[CodingKeys::p]; - if (json.count(CodingKeys::n) != 0) - r = json[CodingKeys::r]; + salt = parse_hex(json[CodingKeys::SP::salt].get()); + desiredKeyLength = json[CodingKeys::SP::desiredKeyLength]; + if (json.count(CodingKeys::SP::n) != 0) + n = json[CodingKeys::SP::n]; + if (json.count(CodingKeys::SP::n) != 0) + p = json[CodingKeys::SP::p]; + if (json.count(CodingKeys::SP::n) != 0) + r = json[CodingKeys::SP::r]; } /// Saves `this` as a JSON object. nlohmann::json ScryptParameters::json() const { nlohmann::json j; - j[CodingKeys::salt] = hex(salt); - j[CodingKeys::desiredKeyLength] = desiredKeyLength; - j[CodingKeys::n] = n; - j[CodingKeys::p] = p; - j[CodingKeys::r] = r; + j[CodingKeys::SP::salt] = hex(salt); + j[CodingKeys::SP::desiredKeyLength] = desiredKeyLength; + j[CodingKeys::SP::n] = n; + j[CodingKeys::SP::p] = p; + j[CodingKeys::SP::r] = r; return j; } + +} // namespace TW::Keystore diff --git a/src/Keystore/ScryptParameters.h b/src/Keystore/ScryptParameters.h index 541d4419e43..94cf3f1b1f2 100644 --- a/src/Keystore/ScryptParameters.h +++ b/src/Keystore/ScryptParameters.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../HexCoding.h" #include diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index 11b92034efb..b216461fe31 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "StoredKey.h" @@ -11,61 +9,56 @@ #include "Mnemonic.h" #include "PrivateKey.h" -#define BOOST_UUID_RANDOM_PROVIDER_FORCE_POSIX 1 - -#include -#include -#include -#include #include +#include +#include #include -#include -#include #include -#include using namespace TW; -using namespace TW::Keystore; -StoredKey StoredKey::createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic, TWStoredKeyEncryptionLevel encryptionLevel) { +namespace TW::Keystore { + +StoredKey StoredKey::createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption) { if (!Mnemonic::isValid(mnemonic)) { throw std::invalid_argument("Invalid mnemonic"); } - + Data mnemonicData = TW::Data(mnemonic.begin(), mnemonic.end()); - StoredKey key = StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData, encryptionLevel); + StoredKey key(StoredKeyType::mnemonicPhrase, name, password, mnemonicData, encryptionLevel, encryption); + memzero(mnemonicData.data(), mnemonic.size()); return key; } -StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const Data& password, TWStoredKeyEncryptionLevel encryptionLevel) { - const auto wallet = TW::HDWallet(128, ""); +StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const Data& password, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption) { + const auto wallet = TW::HDWallet<>(128, ""); const auto& mnemonic = wallet.getMnemonic(); assert(Mnemonic::isValid(mnemonic)); Data mnemonicData = TW::Data(mnemonic.begin(), mnemonic.end()); - StoredKey key = StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData, encryptionLevel); + StoredKey key(StoredKeyType::mnemonicPhrase, name, password, mnemonicData, encryptionLevel, encryption); + memzero(mnemonicData.data(), mnemonic.size()); return key; } -StoredKey StoredKey::createWithMnemonicAddDefaultAddress(const std::string& name, const Data& password, const std::string& mnemonic, TWCoinType coin) { - StoredKey key = createWithMnemonic(name, password, mnemonic, TWStoredKeyEncryptionLevelDefault); +StoredKey StoredKey::createWithMnemonicAddDefaultAddress(const std::string& name, const Data& password, const std::string& mnemonic, TWCoinType coin, TWStoredKeyEncryption encryption) { + StoredKey key = createWithMnemonic(name, password, mnemonic, TWStoredKeyEncryptionLevelDefault, encryption); const auto wallet = key.wallet(password); key.account(coin, &wallet); return key; } -StoredKey StoredKey::createWithPrivateKey(const std::string& name, const Data& password, const Data& privateKeyData) { - StoredKey key = StoredKey(StoredKeyType::privateKey, name, password, privateKeyData, TWStoredKeyEncryptionLevelDefault); - return key; +StoredKey StoredKey::createWithPrivateKey(const std::string& name, const Data& password, const Data& privateKeyData, TWStoredKeyEncryption encryption) { + return StoredKey(StoredKeyType::privateKey, name, password, privateKeyData, TWStoredKeyEncryptionLevelDefault, encryption); } -StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData) { +StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData, TWStoredKeyEncryption encryption) { const auto curve = TW::curve(coin); if (!PrivateKey::isValid(privateKeyData, curve)) { throw std::invalid_argument("Invalid private key data"); } - StoredKey key = createWithPrivateKey(name, password, privateKeyData); + StoredKey key = createWithPrivateKey(name, password, privateKeyData, encryption); const auto derivationPath = TW::derivationPath(coin); const auto pubKeyType = TW::publicKeyType(coin); const auto pubKey = PrivateKey(privateKeyData).getPublicKey(pubKeyType); @@ -74,26 +67,28 @@ StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& na return key; } -StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel) +StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption) : type(type), id(), name(std::move(name)), accounts() { - const auto encryptionParams = EncryptionParameters::getPreset(encryptionLevel); + const auto encryptionParams = EncryptionParameters::getPreset(encryptionLevel, encryption); payload = EncryptedPayload(password, data, encryptionParams); - boost::uuids::random_generator gen; - id = boost::lexical_cast(gen()); + + const char* uuid_ptr = Rust::tw_uuid_random(); + id = std::make_optional(uuid_ptr); + Rust::free_string(uuid_ptr); } -const HDWallet StoredKey::wallet(const Data& password) const { +const HDWallet<> StoredKey::wallet(const Data& password) const { if (type != StoredKeyType::mnemonicPhrase) { throw std::invalid_argument("Invalid account requested."); } const auto data = payload.decrypt(password); const auto mnemonic = std::string(reinterpret_cast(data.data()), data.size()); - return HDWallet(mnemonic, ""); + return HDWallet<>(mnemonic, ""); } std::vector StoredKey::getAccounts(TWCoinType coin) const { std::vector result; - for (auto& account: accounts) { + for (auto& account : accounts) { if (account.coin == coin) { result.push_back(account); } @@ -101,7 +96,7 @@ std::vector StoredKey::getAccounts(TWCoinType coin) const { return result; } -std::optional StoredKey::getDefaultAccount(TWCoinType coin, const HDWallet* wallet) const { +std::optional StoredKey::getDefaultAccount(TWCoinType coin, const HDWallet<>* wallet) const { // there are multiple, try to look for default if (wallet != nullptr) { const auto address = wallet->deriveAddress(coin); @@ -112,7 +107,7 @@ std::optional StoredKey::getDefaultAccount(TWCoinType coin, const HDWal } // no wallet or not found, rely on derivation=0 condition const auto coinAccounts = getAccounts(coin); - for (auto& account: coinAccounts) { + for (auto& account : coinAccounts) { if (account.derivation == TWDerivationDefault) { return account; } @@ -120,7 +115,7 @@ std::optional StoredKey::getDefaultAccount(TWCoinType coin, const HDWal return std::nullopt; } -std::optional StoredKey::getDefaultAccountOrAny(TWCoinType coin, const HDWallet* wallet) const { +std::optional StoredKey::getDefaultAccountOrAny(TWCoinType coin, const HDWallet<>* wallet) const { const auto defaultAccount = getDefaultAccount(coin, wallet); if (defaultAccount.has_value()) { return defaultAccount; @@ -142,13 +137,13 @@ std::optional StoredKey::getAccount(TWCoinType coin, const std::string& return std::nullopt; } -std::optional StoredKey::getAccount(TWCoinType coin, TWDerivation derivation, const HDWallet& wallet) const { +std::optional StoredKey::getAccount(TWCoinType coin, TWDerivation derivation, const HDWallet<>& wallet) const { // obtain address const auto address = wallet.deriveAddress(coin, derivation); return getAccount(coin, address); } -Account StoredKey::fillAddressIfMissing(Account& account, const HDWallet* wallet) const { +Account StoredKey::fillAddressIfMissing(Account& account, const HDWallet<>* wallet) const { if (account.address.empty() && wallet != nullptr) { account.address = wallet->deriveAddress(account.coin, account.derivation); } @@ -160,7 +155,13 @@ Account StoredKey::fillAddressIfMissing(Account& account, const HDWallet* wallet return account; } -std::optional StoredKey::account(TWCoinType coin, const HDWallet* wallet) { +void StoredKey::updateAddressForAccount(const PrivateKey& privKey, Account& account) { + const auto pubKey = privKey.getPublicKey(TW::publicKeyType(account.coin)); + account.address = TW::deriveAddress(account.coin, pubKey, account.derivation); + account.publicKey = hex(pubKey.bytes); +} + +std::optional StoredKey::account(TWCoinType coin, const HDWallet<>* wallet) { const auto account = getDefaultAccountOrAny(coin, wallet); if (account.has_value()) { Account accountLval = account.value(); @@ -182,7 +183,7 @@ std::optional StoredKey::account(TWCoinType coin, const HDWallet* return accounts.back(); } -Account StoredKey::account(TWCoinType coin, TWDerivation derivation, const HDWallet& wallet) { +Account StoredKey::account(TWCoinType coin, TWDerivation derivation, const HDWallet<>& wallet) { const auto coinAccount = getAccount(coin, derivation, wallet); if (coinAccount.has_value()) { Account accountLval = coinAccount.value(); @@ -204,7 +205,7 @@ std::optional StoredKey::account(TWCoinType coin) const { return getDefaultAccountOrAny(coin, nullptr); } -std::optional StoredKey::account(TWCoinType coin, TWDerivation derivation, const HDWallet& wallet) const { +std::optional StoredKey::account(TWCoinType coin, TWDerivation derivation, const HDWallet<>& wallet) const { const auto account = getAccount(coin, derivation, wallet); if (account.has_value()) { Account accountLval = account.value(); @@ -219,8 +220,7 @@ void StoredKey::addAccount( TWDerivation derivation, const DerivationPath& derivationPath, const std::string& publicKey, - const std::string& extendedPublicKey -) { + const std::string& extendedPublicKey) { if (getAccount(coin, address).has_value()) { // address already present return; @@ -229,77 +229,86 @@ void StoredKey::addAccount( } void StoredKey::removeAccount(TWCoinType coin) { - accounts.erase(std::remove_if(accounts.begin(), accounts.end(), [coin](Account& account) -> bool { - return account.coin == coin; - }), accounts.end()); + accounts.erase( + std::remove_if(accounts.begin(), accounts.end(), [coin](Account& account) -> bool { return account.coin == coin; }), + accounts.end()); } void StoredKey::removeAccount(TWCoinType coin, TWDerivation derivation) { - accounts.erase(std::remove_if(accounts.begin(), accounts.end(), [coin, derivation](Account& account) -> bool { - return account.coin == coin && account.derivation == derivation; - }), accounts.end()); + accounts.erase( + std::remove_if(accounts.begin(), accounts.end(), [coin, derivation](Account& account) -> bool { + return account.coin == coin && account.derivation == derivation; + }), + accounts.end()); } void StoredKey::removeAccount(TWCoinType coin, DerivationPath derivationPath) { - accounts.erase(std::remove_if(accounts.begin(), accounts.end(), [coin, derivationPath](Account& account) -> bool { - return account.coin == coin && account.derivationPath == derivationPath; - }), accounts.end()); + accounts.erase( + std::remove_if(accounts.begin(), accounts.end(), [coin, derivationPath](Account& account) -> bool { + return account.coin == coin && account.derivationPath == derivationPath; + }), + accounts.end()); } const PrivateKey StoredKey::privateKey(TWCoinType coin, const Data& password) { return privateKey(coin, TWDerivationDefault, password); } -const PrivateKey StoredKey::privateKey(TWCoinType coin, TWDerivation derivation, const Data& password) { - switch (type) { - case StoredKeyType::mnemonicPhrase: { +const PrivateKey StoredKey::privateKey(TWCoinType coin, [[maybe_unused]] TWDerivation derivation, const Data& password) { + if (type == StoredKeyType::mnemonicPhrase) { const auto wallet = this->wallet(password); - const auto account = this->account(coin, &wallet); - return wallet.getKey(coin, account->derivationPath); - } - case StoredKeyType::privateKey: - return PrivateKey(payload.decrypt(password)); + const Account& account = this->account(coin, derivation, wallet); + return wallet.getKey(coin, account.derivationPath); } + // type == StoredKeyType::privateKey + return PrivateKey(payload.decrypt(password)); } void StoredKey::fixAddresses(const Data& password) { switch (type) { - case StoredKeyType::mnemonicPhrase: { - const auto wallet = this->wallet(password); - for (auto& account : accounts) { - if (!account.address.empty() && - !account.publicKey.empty() && - TW::validateAddress(account.coin, account.address) - ) { - continue; - } - const auto& derivationPath = account.derivationPath; - const auto key = wallet.getKey(account.coin, derivationPath); - const auto pubKey = key.getPublicKey(TW::publicKeyType(account.coin)); - account.address = TW::deriveAddress(account.coin, pubKey, account.derivation); - account.publicKey = hex(pubKey.bytes); - } + case StoredKeyType::mnemonicPhrase: { + const auto wallet = this->wallet(password); + for (auto& account : accounts) { + if (!account.address.empty() && !account.publicKey.empty() && + TW::validateAddress(account.coin, account.address)) { + continue; } - break; - - case StoredKeyType::privateKey: { - auto key = PrivateKey(payload.decrypt(password)); - for (auto& account : accounts) { - if (!account.address.empty() && - !account.publicKey.empty() && - TW::validateAddress(account.coin, account.address) - ) { - continue; - } - const auto pubKey = key.getPublicKey(TW::publicKeyType(account.coin)); - account.address = TW::deriveAddress(account.coin, pubKey, account.derivation); - account.publicKey = hex(pubKey.bytes); - } + const auto& derivationPath = account.derivationPath; + const auto key = wallet.getKey(account.coin, derivationPath); + updateAddressForAccount(key, account); + } + } break; + + case StoredKeyType::privateKey: { + auto key = PrivateKey(payload.decrypt(password)); + for (auto& account : accounts) { + if (!account.address.empty() && !account.publicKey.empty() && + TW::validateAddress(account.coin, account.address)) { + continue; } - break; + updateAddressForAccount(key, account); + } + } break; } } +bool StoredKey::updateAddress(TWCoinType coin) { + bool addressUpdated = false; + const auto publicKeyType = TW::publicKeyType(coin); + + for (auto& account : accounts) { + // Update the address for the given chain if only `publicKey` is set. + if (account.coin == coin && !account.publicKey.empty()) { + const auto publicKeyBytes = parse_hex(account.publicKey); + const PublicKey publicKey(publicKeyBytes, publicKeyType); + account.address = TW::deriveAddress(account.coin, publicKey, account.derivation); + + addressUpdated = true; + } + } + + return addressUpdated; +} // ----------------- // Encoding/Decoding @@ -311,44 +320,46 @@ StoredKey StoredKey::createWithJson(const nlohmann::json& json) { return storedKey; } -namespace CodingKeys { - static const auto address = "address"; - static const auto type = "type"; - static const auto name = "name"; - static const auto id = "id"; - static const auto crypto = "crypto"; - static const auto activeAccounts = "activeAccounts"; - static const auto version = "version"; - static const auto coin = "coin"; -} // namespace CodingKeys +namespace CodingKeys::SK { + +static const auto address = "address"; +static const auto type = "type"; +static const auto name = "name"; +static const auto id = "id"; +static const auto crypto = "crypto"; +static const auto activeAccounts = "activeAccounts"; +static const auto version = "version"; +static const auto coin = "coin"; + +} // namespace CodingKeys::SK namespace UppercaseCodingKeys { - static const auto crypto = "Crypto"; +static const auto crypto = "Crypto"; } // namespace UppercaseCodingKeys namespace TypeString { - static const auto privateKey = "private-key"; - static const auto mnemonic = "mnemonic"; +static const auto privateKey = "private-key"; +static const auto mnemonic = "mnemonic"; } // namespace TypeString void StoredKey::loadJson(const nlohmann::json& json) { - if (json.count(CodingKeys::type) != 0 && - json[CodingKeys::type].get() == TypeString::mnemonic) { + if (json.count(CodingKeys::SK::type) != 0 && + json[CodingKeys::SK::type].get() == TypeString::mnemonic) { type = StoredKeyType::mnemonicPhrase; } else { type = StoredKeyType::privateKey; } - if (json.count(CodingKeys::name) != 0) { - name = json[CodingKeys::name].get(); + if (json.count(CodingKeys::SK::name) != 0) { + name = json[CodingKeys::SK::name].get(); } - if (json.count(CodingKeys::id) != 0) { - id = json[CodingKeys::id].get(); + if (json.count(CodingKeys::SK::id) != 0) { + id = json[CodingKeys::SK::id].get(); } - if (json.count(CodingKeys::crypto) != 0) { - payload = EncryptedPayload(json[CodingKeys::crypto]); + if (json.count(CodingKeys::SK::crypto) != 0) { + payload = EncryptedPayload(json[CodingKeys::SK::crypto]); } else if (json.count(UppercaseCodingKeys::crypto) != 0) { // Workaround for myEtherWallet files payload = EncryptedPayload(json[UppercaseCodingKeys::crypto]); @@ -356,48 +367,49 @@ void StoredKey::loadJson(const nlohmann::json& json) { throw DecryptionError::invalidKeyFile; } - if (json.count(CodingKeys::activeAccounts) != 0 && - json[CodingKeys::activeAccounts].is_array()) { - for (auto& accountJSON : json[CodingKeys::activeAccounts]) { + if (json.count(CodingKeys::SK::activeAccounts) != 0 && + json[CodingKeys::SK::activeAccounts].is_array()) { + for (auto& accountJSON : json[CodingKeys::SK::activeAccounts]) { accounts.emplace_back(accountJSON); } } - if (accounts.empty() && json.count(CodingKeys::address) != 0 && json[CodingKeys::address].is_string()) { + if (accounts.empty() && json.count(CodingKeys::SK::address) != 0 && + json[CodingKeys::SK::address].is_string()) { TWCoinType coin = TWCoinTypeEthereum; - if (json.count(CodingKeys::coin) != 0) { - coin = json[CodingKeys::coin].get(); + if (json.count(CodingKeys::SK::coin) != 0) { + coin = json[CodingKeys::SK::coin].get(); } - auto address = json[CodingKeys::address].get(); + auto address = json[CodingKeys::SK::address].get(); accounts.emplace_back(address, coin, TWDerivationDefault, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(coin), 0, 0, 0), "", ""); } } nlohmann::json StoredKey::json() const { nlohmann::json j; - j[CodingKeys::version] = 3; + j[CodingKeys::SK::version] = 3; switch (type) { case StoredKeyType::privateKey: - j[CodingKeys::type] = TypeString::privateKey; + j[CodingKeys::SK::type] = TypeString::privateKey; break; case StoredKeyType::mnemonicPhrase: - j[CodingKeys::type] = TypeString::mnemonic; + j[CodingKeys::SK::type] = TypeString::mnemonic; break; } if (id) { - j[CodingKeys::id] = *id; + j[CodingKeys::SK::id] = *id; } - j[CodingKeys::name] = name; - j[CodingKeys::crypto] = payload.json(); + j[CodingKeys::SK::name] = name; + j[CodingKeys::SK::crypto] = payload.json(); nlohmann::json accountsJSON = nlohmann::json::array(); for (const auto& account : accounts) { accountsJSON.push_back(account.json()); } - j[CodingKeys::activeAccounts] = accountsJSON; + j[CodingKeys::SK::activeAccounts] = accountsJSON; return j; } @@ -419,3 +431,5 @@ StoredKey StoredKey::load(const std::string& path) { return createWithJson(j); } + +} // namespace TW::Keystore diff --git a/src/Keystore/StoredKey.h b/src/Keystore/StoredKey.h index 790ba9206e8..12eaf5038f4 100644 --- a/src/Keystore/StoredKey.h +++ b/src/Keystore/StoredKey.h @@ -1,18 +1,17 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Account.h" #include "EncryptionParameters.h" -#include "../Data.h" +#include "Data.h" #include "../HDWallet.h" #include #include +#include #include #include @@ -45,23 +44,23 @@ class StoredKey { /// Create a new StoredKey, with the given name, mnemonic and password. /// @throws std::invalid_argument if mnemonic is invalid - static StoredKey createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic, TWStoredKeyEncryptionLevel encryptionLevel); + static StoredKey createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); /// Create a new StoredKey, with the given name, mnemonic and password. /// @throws std::invalid_argument if mnemonic is invalid - static StoredKey createWithMnemonicRandom(const std::string& name, const Data& password, TWStoredKeyEncryptionLevel encryptionLevel); + static StoredKey createWithMnemonicRandom(const std::string& name, const Data& password, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); /// Create a new StoredKey, with the given name, mnemonic and password, and also add the default address for the given coin.. /// @throws std::invalid_argument if mnemonic is invalid - static StoredKey createWithMnemonicAddDefaultAddress(const std::string& name, const Data& password, const std::string& mnemonic, TWCoinType coin); + static StoredKey createWithMnemonicAddDefaultAddress(const std::string& name, const Data& password, const std::string& mnemonic, TWCoinType coin, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); /// Create a new StoredKey, with the given name and private key. /// @throws std::invalid_argument if privateKeyData is not a valid private key - static StoredKey createWithPrivateKey(const std::string& name, const Data& password, const Data& privateKeyData); + static StoredKey createWithPrivateKey(const std::string& name, const Data& password, const Data& privateKeyData, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); /// Create a new StoredKey, with the given name and private key, and also add the default address for the given coin.. /// @throws std::invalid_argument if privateKeyData is not a valid private key - static StoredKey createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData); + static StoredKey createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); /// Create a StoredKey from a JSON object. static StoredKey createWithJson(const nlohmann::json& json); @@ -69,25 +68,25 @@ class StoredKey { /// Returns the HDWallet for this key. /// /// @throws std::invalid_argument if this key is of a type other than `mnemonicPhrase`. - const HDWallet wallet(const Data& password) const; + const HDWallet<> wallet(const Data& password) const; /// Returns all the accounts for a specific coin: 0, 1, or more. std::vector getAccounts(TWCoinType coin) const; /// If found, returns the account for a specific coin. In case of muliple accounts, the default derivation is returned, or the first one is returned. /// If none exists, and wallet is not null, an account is created (with default derivation). - std::optional account(TWCoinType coin, const HDWallet* wallet); + std::optional account(TWCoinType coin, const HDWallet<>* wallet); /// If found, returns the account for a specific coin and derivation. In case of muliple accounts, the first one is returned. /// If none exists, an account is created. - Account account(TWCoinType coin, TWDerivation derivation, const HDWallet& wallet); + Account account(TWCoinType coin, TWDerivation derivation, const HDWallet<>& wallet); /// Returns the account for a specific coin if it exists. /// In case of muliple accounts, the default derivation is returned, or the first one is returned. std::optional account(TWCoinType coin) const; /// Returns the account for a specific coin and derivation, if it exists. - std::optional account(TWCoinType coin, TWDerivation derivation, const HDWallet& wallet) const; + std::optional account(TWCoinType coin, TWDerivation derivation, const HDWallet<>& wallet) const; /// Add an account with aribitrary address/derivation path. Discouraged, use account() versions. /// Address must be unique (for a coin). @@ -111,26 +110,26 @@ class StoredKey { /// Returns the private key for a specific coin, using default derivation, creating an account if necessary. /// - /// @throws std::invalid_argument if this key is of a type other than + /// \throws std::invalid_argument if this key is of a type other than /// `mnemonicPhrase` and a coin other than the default is requested. const PrivateKey privateKey(TWCoinType coin, const Data& password); /// Returns the private key for a specific coin, creating an account if necessary. /// - /// @throws std::invalid_argument if this key is of a type other than + /// \throws std::invalid_argument if this key is of a type other than /// `mnemonicPhrase` and a coin other than the default is requested. const PrivateKey privateKey(TWCoinType coin, TWDerivation derivation, const Data& password); /// Loads and decrypts a stored key from a file. /// - /// @param path file path to load from. - /// @returns decrypted key. - /// @throws DecryptionError + /// \param path file path to load from. + /// \returns decrypted key. + /// \throws DecryptionError static StoredKey load(const std::string& path); /// Stores the key into an encrypted file. /// - /// @param path file path to store in. + /// \param path file path to store in. void store(const std::string& path); /// Initializes `StoredKey` with a JSON object. @@ -145,6 +144,12 @@ class StoredKey { /// the encryption password to re-derive addresses from private keys. void fixAddresses(const Data& password); + /// Re-derives address for the account(s) associated with the given coin. + /// + /// This method can be used if address format has been changed. + /// In case of multiple accounts, all of them will be updated. + bool updateAddress(TWCoinType coin); + private: /// Default constructor, private StoredKey() : type(StoredKeyType::mnemonicPhrase) {} @@ -152,24 +157,27 @@ class StoredKey { /// Initializes a `StoredKey` with a type, an encryption password, and unencrypted data. /// This constructor will encrypt the provided data with default encryption /// parameters. - StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel); + StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); /// Find default account for coin, if exists. If multiple exist, default is returned. /// Optional wallet is needed to derive default address - std::optional getDefaultAccount(TWCoinType coin, const HDWallet* wallet) const; + std::optional getDefaultAccount(TWCoinType coin, const HDWallet<>* wallet) const; /// Find account for coin, if exists. If multiple exist, default is returned, or any. /// Optional wallet is needed to derive default address - std::optional getDefaultAccountOrAny(TWCoinType coin, const HDWallet* wallet) const; + std::optional getDefaultAccountOrAny(TWCoinType coin, const HDWallet<>* wallet) const; /// Find account by coin+address (should be one, if multiple, first is returned) std::optional getAccount(TWCoinType coin, const std::string& address) const; /// Find account by coin+derivation (should be one, if multiple, first is returned) - std::optional getAccount(TWCoinType coin, TWDerivation derivation, const HDWallet& wallet) const; + std::optional getAccount(TWCoinType coin, TWDerivation derivation, const HDWallet<>& wallet) const; /// Re-derive account address if missing - Account fillAddressIfMissing(Account& account, const HDWallet* wallet) const; + Account fillAddressIfMissing(Account& account, const HDWallet<>* wallet) const; + + /// Re-derives public key and address for the specified account. + static void updateAddressForAccount(const PrivateKey& privKey, Account& account); }; } // namespace TW::Keystore diff --git a/src/Kusama/Address.h b/src/Kusama/Address.h index c973018e172..6d4758002c2 100644 --- a/src/Kusama/Address.h +++ b/src/Kusama/Address.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include "../Polkadot/SS58Address.h" #include @@ -27,4 +25,3 @@ class Address: public SS58Address { Address(const PublicKey& publicKey): SS58Address(publicKey, TWSS58AddressTypeKusama) {} }; } // namespace TW::Kusama - diff --git a/src/Kusama/Entry.cpp b/src/Kusama/Entry.cpp index 3e96d1fdb08..46df5c4bfff 100644 --- a/src/Kusama/Entry.cpp +++ b/src/Kusama/Entry.cpp @@ -1,33 +1,31 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" #include "Polkadot/Signer.h" -using namespace TW::Kusama; -using namespace TW; -using namespace std; +namespace TW::Kusama { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { const auto addr = Address(address); return {addr.bytes.begin() + 1, addr.bytes.end()}; } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Kusama diff --git a/src/Kusama/Entry.h b/src/Kusama/Entry.h index 9bdacd9be53..d127bd39a4d 100644 --- a/src/Kusama/Entry.h +++ b/src/Kusama/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,12 +10,12 @@ namespace TW::Kusama { /// Entry point for implementation of Kusama coin. See also Polkadot. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Kusama diff --git a/src/LiquidStaking/LiquidStaking.cpp b/src/LiquidStaking/LiquidStaking.cpp new file mode 100644 index 00000000000..fbb83adc742 --- /dev/null +++ b/src/LiquidStaking/LiquidStaking.cpp @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "LiquidStaking/LiquidStaking.h" +#include "Data.h" + +// Stride +#include "proto/Cosmos.pb.h" + +// Aptos +#include "proto/Aptos.pb.h" + +// ETH +#include "Ethereum/ABI/Function.h" +#include "Ethereum/Address.h" +#include "proto/Ethereum.pb.h" +#include "uint256.h" + +namespace TW::LiquidStaking { + +using BlockchainActionEnumPair = std::pair; + +struct PairHash { + template + std::size_t operator()(const std::pair& pair) const { + return std::hash()(pair.first) ^ std::hash()(pair.second); + } +}; + +using EVMLiquidStakingFunctionRegistry = std::unordered_map; +using EVMLiquidStakingParamsRegistry = std::unordered_map; +using EVMLiquidStakingRegistry = std::unordered_map; + +static const EVMLiquidStakingFunctionRegistry gStraderFunctionRegistry = + {{std::make_pair(Proto::POLYGON, Action::Stake), "swapMaticForMaticXViaInstantPool"}, + {std::make_pair(Proto::POLYGON, Action::Unstake), "requestMaticXSwap"}, + {std::make_pair(Proto::POLYGON, Action::Withdraw), "claimMaticXSwap"}, + {std::make_pair(Proto::BNB_BSC, Action::Stake), "deposit"}, + {std::make_pair(Proto::BNB_BSC, Action::Unstake), "requestWithdraw"} +}; + +static const EVMLiquidStakingFunctionRegistry gLidoFunctionRegistry = + {{std::make_pair(Proto::ETHEREUM, Action::Stake), "submit"}, +}; + +static const EVMLiquidStakingRegistry gEVMLiquidStakingRegistry = { + {Proto::Protocol::Strader, gStraderFunctionRegistry}, + {Proto::Protocol::Lido, gLidoFunctionRegistry}, +}; + +namespace internal { + void setTransferDataAndAmount(Ethereum::Proto::Transaction::ContractGeneric& transfer, const Data& payload, const uint256_t& amount) { + transfer.set_data(payload.data(), payload.size()); + Data amountData = store(amount); + transfer.set_amount(amountData.data(), amountData.size()); + } + + void handleStake(const Proto::Stake& stake, const Proto::Blockchain& blockchain, Data& payload, uint256_t& amount, const Proto::Protocol protocol) { + Ethereum::ABI::BaseParams params; + if (protocol == Proto::Lido) { + params.emplace_back(std::make_shared()); + } + auto funcData = Ethereum::ABI::Function::encodeFunctionCall(gEVMLiquidStakingRegistry.at(protocol).at({blockchain, Action::Stake}), params); + if (funcData.has_value()) { + payload = funcData.value(); + } + amount = uint256_t(stake.amount()); + } + + void handleUnstake(const Proto::Unstake& unstake, const Proto::Blockchain& blockchain, Data& payload) { + Ethereum::ABI::BaseParams params; + params.emplace_back(std::make_shared(uint256_t(unstake.amount()))); + auto functionName = gStraderFunctionRegistry.at({blockchain, Action::Unstake}); + auto funcData = Ethereum::ABI::Function::encodeFunctionCall(functionName, params); + if (funcData.has_value()) { + payload = funcData.value(); + } + } + + void handleWithdraw(const Proto::Withdraw& withdraw, const Proto::Blockchain& blockchain, Data& payload) { + Ethereum::ABI::BaseParams params; + params.emplace_back(std::make_shared(uint256_t(withdraw.idx()))); + auto functionName = gStraderFunctionRegistry.at({blockchain, Action::Withdraw}); + auto funcData = Ethereum::ABI::Function::encodeFunctionCall(functionName, params); + if (funcData.has_value()) { + payload = funcData.value(); + } + } +} + +Proto::Output Builder::buildStraderEVM() const { + Proto::Output output; + if (!mSmartContractAddress) { + *output.mutable_status() = generateError(Proto::ERROR_SMART_CONTRACT_ADDRESS_NOT_SET, "Strader protocol require the smart contract address to be set"); + return output; + } + auto input = Ethereum::Proto::SigningInput(); + + if (this->mBlockchain == Proto::POLYGON || this->mBlockchain == Proto::ETHEREUM) { + input.set_tx_mode(Ethereum::Proto::Enveloped); + } + input.set_to_address(*mSmartContractAddress); + + auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); + + auto visitFunctor = [&transfer, this](const TAction& value) { + Data payload; + uint256_t amount; + + if (auto* stake = std::get_if(&value); stake) { + internal::handleStake(*stake, mBlockchain, payload, amount, Proto::Protocol::Strader); + } else if (auto* unstake = std::get_if(&value); unstake) { + internal::handleUnstake(*unstake, mBlockchain, payload); + amount = uint256_t(0); + } else if (auto* withdraw = std::get_if(&value); withdraw) { + internal::handleWithdraw(*withdraw, mBlockchain, payload); + amount = uint256_t(0); + } + + internal::setTransferDataAndAmount(transfer, payload, amount); + }; + + std::visit(visitFunctor, this->mAction); + + *output.mutable_ethereum() = input; + return output; +} + +Proto::Output Builder::buildTortugaAptos() const { + Proto::Output output; + if (!mSmartContractAddress) { + *output.mutable_status() = generateError(Proto::ERROR_SMART_CONTRACT_ADDRESS_NOT_SET, "Tortuga protocol require the smart contract address to be set"); + return output; + } + auto input = Aptos::Proto::SigningInput(); + auto &liquid_staking_message = *input.mutable_liquid_staking_message(); + liquid_staking_message.set_smart_contract_address(*mSmartContractAddress); + + auto visitFunctor = [&liquid_staking_message](const TAction& value) { + if (auto* stake = std::get_if(&value); stake) { + auto& tortuga_stake = *liquid_staking_message.mutable_stake(); + tortuga_stake.set_amount(std::strtoull(stake->amount().c_str(), nullptr, 0)); + } else if (auto* unstake = std::get_if(&value); unstake) { + auto& tortuga_unstake = *liquid_staking_message.mutable_unstake(); + tortuga_unstake.set_amount(std::strtoull(unstake->amount().c_str(), nullptr, 0)); + } else if (auto* withdraw = std::get_if(&value); withdraw) { + auto& tortuga_claim = *liquid_staking_message.mutable_claim(); + tortuga_claim.set_idx(std::strtoull(withdraw->idx().c_str(), nullptr, 0)); + } + }; + + std::visit(visitFunctor, this->mAction); + *output.mutable_aptos() = input; + return output; +} + +Proto::Output Builder::buildStride() const { + if (this->mBlockchain != Proto::STRIDE) { + auto output = Proto::Output(); + *output.mutable_status() = generateError(Proto::ERROR_TARGETED_BLOCKCHAIN_NOT_SUPPORTED_BY_PROTOCOL, "Only Stride blockchain is supported on stride for now"); + return output; + } + + Proto::Output output; + auto input = Cosmos::Proto::SigningInput(); + auto visitFunctor = [&input, &output](const TAction& value) { + if (auto* stake = std::get_if(&value); stake) { + auto& stride_stake = *input.add_messages()->mutable_msg_stride_liquid_staking_stake(); + stride_stake.set_creator(stake->asset().from_address()); + stride_stake.set_amount(stake->amount()); + stride_stake.set_host_denom(stake->asset().denom()); + } else if (auto* unstake = std::get_if(&value); unstake) { + auto& stride_redeem = *input.add_messages()->mutable_msg_stride_liquid_staking_redeem(); + stride_redeem.set_creator(unstake->asset().from_address()); + stride_redeem.set_amount(unstake->amount()); + stride_redeem.set_host_zone(unstake->receiver_chain_id()); + stride_redeem.set_receiver(unstake->receiver_address()); + } else { + *output.mutable_status() = generateError(Proto::ERROR_OPERATION_NOT_SUPPORTED_BY_PROTOCOL, "Stride protocol unstake include withdraw operation"); + } + }; + + std::visit(visitFunctor, this->mAction); + *output.mutable_cosmos() = input; + return output; +} + +Proto::Output Builder::buildTortuga() const { + if (this->mBlockchain == Proto::APTOS) { + return buildTortugaAptos(); + } + auto output = Proto::Output(); + *output.mutable_status() = generateError(Proto::ERROR_TARGETED_BLOCKCHAIN_NOT_SUPPORTED_BY_PROTOCOL, "Only Aptos blockchain is supported on tortuga for now"); + return output; +} + +Proto::Output Builder::buildLidoEVM() const { + Proto::Output output; + if (!mSmartContractAddress) { + *output.mutable_status() = generateError(Proto::ERROR_SMART_CONTRACT_ADDRESS_NOT_SET, "Lido protocol require the smart contract address to be set"); + return output; + } + auto input = Ethereum::Proto::SigningInput(); + + if (this->mBlockchain == Proto::ETHEREUM) { + input.set_tx_mode(Ethereum::Proto::Enveloped); + } + input.set_to_address(*mSmartContractAddress); + + auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); + + + auto visitFunctor = [&transfer, this, &output](const TAction& value) { + Data payload; + uint256_t amount; + + if (auto* stake = std::get_if(&value); stake) { + internal::handleStake(*stake, mBlockchain, payload, amount, Proto::Lido); + } else { + *output.mutable_status() = generateError(Proto::ERROR_OPERATION_NOT_SUPPORTED_BY_PROTOCOL, "Lido protocol only support stake action for now"); + } + + internal::setTransferDataAndAmount(transfer, payload, amount); + }; + + std::visit(visitFunctor, this->mAction); + *output.mutable_ethereum() = input; + return output; +} + +Proto::Output Builder::buildLido() const { + switch (this->mBlockchain) { + case Proto::ETHEREUM: + return buildLidoEVM(); + default: + auto output = Proto::Output(); + *output.mutable_status() = generateError(Proto::ERROR_TARGETED_BLOCKCHAIN_NOT_SUPPORTED_BY_PROTOCOL, "Only Lido EVM chains is supported for now"); + return output; + } +} + + +Proto::Output Builder::buildStrader() const { + switch (this->mBlockchain) { + case Proto::POLYGON: + case Proto::BNB_BSC: + return buildStraderEVM(); + default: + auto output = Proto::Output(); + *output.mutable_status() = generateError(Proto::ERROR_TARGETED_BLOCKCHAIN_NOT_SUPPORTED_BY_PROTOCOL, "Only Strader EVM chains is supported for now"); + return output; + } +} + +Proto::Output Builder::build() const { + switch (this->mProtocol) { + case Proto::Strader: + return this->buildStrader(); + case Proto::Tortuga: + return this->buildTortuga(); + case Proto::Stride: + return this->buildStride(); + case Proto::Lido: + return this->buildLido(); + default: + return Proto::Output(); + } +} + +} // namespace TW::LiquidStaking diff --git a/src/LiquidStaking/LiquidStaking.h b/src/LiquidStaking/LiquidStaking.h new file mode 100644 index 00000000000..b4f974b7701 --- /dev/null +++ b/src/LiquidStaking/LiquidStaking.h @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "proto/LiquidStaking.pb.h" +#include "TrustWalletCore/TWBlockchain.h" +#include +#include + +namespace TW::LiquidStaking { +using TAction = std::variant; + +enum class Action { + Stake = 0, + Unstake = 1, + Withdraw = 2 +}; + +class Builder { + TAction mAction; + std::string mFromAddress; + std::optional mSmartContractAddress{std::nullopt}; + Proto::Protocol mProtocol; + Proto::Blockchain mBlockchain; + + Proto::Output buildStraderEVM() const; + Proto::Output buildStrader() const; + Proto::Output buildTortugaAptos() const; + Proto::Output buildTortuga() const; + Proto::Output buildStride() const; + Proto::Output buildLidoEVM() const; + Proto::Output buildLido() const; +public: + Builder() noexcept = default; + + static Builder builder() noexcept { return {}; } + + Builder& protocol(Proto::Protocol protocol) noexcept { + mProtocol = protocol; + return *this; + } + + Builder& blockchain(Proto::Blockchain blockchain) noexcept { + mBlockchain = blockchain; + return *this; + } + + Builder& action(TAction action) noexcept { + mAction = std::move(action); + return *this; + } + + Builder& smartContractAddress(std::string smartContractAddress) noexcept { + if (!smartContractAddress.empty()) { + mSmartContractAddress = std::move(smartContractAddress); + } + return *this; + } + + Proto::Output build() const; +}; + +static inline Proto::Status generateError(Proto::StatusCode code, const std::optional& message = std::nullopt) { + Proto::Status status; + status.set_code(code); + switch (code) { + case Proto::ERROR_ACTION_NOT_SET: + status.set_message(message.value_or("Liquid staking action not set")); + break; + case Proto::ERROR_TARGETED_BLOCKCHAIN_NOT_SUPPORTED_BY_PROTOCOL: + status.set_message(message.value_or("The selected protocol doesn't support the targeted blockchain")); + break; + case Proto::ERROR_SMART_CONTRACT_ADDRESS_NOT_SET: + status.set_message(message.value_or("The selected protocol require a smart contract address to be set")); + break; + case Proto::ERROR_INPUT_PROTO_DESERIALIZATION: + status.set_message(message.value_or("Could not deserialize input proto")); + break; + case Proto::ERROR_OPERATION_NOT_SUPPORTED_BY_PROTOCOL: + status.set_message(message.value_or("The selected protocol doesn't support this liquid staking operation")); + break; + default: + return status; + } + return status; +} + +static inline Proto::Output build(const Proto::Input& input) { + TAction action; + switch (input.action_case()) { + case Proto::Input::kStake: + action = input.stake(); + break; + case Proto::Input::kUnstake: + action = input.unstake(); + break; + case Proto::Input::kWithdraw: + action = input.withdraw(); + break; + default: + auto output = Proto::Output(); + *output.mutable_status() = generateError(Proto::ERROR_ACTION_NOT_SET); + return output; + } + + return Builder::builder().action(action).protocol(input.protocol()).smartContractAddress(input.smart_contract_address()).blockchain(input.blockchain()).build(); +} +} // namespace TW::LiquidStaking diff --git a/src/LiquidStaking/TWLiquidStaking.cpp b/src/LiquidStaking/TWLiquidStaking.cpp new file mode 100644 index 00000000000..2b6dceb3451 --- /dev/null +++ b/src/LiquidStaking/TWLiquidStaking.cpp @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Data.h" +#include "proto/LiquidStaking.pb.h" +#include "LiquidStaking/LiquidStaking.h" +#include "TrustWalletCore/TWLiquidStaking.h" + +using namespace TW; + +TWData *_Nonnull TWLiquidStakingBuildRequest(TWData *_Nonnull input) { + LiquidStaking::Proto::Input inputProto; + LiquidStaking::Proto::Output outputProto; + + if (!inputProto.ParseFromArray(TWDataBytes(input), static_cast(TWDataSize(input)))) { + *outputProto.mutable_status() = LiquidStaking::generateError(LiquidStaking::Proto::ERROR_INPUT_PROTO_DESERIALIZATION); + auto outputData = TW::data(outputProto.SerializeAsString()); + return TWDataCreateWithBytes(outputData.data(), outputData.size()); + } + + outputProto = LiquidStaking::build(inputProto); + auto outputData = TW::data(outputProto.SerializeAsString()); + return TWDataCreateWithBytes(outputData.data(), outputData.size()); +} diff --git a/src/Mnemonic.cpp b/src/Mnemonic.cpp index 08a3112ec2a..a6e9a2227cd 100644 --- a/src/Mnemonic.cpp +++ b/src/Mnemonic.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Mnemonic.h" @@ -28,16 +26,15 @@ inline const char* const* mnemonicWordlist() { return wordlist; } bool Mnemonic::isValidWord(const std::string& word) { const char* wordC = word.c_str(); const auto len = word.length(); + // Although this operation is not security-critical, we aim for constant-time operation here as well + // (i.e., no early exit on match) + auto found = false; for (const char* const* w = mnemonicWordlist(); *w != nullptr; ++w) { - if (strlen(*w) != len) { - continue; - } - if (strncmp(*w, wordC, len) == 0) { - return true; + if (std::string(*w).size() == len && strncmp(*w, wordC, len) == 0) { + found = true; } } - // not found - return false; + return found; } std::string Mnemonic::suggest(const std::string& prefix) { diff --git a/src/Mnemonic.h b/src/Mnemonic.h index 9071a4d697a..dc6fc4ff2d6 100644 --- a/src/Mnemonic.h +++ b/src/Mnemonic.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Move/Address.h b/src/Move/Address.h new file mode 100644 index 00000000000..a734df9cd9e --- /dev/null +++ b/src/Move/Address.h @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "HexCoding.h" +#include "PublicKey.h" +#include + +namespace TW::Move { +template +class Address { +private: + static constexpr std::size_t shortSizeAddress = 3; + static constexpr std::size_t hexShortSizeAddress = shortSizeAddress - 2; + static constexpr std::size_t hexSizeAddress = N*2; + + static std::string normalize(const std::string& string, std::size_t hexLen) { + std::string hexStr((size * 2) - hexLen, '0'); + hexStr.append(string); + return hexStr; + } + + /// Determines whether a collection of bytes makes a valid address. + static bool isValid(const Data& data) { return data.size() == size; } +public: + static constexpr int size = N; + std::array bytes; + + /// Determines whether a string makes a valid address. + static bool isValid(const std::string& string) { + if (!is_hex_encoded(string)) { + return false; + } + auto address = string; + if (address.starts_with("0x")) { + address = address.substr(2); + } + if (address.size() == hexShortSizeAddress || (StrictPadding && (address.size() < hexSizeAddress))) { + address = normalize(address, address.size()); + } + if (address.size() != 2 * Address::size) { + return false; + } + const auto data = parse_hex(address); + return isValid(data); + }; + + Address() noexcept = default; + + Address(const std::string& string) { + if (!isValid(string)) { + throw std::invalid_argument("Invalid address string"); + } + auto hexFunctor = [&string]() { + std::size_t hexLen = string.size() - 2; + bool isExpectedLen = hexLen == hexShortSizeAddress; + if (string.starts_with("0x") && (isExpectedLen || (StrictPadding && (hexLen < hexSizeAddress)))) { + //! We have specific address like 0x1, padding it. + return parse_hex(normalize(string.substr(2), hexLen)); + } else { + auto address = string; + if (StrictPadding && (address.size() < hexSizeAddress)) { + address = normalize(address, address.size()); + } + return parse_hex(address); + } + }; + + const auto data = hexFunctor(); + std::copy(data.begin(), data.end(), bytes.begin()); + } + + Address(const Data& data) { + if (!isValid(data)) { + throw std::invalid_argument("Invalid address data"); + } + std::copy_n(data.begin(), size, bytes.begin()); + } + + Address(const PublicKey& publicKey, TW::Hash::Hasher hasher = Hash::Hasher::HasherSha3_256) { + if (publicKey.type != TWPublicKeyTypeED25519) { + throw std::invalid_argument("Invalid public key type"); + } + auto digest = static_cast(this)->getDigest(publicKey); + const auto data = functionPointerFromEnum(hasher)(digest.data(), digest.size()); + std::copy_n(data.begin(), Address::size, bytes.begin()); + } + + static Derived zero() { + return Derived("0x0"); + } + + static Derived one() { + return Derived("0x1"); + } + + static Derived three() { + return Derived("0x3"); + } + + /// Returns a string representation of the address. + [[nodiscard]] std::string string(bool withPrefix = true) const { + std::string output = withPrefix ? "0x" : ""; + return output + hex(bytes); + }; + + /// Returns a short string representation of the address. E.G 0x1; + [[nodiscard]] std::string shortString() const { + std::string s = hex(bytes); + s.erase(0, s.find_first_not_of('0')); + return s; + }; +}; +} // namespace TW::Move diff --git a/src/MultiversX/Address.cpp b/src/MultiversX/Address.cpp new file mode 100644 index 00000000000..2b3c3a89cec --- /dev/null +++ b/src/MultiversX/Address.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "Address.h" + +namespace TW::MultiversX { + +const std::string Address::hrp = HRP_ELROND; + +bool Address::isValid(const std::string& string) { + return Bech32Address::isValid(string, hrp); +} + +} // namespace TW::MultiversX diff --git a/src/MultiversX/Address.h b/src/MultiversX/Address.h new file mode 100644 index 00000000000..2047e9b1df2 --- /dev/null +++ b/src/MultiversX/Address.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "../Bech32Address.h" +#include "../PublicKey.h" + +#include + +namespace TW::MultiversX { + +class Address : public Bech32Address { +public: + /// The human-readable part of the address, as defined in "registry.json" + static const std::string hrp; // HRP_ELROND + + /// Determines whether a string makes a valid address. + static bool isValid(const std::string& string); + + Address() + : Bech32Address(hrp) {} + + /// Initializes an address with a key hash. + Address(Data keyHash) + : Bech32Address(hrp, keyHash) {} + + /// Initializes an address with a public key. + Address(const PublicKey& publicKey) + : Bech32Address(hrp, publicKey.bytes) {} + + static bool decode(const std::string& addr, Address& obj_out) { + return Bech32Address::decode(addr, obj_out, hrp); + } +}; + +} // namespace TW::MultiversX diff --git a/src/MultiversX/Codec.cpp b/src/MultiversX/Codec.cpp new file mode 100644 index 00000000000..95e19883315 --- /dev/null +++ b/src/MultiversX/Codec.cpp @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Codec.h" + +#include "HexCoding.h" +#include "uint256.h" + +namespace TW::MultiversX { + +std::string Codec::encodeString(const std::string& value) { + std::string encoded = hex(TW::data(value)); + return encoded; +} + +std::string Codec::encodeUint64(uint64_t value) { + std::string encoded = hex(store(uint256_t(value))); + return encoded; +} + +std::string Codec::encodeBigInt(const std::string& value) { + return encodeBigInt(uint256_t(value)); +} + +// For reference, see https://docs.multiversx.com/developers/developer-reference/serialization-format#arbitrary-width-big-numbers. +std::string Codec::encodeBigInt(uint256_t value) { + std::string encoded = hex(store(value)); + return encoded; +} + +std::string Codec::encodeAddress(const std::string& bech32Address) { + Address address; + Address::decode(bech32Address, address); + return encodeAddress(address); +} + +std::string Codec::encodeAddress(const Address& address) { + std::string encoded = hex(address.getKeyHash()); + return encoded; +} + +} // namespace TW::MultiversX diff --git a/src/MultiversX/Codec.h b/src/MultiversX/Codec.h new file mode 100644 index 00000000000..88d30aa7db3 --- /dev/null +++ b/src/MultiversX/Codec.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Address.h" +#include "uint256.h" + +namespace TW::MultiversX { + +/// A stripped-down variant of the MultiversX codec. +/// For reference, see: +/// - https://docs.multiversx.com/developers/developer-reference/overview +/// - https://github.com/multiversx/mx-sdk-erdjs/tree/main/src/smartcontracts/codec +/// - https://github.com/multiversx/mx-sdk-rs/tree/master/framework/codec +class Codec { +public: + static std::string encodeString(const std::string& value); + static std::string encodeUint64(uint64_t value); + static std::string encodeBigInt(const std::string& value); + static std::string encodeBigInt(TW::uint256_t value); + static std::string encodeAddress(const std::string& bech32Address); + static std::string encodeAddress(const Address& address); +}; + +} // namespace TW::MultiversX diff --git a/src/MultiversX/Entry.cpp b/src/MultiversX/Entry.cpp new file mode 100644 index 00000000000..7aec5758beb --- /dev/null +++ b/src/MultiversX/Entry.cpp @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Entry.h" +#include "Address.h" +#include "Signer.h" +#include + +using namespace TW; +using namespace std; + +namespace TW::MultiversX { +// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Address::isValid(address); +} + +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Address(publicKey).string(); +} + +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + Address addr; + if (!MultiversX::Address::decode(address, addr)) { + return Data(); + } + return addr.getKeyHash(); +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { + return Signer::signJSON(json, key); +} + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto unsignedTxBytes = Signer::buildUnsignedTxBytes(input); + output.set_data(unsignedTxBytes.data(), unsignedTxBytes.size()); + output.set_data_hash(unsignedTxBytes.data(), unsignedTxBytes.size()); }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, [[maybe_unused]] const auto& publicKey) { + output = Signer::buildSigningOutput(input, signature); + }); +} + +} // namespace TW::MultiversX diff --git a/src/MultiversX/Entry.h b/src/MultiversX/Entry.h new file mode 100644 index 00000000000..a51b55efac2 --- /dev/null +++ b/src/MultiversX/Entry.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../CoinEntry.h" + +namespace TW::MultiversX { + +/// Entry point for implementation of MultiversX coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final : public CoinEntry { +public: + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; +}; + +} // namespace TW::MultiversX diff --git a/src/MultiversX/Serialization.cpp b/src/MultiversX/Serialization.cpp new file mode 100644 index 00000000000..dbd7dc25a21 --- /dev/null +++ b/src/MultiversX/Serialization.cpp @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Serialization.h" + +#include "Address.h" +#include "Base64.h" + +using namespace TW; + +std::map fields_order{ + {"nonce", 1}, + {"value", 2}, + {"receiver", 3}, + {"sender", 4}, + {"senderUsername", 5}, + {"receiverUsername", 6}, + {"gasPrice", 7}, + {"gasLimit", 8}, + {"data", 9}, + {"chainID", 10}, + {"version", 11}, + {"signature", 12}, + {"options", 13}, + {"guardian", 14}}; + +struct FieldsSorter { + bool operator()(const std::string& lhs, const std::string& rhs) const { + return fields_order[lhs] < fields_order[rhs]; + } +}; + +template +using sorted_map = std::map; +using sorted_json = nlohmann::basic_json; + +sorted_json preparePayload(const MultiversX::Transaction& transaction) { + using namespace nlohmann; + sorted_json payload{ + {"nonce", json(transaction.nonce)}, + {"value", json(transaction.value)}, + {"receiver", json(transaction.receiver)}, + {"sender", json(transaction.sender)}, + {"gasPrice", json(transaction.gasPrice)}, + {"gasLimit", json(transaction.gasLimit)}, + }; + + if (!transaction.senderUsername.empty()) { + payload["senderUsername"] = json(Base64::encode(data(transaction.senderUsername))); + } + + if (!transaction.receiverUsername.empty()) { + payload["receiverUsername"] = json(Base64::encode(data(transaction.receiverUsername))); + } + + if (!transaction.data.empty()) { + payload["data"] = json(Base64::encode(data(transaction.data))); + } + + payload["chainID"] = json(transaction.chainID); + payload["version"] = json(transaction.version); + + if (transaction.options != 0) { + payload["options"] = json(transaction.options); + } + + if (!transaction.guardian.empty()) { + payload["guardian"] = json(transaction.guardian); + } + + return payload; +} + +std::string MultiversX::serializeTransaction(const MultiversX::Transaction& transaction) { + sorted_json payload = preparePayload(transaction); + return payload.dump(); +} + +std::string MultiversX::serializeSignedTransaction(const MultiversX::Transaction& transaction, std::string signature) { + sorted_json payload = preparePayload(transaction); + payload["signature"] = nlohmann::json(signature); + return payload.dump(); +} diff --git a/src/MultiversX/Serialization.h b/src/MultiversX/Serialization.h new file mode 100644 index 00000000000..c344bf0593e --- /dev/null +++ b/src/MultiversX/Serialization.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "Transaction.h" +#include + +namespace TW::MultiversX { + +using string = std::string; +using json = nlohmann::json; + +string serializeTransaction(const Transaction& transaction); +string serializeSignedTransaction(const Transaction& transaction, string encodedSignature); + +} // namespace TW::MultiversX diff --git a/src/MultiversX/Signer.cpp b/src/MultiversX/Signer.cpp new file mode 100644 index 00000000000..a2a80fdedd3 --- /dev/null +++ b/src/MultiversX/Signer.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Signer.h" +#include "Address.h" +#include "HexCoding.h" +#include "Serialization.h" +#include "TransactionFactory.h" + +#include + +namespace TW::MultiversX { + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { + TransactionFactory factory; + + auto privateKey = PrivateKey(input.private_key()); + auto signableAsData = buildUnsignedTxBytes(input); + auto signature = privateKey.sign(signableAsData, TWCurveED25519); + + return buildSigningOutput(input, signature); +} + +std::string Signer::signJSON(const std::string& json, const Data& key) { + auto input = Proto::SigningInput(); + google::protobuf::util::JsonStringToMessage(json, &input); + input.set_private_key(key.data(), key.size()); + auto output = sign(input); + return output.encoded(); +} + +Data Signer::buildUnsignedTxBytes(const Proto::SigningInput &input) { + TransactionFactory factory; + auto transaction = factory.create(input); + auto signableAsString = serializeTransaction(transaction); + + auto signableAsData = TW::data(signableAsString); + return signableAsData; +} + +Proto::SigningOutput Signer::buildSigningOutput(const Proto::SigningInput &input, const Data &signature) { + TransactionFactory factory; + + auto transaction = factory.create(input); + auto encodedSignature = hex(signature); + auto encoded = serializeSignedTransaction(transaction, encodedSignature); + + auto protoOutput = Proto::SigningOutput(); + protoOutput.set_signature(encodedSignature); + protoOutput.set_encoded(encoded); + return protoOutput; +} + +} // namespace TW::MultiversX diff --git a/src/MultiversX/Signer.h b/src/MultiversX/Signer.h new file mode 100644 index 00000000000..7ccc88de919 --- /dev/null +++ b/src/MultiversX/Signer.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "../PrivateKey.h" +#include "../proto/MultiversX.pb.h" + +namespace TW::MultiversX { + +/// Helper class that performs MultiversX transaction signing. +class Signer { +public: + /// Hide default constructor + Signer() = delete; + + /// Signs a Proto::SigningInput transaction + static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + + /// Signs a json Proto::SigningInput with private key + static std::string signJSON(const std::string& json, const Data& key); + + static Data buildUnsignedTxBytes(const Proto::SigningInput &input); + static Proto::SigningOutput buildSigningOutput(const Proto::SigningInput &input, const Data &signature); +}; + +} // namespace TW::MultiversX diff --git a/src/MultiversX/Transaction.cpp b/src/MultiversX/Transaction.cpp new file mode 100644 index 00000000000..54f38c135cb --- /dev/null +++ b/src/MultiversX/Transaction.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Transaction.h" + +namespace TW::MultiversX { + +Transaction::Transaction() + : nonce(0), sender(""), senderUsername(""), receiver(""), receiverUsername(""), guardian(""), value("0"), data(""), gasPrice(0), gasLimit(0), chainID(""), version(0), options(TransactionOptions::Default) { +} + +bool Transaction::hasGuardian() const { + return !guardian.empty(); +} + +} // namespace TW::MultiversX diff --git a/src/MultiversX/Transaction.h b/src/MultiversX/Transaction.h new file mode 100644 index 00000000000..518c39b201a --- /dev/null +++ b/src/MultiversX/Transaction.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include + +namespace TW::MultiversX { + +enum TransactionOptions : uint32_t { + Default = 0, + // Not applicable for applications based on TW Core (as of April 2023). + HashSign = 1, + // Whether the transaction is guarded (using a guardian account). + // Generally speaking, applications can ignore this option (though some can choose to implement guarded transactions). + Guarded = 2 +}; + +class Transaction { +public: + uint64_t nonce; + std::string sender; + std::string senderUsername; + std::string receiver; + std::string receiverUsername; + std::string guardian; + std::string value; + std::string data; + uint64_t gasPrice; + uint64_t gasLimit; + std::string chainID; + uint32_t version; + TransactionOptions options; + + Transaction(); + + bool hasGuardian() const; +}; + +} // namespace TW::MultiversX diff --git a/src/MultiversX/TransactionFactory.cpp b/src/MultiversX/TransactionFactory.cpp new file mode 100644 index 00000000000..6894ce17f03 --- /dev/null +++ b/src/MultiversX/TransactionFactory.cpp @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TransactionFactory.h" + +#include "Codec.h" + +namespace TW::MultiversX { + +const int TX_VERSION = 2; + +TransactionFactory::TransactionFactory() + : TransactionFactory(TransactionFactoryConfig::GetDefault()) { +} + +TransactionFactory::TransactionFactory(const TransactionFactoryConfig& config) + : config(config) { +} + +Transaction TransactionFactory::create(const Proto::SigningInput& input) { + if (input.has_egld_transfer()) { + return fromEGLDTransfer(input); + } else if (input.has_esdt_transfer()) { + return fromESDTTransfer(input); + } else if (input.has_esdtnft_transfer()) { + return fromESDTNFTTransfer(input); + } else { + return fromGenericAction(input); + } +} + +/// Copies the input fields into a transaction object, without any other logic. +Transaction TransactionFactory::fromGenericAction(const Proto::SigningInput& input) { + auto action = input.generic_action(); + + Transaction transaction; + transaction.nonce = action.accounts().sender_nonce(); + transaction.sender = action.accounts().sender(); + transaction.senderUsername = action.accounts().sender_username(); + transaction.receiver = action.accounts().receiver(); + transaction.receiverUsername = action.accounts().receiver_username(); + transaction.guardian = action.accounts().guardian(); + transaction.value = action.value(); + transaction.data = action.data(); + transaction.gasLimit = input.gas_limit(); + transaction.gasPrice = input.gas_price(); + transaction.chainID = input.chain_id(); + transaction.version = action.version(); + transaction.options = static_cast(action.options()); + + return transaction; +} + +Transaction TransactionFactory::fromEGLDTransfer(const Proto::SigningInput& input) { + auto transfer = input.egld_transfer(); + + Transaction transaction; + transaction.nonce = transfer.accounts().sender_nonce(); + transaction.sender = transfer.accounts().sender(); + transaction.senderUsername = transfer.accounts().sender_username(); + transaction.receiver = transfer.accounts().receiver(); + transaction.receiverUsername = transfer.accounts().receiver_username(); + transaction.guardian = transfer.accounts().guardian(); + transaction.value = transfer.amount(); + transaction.data = transfer.data(); + transaction.gasPrice = coalesceGasPrice(input.gas_price()); + transaction.chainID = coalesceChainId(input.chain_id()); + transaction.version = transfer.version() ? transfer.version() : TX_VERSION; + transaction.options = decideOptions(transaction); + + // Estimate & set gasLimit: + uint64_t estimatedGasLimit = computeGasLimit(0, 0, transaction.hasGuardian()); + transaction.gasLimit = coalesceGasLimit(input.gas_limit(), estimatedGasLimit); + + return transaction; +} + +Transaction TransactionFactory::fromESDTTransfer(const Proto::SigningInput& input) { + auto transfer = input.esdt_transfer(); + + std::string encodedTokenIdentifier = Codec::encodeString(transfer.token_identifier()); + std::string encodedAmount = Codec::encodeBigInt(transfer.amount()); + std::string data = prepareFunctionCall("ESDTTransfer", {encodedTokenIdentifier, encodedAmount}); + + Transaction transaction; + transaction.nonce = transfer.accounts().sender_nonce(); + transaction.sender = transfer.accounts().sender(); + transaction.senderUsername = transfer.accounts().sender_username(); + transaction.receiver = transfer.accounts().receiver(); + transaction.receiverUsername = transfer.accounts().receiver_username(); + transaction.guardian = transfer.accounts().guardian(); + transaction.value = "0"; + transaction.data = data; + transaction.gasPrice = coalesceGasPrice(input.gas_price()); + transaction.chainID = coalesceChainId(input.chain_id()); + transaction.version = transfer.version() ? transfer.version() : TX_VERSION; + transaction.options = decideOptions(transaction); + + // Estimate & set gasLimit: + uint64_t executionGasLimit = this->config.getGasCostESDTTransfer() + this->config.getAdditionalGasForESDTTransfer(); + uint64_t estimatedGasLimit = computeGasLimit(data.size(), executionGasLimit, transaction.hasGuardian()); + transaction.gasLimit = coalesceGasLimit(input.gas_limit(), estimatedGasLimit); + + return transaction; +} + +Transaction TransactionFactory::fromESDTNFTTransfer(const Proto::SigningInput& input) { + auto transfer = input.esdtnft_transfer(); + + std::string encodedCollection = Codec::encodeString(transfer.token_collection()); + std::string encodedNonce = Codec::encodeUint64(transfer.token_nonce()); + std::string encodedQuantity = Codec::encodeBigInt(transfer.amount()); + std::string encodedReceiver = Codec::encodeAddress(transfer.accounts().receiver()); + std::string data = prepareFunctionCall("ESDTNFTTransfer", {encodedCollection, encodedNonce, encodedQuantity, encodedReceiver}); + + Transaction transaction; + transaction.nonce = transfer.accounts().sender_nonce(); + // For NFT, SFT and MetaESDT, transaction.sender == transaction.receiver. + transaction.sender = transfer.accounts().sender(); + transaction.receiver = transfer.accounts().sender(); + transaction.guardian = transfer.accounts().guardian(); + transaction.value = "0"; + transaction.data = data; + transaction.gasPrice = coalesceGasPrice(input.gas_price()); + transaction.chainID = coalesceChainId(input.chain_id()); + transaction.version = transfer.version() ? transfer.version() : TX_VERSION; + transaction.options = decideOptions(transaction); + + // Estimate & set gasLimit: + uint64_t executionGasLimit = this->config.getGasCostESDTNFTTransfer() + this->config.getAdditionalGasForESDTNFTTransfer(); + uint64_t estimatedGasLimit = computeGasLimit(data.size(), executionGasLimit, transaction.hasGuardian()); + transaction.gasLimit = coalesceGasLimit(input.gas_limit(), estimatedGasLimit); + + return transaction; +} + +uint64_t TransactionFactory::computeGasLimit(size_t dataLength, uint64_t executionGasLimit, bool hasGuardian) { + uint64_t dataMovementGasLimit = this->config.getMinGasLimit() + this->config.getGasPerDataByte() * dataLength; + uint64_t gasLimit = dataMovementGasLimit + executionGasLimit; + + if (hasGuardian) { + gasLimit += this->config.getExtraGasLimitForGuardedTransaction(); + } + + return gasLimit; +} + +uint64_t TransactionFactory::coalesceGasLimit(uint64_t providedGasLimit, uint64_t estimatedGasLimit) { + return providedGasLimit > 0 ? providedGasLimit : estimatedGasLimit; +} + +uint64_t TransactionFactory::coalesceGasPrice(uint64_t gasPrice) { + return gasPrice > 0 ? gasPrice : this->config.getMinGasPrice(); +} + +std::string TransactionFactory::coalesceChainId(std::string chainID) { + return chainID.empty() ? this->config.getChainId() : chainID; +} + +TransactionOptions TransactionFactory::decideOptions(const Transaction& transaction) { + TransactionOptions options = TransactionOptions::Default; + + if (transaction.hasGuardian()) { + options = static_cast(options | TransactionOptions::Guarded); + } + + return options; +} + +std::string TransactionFactory::prepareFunctionCall(const std::string& function, std::initializer_list arguments) { + const auto ARGUMENTS_SEPARATOR = "@"; + std::string result; + + result.append(function); + + for (auto argument : arguments) { + result.append(ARGUMENTS_SEPARATOR); + result.append(argument); + } + + return result; +} + +} // namespace TW::MultiversX diff --git a/src/MultiversX/TransactionFactory.h b/src/MultiversX/TransactionFactory.h new file mode 100644 index 00000000000..f3a62e019e6 --- /dev/null +++ b/src/MultiversX/TransactionFactory.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Address.h" +#include "Transaction.h" +#include "TransactionFactoryConfig.h" +#include "uint256.h" +#include "../proto/MultiversX.pb.h" + +namespace TW::MultiversX { + +/// Creates specific transaction objects, wrt. the provided "TransactionFactoryConfig". +class TransactionFactory { +private: + TransactionFactoryConfig config; + +public: + TransactionFactory(); + TransactionFactory(const TransactionFactoryConfig& config); + + /// Creates the appropriate transaction object, with respect to the "oneof" field (substructure) of Proto::SigningInput. + Transaction create(const Proto::SigningInput& input); + + Transaction fromGenericAction(const Proto::SigningInput& input); + + /// This should be used to transfer EGLD. + /// For reference, see: https://docs.multiversx.com/developers/signing-transactions/signing-transactions. + Transaction fromEGLDTransfer(const Proto::SigningInput& input); + + /// This should be used to transfer regular ESDTs (fungible tokens). + /// For reference, see: https://docs.multiversx.com/developers/esdt-tokens + /// + /// The "regular" ESDT tokens held by an account can be fetched from https://api.multiversx.com/accounts/{address}/tokens. + Transaction fromESDTTransfer(const Proto::SigningInput& input); + + /// This should be used to transfer NFTs, SFTs and Meta ESDTs. + /// For reference, see: https://docs.multiversx.com/developers/nft-tokens + /// + /// The semi-fungible and non-fungible tokens held by an account can be fetched from https://api.multiversx.com/accounts/{address}/nfts?type=SemiFungibleESDT,NonFungibleESDT. + /// The Meta ESDTs (a special kind of SFTs) held by an account can be fetched from https://api.multiversx.com/accounts/{address}/nfts?type=MetaESDT. + /// + /// The fields "token_collection" and "token_nonce" are found as well in the HTTP response of the API call (as "collection" and "nonce", respectively). + Transaction fromESDTNFTTransfer(const Proto::SigningInput& input); + +private: + uint64_t computeGasLimit(size_t dataLength, uint64_t executionGasLimit, bool hasGuardian); + uint64_t coalesceGasLimit(uint64_t providedGasLimit, uint64_t estimatedGasLimit); + uint64_t coalesceGasPrice(uint64_t gasPrice); + std::string coalesceChainId(std::string chainID); + TransactionOptions decideOptions(const Transaction& transaction); + std::string prepareFunctionCall(const std::string& function, std::initializer_list arguments); +}; + +} // namespace TW::MultiversX diff --git a/src/MultiversX/TransactionFactoryConfig.cpp b/src/MultiversX/TransactionFactoryConfig.cpp new file mode 100644 index 00000000000..1c8ff82f9bb --- /dev/null +++ b/src/MultiversX/TransactionFactoryConfig.cpp @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TransactionFactoryConfig.h" + +#include + +namespace TW::MultiversX { + +TransactionFactoryConfig::TransactionFactoryConfig() + : chainId("1") /* Mainnet */ { +} + +const std::string& TransactionFactoryConfig::getChainId() const { + return this->chainId; +} + +void TransactionFactoryConfig::setChainId(const std::string& value) { + this->chainId = value; +} + +uint32_t TransactionFactoryConfig::getGasPerDataByte() const { + return this->gasPerDataByte; +} + +void TransactionFactoryConfig::setGasPerDataByte(uint32_t value) { + this->gasPerDataByte = value; +} + +uint32_t TransactionFactoryConfig::getMinGasLimit() const { + return this->minGasLimit; +} + +void TransactionFactoryConfig::setMinGasLimit(uint32_t value) { + this->minGasLimit = value; +} + +uint32_t TransactionFactoryConfig::getExtraGasLimitForGuardedTransaction() const { + return this->minGasLimit; +} + +void TransactionFactoryConfig::setExtraGasLimitForGuardedTransaction(uint32_t value) { + this->extraGasLimitForGuardedTransaction = value; +} + +uint64_t TransactionFactoryConfig::getMinGasPrice() const { + return this->minGasPrice; +} + +void TransactionFactoryConfig::setMinGasPrice(uint64_t value) { + this->minGasPrice = value; +} + +uint32_t TransactionFactoryConfig::getGasCostESDTTransfer() const { + return this->gasCostESDTTransfer; +} + +void TransactionFactoryConfig::setGasCostESDTTransfer(uint32_t value) { + this->gasCostESDTTransfer = value; +} + +uint32_t TransactionFactoryConfig::getGasCostESDTNFTTransfer() const { + return this->gasCostESDTNFTTransfer; +} + +void TransactionFactoryConfig::setGasCostESDTNFTTransfer(uint32_t value) { + this->gasCostESDTNFTTransfer = value; +} + +uint64_t TransactionFactoryConfig::getAdditionalGasForESDTTransfer() const { + return this->additionalGasForESDTTransfer; +} + +void TransactionFactoryConfig::setAdditionalGasForESDTTransfer(uint64_t value) { + this->additionalGasForESDTTransfer = value; +} + +uint64_t TransactionFactoryConfig::getAdditionalGasForESDTNFTTransfer() const { + return this->additionalGasForESDTNFTTransfer; +} + +void TransactionFactoryConfig::setAdditionalGasForESDTNFTTransfer(uint64_t value) { + this->additionalGasForESDTNFTTransfer = value; +} + +TransactionFactoryConfig TransactionFactoryConfig::GetDefault() { + const uint64_t timestamp = duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + return GetByTimestamp(timestamp); +} + +TransactionFactoryConfig TransactionFactoryConfig::GetByTimestamp(uint64_t timestamp) { + TransactionFactoryConfig config; + + // Mainnet values at the time of defining the "TransactionFactoryConfig" component (April / May 2023). + if (timestamp > 0) { + config.setGasPerDataByte(1500); + config.setMinGasLimit(50000); + config.setExtraGasLimitForGuardedTransaction(50000); + config.setMinGasPrice(1000000000); + config.setGasCostESDTTransfer(200000); + config.setGasCostESDTNFTTransfer(200000); + config.setAdditionalGasForESDTTransfer(100000); + config.setAdditionalGasForESDTNFTTransfer(500000); + } + + return config; +} + +} // namespace TW::MultiversX diff --git a/src/MultiversX/TransactionFactoryConfig.h b/src/MultiversX/TransactionFactoryConfig.h new file mode 100644 index 00000000000..2cf157edd73 --- /dev/null +++ b/src/MultiversX/TransactionFactoryConfig.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include + +namespace TW::MultiversX { + +/// A "TransactionFactoryConfig" object holds the network parameters relevant to creating transactions (e.g. minimum gas limit, minimum gas price). +class TransactionFactoryConfig { + /// The following fields can (should) be fetched from https://api.multiversx.com/network/config. + /// However, a "TransactionFactoryConfig" object is initialized with proper default values for Mainnet (as of 2023). + std::string chainId; + uint32_t gasPerDataByte; + uint32_t minGasLimit; + uint32_t extraGasLimitForGuardedTransaction; + uint64_t minGasPrice; + + /// GasSchedule entries of interest (only one at this moment), according to: https://github.com/multiversx/mx-chain-mainnet-config/blob/master/gasSchedules. + /// Here, for the sake of simplicity, we define the gas costs of interest directly in the class "TransactionFactoryConfig" + /// (that is, without defining extra nested structures such as "GasSchedule" and "BuiltInCosts"). + uint32_t gasCostESDTTransfer; + uint32_t gasCostESDTNFTTransfer; + + // Additional gas to account for eventual increases in gas requirements (thus avoid breaking changes in clients of TW-core). + uint64_t additionalGasForESDTTransfer; + + // Additional gas to account for extra blockchain operations (e.g. data movement (between accounts) for NFTs), + // and for eventual increases in gas requirements (thus avoid breaking changes in clients of TW-core). + uint64_t additionalGasForESDTNFTTransfer; + +public: + TransactionFactoryConfig(); + + const std::string& getChainId() const; + void setChainId(const std::string& value); + + uint32_t getGasPerDataByte() const; + void setGasPerDataByte(uint32_t value); + + uint32_t getMinGasLimit() const; + void setMinGasLimit(uint32_t value); + + uint32_t getExtraGasLimitForGuardedTransaction() const; + void setExtraGasLimitForGuardedTransaction(uint32_t value); + + uint64_t getMinGasPrice() const; + void setMinGasPrice(uint64_t value); + + uint32_t getGasCostESDTTransfer() const; + void setGasCostESDTTransfer(uint32_t value); + + uint64_t getAdditionalGasForESDTTransfer() const; + void setAdditionalGasForESDTTransfer(uint64_t value); + + uint64_t getAdditionalGasForESDTNFTTransfer() const; + void setAdditionalGasForESDTNFTTransfer(uint64_t value); + + uint32_t getGasCostESDTNFTTransfer() const; + void setGasCostESDTNFTTransfer(uint32_t value); + + static TransactionFactoryConfig GetDefault(); + + /// Useful to implement upwards-compatible changes of the network configuration (a TWCore client can receive planned configuration updates, in advance). + static TransactionFactoryConfig GetByTimestamp(uint64_t timestamp); +}; + +} // namespace TW::MultiversX diff --git a/src/NEAR/Account.cpp b/src/NEAR/Account.cpp index bf9762fbb3c..08c0ea8adb5 100644 --- a/src/NEAR/Account.cpp +++ b/src/NEAR/Account.cpp @@ -1,17 +1,15 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Account.h" #include -using namespace TW; -using namespace TW::NEAR; +namespace TW::NEAR { static auto pattern = std::regex(R"(^(([a-z\d]+[\-_])*[a-z\d]+\.)*([a-z\d]+[\-_])*[a-z\d]+$)"); + bool Account::isValid(const std::string& string) { // https://docs.near.org/docs/concepts/account#account-id-rules if (string.size() < 2 || string.size() > 64) { @@ -20,3 +18,5 @@ bool Account::isValid(const std::string& string) { std::smatch match; return regex_search(string, match, pattern); } + +} // namespace TW::NEAR diff --git a/src/NEAR/Account.h b/src/NEAR/Account.h index 1b2c090e8e9..6ae30ebcd5a 100644 --- a/src/NEAR/Account.h +++ b/src/NEAR/Account.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/NEAR/Address.cpp b/src/NEAR/Address.cpp index 3d8eeff1fad..4ba2f6aca7a 100644 --- a/src/NEAR/Address.cpp +++ b/src/NEAR/Address.cpp @@ -1,23 +1,22 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. +#include "Address.h" #include "Base58.h" #include "HexCoding.h" -#include "Address.h" #include using namespace TW; -using namespace TW::NEAR; + +namespace TW::NEAR { bool Address::isValid(const std::string& string) { const auto data = Address::decodeLegacyAddress(string); if (data.has_value()) { return true; - } + } const auto parsed = parse_hex(string); return parsed.size() == PublicKey::ed25519Size; } @@ -26,11 +25,14 @@ bool Address::isValid(const std::string& string) { std::optional Address::decodeLegacyAddress(const std::string& string) { const auto prefix = std::string("NEAR"); if (string.substr(0, prefix.size()) != prefix) { - return {}; + return std::nullopt; } - const Data& decoded = Base58::bitcoin.decode(string.substr(prefix.size())); - return Data(decoded.begin(), decoded.end() - 4); + const Data& decoded = Base58::decode(string.substr(prefix.size())); + if (decoded.size() != size + legacyChecksumSize) { + return std::nullopt; + } + return Data(decoded.begin(), decoded.end() - legacyChecksumSize); } /// Initializes a NEAR address from a string representation. @@ -39,10 +41,10 @@ Address::Address(const std::string& string) { if (data.has_value()) { std::copy(std::begin(*data), std::end(*data), std::begin(bytes)); } else { - if (!Address::isValid(string)) { - throw std::invalid_argument("Invalid address string!"); - } const auto parsed = parse_hex(string); + if (parsed.size() != PublicKey::ed25519Size) { + throw std::invalid_argument("Invalid address string!"); + } std::copy(std::begin(parsed), std::end(parsed), std::begin(bytes)); } } @@ -58,3 +60,5 @@ Address::Address(const PublicKey& publicKey) { std::string Address::string() const { return hex(bytes); } + +} // namespace TW::NEAR diff --git a/src/NEAR/Address.h b/src/NEAR/Address.h index 5a81fd24c03..4ee35512d36 100644 --- a/src/NEAR/Address.h +++ b/src/NEAR/Address.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include @@ -16,8 +14,10 @@ namespace TW::NEAR { class Address { public: - /// Number of bytes in an address, public key size + /// Number of bytes in an address, public key size. static const size_t size = PublicKey::ed25519Size; + /// Number of bytes of a checksum in a legacy address. + static const size_t legacyChecksumSize = 4; /// Address data consisting of a prefix byte followed by the public key /// hash. diff --git a/src/NEAR/Entry.cpp b/src/NEAR/Entry.cpp index bb2da8a8f0c..9be0149177b 100644 --- a/src/NEAR/Entry.cpp +++ b/src/NEAR/Entry.cpp @@ -1,37 +1,60 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" - #include "Address.h" +#include "Serialization.h" #include "Signer.h" +#include "../proto/Common.pb.h" +#include "../proto/TransactionCompiler.pb.h" -using namespace TW::NEAR; using namespace TW; using namespace std; +namespace TW::NEAR { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::normalizeAddress(TWCoinType coin, const string& address) const { +string Entry::normalizeAddress([[maybe_unused]] TWCoinType coin, const string& address) const { return Address(address).string(); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { const auto addr = Address(address); return {addr.bytes.begin(), addr.bytes.end()}; } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + const auto signer = Signer(input); + auto preImage = signer.signaturePreimage(); + auto preImageHash = Hash::sha256(preImage); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + const auto signer = Signer(input); + output = signer.compile(signature, publicKey); + }); +} +} // namespace TW::NEAR diff --git a/src/NEAR/Entry.h b/src/NEAR/Entry.h index b54a934db47..f18a67be084 100644 --- a/src/NEAR/Entry.h +++ b/src/NEAR/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,13 +10,18 @@ namespace TW::NEAR { /// Entry point for implementation of NEAR coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string normalizeAddress(TWCoinType coin, const std::string& address) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, + const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::NEAR diff --git a/src/NEAR/Serialization.cpp b/src/NEAR/Serialization.cpp index 71ab3d2f181..f2272383ede 100644 --- a/src/NEAR/Serialization.cpp +++ b/src/NEAR/Serialization.cpp @@ -1,18 +1,19 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Serialization.h" #include "../BinaryCoding.h" #include "../PrivateKey.h" -using namespace TW; -using namespace TW::NEAR; -using namespace TW::NEAR::Proto; +#include +namespace TW::NEAR { + +using json = nlohmann::json; + +static constexpr auto tokenTransferMethodName = "ft_transfer"; static void writeU8(Data& data, uint8_t number) { data.push_back(number); @@ -27,10 +28,12 @@ static void writeU64(Data& data, uint64_t number) { } static void writeU128(Data& data, const std::string& numberData) { + assert(numberData.size() == 16 && "U128 number should be 16 bytes long"); data.insert(std::end(data), std::begin(numberData), std::end(numberData)); } -template static void writeRawBuffer(Data& data, const T& buf) { +template +static void writeRawBuffer(Data& data, const T& buf) { data.insert(std::end(data), std::begin(buf), std::end(buf)); } @@ -51,29 +54,114 @@ static void writeTransfer(Data& data, const Proto::Transfer& transfer) { static void writeFunctionCall(Data& data, const Proto::FunctionCall& functionCall) { writeString(data, functionCall.method_name()); - + writeU32(data, static_cast(functionCall.args().size())); writeRawBuffer(data, functionCall.args()); - + writeU64(data, functionCall.gas()); writeU128(data, functionCall.deposit()); } +static void writeStake(Data& data, const Proto::Stake& stake) { + writeU128(data, stake.stake()); + writePublicKey(data, stake.public_key()); +} + +static void writeFunctionCallPermission(Data& data, const Proto::FunctionCallPermission& functionCallPermission) { + if (functionCallPermission.allowance().empty()) { + writeU8(data, 0); + } else { + writeU8(data, 1); + writeU128(data, functionCallPermission.allowance()); + } + writeString(data, functionCallPermission.receiver_id()); + writeU32(data, static_cast(functionCallPermission.method_names().size())); + for (auto&& methodName : functionCallPermission.method_names()) { + writeString(data, methodName); + } +} + +static void writeAccessKey(Data& data, const Proto::AccessKey& accessKey) { + writeU64(data, accessKey.nonce()); + switch (accessKey.permission_case()) { + case Proto::AccessKey::kFunctionCall: + writeU8(data, 0); + writeFunctionCallPermission(data, accessKey.function_call()); + break; + case Proto::AccessKey::kFullAccess: + writeU8(data, 1); + break; + case Proto::AccessKey::PERMISSION_NOT_SET: + break; + } +} + +static void writeAddKey(Data& data, const Proto::AddKey& addKey) { + writePublicKey(data, addKey.public_key()); + writeAccessKey(data, addKey.access_key()); +} + +static void writeDeleteKey(Data& data, const Proto::DeleteKey& deleteKey) { + writePublicKey(data, deleteKey.public_key()); +} + +static void writeDeleteAccount(Data& data, const Proto::DeleteAccount& deleteAccount) { + writeString(data, deleteAccount.beneficiary_id()); +} + +static void writeTokenTransfer(Data& data, const Proto::TokenTransfer& tokenTransfer) { + writeString(data, tokenTransferMethodName); + + json functionCallArgs = { + {"amount", tokenTransfer.token_amount()}, + {"receiver_id", tokenTransfer.receiver_id()}, + }; + auto functionCallArgsStr = functionCallArgs.dump(); + + writeU32(data, static_cast(functionCallArgsStr.size())); + writeRawBuffer(data, functionCallArgsStr); + + writeU64(data, tokenTransfer.gas()); + writeU128(data, tokenTransfer.deposit()); +} + static void writeAction(Data& data, const Proto::Action& action) { - writeU8(data, action.payload_case() - Proto::Action::kCreateAccount); + uint8_t actionByte = action.payload_case() - Proto::Action::kCreateAccount; + // `TokenTransfer` action is actually a `FunctionCall`, + // so we need to set the actionByte to the proper value. + if (action.payload_case() == Proto::Action::kTokenTransfer) { + actionByte = Proto::Action::kFunctionCall - Proto::Action::kCreateAccount; + } + + writeU8(data, actionByte); switch (action.payload_case()) { - case Proto::Action::kTransfer: - writeTransfer(data, action.transfer()); - return; - case Proto::Action::kFunctionCall: - writeFunctionCall(data, action.function_call()); - return; - default: - return; + case Proto::Action::kFunctionCall: + writeFunctionCall(data, action.function_call()); + return; + case Proto::Action::kTransfer: + writeTransfer(data, action.transfer()); + return; + case Proto::Action::kStake: + writeStake(data, action.stake()); + return; + case Proto::Action::kAddKey: + writeAddKey(data, action.add_key()); + return; + case Proto::Action::kDeleteKey: + writeDeleteKey(data, action.delete_key()); + return; + case Proto::Action::kDeleteAccount: + writeDeleteAccount(data, action.delete_account()); + return; + case Proto::Action::kTokenTransfer: + writeTokenTransfer(data, action.token_transfer()); + return; + default: + return; } } -Data TW::NEAR::transactionData(const Proto::SigningInput& input) { +Data transactionData(const Proto::SigningInput& input) { Data data; writeString(data, input.signer_id()); auto key = PrivateKey(input.private_key()); @@ -92,10 +180,29 @@ Data TW::NEAR::transactionData(const Proto::SigningInput& input) { return data; } -Data TW::NEAR::signedTransactionData(const Data& transactionData, const Data& signatureData) { +Data transactionDataWithPublicKey(const Proto::SigningInput& input) { + Data data; + writeString(data, input.signer_id()); + auto public_key_proto = Proto::PublicKey(); + public_key_proto.set_data(input.public_key().data(), input.public_key().size()); + writePublicKey(data, public_key_proto); + writeU64(data, input.nonce()); + writeString(data, input.receiver_id()); + const auto& block_hash = input.block_hash(); + writeRawBuffer(data, block_hash); + writeU32(data, input.actions_size()); + for (const auto& action : input.actions()) { + writeAction(data, action); + } + return data; +} + +Data signedTransactionData(const Data& transactionData, const Data& signatureData) { Data data; writeRawBuffer(data, transactionData); writeU8(data, 0); writeRawBuffer(data, signatureData); return data; } + +} // namespace TW::NEAR diff --git a/src/NEAR/Serialization.h b/src/NEAR/Serialization.h index 1e2ecd9e493..e1025553cc3 100644 --- a/src/NEAR/Serialization.h +++ b/src/NEAR/Serialization.h @@ -1,17 +1,15 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../proto/NEAR.pb.h" -#include "../Data.h" +#include "Data.h" namespace TW::NEAR { Data transactionData(const Proto::SigningInput& input); Data signedTransactionData(const Data& transactionData, const Data& signatureData); - +Data transactionDataWithPublicKey(const Proto::SigningInput& input); } // namespace TW::NEAR diff --git a/src/NEAR/Signer.cpp b/src/NEAR/Signer.cpp index bdb4cf47fed..9e2589ed09e 100644 --- a/src/NEAR/Signer.cpp +++ b/src/NEAR/Signer.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "Serialization.h" @@ -10,8 +8,7 @@ #include "../Hash.h" #include "../PrivateKey.h" -using namespace TW; -using namespace TW::NEAR; +namespace TW::NEAR { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto transaction = transactionData(input); @@ -24,3 +21,27 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { output.set_hash(hash.data(), hash.size()); return output; } + +Data Signer::signaturePreimage() const { + return TW::NEAR::transactionDataWithPublicKey(input); +}; + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey) const { + // validate public key + if (publicKey.type != TWPublicKeyTypeED25519) { + throw std::invalid_argument("Invalid public key"); + } + auto preImage = signaturePreimage(); + auto hash = Hash::sha256(preImage); + { + // validate correctness of signature + if (!publicKey.verify(signature, hash)) { + throw std::invalid_argument("Invalid signature/hash/publickey combination"); + } + } + auto signedPreImage = TW::NEAR::signedTransactionData(preImage, signature); + auto output = Proto::SigningOutput(); + output.set_signed_transaction(signedPreImage.data(), signedPreImage.size()); + return output; +} +} // namespace TW::NEAR diff --git a/src/NEAR/Signer.h b/src/NEAR/Signer.h index 5f7fd3a2752..1343851b9ab 100644 --- a/src/NEAR/Signer.h +++ b/src/NEAR/Signer.h @@ -1,22 +1,26 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../proto/NEAR.pb.h" +#include "../Data.h" +#include "../PublicKey.h" namespace TW::NEAR { /// Helper class that performs NEAR transaction signing. class Signer { public: + Proto::SigningInput input; Signer() = delete; - + /// Initializes a transaction signer. + explicit Signer(const Proto::SigningInput& input) : input(input) {} /// Signs the given transaction. static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) const; + Data signaturePreimage() const; }; } // namespace TW::NEAR diff --git a/src/NEO/Address.cpp b/src/NEO/Address.cpp index 229e61f27d4..3c4ee0ebd0c 100644 --- a/src/NEO/Address.cpp +++ b/src/NEO/Address.cpp @@ -1,28 +1,26 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../Ontology/ParamsBuilder.h" +#include "OpCode.h" #include "../Base58.h" +#include "Data.h" #include "../Hash.h" -#include "../Data.h" -#include "OpCode.h" #include "Address.h" using namespace TW; -using namespace TW::NEO; + +namespace TW::NEO { bool Address::isValid(const std::string& string) { - const auto decoded = Base58::bitcoin.decodeCheck(string); + const auto decoded = Base58::decodeCheck(string); return !(decoded.size() != Address::size || decoded[0] != version); } Address::Address() { Data keyHash; - for (int i = 0; i < Address::size; i++) { + for (auto i = 0ul; i < Address::size; i++) { keyHash.push_back(0); } std::copy(keyHash.data(), keyHash.data() + Address::size, bytes.begin()); @@ -36,7 +34,7 @@ Address::Address(const PublicKey& publicKey) { pkdata.push_back(CHECKSIG); auto keyHash = Hash::ripemd(Hash::sha256(pkdata)); - keyHash.insert(keyHash.begin(), (byte) Address::version); + keyHash.insert(keyHash.begin(), (byte)Address::version); if (keyHash.size() != Address::size) { throw std::invalid_argument("Invalid address key data"); @@ -45,11 +43,6 @@ Address::Address(const PublicKey& publicKey) { std::copy(keyHash.data(), keyHash.data() + Address::size, bytes.begin()); } -Address::Address(uint8_t m, const std::vector& publicKeys) { - auto builderData = toScriptHash(Ontology::ParamsBuilder::fromMultiPubkey(m, publicKeys)); - std::copy(builderData.begin(), builderData.end(), bytes.begin()); -} - Data Address::toScriptHash(const Data& data) const { return Hash::ripemd(Hash::sha256(data)); } @@ -60,3 +53,5 @@ Data Address::toScriptHash() const { std::copy(bytes.begin() + 1, bytes.begin() + Hash::ripemdSize + 1, data.begin()); return data; } + +} // namespace TW::NEO diff --git a/src/NEO/Address.h b/src/NEO/Address.h index d3d78ab7088..4ab69e36cc1 100644 --- a/src/NEO/Address.h +++ b/src/NEO/Address.h @@ -1,15 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include #include "../Base58Address.h" -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" namespace TW::NEO { @@ -31,9 +29,6 @@ class Address : public TW::Base58Address { /// Initializes a NEO address with a collection of bytes. explicit Address(const Data& data) : TW::Base58Address(data) {} - /// Initializes an address with a collection of public key. - explicit Address(uint8_t m, const std::vector& publicKeys); - /// Initializes a NEO address with a public key. explicit Address(const PublicKey &publicKey); @@ -49,4 +44,4 @@ inline bool operator==(const Address& lhs, const Address& rhs) { return lhs.string() == rhs.string(); } -} // namespace TW::NEO \ No newline at end of file +} // namespace TW::NEO diff --git a/src/NEO/BinaryCoding.h b/src/NEO/BinaryCoding.h new file mode 100644 index 00000000000..03aaa8843b0 --- /dev/null +++ b/src/NEO/BinaryCoding.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "OpCode.h" +#include "uint256.h" +#include "../BinaryCoding.h" + +namespace TW::NEO { + +// Append a uint256_t value as a little-endian byte array into the provided buffer, and limit +// the array size by digit/8. +// Noted: No padding with it. +inline void encode256LE(Data& data, const uint256_t& value) { + Data bytes = store(value); + data.insert(data.end(), bytes.rbegin(), bytes.rend()); + if (data.back() >= 128) { + data.push_back(0x00); + } +} + +inline void encodeBytes(Data& data, const Data& value) { + if (value.size() <= (size_t)PUSHBYTES75) { + data.push_back((byte)value.size()); + data.insert(data.end(), value.begin(), value.end()); + } else if (value.size() < 0x100) { + data.push_back(PUSHDATA1); + data.push_back((byte)value.size()); + data.insert(data.end(), value.begin(), value.end()); + } else if (value.size() < 0x10000) { + data.push_back(PUSHDATA2); + encode16LE((uint16_t)value.size(), data); + data.insert(data.end(), value.begin(), value.end()); + } else { + data.push_back(PUSHDATA4); + encode32LE((uint32_t)value.size(), data); + data.insert(data.end(), value.begin(), value.end()); + } +} + +} // namespace TW::NEO diff --git a/src/NEO/CoinReference.h b/src/NEO/CoinReference.h index ad185c0e143..6d08a78b257 100644 --- a/src/NEO/CoinReference.h +++ b/src/NEO/CoinReference.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../uint256.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../BinaryCoding.h" #include "ISerializable.h" @@ -15,27 +13,31 @@ namespace TW::NEO { -class CoinReference : public Serializable { +class CoinReference final: public Serializable { public: /// Number of bytes for prevIndex. static const size_t prevIndexSize = 2; + static const size_t prevHashSize = 32; uint256_t prevHash; uint16_t prevIndex = 0; - virtual ~CoinReference() {} + ~CoinReference() override = default; - int64_t size() const override { + size_t size() const override { return Hash::sha256Size + prevIndexSize; } - void deserialize(const Data& data, int initial_pos = 0) override { + void deserialize(const Data& data, size_t initial_pos = 0) override { + if (data.size() < initial_pos + size()) { + throw std::invalid_argument("Data::Cannot read enough bytes!"); + } prevHash = load(readBytes(data, Hash::sha256Size, initial_pos)); prevIndex = decode16LE(data.data() + initial_pos + Hash::sha256Size); } Data serialize() const override { - auto resp = store(prevHash); + auto resp = store(prevHash, prevHashSize); encode16LE(prevIndex, resp); return resp; } @@ -46,4 +48,4 @@ class CoinReference : public Serializable { } }; -} \ No newline at end of file +} // namespace TW::NEO diff --git a/src/NEO/Constants.h b/src/NEO/Constants.h new file mode 100644 index 00000000000..73866c3ed4f --- /dev/null +++ b/src/NEO/Constants.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +namespace TW::NEO { + +static const size_t assetIdSize = 32; +static const size_t contractHashSize = 32; +static const size_t valueSize = 8; +static const size_t scriptHashSize = 20; + +} // namespace TW::NEO diff --git a/src/NEO/Entry.cpp b/src/NEO/Entry.cpp index 9920ff2cc62..4fc03845630 100644 --- a/src/NEO/Entry.cpp +++ b/src/NEO/Entry.cpp @@ -1,35 +1,61 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" #include "Signer.h" +#include "proto/TransactionCompiler.pb.h" -using namespace TW::NEO; using namespace TW; using namespace std; -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const { +namespace TW::NEO { + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { const auto addr = Address(address); return {addr.bytes.begin(), addr.bytes.end()}; } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -void Entry::plan(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::plan([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { planTemplate(dataIn, dataOut); } + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto encoded = Signer::signaturePreimage(input); + auto hash = TW::Hash::sha256(encoded); + output.set_data_hash(hash.data(), hash.size()); + output.set_data(encoded.data(), encoded.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.empty() || publicKeys.empty() || signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + + auto encoded = Signer::encodeTransaction(input, publicKeys, signatures); + output.set_encoded(encoded.data(), encoded.size()); + }); +} + +} // namespace TW::NEO diff --git a/src/NEO/Entry.h b/src/NEO/Entry.h index d6e1b12e607..c38662a8d74 100644 --- a/src/NEO/Entry.h +++ b/src/NEO/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,13 +10,16 @@ namespace TW::NEO { /// NEO entry dispatcher. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final: public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + Data addressToData(TWCoinType coin, const std::string& address) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; }; } // namespace TW::NEO diff --git a/src/NEO/ISerializable.h b/src/NEO/ISerializable.h index 64e30ee0754..79461bd5c92 100644 --- a/src/NEO/ISerializable.h +++ b/src/NEO/ISerializable.h @@ -1,23 +1,21 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../BinaryCoding.h" #include "ReadData.h" namespace TW::NEO { class ISerializable { - public: - virtual ~ISerializable() {} - virtual int64_t size() const = 0; +public: + virtual ~ISerializable() = default; + virtual size_t size() const = 0; virtual Data serialize() const = 0; - virtual void deserialize(const Data& data, int initial_pos = 0) = 0; + virtual void deserialize(const Data& data, size_t initial_pos) = 0; }; } // namespace TW::NEO diff --git a/src/NEO/InvocationTransaction.h b/src/NEO/InvocationTransaction.h new file mode 100644 index 00000000000..bc88148f195 --- /dev/null +++ b/src/NEO/InvocationTransaction.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Transaction.h" +#include "../Data.h" + +namespace TW::NEO { + +class InvocationTransaction final: public Transaction { +public: + Data script; + uint64_t gas = 0; + + explicit InvocationTransaction(TransactionType t = TransactionType::TT_InvocationTransaction, byte ver = 1) + : Transaction(t, ver) {} + + size_t deserializeExclusiveData(const Data& data, size_t initial_pos) override { + uint32_t readBytes = 0; + script = readVarBytes(data, initial_pos, &readBytes); + if (version >= 1) { + gas = decode64LE(data.data() + initial_pos + readBytes); + readBytes += sizeof(gas); + } + return initial_pos + static_cast(readBytes); + } + + Data serializeExclusiveData() const override { + auto resp = writeVarBytes(script); + if (version >= 1) { + encode64LE(gas, resp); + } + return resp; + } + + bool operator==(const InvocationTransaction& other) const { + return this->script == other.script && this->gas == other.gas && + Transaction::operator==(other); + } +}; + +} // namespace TW::NEO diff --git a/src/NEO/MinerTransaction.h b/src/NEO/MinerTransaction.h index ca73b306958..ce2c9c2778e 100644 --- a/src/NEO/MinerTransaction.h +++ b/src/NEO/MinerTransaction.h @@ -1,26 +1,24 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "Transaction.h" namespace TW::NEO { -class MinerTransaction : public Transaction { +class MinerTransaction final: public Transaction { public: uint32_t nonce; - virtual int deserializeExclusiveData(const Data& data, int initial_pos = 0) { + size_t deserializeExclusiveData(const Data& data, size_t initial_pos) override { nonce = decode32LE(data.data() + initial_pos); return initial_pos + 4; } - virtual Data serializeExclusiveData() const { + Data serializeExclusiveData() const override { auto resp = Data(); encode32LE(nonce, resp); return resp; diff --git a/src/NEO/OpCode.h b/src/NEO/OpCode.h index 847c5b74fa6..db1dc7eddb6 100644 --- a/src/NEO/OpCode.h +++ b/src/NEO/OpCode.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -10,8 +8,22 @@ namespace TW::NEO { +static const uint8_t PUSHBYTES1{0x01}; static const uint8_t PUSHBYTES21{0x21}; static const uint8_t PUSHBYTES40{0x40}; +static const uint8_t PUSHBYTES75{0x4B}; +static const uint8_t PUSHDATA1{0x4C}; +static const uint8_t PUSHDATA2{0x4D}; +static const uint8_t PUSHDATA4{0x4E}; +static const uint8_t PUSH0{0x00}; +static const uint8_t PUSH1{0x51}; +static const uint8_t PUSH2{0x52}; +static const uint8_t PUSH3{0x53}; +static const uint8_t PUSH5{0x55}; +static const uint8_t RET{0x66}; +static const uint8_t APPCALL{0x67}; static const uint8_t CHECKSIG{0xAC}; +static const uint8_t PACK{0xC1}; +static const uint8_t THROWIFNOT{0xF1}; -} // namespace TW::NEO \ No newline at end of file +} // namespace TW::NEO diff --git a/src/NEO/ReadData.cpp b/src/NEO/ReadData.cpp index e8f3487e4d8..97580a6c1c4 100644 --- a/src/NEO/ReadData.cpp +++ b/src/NEO/ReadData.cpp @@ -1,31 +1,29 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../Data.h" +#include "Data.h" #include "ReadData.h" #include -TW::Data TW::readBytes(const TW::Data& from, int max, int initial_pos) { - if (from.size() - initial_pos < max) { +TW::Data TW::readBytes(const TW::Data& from, size_t max, size_t initial_pos) { + if (from.size() < initial_pos + max) { throw std::invalid_argument("Data::Cannot read enough bytes!"); } - return TW::Data(from.begin() + initial_pos, from.begin() + initial_pos + max); + return TW::subData(from, initial_pos, max); } -TW::Data TW::readVarBytes(const Data& from, int initial_pos, uint32_t* dataRead) { +TW::Data TW::readVarBytes(const Data& from, size_t initial_pos, uint32_t* dataRead) { uint64_t size = readVar(from, initial_pos); auto shift = varIntSize(size); if (dataRead) { *dataRead = uint32_t(shift + size); } - return readBytes(from, int(size), initial_pos + int(shift)); + return readBytes(from, int(size), initial_pos + static_cast(shift)); } -template<> uint64_t TW::readVar(const TW::Data& from, int initial_pos, const uint64_t &max) { +template<> uint64_t TW::readVar(const TW::Data& from, size_t initial_pos, const uint64_t &max) { byte fb = from[initial_pos]; uint64_t value; if (fb == 0xFD) { @@ -44,11 +42,11 @@ template<> uint64_t TW::readVar(const TW::Data& from, int initial_pos, const uin return value; } -template<> int64_t TW::readVar(const TW::Data& from, int initial_pos, const int64_t &max) { +template<> int64_t TW::readVar(const TW::Data& from, size_t initial_pos, const int64_t &max) { return (int64_t) readVar(from, initial_pos, uint64_t(max)); } -TW::Data TW::writeVarBytes(const Data& from, int initial_pos) { +TW::Data TW::writeVarBytes(const Data& from, size_t initial_pos) { Data resp; encodeVarInt(uint64_t(from.size() - initial_pos), resp); resp.insert(resp.end(), from.begin() + initial_pos, from.end()); diff --git a/src/NEO/ReadData.h b/src/NEO/ReadData.h index 3c273d75dec..8c661c29e90 100644 --- a/src/NEO/ReadData.h +++ b/src/NEO/ReadData.h @@ -1,33 +1,31 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include #include -#include "../Data.h" +#include "Data.h" #include "../BinaryCoding.h" namespace TW { -Data readBytes(const Data& from, int max, int initial_pos = 0); -Data readVarBytes(const Data& from, int initial_pos = 0, uint32_t* dataRead = nullptr); +Data readBytes(const Data& from, size_t max, size_t initial_pos = 0); +Data readVarBytes(const Data& from, size_t initial_pos = 0, uint32_t* dataRead = nullptr); -template T readVar(const TW::Data& from, int initial_pos = 0, const T& max = std::numeric_limits::max()); -template<> int64_t readVar(const TW::Data& from, int initial_pos, const int64_t& max); -template<> uint64_t readVar(const TW::Data& from, int initial_pos, const uint64_t& max); +template T readVar(const TW::Data& from, size_t initial_pos = 0, const T& max = std::numeric_limits::max()); +template<> int64_t readVar(const TW::Data& from, size_t initial_pos, const int64_t& max); +template<> uint64_t readVar(const TW::Data& from, size_t initial_pos, const uint64_t& max); -Data writeVarBytes(const Data& from, int initial_pos = 0); +Data writeVarBytes(const Data& from, size_t initial_pos = 0); template static std::vector concat(const std::vector& v1, const std::vector& v2) { std::vector v(v1); v.insert(v.end(), v2.begin(), v2.end()); - return std::move(v); + return v; } } // namespace TW diff --git a/src/NEO/Script.cpp b/src/NEO/Script.cpp index a168dfe74bb..0fc1f31c7e1 100644 --- a/src/NEO/Script.cpp +++ b/src/NEO/Script.cpp @@ -1,9 +1,9 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Script.h" + +#include "BinaryCoding.h" #include "OpCode.h" namespace TW::NEO { @@ -23,4 +23,41 @@ Data Script::CreateInvocationScript(const Data& signature) { return result; } +Data Script::CreateNep5TransferScript(const Data& assetId, const Data& from, const Data& to, + uint256_t value, bool withRet /*= false*/) { + Data result; + + // handle value + if (value == uint256_t(0)) { + result.push_back(PUSH0); + } else if (value >= uint256_t(1) && value <= uint256_t(16)) { + result.push_back(PUSH1 - 1 + (byte)value); + } else { + Data v; + encode256LE(v, value); + result.push_back((byte)v.size()); + result.insert(result.end(), v.begin(), v.end()); + } + + encodeBytes(result, to); + encodeBytes(result, from); + + // args length + result.push_back(PUSH3); + + result.push_back(PACK); + + std::string operation = "transfer"; + encodeBytes(result, {operation.begin(), operation.end()}); + + result.push_back(APPCALL); + result.insert(result.end(), assetId.begin(), assetId.end()); + + if (withRet) { + result.push_back(THROWIFNOT); + result.push_back(RET); + } + return result; +} + } // namespace TW::NEO diff --git a/src/NEO/Script.h b/src/NEO/Script.h index 64d47982754..35984613c14 100644 --- a/src/NEO/Script.h +++ b/src/NEO/Script.h @@ -1,18 +1,18 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" +#include "uint256.h" namespace TW::NEO { class Script { - public: +public: static Data CreateSignatureRedeemScript(const Data& publicKey); static Data CreateInvocationScript(const Data& signature); + // nep5 assetId has only 20 bytes, different with gas & neo that are 32 bytes. + static Data CreateNep5TransferScript(const Data& assetId, const Data& from, const Data& to, uint256_t value, bool withRet = false); }; - } // namespace TW::NEO diff --git a/src/NEO/Serializable.h b/src/NEO/Serializable.h index 51ec20edf73..e67bda608ca 100644 --- a/src/NEO/Serializable.h +++ b/src/NEO/Serializable.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -23,10 +21,10 @@ class Serializable : public ISerializable { } template - static inline Data serialize(const T *data, int size) { + static inline Data serialize(const T *data, size_t size) { Data resp; encodeVarInt(uint64_t(size), resp); - for (int i = 0; i < size; ++i) { + for (size_t i = 0; i < size; ++i) { append(resp, data[i].serialize()); } return resp; @@ -44,10 +42,10 @@ class Serializable : public ISerializable { } template - static inline int deserialize(std::vector &resp, const Data& data, int initial_pos = 0) { + static inline size_t deserialize(std::vector &resp, const Data& data, size_t initial_pos = 0) { uint64_t size = readVar(data, initial_pos, INT_MAX); // assert(size >= 0); - initial_pos += varIntSize(size); + initial_pos += static_cast(varIntSize(size)); for (uint64_t i = 0; i < size; ++i) { T value; value.deserialize(data, initial_pos); diff --git a/src/NEO/Signer.cpp b/src/NEO/Signer.cpp index 1449f328d7d..df3187c9447 100644 --- a/src/NEO/Signer.cpp +++ b/src/NEO/Signer.cpp @@ -1,24 +1,23 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" +#include "InvocationTransaction.h" #include "Script.h" -#include "../Hash.h" #include "../HexCoding.h" #include "../PrivateKey.h" #include "../PublicKey.h" -#include "../proto/NEO.pb.h" #include "../proto/Common.pb.h" +#include "../proto/NEO.pb.h" -using namespace TW; -using namespace TW::NEO; using namespace std; +using namespace TW; +namespace TW::NEO { -Signer::Signer(const PrivateKey& priKey) : privateKey(std::move(priKey)) { +Signer::Signer(const PrivateKey& priKey) + : privateKey(std::move(priKey)) { auto pub = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); publicKey = pub.bytes; address = Address(pub); @@ -60,6 +59,9 @@ Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) { for (int i = 0; i < input.outputs_size(); i++) { required[input.outputs(i).asset_id()] = input.outputs(i).amount(); + for (int j = 0; j < input.outputs(i).extra_outputs_size(); j++) { + required[input.outputs(i).asset_id()] += input.outputs(i).extra_outputs(j).amount(); + } } for (int i = 0; i < input.inputs_size(); i++) { @@ -69,6 +71,7 @@ Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) { continue; } + // if the required has been enough, not need to add input if (input.inputs(i).asset_id() != input.gas_asset_id() && required[input.inputs(i).asset_id()] < available[input.inputs(i).asset_id()]) { continue; @@ -82,8 +85,8 @@ Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) { for (int i = 0; i < input.outputs_size(); i++) { auto* outputPlan = plan.add_outputs(); - if (available.find(input.inputs(i).asset_id()) == available.end() || - available[input.outputs(i).asset_id()] < input.outputs(i).amount()) { + if (available.find(input.outputs(i).asset_id()) == available.end() || + available[input.outputs(i).asset_id()] < required[input.outputs(i).asset_id()]) { throw Common::Proto::SigningError(Common::Proto::Error_low_balance); } @@ -94,16 +97,25 @@ Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) { int64_t availableAmount = available[input.outputs(i).asset_id()]; outputPlan->set_available_amount(availableAmount); outputPlan->set_amount(input.outputs(i).amount()); - outputPlan->set_change(availableAmount - input.outputs(i).amount()); outputPlan->set_to_address(input.outputs(i).to_address()); outputPlan->set_asset_id(input.outputs(i).asset_id()); outputPlan->set_change_address(input.outputs(i).change_address()); + + auto changeAmount = availableAmount - input.outputs(i).amount(); + for (int j = 0; j < input.outputs(i).extra_outputs_size(); j++) { + auto* extra_plan = outputPlan->add_extra_outputs(); + + extra_plan->set_to_address(input.outputs(i).extra_outputs(j).to_address()); + extra_plan->set_amount(input.outputs(i).extra_outputs(j).amount()); + changeAmount -= input.outputs(i).extra_outputs(j).amount(); + } + outputPlan->set_change(changeAmount); } const int64_t SIGNATURE_SIZE = 103; int64_t transactionSize = - prepareUnsignedTransaction(input, plan, false).serialize().size() + SIGNATURE_SIZE; + prepareUnsignedTransaction(input, plan, false)->serialize().size() + SIGNATURE_SIZE; const int64_t LARGE_TX_SIZE = 1024; const int64_t MIN_FEE_FOR_LARGE_TX = 100000; @@ -134,7 +146,7 @@ Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) { if (feeNeed) { transactionSize = - prepareUnsignedTransaction(input, plan, false).serialize().size() + SIGNATURE_SIZE; + prepareUnsignedTransaction(input, plan, false)->serialize().size() + SIGNATURE_SIZE; int64_t fee = 0; if (transactionSize >= LARGE_TX_SIZE) { fee = MIN_FEE_FOR_LARGE_TX; @@ -152,12 +164,35 @@ Proto::TransactionPlan Signer::plan(const Proto::SigningInput& input) { return plan; } -Transaction Signer::prepareUnsignedTransaction(const Proto::SigningInput& input, - const Proto::TransactionPlan& plan, bool validate) { +std::shared_ptr Signer::prepareUnsignedTransaction(const Proto::SigningInput& input, + const Proto::TransactionPlan& plan, + bool validate) { + std::shared_ptr transaction; try { - auto transaction = Transaction(); - transaction.type = TransactionType::TT_ContractTransaction; - transaction.version = 0; + switch (input.transaction().transaction_oneof_case()) { + case Proto::Transaction::kNep5Transfer: { + auto t = std::make_shared(); + auto nep5Tx = input.transaction().nep5_transfer(); + t->script = Script::CreateNep5TransferScript( + parse_hex(nep5Tx.asset_id()), Address(nep5Tx.from()).toScriptHash(), + Address(nep5Tx.to()).toScriptHash(), load(nep5Tx.amount()), nep5Tx.script_with_ret()); + + transaction = t; + break; + } + case Proto::Transaction::kInvocationGeneric: { + auto t = std::make_shared(); + auto script = input.transaction().invocation_generic().script(); + t->script = Data(script.begin(), script.end()); + t->gas = input.transaction().invocation_generic().gas(); + + transaction = t; + break; + } + default: + transaction = std::make_shared(); + break; + } for (int i = 0; i < plan.inputs_size(); i++) { CoinReference coin; @@ -166,14 +201,19 @@ Transaction Signer::prepareUnsignedTransaction(const Proto::SigningInput& input, std::reverse(prevHashReverse.begin(), prevHashReverse.end()); coin.prevHash = load(prevHashReverse); coin.prevIndex = (uint16_t)plan.inputs(i).prev_index(); - transaction.inInputs.push_back(coin); + transaction->inInputs.push_back(coin); } for (int i = 0; i < plan.outputs_size(); i++) { if (plan.outputs(i).asset_id() == input.gas_asset_id()) { - if (validate && plan.outputs(i).amount() + plan.outputs(i).change() + plan.fee() != - plan.outputs(i).available_amount()) { - throw Common::Proto::SigningError(Common::Proto::Error_wrong_fee); + if (validate) { + auto sumAmount = plan.outputs(i).amount() + plan.outputs(i).change() + plan.fee(); + for (int j = 0; j < plan.outputs(i).extra_outputs_size(); j++) { + sumAmount += plan.outputs(i).extra_outputs(j).amount(); + } + if (sumAmount != plan.outputs(i).available_amount()) { + throw Common::Proto::SigningError(Common::Proto::Error_wrong_fee); + } } } @@ -183,7 +223,16 @@ Transaction Signer::prepareUnsignedTransaction(const Proto::SigningInput& input, out.value = (int64_t)plan.outputs(i).amount(); auto scriptHash = TW::NEO::Address(plan.outputs(i).to_address()).toScriptHash(); out.scriptHash = load(scriptHash); - transaction.outputs.push_back(out); + transaction->outputs.push_back(out); + + for (int j = 0; j < plan.outputs(i).extra_outputs_size(); j++) { + TransactionOutput extraOut; + extraOut.assetId = load(parse_hex(plan.outputs(i).asset_id())); + extraOut.value = (int64_t)plan.outputs(i).extra_outputs(j).amount(); + auto extraScriptHash = TW::NEO::Address(plan.outputs(i).extra_outputs(j).to_address()).toScriptHash(); + extraOut.scriptHash = load(extraScriptHash); + transaction->outputs.push_back(extraOut); + } } // change @@ -193,20 +242,29 @@ Transaction Signer::prepareUnsignedTransaction(const Proto::SigningInput& input, out.value = plan.outputs(i).change(); auto scriptHash = TW::NEO::Address(plan.outputs(i).change_address()).toScriptHash(); out.scriptHash = load(scriptHash); - transaction.outputs.push_back(out); + transaction->outputs.push_back(out); } } + + for (int i = 0; i < plan.attributes_size(); i++) { + TransactionAttribute attr; + attr.usage = (TransactionAttributeUsage)plan.attributes(i).usage(); + attr._data.assign(plan.attributes(i).data().begin(), plan.attributes(i).data().end()); + + transaction->attributes.push_back(attr); + } return transaction; } catch (...) { } - return Transaction(); + return transaction; } Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto output = Proto::SigningOutput(); try { - auto signer = Signer(PrivateKey(Data(input.private_key().begin(), input.private_key().end()))); + auto signer = + Signer(PrivateKey(Data(input.private_key().begin(), input.private_key().end()))); Proto::TransactionPlan plan; if (input.has_plan()) { plan = input.plan(); @@ -214,8 +272,8 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { plan = signer.plan(input); } auto transaction = prepareUnsignedTransaction(input, plan); - signer.sign(transaction); - auto signedTx = transaction.serialize(); + signer.sign(*transaction); + auto signedTx = transaction->serialize(); output.set_encoded(signedTx.data(), signedTx.size()); } catch (const Common::Proto::SigningError& error) { @@ -224,3 +282,42 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { return output; } + +Data Signer::signaturePreimage(const Proto::SigningInput& input) { + Proto::TransactionPlan p; + if (input.has_plan()) { + p = input.plan(); + } else { + p = plan(input); + } + auto transaction = prepareUnsignedTransaction(input, p); + return transaction->serialize(); +} + +Data Signer::encodeTransaction(const Proto::SigningInput& input, + const std::vector& publicKeys, + const std::vector& signatures) { + Proto::TransactionPlan p; + if (input.has_plan()) { + p = input.plan(); + } else { + p = plan(input); + } + auto transaction = prepareUnsignedTransaction(input, p); + transaction->witnesses.clear(); + + if (publicKeys.size() != signatures.size()) { + return {Data()}; + } + + for (size_t i = 0; i < publicKeys.size(); i++) { + Witness witness; + witness.invocationScript = Script::CreateInvocationScript(signatures[i]); + witness.verificationScript = Script::CreateSignatureRedeemScript(publicKeys[i].bytes); + transaction->witnesses.push_back(witness); + } + + return transaction->serialize(); +} + +} // namespace TW::NEO diff --git a/src/NEO/Signer.h b/src/NEO/Signer.h index bb00b08e09e..d26fb9addb3 100644 --- a/src/NEO/Signer.h +++ b/src/NEO/Signer.h @@ -1,26 +1,24 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Address.h" #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/NEO.pb.h" namespace TW::NEO { class Signer { - private: +private: Data publicKey; TW::PrivateKey privateKey; Address address; - public: +public: explicit Signer(const TW::PrivateKey& priKey); PrivateKey getPrivateKey() const; PublicKey getPublicKey() const; @@ -31,10 +29,15 @@ class Signer { void sign(Transaction& tx) const; Data sign(const Data& data) const; - private: - static Transaction prepareUnsignedTransaction(const Proto::SigningInput& input, - const Proto::TransactionPlan& plan, - bool validate = true); + static Data signaturePreimage(const Proto::SigningInput& input); + static Data encodeTransaction(const Proto::SigningInput& input, + const std::vector& publicKeys, + const std::vector& signatures); + +private: + static std::shared_ptr + prepareUnsignedTransaction(const Proto::SigningInput& input, const Proto::TransactionPlan& plan, + bool validate = true); }; } // namespace TW::NEO diff --git a/src/NEO/Transaction.cpp b/src/NEO/Transaction.cpp index 66b88d9ef83..933dc97df50 100644 --- a/src/NEO/Transaction.cpp +++ b/src/NEO/Transaction.cpp @@ -1,28 +1,27 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include -#include "../uint256.h" -#include "../Data.h" -#include "../Hash.h" -#include "Transaction.h" +#include "InvocationTransaction.h" #include "MinerTransaction.h" +#include "Transaction.h" +#include "Data.h" +#include "../Hash.h" +#include "../uint256.h" using namespace std; - using namespace TW; -using namespace TW::NEO; -int64_t Transaction::size() const { +namespace TW::NEO { + +size_t Transaction::size() const { return serialize().size(); } -void Transaction::deserialize(const Data& data, int initial_pos) { - type = (TransactionType) data[initial_pos++]; +void Transaction::deserialize(const Data& data, size_t initial_pos) { + type = (TransactionType)data[initial_pos++]; version = data[initial_pos++]; initial_pos = deserializeExclusiveData(data, initial_pos); attributes.clear(); @@ -33,15 +32,21 @@ void Transaction::deserialize(const Data& data, int initial_pos) { Serializable::deserialize(outputs, data, initial_pos); } -Transaction * Transaction::deserializeFrom(const Data& data, int initial_pos) { - Transaction * resp = nullptr; - switch ((TransactionType) data[initial_pos]) { - case TransactionType::TT_MinerTransaction: - resp = new MinerTransaction(); - break; - default: - throw std::invalid_argument("Transaction::deserializeFrom Invalid transaction type"); - break; +Transaction* Transaction::deserializeFrom(const Data& data, size_t initial_pos) { + Transaction* resp = nullptr; + switch ((TransactionType)data[initial_pos]) { + case TransactionType::TT_MinerTransaction: + resp = new MinerTransaction(); + break; + case TransactionType::TT_ContractTransaction: + resp = new Transaction(); + break; + case TransactionType::TT_InvocationTransaction: + resp = new InvocationTransaction(); + break; + default: + throw std::invalid_argument("Transaction::deserializeFrom Invalid transaction type"); + break; } resp->deserialize(data, initial_pos); return resp; @@ -49,27 +54,27 @@ Transaction * Transaction::deserializeFrom(const Data& data, int initial_pos) { Data Transaction::serialize() const { Data resp; - resp.push_back((byte) type); + resp.push_back((byte)type); resp.push_back(version); append(resp, serializeExclusiveData()); append(resp, Serializable::serialize(attributes)); append(resp, Serializable::serialize(inInputs)); append(resp, Serializable::serialize(outputs)); - if(witnesses.size()) - { - resp.push_back((byte) witnesses.size()); - for (const auto& witnesse : witnesses) - append(resp, witnesse.serialize()); - } + if (witnesses.size()) { + resp.push_back((byte)witnesses.size()); + for (const auto& witnesse : witnesses) + append(resp, witnesse.serialize()); + } return resp; } -bool Transaction::operator==(const Transaction &other) const { +bool Transaction::operator==(const Transaction& other) const { if (this == &other) { return true; } + // clang-format off return this->type == other.type && this->version == other.version && this->attributes.size() == other.attributes.size() @@ -78,6 +83,7 @@ bool Transaction::operator==(const Transaction &other) const { && this->attributes == other.attributes && this->inInputs == other.inInputs && this->outputs == other.outputs; + // clang-format on } Data Transaction::getHash() const { @@ -87,3 +93,5 @@ Data Transaction::getHash() const { uint256_t Transaction::getHashUInt256() const { return load(getHash()); } + +} // namespace TW::NEO diff --git a/src/NEO/Transaction.h b/src/NEO/Transaction.h index 50a311e3d9a..3df16aa3d51 100644 --- a/src/NEO/Transaction.h +++ b/src/NEO/Transaction.h @@ -1,19 +1,17 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../uint256.h" +#include "CoinReference.h" #include "ISerializable.h" #include "Serializable.h" -#include "TransactionType.h" #include "TransactionAttribute.h" #include "TransactionOutput.h" -#include "CoinReference.h" +#include "TransactionType.h" #include "Witness.h" +#include "../uint256.h" namespace TW::NEO { @@ -26,20 +24,22 @@ class Transaction : public Serializable { std::vector outputs; std::vector witnesses; - virtual ~Transaction() {} - int64_t size() const override; - void deserialize(const Data& data, int initial_pos = 0) override; + Transaction(TransactionType t = TransactionType::TT_ContractTransaction, byte ver = 0) : type(t), version(ver) {} + ~Transaction() override = default; + + size_t size() const override; + void deserialize(const Data& data, size_t initial_pos = 0) override; Data serialize() const override; - bool operator==(const Transaction &other) const; + bool operator==(const Transaction& other) const; - virtual int deserializeExclusiveData(const Data& data, int initial_pos = 0) { return initial_pos; } - virtual Data serializeExclusiveData() const { return Data(); } + virtual size_t deserializeExclusiveData([[maybe_unused]] const Data& data, size_t initial_pos) { return initial_pos; } + virtual Data serializeExclusiveData() const { return {}; } Data getHash() const; uint256_t getHashUInt256() const; - static Transaction * deserializeFrom(const Data& data, int initial_pos = 0); + static Transaction* deserializeFrom(const Data& data, size_t initial_pos = 0); }; } // namespace TW::NEO diff --git a/src/NEO/TransactionAttribute.h b/src/NEO/TransactionAttribute.h index 8d8c5976c0f..5c237a1201c 100644 --- a/src/NEO/TransactionAttribute.h +++ b/src/NEO/TransactionAttribute.h @@ -1,61 +1,106 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "TransactionAttributeUsage.h" +#include "Constants.h" #include "ISerializable.h" #include "Serializable.h" -#include "../Data.h" +#include "TransactionAttributeUsage.h" +#include "Data.h" namespace TW::NEO { -class TransactionAttribute : public Serializable { - public: +class TransactionAttribute final: public Serializable { +public: TransactionAttributeUsage usage = TAU_ContractHash; - Data data; + Data _data; - virtual ~TransactionAttribute() {} + ~TransactionAttribute() override = default; - int64_t size() const override { - return 1 + data.size(); + size_t size() const override { + switch (usage) { + case TransactionAttributeUsage::TAU_ContractHash: + case TransactionAttributeUsage::TAU_ECDH02: + case TransactionAttributeUsage::TAU_ECDH03: + case TransactionAttributeUsage::TAU_Vote: + return 1UL + contractHashSize; + case TransactionAttributeUsage::TAU_Script: + return 1UL + scriptHashSize; + default: + if (usage >= TransactionAttributeUsage::TAU_Hash1 && + usage <= TransactionAttributeUsage::TAU_Hash15) { + return 1 + contractHashSize; + } + return 1UL + static_cast(varIntSize(_data.size())) + _data.size(); + } } - void deserialize(const Data& data, int initial_pos = 0) override { + void deserialize(const Data& data, size_t initial_pos = 0) override { if (data.size() < initial_pos + 1) { throw std::invalid_argument("Invalid data for deserialization"); } - usage = (TransactionAttributeUsage) data[initial_pos]; - if (usage == TransactionAttributeUsage::TAU_ContractHash || usage == TransactionAttributeUsage::TAU_Vote || - (usage >= TransactionAttributeUsage::TAU_Hash1 && usage <= TransactionAttributeUsage::TAU_Hash15)) { - this->data = readBytes(data, 32, initial_pos + 1); - } else if (usage == TransactionAttributeUsage::TAU_ECDH02 || - usage == TransactionAttributeUsage::TAU_ECDH03) { - this->data = readBytes(data, 32, initial_pos + 1); - } else if (usage == TransactionAttributeUsage::TAU_Script) { - this->data = readBytes(data, 20, initial_pos + 1); - } else if (usage == TransactionAttributeUsage::TAU_DescriptionUrl) { - this->data = readBytes(data, 1, initial_pos + 1); - } else if (usage == TransactionAttributeUsage::TAU_Description || - usage >= TransactionAttributeUsage::TAU_Remark) { - this->data = readBytes(data, int(data.size()) - 1 - initial_pos, initial_pos + 1); - } else { + + // see: https://github.com/neo-project/neo/blob/v2.12.0/neo/Network/P2P/Payloads/TransactionAttribute.cs#L32 + usage = (TransactionAttributeUsage)data[initial_pos]; + switch (usage) { + case TransactionAttributeUsage::TAU_ECDH02: + case TransactionAttributeUsage::TAU_ECDH03: { + this->_data = concat({(TW::byte)usage}, readBytes(data, contractHashSize, initial_pos + 1)); + break; + } + + case TransactionAttributeUsage::TAU_Script: { + this->_data = readBytes(data, scriptHashSize, initial_pos + 1); + break; + } + + case TransactionAttributeUsage::TAU_DescriptionUrl: + case TransactionAttributeUsage::TAU_Description: + case TransactionAttributeUsage::TAU_Remark: { + this->_data = readVarBytes(data, initial_pos + 1); + break; + } + + default: + if (usage == TransactionAttributeUsage::TAU_ContractHash || + usage == TransactionAttributeUsage::TAU_Vote || + (usage >= TransactionAttributeUsage::TAU_Hash1 && usage <= TransactionAttributeUsage::TAU_Hash15)) { + this->_data = readBytes(data, contractHashSize, initial_pos + 1); + break; + } throw std::invalid_argument("TransactionAttribute Deserialize FormatException"); } } Data serialize() const override { - return concat(Data({static_cast(usage)}), data); + Data result; + result.push_back((TW::byte)usage); + + // see: https://github.com/neo-project/neo/blob/v2.12.0/neo/Network/P2P/Payloads/TransactionAttribute.cs#L49 + if (usage == TransactionAttributeUsage::TAU_DescriptionUrl || + usage == TransactionAttributeUsage::TAU_Description || + usage >= TransactionAttributeUsage::TAU_Remark) { + Data resp; + encodeVarInt((uint64_t)_data.size(), resp); + result.insert(result.end(), resp.begin(), resp.end()); + } + if (usage == TransactionAttributeUsage::TAU_ECDH02 || + usage == TransactionAttributeUsage::TAU_ECDH03) { + result.insert(result.end(), _data.begin() + 1, _data.begin() + 1 + contractHashSize); + } else { + result.insert(result.end(), _data.begin(), _data.end()); + } + + return result; } bool operator==(const TransactionAttribute &other) const { return this->usage == other.usage - && this->data.size() == other.data.size() - && this->data == other.data; + && _data.size() == other._data.size() + && _data == other._data; } }; -} \ No newline at end of file +} // namespace TW::NEO diff --git a/src/NEO/TransactionAttributeUsage.h b/src/NEO/TransactionAttributeUsage.h index 3cc20ecc4ab..7c061df70df 100644 --- a/src/NEO/TransactionAttributeUsage.h +++ b/src/NEO/TransactionAttributeUsage.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/NEO/TransactionOutput.h b/src/NEO/TransactionOutput.h index 86b51af36bc..df473006a19 100644 --- a/src/NEO/TransactionOutput.h +++ b/src/NEO/TransactionOutput.h @@ -1,13 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../uint256.h" -#include "../Data.h" +#include "Constants.h" +#include "Data.h" #include "../BinaryCoding.h" #include "ReadData.h" #include "ISerializable.h" @@ -15,32 +14,32 @@ namespace TW::NEO { -class TransactionOutput : public Serializable { +class TransactionOutput final: public Serializable { public: - static const size_t assetIdSize = 32; - static const size_t valueSize = 8; - static const size_t scriptHashSize = 20; - uint256_t assetId; - int64_t value = 0; + uint64_t value = 0; uint256_t scriptHash; - virtual ~TransactionOutput() {} + ~TransactionOutput() override = default; - int64_t size() const override { - return store(assetId).size() + valueSize + store(scriptHash).size(); + size_t size() const override { + return assetIdSize + valueSize + scriptHashSize; } - void deserialize(const Data& data, int initial_pos = 0) override { + void deserialize(const Data& data, size_t initial_pos = 0) override { + if (data.size() < initial_pos + size()) { + throw std::invalid_argument("Data::Cannot read enough bytes!"); + } + assetId = load(readBytes(data, assetIdSize, initial_pos)); value = decode64LE(data.data() + initial_pos + assetIdSize); scriptHash = load(readBytes(data, scriptHashSize, initial_pos + assetIdSize + valueSize)); } Data serialize() const override { - auto resp = store(assetId); + auto resp = store(assetId, assetIdSize); encode64LE(value, resp); - return concat(resp, store(scriptHash)); + return concat(resp, store(scriptHash, scriptHashSize)); } bool operator==(const TransactionOutput &other) const { @@ -50,4 +49,4 @@ class TransactionOutput : public Serializable { } }; -} \ No newline at end of file +} // namespace TW::NEO diff --git a/src/NEO/TransactionType.h b/src/NEO/TransactionType.h index a70400a8c26..4b3840fab2a 100644 --- a/src/NEO/TransactionType.h +++ b/src/NEO/TransactionType.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/NEO/Witness.h b/src/NEO/Witness.h index 6f0e7c9d139..fe67e5e260e 100644 --- a/src/NEO/Witness.h +++ b/src/NEO/Witness.h @@ -1,32 +1,30 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "ISerializable.h" #include "Serializable.h" namespace TW::NEO { -class Witness : public Serializable { +class Witness final: public Serializable { public: Data invocationScript; Data verificationScript; - virtual ~Witness() {} + ~Witness() override = default; - int64_t size() const override { + size_t size() const override { return invocationScript.size() + verificationScript.size(); } - void deserialize(const Data& data, int initial_pos = 0) override { + void deserialize(const Data& data, size_t initial_pos = 0) override { uint32_t size; invocationScript = readVarBytes(data, initial_pos, &size); - verificationScript = readVarBytes(data, initial_pos + size); + verificationScript = readVarBytes(data, initial_pos + static_cast(size)); } Data serialize() const override { diff --git a/src/NULS/Address.cpp b/src/NULS/Address.cpp index 545a618f972..d71d4279341 100644 --- a/src/NULS/Address.cpp +++ b/src/NULS/Address.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" #include @@ -12,21 +10,30 @@ #include "../HexCoding.h" using namespace TW; -using namespace TW::NULS; -const std::string Address::prefix("NULSd"); -const std::array Address::mainnetId = {0x01, 0x00}; +namespace TW::NULS { -bool Address::isValid(const std::string& string) { - if (string.empty()) { +std::string mainnetPrefix = std::string("NULSd"); +std::string testnetPrefix = std::string("tNULSe"); + +bool Address::isValid(const std::string& addrStr) { + if (addrStr.empty()) { + return false; + } + std::string addrPrefix; + if(addrStr.find(mainnetPrefix) == 0) { + addrPrefix = mainnetPrefix; + } else if (addrStr.find(testnetPrefix) == 0) { + addrPrefix = testnetPrefix; + } else { return false; } - if (string.length() <= prefix.length()) { + if (addrStr.length() <= addrPrefix.length()) { return false; } - std::string address = string.substr(prefix.length(), string.length() - prefix.length()); - Data decoded = Base58::bitcoin.decode(address); + std::string address = addrStr.substr(addrPrefix.length(), addrStr.length() - addrPrefix.length()); + Data decoded = Base58::decode(address); if (decoded.size() != size) { return false; } @@ -40,22 +47,35 @@ bool Address::isValid(const std::string& string) { return decoded[23] == checkSum; } -Address::Address(const TW::PublicKey& publicKey) { - // Main-Net chainID - bytes[0] = mainnetId[0]; - bytes[1] = mainnetId[1]; +Address::Address(const TW::PublicKey& publicKey, bool isMainnet) { + if (isMainnet) { + prefix = mainnetPrefix; + bytes[0] = 0x01; + bytes[1] = 0x00; + } else { + prefix = testnetPrefix; + bytes[0] = 0x02; + bytes[1] = 0x00; + } // Address Type bytes[2] = addressType; ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, bytes.begin() + 3); bytes[23] = checksum(bytes); } -Address::Address(const std::string& string) { - if (false == isValid(string)){ +Address::Address(const std::string& addrStr) { + if(addrStr.find(mainnetPrefix) == 0) { + prefix = mainnetPrefix; + } else if (addrStr.find(testnetPrefix) == 0) { + prefix = testnetPrefix; + } else { + throw std::invalid_argument("wrong address prefix"); + } + if (!isValid(addrStr)) { throw std::invalid_argument("Invalid address string"); } - std::string address = string.substr(prefix.length(), string.length() - prefix.length()); - const auto decoded = Base58::bitcoin.decode(address); + std::string address = addrStr.substr(prefix.length(), addrStr.length() - prefix.length()); + const auto decoded = Base58::decode(address); std::copy(decoded.begin(), decoded.end(), bytes.begin()); } @@ -68,10 +88,10 @@ uint8_t Address::type() const { } std::string Address::string() const { - return prefix + Base58::bitcoin.encode(bytes.begin(), bytes.end()); + return prefix + Base58::encode(bytes); } -uint8_t Address::checksum(std::array& byteArray) const{ +uint8_t Address::checksum(std::array& byteArray) const { uint8_t checkSum = 0x00; for (int i = 0; i < 23; ++i) { checkSum ^= byteArray[i]; @@ -79,4 +99,4 @@ uint8_t Address::checksum(std::array& byteArray) const{ return checkSum; } - +} // namespace TW::NULS diff --git a/src/NULS/Address.h b/src/NULS/Address.h index 7134f814be7..73425100bf0 100644 --- a/src/NULS/Address.h +++ b/src/NULS/Address.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../Base58Address.h" #include "../PrivateKey.h" @@ -14,23 +12,19 @@ namespace TW::NULS { class Address : public Base58Address<24> { public: - /// NULS Main Net Chain ID = 1 - static const std::array mainnetId; - /// NULS address prefix - static const std::string prefix; + std::string prefix; /// NULS address type static const byte addressType = 0x01; /// Determines whether a string makes a valid address. - static bool isValid(const std::string& string); + static bool isValid(const std::string& addrStr); /// Initializes an address from a string representation. explicit Address(const std::string& string); - /// Initializes an address from a public key. - explicit Address(const PublicKey& publicKey); + explicit Address(const TW::PublicKey& publicKey, bool isMainnet = true); /// Initializes an address with a collection of bytes. explicit Address(const Data& data) : TW::Base58Address<24>(data) {} diff --git a/src/NULS/BinaryCoding.h b/src/NULS/BinaryCoding.h index 077bf30cf85..f9c59e4f8db 100644 --- a/src/NULS/BinaryCoding.h +++ b/src/NULS/BinaryCoding.h @@ -1,57 +1,87 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../uint256.h" +#include "Address.h" #include "../BinaryCoding.h" -#include "../proto/NULS.pb.h" #include "../HexCoding.h" -#include "Address.h" +#include "../proto/NULS.pb.h" +#include "../uint256.h" using namespace TW; -using namespace TW::NULS; + +namespace TW::NULS { static inline void serializerRemark(std::string& remark, Data& data) { encodeVarInt(remark.length(), data); std::copy(remark.begin(), remark.end(), std::back_inserter(data)); } -static inline void serializerInput(const Proto::TransactionCoinFrom& input, Data& data) { - encodeVarInt(1, data); //there is one coinFrom - const auto& fromAddress = input.from_address(); - if (!NULS::Address::isValid(fromAddress)) { - throw std::invalid_argument("Invalid address"); +static inline void serializerCoinData(const TW::NULS::Proto::Transaction& tx, Data& data) { + auto coinData = Data(); + // CoinFrom + encodeVarInt(tx.input_size(), coinData); + for (int i = 0; i < tx.input_size(); i++) { + // address + const auto& fromAddress = tx.input(i).from_address(); + if (!NULS::Address::isValid(fromAddress)) { + throw std::invalid_argument("Invalid address"); + } + const auto& addr = NULS::Address(fromAddress); + encodeVarInt(addr.bytes.size() - 1, coinData); + std::copy(addr.bytes.begin(), addr.bytes.end() - 1, std::back_inserter(coinData)); + // chain id of asset + encode16LE(static_cast(tx.input(i).assets_chainid()), coinData); + // asset id + encode16LE(static_cast(tx.input(i).assets_id()), coinData); + // amount + uint256_t numeric = load(tx.input(i).id_amount()); + Data numericData = store(numeric); + std::reverse(numericData.begin(), numericData.end()); + std::string numericStr; + numericStr.insert(numericStr.begin(), numericData.begin(), numericData.end()); + numericStr.append(static_cast(numericData.capacity() - numericData.size()), + '\0'); + std::copy(numericStr.begin(), numericStr.end(), std::back_inserter(coinData)); + // nonce + Data nonce = parse_hex(tx.input(i).nonce()); + encodeVarInt(nonce.size(), coinData); + append(coinData, nonce); + // locked + coinData.push_back(static_cast(tx.input(i).locked())); } - const auto& addr = NULS::Address(fromAddress); - encodeVarInt(addr.bytes.size() - 1, data); - std::copy(addr.bytes.begin(), addr.bytes.end() - 1, std::back_inserter(data)); - encode16LE(static_cast(input.assets_chainid()), data); - encode16LE(static_cast(input.assets_id()), data); - std::copy(input.id_amount().begin(), input.id_amount().end(), std::back_inserter(data)); - Data nonce = parse_hex(input.nonce()); - encodeVarInt(nonce.size(), data); - append(data, nonce); - data.push_back(static_cast(input.locked())); -} - -static inline void serializerOutput(const Proto::TransactionCoinTo& output, Data& data) { - encodeVarInt(1, data); //there is one coinTo - - const auto& toAddress = output.to_address(); - if (!NULS::Address::isValid(toAddress)) { - throw std::invalid_argument("Invalid address"); + encodeVarInt(tx.output_size(), coinData); + for (int i = 0; i < tx.output_size(); i++) { + // address + const auto& toAddress = tx.output(i).to_address(); + if (!NULS::Address::isValid(toAddress)) { + throw std::invalid_argument("Invalid address"); + } + const auto& addr = NULS::Address(toAddress); + encodeVarInt(addr.bytes.size() - 1, coinData); + std::copy(addr.bytes.begin(), addr.bytes.end() - 1, std::back_inserter(coinData)); + // chain id of asset + encode16LE(static_cast(tx.output(i).assets_chainid()), coinData); + // asset id + encode16LE(static_cast(tx.output(i).assets_id()), coinData); + // amount + uint256_t numeric = load(tx.output(i).id_amount()); + Data numericData = store(numeric); + std::reverse(numericData.begin(), numericData.end()); + std::string numericStr; + numericStr.insert(numericStr.begin(), numericData.begin(), numericData.end()); + numericStr.append(static_cast(numericData.capacity() - numericData.size()), + '\0'); + std::copy(numericStr.begin(), numericStr.end(), std::back_inserter(coinData)); + // lock time + encode64LE(tx.output(i).lock_time(), coinData); } - const auto& addr = NULS::Address(toAddress); - encodeVarInt(addr.bytes.size() - 1, data); - std::copy(addr.bytes.begin(), addr.bytes.end() - 1, std::back_inserter(data)); - encode16LE(static_cast(output.assets_chainid()), data); - encode16LE(static_cast(output.assets_id()), data); - std::copy(output.id_amount().begin(), output.id_amount().end(), std::back_inserter(data)); - encode64LE(output.lock_time(), data); + encodeVarInt(tx.input_size() * Signer::TRANSACTION_INPUT_SIZE + + tx.output_size() * Signer::TRANSACTION_OUTPUT_SIZE, + data); + append(data, coinData); } static inline Data calcTransactionDigest(Data& data) { @@ -65,10 +95,10 @@ static inline Data makeTransactionSignature(PrivateKey& privateKey, Data& txHash Data transactionSignature = Data(); encodeVarInt(pubKey.bytes.size(), transactionSignature); std::copy(pubKey.bytes.begin(), pubKey.bytes.end(), std::back_inserter(transactionSignature)); - auto signature = privateKey.signAsDER(txHash, TWCurve::TWCurveSECP256k1); + auto signature = privateKey.signAsDER(txHash); encodeVarInt(signature.size(), transactionSignature); std::copy(signature.begin(), signature.end(), std::back_inserter(transactionSignature)); return transactionSignature; } - +} // namespace TW::NULS diff --git a/src/NULS/Entry.cpp b/src/NULS/Entry.cpp index 2dee29d9e31..12560ffd6be 100644 --- a/src/NULS/Entry.cpp +++ b/src/NULS/Entry.cpp @@ -1,27 +1,72 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" - #include "Address.h" #include "Signer.h" -using namespace TW::NULS; using namespace std; +namespace TW::NULS { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +TW::Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Address(address); + return {addr.bytes.begin(), addr.bytes.end()}; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const TW::Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + const auto signer = Signer(input); + auto unsignedTxBytes = signer.buildUnsignedTx(); + output.set_data(unsignedTxBytes.data(), unsignedTxBytes.size()); + Data unsignedTxBytesHash = Hash::sha256(Hash::sha256((unsignedTxBytes))); + output.set_data_hash(unsignedTxBytesHash.data(), unsignedTxBytesHash.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_signatures_count); + output.set_error_message( + Common::Proto::SigningError_Name(Common::Proto::Error_signatures_count)); + return; + } + const auto signer = Signer(input); + // verify signatures + auto unsignedTxBytes = signer.buildUnsignedTx(); + Data unsignedTxBytesHash = Hash::sha256(Hash::sha256((unsignedTxBytes))); + for (std::vector::size_type i = 0; i < signatures.size(); i++) { + if (!publicKeys[i].verify(signatures[i], unsignedTxBytesHash)) { + throw std::invalid_argument("invalid signature at " + std::to_string(i)); + } + } + std::vector publicKeysData; + for (std::vector::size_type i = 0; i < publicKeys.size(); i++) { + publicKeysData.push_back(publicKeys[i].bytes); + } + + auto signedData = signer.buildSignedTx(publicKeysData, signatures, unsignedTxBytes); + output.set_encoded(signedData.data(), signedData.size()); + }); +} + +} // namespace TW::NULS diff --git a/src/NULS/Entry.h b/src/NULS/Entry.h index ef0b9e61ccc..fe29d702463 100644 --- a/src/NULS/Entry.h +++ b/src/NULS/Entry.h @@ -1,22 +1,25 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../CoinEntry.h" +#include "../proto/TransactionCompiler.pb.h" namespace TW::NULS { /// Entry point for implementation of NULS coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::NULS diff --git a/src/NULS/Signer.cpp b/src/NULS/Signer.cpp index 1e0c903d2a0..2e1ffca2784 100644 --- a/src/NULS/Signer.cpp +++ b/src/NULS/Signer.cpp @@ -1,10 +1,9 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" +#include #include "Address.h" #include "BinaryCoding.h" @@ -12,7 +11,8 @@ #include "../PrivateKey.h" using namespace TW; -using namespace TW::NULS; + +namespace TW::NULS { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto output = Proto::SigningOutput(); @@ -20,22 +20,15 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(input); auto data = signer.sign(); output.set_encoded(data.data(), data.size()); + } catch (const std::exception& e) { + output.set_error(Common::Proto::Error_general); + output.set_error_message(e.what()); } - catch(...) {} return output; } Signer::Signer(const Proto::SigningInput& input) : input(input) { - Proto::TransactionCoinFrom coinFrom; - coinFrom.set_from_address(input.from()); - coinFrom.set_assets_chainid(input.chain_id()); - coinFrom.set_assets_id(input.idassets_id()); - //need to update with amount + fee - coinFrom.set_id_amount(input.amount()); - coinFrom.set_nonce(input.nonce()); - //default unlocked - coinFrom.set_locked(0); - *tx.mutable_input() = coinFrom; + uint256_t balance = load(input.balance()); Proto::TransactionCoinTo coinTo; coinTo.set_to_address(input.to()); @@ -43,10 +36,97 @@ Signer::Signer(const Proto::SigningInput& input) : input(input) { coinTo.set_assets_chainid(input.chain_id()); coinTo.set_assets_id(input.idassets_id()); coinTo.set_lock_time(0); - *tx.mutable_output() = coinTo; + *tx.add_output() = coinTo; - tx.set_remark(input.remark()); tx.set_type(TX_TYPE); + + TW::uint256_t fromAmount; + // get mainnet chain id from address + auto from = Address(input.from()); + if (input.chain_id() == from.chainID() && input.idassets_id() == 1) { + // asset is NULS + uint256_t txAmount = load(input.amount()); + uint32_t txSize = + CalculatorTransactionSize(1, 1, static_cast(tx.remark().size())); + uint256_t fee = (uint256_t)CalculatorTransactionFee(txSize); + fromAmount = txAmount; + if (Address::isValid(input.fee_payer()) && input.fee_payer() != input.from()) { + uint256_t feePayerBalance = load(input.fee_payer_balance()); + if (fee > feePayerBalance) { + throw std::invalid_argument("fee payer balance not sufficient"); + } + Data feeData = store(fee); + std::string feeStr(feeData.begin(), feeData.end()); + Proto::TransactionCoinFrom feeCoinFrom; + feeCoinFrom.set_from_address(input.fee_payer()); + feeCoinFrom.set_assets_chainid(input.chain_id()); + feeCoinFrom.set_assets_id(input.idassets_id()); + feeCoinFrom.set_id_amount(feeStr); + feeCoinFrom.set_nonce(input.fee_payer_nonce()); + // default unlocked + feeCoinFrom.set_locked(0); + *tx.add_input() = feeCoinFrom; + } else { + fromAmount += fee; + } + // update the amount + Data amount = store(fromAmount); + std::string amountStr(amount.begin(), amount.end()); + Proto::TransactionCoinFrom coinFrom; + coinFrom.set_from_address(input.from()); + coinFrom.set_assets_chainid(input.chain_id()); + coinFrom.set_assets_id(input.idassets_id()); + coinFrom.set_id_amount(amountStr); + coinFrom.set_nonce(input.nonce()); + // default unlocked + coinFrom.set_locked(0); + *tx.add_input() = coinFrom; + } else { + // asset is not NULS + uint256_t txAmount = load(input.amount()); + fromAmount = txAmount; + // coinFrom + // asset + Proto::TransactionCoinFrom asset; + asset.set_from_address(input.from()); + asset.set_assets_chainid(input.chain_id()); + asset.set_assets_id(input.idassets_id()); + asset.set_id_amount(input.amount()); + asset.set_nonce(input.nonce()); + // default unlocked + asset.set_locked(0); + *tx.add_input() = asset; + + // fee + uint32_t txSize = CalculatorTransactionSize( + 2, 1, + static_cast( + tx.remark().size())); // 2 inputs, one for the asset, another for NULS fee + uint256_t fee = (uint256_t)CalculatorTransactionFee(txSize); + uint256_t feePayerBalance = load(input.fee_payer_balance()); + if (fee > feePayerBalance) { + throw std::invalid_argument("fee payer balance not sufficient"); + } + Data feeData = store(fee); + std::string feeStr(feeData.begin(), feeData.end()); + // add new input for fee + Proto::TransactionCoinFrom txFee; + txFee.set_from_address(input.fee_payer()); + txFee.set_nonce(input.fee_payer_nonce()); + + txFee.set_assets_chainid(from.chainID()); + // network asset id 1 is NULS + txFee.set_assets_id(1); + txFee.set_id_amount(feeStr); + // default unlocked + txFee.set_locked(0); + *tx.add_input() = txFee; + } + if (fromAmount > balance) { + throw std::invalid_argument("User account balance not sufficient"); + } + + tx.set_remark(input.remark()); tx.set_timestamp(input.timestamp()); tx.set_tx_data(""); } @@ -55,69 +135,88 @@ Data Signer::sign() const { if (input.private_key().empty()) { throw std::invalid_argument("Must have private key string"); } - - uint32_t txSize = CalculatorTransactionSize(1, 1, static_cast(tx.remark().size())); - uint256_t fee = (uint256_t)CalculatorTransactionFee(txSize); - uint256_t txAmount = load(input.amount()); - uint256_t balance = load(input.balance()); - uint256_t fromAmount = txAmount + fee; - if (fromAmount > balance) { - throw std::invalid_argument("User account balance not sufficient"); - } - - auto& coinFrom = (Proto::TransactionCoinFrom&)tx.input(); - Data amount; - amount = store(fromAmount); - std::reverse(amount.begin(), amount.end()); - std::string amountStr; - amountStr.insert(amountStr.begin(), amount.begin(), amount.end()); - amountStr.append(static_cast(amount.capacity() - amount.size()), '\0'); - coinFrom.set_id_amount(amountStr); - - auto& coinTo = (Proto::TransactionCoinTo&)tx.output(); - Data amountTo; - amountTo = store(txAmount); - std::reverse(amountTo.begin(), amountTo.end()); - std::string amountToStr; - amountToStr.insert(amountToStr.begin(), amountTo.begin(), amountTo.end()); - amountToStr.append(static_cast(amountTo.capacity() - amountTo.size()), '\0'); - coinTo.set_id_amount(amountToStr); - auto dataRet = Data(); // Transaction Type encode16LE(static_cast(tx.type()), dataRet); // Timestamp encode32LE(tx.timestamp(), dataRet); - // Remark + // Remark std::string remark = tx.remark(); serializerRemark(remark, dataRet); // txData encodeVarInt(0, dataRet); - - //coinFrom and coinTo size - encodeVarInt(TRANSACTION_INPUT_SIZE + TRANSACTION_OUTPUT_SIZE, dataRet); - - // CoinData Input - serializerInput(tx.input(), dataRet); - - // CoinData Output - serializerOutput(tx.output(), dataRet); - + // coinData + serializerCoinData(tx, dataRet); // Calc transaction hash Data txHash = calcTransactionDigest(dataRet); - + Data privKey = data(input.private_key()); auto priv = PrivateKey(privKey); auto transactionSignature = makeTransactionSignature(priv, txHash); + if (Address::isValid(input.fee_payer()) && input.from() != input.fee_payer()) { + Data feePayerPrivKey = data(input.fee_payer_private_key()); + auto feePayerPriv = PrivateKey(feePayerPrivKey); + auto feePayerTransactionSignature = makeTransactionSignature(feePayerPriv, txHash); + transactionSignature.insert(transactionSignature.end(), + feePayerTransactionSignature.begin(), + feePayerTransactionSignature.end()); + } + encodeVarInt(transactionSignature.size(), dataRet); - std::copy(transactionSignature.begin(), transactionSignature.end(), std::back_inserter(dataRet)); + std::copy(transactionSignature.begin(), transactionSignature.end(), + std::back_inserter(dataRet)); return dataRet; } -uint32_t Signer::CalculatorTransactionSize(uint32_t inputCount, uint32_t outputCount, uint32_t remarkSize) const { - uint32_t size = TRANSACTION_FIX_SIZE + TRANSACTION_SIG_MAX_SIZE + TRANSACTION_INPUT_SIZE * inputCount + - TRANSACTION_OUTPUT_SIZE * outputCount + remarkSize; +Data Signer::buildUnsignedTx() const { + auto dataRet = Data(); + // Transaction Type + encode16LE(static_cast(tx.type()), dataRet); + // Timestamp + encode32LE(tx.timestamp(), dataRet); + // Remark + std::string remark = tx.remark(); + serializerRemark(remark, dataRet); + // txData + encodeVarInt(0, dataRet); + // coinData + serializerCoinData(tx, dataRet); + + return dataRet; +} + +Data Signer::buildSignedTx(const std::vector publicKeys, + const std::vector signatures, + const Data unsignedTxBytes) const { + Data transactionSignature = Data(); + // the size of publicKeys must be the same as the size of the signatures. + for (std::vector::size_type i = 0; i < publicKeys.size(); i++) { + encodeVarInt(publicKeys[i].size(), transactionSignature); + std::copy(publicKeys[i].begin(), publicKeys[i].end(), + std::back_inserter(transactionSignature)); + + std::array tempSigBytes; + size_t size = ecdsa_sig_to_der(signatures[i].data(), tempSigBytes.data()); + auto signature = Data{}; + std::copy(tempSigBytes.begin(), tempSigBytes.begin() + size, std::back_inserter(signature)); + + encodeVarInt(signature.size(), transactionSignature); + std::copy(signature.begin(), signature.end(), std::back_inserter(transactionSignature)); + } + + Data signedTxBytes = unsignedTxBytes; + encodeVarInt(transactionSignature.size(), signedTxBytes); + std::copy(transactionSignature.begin(), transactionSignature.end(), + std::back_inserter(signedTxBytes)); + return signedTxBytes; +} + +uint32_t Signer::CalculatorTransactionSize(uint32_t inputCount, uint32_t outputCount, + uint32_t remarkSize) const { + uint32_t size = TRANSACTION_FIX_SIZE + TRANSACTION_SIG_MAX_SIZE + + TRANSACTION_INPUT_SIZE * inputCount + TRANSACTION_OUTPUT_SIZE * outputCount + + remarkSize; return size; } @@ -127,4 +226,6 @@ uint64_t Signer::CalculatorTransactionFee(uint64_t size) const { fee += MIN_PRICE_PRE_1024_BYTES; } return fee; -} \ No newline at end of file +} + +} // namespace TW::NULS diff --git a/src/NULS/Signer.h b/src/NULS/Signer.h index ccbd77bc023..748502aba6e 100644 --- a/src/NULS/Signer.h +++ b/src/NULS/Signer.h @@ -1,15 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../proto/NULS.pb.h" -#include -#include #include "Data.h" #include "../uint256.h" +#include +#include namespace TW::NULS { @@ -18,6 +16,7 @@ class Signer { public: /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + public: static const uint16_t TRANSACTION_FIX_SIZE = 11; //type size 2, time size 4, txData size 1, hash size 4 static const uint16_t TRANSACTION_SIG_MAX_SIZE = 110; @@ -41,7 +40,14 @@ class Signer { /// /// \returns the transaction signature or an empty vector if there is an error. Data sign() const; - private: + + Data buildUnsignedTx() const; + + Data buildSignedTx(const std::vector publicKeys, + const std::vector signatures, + const Data unsignedTxBytes) const; + +private: uint64_t CalculatorTransactionFee(uint64_t size) const; int32_t CalculatorMaxInput(uint32_t remarkSize) const; uint32_t CalculatorTransactionSize(uint32_t inputCount, uint32_t outputCount, uint32_t remarkSize) const; diff --git a/src/Nano/Address.cpp b/src/Nano/Address.cpp index ba16435bfd6..beb05b919be 100644 --- a/src/Nano/Address.cpp +++ b/src/Nano/Address.cpp @@ -1,16 +1,14 @@ // Copyright © 2019 Mart Roosmaa. -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" #include #include -using namespace TW::Nano; +namespace TW::Nano { const std::string kPrefixNano{"nano_"}; const std::string kPrefixXrb{"xrb_"}; @@ -21,14 +19,12 @@ bool Address::isValid(const std::string& address) { valid = nano_validate_address( kPrefixNano.c_str(), kPrefixNano.length(), address.c_str(), address.length(), - nullptr - ); + nullptr); if (!valid) { valid = nano_validate_address( kPrefixXrb.c_str(), kPrefixXrb.length(), address.c_str(), address.length(), - nullptr - ); + nullptr); } return valid; @@ -68,11 +64,13 @@ std::string Address::string() const { std::array out = {0}; size_t count = nano_get_address( - bytes.data(), - kPrefixNano.c_str(), kPrefixNano.length(), - out.data(), out.size()); + bytes.data(), + kPrefixNano.c_str(), kPrefixNano.length(), + out.data(), out.size()); // closing \0 assert(count < out.size()); out[count] = 0; - return std::string(out.data()); + return {out.data()}; } + +} // namespace TW::Nano diff --git a/src/Nano/Address.h b/src/Nano/Address.h index a2c686ac98e..9901577d11d 100644 --- a/src/Nano/Address.h +++ b/src/Nano/Address.h @@ -1,9 +1,7 @@ // Copyright © 2019 Mart Roosmaa. -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Nano/Entry.cpp b/src/Nano/Entry.cpp index 90b0a9ced38..5fae524f9a3 100644 --- a/src/Nano/Entry.cpp +++ b/src/Nano/Entry.cpp @@ -1,37 +1,52 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" - #include "Address.h" #include "Signer.h" +#include -using namespace TW::Nano; -using namespace TW; -using namespace std; +namespace TW::Nano { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { const auto addr = Address(address); return {addr.bytes.begin(), addr.bytes.end()}; } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + + auto unsignedTxBytes = Signer::buildUnsignedTxBytes(input); + output.set_data(unsignedTxBytes.data(), unsignedTxBytes.size()); + output.set_data_hash(unsignedTxBytes.data(), unsignedTxBytes.size()); }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, [[maybe_unused]] const auto& publicKey) { + output = Signer::buildSigningOutput(input, signature); }); +} + +} // namespace TW::Nano diff --git a/src/Nano/Entry.h b/src/Nano/Entry.h index d81621a0209..441028831f7 100644 --- a/src/Nano/Entry.h +++ b/src/Nano/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,14 +10,17 @@ namespace TW::Nano { /// Entry point for implementation of Nano coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Nano diff --git a/src/Nano/Signer.cpp b/src/Nano/Signer.cpp index a177a84e729..bfac78f141d 100644 --- a/src/Nano/Signer.cpp +++ b/src/Nano/Signer.cpp @@ -1,22 +1,18 @@ // Copyright © 2019 Mart Roosmaa. -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "../BinaryCoding.h" -#include "../Hash.h" #include "../HexCoding.h" +#include "../uint256.h" #include -#include #include using namespace TW; -using uint128_t = boost::multiprecision::uint128_t; using json = nlohmann::json; namespace TW::Nano { @@ -29,8 +25,6 @@ const std::array kBlockHashPreamble{ }; std::array store(const uint128_t& value) { - using boost::multiprecision::cpp_int; - Data buf; buf.reserve(16); export_bits(value, std::back_inserter(buf), 8); @@ -181,4 +175,40 @@ Proto::SigningOutput Signer::build() const { return output; } +Data Signer::buildUnsignedTxBytes(const Proto::SigningInput& input) { + const auto pubKey = PublicKey(Data(input.public_key().begin(), input.public_key().end()), TWPublicKeyTypeED25519Blake2b); + auto block = hashBlockData(pubKey, input); + return Data(block.begin(), block.end()); +} + +Proto::SigningOutput Signer::buildSigningOutput(const Proto::SigningInput& input, const Data& signature) { + auto output = Proto::SigningOutput(); + const auto pubKey = PublicKey(Data(input.public_key().begin(), input.public_key().end()), TWPublicKeyTypeED25519Blake2b); + auto block = hashBlockData(pubKey, input); + output.set_signature(signature.data(), signature.size()); + output.set_block_hash(block.data(), block.size()); + + auto prev = previousFromInput(input); + auto li = linkFromInput(input); + + // build json + json json = { + {"type", "state"}, + {"account", Address(pubKey).string()}, + {"previous", hex(prev)}, + {"representative", Address(input.representative()).string()}, + {"balance", input.balance()}, + {"link", hex(li)}, + {"link_as_account", Address(PublicKey(Data(li.begin(), li.end()), TWPublicKeyTypeED25519Blake2b)).string()}, + {"signature", hex(signature)}, + }; + + if (input.work().size() > 0) { + json["work"] = input.work(); + } + + output.set_json(json.dump()); + return output; +} + } // namespace TW::Nano diff --git a/src/Nano/Signer.h b/src/Nano/Signer.h index ea41ff542b2..fb71e452dfa 100644 --- a/src/Nano/Signer.h +++ b/src/Nano/Signer.h @@ -1,25 +1,24 @@ // Copyright © 2019 Mart Roosmaa. -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Address.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include namespace TW::Nano { /// Helper class that performs Ripple transaction signing. class Signer { - public: +public: /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; /// Signs a json Proto::SigningInput with private key static std::string signJSON(const std::string& json, const Data& key); - public: + +public: const PrivateKey privateKey; const PublicKey publicKey; const Proto::SigningInput& input; @@ -34,6 +33,9 @@ class Signer { /// Builds signed transaction, incl. signature, and json format Proto::SigningOutput build() const; + + static Data buildUnsignedTxBytes(const Proto::SigningInput& input); + static Proto::SigningOutput buildSigningOutput(const Proto::SigningInput& input, const Data& signature); }; } // namespace TW::Nano diff --git a/src/NativeEvmos/Entry.h b/src/NativeEvmos/Entry.h new file mode 100644 index 00000000000..17fccc33834 --- /dev/null +++ b/src/NativeEvmos/Entry.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Cosmos/Entry.h" + +namespace TW::NativeEvmos { + +/// Entry point for implementation of NativeEvmos coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final : public Cosmos::Entry { +}; + +} // namespace TW::NativeEvmos diff --git a/src/NativeInjective/Entry.h b/src/NativeInjective/Entry.h new file mode 100644 index 00000000000..9061e28c3cd --- /dev/null +++ b/src/NativeInjective/Entry.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Cosmos/Entry.h" + +namespace TW::NativeInjective { + +/// Entry point for implementation of NativeEvmos coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final : public Cosmos::Entry { +}; + +} // namespace TW::NativeInjective diff --git a/src/Nebulas/Address.cpp b/src/Nebulas/Address.cpp index ca94865b701..760dbe0084c 100644 --- a/src/Nebulas/Address.cpp +++ b/src/Nebulas/Address.cpp @@ -1,18 +1,17 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" #include "../Base58.h" #include "../Hash.h" #include "../HexCoding.h" +#include -using namespace TW::Nebulas; +namespace TW::Nebulas { bool Address::isValid(const std::string& string) { - auto data = Base58::bitcoin.decode(string); + auto data = Base58::decode(string); if (data.size() != (size_t)Address::size) { return false; } @@ -27,7 +26,7 @@ bool Address::isValid(const std::string& string) { Data content(data.begin(), data.begin() + 22); Data checksum(data.begin() + 22, data.end()); auto dataSha3 = Hash::sha3_256(content); - return ::memcmp(dataSha3.data(), checksum.data(), 4) == 0; + return std::memcmp(dataSha3.data(), checksum.data(), 4) == 0; } Address::Address(const std::string& string) { @@ -35,7 +34,7 @@ Address::Address(const std::string& string) { throw std::invalid_argument("Invalid address string"); } - auto data = Base58::bitcoin.decode(string); + auto data = Base58::decode(string); std::copy(data.begin(), data.end(), bytes.begin()); } @@ -46,19 +45,21 @@ Address::Address(const Data& data) { std::copy(data.begin(), data.end(), bytes.begin()); } -Address::Address(const PublicKey &publicKey) { +Address::Address(const PublicKey& publicKey) { if (publicKey.type != TWPublicKeyTypeSECP256k1Extended) { throw std::invalid_argument("Nebulas::Address needs an extended SECP256k1 public key."); } const auto data = publicKey.hash( {Address::AddressPrefix, Address::NormalType}, Hash::HasherSha3_256ripemd, false); - + std::copy(data.begin(), data.end(), bytes.begin()); auto checksum = Hash::sha3_256(data); std::copy(checksum.begin(), checksum.begin() + 4, bytes.begin() + 22); } std::string Address::string() const { - return Base58::bitcoin.encode(bytes); + return Base58::encode(bytes); } + +} // namespace TW::Nebulas diff --git a/src/Nebulas/Address.h b/src/Nebulas/Address.h index d0bcaa5c327..469a30876ad 100644 --- a/src/Nebulas/Address.h +++ b/src/Nebulas/Address.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Nebulas/Entry.cpp b/src/Nebulas/Entry.cpp index a432250c5da..cf953f588c5 100644 --- a/src/Nebulas/Entry.cpp +++ b/src/Nebulas/Entry.cpp @@ -1,27 +1,27 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" #include "Signer.h" -using namespace TW::Nebulas; using namespace std; +namespace TW::Nebulas { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Nebulas diff --git a/src/Nebulas/Entry.h b/src/Nebulas/Entry.h index ac3aeece572..4792552c1f4 100644 --- a/src/Nebulas/Entry.h +++ b/src/Nebulas/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,11 +10,11 @@ namespace TW::Nebulas { /// Entry point for implementation of Nebulas coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Nebulas diff --git a/src/Nebulas/Signer.cpp b/src/Nebulas/Signer.cpp index 383ae6b3575..ada56c94f8b 100644 --- a/src/Nebulas/Signer.cpp +++ b/src/Nebulas/Signer.cpp @@ -1,29 +1,20 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "Base64.h" #include "../HexCoding.h" using namespace TW; -using namespace TW::Nebulas; + +namespace TW::Nebulas { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(load(input.chain_id())); - auto tx = Transaction(Address(input.from_address()), - load(input.nonce()), - load(input.gas_price()), - load(input.gas_limit()), - Address(input.to_address()), - load(input.amount()), - load(input.timestamp()), - input.payload() - ); - + auto tx = signer.buildTransaction(input); + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); signer.sign(privateKey, tx); @@ -34,7 +25,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { return output; } -void Signer::sign(const PrivateKey &privateKey, Transaction &transaction) const noexcept { +void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) const noexcept { transaction.hash = this->hash(transaction); transaction.chainID = chainID; transaction.algorithm = 1; @@ -42,12 +33,26 @@ void Signer::sign(const PrivateKey &privateKey, Transaction &transaction) const transaction.serializeToRaw(); } -Data Signer::hash(const Transaction &transaction) const noexcept { +Data Signer::hash(const Transaction& transaction) const noexcept { + return Hash::sha3_256(getPreImage(transaction)); +} + +Data Signer::hash(const Data& data) const noexcept { + return Hash::sha3_256(data); +} + +Transaction Signer::buildTransaction(const Proto::SigningInput& input) const noexcept { + return {Transaction(Address(input.from_address()), load(input.nonce()), load(input.gas_price()), + load(input.gas_limit()), Address(input.to_address()), load(input.amount()), + load(input.timestamp()), input.payload())}; +} + +Data Signer::getPreImage(const Transaction& transaction) const noexcept { auto encoded = Data(); auto payload = Data(); auto* data = Transaction::newPayloadData(transaction.payload); payload.resize(data->ByteSizeLong()); - data->SerializePartialToArray(payload.data(),(int)payload.size()); + data->SerializePartialToArray(payload.data(), (int)payload.size()); delete data; encoded.insert(encoded.end(), transaction.from.bytes.begin(), transaction.from.bytes.end()); @@ -59,5 +64,7 @@ Data Signer::hash(const Transaction &transaction) const noexcept { encode256BE(encoded, chainID, 32); encode256BE(encoded, transaction.gasPrice, 128); encode256BE(encoded, transaction.gasLimit, 128); - return Hash::sha3_256(encoded); + return encoded; } + +} // namespace TW::Nebulas diff --git a/src/Nebulas/Signer.h b/src/Nebulas/Signer.h index 86a74d2bed2..51238e21f3d 100644 --- a/src/Nebulas/Signer.h +++ b/src/Nebulas/Signer.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/Nebulas.pb.h" #include "../uint256.h" @@ -32,6 +30,14 @@ class Signer { protected: /// Computes the transaction hash. Data hash(const Transaction& transaction) const noexcept; + + /// Computes hash. + Data hash(const Data& preImage) const noexcept; + + Transaction buildTransaction(const Proto::SigningInput& input) const noexcept; + + /// Get transaction data. + Data getPreImage(const Transaction& transaction) const noexcept; }; } // namespace TW::Nebulas diff --git a/src/Nebulas/Transaction.cpp b/src/Nebulas/Transaction.cpp index c243e3cd769..915c57f60a2 100644 --- a/src/Nebulas/Transaction.cpp +++ b/src/Nebulas/Transaction.cpp @@ -1,70 +1,73 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -#include +// Copyright © 2017 Trust Wallet. + #include "Transaction.h" #include "../HexCoding.h" +#include using namespace TW; -using namespace TW::Nebulas; - -const char *Transaction::TxPayloadBinaryType = "binary"; -const char *Transaction::TxPayloadDeployType = "deploy"; -const char *Transaction::TxPayloadCallType = "call"; std::string htmlescape(const std::string& str) { std::string result; - for(size_t i=0; i': result += "\\u003e"; break; - case '<': result += "\\u003c"; break; - case 0x20: - if(i+1 < str.size()) { - if(str[i+1]==0x28) { - result += "\\u2028"; - ++i; - break; - } - else if (str[i+1]==0x29) { - result += "\\u2029"; - ++i; - break; - } + for (size_t i = 0; i < str.size(); ++i) { + switch (str[i]) { + case '&': + result += "\\u0026"; + break; + case '>': + result += "\\u003e"; + break; + case '<': + result += "\\u003c"; + break; + case 0x20: + if (i + 1 < str.size()) { + if (str[i + 1] == 0x28) { + result += "\\u2028"; + ++i; + break; + } else if (str[i + 1] == 0x29) { + result += "\\u2029"; + ++i; + break; } - default: result += str[i]; break; + } + default: + result += str[i]; + break; } } return result; } -Proto::Data* Transaction::newPayloadData(const std::string& payload){ +namespace TW::Nebulas { + +const char* Transaction::TxPayloadBinaryType = "binary"; +const char* Transaction::TxPayloadDeployType = "deploy"; +const char* Transaction::TxPayloadCallType = "call"; + +Proto::Data* Transaction::newPayloadData(const std::string& payload) { auto* data = new Proto::Data(); data->set_type(Transaction::TxPayloadBinaryType); nlohmann::json payloadData; - if(!payload.empty()) { + if (!payload.empty()) { auto json = nlohmann::json::parse(payload); - if(json.find("binary")!=json.end()) { + if (json.find("binary") != json.end()) { std::string binary_data = json["binary"]; - auto buff = Data(binary_data.begin(),binary_data.end()); + auto buff = Data(binary_data.begin(), binary_data.end()); payloadData["Data"]["type"] = "Buffer"; payloadData["Data"]["data"] = nlohmann::json(buff); } } - if(!payloadData.empty()) + if (!payloadData.empty()) data->set_payload(htmlescape(payloadData.dump())); return data; } -void Transaction::serializeToRaw(){ - if(signature.empty()) { +void Transaction::serializeToRaw() { + if (signature.empty()) { throw std::logic_error("The transaction is unsigned!"); } @@ -74,23 +77,25 @@ void Transaction::serializeToRaw(){ auto value = Data(); auto gas_price = Data(); auto gas_limit = Data(); - tx.set_hash(reinterpret_cast(hash.data()),hash.size()); - tx.set_from(from.bytes.data(),from.size); - tx.set_to(to.bytes.data(),to.size); + tx.set_hash(reinterpret_cast(hash.data()), hash.size()); + tx.set_from(from.bytes.data(), from.size); + tx.set_to(to.bytes.data(), to.size); encode256BE(value, amount, 128); - tx.set_value(value.data(),value.size()); + tx.set_value(value.data(), value.size()); tx.set_nonce((uint64_t)nonce); tx.set_timestamp((int64_t)timestamp); tx.set_allocated_data(data); tx.set_chain_id((uint32_t)chainID); encode256BE(gas_price, gasPrice, 128); - tx.set_gas_price(gas_price.data(),gas_price.size()); + tx.set_gas_price(gas_price.data(), gas_price.size()); encode256BE(gas_limit, gasLimit, 128); - tx.set_gas_limit(gas_limit.data(),gas_limit.size()); - + tx.set_gas_limit(gas_limit.data(), gas_limit.size()); + tx.set_alg((uint32_t)algorithm); - tx.set_sign(reinterpret_cast(signature.data()),signature.size()); + tx.set_sign(reinterpret_cast(signature.data()), signature.size()); raw.resize(tx.ByteSizeLong()); - tx.SerializeToArray(raw.data(),(int)raw.size()); -} \ No newline at end of file + tx.SerializeToArray(raw.data(), (int)raw.size()); +} + +} // namespace TW::Nebulas diff --git a/src/Nebulas/Transaction.h b/src/Nebulas/Transaction.h index dfeb5585f04..3edc9e231a6 100644 --- a/src/Nebulas/Transaction.h +++ b/src/Nebulas/Transaction.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Nervos/Address.cpp b/src/Nervos/Address.cpp new file mode 100644 index 00000000000..5f27a73f94d --- /dev/null +++ b/src/Nervos/Address.cpp @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Address.h" +#include "Constants.h" + +#include "../Bech32.h" +#include "../Coin.h" + +#include +#include +#include + +namespace TW::Nervos { + +[[nodiscard]] bool Address::isValid(const std::string& string) noexcept { + return Address::isValid(string, HRP_NERVOS); +} + +[[nodiscard]] bool Address::isValid(const std::string& string, const char* hrp) noexcept { + return Address().decode(string, hrp); +} + +Address::Address(const std::string& string, const char* hrp) { + if (!decode(string, hrp)) { + throw std::invalid_argument("Invalid address string"); + } +} + +bool Address::decode(const std::string& string, const char* hrp) noexcept { + _hrp = hrp; + auto decoded = Bech32::decode(string); + auto&& [decodedHrp, decodedData, decodedVariant] = decoded; + if (decodedHrp.compare(hrp)) { + return false; + } + Data decodedPayload; + if (!Bech32::convertBits<5, 8, false>(decodedPayload, decodedData)) { + return false; + } + if (decodedPayload.empty()) { + return false; + } + addressType = AddressType(decodedPayload[0]); + switch (addressType) { + case AddressType::FullVersion: { + size_t codeHashOffset = 1; + size_t codeHashSize = 32; + size_t hashTypeOffset = codeHashOffset + codeHashSize; + size_t hashTypeSize = 1; + size_t argsOffset = hashTypeOffset + hashTypeSize; + if (decodedVariant != Bech32::ChecksumVariant::Bech32M) { + return false; + } + if (decodedPayload.size() < argsOffset) { + return false; + } + codeHashIndex = -1; + codeHash = Data(decodedPayload.begin() + codeHashOffset, + decodedPayload.begin() + codeHashOffset + codeHashSize); + hashType = HashType(decodedPayload[hashTypeOffset]); + args = Data(decodedPayload.begin() + argsOffset, decodedPayload.end()); + break; + } + case AddressType::HashIdx: { + size_t codeHashIndexOffset = 1; + size_t codeHashIndexSize = 1; + size_t argsOffset = codeHashIndexOffset + codeHashIndexSize; + size_t argsSize = 20; + if (decodedVariant != Bech32::ChecksumVariant::Bech32) { + return false; + } + if (decodedPayload.size() != argsOffset + argsSize) { + return false; + } + codeHashIndex = decodedPayload[codeHashIndexOffset]; + if (codeHashIndex != 0) { + return false; + } + codeHash = Constants::gSecp256k1CodeHash; + hashType = HashType::Type1; + args = Data(decodedPayload.begin() + argsOffset, decodedPayload.end()); + break; + } + case AddressType::DataCodeHash: + case AddressType::TypeCodeHash: { + size_t codeHashOffset = 1; + size_t codeHashSize = 32; + size_t argsOffset = codeHashOffset + codeHashSize; + if (decodedVariant != Bech32::ChecksumVariant::Bech32) { + return false; + } + if (decodedPayload.size() < argsOffset) { + return false; + } + codeHashIndex = -1; + codeHash = Data(decodedPayload.begin() + codeHashOffset, + decodedPayload.begin() + codeHashOffset + codeHashSize); + hashType = addressType == AddressType::DataCodeHash ? HashType::Data0 : HashType::Type1; + args = Data(decodedPayload.begin() + argsOffset, decodedPayload.end()); + break; + } + default: { + return false; + } + } + return true; +} + +Address::Address(const PublicKey& publicKey, const char* hrp) + : _hrp(hrp) { + if (publicKey.type != TWPublicKeyTypeSECP256k1) { + throw std::invalid_argument("Nervos::Address needs a SECP256k1 public key."); + } + addressType = AddressType::FullVersion; + codeHashIndex = -1; + codeHash = Constants::gSecp256k1CodeHash; + hashType = HashType::Type1; + Data publicKeyHash = Hash::blake2b(publicKey.bytes, 32, Constants::gHashPersonalization); + Data truncatedPublicKeyHash = Data(publicKeyHash.begin(), publicKeyHash.begin() + 20); + args = truncatedPublicKeyHash; +} + +std::string Address::string() const { + auto data = Data(); + data.emplace_back(addressType); + Bech32::ChecksumVariant checksumVariant; + switch (addressType) { + case AddressType::FullVersion: { + data.insert(data.end(), codeHash.begin(), codeHash.end()); + data.emplace_back(hashType); + data.insert(data.end(), args.begin(), args.end()); + checksumVariant = Bech32::ChecksumVariant::Bech32M; + break; + } + case AddressType::HashIdx: { + data.emplace_back(codeHashIndex); + data.insert(data.end(), args.begin(), args.end()); + checksumVariant = Bech32::ChecksumVariant::Bech32; + break; + } + case AddressType::DataCodeHash: + case AddressType::TypeCodeHash: { + data.insert(data.end(), codeHash.begin(), codeHash.end()); + data.insert(data.end(), args.begin(), args.end()); + checksumVariant = Bech32::ChecksumVariant::Bech32; + break; + } + default: { + return ""; + } + } + Data payload; + if (!Bech32::convertBits<8, 5, true>(payload, data)) { + return ""; + } + return Bech32::encode(_hrp, payload, checksumVariant); +} + +std::string Address::hashTypeString() const { + switch (hashType) { + case HashType::Data0: { + return HashTypeString[0]; + } + case HashType::Type1: { + return HashTypeString[1]; + } + case HashType::Data1: { + return HashTypeString[2]; + } + } +} + +} // namespace TW::Nervos diff --git a/src/Nervos/Address.h b/src/Nervos/Address.h new file mode 100644 index 00000000000..6e6568d4bf4 --- /dev/null +++ b/src/Nervos/Address.h @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include + +#include "Data.h" +#include "../PublicKey.h" + +#include + +namespace TW::Nervos { + +enum HashType { + Data0 = 0, + Type1 = 1, + Data1 = 2 +}; + +static const char* HashTypeString[] { + "data", + "type", + "data1" +}; + +enum AddressType { + FullVersion = 0, // full version identifies the hash_type + HashIdx = 1, // short version for locks with popular codehash, deprecated + DataCodeHash = 2, // full version with hash type 'Data', deprecated + TypeCodeHash = 4, // full version with hash type 'Type', deprecated +}; + +class Address { +public: + const char* _hrp; + AddressType addressType; + TW::byte codeHashIndex; + Data codeHash; + HashType hashType; + Data args; + + /// Determines whether a string makes a valid address. + [[nodiscard]] static bool isValid(const std::string& string) noexcept; + [[nodiscard]] static bool isValid(const std::string& string, const char* hrp) noexcept; + + /// Initializes a Nervos address with a string representation. + explicit Address(const std::string& string) : Address(string, HRP_NERVOS) {} + explicit Address(const std::string& string, const char* hrp); + + /// Initializes a Nervos address with a public key. + explicit Address(const PublicKey& publicKey) : Address(publicKey, HRP_NERVOS) {} + explicit Address(const PublicKey& publicKey, const char* hrp); + + /// Returns a string representation of the address. + std::string string() const; + + std::string hashTypeString() const; + +private: + Address() = default; + + // Decodes address from string + bool decode(const std::string& string, const char* hrp) noexcept; +}; + +inline bool operator==(const Address& lhs, const Address& rhs) { + return (lhs.codeHash == rhs.codeHash) && (lhs.hashType == rhs.hashType) && + (lhs.args == rhs.args); +} + +} // namespace TW::Nervos + +/// Wrapper for C interface. +struct TWNervosAddress { + TW::Nervos::Address impl; +}; diff --git a/src/Nervos/Cell.h b/src/Nervos/Cell.h new file mode 100644 index 00000000000..8f7c0c7f79e --- /dev/null +++ b/src/Nervos/Cell.h @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "OutPoint.h" +#include "Script.h" +#include "../proto/Nervos.pb.h" + +#include + +namespace TW::Nervos { + +struct Cell { + OutPoint outPoint; + uint64_t capacity; + Script lock; + Script type; + Data data; + uint64_t blockNumber; + Data blockHash; + uint64_t since; + Data inputType; + Data outputType; + + Cell() = default; + + // Copy constructor + Cell(const Cell& cell) + : outPoint(cell.outPoint) + , capacity(cell.capacity) + , lock(cell.lock) + , type(cell.type) + , data(cell.data) + , blockNumber(cell.blockNumber) + , blockHash(cell.blockHash) + , since(cell.since) + , inputType(cell.inputType) + , outputType(cell.outputType) {} + + // Move constructor + Cell(Cell&& cell) + : outPoint(std::move(cell.outPoint)) + , capacity(cell.capacity) + , lock(std::move(cell.lock)) + , type(std::move(cell.type)) + , data(std::move(cell.data)) + , blockNumber(cell.blockNumber) + , blockHash(std::move(cell.blockHash)) + , since(cell.since) + , inputType(std::move(cell.inputType)) + , outputType(std::move(cell.outputType)) {} + + // Copy assignment operator + Cell& operator=(const Cell& cell) { + outPoint = cell.outPoint; + capacity = cell.capacity; + lock = cell.lock; + type = cell.type; + data = cell.data; + blockNumber = cell.blockNumber; + blockHash = cell.blockHash; + since = cell.since; + inputType = cell.inputType; + outputType = cell.outputType; + return *this; + } + + Cell(const Proto::Cell& cell) + : outPoint(cell.out_point()) + , capacity(cell.capacity()) + , lock(cell.lock()) + , type(cell.type()) + , blockNumber(cell.block_number()) + , since(cell.since()) { + auto&& cellData = cell.data(); + data.insert(data.end(), cellData.begin(), cellData.end()); + auto&& cellBlockHash = cell.block_hash(); + blockHash.insert(blockHash.end(), cellBlockHash.begin(), cellBlockHash.end()); + auto&& cellInputType = cell.input_type(); + inputType.insert(inputType.end(), cellInputType.begin(), cellInputType.end()); + auto&& cellOutputType = cell.output_type(); + outputType.insert(outputType.end(), cellOutputType.begin(), cellOutputType.end()); + } + + Proto::Cell proto() const { + auto cell = Proto::Cell(); + *cell.mutable_out_point() = outPoint.proto(); + cell.set_capacity(capacity); + *cell.mutable_lock() = lock.proto(); + *cell.mutable_type() = type.proto(); + cell.set_data(std::string(data.begin(), data.end())); + cell.set_block_number(blockNumber); + cell.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + cell.set_since(since); + cell.set_input_type(std::string(inputType.begin(), inputType.end())); + cell.set_output_type(std::string(outputType.begin(), outputType.end())); + return cell; + } +}; + +/// A list of Cell's +using Cells = std::vector; + +} // namespace TW::Nervos diff --git a/src/Nervos/CellDep.cpp b/src/Nervos/CellDep.cpp new file mode 100644 index 00000000000..1dabbebfa66 --- /dev/null +++ b/src/Nervos/CellDep.cpp @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "CellDep.h" + +#include "../BinaryCoding.h" + +namespace TW::Nervos { + +CellDep::CellDep(const Proto::CellDep& cellDep) + : outPoint(cellDep.out_point()) { + auto&& depTypeString = cellDep.dep_type(); + for (int i = 0; i < (int)(sizeof(DepTypeString) / sizeof(DepTypeString[0])); i++) { + if (depTypeString == DepTypeString[i]) { + depType = (DepType)i; + } + } +} + +Proto::CellDep CellDep::proto() const { + auto cellDep = Proto::CellDep(); + *cellDep.mutable_out_point() = outPoint.proto(); + cellDep.set_dep_type(DepTypeString[depType]); + return cellDep; +} + +void CellDep::encode(Data& data) const { + outPoint.encode(data); + data.emplace_back(depType); +} + +nlohmann::json CellDep::json() const { + return nlohmann::json{{"out_point", outPoint.json()}, {"dep_type", DepTypeString[depType]}}; +} + +} // namespace TW::Nervos diff --git a/src/Nervos/CellDep.h b/src/Nervos/CellDep.h new file mode 100644 index 00000000000..d28543052e0 --- /dev/null +++ b/src/Nervos/CellDep.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "OutPoint.h" +#include "../proto/Nervos.pb.h" +#include + +namespace TW::Nervos { + +enum DepType { + Code = 0, + DepGroup = 1 +}; + +static const char* DepTypeString[] { + "code", + "dep_group" +}; + +/// Nervos cell dep. +struct CellDep { + OutPoint outPoint; + DepType depType; + + /// Initializes a cell dep with a previous output and depType + CellDep(OutPoint outPoint, DepType depType) : outPoint(std::move(outPoint)), depType(depType) {} + + CellDep(const Proto::CellDep& cellDep); + + /// Encodes the transaction into the provided buffer. + void encode(Data& data) const; + nlohmann::json json() const; + + Proto::CellDep proto() const; +}; + +/// A list of Cell Deps +using CellDeps = std::vector; + +} // namespace TW::Nervos diff --git a/src/Nervos/CellInput.cpp b/src/Nervos/CellInput.cpp new file mode 100644 index 00000000000..66e248f2f29 --- /dev/null +++ b/src/Nervos/CellInput.cpp @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "CellInput.h" +#include "Serialization.h" + +namespace TW::Nervos { + +void CellInput::encode(Data& data) const { + encode64LE(since, data); + previousOutput.encode(data); +} + +nlohmann::json CellInput::json() const { + return nlohmann::json{{"previous_output", previousOutput.json()}, + {"since", Serialization::numberToHex(since)}}; +} + +} // namespace TW::Nervos diff --git a/src/Nervos/CellInput.h b/src/Nervos/CellInput.h new file mode 100644 index 00000000000..09f5b4da00c --- /dev/null +++ b/src/Nervos/CellInput.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "OutPoint.h" +#include + +namespace TW::Nervos { + +/// Nervos cell input. +struct CellInput { + /// Reference to the previous transaction's output. + OutPoint previousOutput; + + /// Prevents the transaction to be mined before an absolute or relative time. + uint64_t since; + + /// Initializes a cell input with a previous output and since + CellInput(OutPoint previousOutput, uint64_t since) + : previousOutput(std::move(previousOutput)), since(since) {} + + /// Encodes the transaction into the provided buffer. + void encode(Data& data) const; + + /// Encodes the output into json format. + nlohmann::json json() const; +}; + +/// A list of Cell Inputs +using CellInputs = std::vector; + +} // namespace TW::Nervos diff --git a/src/Nervos/CellOutput.cpp b/src/Nervos/CellOutput.cpp new file mode 100644 index 00000000000..ea938845446 --- /dev/null +++ b/src/Nervos/CellOutput.cpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "CellOutput.h" +#include "Serialization.h" + +namespace TW::Nervos { + +void CellOutput::encode(Data& data) const { + Data capacityData; + Data lockData; + Data typeData; + encode64LE(capacity, capacityData); + lock.encode(lockData); + type.encode(typeData); + Serialization::encodeDataArray(std::vector{capacityData, lockData, typeData}, data); +} + +nlohmann::json CellOutput::json() const { + return nlohmann::json{{"capacity", Serialization::numberToHex(capacity)}, + {"lock", lock.json()}, + {"type", type.json()}}; +} + +} // namespace TW::Nervos diff --git a/src/Nervos/CellOutput.h b/src/Nervos/CellOutput.h new file mode 100644 index 00000000000..d3e5ac57fd6 --- /dev/null +++ b/src/Nervos/CellOutput.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Script.h" +#include "Data.h" +#include "../proto/Nervos.pb.h" +#include + +#include + +namespace TW::Nervos { + +/// Nervos cell output. +struct CellOutput { + uint64_t capacity; + Script lock; + Script type; + + /// Initializes an empty cell output. + CellOutput() = default; + + /// Initializes a cell output with a capacity and scripts. + CellOutput(uint64_t capacity, Script&& lock, Script&& type) + : capacity(capacity), lock(std::move(lock)), type(std::move(type)) {} + + /// Initializes a CellInput from a Protobuf CellInput. + CellOutput(const Proto::CellOutput& cellOutput) + : capacity(cellOutput.capacity()), lock(cellOutput.lock()), type(cellOutput.type()) {} + + /// Encodes the output into the provided buffer. + void encode(Data& data) const; + + /// Encodes the output into json format. + nlohmann::json json() const; + + Proto::CellOutput proto() const { + auto cellOutput = Proto::CellOutput(); + cellOutput.set_capacity(capacity); + *cellOutput.mutable_lock() = lock.proto(); + *cellOutput.mutable_type() = type.proto(); + return cellOutput; + } +}; + +/// A list of Cell Outputs +using CellOutputs = std::vector; + +} // namespace TW::Nervos diff --git a/src/Nervos/Constants.h b/src/Nervos/Constants.h new file mode 100644 index 00000000000..d492dcc76c4 --- /dev/null +++ b/src/Nervos/Constants.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Address.h" +#include "CellDep.h" +#include "OutPoint.h" +#include "Data.h" +#include "../HexCoding.h" + +#include +#include + +namespace TW::Nervos::Constants { + +static const uint64_t gTransactionBaseSize = 72; +static const uint64_t gCellDepSize = 37; +static const uint64_t gHeaderDepSize = 32; +static const uint64_t gSingleInputAndWitnessBaseSize = 44; +static const uint64_t gBlankWitnessBytes = 65; +static const uint64_t gUint32Size = 4; +static const uint64_t gMinCellCapacityForNativeToken = 6100000000; +static const uint64_t gMinCellCapacityForSUDT = 14400000000; + +static const Data gHashPersonalization{'c', 'k', 'b', '-', 'd', 'e', 'f', 'a', + 'u', 'l', 't', '-', 'h', 'a', 's', 'h'}; + +static const Data gSecp256k1CodeHash = + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"); + +static const CellDep gSecp256k1CellDep = CellDep( + OutPoint(parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c"), 0), + DepType::DepGroup); + +static const Data gSUDTCodeHash = + parse_hex("5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5"); + +static const CellDep gSUDTCellDep = CellDep( + OutPoint(parse_hex("c7813f6a415144643970c2e88e0bb6ca6a8edc5dd7c1022746f628284a9936d5"), 0), + DepType::Code); + +static const Data gDAOCodeHash = + parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"); + +static const CellDep gDAOCellDep = CellDep( + OutPoint(parse_hex("e2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c"), 2), + DepType::Code); + +} // namespace TW::Nervos::Constants diff --git a/src/Nervos/Entry.cpp b/src/Nervos/Entry.cpp new file mode 100644 index 00000000000..e8a2b4056c4 --- /dev/null +++ b/src/Nervos/Entry.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Entry.h" + +#include "Address.h" +#include "Signer.h" + +namespace TW::Nervos { +using namespace std; + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const { + auto* hrpPrefix = std::get_if(&addressPrefix); + return Address::isValid(address, hrpPrefix ? *hrpPrefix : HRP_NERVOS); +} + +std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, const PrefixVariant& addressPrefix) const { + const char* hrp = getFromPrefixHrpOrDefault(addressPrefix, coin); + return Address(publicKey, hrp).string(); +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const Data& dataIn, Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +void Entry::plan([[maybe_unused]] TWCoinType coin, const Data& dataIn, Data& dataOut) const { + planTemplate(dataIn, dataOut); +} + +} // namespace TW::Nervos diff --git a/src/Nervos/Entry.h b/src/Nervos/Entry.h new file mode 100644 index 00000000000..a0876175889 --- /dev/null +++ b/src/Nervos/Entry.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../CoinEntry.h" + +using namespace TW; + +namespace TW::Nervos { + +/// Entry point for implementation of Nervos coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific +/// includes in this file +class Entry final : public CoinEntry { +public: + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; +}; + +} // namespace TW::Nervos diff --git a/src/Nervos/HeaderDep.h b/src/Nervos/HeaderDep.h new file mode 100644 index 00000000000..63e440f9d93 --- /dev/null +++ b/src/Nervos/HeaderDep.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include + +namespace TW::Nervos { + +using HeaderDep = Data; + +/// A list of header deps +using HeaderDeps = std::vector; + +} // namespace TW::Nervos diff --git a/src/Nervos/OutPoint.cpp b/src/Nervos/OutPoint.cpp new file mode 100644 index 00000000000..b6c2992e09d --- /dev/null +++ b/src/Nervos/OutPoint.cpp @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "OutPoint.h" +#include "Serialization.h" + +namespace TW::Nervos { + +void OutPoint::encode(Data& data) const { + data.insert(data.end(), txHash.begin(), txHash.end()); + encode32LE(index, data); +} + +nlohmann::json OutPoint::json() const { + return nlohmann::json{{"tx_hash", hexEncoded(txHash)}, + {"index", Serialization::numberToHex(uint64_t(index))}}; +} + +} // namespace TW::Nervos \ No newline at end of file diff --git a/src/Nervos/OutPoint.h b/src/Nervos/OutPoint.h new file mode 100644 index 00000000000..5c7b9e6a241 --- /dev/null +++ b/src/Nervos/OutPoint.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "../proto/Nervos.pb.h" +#include + +#include +#include +#include + +namespace TW::Nervos { + +/// Nervos transaction out-point reference. +struct OutPoint { + /// The hash of the referenced transaction. + Data txHash; + + /// The index of the specific output in the transaction. + uint32_t index; + + OutPoint() = default; + + /// Initializes an out-point reference with hash, index. + template + OutPoint(const T& h, uint32_t index) : txHash(std::begin(h), std::end(h)), index(index) {} + + /// Initializes an out-point from a Protobuf out-point. + OutPoint(const Proto::OutPoint& outPoint) + : txHash(std::begin(outPoint.tx_hash()), std::end(outPoint.tx_hash())) + , index(outPoint.index()) {} + + /// Encodes the out-point into the provided buffer. + void encode(Data& data) const; + nlohmann::json json() const; + + friend bool operator==(const OutPoint& lhs, const OutPoint& rhs) { + return (lhs.txHash == rhs.txHash && lhs.index == rhs.index); + } + + Proto::OutPoint proto() const { + auto outPoint = Proto::OutPoint(); + outPoint.set_tx_hash(std::string(txHash.begin(), txHash.end())); + outPoint.set_index(index); + return outPoint; + } +}; + +} // namespace TW::Nervos diff --git a/src/Nervos/Script.cpp b/src/Nervos/Script.cpp new file mode 100644 index 00000000000..abcf703259c --- /dev/null +++ b/src/Nervos/Script.cpp @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Script.h" +#include "Constants.h" +#include "Serialization.h" +#include "../Bech32.h" + +#include +#include + +namespace TW::Nervos { + +Data Script::hash() const { + Data data; + encode(data); + return Hash::blake2b(data, 32, Constants::gHashPersonalization); +} + +[[nodiscard]] bool Script::empty() const { + return std::all_of(codeHash.begin(), codeHash.end(), [](byte element) { return element == 0; }); +} + +void Script::encode(Data& data) const { + Data hashTypeData(1); + Data argsData; + if (empty()) { + return; + } + hashTypeData[0] = hashType; + encode32LE(uint32_t(args.size()), argsData); + argsData.insert(argsData.end(), args.begin(), args.end()); + Serialization::encodeDataArray(std::vector{codeHash, hashTypeData, argsData}, data); +} + +nlohmann::json Script::json() const { + if (empty()) { + return nullptr; + } else { + return nlohmann::json{{"code_hash", hexEncoded(codeHash)}, + {"hash_type", HashTypeString[hashType]}, + {"args", hexEncoded(args)}}; + } +} + +} // namespace TW::Nervos diff --git a/src/Nervos/Script.h b/src/Nervos/Script.h new file mode 100644 index 00000000000..2c738a5c3af --- /dev/null +++ b/src/Nervos/Script.h @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Address.h" +#include "Constants.h" +#include "Data.h" +#include "../proto/Nervos.pb.h" +#include + +#include +#include + +namespace TW::Nervos { + +struct Script { + Data codeHash; + HashType hashType; + Data args; + + /// Initializes an empty script. + Script() { + hashType = HashType::Data0; + } + + /// Copy constructor + Script(const Script& script) + : codeHash(script.codeHash), hashType(script.hashType), args(script.args) {} + + /// Move constructor + Script(Script&& script) + : codeHash(std::move(script.codeHash)) + , hashType(script.hashType) + , args(std::move(script.args)) {} + + /// Initializes a script with codeHash, args and hashType. + Script(const Data& codeHash, const HashType hashType, const Data& args) + : codeHash(codeHash), hashType(hashType), args(args) {} + + /// Initializes a script with the given address. + Script(const Address& address) + : codeHash(address.codeHash), hashType(address.hashType), args(address.args) {} + + // Copy assignment operator + Script& operator=(const Script& script) { + codeHash = script.codeHash; + hashType = script.hashType; + args = script.args; + return *this; + } + + // Move assignment operator + Script& operator=(Script&& script) { + codeHash = std::move(script.codeHash); + hashType = script.hashType; + args = std::move(script.args); + return *this; + } + + friend bool operator==(const Script& lhs, const Script& rhs) { + return (lhs.codeHash == rhs.codeHash) && (lhs.hashType == rhs.hashType) && + (lhs.args == rhs.args); + } + + friend bool operator!=(const Script& lhs, const Script& rhs) { return !(lhs == rhs); } + + /// Returns the script's script hash. + Data hash() const; + + /// Whether the script is empty. + [[nodiscard]] bool empty() const; + + /// Initializes an script from a Protobuf script. + Script(const Proto::Script& script) { + auto&& scriptCodeHash = script.code_hash(); + codeHash.insert(codeHash.end(), scriptCodeHash.begin(), scriptCodeHash.end()); + auto&& hashTypeString = script.hash_type(); + hashType = HashType::Data0; + for (int i = 0; i < (int)(sizeof(HashTypeString) / sizeof(HashTypeString[0])); i++) { + if (hashTypeString == HashTypeString[i]) { + hashType = (HashType)i; + } + } + auto&& scriptArgs = script.args(); + args.insert(args.end(), scriptArgs.begin(), scriptArgs.end()); + } + + /// Encodes the script. + void encode(Data& data) const; + + /// Encodes the script into json format. + nlohmann::json json() const; + + Proto::Script proto() const { + auto script = Proto::Script(); + script.set_code_hash(std::string(codeHash.begin(), codeHash.end())); + script.set_hash_type(HashTypeString[hashType]); + script.set_args(std::string(args.begin(), args.end())); + return script; + } +}; + +} // namespace TW::Nervos diff --git a/src/Nervos/Serialization.h b/src/Nervos/Serialization.h new file mode 100644 index 00000000000..96760e14321 --- /dev/null +++ b/src/Nervos/Serialization.h @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../BinaryCoding.h" +#include "Data.h" +#include "../HexCoding.h" +#include "../uint256.h" + +#include +#include + +namespace TW::Nervos { + +struct Serialization { + static void encodeDataArray(const std::vector& dataArray, Data& data) { + uint32_t dataLength = std::accumulate(dataArray.begin(), dataArray.end(), uint32_t(0), + [](const uint32_t total, const Data& element) { + return total + uint32_t(element.size()); + }); + uint32_t headerLength = 4 + 4 * uint32_t(dataArray.size()); + uint32_t fullLength = headerLength + dataLength; + encode32LE(fullLength, data); + std::accumulate(dataArray.begin(), dataArray.end(), headerLength, + [&data](const uint32_t offset, const Data& element) { + encode32LE(offset, data); + return offset + uint32_t(element.size()); + }); + for (auto&& element : dataArray) { + data.insert(data.end(), element.begin(), element.end()); + } + } + + static Data encodeUint256(uint256_t number, byte minLen = 0) { + auto data = store(number, minLen); + std::reverse(data.begin(), data.end()); + return data; + } + + static uint256_t decodeUint256(const Data& data) { + auto data1 = Data(data); + std::reverse(data1.begin(), data1.end()); + return load(data1); + } + + static std::string numberToHex(uint64_t number) { + auto str = hex(number); + str.erase(0, str.find_first_not_of('0')); + if (str.length() == 0) { + return "0x0"; + } else { + return str.insert(0, "0x"); + } + } +}; +} // namespace TW::Nervos diff --git a/src/Nervos/Signer.cpp b/src/Nervos/Signer.cpp new file mode 100644 index 00000000000..d5cb006ad30 --- /dev/null +++ b/src/Nervos/Signer.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Signer.h" +#include "Transaction.h" +#include "TransactionPlan.h" + +namespace TW::Nervos { + +Proto::TransactionPlan Signer::plan(const Proto::SigningInput& signingInput) noexcept { + TransactionPlan txPlan; + txPlan.plan(signingInput); + return txPlan.proto(); +} + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& signingInput) noexcept { + Proto::SigningOutput output; + + TransactionPlan txPlan; + if (signingInput.has_plan()) { + txPlan = TransactionPlan(signingInput.plan()); + } else { + txPlan.plan(signingInput); + } + if (txPlan.error != Common::Proto::OK) { + // Planning failed + output.set_error(txPlan.error); + return output; + } + + Transaction tx; + tx.build(txPlan); + std::vector privateKeys; + privateKeys.reserve(signingInput.private_key_size()); + for (auto&& privateKey : signingInput.private_key()) { + privateKeys.emplace_back(privateKey); + } + auto error = tx.sign(privateKeys); + if (error != Common::Proto::OK) { + // Signing failed + output.set_error(error); + return output; + } + + output.set_transaction_json(tx.json().dump()); + output.set_transaction_id(hexEncoded(tx.hash())); + output.set_error(Common::Proto::OK); + + return output; +} + +} // namespace TW::Nervos diff --git a/src/Nervos/Signer.h b/src/Nervos/Signer.h new file mode 100644 index 00000000000..bbbc70d3677 --- /dev/null +++ b/src/Nervos/Signer.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "CoinEntry.h" +#include "Data.h" +#include "../proto/Nervos.pb.h" + +namespace TW::Nervos { + +class Signer { +public: + Signer() = delete; + + /// Returns a transaction plan (utxo selection, fee estimation) + static Proto::TransactionPlan plan(const Proto::SigningInput& signingInputProto) noexcept; + + /// Signs a Proto::SigningInput transaction + static Proto::SigningOutput sign(const Proto::SigningInput& signingInputProto) noexcept; +}; + +} // namespace TW::Nervos diff --git a/src/Nervos/Transaction.cpp b/src/Nervos/Transaction.cpp new file mode 100644 index 00000000000..ca65efa723e --- /dev/null +++ b/src/Nervos/Transaction.cpp @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Transaction.h" +#include "Constants.h" +#include "Serialization.h" + +#include +#include +#include +#include + +#include + +namespace TW::Nervos { + +Data Transaction::hash() const { + Data data; + std::vector dataArray; + dataArray.reserve(6); + + // version + Data versionData; + encode32LE(version, versionData); + dataArray.emplace_back(versionData); + + // cell deps + Data cellDepsData; + encode32LE(uint32_t(cellDeps.size()), cellDepsData); + for (auto&& cellDep : cellDeps) { + cellDep.encode(cellDepsData); + } + dataArray.emplace_back(cellDepsData); + + // header deps + Data headerDepsData; + encode32LE(uint32_t(headerDeps.size()), headerDepsData); + for (auto&& headerDep : headerDeps) { + headerDepsData.insert(headerDepsData.end(), headerDep.begin(), headerDep.end()); + } + dataArray.emplace_back(headerDepsData); + + // inputs + Data inputsData; + encode32LE(uint32_t(inputs.size()), inputsData); + for (auto&& input : inputs) { + input.encode(inputsData); + } + dataArray.emplace_back(inputsData); + + // outputs + Data outputsData1; + std::vector outputsData1Array; + outputsData1Array.reserve(outputs.size()); + for (auto&& output : outputs) { + Data outputData1; + output.encode(outputData1); + outputsData1Array.emplace_back(outputData1); + } + Serialization::encodeDataArray(outputsData1Array, outputsData1); + dataArray.emplace_back(outputsData1); + + // outputs data + Data outputsData2; + std::vector outputsData2Array; + outputsData2Array.reserve(outputsData.size()); + for (auto&& outputData : outputsData) { + Data outputData2; + encode32LE(uint32_t(outputData.size()), outputData2); + outputData2.insert(outputData2.end(), outputData.begin(), outputData.end()); + outputsData2Array.emplace_back(outputData2); + } + Serialization::encodeDataArray(outputsData2Array, outputsData2); + dataArray.emplace_back(outputsData2); + + Serialization::encodeDataArray(dataArray, data); + + return Hash::blake2b(data, 32, Constants::gHashPersonalization); +} + +nlohmann::json Transaction::json() const { + auto json = nlohmann::json(); + json["version"] = "0x0"; + auto cellDepsJSON = nlohmann::json::array(); + for (auto&& cellDep : cellDeps) { + cellDepsJSON.push_back(cellDep.json()); + } + json["cell_deps"] = cellDepsJSON; + auto headerDepsJSON = nlohmann::json::array(); + for (auto&& headerDep : headerDeps) { + headerDepsJSON.push_back(hexEncoded(headerDep)); + } + json["header_deps"] = headerDepsJSON; + auto inputsJSON = nlohmann::json::array(); + for (auto&& input : inputs) { + inputsJSON.push_back(input.json()); + } + json["inputs"] = inputsJSON; + auto outputsJSON = nlohmann::json::array(); + for (auto&& output : outputs) { + outputsJSON.push_back(output.json()); + } + json["outputs"] = outputsJSON; + auto outputsDataJSON = nlohmann::json::array(); + for (auto&& outputData : outputsData) { + outputsDataJSON.push_back(hexEncoded(outputData)); + } + json["outputs_data"] = outputsDataJSON; + auto witnessesJSON = nlohmann::json::array(); + for (auto&& serializedWitness : serializedWitnesses) { + witnessesJSON.push_back(hexEncoded(serializedWitness)); + } + json["witnesses"] = witnessesJSON; + return json; +} + +void Transaction::build(const TransactionPlan& txPlan) { + cellDeps = txPlan.cellDeps; + headerDeps = txPlan.headerDeps; + selectedCells = txPlan.selectedCells; + outputs = txPlan.outputs; + outputsData = txPlan.outputsData; + for (auto&& cell : selectedCells) { + inputs.emplace_back(cell.outPoint, cell.since); + } +} + +Common::Proto::SigningError Transaction::sign(const std::vector& privateKeys) { + formGroups(); + return signGroups(privateKeys); +} + +void Transaction::formGroups() { + for (size_t index = 0; index < selectedCells.size(); index++) { + auto&& cell = selectedCells[index]; + auto lockHash = cell.lock.hash(); + int groupNum = -1; + for (size_t groupNum1 = 0; groupNum1 < m_groupNumToLockHash.size(); groupNum1++) { + if (lockHash == m_groupNumToLockHash[groupNum1]) { + // Group found. Add to existing group. + groupNum = int(groupNum1); + break; + } + } + if (groupNum == -1) { + // Group not found. Create new group. + groupNum = int(m_groupNumToLockHash.size()); + m_groupNumToLockHash.emplace_back(lockHash); + m_groupNumToInputIndices.emplace_back(); + m_groupNumToWitnesses.emplace_back(); + } + m_groupNumToInputIndices[groupNum].emplace_back(index); + m_groupNumToWitnesses[groupNum].emplace_back(Data(), cell.inputType, cell.outputType); + serializedWitnesses.emplace_back(); + } +} + +Common::Proto::SigningError Transaction::signGroups(const std::vector& privateKeys) { + const Data txHash = hash(); + for (size_t groupNum = 0; groupNum < m_groupNumToLockHash.size(); groupNum++) { + auto&& cell = selectedCells[m_groupNumToInputIndices[groupNum][0]]; + const PrivateKey* privateKey = nullptr; + for (auto&& privateKey1 : privateKeys) { + auto publicKey1 = privateKey1.getPublicKey(TWPublicKeyTypeSECP256k1); + auto address = Address(publicKey1, HRP_NERVOS); + auto script = Script(address); + if (script == cell.lock) { + privateKey = &privateKey1; + break; + } + } + if (!privateKey) { + return Common::Proto::Error_missing_private_key; + } + auto result = signWitnesses(*privateKey, txHash, m_groupNumToWitnesses[groupNum]); + if (result != Common::Proto::OK) { + return result; + } + m_groupNumToWitnesses[groupNum][0].encode(serializedWitnesses[m_groupNumToInputIndices[groupNum][0]]); + } + return Common::Proto::OK; +} + +Common::Proto::SigningError Transaction::signWitnesses(const PrivateKey& privateKey, + const Data& txHash, Witnesses& witnesses) { + Data message; + message.insert(message.end(), txHash.begin(), txHash.end()); + + witnesses[0].lock = Data(Constants::gBlankWitnessBytes, 0); + + for (auto&& witness : witnesses) { + Data serializedWitness; + witness.encode(serializedWitness); + encode64LE(serializedWitness.size(), message); + message.insert(message.end(), serializedWitness.begin(), serializedWitness.end()); + } + + auto messageHash = Hash::blake2b(message, 32, Constants::gHashPersonalization); + auto signature = privateKey.sign(messageHash, TWCurveSECP256k1); + if (signature.empty()) { + // Error: Failed to sign + return Common::Proto::Error_signing; + } + witnesses[0].lock = signature; + + return Common::Proto::OK; +} + +} // namespace TW::Nervos diff --git a/src/Nervos/Transaction.h b/src/Nervos/Transaction.h new file mode 100644 index 00000000000..9f6f6115fb9 --- /dev/null +++ b/src/Nervos/Transaction.h @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Address.h" +#include "Cell.h" +#include "CellDep.h" +#include "CellInput.h" +#include "CellOutput.h" +#include "HeaderDep.h" +#include "Script.h" +#include "TransactionPlan.h" +#include "Witness.h" + +#include "../Coin.h" +#include "../CoinEntry.h" +#include "Data.h" +#include "../Hash.h" +#include "../KeyPair.h" +#include "../PrivateKey.h" +#include "../PublicKey.h" +#include "../Result.h" + +#include + +namespace TW::Nervos { + +class Transaction { +public: + /// Transaction data format version (note, this is signed) + int32_t version = 0; + + // List of cell deps + CellDeps cellDeps; + + // List of header deps + HeaderDeps headerDeps; + + // List of cell inputs + CellInputs inputs; + + // List of cell outputs + CellOutputs outputs; + + // List of outputs data + std::vector outputsData; + + // List of serialized witnesses + std::vector serializedWitnesses; + + // List of cells selected for this transaction + Cells selectedCells; + + Transaction() = default; + + Data hash() const; + nlohmann::json json() const; + void build(const TransactionPlan& txPlan); + Common::Proto::SigningError sign(const std::vector& privateKeys); + +private: + std::vector m_groupNumToLockHash; + std::vector> m_groupNumToInputIndices; + std::vector m_groupNumToWitnesses; + + void formGroups(); + Common::Proto::SigningError signGroups(const std::vector& privateKeys); + Common::Proto::SigningError signWitnesses(const PrivateKey& privateKey, const Data& txHash, + Witnesses& witnesses); +}; + +} // namespace TW::Nervos diff --git a/src/Nervos/TransactionPlan.cpp b/src/Nervos/TransactionPlan.cpp new file mode 100644 index 00000000000..0feb4663a32 --- /dev/null +++ b/src/Nervos/TransactionPlan.cpp @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TransactionPlan.h" +#include "Constants.h" +#include "Serialization.h" +#include "Witness.h" +#include "../BinaryCoding.h" +#include "../HexCoding.h" + +#include + +#include +#include +#include + +using namespace TW; +namespace TW::Nervos { + +void TransactionPlan::plan(const Proto::SigningInput& signingInput) { + error = Common::Proto::OK; + + m_byteFee = signingInput.byte_fee(); + + if (signingInput.cell_size() == 0) { + error = Common::Proto::Error_missing_input_utxos; + return; + } + for (auto&& cell : signingInput.cell()) { + m_availableCells.emplace_back(cell); + } + + switch (signingInput.operation_oneof_case()) { + case Proto::SigningInput::kNativeTransfer: { + planNativeTransfer(signingInput); + break; + } + case Proto::SigningInput::kSudtTransfer: { + planSudtTransfer(signingInput); + break; + } + case Proto::SigningInput::kDaoDeposit: { + planDaoDeposit(signingInput); + break; + } + case Proto::SigningInput::kDaoWithdrawPhase1: { + planDaoWithdrawPhase1(signingInput); + break; + } + case Proto::SigningInput::kDaoWithdrawPhase2: { + planDaoWithdrawPhase2(signingInput); + break; + } + default: { + error = Common::Proto::Error_invalid_params; + } + } +} + +void TransactionPlan::planNativeTransfer(const Proto::SigningInput& signingInput) { + auto useMaxAmount = signingInput.native_transfer().use_max_amount(); + auto amount = signingInput.native_transfer().amount(); + if ((amount == 0) && !useMaxAmount) { + error = Common::Proto::Error_zero_amount_requested; + return; + } + + cellDeps.emplace_back(Constants::gSecp256k1CellDep); + + outputs.emplace_back(amount, Script(Address(signingInput.native_transfer().to_address())), + Script()); + outputsData.emplace_back(); + + if (useMaxAmount) { + selectMaximumCapacity(); + } else { + auto changeAddress = Address(signingInput.native_transfer().change_address()); + selectRequiredCapacity(changeAddress); + } +} + +void TransactionPlan::planSudtTransfer(const Proto::SigningInput& signingInput) { + auto useMaxAmount = signingInput.sudt_transfer().use_max_amount(); + uint256_t amount = uint256_t(signingInput.sudt_transfer().amount()); + if ((amount == 0) && !useMaxAmount) { + error = Common::Proto::Error_zero_amount_requested; + return; + } + + cellDeps.emplace_back(Constants::gSecp256k1CellDep); + cellDeps.emplace_back(Constants::gSUDTCellDep); + + outputs.emplace_back(Constants::gMinCellCapacityForSUDT, + Script(Address(signingInput.sudt_transfer().to_address())), + Script(Constants::gSUDTCodeHash, HashType::Type1, + data(signingInput.sudt_transfer().sudt_address()))); + outputsData.emplace_back(); + + auto changeAddress = Address(signingInput.sudt_transfer().change_address()); + selectSudtTokens(useMaxAmount, amount, changeAddress); + selectRequiredCapacity(changeAddress); +} + +void TransactionPlan::planDaoDeposit(const Proto::SigningInput& signingInput) { + auto amount = signingInput.dao_deposit().amount(); + + cellDeps.emplace_back(Constants::gSecp256k1CellDep); + cellDeps.emplace_back(Constants::gDAOCellDep); + + outputs.emplace_back(amount, Script(Address(signingInput.dao_deposit().to_address())), + Script(Constants::gDAOCodeHash, HashType::Type1, Data())); + outputsData.emplace_back(); + encode64LE(0, outputsData[outputsData.size() - 1]); + + auto changeAddress = Address(signingInput.dao_deposit().change_address()); + selectRequiredCapacity(changeAddress); +} + +void TransactionPlan::planDaoWithdrawPhase1(const Proto::SigningInput& signingInput) { + cellDeps.emplace_back(Constants::gSecp256k1CellDep); + cellDeps.emplace_back(Constants::gDAOCellDep); + + auto depositCell = Cell(signingInput.dao_withdraw_phase1().deposit_cell()); + selectedCells.emplace_back(depositCell); + m_availableCells.erase(std::remove_if( + m_availableCells.begin(), m_availableCells.end(), + [&depositCell](const Cell& cell) { return cell.outPoint == depositCell.outPoint; })); + + headerDeps.emplace_back(depositCell.blockHash); + + outputs.emplace_back(depositCell.capacity, Script(depositCell.lock), Script(depositCell.type)); + outputsData.emplace_back(); + encode64LE(depositCell.blockNumber, outputsData[outputsData.size() - 1]); + + auto changeAddress = Address(signingInput.dao_withdraw_phase1().change_address()); + selectRequiredCapacity(changeAddress); +} + +void TransactionPlan::planDaoWithdrawPhase2(const Proto::SigningInput& signingInput) { + cellDeps.emplace_back(Constants::gSecp256k1CellDep); + cellDeps.emplace_back(Constants::gDAOCellDep); + + auto depositCell = Cell(signingInput.dao_withdraw_phase2().deposit_cell()); + auto withdrawingCell = Cell(signingInput.dao_withdraw_phase2().withdrawing_cell()); + selectedCells.emplace_back(withdrawingCell); + encode64LE(0, selectedCells[selectedCells.size() - 1].inputType); + + headerDeps.emplace_back(depositCell.blockHash); + headerDeps.emplace_back(withdrawingCell.blockHash); + + outputs.emplace_back(signingInput.dao_withdraw_phase2().amount(), Script(withdrawingCell.lock), + Script()); + outputsData.emplace_back(); + + outputs[0].capacity -= calculateFee(); +} + +void TransactionPlan::selectMaximumCapacity() { + uint64_t selectedCapacity = + std::accumulate(m_availableCells.begin(), m_availableCells.end(), uint64_t(0), + [&](const uint64_t total, const Cell& cell) { + if (cell.type.empty()) { + selectedCells.emplace_back(cell); + return total + cell.capacity; + } else { + return total; + } + }); + uint64_t fee = calculateFee(); + outputs[0].capacity = selectedCapacity - fee; +} + +void TransactionPlan::selectRequiredCapacity(const Address& changeAddress) { + uint64_t requiredCapacity = getRequiredCapacity(); + uint64_t fee = calculateFee(); + uint64_t feeForChangeOutput = sizeOfSingleOutput(changeAddress) * m_byteFee; + uint64_t selectedCapacity = getSelectedCapacity(); + uint64_t requiredCapacityPlusFees = requiredCapacity + fee + feeForChangeOutput; + if (selectedCapacity >= requiredCapacityPlusFees + Constants::gMinCellCapacityForNativeToken) { + outputs.emplace_back(selectedCapacity - requiredCapacityPlusFees, Script(changeAddress), + Script()); + outputsData.emplace_back(); + return; + } + sortAccordingToCapacity(); + bool gotEnough = false; + for (auto&& cell : m_availableCells) { + if (!cell.type.empty()) { + continue; + } + selectedCells.emplace_back(cell); + selectedCapacity += cell.capacity; + fee += sizeOfSingleInputAndWitness(cell.inputType, cell.outputType) * m_byteFee; + if (selectedCapacity >= requiredCapacity + fee) { + gotEnough = true; + uint64_t remainingCapacity = selectedCapacity - requiredCapacity - fee; + if (remainingCapacity >= + feeForChangeOutput + Constants::gMinCellCapacityForNativeToken) { + // If change is enough, add it to the change address + outputs.emplace_back(remainingCapacity - feeForChangeOutput, Script(changeAddress), + Script()); + outputsData.emplace_back(); + } else { + // If change is not enough, add it to the destination address + outputs[outputs.size() - 1].capacity += remainingCapacity; + } + break; + } + } + if (!gotEnough) { + error = Common::Proto::Error_not_enough_utxos; + } +} + +void TransactionPlan::selectSudtTokens(const bool useMaxAmount, const uint256_t amount, + const Address& changeAddress) { + uint256_t selectedSudtAmount = 0; + sortAccordingToTypeAndData(outputs[0].type); + bool gotEnough = false; + auto cell = m_availableCells.begin(); + while (cell != m_availableCells.end()) { + if (cell->type != outputs[0].type) { + cell++; + continue; + } + selectedCells.emplace_back(*cell); + selectedSudtAmount += Serialization::decodeUint256(cell->data); + cell = m_availableCells.erase(cell); + if (useMaxAmount) { + // Transfer maximum available tokens + gotEnough = true; + } else if (selectedSudtAmount >= amount) { + // Transfer exact number of tokens + gotEnough = true; + uint256_t changeValue = selectedSudtAmount - amount; + if (changeValue > 0) { + outputs.emplace_back(Constants::gMinCellCapacityForSUDT, Script(changeAddress), + Script(outputs[0].type)); + outputsData.emplace_back(Serialization::encodeUint256(changeValue, 16)); + } + break; + } + } + if (!gotEnough) { + error = Common::Proto::Error_not_enough_utxos; + return; + } + outputsData[0] = Serialization::encodeUint256(useMaxAmount ? selectedSudtAmount : amount, 16); +} + +uint64_t TransactionPlan::sizeWithoutInputs() { + uint64_t size = Constants::gTransactionBaseSize; + size += Constants::gCellDepSize * cellDeps.size(); + size += Constants::gHeaderDepSize * headerDeps.size(); + size += std::accumulate(outputs.begin(), outputs.end(), 0, + [](const uint64_t size, const CellOutput& output) { + Data outputData1; + output.encode(outputData1); + return size + outputData1.size() + Constants::gUint32Size; + }); + size += std::accumulate( + outputsData.begin(), outputsData.end(), 0, [](const uint64_t size, const Data& outputData) { + return size + Constants::gUint32Size + outputData.size() + Constants::gUint32Size; + }); + return size; +} + +uint64_t TransactionPlan::sizeOfSingleInputAndWitness(const Data& inputType, + const Data& outputType) { + uint64_t size = Constants::gSingleInputAndWitnessBaseSize; + auto witness = Witness(Data(Constants::gBlankWitnessBytes, 0), inputType, outputType); + Data witnessData; + witness.encode(witnessData); + size += Constants::gUint32Size + witnessData.size() + Constants::gUint32Size; + return size; +} + +uint64_t TransactionPlan::sizeOfSingleOutput(const Address& address) { + uint64_t size = 0; + auto output = CellOutput(0, Script(address), Script()); + Data outputData1; + output.encode(outputData1); + size += outputData1.size() + Constants::gUint32Size; // output + size += Constants::gUint32Size + 0 + Constants::gUint32Size; // blank outputData + return size; +} + +uint64_t TransactionPlan::calculateFee() { + uint64_t size = sizeWithoutInputs(); + size += std::accumulate(selectedCells.begin(), selectedCells.end(), uint64_t(0), + [&](const uint64_t total, const Cell& cell) { + return total + + sizeOfSingleInputAndWitness(cell.inputType, cell.outputType); + }); + return size * m_byteFee; +} + +void TransactionPlan::sortAccordingToCapacity() { + std::sort(m_availableCells.begin(), m_availableCells.end(), + [](const Cell& lhs, const Cell& rhs) { return lhs.capacity < rhs.capacity; }); +} + +void TransactionPlan::sortAccordingToTypeAndData(const Script& type) { + std::sort( + m_availableCells.begin(), m_availableCells.end(), [&](const Cell& lhs, const Cell& rhs) { + uint256_t lhsAmount = (lhs.type == type) ? Serialization::decodeUint256(lhs.data) : 0; + uint256_t rhsAmount = (rhs.type == type) ? Serialization::decodeUint256(rhs.data) : 0; + return lhsAmount < rhsAmount; + }); +} + +uint64_t TransactionPlan::getRequiredCapacity() { + return std::accumulate(outputs.begin(), outputs.end(), uint64_t(0), + [](const uint64_t total, const CellOutput& cellOutput) { + return total + cellOutput.capacity; + }); +} + +uint64_t TransactionPlan::getSelectedCapacity() { + return std::accumulate( + selectedCells.begin(), selectedCells.end(), uint64_t(0), + [](const uint64_t total, const Cell& cell) { return total + cell.capacity; }); +} + +} // namespace TW::Nervos diff --git a/src/Nervos/TransactionPlan.h b/src/Nervos/TransactionPlan.h new file mode 100644 index 00000000000..7812f2ee124 --- /dev/null +++ b/src/Nervos/TransactionPlan.h @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Address.h" +#include "Cell.h" +#include "CellDep.h" +#include "CellInput.h" +#include "CellOutput.h" +#include "HeaderDep.h" +#include "Script.h" + +#include "../Coin.h" +#include "../CoinEntry.h" +#include "Data.h" +#include "../Hash.h" +#include "../KeyPair.h" +#include "../PrivateKey.h" +#include "../PublicKey.h" +#include "../Result.h" +#include "../proto/Nervos.pb.h" + +#include + +namespace TW::Nervos { + +class TransactionPlan { +public: + // List of cell deps + CellDeps cellDeps; + + // List of header deps + HeaderDeps headerDeps; + + // List of cells selected for this transaction + Cells selectedCells; + + // List of cell outputs + CellOutputs outputs; + + // List of outputs data + std::vector outputsData; + + // Error during transaction planning + Common::Proto::SigningError error; + + TransactionPlan() = default; + + /// Initializes a transaction from a Protobuf transaction. + TransactionPlan(const Proto::TransactionPlan& txPlan) { + for (auto&& cellDep : txPlan.cell_deps()) { + cellDeps.emplace_back(cellDep); + } + for (auto&& headerDep : txPlan.header_deps()) { + Data data; + data.insert(data.end(), headerDep.begin(), headerDep.end()); + headerDeps.emplace_back(data); + } + for (auto&& cell : txPlan.selected_cells()) { + selectedCells.emplace_back(cell); + } + for (auto&& output : txPlan.outputs()) { + outputs.emplace_back(output); + } + for (auto&& outputData : txPlan.outputs_data()) { + Data data; + data.insert(data.end(), outputData.begin(), outputData.end()); + outputsData.emplace_back(data); + } + error = txPlan.error(); + } + + /// Converts to Protobuf model + Proto::TransactionPlan proto() const { + auto txPlan = Proto::TransactionPlan(); + for (auto&& cellDep : cellDeps) { + *txPlan.add_cell_deps() = cellDep.proto(); + } + for (auto&& headerDep : headerDeps) { + txPlan.add_header_deps(headerDep.data(), headerDep.size()); + } + for (auto&& cell : selectedCells) { + *txPlan.add_selected_cells() = cell.proto(); + } + for (auto&& output : outputs) { + *txPlan.add_outputs() = output.proto(); + } + for (auto&& outputData : outputsData) { + txPlan.add_outputs_data(outputData.data(), outputData.size()); + } + return txPlan; + } + + void plan(const Proto::SigningInput& signingInput); + +private: + uint64_t m_byteFee; + Cells m_availableCells; + + void planNativeTransfer(const Proto::SigningInput& signingInput); + void planSudtTransfer(const Proto::SigningInput& signingInput); + void planDaoDeposit(const Proto::SigningInput& signingInput); + void planDaoWithdrawPhase1(const Proto::SigningInput& signingInput); + void planDaoWithdrawPhase2(const Proto::SigningInput& signingInput); + void selectMaximumCapacity(); + void selectRequiredCapacity(const Address& changeAddress); + void selectSudtTokens(const bool useMaxAmount, const uint256_t amount, + const Address& changeAddress); + uint64_t sizeWithoutInputs(); + uint64_t sizeOfSingleInputAndWitness(const Data& inputType, const Data& outputType); + uint64_t sizeOfSingleOutput(const Address& address); + uint64_t calculateFee(); + void sortAccordingToCapacity(); + void sortAccordingToTypeAndData(const Script& type); + uint64_t getRequiredCapacity(); + uint64_t getSelectedCapacity(); +}; + +} // namespace TW::Nervos diff --git a/src/Nervos/Witness.cpp b/src/Nervos/Witness.cpp new file mode 100644 index 00000000000..aa66b4aeed1 --- /dev/null +++ b/src/Nervos/Witness.cpp @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Witness.h" +#include "Serialization.h" + +namespace TW::Nervos { + +void Witness::encode(Data& data) const { + if ((lock.empty()) && (inputType.empty()) && (outputType.empty())) { + return; + } + std::vector dataArray; + dataArray.reserve(3); + for (auto&& data1 : std::vector({lock, inputType, outputType})) { + Data data2; + if (!data1.empty()) { + encode32LE(uint32_t(data1.size()), data2); + data2.insert(data2.end(), data1.begin(), data1.end()); + } + dataArray.emplace_back(data2); + } + Serialization::encodeDataArray(dataArray, data); +} + +} // namespace TW::Nervos diff --git a/src/Nervos/Witness.h b/src/Nervos/Witness.h new file mode 100644 index 00000000000..0e67da4a72a --- /dev/null +++ b/src/Nervos/Witness.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" + +#include + +namespace TW::Nervos { + +struct Witness { + Data lock; + Data inputType; + Data outputType; + + Witness() = default; + + /// Initializes a witness with lock, inputType and outputType. + Witness(const Data& lock, const Data& inputType, const Data& outputType) + : lock(lock), inputType(inputType), outputType(outputType) {} + + /// Encodes the witness into the provided buffer. + void encode(Data& data) const; +}; + +/// A list of Witness's +using Witnesses = std::vector; + +} // namespace TW::Nervos diff --git a/src/Nimiq/Address.cpp b/src/Nimiq/Address.cpp index 458507aa506..ae58500cfc0 100644 --- a/src/Nimiq/Address.cpp +++ b/src/Nimiq/Address.cpp @@ -1,22 +1,16 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" #include "../Base32.h" -#include "../Hash.h" -#include "../HexCoding.h" #include #include -#include -#include -using namespace TW::Nimiq; +namespace TW::Nimiq { static const char* BASE32_ALPHABET_NIMIQ = "0123456789ABCDEFGHJKLMNPQRSTUVXY"; @@ -150,3 +144,5 @@ static inline int check_add(int check, int num) { ; return (check + num) % 97; } + +} // namespace TW::Nimiq diff --git a/src/Nimiq/Address.h b/src/Nimiq/Address.h index 66acb288bad..6b94b61c548 100644 --- a/src/Nimiq/Address.h +++ b/src/Nimiq/Address.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -22,7 +20,7 @@ class Address { /// Address data consisting of a prefix byte followed by the public key /// hash. - std::array bytes; + std::array bytes{}; /// Determines whether a collection of bytes makes a valid address. static bool isValid(const std::vector& data) { return data.size() == size; } diff --git a/src/Nimiq/Entry.cpp b/src/Nimiq/Entry.cpp index 9e795074477..413d8ba854c 100644 --- a/src/Nimiq/Entry.cpp +++ b/src/Nimiq/Entry.cpp @@ -1,27 +1,27 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" #include "Signer.h" -using namespace TW::Nimiq; -using namespace std; +namespace TW::Nimiq { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Nimiq + diff --git a/src/Nimiq/Entry.h b/src/Nimiq/Entry.h index 60e184573d1..50256cd871d 100644 --- a/src/Nimiq/Entry.h +++ b/src/Nimiq/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,11 +10,11 @@ namespace TW::Nimiq { /// Entry point for implementation of Nimiq coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Nimiq diff --git a/src/Nimiq/Signer.cpp b/src/Nimiq/Signer.cpp index 2d3201e2892..ed08cefdff3 100644 --- a/src/Nimiq/Signer.cpp +++ b/src/Nimiq/Signer.cpp @@ -1,16 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include #include -using namespace TW; -using namespace TW::Nimiq; +namespace TW::Nimiq { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); @@ -39,3 +36,5 @@ void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) const auto signature = privateKey.sign(preImage, TWCurveED25519); std::copy(signature.begin(), signature.end(), transaction.signature.begin()); } + +} // namespace TW::Nimiq diff --git a/src/Nimiq/Signer.h b/src/Nimiq/Signer.h index 52baa9f03f0..1e485e45773 100644 --- a/src/Nimiq/Signer.h +++ b/src/Nimiq/Signer.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/Nimiq.pb.h" diff --git a/src/Nimiq/Transaction.cpp b/src/Nimiq/Transaction.cpp index 4498166bad3..e5ba42006fe 100644 --- a/src/Nimiq/Transaction.cpp +++ b/src/Nimiq/Transaction.cpp @@ -1,18 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Transaction.h" #include "Signer.h" #include "../BinaryCoding.h" -#include "../HexCoding.h" -#include "../PublicKey.h" -using namespace TW; -using namespace TW::Nimiq; +namespace TW::Nimiq { const uint8_t NETWORK_ID = 42; const uint8_t EMPTY_FLAGS = 0; @@ -50,3 +45,5 @@ std::vector Transaction::getPreImage() const { return data; } + +} // namespace TW::Nimiq diff --git a/src/Nimiq/Transaction.h b/src/Nimiq/Transaction.h index ad8c192756d..4615d440537 100644 --- a/src/Nimiq/Transaction.h +++ b/src/Nimiq/Transaction.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Numeric.h b/src/Numeric.h new file mode 100644 index 00000000000..d7db412fda7 --- /dev/null +++ b/src/Numeric.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include + +namespace TW { + +template < + typename T, + typename = std::enable_if_t> +> +bool checkAddUnsignedOverflow(T x, T y) { + return x > std::numeric_limits::max() - y; +} + +template < + typename T, + typename = std::enable_if_t> +> +bool checkMulUnsignedOverflow(T x, T y) { + if (x == 0 || y == 0) { + return false; + } + return x > std::numeric_limits::max() / y; +} + +} // namespace TW diff --git a/src/NumericLiteral.h b/src/NumericLiteral.h index 4674dda8d88..0daa1f13242 100644 --- a/src/NumericLiteral.h +++ b/src/NumericLiteral.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Oasis/Address.cpp b/src/Oasis/Address.cpp index af150206a80..0b394ddf2c1 100644 --- a/src/Oasis/Address.cpp +++ b/src/Oasis/Address.cpp @@ -1,27 +1,27 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" #include #define COIN_ADDRESS_CONTEXT "oasis-core/address: staking" -#define COIN_ADDRESS_VERSION 0 +#define COIN_ADDRESS_VERSION 0 -using namespace TW::Oasis; +namespace TW::Oasis { const std::string Address::hrp = HRP_OASIS; -Address::Address(const Data& keyHash) : Bech32Address(hrp, keyHash) { +Address::Address(const Data& keyHash) + : Bech32Address(hrp, keyHash) { if (getKeyHash().size() != Address::size) { throw std::invalid_argument("invalid address data"); } } -Address::Address(const TW::PublicKey& publicKey) : Bech32Address(hrp){ +Address::Address(const TW::PublicKey& publicKey) + : Bech32Address(hrp) { if (publicKey.type != TWPublicKeyTypeED25519) { throw std::invalid_argument("address may only be an extended ED25519 public key"); } @@ -39,8 +39,9 @@ Address::Address(const TW::PublicKey& publicKey) : Bech32Address(hrp){ setKey(key); } -Address::Address(const std::string& addr) : Bech32Address(addr) { - if(!isValid(addr)) { +Address::Address(const std::string& addr) + : Bech32Address(addr) { + if (!isValid(addr)) { throw std::invalid_argument("invalid address string"); } } @@ -49,3 +50,4 @@ bool Address::isValid(const std::string& addr) { return Bech32Address::isValid(addr, hrp); } +} // namespace TW::Oasis diff --git a/src/Oasis/Address.h b/src/Oasis/Address.h index d9784cd63f1..1e500aaffda 100644 --- a/src/Oasis/Address.h +++ b/src/Oasis/Address.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include "../Bech32Address.h" diff --git a/src/Oasis/Entry.cpp b/src/Oasis/Entry.cpp index 44c756948a3..48efdde0ae4 100644 --- a/src/Oasis/Entry.cpp +++ b/src/Oasis/Entry.cpp @@ -1,27 +1,50 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" #include "Signer.h" -using namespace TW::Oasis; +#include "../proto/TransactionCompiler.pb.h" + using namespace std; +namespace TW::Oasis { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + const auto signer = Signer(input); + auto preImage = signer.signaturePreimage(); + auto preImageHash = Hash::sha512_256(preImage); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + const auto signer = Signer(input); + output = signer.compile(signature, publicKey); + }); +} +} // namespace TW::Oasis diff --git a/src/Oasis/Entry.h b/src/Oasis/Entry.h index d2d765d7fba..497f4df9593 100644 --- a/src/Oasis/Entry.h +++ b/src/Oasis/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,11 +10,16 @@ namespace TW::Oasis { /// Entry point for implementation of Oasis coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + + void compile(TWCoinType coin, const Data& txInputData, + const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Oasis diff --git a/src/Oasis/Signer.cpp b/src/Oasis/Signer.cpp index 7babc53ee84..ea3405382a8 100644 --- a/src/Oasis/Signer.cpp +++ b/src/Oasis/Signer.cpp @@ -1,19 +1,19 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include -#include "Signer.h" #include "Address.h" +#include "Signer.h" #define TRANSFER_METHOD "staking.Transfer" +#define ESCROW_METHOD "staking.AddEscrow" +#define RECLAIM_ESCROW_METHOD "staking.ReclaimEscrow" using namespace TW; -using namespace TW::Oasis; +namespace TW::Oasis { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto output = Proto::SigningOutput(); @@ -24,6 +24,96 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { } Data Signer::build() const { + auto privateKey = PrivateKey(input.private_key()); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + + if(input.has_transfer()) { + auto tx = buildTransfer(); + auto signature = signTransaction(tx); + auto encoded = tx.serialize(signature, publicKey); + return encoded; + } + + if(input.has_escrow()) { + auto tx = buildEscrow(); + auto signature = signTransaction(tx); + auto encoded = tx.serialize(signature, publicKey); + return encoded; + } + + if(input.has_reclaimescrow()) { + auto tx = buildReclaimEscrow(); + auto signature = signTransaction(tx); + auto encoded = tx.serialize(signature, publicKey); + return encoded; + } + + throw std::invalid_argument("Invalid message"); +} + +template +Data Signer::signTransaction(T& tx) const { + auto privateKey = PrivateKey(input.private_key()); + + // The use of this context thing is explained here --> + // https://docs.oasis.dev/oasis-core/common-functionality/crypto#domain-separation + auto encodedMessage = tx.encodeMessage().encoded(); + Data dataToHash(tx.context.begin(), tx.context.end()); + dataToHash.insert(dataToHash.end(), encodedMessage.begin(), encodedMessage.end()); + auto hash = Hash::sha512_256(dataToHash); + + auto signature = privateKey.sign(hash, TWCurveED25519); + return Data(signature.begin(), signature.end()); +} + +Data Signer::signaturePreimage() const { + if(input.has_transfer()) { + auto tx = buildTransfer(); + return tx.signaturePreimage(); + } + + if(input.has_escrow()) { + auto tx = buildEscrow(); + return tx.signaturePreimage(); + } + + if(input.has_reclaimescrow()) { + auto tx = buildReclaimEscrow(); + return tx.signaturePreimage(); + } + + throw std::invalid_argument("Invalid message"); +} + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey) const { + Proto::SigningOutput output; + + if(input.has_transfer()) { + auto tx = buildTransfer(); + auto encoded = tx.serialize(signature, publicKey); + output.set_encoded(encoded.data(), encoded.size()); + return output; + } + + if(input.has_escrow()) { + auto tx = buildEscrow(); + auto encoded = tx.serialize(signature, publicKey); + output.set_encoded(encoded.data(), encoded.size()); + return output; + } + + if(input.has_reclaimescrow()) { + auto tx = buildReclaimEscrow(); + auto encoded = tx.serialize(signature, publicKey); + output.set_encoded(encoded.data(), encoded.size()); + return output; + } + + throw std::invalid_argument("Invalid message"); +} + + +Transaction Signer::buildTransfer() const { // Create empty address var and check if value we want to load is valid Address address(input.transfer().to()); @@ -40,33 +130,68 @@ Data Signer::build() const { gasAmountStream >> gasAmount; Transaction transaction( - /* to */ address, + /* to */ address, /* method */ TRANSFER_METHOD, /* gasPrice */ input.transfer().gas_price(), /* gasAmount */ gasAmount, /* amount */ amount, /* nonce */ input.transfer().nonce(), /* context */ input.transfer().context()); + return transaction; +} +Escrow Signer::buildEscrow() const { + // Create empty address var and check if value we want to load is valid + Address address(input.escrow().account()); - auto privateKey = PrivateKey(input.private_key()); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + // Load value on that address var we create before + Address::decode(input.escrow().account(), address); + + // Convert values from string to uint256 + std::istringstream amountStream(input.escrow().amount()); + uint256_t amount; + amountStream >> amount; - auto signature = sign(transaction); - auto encoded = transaction.serialize(signature, publicKey); + std::istringstream gasAmountStream(input.escrow().gas_amount()); + uint256_t gasAmount; + gasAmountStream >> gasAmount; - return encoded; + Escrow escrow( + /* method */ ESCROW_METHOD, + /* gasPrice */ input.escrow().gas_price(), + /* gasAmount */ gasAmount, + /* nonce */ input.escrow().nonce(), + /* account */ address, + /* amount */ amount, + /* context */ input.escrow().context()); + return escrow; } -Data Signer::sign(Transaction& tx) const { - auto privateKey = PrivateKey(input.private_key()); +ReclaimEscrow Signer::buildReclaimEscrow() const { + // Create empty address var and check if value we want to load is valid + Address address(input.reclaimescrow().account()); - // The use of this context thing is explained here --> https://docs.oasis.dev/oasis-core/common-functionality/crypto#domain-separation - auto encodedMessage = tx.encodeMessage().encoded(); - Data dataToHash(tx.context.begin(), tx.context.end()); - dataToHash.insert(dataToHash.end(), encodedMessage.begin(), encodedMessage.end()); - auto hash = Hash::sha512_256(dataToHash); + // Load value on that address var we create before + Address::decode(input.reclaimescrow().account(), address); - auto signature = privateKey.sign(hash, TWCurveED25519); - return Data(signature.begin(), signature.end()); + // Convert values from string to uint256 + std::istringstream sharesStream(input.reclaimescrow().shares()); + uint256_t shares; + sharesStream >> shares; + + std::istringstream gasAmountStream(input.reclaimescrow().gas_amount()); + uint256_t gasAmount; + gasAmountStream >> gasAmount; + + ReclaimEscrow reclaimEscrow( + /* method */ RECLAIM_ESCROW_METHOD, + /* gasPrice */ input.reclaimescrow().gas_price(), + /* gasAmount */ gasAmount, + /* nonce */ input.reclaimescrow().nonce(), + /* account */ address, + /* shares */ shares, + /* context */ input.reclaimescrow().context()); + return reclaimEscrow; } + +} // namespace TW::Oasis diff --git a/src/Oasis/Signer.h b/src/Oasis/Signer.h index 76df838c029..eae5d2b772d 100644 --- a/src/Oasis/Signer.h +++ b/src/Oasis/Signer.h @@ -1,15 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/Oasis.pb.h" #include "Transaction.h" @@ -18,6 +16,11 @@ namespace TW::Oasis { /// Helper class that performs Oasis transaction signing. class Signer { +private: + Transaction buildTransfer() const; + Escrow buildEscrow() const; + ReclaimEscrow buildReclaimEscrow() const; + public: Proto::SigningInput input; @@ -34,13 +37,16 @@ class Signer { /// /// \returns the transaction signature or an empty vector if there is an /// error. - Data sign(Transaction& tx) const; + template + Data signTransaction(T& tx) const; /// Builds a signed transaction. /// /// \returns the signed transaction data or an empty vector if there is an /// error. Data build() const; + Data signaturePreimage() const; + Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) const; }; } // namespace TW::Oasis diff --git a/src/Oasis/Transaction.cpp b/src/Oasis/Transaction.cpp index c2f1cc88ffc..262ec08ee82 100644 --- a/src/Oasis/Transaction.cpp +++ b/src/Oasis/Transaction.cpp @@ -1,13 +1,14 @@ -// Copyright © 2017-2020 Trust. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Transaction.h" using namespace TW; -using namespace TW::Oasis; + +namespace TW::Oasis { + +// clang-format off // encodeVaruint encodes a 256-bit number into a big endian encoding, omitting leading zeros. static Data encodeVaruint(const uint256_t& value) { @@ -27,6 +28,24 @@ static Data encodeVaruint(const uint256_t& value) { return small; } +static Data serializeTransaction(const Data& signature, const PublicKey& publicKey, const Data& body) { + auto signedMessage = Cbor::Encode::map({ + { Cbor::Encode::string("untrusted_raw_value"), Cbor::Encode::bytes(body) }, + { Cbor::Encode::string("signature"), Cbor::Encode::map({ + { Cbor::Encode::string("public_key"), Cbor::Encode::bytes(publicKey.bytes) }, + { Cbor::Encode::string("signature"), Cbor::Encode::bytes(signature) } + }) + } + }); + return signedMessage.encoded(); +} + +static Data buildSignaturePreimage(std::string context, const Data& encodedMessage) { + Data dataToHash(context.begin(), context.end()); + dataToHash.insert(dataToHash.end(), encodedMessage.begin(), encodedMessage.end()); + return dataToHash; +} + Cbor::Encode Transaction::encodeMessage() const { return Cbor::Encode::map({ @@ -45,14 +64,65 @@ Cbor::Encode Transaction::encodeMessage() const { }); } -Data Transaction::serialize(Data& signature, PublicKey& publicKey) const { - auto signedMessage = Cbor::Encode::map({ - { Cbor::Encode::string("untrusted_raw_value"), Cbor::Encode::bytes(encodeMessage().encoded()) }, - { Cbor::Encode::string("signature"), Cbor::Encode::map({ - { Cbor::Encode::string("public_key"), Cbor::Encode::bytes(publicKey.bytes) }, - { Cbor::Encode::string("signature"), Cbor::Encode::bytes(signature) } - }) - } - }); - return signedMessage.encoded(); +Data Transaction::serialize(const Data& signature, const PublicKey& publicKey) const { + return serializeTransaction(signature, publicKey, encodeMessage().encoded()); +} + +Data Transaction::signaturePreimage() const { + return buildSignaturePreimage(context, encodeMessage().encoded()); +} + +Cbor::Encode Escrow::encodeMessage() const { + + return Cbor::Encode::map({ + { Cbor::Encode::string("nonce"), Cbor::Encode::uint(nonce) }, + { Cbor::Encode::string("method"), Cbor::Encode::string(method) }, + { Cbor::Encode::string("fee"), Cbor::Encode::map({ + { Cbor::Encode::string("gas"), Cbor::Encode::uint(gasPrice) }, + { Cbor::Encode::string("amount"), Cbor::Encode::bytes(encodeVaruint(gasAmount)) } + }) + }, + { Cbor::Encode::string("body"), Cbor::Encode::map({ + { Cbor::Encode::string("account"), Cbor::Encode::bytes(account.getKeyHash()) }, + { Cbor::Encode::string("amount"), Cbor::Encode::bytes(encodeVaruint(amount)) } + }) + } + }); +} + +Data Escrow::serialize(const Data& signature, const PublicKey& publicKey) const { + return serializeTransaction(signature, publicKey, encodeMessage().encoded()); +} + +Data Escrow::signaturePreimage() const { + return buildSignaturePreimage(context, encodeMessage().encoded()); +} + +Cbor::Encode ReclaimEscrow::encodeMessage() const { + + return Cbor::Encode::map({ + { Cbor::Encode::string("nonce"), Cbor::Encode::uint(nonce) }, + { Cbor::Encode::string("method"), Cbor::Encode::string(method) }, + { Cbor::Encode::string("fee"), Cbor::Encode::map({ + { Cbor::Encode::string("gas"), Cbor::Encode::uint(gasPrice) }, + { Cbor::Encode::string("amount"), Cbor::Encode::bytes(encodeVaruint(gasAmount)) } + }) + }, + { Cbor::Encode::string("body"), Cbor::Encode::map({ + { Cbor::Encode::string("account"), Cbor::Encode::bytes(account.getKeyHash()) }, + { Cbor::Encode::string("shares"), Cbor::Encode::bytes(encodeVaruint(shares)) } + }) + } + }); +} + +Data ReclaimEscrow::serialize(const Data& signature, const PublicKey& publicKey) const { + return serializeTransaction(signature, publicKey, encodeMessage().encoded()); } + +Data ReclaimEscrow::signaturePreimage() const { + return buildSignaturePreimage(context, encodeMessage().encoded()); +} +// clang-format on + +} // namespace TW::Oasis diff --git a/src/Oasis/Transaction.h b/src/Oasis/Transaction.h index 2c72e7762c4..0a423b2c7cd 100644 --- a/src/Oasis/Transaction.h +++ b/src/Oasis/Transaction.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -14,6 +12,7 @@ namespace TW::Oasis { +// Transfer transaction class Transaction { public: // Recipient address @@ -46,7 +45,86 @@ class Transaction { Cbor::Encode encodeMessage() const; // serialize returns the CBOR encoding of the SignedMessage. - Data serialize(Data& signature, PublicKey& publicKey) const; + Data serialize(const Data& signature, const PublicKey& publicKey) const; + + // serialize transaction that can be signed + Data signaturePreimage() const; +}; + +// Escrow transaction +class Escrow { + public: + // Method name + std::string method; + // Gas price + uint64_t gasPrice; + // Gas amount + uint256_t gasAmount; + // Transaction nonce + uint64_t nonce; + Address account; + // Transaction amount + uint256_t amount; + // Transaction context + std::string context; + + Escrow(std::string method, uint64_t gasPrice, uint256_t gasAmount, uint64_t nonce, + Address account, uint256_t amount, std::string context) + : method(std::move(method)) + , gasPrice(gasPrice) + , gasAmount(std::move(gasAmount)) + , nonce(nonce) + , account(std::move(account)) + , amount(std::move(amount)) + , context(std::move(context)){} + + public: + // message returns the CBOR encoding of the Message to be signed. + Cbor::Encode encodeMessage() const; + + // serialize returns the CBOR encoding of the SignedMessage. + Data serialize(const Data& signature, const PublicKey& publicKey) const; + + // serialize transaction that can be signed + Data signaturePreimage() const; +}; + +// ReclaimEscrow transaction +class ReclaimEscrow { + public: + // Method name + std::string method; + // Gas price + uint64_t gasPrice; + // Gas amount + uint256_t gasAmount; + // Transaction nonce + uint64_t nonce; + Address account; + // Transaction amount + uint256_t shares; + // Transaction context + std::string context; + + ReclaimEscrow(std::string method, uint64_t gasPrice, uint256_t gasAmount, uint64_t nonce, + Address account, uint256_t shares, std::string context) + : method(std::move(method)) + , gasPrice(gasPrice) + , gasAmount(std::move(gasAmount)) + , nonce(nonce) + , account(std::move(account)) + , shares(std::move(shares)) + , context(std::move(context)){} + + public: + // message returns the CBOR encoding of the Message to be signed. + Cbor::Encode encodeMessage() const; + + // serialize returns the CBOR encoding of the SignedMessage. + Data serialize(const Data& signature, const PublicKey& publicKey) const; + + // serialize transaction that can be signed + Data signaturePreimage() const; }; } // namespace TW::Oasis diff --git a/src/Ontology/Address.cpp b/src/Ontology/Address.cpp index 979d204dc05..6cf098eb111 100644 --- a/src/Ontology/Address.cpp +++ b/src/Ontology/Address.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" #include "OpCode.h" @@ -16,14 +14,15 @@ #include using namespace TW; -using namespace TW::Ontology; + +namespace TW::Ontology { Address::Address(const PublicKey& publicKey) { std::vector builder(publicKey.bytes); builder.insert(builder.begin(), PUSH_BYTE_33); builder.push_back(CHECK_SIG); auto builderData = toScriptHash(builder); - std::copy(builderData.begin(), builderData.end(), data.begin()); + std::copy(builderData.begin(), builderData.end(), _data.begin()); } Address::Address(const std::string& b58Address) { @@ -32,19 +31,19 @@ Address::Address(const std::string& b58Address) { } Data addressWithVersion(size + 1); base58_decode_check(b58Address.c_str(), HASHER_SHA2D, addressWithVersion.data(), size + 1); - std::copy(addressWithVersion.begin() + 1, addressWithVersion.end(), data.begin()); + std::copy(addressWithVersion.begin() + 1, addressWithVersion.end(), _data.begin()); } Address::Address(const std::vector& bytes) { if (bytes.size() != size) { throw std::runtime_error("Invalid bytes data."); } - std::copy(bytes.begin(), bytes.end(), data.begin()); + std::copy(bytes.begin(), bytes.end(), _data.begin()); } Address::Address(uint8_t m, const std::vector& publicKeys) { auto builderData = toScriptHash(ParamsBuilder::fromMultiPubkey(m, publicKeys)); - std::copy(builderData.begin(), builderData.end(), data.begin()); + std::copy(builderData.begin(), builderData.end(), _data.begin()); } Data Address::toScriptHash(const Data& data) { @@ -64,10 +63,12 @@ bool Address::isValid(const std::string& b58Address) noexcept { std::string Address::string() const { std::vector encodeData(size + 1); encodeData[0] = version; - std::copy(data.begin(), data.end(), encodeData.begin() + 1); + std::copy(_data.begin(), _data.end(), encodeData.begin() + 1); size_t b58StrSize = 34; std::string b58Str(b58StrSize, ' '); base58_encode_check(encodeData.data(), (int)encodeData.size(), HASHER_SHA2D, &b58Str[0], (int)b58StrSize + 1); return b58Str; } + +} // namespace TW::Ontology diff --git a/src/Ontology/Address.h b/src/Ontology/Address.h index 7d5b2377b31..463ebb73c91 100644 --- a/src/Ontology/Address.h +++ b/src/Ontology/Address.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -22,7 +20,7 @@ class Address { static const size_t size = 20; static const uint8_t version = 0x17; - std::array data; + std::array _data; /// Initializes an address with a public key. explicit Address(const PublicKey& publicKey); @@ -44,7 +42,7 @@ class Address { }; inline bool operator==(const Address& lhs, const Address& rhs) { - return lhs.data == rhs.data; + return lhs._data == rhs._data; } } // namespace TW::Ontology diff --git a/src/Ontology/Asset.h b/src/Ontology/Asset.h index 123b6738ab7..69c060e128c 100644 --- a/src/Ontology/Asset.h +++ b/src/Ontology/Asset.h @@ -1,16 +1,14 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Address.h" +#include "Data.h" #include "Signer.h" #include "Transaction.h" #include "../BinaryCoding.h" -#include "../Data.h" #include #include @@ -19,18 +17,23 @@ namespace TW::Ontology { class Asset { - protected: +protected: const uint8_t txType = 0xD1; - public: +public: + virtual ~Asset() noexcept = default; virtual Data contractAddress() = 0; virtual Transaction decimals(uint32_t nonce) = 0; - virtual Transaction balanceOf(const Address &address, uint32_t nonce) = 0; + virtual Transaction balanceOf(const Address& address, uint32_t nonce) = 0; - virtual Transaction transfer(const Signer &from, const Address &to, uint64_t amount, - const Signer &payer, uint64_t gasPrice, uint64_t gasLimit, + virtual Transaction transfer(const Signer& from, const Address& to, uint64_t amount, + const Signer& payer, uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce) = 0; + + virtual Transaction unsignedTransfer(const Address& from, const Address& to, uint64_t amount, + const Address& payer, uint64_t gasPrice, uint64_t gasLimit, + uint32_t nonce) = 0; }; } // namespace TW::Ontology diff --git a/src/Ontology/Entry.cpp b/src/Ontology/Entry.cpp index f0129ed1e08..6dace88d25a 100644 --- a/src/Ontology/Entry.cpp +++ b/src/Ontology/Entry.cpp @@ -1,27 +1,73 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" #include "Signer.h" +#include "OntTxBuilder.h" +#include "OngTxBuilder.h" +#include "Oep4TxBuilder.h" +#include "../proto/TransactionCompiler.pb.h" -using namespace TW::Ontology; -using namespace std; +namespace TW::Ontology { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto contract = std::string(input.contract().begin(), input.contract().end()); + Data preImage, preImageHash; + + if (contract == "ONT") { + auto tx = OntTxBuilder::buildTransferTx(input); + preImage = tx.serializeUnsigned(); + preImageHash = tx.txHash(); + } else if (contract == "ONG") { + auto tx = OngTxBuilder::buildTransferTx(input); + preImage = tx.serializeUnsigned(); + preImageHash = tx.txHash(); + } else { + auto tx = Oep4TxBuilder::buildTx(input); + preImage = tx.serializeUnsigned(); + preImageHash = tx.txHash(); + } + + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_signatures_count); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_signatures_count)); + return; + } + + auto signedTx = Signer::encodeTransaction(input, signatures, publicKeys); + output.set_encoded(signedTx.data(), signedTx.size()); + }); +} +} // namespace TW::Ontology diff --git a/src/Ontology/Entry.h b/src/Ontology/Entry.h index e4d9ab17597..9e43327ab02 100644 --- a/src/Ontology/Entry.h +++ b/src/Ontology/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,11 +10,14 @@ namespace TW::Ontology { /// Entry point for implementation of Ontology coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Ontology diff --git a/src/Ontology/Oep4.cpp b/src/Ontology/Oep4.cpp new file mode 100644 index 00000000000..d6388793810 --- /dev/null +++ b/src/Ontology/Oep4.cpp @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Oep4.h" +#include + +namespace TW::Ontology { +Oep4::Oep4(const Address addr) noexcept + : oep4Contract(addr._data.begin(), addr._data.end()) { +} + +Oep4::Oep4(const Data bin) noexcept + : oep4Contract(bin) { +} + +Transaction Oep4::readOnlyMethod(std::string method_name, uint32_t nonce) { + Address addr(oep4Contract); + NeoVmParamValue::ParamArray args{}; + auto invokeCode = ParamsBuilder::buildOep4InvokeCode(addr, method_name, {args}); + + return Transaction(0, 0xD1, nonce, 0, 0, "", invokeCode); +} + +Transaction Oep4::name(uint32_t nonce) { + return Oep4::readOnlyMethod("name", nonce); +} + +Transaction Oep4::symbol(uint32_t nonce) { + return Oep4::readOnlyMethod("symbol", nonce); +} + +Transaction Oep4::decimals(uint32_t nonce) { + return Oep4::readOnlyMethod("decimals", nonce); +} + +Transaction Oep4::totalSupply(uint32_t nonce) { + return Oep4::readOnlyMethod("totalSupply", nonce); +} + +Transaction Oep4::balanceOf(const Address& user, uint32_t nonce) { + Address contract(oep4Contract); + Data d(std::begin(user._data), std::end(user._data)); + NeoVmParamValue::ParamArray args{d}; + auto invokeCode = ParamsBuilder::buildOep4InvokeCode(contract, "balanceOf", {args}); + return Transaction(0, 0xD1, nonce, 0, 0, "", invokeCode); +} + +Transaction Oep4::transfer(const Signer& from, const Address& to, uint64_t amount, + const Signer& payer, uint64_t gasPrice, uint64_t gasLimit, + uint32_t nonce) { + Address contract(oep4Contract); + + auto fromAddr = from.getAddress(); + NeoVmParamValue::ParamArray args{fromAddr._data, to._data, amount}; + // yes, invoke neovm is not like ont transfer + std::reverse(args.begin(), args.end()); + auto invokeCode = ParamsBuilder::buildOep4InvokeCode(contract, "transfer", {args}); + + auto tx = Transaction(0, 0xD1, nonce, gasPrice, gasLimit, payer.getAddress().string(), invokeCode); + from.sign(tx); + payer.addSign(tx); + + return tx; +} + +Transaction Oep4::unsignedTransfer(const Address& from, const Address& to, uint64_t amount, + const Address& payer, uint64_t gasPrice, uint64_t gasLimit, + uint32_t nonce) { + Address contract(oep4Contract); + + NeoVmParamValue::ParamArray args{from._data, to._data, amount}; + // yes, invoke neovm is not like ont transfer + std::reverse(args.begin(), args.end()); + auto invokeCode = ParamsBuilder::buildOep4InvokeCode(contract, "transfer", {args}); + + auto tx = Transaction(0, 0xD1, nonce, gasPrice, gasLimit, payer.string(), invokeCode); + return tx; +} + +} // namespace TW::Ontology diff --git a/src/Ontology/Oep4.h b/src/Ontology/Oep4.h new file mode 100644 index 00000000000..31f4a99f283 --- /dev/null +++ b/src/Ontology/Oep4.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Address.h" +#include "Asset.h" +#include "Data.h" +#include "ParamsBuilder.h" +#include "Transaction.h" + +namespace TW::Ontology { + +class Oep4 { +private: + static constexpr uint8_t version = 0x00; + + Data oep4Contract; + // building neovm instruction for oep4 readonly method(name, symbol...) + // are all the same except the method name + Transaction readOnlyMethod(std::string methodName, uint32_t nonce); + +public: + explicit Oep4(const Address addr) noexcept; + explicit Oep4(const Data bin) noexcept; + Oep4() = delete; + Data contractAddress() { return oep4Contract; } + Transaction name(uint32_t nonce); + Transaction symbol(uint32_t nonce); + Transaction decimals(uint32_t nonce); + Transaction totalSupply(uint32_t nonce); + Transaction balanceOf(const Address& address, uint32_t nonce); + Transaction transfer(const Signer& from, const Address& to, uint64_t amount, + const Signer& payer, uint64_t gasPrice, uint64_t gasLimit, + uint32_t nonce); + Transaction unsignedTransfer(const Address& from, const Address& to, uint64_t amount, + const Address& payer, uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce); +}; +} // namespace TW::Ontology diff --git a/src/Ontology/Oep4TxBuilder.cpp b/src/Ontology/Oep4TxBuilder.cpp new file mode 100644 index 00000000000..9041adc3b78 --- /dev/null +++ b/src/Ontology/Oep4TxBuilder.cpp @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Oep4TxBuilder.h" +#include "../HexCoding.h" + +namespace TW::Ontology { + +Data Oep4TxBuilder::decimals(const Ontology::Proto::SigningInput& input) { + Oep4 oep4(parse_hex(input.contract())); + auto transaction = oep4.decimals(input.nonce()); + auto encoded = transaction.serialize(); + return encoded; +} + +Data Oep4TxBuilder::balanceOf(const Ontology::Proto::SigningInput& input) { + Oep4 oep4(parse_hex(input.contract())); + auto queryAddress = Address(input.query_address()); + auto transaction = oep4.balanceOf(queryAddress, input.nonce()); + auto encoded = transaction.serialize(); + return encoded; +} + +Data Oep4TxBuilder::transfer(const Ontology::Proto::SigningInput& input) { + Oep4 oep4(parse_hex(input.contract())); + auto payerSigner = Signer(PrivateKey(input.payer_private_key())); + auto fromSigner = Signer(PrivateKey(input.owner_private_key())); + auto toAddress = Address(input.to_address()); + auto tranferTx = oep4.transfer(fromSigner, toAddress, input.amount(), payerSigner, + input.gas_price(), input.gas_limit(), input.nonce()); + auto encoded = tranferTx.serialize(); + return encoded; +} + +Data Oep4TxBuilder::build(const Ontology::Proto::SigningInput& input) { + auto method = std::string(input.method().begin(), input.method().end()); + if (method == "transfer") { + return Oep4TxBuilder::transfer(input); + } else if (method == "balanceOf") { + return Oep4TxBuilder::balanceOf(input); + } else if (method == "decimals") { + return Oep4TxBuilder::decimals(input); + } + + return Data(); +} + +Transaction Oep4TxBuilder::buildTx(const Ontology::Proto::SigningInput &input) { + Transaction tx; + auto method = std::string(input.method().begin(), input.method().end()); + auto oep4 = Oep4(parse_hex(input.contract())); + + if (method == "transfer") { + auto fromAddress = Address(input.owner_address()); + auto toAddress = Address(input.to_address()); + auto payerAddress = Address(input.payer_address()); + tx = oep4.unsignedTransfer(fromAddress, toAddress, input.amount(), payerAddress, input.gas_price(), input.gas_limit(), input.nonce()); + } else if (method == "balanceOf") { + auto queryAddress = Address(input.query_address()); + tx = oep4.balanceOf(queryAddress, input.nonce()); + } else if (method == "decimals") { + tx = oep4.decimals(input.nonce()); + } + + return tx; +} + +} // namespace TW::Ontology diff --git a/src/Ontology/Oep4TxBuilder.h b/src/Ontology/Oep4TxBuilder.h new file mode 100644 index 00000000000..ea09784bc6a --- /dev/null +++ b/src/Ontology/Oep4TxBuilder.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Oep4.h" + +#include "../proto/Ontology.pb.h" + +#include + +namespace TW::Ontology { + +class Oep4TxBuilder { + +public: + static Data decimals(const Ontology::Proto::SigningInput& input); + + static Data balanceOf(const Ontology::Proto::SigningInput& input); + + static Data transfer(const Ontology::Proto::SigningInput& input); + + static Data build(const Ontology::Proto::SigningInput& input); + + static Transaction buildTx(const Ontology::Proto::SigningInput& input); +}; + +} // namespace TW::Ontology diff --git a/src/Ontology/Ong.cpp b/src/Ontology/Ong.cpp index 383a411e549..2ba3f5ca170 100644 --- a/src/Ontology/Ong.cpp +++ b/src/Ontology/Ong.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Ong.h" #include "Data.h" @@ -10,34 +8,33 @@ #include -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology { Transaction Ong::decimals(uint32_t nonce) { auto builder = ParamsBuilder(); auto invokeCode = - ParamsBuilder::buildNativeInvokeCode(contractAddress(), version, "decimals", Data()); + ParamsBuilder::buildNativeInvokeCode(contractAddress(), version, "decimals", {Data()}); auto tx = Transaction(version, txType, nonce, (uint64_t)0, (uint64_t)0, (std::string) "", invokeCode); return tx; } -Transaction Ong::balanceOf(const Address &address, uint32_t nonce) { +Transaction Ong::balanceOf(const Address& address, uint32_t nonce) { auto builder = ParamsBuilder(); auto invokeCode = - ParamsBuilder::buildNativeInvokeCode(contractAddress(), version, "balanceOf", address.data); + ParamsBuilder::buildNativeInvokeCode(contractAddress(), version, "balanceOf", {address._data}); auto tx = Transaction(version, txType, nonce, (uint64_t)0, (uint64_t)0, (std::string) "", invokeCode); return tx; } -Transaction Ong::transfer(const Signer &from, const Address &to, uint64_t amount, - const Signer &payer, uint64_t gasPrice, uint64_t gasLimit, +Transaction Ong::transfer(const Signer& from, const Address& to, uint64_t amount, + const Signer& payer, uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce) { - std::list transferParam{from.getAddress().data, to.data, amount}; - std::vector args{transferParam}; + NeoVmParamValue::ParamList transferParam{from.getAddress()._data, to._data, amount}; + NeoVmParamValue::ParamArray args{transferParam}; auto invokeCode = - ParamsBuilder::buildNativeInvokeCode(contractAddress(), 0x00, "transfer", args); + ParamsBuilder::buildNativeInvokeCode(contractAddress(), 0x00, "transfer", {args}); auto tx = Transaction(version, txType, nonce, gasPrice, gasLimit, payer.getAddress().string(), invokeCode); from.sign(tx); @@ -45,16 +42,29 @@ Transaction Ong::transfer(const Signer &from, const Address &to, uint64_t amount return tx; } -Transaction Ong::withdraw(const Signer &claimer, const Address &receiver, uint64_t amount, - const Signer &payer, uint64_t gasPrice, uint64_t gasLimit, +Transaction Ong::withdraw(const Signer& claimer, const Address& receiver, uint64_t amount, + const Signer& payer, uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce) { auto ontContract = Address("AFmseVrdL9f9oyCzZefL9tG6UbvhUMqNMV"); - std::list args{claimer.getAddress().data, ontContract.data, receiver.data, amount}; + NeoVmParamValue::ParamList args{claimer.getAddress()._data, ontContract._data, receiver._data, amount}; auto invokeCode = - ParamsBuilder::buildNativeInvokeCode(contractAddress(), 0x00, "transferFrom", args); + ParamsBuilder::buildNativeInvokeCode(contractAddress(), 0x00, "transferFrom", {args}); auto tx = Transaction(version, txType, nonce, gasPrice, gasLimit, payer.getAddress().string(), invokeCode); claimer.sign(tx); payer.addSign(tx); return tx; -} \ No newline at end of file +} + +Transaction Ong::unsignedTransfer(const Address &from, const Address &to, uint64_t amount, const Address &payer, + uint64_t gasPrice, uint64_t gasLimit,uint32_t nonce) { + NeoVmParamValue::ParamList transferParam{from._data, to._data, amount}; + NeoVmParamValue::ParamArray args{transferParam}; + auto invokeCode = + ParamsBuilder::buildNativeInvokeCode(contractAddress(), 0x00, "transfer", {args}); + auto tx = Transaction(version, txType, nonce, gasPrice, gasLimit, + payer.string(), invokeCode); + return tx; +} + +} // namespace TW::Ontology diff --git a/src/Ontology/Ong.h b/src/Ontology/Ong.h index 27de0c1d108..0d69f9cf090 100644 --- a/src/Ontology/Ong.h +++ b/src/Ontology/Ong.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Asset.h" -#include "../Data.h" +#include "Data.h" namespace TW::Ontology { @@ -31,6 +29,9 @@ class Ong : public Asset { Transaction withdraw(const Signer &claimer, const Address &receiver, uint64_t amount, const Signer &payer, uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce); + + Transaction unsignedTransfer(const Address &from, const Address &to, uint64_t amount, const Address &payer, + uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce) override; }; } // namespace TW::Ontology diff --git a/src/Ontology/OngTxBuilder.cpp b/src/Ontology/OngTxBuilder.cpp index 8fc3c7f89d0..2cae37b9cb1 100644 --- a/src/Ontology/OngTxBuilder.cpp +++ b/src/Ontology/OngTxBuilder.cpp @@ -1,28 +1,25 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "OngTxBuilder.h" -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology { -Data OngTxBuilder::decimals(const Ontology::Proto::SigningInput &input) { +Data OngTxBuilder::decimals(const Ontology::Proto::SigningInput& input) { auto transaction = Ong().decimals(input.nonce()); auto encoded = transaction.serialize(); return encoded; } -Data OngTxBuilder::balanceOf(const Ontology::Proto::SigningInput &input) { +Data OngTxBuilder::balanceOf(const Ontology::Proto::SigningInput& input) { auto queryAddress = Address(input.query_address()); auto transaction = Ong().balanceOf(queryAddress, input.nonce()); auto encoded = transaction.serialize(); return encoded; } -Data OngTxBuilder::transfer(const Ontology::Proto::SigningInput &input) { +Data OngTxBuilder::transfer(const Ontology::Proto::SigningInput& input) { auto payer = Signer(PrivateKey(input.payer_private_key())); auto owner = Signer(PrivateKey(input.owner_private_key())); auto toAddress = Address(input.to_address()); @@ -32,7 +29,7 @@ Data OngTxBuilder::transfer(const Ontology::Proto::SigningInput &input) { return encoded; } -Data OngTxBuilder::withdraw(const Ontology::Proto::SigningInput &input) { +Data OngTxBuilder::withdraw(const Ontology::Proto::SigningInput& input) { auto payer = Signer(PrivateKey(input.payer_private_key())); auto owner = Signer(PrivateKey(input.owner_private_key())); auto toAddress = Address(input.to_address()); @@ -42,7 +39,7 @@ Data OngTxBuilder::withdraw(const Ontology::Proto::SigningInput &input) { return encoded; } -Data OngTxBuilder::build(const Ontology::Proto::SigningInput &input) { +Data OngTxBuilder::build(const Ontology::Proto::SigningInput& input) { auto method = std::string(input.method().begin(), input.method().end()); if (method == "transfer") { return OngTxBuilder::transfer(input); @@ -55,3 +52,14 @@ Data OngTxBuilder::build(const Ontology::Proto::SigningInput &input) { } return Data(); } + +Transaction OngTxBuilder::buildTransferTx(const Ontology::Proto::SigningInput &input) { + auto fromSigner = Address(input.owner_address()); + auto toAddress = Address(input.to_address()); + auto payerAddress = Address(input.payer_address()); + auto transferTx = Ong().unsignedTransfer(fromSigner, toAddress, input.amount(), payerAddress, + input.gas_price(), input.gas_limit(), input.nonce()); + return transferTx; +} + +} // namespace TW::Ontology diff --git a/src/Ontology/OngTxBuilder.h b/src/Ontology/OngTxBuilder.h index e5843a0909b..8a3365017f9 100644 --- a/src/Ontology/OngTxBuilder.h +++ b/src/Ontology/OngTxBuilder.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -26,6 +24,8 @@ class OngTxBuilder { static Data withdraw(const Ontology::Proto::SigningInput& input); static Data build(const Ontology::Proto::SigningInput& input); + + static Transaction buildTransferTx(const Ontology::Proto::SigningInput &input); }; } // namespace TW::Ontology diff --git a/src/Ontology/Ont.cpp b/src/Ontology/Ont.cpp index 08ce5847328..9efd9812d06 100644 --- a/src/Ontology/Ont.cpp +++ b/src/Ontology/Ont.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Ont.h" #include "Data.h" @@ -10,37 +8,50 @@ #include -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology { Transaction Ont::decimals(uint32_t nonce) { auto builder = ParamsBuilder(); auto invokeCode = - ParamsBuilder::buildNativeInvokeCode(contractAddress(), version, "decimals", Data()); + ParamsBuilder::buildNativeInvokeCode(contractAddress(), version, "decimals", {Data()}); auto tx = Transaction((uint8_t)0, txType, nonce, (uint64_t)0, (uint64_t)0, (std::string) "", invokeCode); return tx; } -Transaction Ont::balanceOf(const Address &address, uint32_t nonce) { +Transaction Ont::balanceOf(const Address& address, uint32_t nonce) { auto builder = ParamsBuilder(); auto invokeCode = - ParamsBuilder::buildNativeInvokeCode(contractAddress(), version, "balanceOf", address.data); + ParamsBuilder::buildNativeInvokeCode(contractAddress(), version, "balanceOf", {address._data}); auto tx = Transaction((uint8_t)0, txType, nonce, (uint64_t)0, (uint64_t)0, (std::string) "", invokeCode); return tx; } -Transaction Ont::transfer(const Signer &from, const Address &to, uint64_t amount, - const Signer &payer, uint64_t gasPrice, uint64_t gasLimit, +Transaction Ont::transfer(const Signer& from, const Address& to, uint64_t amount, + const Signer& payer, uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce) { - std::list transferParam{from.getAddress().data, to.data, amount}; - std::vector args{transferParam}; + NeoVmParamValue::ParamList transferParam{from.getAddress()._data, to._data, amount}; + NeoVmParamValue::ParamArray args{transferParam}; auto invokeCode = - ParamsBuilder::buildNativeInvokeCode(contractAddress(), 0x00, "transfer", args); + ParamsBuilder::buildNativeInvokeCode(contractAddress(), 0x00, "transfer", {args}); auto tx = Transaction(version, txType, nonce, gasPrice, gasLimit, payer.getAddress().string(), invokeCode); from.sign(tx); payer.addSign(tx); return tx; } + +Transaction Ont::unsignedTransfer(const Address& from, const Address& to, uint64_t amount, + const Address& payer, uint64_t gasPrice, uint64_t gasLimit, + uint32_t nonce) { + NeoVmParamValue::ParamList transferParam{from._data, to._data, amount}; + NeoVmParamValue::ParamArray args{transferParam}; + auto invokeCode = + ParamsBuilder::buildNativeInvokeCode(contractAddress(), 0x00, "transfer", {args}); + auto tx = Transaction(version, txType, nonce, gasPrice, gasLimit, + payer.string(), invokeCode); + return tx; +} + +} // namespace TW::Ontology diff --git a/src/Ontology/Ont.h b/src/Ontology/Ont.h index d0dce26e64d..4ceca2a9aeb 100644 --- a/src/Ontology/Ont.h +++ b/src/Ontology/Ont.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Asset.h" -#include "../Data.h" +#include "Data.h" namespace TW::Ontology { @@ -23,11 +21,15 @@ class Ont : public Asset { Transaction decimals(uint32_t nonce) override; - Transaction balanceOf(const Address &address, uint32_t nonce) override; + Transaction balanceOf(const Address& address, uint32_t nonce) override; - Transaction transfer(const Signer &from, const Address &to, uint64_t amount, - const Signer &payer, uint64_t gasPrice, uint64_t gasLimit, + Transaction transfer(const Signer& from, const Address& to, uint64_t amount, + const Signer& payer, uint64_t gasPrice, uint64_t gasLimit, uint32_t nonce) override; + + Transaction unsignedTransfer(const Address& from, const Address& to, uint64_t amount, + const Address& payer, uint64_t gasPrice, uint64_t gasLimit, + uint32_t nonce) override; }; } // namespace TW::Ontology diff --git a/src/Ontology/OntTxBuilder.cpp b/src/Ontology/OntTxBuilder.cpp index 9b727313fae..dc26b38e192 100644 --- a/src/Ontology/OntTxBuilder.cpp +++ b/src/Ontology/OntTxBuilder.cpp @@ -1,28 +1,25 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "OntTxBuilder.h" -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology { -Data OntTxBuilder::decimals(const Ontology::Proto::SigningInput &input) { +Data OntTxBuilder::decimals(const Ontology::Proto::SigningInput& input) { auto transaction = Ont().decimals(input.nonce()); auto encoded = transaction.serialize(); return encoded; } -Data OntTxBuilder::balanceOf(const Ontology::Proto::SigningInput &input) { +Data OntTxBuilder::balanceOf(const Ontology::Proto::SigningInput& input) { auto queryAddress = Address(input.query_address()); auto transaction = Ont().balanceOf(queryAddress, input.nonce()); auto encoded = transaction.serialize(); return encoded; } -Data OntTxBuilder::transfer(const Ontology::Proto::SigningInput &input) { +Data OntTxBuilder::transfer(const Ontology::Proto::SigningInput& input) { auto payerSigner = Signer(PrivateKey(input.payer_private_key())); auto fromSigner = Signer(PrivateKey(input.owner_private_key())); auto toAddress = Address(input.to_address()); @@ -32,7 +29,7 @@ Data OntTxBuilder::transfer(const Ontology::Proto::SigningInput &input) { return encoded; } -Data OntTxBuilder::build(const Ontology::Proto::SigningInput &input) { +Data OntTxBuilder::build(const Ontology::Proto::SigningInput& input) { auto method = std::string(input.method().begin(), input.method().end()); if (method == "transfer") { return OntTxBuilder::transfer(input); @@ -43,3 +40,14 @@ Data OntTxBuilder::build(const Ontology::Proto::SigningInput &input) { } return Data(); } + +Transaction OntTxBuilder::buildTransferTx(const Ontology::Proto::SigningInput &input) { + auto fromSigner = Address(input.owner_address()); + auto toAddress = Address(input.to_address()); + auto payerAddress = Address(input.payer_address()); + auto transferTx = Ont().unsignedTransfer(fromSigner, toAddress, input.amount(), payerAddress, + input.gas_price(), input.gas_limit(), input.nonce()); + return transferTx; +} + +} // namespace TW::Ontology diff --git a/src/Ontology/OntTxBuilder.h b/src/Ontology/OntTxBuilder.h index aabc1cc8e40..dfeaee07fb7 100644 --- a/src/Ontology/OntTxBuilder.h +++ b/src/Ontology/OntTxBuilder.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -24,6 +22,8 @@ class OntTxBuilder { static Data transfer(const Ontology::Proto::SigningInput& input); static Data build(const Ontology::Proto::SigningInput& input); + + static Transaction buildTransferTx(const Ontology::Proto::SigningInput &input); }; } // namespace TW::Ontology diff --git a/src/Ontology/OpCode.h b/src/Ontology/OpCode.h index 6c44454b1a3..b4d86aa9303 100644 --- a/src/Ontology/OpCode.h +++ b/src/Ontology/OpCode.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -25,5 +23,6 @@ static const uint8_t TO_ALT_STACK{0x6B}; static const uint8_t FROM_ALT_STACK{0x6C}; static const uint8_t SWAP{0x7C}; static const uint8_t HAS_KEY{0xC8}; +static const uint8_t APP_CALL{0x67}; -} // namespace TW::Ontology \ No newline at end of file +} // namespace TW::Ontology diff --git a/src/Ontology/ParamsBuilder.cpp b/src/Ontology/ParamsBuilder.cpp index 367863b085e..7c529aa0080 100644 --- a/src/Ontology/ParamsBuilder.cpp +++ b/src/Ontology/ParamsBuilder.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "ParamsBuilder.h" #include "Data.h" @@ -12,34 +10,40 @@ #include #include -#include #include +#include -using namespace TW; -using namespace TW::Ontology; - -void ParamsBuilder::buildNeoVmParam(ParamsBuilder& builder, const boost::any& param) { - if (param.type() == typeid(std::string)) { - builder.push(boost::any_cast(param)); - } else if (param.type() == typeid(std::array)) { - builder.push(boost::any_cast>(param)); - } else if (param.type() == typeid(Data)) { - builder.push(boost::any_cast(param)); - } else if (param.type() == typeid(uint64_t)) { - builder.push(boost::any_cast(param)); - } else if (param.type() == typeid(std::vector)) { - auto paramVec = boost::any_cast>(param); - for (const auto& item : paramVec) { - ParamsBuilder::buildNeoVmParam(builder, item); +namespace TW::Ontology { + +void ParamsBuilder::buildNeoVmParam(ParamsBuilder& builder, const NeoVmParamValue& param) { + + if (auto* paramStr = std::get_if(¶m.params); paramStr) { + builder.push(*paramStr); + } else if (auto* paramFixedArray = std::get_if(¶m.params); paramFixedArray) { + builder.push(*paramFixedArray); + } else if (auto* paramData = std::get_if(¶m.params); paramData) { + builder.push(*paramData); + } else if (auto* paramInteger = std::get_if(¶m.params); paramInteger) { + builder.push(*paramInteger); + } else if (auto* paramArray = std::get_if(¶m.params); paramArray) { + for (auto&& item : *paramArray) { + std::visit([&builder](auto&& arg) { + NeoVmParamValue::ParamVariant value = arg; + ParamsBuilder::buildNeoVmParam(builder, {value}); + }, + item); } - builder.push(static_cast(paramVec.size())); + builder.push(static_cast(paramArray->size())); builder.pushBack(PACK); - } else if (param.type() == typeid(std::list)) { + } else if (auto* paramList = std::get_if(¶m.params); paramList) { builder.pushBack(PUSH0); builder.pushBack(NEW_STRUCT); builder.pushBack(TO_ALT_STACK); - for (auto const& p : boost::any_cast>(param)) { - ParamsBuilder::buildNeoVmParam(builder, p); + for (auto const& p : *paramList) { + std::visit([&builder](auto&& arg) { + NeoVmParamValue::ParamVariant value = arg; + ParamsBuilder::buildNeoVmParam(builder, {value}); + }, p); builder.pushBack(DUP_FROM_ALT_STACK); builder.pushBack(SWAP); builder.pushBack(HAS_KEY); @@ -221,7 +225,7 @@ Data ParamsBuilder::fromMultiPubkey(uint8_t m, const std::vector& pubKeys) } Data ParamsBuilder::buildNativeInvokeCode(const Data& contractAddress, uint8_t version, - const std::string& method, const boost::any& params) { + const std::string& method, const NeoVmParamValue& params) { ParamsBuilder builder; ParamsBuilder::buildNeoVmParam(builder, params); builder.push(Data(method.begin(), method.end())); @@ -231,4 +235,18 @@ Data ParamsBuilder::buildNativeInvokeCode(const Data& contractAddress, uint8_t v std::string nativeInvoke = "Ontology.Native.Invoke"; builder.push(Data(nativeInvoke.begin(), nativeInvoke.end())); return builder.getBytes(); -} \ No newline at end of file +} + +Data ParamsBuilder::buildOep4InvokeCode(const Address& contractAddress, const std::string& method, const NeoVmParamValue& params) { + ParamsBuilder builder; + ParamsBuilder::buildNeoVmParam(builder, params); + builder.push(method); + builder.pushBack(APP_CALL); + Address clone = contractAddress; + std::reverse(std::begin(clone._data), std::end(clone._data)); + builder.pushBack(clone._data); + + return builder.getBytes(); +} + +} // namespace TW::Ontology diff --git a/src/Ontology/ParamsBuilder.h b/src/Ontology/ParamsBuilder.h index cf2b3202003..a2d8e0d8818 100644 --- a/src/Ontology/ParamsBuilder.h +++ b/src/Ontology/ParamsBuilder.h @@ -1,29 +1,39 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../BinaryCoding.h" -#include "../Data.h" - -#include +#include "Data.h" #include #include #include #include +#include +#include + +#include "Address.h" namespace TW::Ontology { +struct NeoVmParamValue; + +struct NeoVmParamValue { + using ParamFixedArray = std::array; + using ParamList = std::list>; + using ParamArray = std::vector>; + using ParamVariant = std::variant; + ParamVariant params; +}; + class ParamsBuilder { - private: +private: std::vector bytes; - public: +public: static const size_t MAX_PK_SIZE = 16; std::vector getBytes() { return bytes; } @@ -36,7 +46,7 @@ class ParamsBuilder { static Data fromMultiPubkey(uint8_t m, const std::vector& pubKeys); - static void buildNeoVmParam(ParamsBuilder& builder, const boost::any& param); + static void buildNeoVmParam(ParamsBuilder& builder, const NeoVmParamValue& param); static void buildNeoVmParam(ParamsBuilder& builder, const std::string& param); @@ -77,7 +87,9 @@ class ParamsBuilder { static std::vector buildNativeInvokeCode(const std::vector& contractAddress, uint8_t version, const std::string& method, - const boost::any& params); + const NeoVmParamValue& params); + + static std::vector buildOep4InvokeCode(const Address& contractAddress, const std::string& method, const NeoVmParamValue& params); }; -} // namespace TW::Ontology \ No newline at end of file +} // namespace TW::Ontology diff --git a/src/Ontology/SigData.cpp b/src/Ontology/SigData.cpp index e1f52e1b1e2..e4901bc910f 100644 --- a/src/Ontology/SigData.cpp +++ b/src/Ontology/SigData.cpp @@ -1,16 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include "ParamsBuilder.h" #include "SigData.h" -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology { Data SigData::serialize() { auto sigInfo = ParamsBuilder::fromSigs(sigs); @@ -28,3 +25,5 @@ Data SigData::serialize() { builder.pushVar(verifyInfo); return builder.getBytes(); } + +} // namespace TW::Ontology diff --git a/src/Ontology/SigData.h b/src/Ontology/SigData.h index eaa054c4115..67b1290b992 100644 --- a/src/Ontology/SigData.h +++ b/src/Ontology/SigData.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" namespace TW::Ontology { diff --git a/src/Ontology/Signer.cpp b/src/Ontology/Signer.cpp index 70fbc9e0661..48cf1bab857 100644 --- a/src/Ontology/Signer.cpp +++ b/src/Ontology/Signer.cpp @@ -1,21 +1,18 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" + #include "HexCoding.h" #include "SigData.h" +#include "../Ontology/Oep4TxBuilder.h" #include "../Ontology/OngTxBuilder.h" #include "../Ontology/OntTxBuilder.h" -#include "../Hash.h" - #include -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto contract = std::string(input.contract().begin(), input.contract().end()); @@ -27,20 +24,24 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { } else if (contract == "ONG") { auto encoded = OngTxBuilder::build(input); output.set_encoded(encoded.data(), encoded.size()); + } else { + // then assume it's oep4 address + auto encoded = Oep4TxBuilder::build(input); + output.set_encoded(encoded.data(), encoded.size()); } } catch (...) { } return output; } -Signer::Signer(TW::PrivateKey priKey) : privateKey(std::move(priKey)) { - auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); +Signer::Signer(TW::PrivateKey priKey) : privKey(std::move(priKey)) { + auto pubKey = privKey.getPublicKey(TWPublicKeyTypeNIST256p1); publicKey = pubKey.bytes; address = Address(pubKey).string(); } PrivateKey Signer::getPrivateKey() const { - return privateKey; + return privKey; } PublicKey Signer::getPublicKey() const { @@ -55,7 +56,7 @@ void Signer::sign(Transaction& tx) const { if (tx.sigVec.size() >= Transaction::sigVecLimit) { throw std::runtime_error("the number of transaction signatures should not be over 16."); } - auto signature = getPrivateKey().sign(Hash::sha256(tx.txHash()), TWCurveNIST256p1); + auto signature = getPrivateKey().sign(tx.txHash(), TWCurveNIST256p1); signature.pop_back(); tx.sigVec.emplace_back(publicKey, signature, 1); } @@ -64,7 +65,30 @@ void Signer::addSign(Transaction& tx) const { if (tx.sigVec.size() >= Transaction::sigVecLimit) { throw std::runtime_error("the number of transaction signatures should not be over 16."); } - auto signature = getPrivateKey().sign(Hash::sha256(tx.txHash()), TWCurveNIST256p1); + auto signature = getPrivateKey().sign(tx.txHash(), TWCurveNIST256p1); signature.pop_back(); tx.sigVec.emplace_back(publicKey, signature, 1); } + +Data Signer::encodeTransaction(const Proto::SigningInput& input, const std::vector& signatures, const std::vector& publicKeys) { + assert(signatures.size() > 0 && signatures.size() == publicKeys.size()); + + auto contract = std::string(input.contract().begin(), input.contract().end()); + auto tx = Transaction(); + + if (contract == "ONT") { + tx = OntTxBuilder::buildTransferTx(input); + } else if (contract == "ONG") { + tx = OngTxBuilder::buildTransferTx(input); + } else { + tx = Oep4TxBuilder::buildTx(input); + } + + for (auto i = 0u; i < signatures.size(); ++i) { + tx.sigVec.emplace_back(publicKeys[i].bytes, signatures[i], 1); + } + + return tx.serialize(); +} + +} // namespace TW::Ontology diff --git a/src/Ontology/Signer.h b/src/Ontology/Signer.h index bf5e21f83ca..cc6745d0ea2 100644 --- a/src/Ontology/Signer.h +++ b/src/Ontology/Signer.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -15,19 +13,19 @@ #include #include - namespace TW::Ontology { class Signer { - public: +public: /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; - private: - Data publicKey; - TW::PrivateKey privateKey; + static Data encodeTransaction(const Proto::SigningInput& input, const std::vector& signatures, const std::vector& publicKeyss); +private: + Data publicKey; + TW::PrivateKey privKey; std::string address; - public: +public: explicit Signer(TW::PrivateKey priKey); PrivateKey getPrivateKey() const; diff --git a/src/Ontology/Transaction.cpp b/src/Ontology/Transaction.cpp index a3306871b75..694c8110ec6 100644 --- a/src/Ontology/Transaction.cpp +++ b/src/Ontology/Transaction.cpp @@ -1,20 +1,14 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Transaction.h" #include "Address.h" #include "ParamsBuilder.h" -#include "../Hash.h" -#include "../HexCoding.h" - #include -using namespace TW; -using namespace TW::Ontology; +namespace TW::Ontology { const std::string Transaction::ZERO_PAYER = "AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM"; @@ -25,7 +19,7 @@ std::vector Transaction::serializeUnsigned() { builder.pushBack(nonce); builder.pushBack(gasPrice); builder.pushBack(gasLimit); - builder.pushBack(Address(payer).data); + builder.pushBack(Address(payer)._data); if (!payload.empty()) { builder.pushVar(payload); } @@ -49,7 +43,7 @@ std::vector Transaction::serialize() { std::vector Transaction::txHash() { auto txSerialized = Transaction::serializeUnsigned(); - return Hash::sha256(Hash::sha256(txSerialized)); + return Hash::sha256(Hash::sha256(Hash::sha256(txSerialized))); } std::vector Transaction::serialize(const PublicKey& pk) { @@ -58,3 +52,5 @@ std::vector Transaction::serialize(const PublicKey& pk) { builder.pushBack((uint8_t)0xAC); return builder.getBytes(); } + +} // namespace TW::Ontology diff --git a/src/Ontology/Transaction.h b/src/Ontology/Transaction.h index e4f9796b4f6..39baf92e75e 100644 --- a/src/Ontology/Transaction.h +++ b/src/Ontology/Transaction.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -40,6 +38,8 @@ class Transaction { std::vector sigVec; + Transaction() = default; + Transaction(uint8_t ver, uint8_t type, uint32_t nonce, uint64_t gasPrice, uint64_t gasLimit, std::string payer, std::vector payload) : version(ver) diff --git a/src/Polkadot/Address.h b/src/Polkadot/Address.h index 46a38376b5d..c717bebe016 100644 --- a/src/Polkadot/Address.h +++ b/src/Polkadot/Address.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include "SS58Address.h" #include @@ -19,12 +17,17 @@ class Address: public SS58Address { public: /// Determines whether a string makes a valid address. static bool isValid(const std::string& string) { return SS58Address::isValid(string, TWSS58AddressTypePolkadot); } + static bool isValid(const std::string& string, uint32_t ss58Prefix) { return SS58Address::isValid(string, ss58Prefix); } /// Initializes a Polkadot address with a string representation. - Address(const std::string& string): SS58Address(string, TWSS58AddressTypePolkadot) {} + explicit Address(const std::string& string): SS58Address(string, TWSS58AddressTypePolkadot) {} + + /// Initializes a Polkadot address with a string representation. + explicit Address(const std::string& string, uint32_t ss58Prefix): SS58Address(string, ss58Prefix) {} /// Initializes a Polkadot address with a public key. - Address(const PublicKey& publicKey): SS58Address(publicKey, TWSS58AddressTypePolkadot) {} + explicit Address(const PublicKey& publicKey): SS58Address(publicKey, TWSS58AddressTypePolkadot) {} + /// Initializes a Polkadot address with a public key and a given ss58Prefix. + explicit Address(const PublicKey& publicKey, std::uint32_t ss58Prefix): SS58Address(publicKey, ss58Prefix) {} }; } // namespace TW::Polkadot - diff --git a/src/Polkadot/Entry.cpp b/src/Polkadot/Entry.cpp index d067d8a2f57..705acc11d9d 100644 --- a/src/Polkadot/Entry.cpp +++ b/src/Polkadot/Entry.cpp @@ -1,33 +1,61 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" +#include "Coin.h" #include "Signer.h" +#include "../proto/TransactionCompiler.pb.h" -using namespace TW::Polkadot; -using namespace TW; -using namespace std; +namespace TW::Polkadot { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { - return Address::isValid(address); +bool Entry::validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const { + if (auto* prefix = std::get_if(&addressPrefix); prefix) { + return Address::isValid(address, *prefix); + } + const auto ss58Prefix = TW::ss58Prefix(coin); + return Address::isValid(address, ss58Prefix); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { - return Address(publicKey).string(); +std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, const PrefixVariant& addressPrefix) const { + if (auto* ss58Prefix = std::get_if(&addressPrefix); ss58Prefix) { + return Address(publicKey, *ss58Prefix).string(); + } + const auto ss58Prefix = TW::ss58Prefix(coin); + return Address(publicKey, ss58Prefix).string(); } Data Entry::addressToData(TWCoinType coin, const std::string& address) const { - const auto addr = Address(address); + const auto ss58Prefix = TW::ss58Prefix(coin); + const auto addr = Address(address, ss58Prefix); return {addr.bytes.begin() + 1, addr.bytes.end()}; } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + + auto preImage = Signer::signaturePreImage(input); + auto preImageHash = Signer::hash(preImage); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + auto signedTx = Signer::encodeTransaction(input, publicKey.bytes, signature); + output.set_encoded(signedTx.data(), signedTx.size()); + }); +} +} // namespace TW::Polkadot diff --git a/src/Polkadot/Entry.h b/src/Polkadot/Entry.h index fff44b3798d..de481bca381 100644 --- a/src/Polkadot/Entry.h +++ b/src/Polkadot/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,12 +10,14 @@ namespace TW::Polkadot { /// Entry point for implementation of Polkadot coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Polkadot diff --git a/src/Polkadot/Extrinsic.cpp b/src/Polkadot/Extrinsic.cpp index 17a41164d0d..b88e5e9a843 100644 --- a/src/Polkadot/Extrinsic.cpp +++ b/src/Polkadot/Extrinsic.cpp @@ -1,15 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Extrinsic.h" #include #include -using namespace TW; -using namespace TW::Polkadot; +namespace TW::Polkadot { static constexpr uint8_t signedBit = 0x80; static constexpr uint8_t sigTypeEd25519 = 0x00; @@ -18,51 +15,65 @@ static constexpr uint32_t multiAddrSpecVersion = 28; static constexpr uint32_t multiAddrSpecVersionKsm = 2028; static const std::string balanceTransfer = "Balances.transfer"; -static const std::string utilityBatch = "Utility.batch"; +static const std::string utilityBatch = "Utility.batch_all"; static const std::string stakingBond = "Staking.bond"; static const std::string stakingBondExtra = "Staking.bond_extra"; static const std::string stakingUnbond = "Staking.unbond"; static const std::string stakingWithdrawUnbond = "Staking.withdraw_unbonded"; static const std::string stakingNominate = "Staking.nominate"; static const std::string stakingChill = "Staking.chill"; +static const std::string stakingReBond = "Staking.rebond"; + +// Non-existent modules and methods on Polkadot and Kusama chains: +static const std::string assetsTransfer = "Assets.transfer"; +static const std::string joinIdentityAsKey = "Identity.join_identity_as_key"; +static const std::string identityAddAuthorization = "Identity.add_authorization"; // Readable decoded call index can be found from https://polkascan.io -static std::map polkadotCallIndices = { - {balanceTransfer, Data{0x05, 0x00}}, - {stakingBond, Data{0x07, 0x00}}, - {stakingBondExtra, Data{0x07, 0x01}}, - {stakingUnbond, Data{0x07, 0x02}}, +const static std::map polkadotCallIndices = { + {balanceTransfer, Data{0x05, 0x00}}, + {stakingBond, Data{0x07, 0x00}}, + {stakingBondExtra, Data{0x07, 0x01}}, + {stakingUnbond, Data{0x07, 0x02}}, {stakingWithdrawUnbond, Data{0x07, 0x03}}, - {stakingNominate, Data{0x07, 0x05}}, - {stakingChill, Data{0x07, 0x06}}, - {utilityBatch, Data{0x1a, 0x02}}, + {stakingNominate, Data{0x07, 0x05}}, + {stakingChill, Data{0x07, 0x06}}, + {utilityBatch, Data{0x1a, 0x02}}, + {stakingReBond, Data{0x07, 0x13}}, }; -static std::map kusamaCallIndices = { - {balanceTransfer, Data{0x04, 0x00}}, - {stakingBond, Data{0x06, 0x00}}, - {stakingBondExtra, Data{0x06, 0x01}}, - {stakingUnbond, Data{0x06, 0x02}}, +// Default Kusama call indices. +const static std::map kusamaCallIndices = { + {balanceTransfer, Data{0x04, 0x00}}, + {stakingBond, Data{0x06, 0x00}}, + {stakingBondExtra, Data{0x06, 0x01}}, + {stakingUnbond, Data{0x06, 0x02}}, {stakingWithdrawUnbond, Data{0x06, 0x03}}, - {stakingNominate, Data{0x06, 0x05}}, - {stakingChill, Data{0x06, 0x06}}, - {utilityBatch, Data{0x18, 0x02}}, + {stakingNominate, Data{0x06, 0x05}}, + {stakingChill, Data{0x06, 0x06}}, + {utilityBatch, Data{0x18, 0x02}}, + {stakingReBond, Data{0x06, 0x13}}, }; -static Data getCallIndex(TWSS58AddressType network, const std::string& key) { - switch (network) { - case TWSS58AddressTypePolkadot: - return polkadotCallIndices[key]; - case TWSS58AddressTypeKusama: - return kusamaCallIndices[key]; +static Data getCallIndex(const Proto::CallIndices& callIndices, uint32_t network, const std::string& key) { + if (callIndices.has_custom()) { + return encodeCallIndex(callIndices.custom().module_index(), callIndices.custom().method_index()); + } + if (network == static_cast(TWSS58AddressTypePolkadot) && polkadotCallIndices.contains(key)) { + return polkadotCallIndices.at(key); } + if (network == static_cast(TWSS58AddressTypeKusama) && kusamaCallIndices.contains(key)) { + return kusamaCallIndices.at(key); + } + throw std::invalid_argument("'call_indices' is not set"); } -bool Extrinsic::encodeRawAccount(TWSS58AddressType network, uint32_t specVersion) { - if ((network == TWSS58AddressTypePolkadot && specVersion >= multiAddrSpecVersion) || - (network == TWSS58AddressTypeKusama && specVersion >= multiAddrSpecVersionKsm)) { - return false; - } +bool Extrinsic::encodeRawAccount() const { + if (multiAddress || + (network == static_cast(TWSS58AddressTypePolkadot) && specVersion >= multiAddrSpecVersion) || + (network == static_cast(TWSS58AddressTypeKusama) && specVersion >= multiAddrSpecVersionKsm)) { + return false; + } return true; } @@ -77,175 +88,329 @@ Data Extrinsic::encodeEraNonceTip() const { return data; } -Data Extrinsic::encodeCall(const Proto::SigningInput& input) { +Data Extrinsic::encodeCall(const Proto::SigningInput& input) const { // call index from MetadataV11 Data data; - auto network = TWSS58AddressType(input.network()); if (input.has_balance_call()) { - data = encodeBalanceCall(input.balance_call(), network, input.spec_version()); + data = encodeBalanceCall(input.balance_call()); } else if (input.has_staking_call()) { - data = encodeStakingCall(input.staking_call(), network, input.spec_version()); + data = encodeStakingCall(input.staking_call()); + } else if (input.has_polymesh_call()) { + data = encodePolymeshCall(input.polymesh_call()); + } + return data; +} + +Data Extrinsic::encodeBalanceCall(const Proto::Balance& balance) const { + Data data; + if (balance.has_transfer()) { + data = encodeTransfer(balance.transfer()); + } else if (balance.has_batchtransfer()) { + //init call array + auto calls = std::vector(); + for (const auto& transfer : balance.batchtransfer().transfers()) { + Data itemData = encodeTransfer(transfer); + // put into calls array + calls.push_back(itemData); + } + data = encodeBatchCall(balance.batchtransfer().call_indices(), calls); + } else if (balance.has_asset_transfer()) { + data = encodeAssetTransfer(balance.asset_transfer()); + } else if (balance.has_batch_asset_transfer()) { + // init call array + auto calls = std::vector(); + for (const auto& transfer : balance.batch_asset_transfer().transfers()) { + // put into calls array + calls.push_back(encodeAssetTransfer(transfer)); + } + data = encodeBatchCall(balance.batch_asset_transfer().call_indices(), calls); } + return data; } -Data Extrinsic::encodeBalanceCall(const Proto::Balance& balance, TWSS58AddressType network, uint32_t specVersion) { +Data Extrinsic::encodeTransfer(const Proto::Balance::Transfer& transfer) const { Data data; - auto transfer = balance.transfer(); + auto address = SS58Address(transfer.to_address(), network); auto value = load(transfer.value()); // call index - append(data, getCallIndex(network, balanceTransfer)); + append(data, getCallIndex(transfer.call_indices(), network, balanceTransfer)); // destination - append(data, encodeAccountId(address.keyBytes(), encodeRawAccount(network, specVersion))); + append(data, encodeAccountId(address.keyBytes(), encodeRawAccount())); // value append(data, encodeCompact(value)); + // memo + if (transfer.memo().length() > 0) { + append(data, 0x01); + auto memo = transfer.memo(); + if (memo.length() < 32) { + // padding memo with null + memo.append(32 - memo.length(), '\0'); + } + append(data, TW::data(memo)); + } + + return data; +} + +Data Extrinsic::encodeAssetTransfer(const Proto::Balance::AssetTransfer& transfer) const { + Data data; + + auto address = SS58Address(transfer.to_address(), network); + auto value = load(transfer.value()); + + // call index + append(data, getCallIndex(transfer.call_indices(), network, assetsTransfer)); + // asset id + if (transfer.asset_id() > 0) { + // For native token transfer, should ignore asset id + append(data, Polkadot::encodeCompact(transfer.asset_id())); + } + // destination + append(data, Polkadot::encodeAccountId(address.keyBytes(), encodeRawAccount())); + // value + append(data, Polkadot::encodeCompact(value)); + return data; } -Data Extrinsic::encodeBatchCall(const std::vector& calls, TWSS58AddressType network) { +Data Extrinsic::encodeBatchCall(const Proto::CallIndices& batchCallIndices, const std::vector& calls) const { Data data; - append(data, getCallIndex(network, utilityBatch)); + append(data, getCallIndex(batchCallIndices, network, utilityBatch)); append(data, encodeVector(calls)); return data; } -Data Extrinsic::encodeStakingCall(const Proto::Staking& staking, TWSS58AddressType network, uint32_t specVersion) { +Data Extrinsic::encodeStakingCall(const Proto::Staking& staking) const { Data data; switch (staking.message_oneof_case()) { - case Proto::Staking::kBond: - { - auto address = SS58Address(staking.bond().controller(), byte(network)); - auto value = load(staking.bond().value()); - auto reward = byte(staking.bond().reward_destination()); - // call index - append(data, getCallIndex(network, stakingBond)); - // controller - append(data, encodeAccountId(address.keyBytes(), encodeRawAccount(network, specVersion))); - // value - append(data, encodeCompact(value)); - // reward destination - append(data, reward); - } - break; - - case Proto::Staking::kBondAndNominate: - { - // encode call1 - Data call1; - { - auto staking1 = Proto::Staking(); - auto* bond = staking1.mutable_bond(); - bond->set_controller(staking.bond_and_nominate().controller()); - bond->set_value(staking.bond_and_nominate().value()); - bond->set_reward_destination(staking.bond_and_nominate().reward_destination()); - // recursive call - call1 = encodeStakingCall(staking1, network, specVersion); - } - - // encode call2 - Data call2; - { - auto staking2 = Proto::Staking(); - auto* nominate = staking2.mutable_nominate(); - for (auto i = 0; i < staking.bond_and_nominate().nominators_size(); ++i) { - nominate->add_nominators(staking.bond_and_nominate().nominators(i)); - } - // recursive call - call2 = encodeStakingCall(staking2, network, specVersion); - } - - auto calls = std::vector{call1, call2}; - data = encodeBatchCall(calls, network); - } - break; - - case Proto::Staking::kBondExtra: - { - auto value = load(staking.bond_extra().value()); - // call index - append(data, getCallIndex(network, stakingBondExtra)); - // value - append(data, encodeCompact(value)); - } - break; - - case Proto::Staking::kUnbond: - { - auto value = load(staking.unbond().value()); - // call index - append(data, getCallIndex(network, stakingUnbond)); - // value - append(data, encodeCompact(value)); - } - break; - - case Proto::Staking::kWithdrawUnbonded: - { - auto spans = staking.withdraw_unbonded().slashing_spans(); - // call index - append(data, getCallIndex(network, stakingWithdrawUnbond)); - // num_slashing_spans - encode32LE(spans, data); - } - break; - - case Proto::Staking::kNominate: - { - std::vector accountIds; - for (auto& n : staking.nominate().nominators()) { - accountIds.emplace_back(SS58Address(n, network)); - } - // call index - append(data, getCallIndex(network, stakingNominate)); - // nominators - append(data, encodeAccountIds(accountIds, encodeRawAccount(network, specVersion))); - } - break; - - case Proto::Staking::kChill: - // call index - append(data, getCallIndex(network, stakingChill)); - break; - - case Proto::Staking::kChillAndUnbond: - { - // encode call1 - Data call1; - { - auto staking1 = Proto::Staking(); - staking1.mutable_chill(); - // recursive call - call1 = encodeStakingCall(staking1, network, specVersion); - } - - // encode call2 - Data call2; - { - auto staking2 = Proto::Staking(); - auto* unbond = staking2.mutable_unbond(); - unbond->set_value(staking.chill_and_unbond().value()); - // recursive call - call2 = encodeStakingCall(staking2, network, specVersion); - } - - auto calls = std::vector{call1, call2}; - data = encodeBatchCall(calls, network); + case Proto::Staking::kBond: { + auto value = load(staking.bond().value()); + auto reward = byte(staking.bond().reward_destination()); + // call index + append(data, getCallIndex(staking.bond().call_indices(), network, stakingBond)); + // controller + if (!staking.bond().controller().empty()) { + auto controller = SS58Address(staking.bond().controller(), network); + append(data, encodeAccountId(controller.keyBytes(), encodeRawAccount())); + } + // value + append(data, encodeCompact(value)); + // reward destination + append(data, reward); + } break; + + case Proto::Staking::kBondAndNominate: { + // encode call1 + Data call1; + { + auto staking1 = Proto::Staking(); + auto* bond = staking1.mutable_bond(); + bond->set_controller(staking.bond_and_nominate().controller()); + bond->set_value(staking.bond_and_nominate().value()); + bond->set_reward_destination(staking.bond_and_nominate().reward_destination()); + // recursive call + call1 = encodeStakingCall(staking1); + } + + // encode call2 + Data call2; + { + auto staking2 = Proto::Staking(); + auto* nominate = staking2.mutable_nominate(); + for (auto i = 0; i < staking.bond_and_nominate().nominators_size(); ++i) { + nominate->add_nominators(staking.bond_and_nominate().nominators(i)); } - break; + // recursive call + call2 = encodeStakingCall(staking2); + } + + auto calls = std::vector{call1, call2}; + data = encodeBatchCall(staking.bond_and_nominate().call_indices(), calls); + } break; + + case Proto::Staking::kBondExtra: { + auto value = load(staking.bond_extra().value()); + // call index + append(data, getCallIndex(staking.bond_extra().call_indices(), network, stakingBondExtra)); + // value + append(data, encodeCompact(value)); + } break; + + case Proto::Staking::kUnbond: { + auto value = load(staking.unbond().value()); + // call index + append(data, getCallIndex(staking.unbond().call_indices(), network, stakingUnbond)); + // value + append(data, encodeCompact(value)); + } break; + + case Proto::Staking::kRebond: { + auto value = load(staking.rebond().value()); + // call index + append(data, getCallIndex(staking.rebond().call_indices(), network, stakingReBond)); + // value + append(data, encodeCompact(value)); + } break; + + case Proto::Staking::kWithdrawUnbonded: { + auto spans = staking.withdraw_unbonded().slashing_spans(); + // call index + append(data, getCallIndex(staking.withdraw_unbonded().call_indices(), network, stakingWithdrawUnbond)); + // num_slashing_spans + encode32LE(spans, data); + } break; + + case Proto::Staking::kNominate: { + std::vector accountIds; + for (auto& n : staking.nominate().nominators()) { + accountIds.emplace_back(SS58Address(n, network)); + } + // call index + append(data, getCallIndex(staking.nominate().call_indices(), network, stakingNominate)); + // nominators + append(data, encodeAccountIds(accountIds, encodeRawAccount())); + } break; + + case Proto::Staking::kChill: + // call index + append(data, getCallIndex(staking.chill().call_indices(), network, stakingChill)); + break; + + case Proto::Staking::kChillAndUnbond: { + // encode call1 + Data call1; + { + auto staking1 = Proto::Staking(); + staking1.mutable_chill(); + // recursive call + call1 = encodeStakingCall(staking1); + } + + // encode call2 + Data call2; + { + auto staking2 = Proto::Staking(); + auto* unbond = staking2.mutable_unbond(); + unbond->set_value(staking.chill_and_unbond().value()); + // recursive call + call2 = encodeStakingCall(staking2); + } + + auto calls = std::vector{call1, call2}; + data = encodeBatchCall(staking.chill_and_unbond().call_indices(), calls); + } break; + + default: + break; + } + return data; +} + +Data Extrinsic::encodePolymeshCall(const Proto::PolymeshCall& polymesh) const { + Data data; + + if (polymesh.has_identity_call()) { + const auto& identity = polymesh.identity_call(); + if (identity.has_join_identity_as_key()) { + data = encodeIdentityJoinIdentityAsKey(identity.join_identity_as_key()); + } else if (identity.has_add_authorization()) { + data = encodeIdentityAddAuthorization(identity.add_authorization()); + } + } + + return data; +} + +Data Extrinsic::encodeIdentityJoinIdentityAsKey(const Proto::Identity::JoinIdentityAsKey& joinIdentity) const { + Data data; + + // call index + append(data, getCallIndex(joinIdentity.call_indices(), network, joinIdentityAsKey)); + + // data + encode64LE(joinIdentity.auth_id(), data); + + return data; +} + +Data Extrinsic::encodeIdentityAddAuthorization(const Proto::Identity::AddAuthorization & addAuthorization) const { + Data data; + + // call index + append(data, getCallIndex(addAuthorization.call_indices(), network, identityAddAuthorization)); + + // target + append(data, 0x01); + SS58Address address(addAuthorization.target(), network); + append(data, Polkadot::encodeAccountId(address.keyBytes(), true)); + + // join identity + append(data, 0x05); + + if (addAuthorization.has_data()) { + const auto& authData = addAuthorization.data(); + + // asset + if (authData.has_asset()) { + append(data, 0x01); + append(data, TW::data(authData.asset().data())); + } else { + append(data, 0x00); + } + + // extrinsic + if (authData.has_extrinsic()) { + append(data, 0x01); + append(data, TW::data(authData.extrinsic().data())); + } else { + append(data, 0x00); + } - default: - break; + // portfolio + if (authData.has_portfolio()) { + append(data, 0x01); + append(data, TW::data(authData.portfolio().data())); + } else { + append(data, 0x00); + } + } else { + // authorize all permissions + append(data, {0x01, 0x00}); // asset + append(data, {0x01, 0x00}); // extrinsic + append(data, {0x01, 0x00}); // portfolio } + + // expiry + append(data, encodeCompact(addAuthorization.expiry())); + return data; } +static bool requires_new_spec_compatbility(uint32_t network, uint32_t specVersion) noexcept { + // version 1002005 introduces a breaking change for Polkadot and Kusama + return ((network == 0 || network == 2) && specVersion >= 1002005); +} + Data Extrinsic::encodePayload() const { Data data; + auto use_new_spec = requires_new_spec_compatbility(network, specVersion); + // call append(data, call); // era / nonce / tip append(data, encodeEraNonceTip()); + // fee asset id + if (!feeAssetId.empty()) { + append(data, feeAssetId); + } + + if (use_new_spec) { + // mode (currently always 0) + data.push_back(0x00); + } + // specVersion encode32LE(specVersion, data); // transactionVersion @@ -254,24 +419,72 @@ Data Extrinsic::encodePayload() const { append(data, genesisHash); // block hash append(data, blockHash); + + if (use_new_spec) { + // empty metadata hash + data.push_back(0x00); + } return data; } Data Extrinsic::encodeSignature(const PublicKey& signer, const Data& signature) const { Data data; + auto use_new_spec = requires_new_spec_compatbility(network, specVersion); + // version header append(data, Data{extrinsicFormat | signedBit}); // signer public key - append(data, encodeAccountId(signer.bytes, encodeRawAccount(network, specVersion))); + append(data, encodeAccountId(signer.bytes, encodeRawAccount())); // signature type append(data, sigTypeEd25519); // signature append(data, signature); // era / nonce / tip append(data, encodeEraNonceTip()); + + // fee asset id + if (!feeAssetId.empty()) { + append(data, feeAssetId); + } + + if (use_new_spec) { + // mode (currently always 0) + data.push_back(0x00); + } + // call append(data, call); // append length encodeLengthPrefix(data); return data; } + +Data Extrinsic::encodeFeeAssetId(const Proto::SigningInput& input) { + if (!input.has_balance_call()) { + return {}; + } + + uint32_t rawFeeAssetId {0}; + if (input.balance_call().has_asset_transfer()) { + rawFeeAssetId = input.balance_call().asset_transfer().fee_asset_id(); + } else if (input.balance_call().has_batch_asset_transfer()) { + rawFeeAssetId = input.balance_call().batch_asset_transfer().fee_asset_id(); + } else { + return {}; + } + + Data feeAssetId; + if (rawFeeAssetId > 0) { + feeAssetId.push_back(0x01); + Data feeEncoding; + encode32LE(rawFeeAssetId, feeEncoding); + append(feeAssetId, feeEncoding); + } else { + // use native token + feeAssetId.push_back(0x00); + } + + return feeAssetId; +} + +} // namespace TW::Polkadot diff --git a/src/Polkadot/Extrinsic.h b/src/Polkadot/Extrinsic.h index dc20eb75908..885040a3bc1 100644 --- a/src/Polkadot/Extrinsic.h +++ b/src/Polkadot/Extrinsic.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Address.h" -#include "../Data.h" +#include "Data.h" #include "../proto/Polkadot.pb.h" #include "../uint256.h" #include "ScaleCodec.h" @@ -31,37 +29,52 @@ class Extrinsic { // encoded Call data Data call; // network - TWSS58AddressType network; + uint32_t network; + // enable multi-address + bool multiAddress; + // fee asset id + Data feeAssetId; - Extrinsic(const Proto::SigningInput& input) + explicit Extrinsic(const Proto::SigningInput& input) : blockHash(input.block_hash().begin(), input.block_hash().end()) , genesisHash(input.genesis_hash().begin(), input.genesis_hash().end()) , nonce(input.nonce()) , specVersion(input.spec_version()) , version(input.transaction_version()) - , tip(load(input.tip())) { + , tip(load(input.tip())) + , network(input.network()) + , multiAddress(input.multi_address()) { if (input.has_era()) { era = encodeEra(input.era().block_number(), input.era().period()); } else { // immortal era era = encodeCompact(0); } - network = TWSS58AddressType(input.network()); + // keep fee asset id encoding which will be used in encodePayload & encodeSignature. + // see: https://github.com/paritytech/substrate/blob/d1221692968b8bc62d6eab9d10cb6b5bf38c5dc2/frame/transaction-payment/asset-tx-payment/src/lib.rs#L152 + feeAssetId = encodeFeeAssetId(input); call = encodeCall(input); } - static Data encodeCall(const Proto::SigningInput& input); + Data encodeCall(const Proto::SigningInput& input) const; // Payload to sign. Data encodePayload() const; // Encode final data with signer public key and signature. Data encodeSignature(const PublicKey& signer, const Data& signature) const; protected: - static bool encodeRawAccount(TWSS58AddressType network, uint32_t specVersion); - static Data encodeBalanceCall(const Proto::Balance& balance, TWSS58AddressType network, uint32_t specVersion); - static Data encodeStakingCall(const Proto::Staking& staking, TWSS58AddressType network, uint32_t specVersion); - static Data encodeBatchCall(const std::vector& calls, TWSS58AddressType network); + bool encodeRawAccount() const; + Data encodeBalanceCall(const Proto::Balance& balance) const; + Data encodeTransfer(const Proto::Balance::Transfer& transfer) const; + Data encodeAssetTransfer(const Proto::Balance::AssetTransfer& transfer) const; + Data encodeStakingCall(const Proto::Staking& staking) const; + Data encodePolymeshCall(const Proto::PolymeshCall& polymesh) const; + Data encodeIdentityJoinIdentityAsKey(const Proto::Identity::JoinIdentityAsKey& joinIdentityAsKey) const; + Data encodeIdentityAddAuthorization(const Proto::Identity::AddAuthorization& addAuthorization) const; + Data encodeBatchCall(const Proto::CallIndices& batchCallIndices, const std::vector& calls) const; Data encodeEraNonceTip() const; + + static Data encodeFeeAssetId(const Proto::SigningInput& input); }; } // namespace TW::Polkadot diff --git a/src/Polkadot/SS58Address.cpp b/src/Polkadot/SS58Address.cpp index 34a25f7c263..d73535352da 100644 --- a/src/Polkadot/SS58Address.cpp +++ b/src/Polkadot/SS58Address.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "SS58Address.h" @@ -10,7 +8,7 @@ using namespace TW; using namespace std; bool SS58Address::isValid(const std::string& string, uint32_t network) { - const auto decoded = Base58::bitcoin.decode(string); + const auto decoded = Base58::decode(string); byte decodedNetworkSize = 0; uint32_t decodedNetwork = 0; if (!decodeNetwork(decoded, decodedNetworkSize, decodedNetwork)) { @@ -34,7 +32,7 @@ bool SS58Address::isValid(const std::string& string, uint32_t network) { template Data SS58Address::computeChecksum(const T& data) { - auto prefix = Data(SS58Prefix.begin(), SS58Prefix.end()); + auto prefix = Data(gSS58Prefix.begin(), gSS58Prefix.end()); append(prefix, Data(data.begin(), data.end())); auto hash = Hash::blake2b(prefix, 64); auto checksum = Data(checksumSize); @@ -47,7 +45,7 @@ SS58Address::SS58Address(const std::string& string, uint32_t network) { if (!isValid(string, network)) { throw std::invalid_argument("Invalid address string"); } - const auto decoded = Base58::bitcoin.decode(string); + const auto decoded = Base58::decode(string); bytes.resize(decoded.size() - checksumSize); std::copy(decoded.begin(), decoded.end() - checksumSize, bytes.begin()); } @@ -68,7 +66,7 @@ std::string SS58Address::string() const { auto result = Data(bytes.begin(), bytes.end()); auto checksum = computeChecksum(bytes); append(result, checksum); - return Base58::bitcoin.encode(result); + return Base58::encode(result); } /// Returns public key bytes diff --git a/src/Polkadot/SS58Address.h b/src/Polkadot/SS58Address.h index 2d8846130da..44ad512874b 100644 --- a/src/Polkadot/SS58Address.h +++ b/src/Polkadot/SS58Address.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,7 +10,7 @@ #include -const std::string SS58Prefix = "SS58PRE"; +inline const std::string gSS58Prefix{"SS58PRE"}; namespace TW { diff --git a/src/Polkadot/ScaleCodec.h b/src/Polkadot/ScaleCodec.h index 6afbc285694..eb7dbadab12 100644 --- a/src/Polkadot/ScaleCodec.h +++ b/src/Polkadot/ScaleCodec.h @@ -1,29 +1,28 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../BinaryCoding.h" -#include "../Data.h" -#include "../PublicKey.h" +#include "BinaryCoding.h" +#include "Data.h" +#include "PublicKey.h" #include "SS58Address.h" -#include +#include "uint256.h" + #include #include #include - -/// Reference https://github.com/soramitsu/kagome/blob/master/core/scale/scale_encoder_stream.cpp -using CompactInteger = boost::multiprecision::cpp_int; +using CompactInteger = TW::uint256_t; namespace TW::Polkadot { static constexpr size_t kMinUint16 = (1ul << 6u); static constexpr size_t kMinUint32 = (1ul << 14u); static constexpr size_t kMinBigInteger = (1ul << 30u); +// max uint8 +static constexpr byte kMaxByte = 255; inline size_t countBytes(CompactInteger value) { if (0 == value) { @@ -39,6 +38,17 @@ inline size_t countBytes(CompactInteger value) { return size; } +inline Data encodeCallIndex(int32_t moduleIndex, int32_t methodIndex) { + if (moduleIndex > kMaxByte) { + throw std::invalid_argument("module index too large"); + } + if (methodIndex > kMaxByte) { + throw std::invalid_argument("method index too large"); + } + + return Data{static_cast(moduleIndex), static_cast(methodIndex)}; +} + inline Data encodeCompact(CompactInteger value) { auto data = Data{}; diff --git a/src/Polkadot/Signer.cpp b/src/Polkadot/Signer.cpp index c7f708adcc7..8107a1bf196 100644 --- a/src/Polkadot/Signer.cpp +++ b/src/Polkadot/Signer.cpp @@ -1,16 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "Extrinsic.h" #include "../Hash.h" #include "../PrivateKey.h" -using namespace TW; -using namespace TW::Polkadot; +namespace TW::Polkadot { static constexpr size_t hashTreshold = 256; @@ -30,3 +27,31 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept { protoOutput.set_encoded(encoded.data(), encoded.size()); return protoOutput; } + +Data Signer::signaturePreImage(const Proto::SigningInput &input) { + auto extrinsic = Extrinsic(input); + auto payload = extrinsic.encodePayload(); + // check if need to hash + if (payload.size() > hashTreshold) { + payload = Hash::blake2b(payload, 32); + } + return payload; +} + +Data Signer::encodeTransaction(const Proto::SigningInput &input, const Data &publicKey, const Data &signature) { + auto pbk = PublicKey(publicKey, TWPublicKeyTypeED25519); + auto extrinsic = Extrinsic(input); + auto encoded = extrinsic.encodeSignature(pbk, signature); + return encoded; +} + +Data Signer::hash(const Data &payload) { + // check if need to hash + if (payload.size() > hashTreshold) { + return Hash::blake2b(payload, 32); + } + + return payload; +} + +} // namespace TW::Polkadot diff --git a/src/Polkadot/Signer.h b/src/Polkadot/Signer.h index 4b790bd8b42..fa0204b2b01 100644 --- a/src/Polkadot/Signer.h +++ b/src/Polkadot/Signer.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/Polkadot.pb.h" @@ -16,10 +14,14 @@ namespace TW::Polkadot { class Signer { public: /// Hide default constructor - Signer() = delete; + explicit Signer(); /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + + static Data signaturePreImage(const Proto::SigningInput &input); + static Data encodeTransaction(const Proto::SigningInput &input, const Data &publicKey, const Data &signature); + static Data hash(const Data &payload); }; } // namespace TW::Polkadot diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index c7b71efa542..501dfc4124c 100644 --- a/src/PrivateKey.cpp +++ b/src/PrivateKey.cpp @@ -1,11 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "PrivateKey.h" +#include "HexCoding.h" #include "PublicKey.h" #include @@ -15,23 +14,53 @@ #include #include #include -#include #include #include +#include +#include #include using namespace TW; +Data rust_get_public_from_private(const Data& key, TWPublicKeyType public_type) { + auto* privkey = Rust::tw_private_key_create_with_data(key.data(), key.size()); + if (privkey == nullptr) { + return {}; + } + Data toReturn; + + auto* pubkey = Rust::tw_private_key_get_public_key_by_type(privkey, static_cast(public_type)); + if (pubkey == nullptr) { + Rust::tw_private_key_delete(privkey); + return {}; + } + + Rust::CByteArrayWrapper res = Rust::tw_public_key_data(pubkey); + + Rust::tw_public_key_delete(pubkey); + Rust::tw_private_key_delete(privkey); + return res.data; +} + +Data rust_private_key_sign(const Data& key, const Data& hash, TWCurve curve) { + auto* priv = Rust::tw_private_key_create_with_data(key.data(), key.size()); + if (priv == nullptr) { + return {}; + } + Rust::CByteArrayWrapper res = Rust::tw_private_key_sign(priv, hash.data(), hash.size(), static_cast(curve)); + Rust::tw_private_key_delete(priv); + return res.data; +} bool PrivateKey::isValid(const Data& data) { // Check length - if (data.size() != size && data.size() != doubleExtendedSize) { + if (data.size() != _size && data.size() != cardanoKeySize) { return false; } // Check for zero address - for (size_t i = 0; i < size; ++i) { + for (size_t i = 0; i < _size; ++i) { if (data[i] != 0) { return true; } @@ -40,17 +69,15 @@ bool PrivateKey::isValid(const Data& data) { return false; } -bool PrivateKey::isValid(const Data& data, TWCurve curve) -{ +bool PrivateKey::isValid(const Data& data, TWCurve curve) { // check size bool valid = isValid(data); if (!valid) { return false; } - const ecdsa_curve *ec_curve = nullptr; - switch (curve) - { + const ecdsa_curve* ec_curve = nullptr; + switch (curve) { case TWCurveSECP256k1: ec_curve = &secp256k1; break; @@ -59,7 +86,7 @@ bool PrivateKey::isValid(const Data& data, TWCurve curve) break; case TWCurveED25519: case TWCurveED25519Blake2bNano: - case TWCurveED25519Extended: + case TWCurveED25519ExtendedCardano: case TWCurveCurve25519: case TWCurveNone: default: @@ -78,6 +105,15 @@ bool PrivateKey::isValid(const Data& data, TWCurve curve) return true; } +TWPrivateKeyType PrivateKey::getType(TWCurve curve) noexcept { + switch (curve) { + case TWCurve::TWCurveED25519ExtendedCardano: + return TWPrivateKeyTypeCardano; + default: + return TWPrivateKeyTypeDefault; + } +} + PrivateKey::PrivateKey(const Data& data) { if (!isValid(data)) { throw std::invalid_argument("Invalid private key data"); @@ -88,8 +124,8 @@ PrivateKey::PrivateKey(const Data& data) { PrivateKey::PrivateKey( const Data& key1, const Data& extension1, const Data& chainCode1, const Data& key2, const Data& extension2, const Data& chainCode2) { - if (key1.size() != size || extension1.size() != size || chainCode1.size() != size || - key2.size() != size || extension2.size() != size || chainCode2.size() != size) { + if (key1.size() != _size || extension1.size() != _size || chainCode1.size() != _size || + key2.size() != _size || extension2.size() != _size || chainCode2.size() != _size) { throw std::invalid_argument("Invalid private key or extended key data"); } bytes = key1; @@ -127,54 +163,41 @@ PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const { result.resize(PublicKey::ed25519Size); ed25519_publickey_blake2b(key().data(), result.data()); break; - case TWPublicKeyTypeED25519Extended: - { - // must be double extended key - if (bytes.size() != doubleExtendedSize) { - throw std::invalid_argument("Invalid extended key"); - } - Data tempPub(64); - ed25519_publickey_ext(key().data(), extension().data(), tempPub.data()); - result = Data(); - append(result, subData(tempPub, 0, 32)); - // copy chainCode - append(result, chainCode()); - - // second key - ed25519_publickey_ext(secondKey().data(), secondExtension().data(), tempPub.data()); - append(result, subData(tempPub, 0, 32)); - append(result, secondChainCode()); + case TWPublicKeyTypeED25519Cardano: { + // must be double extended key + if (bytes.size() != cardanoKeySize) { + throw std::invalid_argument("Invalid extended key"); } - break; + Data pubKey(PublicKey::ed25519Size); + + // first key + ed25519_publickey_ext(key().data(), pubKey.data()); + append(result, pubKey); + // copy chainCode + append(result, chainCode()); + + // second key + ed25519_publickey_ext(secondKey().data(), pubKey.data()); + append(result, pubKey); + append(result, secondChainCode()); + } break; - case TWPublicKeyTypeCURVE25519: + case TWPublicKeyTypeCURVE25519: { result.resize(PublicKey::ed25519Size); PublicKey ed25519PublicKey = getPublicKey(TWPublicKeyTypeED25519); ed25519_pk_to_curve25519(result.data(), ed25519PublicKey.bytes.data()); break; } - return PublicKey(result, type); -} -Data PrivateKey::getSharedKey(const PublicKey& pubKey, TWCurve curve) const { - if (curve != TWCurveSECP256k1) { - return {}; + case TWPublicKeyTypeStarkex: { + result = rust_get_public_from_private(this->bytes, type); + break; } - - Data result(PublicKey::secp256k1ExtendedSize); - bool success = ecdh_multiply(&secp256k1, key().data(), - pubKey.bytes.data(), result.data()) == 0; - - if (success) { - PublicKey sharedKey(result, TWPublicKeyTypeSECP256k1Extended); - auto hash = Hash::sha256(sharedKey.compressed().bytes); - return hash; } - - return {}; + return PublicKey(result, type); } -int ecdsa_sign_digest_checked(const ecdsa_curve *curve, const uint8_t *priv_key, const uint8_t *digest, size_t digest_size, uint8_t *sig, uint8_t *pby, int (*is_canonical)(uint8_t by, uint8_t sig[64])) { +int ecdsa_sign_digest_checked(const ecdsa_curve* curve, const uint8_t* priv_key, const uint8_t* digest, size_t digest_size, uint8_t* sig, uint8_t* pby, int (*is_canonical)(uint8_t by, uint8_t sig[64])) { if (digest_size < 32) { return -1; } @@ -186,47 +209,44 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve) const { Data result; bool success = false; switch (curve) { - case TWCurveSECP256k1: { - result.resize(65); - success = ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), digest.size(), result.data(), - result.data() + 64, nullptr) == 0; - } break; - case TWCurveED25519: { - result.resize(64); - const auto publicKey = getPublicKey(TWPublicKeyTypeED25519); - ed25519_sign(digest.data(), digest.size(), key().data(), publicKey.bytes.data(), result.data()); - success = true; - } break; - case TWCurveED25519Blake2bNano: { - result.resize(64); - const auto publicKey = getPublicKey(TWPublicKeyTypeED25519Blake2b); - ed25519_sign_blake2b(digest.data(), digest.size(), key().data(), - publicKey.bytes.data(), result.data()); - success = true; - } break; - case TWCurveED25519Extended: { - result.resize(64); - const auto publicKey = getPublicKey(TWPublicKeyTypeED25519Extended); - ed25519_sign_ext(digest.data(), digest.size(), key().data(), extension().data(), publicKey.bytes.data(), result.data()); - success = true; - } break; - case TWCurveCurve25519: { - result.resize(64); - const auto publicKey = getPublicKey(TWPublicKeyTypeED25519); - ed25519_sign(digest.data(), digest.size(), key().data(), publicKey.bytes.data(), - result.data()); - const auto sign_bit = publicKey.bytes[31] & 0x80; - result[63] = result[63] & 127; - result[63] |= sign_bit; - success = true; - } break; - case TWCurveNIST256p1: { - result.resize(65); - success = ecdsa_sign_digest_checked(&nist256p1, key().data(), digest.data(), digest.size(), result.data(), - result.data() + 64, nullptr) == 0; + case TWCurveSECP256k1: { + result.resize(65); + success = ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), digest.size(), result.data(), result.data() + 64, nullptr) == 0; + } break; + case TWCurveED25519: { + result.resize(64); + ed25519_sign(digest.data(), digest.size(), key().data(), result.data()); + success = true; + } break; + case TWCurveED25519Blake2bNano: { + result.resize(64); + ed25519_sign_blake2b(digest.data(), digest.size(), key().data(), result.data()); + success = true; + } break; + case TWCurveED25519ExtendedCardano: { + result.resize(64); + ed25519_sign_ext(digest.data(), digest.size(), key().data(), extension().data(), result.data()); + success = true; + } break; + case TWCurveCurve25519: { + result.resize(64); + const auto publicKey = getPublicKey(TWPublicKeyTypeED25519); + ed25519_sign(digest.data(), digest.size(), key().data(), result.data()); + const auto sign_bit = publicKey.bytes[31] & 0x80; + result[63] = result[63] & 127; + result[63] |= sign_bit; + success = true; + } break; + case TWCurveNIST256p1: { + result.resize(65); + success = ecdsa_sign_digest_checked(&nist256p1, key().data(), digest.data(), digest.size(), result.data(), result.data() + 64, nullptr) == 0; + } break; + case TWCurveStarkex: { + result = rust_private_key_sign(key(), digest, curve); + success = result.size() == 64; } break; case TWCurveNone: - default: + default: break; } @@ -236,24 +256,22 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve) const { return result; } -Data PrivateKey::sign(const Data& digest, TWCurve curve, int(*canonicalChecker)(uint8_t by, uint8_t sig[64])) const { +Data PrivateKey::sign(const Data& digest, TWCurve curve, int (*canonicalChecker)(uint8_t by, uint8_t sig[64])) const { Data result; bool success = false; switch (curve) { case TWCurveSECP256k1: { result.resize(65); - success = ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), digest.size(), result.data() + 1, - result.data(), canonicalChecker) == 0; + success = ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), digest.size(), result.data() + 1, result.data(), canonicalChecker) == 0; } break; - case TWCurveED25519: // not supported - case TWCurveED25519Blake2bNano: // not supported - case TWCurveED25519Extended: // not supported - case TWCurveCurve25519: // not supported + case TWCurveED25519: // not supported + case TWCurveED25519Blake2bNano: // not supported + case TWCurveED25519ExtendedCardano: // not supported + case TWCurveCurve25519: // not supported break; case TWCurveNIST256p1: { result.resize(65); - success = ecdsa_sign_digest_checked(&nist256p1, key().data(), digest.data(), digest.size(), result.data() + 1, - result.data(), canonicalChecker) == 0; + success = ecdsa_sign_digest_checked(&nist256p1, key().data(), digest.data(), digest.size(), result.data() + 1, result.data(), canonicalChecker) == 0; } break; case TWCurveNone: default: @@ -269,7 +287,7 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve, int(*canonicalChecker)( return result; } -Data PrivateKey::signAsDER(const Data& digest, TWCurve curve) const { +Data PrivateKey::signAsDER(const Data& digest) const { Data sig(64); bool success = ecdsa_sign_digest(&secp256k1, key().data(), digest.data(), sig.data(), nullptr, nullptr) == 0; @@ -285,24 +303,9 @@ Data PrivateKey::signAsDER(const Data& digest, TWCurve curve) const { return result; } -Data PrivateKey::signSchnorr(const Data& message, TWCurve curve) const { - bool success = false; +Data PrivateKey::signZilliqa(const Data& message) const { Data sig(64); - switch (curve) { - case TWCurveSECP256k1: { - success = zil_schnorr_sign(&secp256k1, key().data(), message.data(), static_cast(message.size()), sig.data()) == 0; - } break; - - case TWCurveNIST256p1: - case TWCurveED25519: - case TWCurveED25519Blake2bNano: - case TWCurveED25519Extended: - case TWCurveCurve25519: - case TWCurveNone: - default: - // not support - break; - } + bool success = zil_schnorr_sign(&secp256k1, key().data(), message.data(), static_cast(message.size()), sig.data()) == 0; if (!success) { return {}; @@ -311,5 +314,5 @@ Data PrivateKey::signSchnorr(const Data& message, TWCurve curve) const { } void PrivateKey::cleanup() { - std::fill(bytes.begin(), bytes.end(), 0); + memzero(bytes.data(), bytes.size()); } diff --git a/src/PrivateKey.h b/src/PrivateKey.h index 207c51a4a23..c6ab3ddd8dc 100644 --- a/src/PrivateKey.h +++ b/src/PrivateKey.h @@ -1,14 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Data.h" #include "PublicKey.h" +#include #include namespace TW { @@ -16,13 +15,13 @@ namespace TW { class PrivateKey { public: /// The number of bytes in a private key. - static const size_t size = 32; - /// The number of bytes in a double extended key (used by Cardano) - static const size_t doubleExtendedSize = 2 * 3 * 32; + static const size_t _size = 32; + /// The number of bytes in a Cardano key (two extended ed25519 keys + chain code) + static const size_t cardanoKeySize = 2 * 3 * 32; /// The private key bytes: /// - common case: 'size' bytes - /// - double extended case: 'doubleExtendedSize' bytes, key+extension+chainCode+second+secondExtension+secondChainCode + /// - double extended case: 'cardanoKeySize' bytes, key+extension+chainCode+second+secondExtension+secondChainCode Data bytes; /// Optional members for extended keys and second extended keys @@ -39,13 +38,16 @@ class PrivateKey { /// Determines if a collection of bytes and curve make a valid private key. static bool isValid(const Data& data, TWCurve curve); + // obtain private key type used by the curve/coin + static TWPrivateKeyType getType(TWCurve curve) noexcept; + /// Initializes a private key with an array of bytes. Size must be exact (normally 32, or 192 for extended) explicit PrivateKey(const Data& data); /// Initializes a private key from a string of bytes. explicit PrivateKey(const std::string& data) : PrivateKey(TW::data(data)) {} - /// Initializes a double extended private key with two extended keys + /// Initializes a Cardano style key explicit PrivateKey( const Data& bytes1, const Data& extension1, const Data& chainCode1, const Data& bytes2, const Data& extension2, const Data& chainCode2); @@ -61,10 +63,6 @@ class PrivateKey { /// Returns the public key for this private key. PublicKey getPublicKey(enum TWPublicKeyType type) const; - /// Computes an EC Diffie-Hellman secret in constant time - /// Supported curves: secp256k1 - Data getSharedKey(const PublicKey& publicKey, TWCurve curve) const; - /// Signs a digest using the given ECDSA curve. Data sign(const Data& digest, TWCurve curve) const; @@ -74,22 +72,15 @@ class PrivateKey { /// Signs a digest using the given ECDSA curve. The result is encoded with /// DER. - Data signAsDER(const Data& digest, TWCurve curve) const; + Data signAsDER(const Data& digest) const; - /// Signs a digest using given ECDSA curve, returns schnorr signature - Data signSchnorr(const Data& message, TWCurve curve) const; + /// Signs a digest using given ECDSA curve, returns Zilliqa schnorr signature + Data signZilliqa(const Data& message) const; /// Cleanup contents (fill with 0s), called before destruction void cleanup(); }; -inline bool operator==(const PrivateKey& lhs, const PrivateKey& rhs) { - return lhs.bytes == rhs.bytes; -} -inline bool operator!=(const PrivateKey& lhs, const PrivateKey& rhs) { - return lhs.bytes != rhs.bytes; -} - } // namespace TW /// Wrapper for C interface. diff --git a/src/PublicKey.cpp b/src/PublicKey.cpp index 70d4e0cb20d..d9789e6bc06 100644 --- a/src/PublicKey.cpp +++ b/src/PublicKey.cpp @@ -1,18 +1,20 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "PublicKey.h" +#include "PrivateKey.h" #include "Data.h" +#include "rust/bindgen/WalletCoreRSBindgen.h" #include #include +#include #include #include #include -#include +#include +#include #include @@ -31,14 +33,16 @@ bool PublicKey::isValid(const Data& data, enum TWPublicKeyType type) { case TWPublicKeyTypeCURVE25519: case TWPublicKeyTypeED25519Blake2b: return size == ed25519Size; - case TWPublicKeyTypeED25519Extended: - return size == ed25519DoubleExtendedSize; + case TWPublicKeyTypeED25519Cardano: + return size == cardanoKeySize; case TWPublicKeyTypeSECP256k1: case TWPublicKeyTypeNIST256p1: return size == secp256k1Size && (data[0] == 0x02 || data[0] == 0x03); case TWPublicKeyTypeSECP256k1Extended: case TWPublicKeyTypeNIST256p1Extended: return size == secp256k1ExtendedSize && data[0] == 0x04; + case TWPublicKeyTypeStarkex: + return size == starkexSize; default: return false; } @@ -46,12 +50,14 @@ bool PublicKey::isValid(const Data& data, enum TWPublicKeyType type) { /// Initializes a public key with a collection of bytes. /// -/// @throws std::invalid_argument if the data is not a valid public key. -PublicKey::PublicKey(const Data& data, enum TWPublicKeyType type) : type(type) { +/// \throws std::invalid_argument if the data is not a valid public key. +PublicKey::PublicKey(const Data& data, enum TWPublicKeyType type) + : type(type) { if (!isValid(data, type)) { throw std::invalid_argument("Invalid public key data"); } switch (type) { + case TWPublicKeyTypeStarkex: case TWPublicKeyTypeSECP256k1: case TWPublicKeyTypeNIST256p1: case TWPublicKeyTypeSECP256k1Extended: @@ -74,8 +80,8 @@ PublicKey::PublicKey(const Data& data, enum TWPublicKeyType type) : type(type) { assert(data.size() == ed25519Size); // ensured by isValid() above std::copy(std::begin(data), std::end(data), std::back_inserter(bytes)); break; - case TWPublicKeyTypeED25519Extended: - bytes.reserve(ed25519DoubleExtendedSize); + case TWPublicKeyTypeED25519Cardano: + bytes.reserve(cardanoKeySize); std::copy(std::begin(data), std::end(data), std::back_inserter(bytes)); } } @@ -118,11 +124,23 @@ PublicKey PublicKey::extended() const { case TWPublicKeyTypeED25519: case TWPublicKeyTypeCURVE25519: case TWPublicKeyTypeED25519Blake2b: - case TWPublicKeyTypeED25519Extended: - return *this; + case TWPublicKeyTypeED25519Cardano: + return *this; + default: + return *this; } } +bool rust_public_key_verify(const Data& key, TWPublicKeyType type, const Data& sig, const Data& msgHash) { + auto* pubkey = Rust::tw_public_key_create_with_data(key.data(), key.size(), static_cast(type)); + if (pubkey == nullptr) { + return {}; + } + bool verified = Rust::tw_public_key_verify(pubkey, sig.data(), sig.size(), msgHash.data(), msgHash.size()); + Rust::tw_public_key_delete(pubkey); + return verified; +} + bool PublicKey::verify(const Data& signature, const Data& message) const { switch (type) { case TWPublicKeyTypeSECP256k1: @@ -135,10 +153,11 @@ bool PublicKey::verify(const Data& signature, const Data& message) const { return ed25519_sign_open(message.data(), message.size(), bytes.data(), signature.data()) == 0; case TWPublicKeyTypeED25519Blake2b: return ed25519_sign_open_blake2b(message.data(), message.size(), bytes.data(), signature.data()) == 0; - case TWPublicKeyTypeED25519Extended: - throw std::logic_error("Not yet implemented"); - //ed25519_sign_open(message.data(), message.size(), bytes.data(), signature.data()) == 0; - case TWPublicKeyTypeCURVE25519: + case TWPublicKeyTypeED25519Cardano: { + const auto key = subData(bytes, 0, ed25519Size); + return ed25519_sign_open(message.data(), message.size(), key.data(), signature.data()) == 0; + } + case TWPublicKeyTypeCURVE25519: { auto ed25519PublicKey = Data(); ed25519PublicKey.resize(PublicKey::ed25519Size); curve25519_pk_to_ed25519(ed25519PublicKey.data(), bytes.data()); @@ -150,30 +169,33 @@ bool PublicKey::verify(const Data& signature, const Data& message) const { auto verifyBuffer = Data(); append(verifyBuffer, signature); verifyBuffer[63] &= 127; - return ed25519_sign_open(message.data(), message.size(), ed25519PublicKey.data(), - verifyBuffer.data()) == 0; + return ed25519_sign_open(message.data(), message.size(), ed25519PublicKey.data(), verifyBuffer.data()) == 0; + } + case TWPublicKeyTypeStarkex: + return rust_public_key_verify(bytes, type, signature, message); + default: + throw std::logic_error("Not yet implemented"); } } bool PublicKey::verifyAsDER(const Data& signature, const Data& message) const { switch (type) { case TWPublicKeyTypeSECP256k1: - case TWPublicKeyTypeSECP256k1Extended: - { - Data sig(64); - int ret = ecdsa_sig_from_der(signature.data(), signature.size(), sig.data()); - if (ret) { - return false; - } - return ecdsa_verify_digest(&secp256k1, bytes.data(), sig.data(), message.data()) == 0; + case TWPublicKeyTypeSECP256k1Extended: { + Data sig(64); + int ret = ecdsa_sig_from_der(signature.data(), signature.size(), sig.data()); + if (ret) { + return false; } + return ecdsa_verify_digest(&secp256k1, bytes.data(), sig.data(), message.data()) == 0; + } default: return false; } } -bool PublicKey::verifySchnorr(const Data& signature, const Data& message) const { +bool PublicKey::verifyZilliqa(const Data& signature, const Data& message) const { switch (type) { case TWPublicKeyTypeSECP256k1: case TWPublicKeyTypeSECP256k1Extended: @@ -182,7 +204,7 @@ bool PublicKey::verifySchnorr(const Data& signature, const Data& message) const case TWPublicKeyTypeNIST256p1Extended: case TWPublicKeyTypeED25519: case TWPublicKeyTypeED25519Blake2b: - case TWPublicKeyTypeED25519Extended: + case TWPublicKeyTypeED25519Cardano: case TWPublicKeyTypeCURVE25519: default: return false; @@ -200,22 +222,35 @@ Data PublicKey::hash(const Data& prefix, Hash::Hasher hasher, bool skipTypeByte) return result; } -PublicKey PublicKey::recover(const Data& signature, const Data& message) { - if (signature.size() < 65) { +PublicKey PublicKey::recoverRaw(const Data& signatureRS, byte recId, const Data& messageDigest) { + if (signatureRS.size() < 2 * PrivateKey::_size) { throw std::invalid_argument("signature too short"); } - auto v = signature[64]; - // handle EIP155 Eth encoding of V, of the form 27+v, or 35+chainID*2+v - if (v >= 27) { - v = !(v & 0x01); + if (recId >= 4) { + throw std::invalid_argument("Invalid recId (>=4)"); + } + if (messageDigest.size() < PrivateKey::_size) { + throw std::invalid_argument("digest too short"); } - TW::Data result(65); - if (ecdsa_recover_pub_from_sig(&secp256k1, result.data(), signature.data(), message.data(), v) != 0) { - throw std::invalid_argument("recover failed"); + TW::Data result(secp256k1SignatureSize); + if (auto ret = ecdsa_recover_pub_from_sig(&secp256k1, result.data(), signatureRS.data(), messageDigest.data(), recId); ret != 0) { + throw std::invalid_argument("recover failed " + std::to_string(ret)); } return PublicKey(result, TWPublicKeyTypeSECP256k1Extended); } +PublicKey PublicKey::recover(const Data& signature, const Data& messageDigest) { + if (signature.size() < secp256k1SignatureSize) { + throw std::invalid_argument("signature too short"); + } + auto v = signature[secp256k1SignatureSize - 1]; + // handle EIP155 Eth encoding of V, of the form 27+v, or 35+chainID*2+v + if (v >= PublicKey::SignatureVOffset) { + v = !(v & 0x01); + } + return recoverRaw(signature, v, messageDigest); +} + bool PublicKey::isValidED25519() const { if (type != TWPublicKeyTypeED25519) { return false; diff --git a/src/PublicKey.h b/src/PublicKey.h index 9c4e2ac07dc..6a1e3d17855 100644 --- a/src/PublicKey.h +++ b/src/PublicKey.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -21,14 +19,24 @@ class PublicKey { /// The number of bytes in a secp256k1 and nist256p1 public key. static const size_t secp256k1Size = 33; - /// The number of bytes in a ed25519 public key. + /// The number of bytes in an ed25519 public key. static const size_t ed25519Size = 32; - static const size_t ed25519DoubleExtendedSize = 2 * 2 * 32; + /// The number of bytes in an starkex public key. + static const size_t starkexSize = 32; + + /// The number of bytes in a Cardano public key (two ed25519 public key + chain code). + static const size_t cardanoKeySize = 2 * 2 * 32; /// The number of bytes in a secp256k1 and nist256p1 extended public key. static const size_t secp256k1ExtendedSize = 65; + /// The number of bytes in a secp256k1 signature. + static const size_t secp256k1SignatureSize = 65; + + /// Magic number used in V compnent encoding + static const byte SignatureVOffset = 27; + /// The public key bytes. Data bytes; @@ -44,7 +52,7 @@ class PublicKey { /// Initializes a public key with a collection of bytes. /// - /// @throws std::invalid_argument if the data is not a valid public key. + /// \throws std::invalid_argument if the data is not a valid public key. explicit PublicKey(const Data& data, enum TWPublicKeyType type); /// Determines if this is a compressed public key. @@ -64,8 +72,8 @@ class PublicKey { /// Verifies a signature in DER format. bool verifyAsDER(const Data& signature, const Data& message) const; - /// Verifies a schnorr signature for the provided message. - bool verifySchnorr(const Data& signature, const Data& message) const; + /// Verifies a Zilliqa schnorr signature for the provided message. + bool verifyZilliqa(const Data& signature, const Data& message) const; /// Computes the public key hash. /// @@ -73,8 +81,19 @@ class PublicKey { /// bytes and then prepending the prefix. Data hash(const Data& prefix, Hash::Hasher hasher = Hash::HasherSha256ripemd, bool skipTypeByte = false) const; + /// Recover public key (SECP256k1Extended) from signature R, S, V values + /// signatureRS: 2x32 bytes with the R and S values + /// recId: the recovery ID, a.k.a. V value, 0 <= v < 4 + /// messageDigest: message digest (hash) to be signed + /// Throws on invalid data. + static PublicKey recoverRaw(const Data& signatureRS, byte recId, const Data& messageDigest); + /// Recover public key from signature (SECP256k1Extended) - static PublicKey recover(const Data& signature, const Data& message); + /// signature: 65-byte signature (R, S, and V). V can have higher value bits, as used by Ethereum (for values over 27 the negated last bit is taken). + /// messageDigest: message digest (hash) to be signed + /// Throws on invalid data. + /// Naming is kept for backwards compatibility. + static PublicKey recover(const Data& signature, const Data& messageDigest); /// Check if this key makes a valid ED25519 key (it is on the curve) bool isValidED25519() const; diff --git a/src/Result.h b/src/Result.h index c7faede9896..456e1b2feac 100644 --- a/src/Result.h +++ b/src/Result.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -42,7 +40,7 @@ struct Result { using Storage = typename std::aligned_storage::type; /// Wether the operation succeeded. - bool success_; + bool success_{false}; Storage storage_; public: @@ -73,6 +71,7 @@ struct Result { } else { new (&storage_) E(other.get()); } + return *this; } Result(Result&& other) { @@ -96,6 +95,7 @@ struct Result { } else { new (&storage_) E(std::move(other.get())); } + return *this; } ~Result() { @@ -120,10 +120,10 @@ struct Result { E error() const { return get(); } /// Returns a new success result with the given payloadd. - static Result success(T&& val) { return Result(Types::Success(std::forward(val))); } + static Result success(T&& val) { return Result(Types::Success(std::move(val))); } /// Returns a new failure result with the given error. - static Result failure(E&& val) { return Result(Types::Failure(std::forward(val))); } + static Result failure(E&& val) { return Result(Types::Failure(std::move(val))); } static Result failure(E& val) { return Result(Types::Failure(val)); } @@ -150,7 +150,7 @@ struct Result { public: /// Initializes a success result with a payload. - Result(Types::Success payload) : success_(true), error_() {} + Result([[maybe_unused]] Types::Success payload) : success_(true), error_() {} /// Initializes a failure result. Result(Types::Failure error) : success_(false), error_(error.val) {} @@ -169,7 +169,7 @@ struct Result { /// Returns a new failure result with the given error. static Result failure(E&& val) { - return Result(Types::Failure(std::forward(val))); + return Result(Types::Failure(std::move(val))); } operator bool() const { return success_; } diff --git a/src/Ripple/Address.cpp b/src/Ripple/Address.cpp deleted file mode 100644 index 4f4a601f775..00000000000 --- a/src/Ripple/Address.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Address.h" -#include "../Base58.h" -#include - -using namespace TW::Ripple; - -bool Address::isValid(const std::string& string) { - const auto decoded = Base58::ripple.decodeCheck(string); - if (decoded.size() != Address::size) { - return false; - } - return true; -} - -Address::Address(const std::string& string) { - const auto decoded = Base58::ripple.decodeCheck(string); - if (decoded.size() != Address::size) { - throw std::invalid_argument("Invalid address string"); - } - std::copy(decoded.begin(), decoded.end(), bytes.begin()); -} - -Address::Address(const PublicKey& publicKey) { - /// see type prefix: https://developers.ripple.com/base58-encodings.html - bytes[0] = 0x00; - ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, bytes.data() + 1); -} - -std::string Address::string() const { - return Base58::ripple.encodeCheck(bytes); -} diff --git a/src/Ripple/Address.h b/src/Ripple/Address.h deleted file mode 100644 index 5aa88a4c168..00000000000 --- a/src/Ripple/Address.h +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "../Data.h" -#include "../PublicKey.h" - -#include - -namespace TW::Ripple { - -class Address { - public: - /// Number of bytes in an address. - static const size_t size = 21; - - /// Address data consisting of a prefix byte followed by the public key hash - std::array bytes; - - /// Determines whether a string makes a valid address. - static bool isValid(const std::string& string); - - /// Initializes a Ripple address with a string representation. - explicit Address(const std::string& string); - - /// Initializes a Ripple address with a public key. - explicit Address(const PublicKey& publicKey); - - /// Returns a string representation of the address. - std::string string() const; -}; - -inline bool operator==(const Address& lhs, const Address& rhs) { - return lhs.bytes == rhs.bytes; -} - -} // namespace TW::Ripple diff --git a/src/Ripple/BinaryCoding.h b/src/Ripple/BinaryCoding.h deleted file mode 100644 index d606116d6c2..00000000000 --- a/src/Ripple/BinaryCoding.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include -#include -#include - -namespace TW::Ripple { - -enum class FieldType; - -/// Encodes a field type. -inline void encodeType(FieldType type, int key, std::vector& data) { - const auto typeValue = static_cast(type); - if (key <= 0xf) { - data.push_back(static_cast((typeValue << 4) | key)); - } else { - data.push_back(static_cast(typeValue << 4)); - data.push_back(static_cast(key)); - } -} - -/// Encodes a variable length. -inline void encodeVariableLength(size_t length, std::vector& data) { - if (length <= 192) { - data.push_back(static_cast(length)); - } else if (length <= 12480) { - length -= 193; - data.push_back(static_cast(length >> 8)); - data.push_back(static_cast(length & 0xff)); - } else if (length <= 918744) { - length -= 12481; - data.push_back(static_cast(length >> 16)); - data.push_back(static_cast((length >> 8) & 0xff)); - data.push_back(static_cast(length & 0xff)); - } -} - -/// Encodes a variable length bytes. -inline void encodeBytes(std::vector bytes, std::vector& data) { - encodeVariableLength(bytes.size(), data); - data.insert(data.end(), bytes.begin(), bytes.end()); -} - -} // namespace TW::Ripple diff --git a/src/Ripple/Entry.cpp b/src/Ripple/Entry.cpp deleted file mode 100644 index a2948f7073e..00000000000 --- a/src/Ripple/Entry.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Entry.h" - -#include "Address.h" -#include "XAddress.h" -#include "Signer.h" - -using namespace TW::Ripple; -using namespace std; - -// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. - -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { - return Address::isValid(address) || XAddress::isValid(address); -} - -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { - return Address(publicKey).string(); -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); -} diff --git a/src/Ripple/Entry.h b/src/Ripple/Entry.h deleted file mode 100644 index 445f108d3f9..00000000000 --- a/src/Ripple/Entry.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "../CoinEntry.h" - -namespace TW::Ripple { - -/// Entry point for implementation of Ripple (XRP) coin. -/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { -public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; -}; - -} // namespace TW::Ripple diff --git a/src/Ripple/Signer.cpp b/src/Ripple/Signer.cpp deleted file mode 100644 index 38b83c4d926..00000000000 --- a/src/Ripple/Signer.cpp +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Signer.h" -#include "../BinaryCoding.h" -#include "../Hash.h" -#include - -using namespace TW; -using namespace TW::Ripple; - -static const int64_t fullyCanonical = 0x80000000; - -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto output = Proto::SigningOutput(); - - const int64_t tag = input.destination_tag(); - if (tag > std::numeric_limits::max() || tag < 0) { - output.set_error(Common::Proto::SigningError::Error_invalid_memo); - return output; - } - - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto transaction = Transaction( - /* amount */input.amount(), - /* fee */input.fee(), - /* flags */input.flags(), - /* sequence */input.sequence(), - /* last_ledger_sequence */input.last_ledger_sequence(), - /* account */Address(input.account()), - /* destination */input.destination(), - /* destination_tag*/tag - ); - - auto signer = Signer(); - signer.sign(key, transaction); - - auto encoded = transaction.serialize(); - output.set_encoded(encoded.data(), encoded.size()); - return output; -} - -void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) const noexcept { - /// See https://github.com/trezor/trezor-core/blob/master/src/apps/ripple/sign_tx.py#L59 - transaction.flags |= fullyCanonical; - transaction.pub_key = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1).bytes; - - auto unsignedTx = transaction.getPreImage(); - auto hash = Hash::sha512(unsignedTx); - auto half = Data(hash.begin(), hash.begin() + 32); - - transaction.signature = privateKey.signAsDER(half, TWCurveSECP256k1); -} diff --git a/src/Ripple/Signer.h b/src/Ripple/Signer.h deleted file mode 100644 index 9674fdb5cd2..00000000000 --- a/src/Ripple/Signer.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Transaction.h" -#include "../Data.h" -#include "../Hash.h" -#include "../PrivateKey.h" - -namespace TW::Ripple { - -/// Helper class that performs Ripple transaction signing. -class Signer { - public: - /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; - - /// Signs the given transaction. - void sign(const PrivateKey& privateKey, Transaction& transaction) const noexcept; -}; - -} // namespace TW::Ripple diff --git a/src/Ripple/Transaction.cpp b/src/Ripple/Transaction.cpp deleted file mode 100644 index 112e3721358..00000000000 --- a/src/Ripple/Transaction.cpp +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "BinaryCoding.h" -#include "Transaction.h" -#include "../BinaryCoding.h" -#include "../HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Ripple; - -const int NETWORK_PREFIX = 0x53545800; - -Data Transaction::serialize() const { - auto data = Data(); - /// field must be sorted by field type then by field name - /// "type" - encodeType(FieldType::int16, 2, data); - encode16BE(uint16_t(TransactionType::payment), data); - /// "flags" - encodeType(FieldType::int32, 2, data); - encode32BE(static_cast(flags), data); - /// "sequence" - encodeType(FieldType::int32, 4, data); - encode32BE(sequence, data); - /// "destinationTag" - if (encode_tag) { - encodeType(FieldType::int32, 14, data); - encode32BE(static_cast(destination_tag), data); - } - /// "lastLedgerSequence" - if (last_ledger_sequence > 0) { - encodeType(FieldType::int32, 27, data); - encode32BE(last_ledger_sequence, data); - } - /// "amount" - encodeType(FieldType::amount, 1, data); - append(data, serializeAmount(amount)); - /// "fee" - encodeType(FieldType::amount, 8, data); - append(data, serializeAmount(fee)); - /// "signingPubKey" - if (!pub_key.empty()) { - encodeType(FieldType::vl, 3, data); - encodeBytes(pub_key, data); - } - /// "txnSignature" - if (!signature.empty()) { - encodeType(FieldType::vl, 4, data); - encodeBytes(signature, data); - } - /// "account" - encodeType(FieldType::account, 1, data); - encodeBytes(serializeAddress(account), data); - /// "destination" - encodeType(FieldType::account, 3, data); - encodeBytes(destination, data); - return data; -} - -Data Transaction::getPreImage() const { - auto preImage = Data(); - encode32BE(NETWORK_PREFIX, preImage); - append(preImage, serialize()); - return preImage; -} - -Data Transaction::serializeAmount(int64_t amount) { - if (amount < 0) { - return Data(); - } - auto data = Data(); - encode64BE(uint64_t(amount), data); - /// clear first bit to indicate XRP - data[0] &= 0x7F; - /// set second bit to indicate positive number - data[0] |= 0x40; - return data; -} - -Data Transaction::serializeAddress(Address address) { - auto data = Data(20); - if (!address.bytes.empty()) { - std::copy(&address.bytes[0] + 1, &address.bytes[0] + std::min(address.bytes.size(), size_t(21)), &data[0]); - } - return data; -} diff --git a/src/Ripple/Transaction.h b/src/Ripple/Transaction.h deleted file mode 100644 index e718f319de4..00000000000 --- a/src/Ripple/Transaction.h +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Address.h" -#include "XAddress.h" -#include "../Data.h" -#include "../proto/Ripple.pb.h" - -namespace TW::Ripple { - -enum class FieldType: int { - int16 = 1, - int32 = 2, - amount = 6, - vl = 7, - account = 8 -}; - -enum class TransactionType { payment = 0 }; - -class Transaction { - /// We only support transaction types other than the Payment transaction. - /// Non-XRP currencies are not supported. Float and negative amounts are not supported. - /// See https://github.com/trezor/trezor-core/tree/master/src/apps/ripple#transactions - public: - int64_t amount; - int64_t fee; - int64_t flags; - int32_t sequence; - int32_t last_ledger_sequence; - Address account; - Data destination; - bool encode_tag; - int64_t destination_tag; - Data pub_key; - Data signature; - - Transaction(int64_t amount, int64_t fee, int64_t flags, int32_t sequence, - int32_t last_ledger_sequence, Address account, const std::string& destination, - int64_t destination_tag) - : amount(amount) - , fee(fee) - , flags(flags) - , sequence(sequence) - , last_ledger_sequence(last_ledger_sequence) - , account(account) { - try { - auto address = Address(destination); - encode_tag = destination_tag > 0; - this->destination_tag = destination_tag; - this->destination = Data(address.bytes.begin() + 1, address.bytes.end()); - } catch(const std::exception& e) { - auto xAddress = XAddress(destination); - encode_tag = xAddress.flag != TagFlag::none; - this->destination_tag = xAddress.tag; - this->destination = Data(xAddress.bytes.begin(), xAddress.bytes.end()); - } - } - - public: - /// simplified serialization format tailored for Payment transaction type - /// exclusively. - Data serialize() const; - Data getPreImage() const; - - static Data serializeAmount(int64_t amount); - static Data serializeAddress(Address address); -}; - -} // namespace TW::Ripple diff --git a/src/Ripple/XAddress.cpp b/src/Ripple/XAddress.cpp deleted file mode 100644 index b6f522d7319..00000000000 --- a/src/Ripple/XAddress.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "XAddress.h" - -#include "../Base58.h" -#include "../BinaryCoding.h" -#include "../Data.h" -#include - -using namespace TW; -using namespace TW::Ripple; - -const Data prefixMainnet = {0x05, 0x44}; - -bool XAddress::isValid(const std::string& string) { - const auto decoded = Base58::ripple.decodeCheck(string); - if (decoded.size() != XAddress::size) { - return false; - } - if(!std::equal(decoded.begin(), decoded.begin() + 2, prefixMainnet.begin())) { - return false; - } - if (!(decoded[22] == byte(TagFlag::none) || decoded[22] == byte(TagFlag::classic))) { - return false; - } - return true; -} - -XAddress::XAddress(const std::string& string) { - if (!XAddress::isValid(string)) { - throw std::invalid_argument("Invalid address string"); - } - const auto decoded = Base58::ripple.decodeCheck(string); - std::copy(decoded.begin() + prefixMainnet.size(), decoded.begin() + prefixMainnet.size() + XAddress::keyHashSize, bytes.begin()); - if (decoded[22] == byte(TagFlag::classic)) { - tag = decode32LE(Data(decoded.end() - 8, decoded.end() - 4).data()); - } else if (decoded[22] == byte(TagFlag::none)) { - flag = TagFlag::none; - } else { - throw std::invalid_argument("Invalid flag"); - } -} - -XAddress::XAddress(const PublicKey& publicKey, const uint32_t destination): tag(destination) { - ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, bytes.data()); -} - -std::string XAddress::string() const { - /// see https://github.com/ripple/ripple-address-codec/blob/master/src/index.ts - /// base58check(2 bytes prefix + 20 bytes keyhash + 1 byte flag + 4 bytes + 32bit tag + 4 bytes reserved) - Data result; - append(result, prefixMainnet); - append(result, Data{bytes.begin(), bytes.end()}); - append(result, byte(flag)); - encode32LE(tag, result); - append(result, Data{0x00, 0x00, 0x00, 0x00}); - return Base58::ripple.encodeCheck(result); -} diff --git a/src/Ronin/Address.cpp b/src/Ronin/Address.cpp deleted file mode 100644 index 88f80d30597..00000000000 --- a/src/Ronin/Address.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Address.h" -#include "Ethereum/Address.h" -#include "Ethereum/AddressChecksum.h" -#include "../Hash.h" -#include "../HexCoding.h" - -const std::string prefix = "ronin:"; -using namespace TW::Ronin; - -bool Address::isValid(const std::string& string) { - // check prefix - if (string.compare(0, prefix.length(), prefix) == 0) { - const auto suffix = string.substr(prefix.length()); - const auto data = parse_hex(suffix); - return Ethereum::Address::isValid(data); - } - // accept Ethereum format as well - if (Ethereum::Address::isValid(string)) { - return true; - } - return false; -} - -Address::Address(const std::string& string) { - // check prefix - if (string.compare(0, prefix.length(), prefix) == 0) { - const auto suffix = string.substr(prefix.length()); - const auto data = parse_hex(suffix); - std::copy(data.begin(), data.end(), bytes.begin()); - } else if (Ethereum::Address::isValid(string)) { - // accept Ethereum format as well - Ethereum::Address ethereumAddress(string); - bytes = ethereumAddress.bytes; - } else { - throw std::invalid_argument("Invalid address data"); - } -} - -// Normalized: with ronin prefix, checksummed hex address, no 0x prefix -std::string Address::string() const { - std::string address = Ethereum::checksumed(*this, Ethereum::ChecksumType::eip55); - if (address.size() >= 2 && address.substr(0, 2) == "0x") { address = address.substr(2); } // skip 0x - return prefix + address; -} diff --git a/src/Ronin/Address.h b/src/Ronin/Address.h deleted file mode 100644 index ac28eb1372f..00000000000 --- a/src/Ronin/Address.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "../PublicKey.h" -#include "../Ethereum/Address.h" -#include -#include - -namespace TW::Ronin { - -class Address: public Ethereum::Address { - public: - /// Determines whether a string makes a valid address. - static bool isValid(const std::string& string); - - /// Initializes an address with a string representation. - explicit Address(const std::string& string); - - /// Initializes an address with a public key. - explicit Address(const PublicKey& publicKey): Ethereum::Address(publicKey) {} - - /// Returns a string representation of the address. - std::string string() const; -}; - -} // namespace TW::Ronin diff --git a/src/Ronin/Entry.cpp b/src/Ronin/Entry.cpp deleted file mode 100644 index 008a717004a..00000000000 --- a/src/Ronin/Entry.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Entry.h" - -#include "Address.h" -#include "../Ethereum/Signer.h" - -using namespace TW::Ronin; -using namespace TW; -using namespace std; - -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { - return Address::isValid(address); -} - -string Entry::normalizeAddress(TWCoinType coin, const string& address) const { - return Address(address).string(); -} - -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { - return Address(publicKey).string(); -} - -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { - const auto addr = Address(address); - return {addr.bytes.begin(), addr.bytes.end()}; -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); -} - -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { - return Ethereum::Signer::signJSON(json, key); -} diff --git a/src/Ronin/Entry.h b/src/Ronin/Entry.h index 83488df897f..eeffca63f40 100644 --- a/src/Ronin/Entry.h +++ b/src/Ronin/Entry.h @@ -1,25 +1,15 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../CoinEntry.h" +#include "Ethereum/Entry.h" namespace TW::Ronin { /// Entry point for Ronin (EVM side chain) -class Entry: public CoinEntry { -public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string normalizeAddress(TWCoinType coin, const std::string& address) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; +class Entry final : public Ethereum::Entry { }; } // namespace TW::Ronin diff --git a/src/Solana/Address.cpp b/src/Solana/Address.cpp index c8a633eb853..e642a59f94a 100644 --- a/src/Solana/Address.cpp +++ b/src/Solana/Address.cpp @@ -1,30 +1,20 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" -#include "Transaction.h" -#include "Program.h" -#include "../Base58.h" -#include "../Base58Address.h" -#include "../Hash.h" - -#include - -#include using namespace TW; -using namespace TW::Solana; + +namespace TW::Solana { bool Address::isValid(const std::string& string) { - const auto data = Base58::bitcoin.decode(string); + const auto data = Base58::decode(string); return Address::isValid(data); } Address::Address(const std::string& string) { - const auto data = Base58::bitcoin.decode(string); + const auto data = Base58::decode(string); if (!isValid(data)) { throw std::invalid_argument("Invalid address string"); } @@ -47,14 +37,11 @@ Address::Address(const Data& publicKeyData) { } std::string Address::string() const { - return Base58::bitcoin.encode(bytes); + return Base58::encode(bytes); } Data Address::vector() const { - Data vec(std::begin(bytes), std::end(bytes)); - return vec; + return Data(begin(bytes), end(bytes)); } -Address Address::defaultTokenAddress(const Address& tokenMintAddress) { - return TokenProgram::defaultTokenAddress(*this, tokenMintAddress); -} +} // namespace TW::Solana diff --git a/src/Solana/Address.h b/src/Solana/Address.h index eef248645d8..d86bd6afd4f 100644 --- a/src/Solana/Address.h +++ b/src/Solana/Address.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Solana/Entry.cpp b/src/Solana/Entry.cpp index c76db86e157..1fc18b03274 100644 --- a/src/Solana/Entry.cpp +++ b/src/Solana/Entry.cpp @@ -1,36 +1,23 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" -#include "Address.h" -#include "Signer.h" +#include "proto/Solana.pb.h" -using namespace TW::Solana; using namespace TW; using namespace std; -// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. - -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { - return Address::isValid(address); -} - -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { - return Address(publicKey).string(); -} - -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { - return Address(address).vector(); -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); -} +namespace TW::Solana { string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { - return Signer::signJSON(json, key); + return signJSONHelper( + coin, + json, + key, + [](const Proto::SigningOutput& output) { return output.encoded(); } + ); } + +} // namespace TW::Solana diff --git a/src/Solana/Entry.h b/src/Solana/Entry.h index 745e2f67f6b..90d02756e5f 100644 --- a/src/Solana/Entry.h +++ b/src/Solana/Entry.h @@ -1,25 +1,20 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../CoinEntry.h" +#include "PublicKey.h" +#include "rust/RustCoinEntry.h" namespace TW::Solana { /// Entry point for implementation of Solana coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public Rust::RustCoinEntryWithSignJSON { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool supportsJSONSigning() const override { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const override; }; } // namespace TW::Solana diff --git a/src/Solana/Program.cpp b/src/Solana/Program.cpp deleted file mode 100644 index ae33d08bd3d..00000000000 --- a/src/Solana/Program.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Program.h" -#include "Address.h" -#include "Transaction.h" -#include "../Base58.h" -#include "../Hash.h" - -#include - -#include - -using namespace TW; -using namespace TW::Solana; - -Address StakeProgram::addressFromValidatorSeed(const Address& fromAddress, const Address& validatorAddress, - const Address& programId) { - Data extended = fromAddress.vector(); - std::string seed = validatorAddress.string(); - Data vecSeed(seed.begin(), seed.end()); - vecSeed.resize(32); - Data additional = programId.vector(); - extended.insert(extended.end(), vecSeed.begin(), vecSeed.end()); - extended.insert(extended.end(), additional.begin(), additional.end()); - Data hash = TW::Hash::sha256(extended); - return Address(hash); -} - -Address StakeProgram::addressFromRecentBlockhash(const Address& fromAddress, const Hash& recentBlockhash, const Address& programId) { - Data extended = fromAddress.vector(); - std::string seed = recentBlockhash.encoded(); - Data vecSeed(seed.begin(), seed.end()); - vecSeed.resize(32); - Data additional = programId.vector(); - extended.insert(extended.end(), vecSeed.begin(), vecSeed.end()); - extended.insert(extended.end(), additional.begin(), additional.end()); - Data hash = TW::Hash::sha256(extended); - return Address(hash); -} - -/* - * Based on solana-program-library code, get_associated_token_address() - * https://github.com/solana-labs/solana-program-library/blob/master/associated-token-account/program/src/lib.rs#L35 - * https://github.com/solana-labs/solana-program-library/blob/master/associated-token-account/program/src/lib.rs#L19 - */ -Address TokenProgram::defaultTokenAddress(const Address& mainAddress, const Address& tokenMintAddress) { - auto programId = Address(TOKEN_PROGRAM_ID_ADDRESS); - std::vector seeds = { - TW::data(mainAddress.bytes.data(), mainAddress.bytes.size()), - TW::data(programId.bytes.data(), programId.bytes.size()), - TW::data(tokenMintAddress.bytes.data(), tokenMintAddress.bytes.size()) - }; - return findProgramAddress(seeds, Address(ASSOCIATED_TOKEN_PROGRAM_ID_ADDRESS)); -} - -/* - * Based on solana code, find_program_address() - * https://github.com/solana-labs/solana/blob/master/sdk/program/src/pubkey.rs#L193 - */ -Address TokenProgram::findProgramAddress(const std::vector& seeds, const Address& programId) { - Address result(Data(32)); - // cycle through seeds for the rare case when result is not valid - for (uint8_t seed = 255; seed >= 0; --seed) { - std::vector seedsCopy; - for (auto& s: seeds) { - seedsCopy.push_back(s); - } - // add extra seed - seedsCopy.push_back({seed}); - Address address = createProgramAddress(seedsCopy, Address(ASSOCIATED_TOKEN_PROGRAM_ID_ADDRESS)); - PublicKey publicKey = PublicKey(TW::data(address.bytes.data(), address.bytes.size()), TWPublicKeyTypeED25519); - if (!publicKey.isValidED25519()) { - result = address; - break; - } - // try next seed - } - return result; -} - -/* - * Based on solana code, create_program_address() - * https://github.com/solana-labs/solana/blob/master/sdk/program/src/pubkey.rs#L135 - */ -Address TokenProgram::createProgramAddress(const std::vector& seeds, const Address& programId) { - // concatenate seeds - Data hashInput; - for (auto& seed: seeds) { - append(hashInput, seed); - } - // append programId - append(hashInput, TW::data(programId.bytes.data(), programId.bytes.size())); - append(hashInput, TW::data("ProgramDerivedAddress")); - // compute hash - Data hash = TW::Hash::sha256(hashInput.data(), hashInput.size()); - return Address(hash); -} diff --git a/src/Solana/Program.h b/src/Solana/Program.h deleted file mode 100644 index 4214bb98355..00000000000 --- a/src/Solana/Program.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Address.h" -#include "Transaction.h" - -#include - -namespace TW::Solana { - -class StakeProgram { -public: - static Address addressFromValidatorSeed(const Address& fromAddress, - const Address& validatorAddress, - const Address& programId); - - static Address addressFromRecentBlockhash(const Address& fromAddress, const Hash& recentBlockhash, const Address& programId); -}; - - -class TokenProgram { -public: - /// Derive default token address for main address and token - static Address defaultTokenAddress(const Address& mainAddress, const Address& tokenMintAddress); - - /// Create a new valid address, if neeed, trying several - static Address findProgramAddress(const std::vector& seeds, const Address& programId); - - /// Create a new address for program, with given seeds - static Address createProgramAddress(const std::vector& seeds, const Address& programId); -}; - -} // namespace TW::Solana diff --git a/src/Solana/Signer.cpp b/src/Solana/Signer.cpp deleted file mode 100644 index f4311dfa35f..00000000000 --- a/src/Solana/Signer.cpp +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Signer.h" -#include "Address.h" -#include "Program.h" -#include "../Base58.h" -#include - -#include - -#include -#include - -using namespace TW; -using namespace TW::Solana; - -void Signer::sign(const std::vector& privateKeys, Transaction& transaction) { - for (auto privateKey : privateKeys) { - auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519)); - auto index = transaction.getAccountIndex(address); - auto message = transaction.messageData(); - auto signature = Signature(privateKey.sign(message, TWCurveED25519)); - transaction.signatures[index] = signature; - } -} - -// Helper to convert protobuf-string-collection references to Address vector -std::vector
convertReferences(const google::protobuf::RepeatedPtrField& references) { - std::vector
ret; - for (auto i = 0; i < references.size(); ++i) { - if (Address::isValid(references[i])) { - ret.push_back(Address(references[i])); - } - } - return ret; -} - -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto blockhash = Solana::Hash(input.recent_blockhash()); - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - Message message; - std::string stakePubkey; - std::vector signerKeys; - - switch (input.transaction_type_case()) { - case Proto::SigningInput::TransactionTypeCase::kTransferTransaction: - { - auto protoMessage = input.transfer_transaction(); - message = Message::createTransfer( - /* from */ Address(key.getPublicKey(TWPublicKeyTypeED25519)), - /* to */ Address(protoMessage.recipient()), - /* value */ protoMessage.value(), - /* recent_blockhash */ blockhash, - /* memo */ protoMessage.memo(), - convertReferences(protoMessage.references())); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kDelegateStakeTransaction: - { - auto protoMessage = input.delegate_stake_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - auto validatorAddress = Address(protoMessage.validator_pubkey()); - auto stakeProgramId = Address(STAKE_PROGRAM_ID_ADDRESS); - std::optional
stakeAddress; - if (protoMessage.stake_account().size() == 0) { - // no stake address specified, generate a new unique - stakeAddress = StakeProgram::addressFromRecentBlockhash(userAddress, blockhash, stakeProgramId); - } else { - // stake address specified, use it - stakeAddress = Address(protoMessage.stake_account()); - } - message = Message::createStake( - /* signer */ userAddress, - /* stakeAddress */ stakeAddress.value(), - /* voteAddress */ validatorAddress, - /* value */ protoMessage.value(), - /* recent_blockhash */ blockhash); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kDeactivateStakeTransaction: - { - auto protoMessage = input.deactivate_stake_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - auto stakeAddress = Address(protoMessage.stake_account()); - message = Message::createStakeDeactivate( - /* signer */ userAddress, - /* stakeAddress */ stakeAddress, - /* recent_blockhash */ blockhash); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kDeactivateAllStakeTransaction: - { - auto protoMessage = input.deactivate_all_stake_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - std::vector
addresses; - for (auto i = 0; i < protoMessage.stake_accounts_size(); ++i) { - addresses.emplace_back(Address(protoMessage.stake_accounts(i))); - } - message = Message::createStakeDeactivateAll(userAddress, addresses, blockhash); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kWithdrawTransaction: - { - auto protoMessage = input.withdraw_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - auto stakeAddress = Address(protoMessage.stake_account()); - message = Message::createStakeWithdraw( - /* signer */ userAddress, - /* stakeAddress */ stakeAddress, - /* value */ protoMessage.value(), - /* recent_blockhash */ blockhash); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kWithdrawAllTransaction: - { - auto protoMessage = input.withdraw_all_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - std::vector> stakes; - for (auto i = 0; i < protoMessage.stake_accounts_size(); ++i) { - stakes.push_back(std::make_pair( - Address(protoMessage.stake_accounts(i).stake_account()), - protoMessage.stake_accounts(i).value() - )); - } - message = Message::createStakeWithdrawAll(userAddress, stakes, blockhash); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kCreateTokenAccountTransaction: - { - auto protoMessage = input.create_token_account_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - auto mainAddress = Address(protoMessage.main_address()); - auto tokenMintAddress = Address(protoMessage.token_mint_address()); - auto tokenAddress = Address(protoMessage.token_address()); - message = Message::createTokenCreateAccount(userAddress, mainAddress, tokenMintAddress, tokenAddress, blockhash); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kTokenTransferTransaction: - { - auto protoMessage = input.token_transfer_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - auto tokenMintAddress = Address(protoMessage.token_mint_address()); - auto senderTokenAddress = Address(protoMessage.sender_token_address()); - auto recipientTokenAddress = Address(protoMessage.recipient_token_address()); - auto amount = protoMessage.amount(); - auto decimals = static_cast(protoMessage.decimals()); - const auto memo = protoMessage.memo(); - message = Message::createTokenTransfer(userAddress, tokenMintAddress, senderTokenAddress, recipientTokenAddress, amount, decimals, blockhash, - memo, convertReferences(protoMessage.references())); - signerKeys.push_back(key); - } - break; - - case Proto::SigningInput::TransactionTypeCase::kCreateAndTransferTokenTransaction: - { - auto protoMessage = input.create_and_transfer_token_transaction(); - auto userAddress = Address(key.getPublicKey(TWPublicKeyTypeED25519)); - auto recipientMainAddress = Address(protoMessage.recipient_main_address()); - auto tokenMintAddress = Address(protoMessage.token_mint_address()); - auto recipientTokenAddress = Address(protoMessage.recipient_token_address()); - auto senderTokenAddress = Address(protoMessage.sender_token_address()); - auto amount = protoMessage.amount(); - auto decimals = static_cast(protoMessage.decimals()); - const auto memo = protoMessage.memo(); - message = Message::createTokenCreateAndTransfer(userAddress, recipientMainAddress, tokenMintAddress, recipientTokenAddress, senderTokenAddress, amount, decimals, blockhash, - memo, convertReferences(protoMessage.references())); - signerKeys.push_back(key); - } - break; - - default: - assert(input.transaction_type_case() != Proto::SigningInput::TransactionTypeCase::TRANSACTION_TYPE_NOT_SET); - } - auto transaction = Transaction(message); - - sign(signerKeys, transaction); - - auto protoOutput = Proto::SigningOutput(); - auto encoded = transaction.serialize(); - protoOutput.set_encoded(encoded); - - return protoOutput; -} - -void Signer::signUpdateBlockhash(const std::vector& privateKeys, - Transaction& transaction, Solana::Hash& recentBlockhash) { - transaction.message.recentBlockhash = recentBlockhash; - Signer::sign(privateKeys, transaction); -} - -// This method does not confirm that PrivateKey order matches that encoded in the messageData -// That order must be correct for the Transaction to succeed on Solana -Data Signer::signRawMessage(const std::vector& privateKeys, const Data messageData) { - std::vector signatures; - for (auto privateKey : privateKeys) { - auto signature = Signature(privateKey.sign(messageData, TWCurveED25519)); - signatures.push_back(signature); - } - Data buffer; - append(buffer, shortVecLength(signatures)); - for (auto signature : signatures) { - Data signature_vec(signature.bytes.begin(), signature.bytes.end()); - append(buffer, signature_vec); - } - append(buffer, messageData); - - return buffer; -} - -std::string Signer::signJSON(const std::string& json, const Data& key) { - auto input = Proto::SigningInput(); - google::protobuf::util::JsonStringToMessage(json, &input); - input.set_private_key(key.data(), key.size()); - return Signer::sign(input).encoded(); -} diff --git a/src/Solana/Signer.h b/src/Solana/Signer.h deleted file mode 100644 index 8b86befe832..00000000000 --- a/src/Solana/Signer.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Transaction.h" -#include "../Data.h" -#include "../Hash.h" -#include "../PrivateKey.h" -#include "../proto/Solana.pb.h" - -namespace TW::Solana { - -/// Helper class that performs Solana transaction signing. -class Signer { - public: - /// Signs the given transaction. - static void sign(const std::vector& privateKeys, Transaction& transaction); - - /// Signs a json Proto::SigningInput with private key - static std::string signJSON(const std::string& json, const Data& key); - - static void signUpdateBlockhash(const std::vector& privateKeys, - Transaction& transaction, Solana::Hash& recentBlockhash); - static Data signRawMessage(const std::vector& privateKeys, const Data messageData); - - static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; -}; - -} // namespace TW::Solana diff --git a/src/Solana/Transaction.cpp b/src/Solana/Transaction.cpp deleted file mode 100644 index dd5dc5c0c05..00000000000 --- a/src/Solana/Transaction.cpp +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Transaction.h" - -#include "Hash.h" -#include "Signer.h" -#include "../BinaryCoding.h" -#include "../PublicKey.h" - -#include - -using namespace TW; -using namespace TW::Solana; -using namespace std; - -uint8_t CompiledInstruction::findAccount(const Address& address) { - auto it = std::find(addresses.begin(), addresses.end(), address); - if (it == addresses.end()) { - throw std::invalid_argument("address not found"); - } - assert(it != addresses.end()); - auto dist = std::distance(addresses.begin(), it); - assert(dist < 256); - return (uint8_t)dist; -} - -void Message::addAccount(const AccountMeta& account) { - bool inSigned = (std::find(signedAccounts.begin(), signedAccounts.end(), account.account) != signedAccounts.end()); - bool inUnsigned = (std::find(unsignedAccounts.begin(), unsignedAccounts.end(), account.account) != unsignedAccounts.end()); - bool inReadOnly = (std::find(readOnlyAccounts.begin(), readOnlyAccounts.end(), account.account) != readOnlyAccounts.end()); - if (account.isSigner) { - if (!inSigned) { - signedAccounts.push_back(account.account); - } - } else if (!account.isReadOnly) { - if (!inSigned && !inUnsigned) { - unsignedAccounts.push_back(account.account); - } - } else { - if (!inSigned && !inUnsigned && !inReadOnly) { - readOnlyAccounts.push_back(account.account); - } - } -} - -void Message::addAccountKeys(const Address& account) { - if (std::find(accountKeys.begin(), accountKeys.end(), account) == accountKeys.end()) { - accountKeys.push_back(account); - } -} - -void Message::compileAccounts() { - for (auto& instr: instructions) { - for (auto& address: instr.accounts) { - addAccount(address); - } - } - // add programIds (read-only, at end) - for (auto& instr: instructions) { - addAccount(AccountMeta{instr.programId, false, true}); - } - - header = MessageHeader{ - (uint8_t)signedAccounts.size(), - 0, - (uint8_t)readOnlyAccounts.size() - }; - - // merge the three buckets - accountKeys.clear(); - for(auto& a: signedAccounts) { - addAccountKeys(a); - } - for(auto& a: unsignedAccounts) { - addAccountKeys(a); - } - for(auto& a: readOnlyAccounts) { - addAccountKeys(a); - } - - compileInstructions(); -} - -void Message::compileInstructions() { - compiledInstructions.clear(); - for (auto instruction: instructions) { - compiledInstructions.emplace_back(CompiledInstruction(instruction, accountKeys)); - } -} - -std::string Transaction::serialize() const { - Data buffer; - - append(buffer, shortVecLength(this->signatures)); - for (auto signature : this->signatures) { - Data signature_vec(signature.bytes.begin(), signature.bytes.end()); - append(buffer, signature_vec); - } - append(buffer, this->messageData()); - - return Base58::bitcoin.encode(buffer); -} - -Data Transaction::messageData() const { - Data buffer; - - buffer.push_back(this->message.header.numRequiredSignatures); - buffer.push_back(this->message.header.numCreditOnlySignedAccounts); - buffer.push_back(this->message.header.numCreditOnlyUnsignedAccounts); - append(buffer, shortVecLength
(this->message.accountKeys)); - for (auto account_key : this->message.accountKeys) { - Data account_key_vec(account_key.bytes.begin(), account_key.bytes.end()); - append(buffer, account_key_vec); - } - Data recentBlockhash(this->message.recentBlockhash.bytes.begin(), - this->message.recentBlockhash.bytes.end()); - append(buffer, recentBlockhash); - - // apppend compiled instructions - append(buffer, shortVecLength(message.compiledInstructions)); - for (auto instruction : message.compiledInstructions) { - buffer.push_back(instruction.programIdIndex); - append(buffer, shortVecLength(instruction.accounts)); - append(buffer, instruction.accounts); - append(buffer, shortVecLength(instruction.data)); - append(buffer, instruction.data); - } - - return buffer; -} - -uint8_t Transaction::getAccountIndex(Address publicKey) { - auto item = - std::find(this->message.accountKeys.begin(), this->message.accountKeys.end(), publicKey); - if (item == this->message.accountKeys.end()) { - throw std::invalid_argument("publicKey not found in message.accountKeys"); - } - return (uint8_t)std::distance(this->message.accountKeys.begin(), item); -} - -bool Signature::operator==(const Signature& v) const { - return bytes == v.bytes; -} diff --git a/src/Solana/Transaction.h b/src/Solana/Transaction.h deleted file mode 100644 index 118a0151c00..00000000000 --- a/src/Solana/Transaction.h +++ /dev/null @@ -1,506 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Address.h" -#include "../Base58.h" -#include "../BinaryCoding.h" -#include "../Data.h" - -#include -#include - -namespace TW::Solana { - -// https://docs.solana.com/developing/programming-model/transactions - -const std::string SYSTEM_PROGRAM_ID_ADDRESS = "11111111111111111111111111111111"; -const std::string STAKE_PROGRAM_ID_ADDRESS = "Stake11111111111111111111111111111111111111"; -const std::string TOKEN_PROGRAM_ID_ADDRESS = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; -const std::string ASSOCIATED_TOKEN_PROGRAM_ID_ADDRESS = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"; -const std::string SYSVAR_RENT_ID_ADDRESS = "SysvarRent111111111111111111111111111111111"; -const std::string SYSVAR_CLOCK_ID_ADDRESS = "SysvarC1ock11111111111111111111111111111111"; -const std::string STAKE_CONFIG_ID_ADDRESS = "StakeConfig11111111111111111111111111111111"; -const std::string NULL_ID_ADDRESS = "11111111111111111111111111111111"; -const std::string SYSVAR_STAKE_HISTORY_ID_ADDRESS = "SysvarStakeHistory1111111111111111111111111"; -const std::string MEMO_PROGRAM_ID_ADDRESS = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"; - -template -Data shortVecLength(std::vector vec) { - auto bytes = Data(); - auto remLen = vec.size(); - while (true) { - uint8_t elem = remLen & 0x7f; - remLen >>= 7; - if (remLen == 0) { - bytes.push_back(elem); - break; - } else { - elem |= 0x80; - bytes.push_back(elem); - } - } - return bytes; -} - -// System instruction types -enum SystemInstruction { - CreateAccount, - Assign, - Transfer, - CreateAccountWithSeed -}; - -// Stake instruction types -enum StakeInstruction { - Initialize = 0, - DelegateStake = 2, - Withdraw = 4, - Deactivate = 5, -}; - -// Token instruction types -enum TokenInstruction { - CreateTokenAccount = 1, - //SetAuthority = 6, - TokenTransfer = 12, -}; - -enum TokenAuthorityType { - MintTokens = 0, - FreezeAccount = 1, - AccountOwner = 2, - CloseAccount = 3, -}; - -struct AccountMeta { - Address account; - bool isSigner; - bool isReadOnly; - AccountMeta(const Address& address, bool isSigner, bool isReadOnly): account(address), isSigner(isSigner), isReadOnly(isReadOnly) {} -}; - -// An instruction to execute a program -struct Instruction { - // Index into the transaction keys array indicating the program account that - // executes this instruction - Address programId; - // Ordered indices into the transaction keys array indicating which accounts - // to pass to the program - std::vector accounts; - // The program input data - Data data; - - Instruction(const Address& programId, const std::vector& accounts, const Data& data) - : programId(programId), accounts(accounts), data(data) {} - - // This creator creates a default System Transfer instruction - static Instruction createTransfer(const std::vector& accounts, uint64_t value) { - const SystemInstruction type = Transfer; - auto data = Data(); - encode32LE(static_cast(type), data); - encode64LE(static_cast(value), data); - - return Instruction(Address(SYSTEM_PROGRAM_ID_ADDRESS), accounts, data); - } - - static Instruction createAccountWithSeed(const std::vector& accounts, uint64_t value, uint64_t space, const Address& programId, - const Address& voteAddress, uint64_t seedLength, const Address& signer) { - const SystemInstruction type = CreateAccountWithSeed; - auto data = Data(); - std::string seed = voteAddress.string(); - Data vecSeed(seed.begin(), seed.end()); - vecSeed.resize(static_cast(seedLength)); - encode32LE(static_cast(type), data); - append(data, signer.vector()); - encode64LE(static_cast(seedLength), data); - append(data, vecSeed); - encode64LE(static_cast(value), data); - encode64LE(static_cast(space), data); - append(data, programId.vector()); - - return Instruction(Address(SYSTEM_PROGRAM_ID_ADDRESS), accounts, data); - } - - // creates an Initialize Stake instruction - static Instruction createStakeInitialize(const std::vector& accounts, const Address& signer) { - const StakeInstruction type = Initialize; - auto data = Data(); - encode32LE(static_cast(type), data); - append(data, signer.vector()); - append(data, signer.vector()); - auto lockup = Data(48); - append(data, lockup); - - return Instruction(Address(STAKE_PROGRAM_ID_ADDRESS), accounts, data); - } - - // creates a Withdraw Stake instruction - static Instruction createStakeWithdraw(const std::vector& accounts, uint64_t value) { - const StakeInstruction type = Withdraw; - auto data = Data(); - encode32LE(static_cast(type), data); - encode64LE(static_cast(value), data); - - return Instruction(Address(STAKE_PROGRAM_ID_ADDRESS), accounts, data); - } - - // creates a Stake instruction - static Instruction createStake(StakeInstruction type, const std::vector& accounts) { - auto data = Data(); - encode32LE(static_cast(type), data); - - return Instruction(Address(STAKE_PROGRAM_ID_ADDRESS), accounts, data); - } - - // creates a createAccount token instruction. - static Instruction createTokenCreateAccount(const std::vector& accounts) { - auto data = Data(); - return Instruction(Address(ASSOCIATED_TOKEN_PROGRAM_ID_ADDRESS), accounts, data); - } - - // creates a transfer token instruction. - static Instruction createTokenTransfer(const std::vector& accounts, uint64_t value, uint8_t decimals) { - const TokenInstruction type = TokenTransfer; - auto data = Data(); - data.push_back(static_cast(type)); - encode64LE(value, data); - data.push_back(static_cast(decimals)); - - return Instruction(Address(TOKEN_PROGRAM_ID_ADDRESS), accounts, data); - } - - static Instruction createMemo(std::string memo) { - auto data = TW::data(memo); - std::vector accounts; // empty - return Instruction(Address(MEMO_PROGRAM_ID_ADDRESS), accounts, data); - } -}; - -// A compiled instruction -struct CompiledInstruction { - // Index into the transaction keys array indicating the program account that executes this instruction - uint8_t programIdIndex; - // Ordered indices into the transaction keys array indicating which accounts - // to pass to the program - std::vector accounts; - // The program input data - Data data; - - // Reference to the address vector - const std::vector
& addresses; - - /// Supplied address vector is expected to contain all addresses and programId from the instruction; they are replaced by index into the address vector. - CompiledInstruction(const Instruction& instruction, const std::vector
& addresses): addresses(addresses) { - programIdIndex = findAccount(instruction.programId); - for (auto& account: instruction.accounts) { - accounts.push_back(findAccount(account.account)); - } - data = instruction.data; - } - - uint8_t findAccount(const Address& address); -}; - -class Hash { - public: - static const size_t size = 32; - /// Hash data - std::array bytes; - - Hash(const std::string& string) { - const auto data = Base58::bitcoin.decode(string); - std::copy(data.begin(), data.end(), this->bytes.begin()); - } - - std::string encoded() const { return Base58::bitcoin.encode(bytes); } -}; - -class Signature { - public: - static const size_t size = 64; - /// Signature data - std::array bytes; - - Signature(const std::string& string) { - const auto data = Base58::bitcoin.decode(string); - std::copy(data.begin(), data.end(), this->bytes.begin()); - } - Signature(const std::array& bytes) { this->bytes = bytes; } - Signature(const Data& bytes) { std::copy(bytes.begin(), bytes.end(), this->bytes.begin()); } - - bool operator==(const Signature& v) const; -}; - -struct MessageHeader { - // The number of signatures required for this message to be considered - // valid. The signatures must match the first `numRequiredSignatures` of - // `accountKeys`. - uint8_t numRequiredSignatures = 0; - // The last numCreditOnlySignedAccounts of the signed keys are - // credit-only accounts. - uint8_t numCreditOnlySignedAccounts = 0; - // The last numCreditOnlyUnsignedAccounts of the unsigned keys are - // credit-only accounts. - uint8_t numCreditOnlyUnsignedAccounts = 0; -}; - -class Message { - public: - // The message header, identifying signed and credit-only `accountKeys` - MessageHeader header; - // All the account keys used by this transaction - std::vector
accountKeys; - // The id of a recent ledger entry. - Hash recentBlockhash; - // Programs that will be executed in sequence and committed in one atomic - // transaction if all succeed. - std::vector instructions; - - // three buckets of different account types - std::vector
signedAccounts; - std::vector
unsignedAccounts; - std::vector
readOnlyAccounts; - std::vector compiledInstructions; - - Message() : recentBlockhash(NULL_ID_ADDRESS) {}; - - Message(Hash recentBlockhash, const std::vector& instructions) - : recentBlockhash(recentBlockhash) - , instructions(instructions) { - compileAccounts(); - } - - // add an acount, to the corresponding bucket - void addAccount(const AccountMeta& account); - // add an account to accountKeys if not yet present - void addAccountKeys(const Address& account); - // compile the single accounts lists from the buckets - void compileAccounts(); - // compile the instructions; replace instruction accounts with indices - void compileInstructions(); - - static void appendReferences(std::vector& accountMetas, const std::vector
& references) { - for (auto reference: references) { - accountMetas.push_back(AccountMeta(reference, false, true)); - } - } - - // This constructor creates a default single-signer Transfer message - static Message createTransfer(const Address& from, const Address& to, uint64_t value, Hash recentBlockhash, - std::string memo = "", std::vector
references = {} - ) { - std::vector instructions; - if (memo.length() > 0) { - // Optional memo. Order: before transfer, as per documentation. - instructions.push_back(Instruction::createMemo(memo)); - } - std::vector accountMetas = { - AccountMeta(from, true, false), - AccountMeta(to, false, false), - }; - appendReferences(accountMetas, references); - instructions.push_back(Instruction::createTransfer(accountMetas, value)); - return Message(recentBlockhash, instructions); - } - - // This constructor creates a create_account_with_seed_and_delegate_stake message - // see delegate_stake() solana/programs/stake/src/stake_instruction.rs - static Message createStake(const Address& signer, const Address& stakeAddress, const Address& voteAddress, uint64_t value, Hash recentBlockhash) { - auto sysvarRentId = Address(SYSVAR_RENT_ID_ADDRESS); - auto sysvarClockId = Address(SYSVAR_CLOCK_ID_ADDRESS); - auto stakeConfigId = Address(STAKE_CONFIG_ID_ADDRESS); - auto sysvarStakeHistoryId = Address(SYSVAR_STAKE_HISTORY_ID_ADDRESS); - auto stakeProgramId = Address(STAKE_PROGRAM_ID_ADDRESS); - std::vector instructions; - // create_account_with_seed instruction - Address seed = Address(data(recentBlockhash.bytes.data(), recentBlockhash.bytes.size())); - auto createAccountInstruction = Instruction::createAccountWithSeed(std::vector{ - AccountMeta(signer, true, true), - AccountMeta(stakeAddress, false, false), - AccountMeta(signer, true, true), - }, value, 200, stakeProgramId, seed, 32, signer); - instructions.push_back(createAccountInstruction); - // initialize instruction - auto initializeInstruction = Instruction::createStakeInitialize(std::vector{ - AccountMeta(stakeAddress, false, false), - AccountMeta(sysvarRentId, false, true) - }, signer); - instructions.push_back(initializeInstruction); - // delegate_stake instruction - auto delegateInstruction = Instruction::createStake(DelegateStake, - std::vector{ - AccountMeta(stakeAddress, false, false), // 0. `[WRITE]` Initialized stake account to be delegated - AccountMeta(voteAddress, false, true), // 1. `[]` Vote account to which this stake will be delegated - AccountMeta(sysvarClockId, false, true), // 2. `[]` Clock sysvar - AccountMeta(sysvarStakeHistoryId, false, true), // 3. `[]` Stake history sysvar that carries stake warmup/cooldown history - AccountMeta(stakeConfigId, false, true), // 4. `[]` Address of config account that carries stake config - AccountMeta(signer, true, true), // 5. `[SIGNER]` Stake authority - }); - instructions.push_back(delegateInstruction); - return Message(recentBlockhash, instructions); - } - - // This constructor creates a deactivate_stake message - static Message createStakeDeactivate(const Address& signer, const Address& stakeAddress, Hash recentBlockhash) { - auto sysvarClockId = Address(SYSVAR_CLOCK_ID_ADDRESS); - auto instruction = Instruction::createStake(Deactivate, std::vector{ - AccountMeta(stakeAddress, false, false), // 0. `[WRITE]` Delegated stake account - AccountMeta(sysvarClockId, false, true), // 1. `[]` Clock sysvar - AccountMeta(signer, true, false), // 2. `[SIGNER]` Stake authority - }); - return Message(recentBlockhash, {instruction}); - } - - // This constructor creates a deactivate_stake message with multiple stake accounts - static Message createStakeDeactivateAll(const Address& signer, const std::vector
& stakeAddresses, Hash recentBlockhash) { - auto sysvarClockId = Address(SYSVAR_CLOCK_ID_ADDRESS); - std::vector instructions; - for(auto& address: stakeAddresses) { - auto instruction = Instruction::createStake(Deactivate, std::vector{ - AccountMeta(address, false, false), // 0. `[WRITE]` Delegated stake account - AccountMeta(sysvarClockId, false, true), // 1. `[]` Clock sysvar - AccountMeta(signer, true, false), // 2. `[SIGNER]` Stake authority - }); - instructions.push_back(instruction); - } - return Message(recentBlockhash, instructions); - } - - // This constructor creates a withdraw message, with the signer as the default recipient - static Message createStakeWithdraw(const Address& signer, const Address& stakeAddress, uint64_t value, Hash recentBlockhash) { - auto sysvarClockId = Address(SYSVAR_CLOCK_ID_ADDRESS); - auto sysvarStakeHistoryId = Address(SYSVAR_STAKE_HISTORY_ID_ADDRESS); - auto instruction = Instruction::createStakeWithdraw(std::vector{ - AccountMeta(stakeAddress, false, false), // 0. `[WRITE]` Stake account from which to withdraw - AccountMeta(signer, false, false), // 1. `[WRITE]` Recipient account - AccountMeta(sysvarClockId, false, true), // 2. `[]` Clock sysvar - AccountMeta(sysvarStakeHistoryId, false, true), // 3. `[]` Stake history sysvar that carries stake warmup/cooldown history - AccountMeta(signer, true, false), // 4. `[SIGNER]` Withdraw authority - }, value); - return Message(recentBlockhash, {instruction}); - } - - // This constructor creates a withdraw message, with multiple stake accounts - static Message createStakeWithdrawAll(const Address& signer, const std::vector>& stakes, Hash recentBlockhash) { - auto sysvarClockId = Address(SYSVAR_CLOCK_ID_ADDRESS); - auto sysvarStakeHistoryId = Address(SYSVAR_STAKE_HISTORY_ID_ADDRESS); - std::vector instructions; - for(auto& stake: stakes) { - auto instruction = Instruction::createStakeWithdraw(std::vector{ - AccountMeta(stake.first, false, false), // 0. `[WRITE]` Stake account from which to withdraw - AccountMeta(signer, false, false), // 1. `[WRITE]` Recipient account - AccountMeta(sysvarClockId, false, true), // 2. `[]` Clock sysvar - AccountMeta(sysvarStakeHistoryId, false, true), // 3. `[]` Stake history sysvar that carries stake warmup/cooldown history - AccountMeta(signer, true, false), // 4. `[SIGNER]` Withdraw authority - }, stake.second); - instructions.push_back(instruction); - } - return Message(recentBlockhash, instructions); - } - - // This constructor creates a createAccount token message - // see create_associated_token_account() solana-program-library/associated-token-account/program/src/lib.rs - static Message createTokenCreateAccount(const Address& signer, const Address& otherMainAccount, const Address& tokenMintAddress, const Address& tokenAddress, Hash recentBlockhash) { - auto sysvarRentId = Address(SYSVAR_RENT_ID_ADDRESS); - auto systemProgramId = Address(SYSTEM_PROGRAM_ID_ADDRESS); - auto tokenProgramId = Address(TOKEN_PROGRAM_ID_ADDRESS); - auto instruction = Instruction::createTokenCreateAccount(std::vector{ - AccountMeta(signer, true, false), // fundingAddress, - AccountMeta(tokenAddress, false, false), - AccountMeta(otherMainAccount, false, true), - AccountMeta(tokenMintAddress, false, true), - AccountMeta(systemProgramId, false, true), - AccountMeta(tokenProgramId, false, true), - AccountMeta(sysvarRentId, false, true), - }); - return Message(recentBlockhash, {instruction}); - } - - // This constructor creates a transfer token message. - // see transfer_checked() solana-program-library/token/program/src/instruction.rs - static Message createTokenTransfer(const Address& signer, const Address& tokenMintAddress, - const Address& senderTokenAddress, const Address& recipientTokenAddress, uint64_t amount, uint8_t decimals, Hash recentBlockhash, - std::string memo = "", std::vector
references = {} - ) { - std::vector instructions; - if (memo.length() > 0) { - // Optional memo. Order: before transfer, as per documentation. - instructions.push_back(Instruction::createMemo(memo)); - } - std::vector accountMetas = { - AccountMeta(senderTokenAddress, false, false), - AccountMeta(tokenMintAddress, false, true), - AccountMeta(recipientTokenAddress, false, false), - AccountMeta(signer, true, false), - }; - appendReferences(accountMetas, references); - instructions.push_back(Instruction::createTokenTransfer(accountMetas, amount, decimals)); - return Message(recentBlockhash, instructions); - } - - // This constructor creates a createAndTransferToken message, combining createAccount and transfer. - static Message createTokenCreateAndTransfer(const Address& signer, const Address& recipientMainAddress, const Address& tokenMintAddress, - const Address& recipientTokenAddress, const Address& senderTokenAddress, uint64_t amount, uint8_t decimals, Hash recentBlockhash, - std::string memo = "", std::vector
references = {} - ) { - const auto sysvarRentId = Address(SYSVAR_RENT_ID_ADDRESS); - const auto systemProgramId = Address(SYSTEM_PROGRAM_ID_ADDRESS); - const auto tokenProgramId = Address(TOKEN_PROGRAM_ID_ADDRESS); - std::vector instructions; - instructions.push_back(Instruction::createTokenCreateAccount(std::vector{ - AccountMeta(signer, true, false), // fundingAddress, - AccountMeta(recipientTokenAddress, false, false), - AccountMeta(recipientMainAddress, false, true), - AccountMeta(tokenMintAddress, false, true), - AccountMeta(systemProgramId, false, true), - AccountMeta(tokenProgramId, false, true), - AccountMeta(sysvarRentId, false, true), - })); - if (memo.length() > 0) { - // Optional memo. Order: before transfer, as per documentation. - instructions.push_back(Instruction::createMemo(memo)); - } - std::vector accountMetas = { - AccountMeta(senderTokenAddress, false, false), - AccountMeta(tokenMintAddress, false, true), - AccountMeta(recipientTokenAddress, false, false), - AccountMeta(signer, true, false), - }; - appendReferences(accountMetas, references); - instructions.push_back(Instruction::createTokenTransfer(accountMetas, amount, decimals)); - return Message(recentBlockhash, instructions); - } -}; - -class Transaction { - public: - // Signatures - std::vector signatures; - // The message to sign - Message message; - - Transaction(const Message& message) : message(message) { - this->signatures.resize(message.header.numRequiredSignatures, Signature(defaultSignature)); - } - - // Default basic transfer transaction - Transaction(const Address& from, const Address& to, uint64_t value, Hash recentBlockhash, std::string memo = "", std::vector
references = {}) - : message(Message::createTransfer(from, to, value, recentBlockhash, memo, references)) { - this->signatures.resize(1, Signature(defaultSignature)); - } - - public: - std::string serialize() const; - std::vector messageData() const; - uint8_t getAccountIndex(Address publicKey); - - private: - TW::Data defaultSignature = TW::Data(64); -}; - -} // namespace TW::Solana diff --git a/src/StarkEx/MessageSigner.cpp b/src/StarkEx/MessageSigner.cpp new file mode 100644 index 00000000000..967bc0bf7e0 --- /dev/null +++ b/src/StarkEx/MessageSigner.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include + +namespace TW::StarkEx { + +std::string MessageSigner::signMessage(const TW::PrivateKey& privateKey, const std::string& message) { + auto digest = parse_hex(message, true); + return hex(privateKey.sign(digest, TWCurveStarkex)); +} + +bool MessageSigner::verifyMessage(const PublicKey& publicKey, const std::string& message, const std::string& signature) noexcept { + auto starkSignature = parse_hex(signature, true); + auto digest = parse_hex(message, true); + return publicKey.verify(starkSignature, digest); +} + +} // namespace TW::StarkEx diff --git a/src/StarkEx/MessageSigner.h b/src/StarkEx/MessageSigner.h new file mode 100644 index 00000000000..1f1eeffe52e --- /dev/null +++ b/src/StarkEx/MessageSigner.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include + +namespace TW::StarkEx { +class MessageSigner { +public: + /// Sign a message following StarkEx Curve + /// \param privateKey the private key to sign with + /// \param message hex message to sign + /// \return hex signed message + static std::string signMessage(const PrivateKey& privateKey, const std::string& message);; + + /// Verify a message following EIP-191 + /// \param publicKey publickey to verify the signed message + /// \param message message to be verified as a string + /// \param signature signature to verify the message against + /// \return true if the message match the signature, false otherwise + static bool verifyMessage(const PublicKey& publicKey, const std::string& message, const std::string& signature) noexcept;; +}; +} diff --git a/src/Stellar/Address.cpp b/src/Stellar/Address.cpp index c7197b89451..dbf2261388e 100644 --- a/src/Stellar/Address.cpp +++ b/src/Stellar/Address.cpp @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" +#include "Crc.h" #include "../Base32.h" #include "../HexCoding.h" -#include "Crc.h" #include #include @@ -15,33 +13,28 @@ #include #include -using namespace TW::Stellar; +namespace TW::Stellar { bool Address::isValid(const std::string& string) { - bool valid = false; - if (string.length() != size) { return false; } // Check that it decodes correctly Data decoded; - valid = Base32::decode(string, decoded); + if (!Base32::decode(string, decoded) || decoded.size() != rawSize) { + return false; + } // ... and that version byte is 0x30 - if (valid && TWStellarVersionByte(decoded[0]) != TWStellarVersionByte::TWStellarVersionByteAccountID) { - valid = false; + if (TWStellarVersionByte(decoded[0]) != TWStellarVersionByte::TWStellarVersionByteAccountID) { + return false; } // ... and that checksums match auto checksum_expected = Crc::crc16(decoded.data(), 33); auto checksum_actual = static_cast((decoded[34] << 8) | decoded[33]); // unsigned short (little endian) - if (valid && checksum_expected != checksum_actual) { - valid = false; - } - - memzero(decoded.data(), decoded.size()); - return valid; + return checksum_expected == checksum_actual; } Address::Address(const std::string& string) { @@ -82,3 +75,5 @@ std::string Address::string() const { auto out = Base32::encode(bytesAsData); return out; } + +} // namespace TW::Stellar diff --git a/src/Stellar/Address.h b/src/Stellar/Address.h index 6183aaeaace..fab7bda7408 100644 --- a/src/Stellar/Address.h +++ b/src/Stellar/Address.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/Stellar/Entry.cpp b/src/Stellar/Entry.cpp index 0e1beb09ca3..56c20599a79 100644 --- a/src/Stellar/Entry.cpp +++ b/src/Stellar/Entry.cpp @@ -1,27 +1,52 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" +#include "proto/TransactionCompiler.pb.h" #include "Signer.h" -using namespace TW::Stellar; -using namespace std; +namespace TW::Stellar { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + Signer signer(input); + + auto preImage = signer.signaturePreimage(); + auto preImageHash = Hash::sha256(preImage); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, [[maybe_unused]] const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() != 1) { + output.set_error(Common::Proto::Error_signatures_count); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_signatures_count)); + return; + } + + output = Signer(input).compile(signatures[0]); + }); +} + +} // namespace TW::Stellar diff --git a/src/Stellar/Entry.h b/src/Stellar/Entry.h index 0d7aa434950..aef0e1b8221 100644 --- a/src/Stellar/Entry.h +++ b/src/Stellar/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,11 +10,14 @@ namespace TW::Stellar { /// Entry point for implementation of Stellar coin, and Kin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; }; } // namespace TW::Stellar diff --git a/src/Stellar/Signer.cpp b/src/Stellar/Signer.cpp index 1f36a90e3e2..01ac21aa776 100644 --- a/src/Stellar/Signer.cpp +++ b/src/Stellar/Signer.cpp @@ -1,11 +1,9 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "Base64.h" #include "Signer.h" +#include "Base64.h" #include "../BinaryCoding.h" #include "../Hash.h" #include "../HexCoding.h" @@ -14,8 +12,8 @@ #include using namespace TW; -using namespace TW::Stellar; +namespace TW::Stellar { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(input); auto output = Proto::SigningOutput(); @@ -25,15 +23,15 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { std::string Signer::sign() const noexcept { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto account = Address(input.account()); - auto encoded = encode(input); + auto key = PrivateKey(Data(_input.private_key().begin(), _input.private_key().end())); + auto account = Address(_input.account()); + auto encoded = encode(_input); auto encodedWithHeaders = Data(); - auto publicNetwork = input.passphrase(); // Header + auto publicNetwork = _input.passphrase(); // Header auto passphrase = Hash::sha256(publicNetwork); encodedWithHeaders.insert(encodedWithHeaders.end(), passphrase.begin(), passphrase.end()); - auto transactionType = Data{0, 0, 0, 2}; // Header + auto transactionType = Data{0, 0, 0, 2}; encodedWithHeaders.insert(encodedWithHeaders.end(), transactionType.begin(), transactionType.end()); encodedWithHeaders.insert(encodedWithHeaders.end(), encoded.begin(), encoded.end()); @@ -64,10 +62,16 @@ Data Signer::encode(const Proto::SigningInput& input) const { // Time bounds if (input.has_op_change_trust() && input.op_change_trust().valid_before() != 0) { encode32BE(1, data); - encode64BE(0, data); // from + encode64BE(0, data); // from encode64BE(input.op_change_trust().valid_before(), data); // to } else { - encode32BE(0, data); // missing + if (input.time_bounds() > 0) { + encode32BE(1, data); + encode64BE(0, data); //from + encode64BE(input.time_bounds(), data); //to + } else { + encode32BE(0, data); // missing + } } // Memo @@ -91,74 +95,100 @@ Data Signer::encode(const Proto::SigningInput& input) const { } // Operations - encode32BE(1, data); // Operation list size. Only 1 operation. - encode32BE(0, data); // Source equals account + encode32BE(1, data); // Operation list size. Only 1 operation. + encode32BE(0, data); // Source equals account encode32BE(operationType(input), data); // Operation type switch (input.operation_oneof_case()) { - case Proto::SigningInput::kOpCreateAccount: - default: - encodeAddress(Address(input.op_create_account().destination()), data); - encode64BE(input.op_create_account().amount(), data); - break; - - case Proto::SigningInput::kOpPayment: - encodeAddress(Address(input.op_payment().destination()), data); - encodeAsset(input.op_payment().asset(), data); - encode64BE(input.op_payment().amount(), data); - break; - - case Proto::SigningInput::kOpChangeTrust: - encodeAsset(input.op_change_trust().asset(), data); - encode64BE(0x7fffffffffffffff, data); // limit MAX - break; - - case Proto::SigningInput::kOpCreateClaimableBalance: - { - const auto ClaimantTypeV0 = 0; - encodeAsset(input.op_create_claimable_balance().asset(), data); - encode64BE(input.op_create_claimable_balance().amount(), data); - auto nClaimants = input.op_create_claimable_balance().claimants_size(); - encode32BE((uint32_t)nClaimants, data); - for (auto i = 0; i < nClaimants; ++i) { - encode32BE((uint32_t)ClaimantTypeV0, data); - encodeAddress(Address(input.op_create_claimable_balance().claimants(i).account()), data); - encode32BE((uint32_t)input.op_create_claimable_balance().claimants(i).predicate(), data); - // Note: other predicates not supported, predicate-specific data would follow here - } - } - break; - - case Proto::SigningInput::kOpClaimClaimableBalance: - { - const auto ClaimableBalanceIdTypeClaimableBalanceIdTypeV0 = 0; - encode32BE((uint32_t)ClaimableBalanceIdTypeClaimableBalanceIdTypeV0, data); - const auto balanceId = input.op_claim_claimable_balance().balance_id(); - if (balanceId.size() != 32) { - return Data(); - } - data.insert(data.end(), balanceId.begin(), balanceId.end()); - } - break; + case Proto::SigningInput::kOpCreateAccount: + default: + encodeAddress(Address(input.op_create_account().destination()), data); + encode64BE(input.op_create_account().amount(), data); + break; + + case Proto::SigningInput::kOpPayment: + encodeAddress(Address(input.op_payment().destination()), data); + encodeAsset(input.op_payment().asset(), data); + encode64BE(input.op_payment().amount(), data); + break; + + case Proto::SigningInput::kOpChangeTrust: + encodeAsset(input.op_change_trust().asset(), data); + encode64BE(0x7fffffffffffffff, data); // limit MAX + break; + + case Proto::SigningInput::kOpCreateClaimableBalance: { + const auto ClaimantTypeV0 = 0; + encodeAsset(input.op_create_claimable_balance().asset(), data); + encode64BE(input.op_create_claimable_balance().amount(), data); + auto nClaimants = input.op_create_claimable_balance().claimants_size(); + encode32BE((uint32_t)nClaimants, data); + for (auto i = 0; i < nClaimants; ++i) { + encode32BE((uint32_t)ClaimantTypeV0, data); + encodeAddress(Address(input.op_create_claimable_balance().claimants(i).account()), data); + encode32BE((uint32_t)input.op_create_claimable_balance().claimants(i).predicate(), data); + // Note: other predicates not supported, predicate-specific data would follow here + } + } break; + + case Proto::SigningInput::kOpClaimClaimableBalance: { + const auto ClaimableBalanceIdTypeClaimableBalanceIdTypeV0 = 0; + encode32BE((uint32_t)ClaimableBalanceIdTypeClaimableBalanceIdTypeV0, data); + const auto balanceId = input.op_claim_claimable_balance().balance_id(); + if (balanceId.size() != 32) { + return Data(); + } + data.insert(data.end(), balanceId.begin(), balanceId.end()); + } break; } encode32BE(0, data); // Ext return data; } +Data Signer::signaturePreimage() const { + auto encoded = encode(_input); + + auto encodedWithHeaders = Data(); + auto publicNetwork = _input.passphrase(); // Header + auto passphrase = Hash::sha256(publicNetwork); + encodedWithHeaders.insert(encodedWithHeaders.end(), passphrase.begin(), passphrase.end()); + auto transactionType = Data{0, 0, 0, 2}; // Header + encodedWithHeaders.insert(encodedWithHeaders.end(), transactionType.begin(), + transactionType.end()); + encodedWithHeaders.insert(encodedWithHeaders.end(), encoded.begin(), encoded.end()); + return encodedWithHeaders; +} + +Proto::SigningOutput Signer::compile(const Data& sig) const { + auto account = Address(_input.account()); + auto encoded = encode(_input); + + auto signature = Data(); + signature.insert(signature.end(), encoded.begin(), encoded.end()); + encode32BE(1, signature); + signature.insert(signature.end(), account.bytes.end() - 4, account.bytes.end()); + encode32BE(static_cast(sig.size()), signature); + signature.insert(signature.end(), sig.begin(), sig.end()); + + Proto::SigningOutput output; + output.set_signature(Base64::encode(signature)); + return output; +} + uint32_t Signer::operationType(const Proto::SigningInput& input) { switch (input.operation_oneof_case()) { - case Proto::SigningInput::kOpCreateAccount: - default: - return 0; - case Proto::SigningInput::kOpPayment: - return 1; - case Proto::SigningInput::kOpChangeTrust: - return 6; - case Proto::SigningInput::kOpCreateClaimableBalance: - return 14; - case Proto::SigningInput::kOpClaimClaimableBalance: - return 15; + case Proto::SigningInput::kOpCreateAccount: + default: + return 0; + case Proto::SigningInput::kOpPayment: + return 1; + case Proto::SigningInput::kOpChangeTrust: + return 6; + case Proto::SigningInput::kOpCreateClaimableBalance: + return 14; + case Proto::SigningInput::kOpClaimClaimableBalance: + return 15; } } @@ -176,7 +206,7 @@ void Signer::encodeAsset(const Proto::Asset& asset, Data& data) { } encode32BE(assetType, data); if (assetType > 0) { - for (auto i = 0; i < 4; ++i) { + for (auto i = 0ul; i < 4; ++i) { if (alphaUse.length() > i) { data.push_back(alphaUse[i]); } else { @@ -197,3 +227,5 @@ void Signer::pad(Data& data) const { data.insert(data.end(), 0); } } + +} // namespace TW::Stellar diff --git a/src/Stellar/Signer.h b/src/Stellar/Signer.h index c38896000b2..3968e354f42 100644 --- a/src/Stellar/Signer.h +++ b/src/Stellar/Signer.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Address.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/Stellar.pb.h" @@ -20,14 +18,16 @@ class Signer { /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; public: - const Proto::SigningInput& input; + const Proto::SigningInput& _input; - Signer(const Proto::SigningInput& input) : input(input) {} + Signer(const Proto::SigningInput& input) : _input(input) {} /// Signs the given transaction. std::string sign() const noexcept; Data encode(const Proto::SigningInput& input) const; + Data signaturePreimage() const; + Proto::SigningOutput compile(const Data& sig) const; private: static uint32_t operationType(const Proto::SigningInput& input); diff --git a/src/Sui/Entry.h b/src/Sui/Entry.h new file mode 100644 index 00000000000..86106198b80 --- /dev/null +++ b/src/Sui/Entry.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "rust/RustCoinEntry.h" + +namespace TW::Sui { + +class Entry final : public Rust::RustCoinEntry { +}; + +} // namespace TW::Sui diff --git a/src/THORChain/Entry.cpp b/src/THORChain/Entry.cpp deleted file mode 100644 index 67faa8a23dc..00000000000 --- a/src/THORChain/Entry.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Entry.h" - -#include "Signer.h" -#include "../proto/Cosmos.pb.h" - -using namespace TW::THORChain; -using namespace std; - -// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - auto input = Cosmos::Proto::SigningInput(); - input.ParseFromArray(dataIn.data(), (int)dataIn.size()); - auto serializedOut = Signer::sign(input).SerializeAsString(); - dataOut.insert(dataOut.end(), serializedOut.begin(), serializedOut.end()); -} - -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { - return Signer::signJSON(json, key); -} diff --git a/src/THORChain/Entry.h b/src/THORChain/Entry.h index c5a2d8ea776..863117825d0 100644 --- a/src/THORChain/Entry.h +++ b/src/THORChain/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -13,10 +11,7 @@ namespace TW::THORChain { /// Entry point for implementation of THORChain coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public Cosmos::Entry { -public: - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; +class Entry final : public Cosmos::Entry { }; } // namespace TW::THORChain diff --git a/src/THORChain/Signer.cpp b/src/THORChain/Signer.cpp deleted file mode 100644 index fa79a09404a..00000000000 --- a/src/THORChain/Signer.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Signer.h" -#include "../Cosmos/Signer.h" -#include "../proto/Cosmos.pb.h" - -#include -#include - -using namespace TW; -using namespace TW::THORChain; - -const std::string TYPE_PREFIX_MSG_SEND = "thorchain/MsgSend"; - -Cosmos::Proto::SigningOutput Signer::sign(Cosmos::Proto::SigningInput& input) noexcept { - for (auto i = 0; i < input.messages_size(); ++i) { - if (input.messages(i).has_send_coins_message()) { - input.mutable_messages(i)->mutable_send_coins_message()->set_type_prefix(TYPE_PREFIX_MSG_SEND); - } - } - return Cosmos::Signer::sign(input, TWCoinTypeTHORChain); -} - -std::string Signer::signJSON(const std::string& json, const Data& key) { - auto input = Cosmos::Proto::SigningInput(); - google::protobuf::util::JsonStringToMessage(json, &input); - input.set_private_key(key.data(), key.size()); - auto output = Signer::sign(input); - return output.json(); -} diff --git a/src/THORChain/Signer.h b/src/THORChain/Signer.h deleted file mode 100644 index 5488f281291..00000000000 --- a/src/THORChain/Signer.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "../Data.h" -#include "../proto/Cosmos.pb.h" -#include - -namespace TW::THORChain { - -/// Helper class that performs THORChain transaction signing. -class Signer { - public: - /// Signs a Proto::SigningInput transaction - static Cosmos::Proto::SigningOutput sign(Cosmos::Proto::SigningInput& input) noexcept; - /// Signs a json Proto::SigningInput with private key - static std::string signJSON(const std::string& json, const Data& key); -}; - -} // namespace TW::THORChain diff --git a/src/THORChain/Swap.cpp b/src/THORChain/Swap.cpp index 8004b6662ff..260337675e3 100644 --- a/src/THORChain/Swap.cpp +++ b/src/THORChain/Swap.cpp @@ -1,22 +1,23 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Swap.h" -#include #include "Coin.h" -#include "proto/THORChainSwap.pb.h" +#include "HexCoding.h" +#include +#include + +// ATOM +#include "Cosmos/Address.h" +#include "../proto/Cosmos.pb.h" // BTC #include "Bitcoin/SigHashType.h" #include "../proto/Bitcoin.pb.h" // ETH -#include "Ethereum/Address.h" #include "Ethereum/ABI/Function.h" -#include "Ethereum/ABI/ParamBase.h" -#include "Ethereum/ABI/ParamAddress.h" +#include "Ethereum/Address.h" #include "uint256.h" #include "../proto/Ethereum.pb.h" // BNB @@ -32,94 +33,151 @@ namespace TW::THORChainSwap { -TWCoinType chainCoinType(Chain chain) { - switch (chain) { - case Chain::ETH: return TWCoinTypeEthereum; - case Chain::BNB: return TWCoinTypeBinance; - case Chain::BTC: return TWCoinTypeBitcoin; - case Chain::THOR: - default: - return TWCoinTypeTHORChain; +static Data ethAddressStringToData(const std::string& asString) { + Data asData(20); + if (asString.empty() || !Ethereum::Address::isValid(asString)) { + return asData; } + auto address = Ethereum::Address(asString); + std::copy(address.bytes.begin(), address.bytes.end(), asData.data()); + return asData; } -std::string chainName(Chain chain) { +TWCoinType chainCoinType(Chain chain) { switch (chain) { - case Chain::ETH: return "ETH"; - case Chain::BNB: return "BNB"; - case Chain::BTC: return "BTC"; - case Chain::THOR: - default: - return "THOR"; + case Chain::ETH: + return TWCoinTypeEthereum; + case Chain::AVAX: + return TWCoinTypeAvalancheCChain; + case Chain::BNB: + return TWCoinTypeBinance; + case Chain::BTC: + return TWCoinTypeBitcoin; + case Chain::DOGE: + return TWCoinTypeDogecoin; + case Chain::BCH: + return TWCoinTypeBitcoinCash; + case Chain::LTC: + return TWCoinTypeLitecoin; + case Chain::ATOM: + return TWCoinTypeCosmos; + case Chain::BSC: + return TWCoinTypeSmartChain; + case Chain::THOR: + default: + return TWCoinTypeTHORChain; } } -std::string Swap::buildMemo(Chain toChain, const std::string& toSymbol, const std::string& toTokenId, const std::string& toAddress, uint64_t limit) { - std::string prefix = "SWAP"; - if (toChain == Chain::ETH) { - prefix = "="; +std::string chainName(Chain chain) { + switch (chain) { + case Chain::AVAX: + return "AVAX"; + case Chain::ETH: + return "ETH"; + case Chain::BNB: + return "BNB"; + case Chain::BSC: + return "BSC"; + case Chain::BTC: + return "BTC"; + case Chain::DOGE: + return "DOGE"; + case Chain::BCH: + return "BCH"; + case Chain::LTC: + return "LTC"; + case Chain::ATOM: + return "GAIA"; + case Chain::THOR: + default: + return "THOR"; } - const auto toCoinToken = (!toTokenId.empty() && toTokenId != "0x0000000000000000000000000000000000000000") ? toTokenId : toSymbol; - return prefix + ":" + chainName(toChain) + "." + toCoinToken + ":" + toAddress + ":" + std::to_string(limit); } bool validateAddress(Chain chain, const std::string& address) { return TW::validateAddress(chainCoinType(chain), address); } -std::tuple Swap::build( - Chain fromChain, - Chain toChain, - const std::string& fromAddress, - const std::string& toSymbol, - const std::string& toTokenId, - const std::string& toAddress, - const std::string& vaultAddress, - const std::string& routerAddress, - const std::string& fromAmount, - const std::string& toAmountLimit -) { - if (!validateAddress(fromChain, fromAddress)) { - return std::make_tuple({}, static_cast(Proto::ErrorCode::Error_Invalid_from_address), "Invalid from address"); +SwapBundled SwapBuilder::build(bool shortened) { + auto fromChain = static_cast(mFromAsset.chain()); + auto toChain = static_cast(mToAsset.chain()); + + if (!validateAddress(fromChain, mFromAddress)) { + return {.status_code = static_cast(Proto::ErrorCode::Error_Invalid_from_address), .error = "Invalid from address"}; } - if (!validateAddress(toChain, toAddress)) { - return std::make_tuple({}, static_cast(Proto::ErrorCode::Error_Invalid_to_address), "Invalid to address"); + if (!validateAddress(toChain, mToAddress)) { + return {.status_code = static_cast(Proto::ErrorCode::Error_Invalid_to_address), .error = "Invalid to address"}; } - uint64_t fromAmountNum = std::atoll(fromAmount.c_str()); - uint64_t toAmountLimitNum = std::atoll(toAmountLimit.c_str()); - const auto memo = buildMemo(toChain, toSymbol, toTokenId, toAddress, toAmountLimitNum); + uint256_t fromAmountNum = uint256_t(mFromAmount); + const auto memo = this->buildMemo(shortened); switch (fromChain) { - case Chain::BTC: { - Data out; - auto res = buildBitcoin(toChain, toSymbol, toTokenId, fromAddress, toAddress, vaultAddress, fromAmountNum, memo, out); - return std::make_tuple(std::move(out), std::move(std::get<0>(res)), std::move(std::get<1>(res))); - } + case Chain::THOR: + return buildRune(fromAmountNum, memo); + case Chain::BTC: + case Chain::DOGE: + case Chain::BCH: + case Chain::LTC: { + return buildBitcoin(fromAmountNum, memo, fromChain); + case Chain::BNB: + return buildBinance(mFromAsset, fromAmountNum, memo); + case Chain::ATOM: + return buildAtom(fromAmountNum, memo); + case Chain::ETH: + case Chain::AVAX: + case Chain::BSC: + return buildEth(fromAmountNum, memo); + } + default: + return {.status_code = static_cast(Proto::ErrorCode::Error_Unsupported_from_chain), .error = "Unsupported from chain: " + std::to_string(fromChain)}; + } +} +std::string SwapBuilder::buildMemo(bool shortened) noexcept { + uint64_t toAmountLimitNum = std::stoull(mToAmountLimit); + + // Memo: 'SWAP', or shortened '='; see https://dev.thorchain.org/thorchain-dev/concepts/memos + std::string prefix = shortened ? "=" : "SWAP"; + const auto& toChain = static_cast(mToAsset.chain()); + const auto& toTokenId = mToAsset.token_id(); + const auto& toSymbol = mToAsset.symbol(); + const auto toCoinToken = (!toTokenId.empty() && toTokenId != "0x0000000000000000000000000000000000000000") ? toTokenId : toSymbol; + std::stringstream memo; + memo << prefix + ":" + chainName(toChain) + "." + toCoinToken + ":" + mToAddress; + + memo << ":" << std::to_string(toAmountLimitNum); + if (mStreamParams.has_value()) { + uint64_t intervalNum = std::stoull(mStreamParams->mInterval); + uint64_t quantityNum = std::stoull(mStreamParams->mQuantity); + memo << "/" << std::to_string(intervalNum) << "/" << std::to_string(quantityNum); + } - case Chain::ETH: { - Data out; - auto res = buildEthereum(toChain, toSymbol, toTokenId, fromAddress, toAddress, vaultAddress, routerAddress, fromAmountNum, memo, out); - return std::make_tuple(std::move(out), std::move(std::get<0>(res)), std::move(std::get<1>(res))); + if (mAffFeeAddress.has_value() || mAffFeeRate.has_value() || mExtraMemo.has_value()) { + memo << ":"; + if (mAffFeeAddress.has_value()) { + memo << mAffFeeAddress.value(); } - - case Chain::BNB: { - Data out; - auto res = buildBinance(toChain, toSymbol, toTokenId, fromAddress, toAddress, vaultAddress, fromAmountNum, memo, out); - return std::make_tuple(std::move(out), std::move(std::get<0>(res)), std::move(std::get<1>(res))); + if (mAffFeeRate.has_value() || mExtraMemo.has_value()) { + memo << ":"; + if (mAffFeeRate.has_value()) { + memo << mAffFeeRate.value(); + } + if (mExtraMemo.has_value()) { + memo << ":" << mExtraMemo.value(); + } } - - case Chain::THOR: - default: - return std::make_tuple({}, static_cast(Proto::ErrorCode::Error_Unsupported_from_chain), "Unsupported from chain: " + std::to_string(toChain)); } + + return memo.str(); } -std::pair Swap::buildBitcoin(Chain toChain, const std::string& toSymbol, const std::string& toTokenId, const std::string& fromAddress, const std::string& toAddress, const std::string& vaultAddress, uint64_t amount, const std::string& memo, Data& out) { +SwapBundled SwapBuilder::buildBitcoin(const uint256_t& amount, const std::string& memo, Chain fromChain) { auto input = Bitcoin::Proto::SigningInput(); - + Data out; // Following fields must be set afterwards, before signing ... - input.set_hash_type(TWBitcoinSigHashTypeAll); + auto coinType = chainCoinType(fromChain); + input.set_hash_type(Bitcoin::hashTypeForCoin(coinType)); input.set_byte_fee(1); input.set_use_max_amount(false); // private_key[] @@ -127,37 +185,68 @@ std::pair Swap::buildBitcoin(Chain toChain, const std::string& // scripts[] // ... end - input.set_amount(amount); - input.set_to_address(vaultAddress); - input.set_change_address(fromAddress); - input.set_coin_type(TWCoinTypeBitcoin); + input.set_amount(static_cast(amount)); + input.set_to_address(mVaultAddress); + input.set_change_address(mFromAddress); + input.set_coin_type(coinType); input.set_output_op_return(memo); auto serialized = input.SerializeAsString(); out.insert(out.end(), serialized.begin(), serialized.end()); - return std::make_pair(0, ""); + return {.out = std::move(out)}; } +SwapBundled SwapBuilder::buildBinance(Proto::Asset fromAsset, const uint256_t& amount, const std::string& memo) { + auto input = Binance::Proto::SigningInput(); + Data out; -Data ethAddressStringToData(const std::string& asString) { - Data asData(20); - if (asString.empty() || !Ethereum::Address::isValid(asString)) { - return asData; + // Following fields must be set afterwards, before signing ... + input.set_chain_id(""); + input.set_account_number(0); + input.set_sequence(0); + input.set_source(0); + input.set_private_key(""); + // ... end + + input.set_memo(memo); + + auto& order = *input.mutable_send_order(); + + auto token = Binance::Proto::SendOrder::Token(); + token.set_denom(fromAsset.token_id().empty() ? "BNB" : fromAsset.token_id()); + token.set_amount(static_cast(amount)); + { + Binance::Address fromAddressBin; + Binance::Address::decode(mFromAddress, fromAddressBin); + auto input_ = order.add_inputs(); + input_->set_address(fromAddressBin.getKeyHash().data(), fromAddressBin.getKeyHash().size()); + *input_->add_coins() = token; } - auto address = Ethereum::Address(asString); - std::copy(address.bytes.begin(), address.bytes.end(), asData.data()); - return asData; + { + Binance::Address vaultAddressBin; + Binance::Address::decode(mVaultAddress, vaultAddressBin); + auto output = order.add_outputs(); + output->set_address(vaultAddressBin.getKeyHash().data(), vaultAddressBin.getKeyHash().size()); + *output->add_coins() = token; + } + + auto serialized = input.SerializeAsString(); + out.insert(out.end(), serialized.begin(), serialized.end()); + return {.out = std::move(out)}; } -std::pair Swap::buildEthereum(Chain toChain, const std::string& toSymbol, const std::string& toTokenId, const std::string& fromAddress, const std::string& toAddress, const std::string& vaultAddress, const std::string& routerAddress, uint64_t amount, const std::string& memo, Data& out) { +SwapBundled SwapBuilder::buildEth(const uint256_t& amount, const std::string& memo) { + Data out; auto input = Ethereum::Proto::SigningInput(); - + // EIP-1559 + input.set_tx_mode(this->mFromAsset.chain() == Proto::Chain::BSC ? Ethereum::Proto::Legacy : Ethereum::Proto::Enveloped); + const auto& toTokenId = mFromAsset.token_id(); // some sanity check / address conversion - Data vaultAddressBin = ethAddressStringToData(vaultAddress); - if (!Ethereum::Address::isValid(vaultAddress) || vaultAddressBin.size() != Ethereum::Address::size) { - return std::make_pair(static_cast(Proto::ErrorCode::Error_Invalid_vault_address), "Invalid vault address: " + vaultAddress); + Data vaultAddressBin = ethAddressStringToData(mVaultAddress); + if (!Ethereum::Address::isValid(mVaultAddress) || vaultAddressBin.size() != Ethereum::Address::size) { + return {.status_code = static_cast(Proto::ErrorCode::Error_Invalid_vault_address), .error = "Invalid vault address: " + mVaultAddress}; } - if (!Ethereum::Address::isValid(routerAddress)) { - return std::make_pair(static_cast(Proto::ErrorCode::Error_Invalid_router_address), "Invalid router address: " + routerAddress); + if (!toTokenId.empty() && !Ethereum::Address::isValid(*mRouterAddress)) { + return {.status_code = static_cast(Proto::ErrorCode::Error_Invalid_router_address), .error = "Invalid router address: " + *mRouterAddress}; } Data toAssetAddressBin = ethAddressStringToData(toTokenId); @@ -173,61 +262,103 @@ std::pair Swap::buildEthereum(Chain toChain, const std::string input.set_private_key(""); // ... end - input.set_to_address(routerAddress); - auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); - auto func = Ethereum::ABI::Function("deposit", std::vector>{ - std::make_shared(vaultAddressBin), - std::make_shared(toAssetAddressBin), - std::make_shared(uint256_t(amount)), - std::make_shared(memo) - }); - Data payload; - func.encode(payload); - transfer.set_data(payload.data(), payload.size()); - Data amountData = store(uint256_t(amount)); - transfer.set_amount(amountData.data(), amountData.size()); + input.set_to_address(*mRouterAddress); + if (!toTokenId.empty()) { + if (!mExpirationPolicy) { + auto now = std::chrono::system_clock::now(); + auto in_15_minutes = now + std::chrono::minutes(15); + mExpirationPolicy = std::chrono::duration_cast(in_15_minutes.time_since_epoch()).count(); + } + auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); + + // Ethereum::ABI::AbiProto::NamedParam + auto payload = Ethereum::ABI::Function::encodeFunctionCall("depositWithExpiry", { + std::make_shared(mVaultAddress), + std::make_shared(toTokenId), + std::make_shared(amount), + std::make_shared(memo), + std::make_shared(uint256_t(*mExpirationPolicy)), + }); + if (payload.has_value()) { + transfer.set_data(payload.value().data(), payload.value().size()); + } + + Data amountData = store(uint256_t(0)); + // if tokenId is set to 0x0000000000000000000000000000000000000000 this means we are sending ethereum and transfer amount also need to be set + if (toTokenId == "0x0000000000000000000000000000000000000000") { + amountData = store(uint256_t(amount)); + } + transfer.set_amount(amountData.data(), amountData.size()); + } else { + input.set_to_address(mVaultAddress); + auto& transfer = *input.mutable_transaction()->mutable_transfer(); + Data amountData = store(uint256_t(amount)); + transfer.set_amount(amountData.data(), amountData.size()); + transfer.set_data(memo.data(), memo.size()); + } auto serialized = input.SerializeAsString(); out.insert(out.end(), serialized.begin(), serialized.end()); - return std::make_pair(0, ""); + return {.out = std::move(out)}; } -std::pair Swap::buildBinance(Chain toChain, const std::string& toSymbol, const std::string& toTokenId, const std::string& fromAddress, const std::string& toAddress, const std::string& vaultAddress, uint64_t amount, const std::string& memo, Data& out) { - auto input = Binance::Proto::SigningInput(); - - // Following fields must be set afterwards, before signing ... - input.set_chain_id(""); - input.set_account_number(0); - input.set_sequence(0); - input.set_source(0); - input.set_private_key(""); - // ... end +SwapBundled SwapBuilder::buildAtom(const uint256_t& amount, const std::string& memo) { + if (!Cosmos::Address::isValid(mVaultAddress, "cosmos")) { + return {.status_code = static_cast(Proto::ErrorCode::Error_Invalid_vault_address), .error = "Invalid vault address: " + mVaultAddress}; + } + Data out; + auto input = Cosmos::Proto::SigningInput(); + input.set_signing_mode(Cosmos::Proto::Protobuf); + input.set_chain_id("cosmoshub-4"); input.set_memo(memo); - auto& order = *input.mutable_send_order(); + auto* msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); - auto token = Binance::Proto::SendOrder::Token(); - token.set_denom("BNB"); - token.set_amount(amount); - { - Binance::Address fromAddressBin; - Binance::Address::decode(fromAddress, fromAddressBin); - auto input = order.add_inputs(); - input->set_address(fromAddressBin.getKeyHash().data(), fromAddressBin.getKeyHash().size()); - *input->add_coins() = token; - } - { - Binance::Address vaultAddressBin; - Binance::Address::decode(vaultAddress, vaultAddressBin); - auto output = order.add_outputs(); - output->set_address(vaultAddressBin.getKeyHash().data(), vaultAddressBin.getKeyHash().size()); - *output->add_coins() = token; - } + message.set_from_address(mFromAddress); + message.set_to_address(mVaultAddress); + + auto* amountOfTx = message.add_amounts(); + amountOfTx->set_denom("uatom"); + amountOfTx->set_amount(amount.str()); auto serialized = input.SerializeAsString(); out.insert(out.end(), serialized.begin(), serialized.end()); - return std::make_pair(0, ""); + + return {.out = std::move(out)}; +} + +SwapBundled SwapBuilder::buildRune(const uint256_t& amount, const std::string& memo) { + auto* hrp = stringForHRP(TW::hrp(TWCoinTypeTHORChain)); + auto* chainId = TW::chainId(TWCoinTypeTHORChain); + + Bech32Address fromAddress(hrp); + Bech32Address::decode(mFromAddress, fromAddress, hrp); + + Data out; + + Cosmos::Proto::SigningInput input; + input.set_signing_mode(Cosmos::Proto::Protobuf); + input.set_chain_id(chainId); + + auto* msg = input.add_messages()->mutable_thorchain_deposit_message(); + msg->set_signer(fromAddress.getKeyHash().data(), fromAddress.getKeyHash().size()); + msg->set_memo(memo); + + auto* coin = msg->add_coins(); + coin->set_amount(toString(amount)); + coin->set_decimals(0); + + auto* asset = coin->mutable_asset(); + asset->set_chain(chainName(static_cast(mFromAsset.chain()))); + asset->set_symbol(mFromAsset.symbol()); + asset->set_ticker(mFromAsset.symbol()); + + auto serialized = input.SerializeAsString(); + out.insert(out.end(), serialized.begin(), serialized.end()); + + return {.out = std::move(out)}; } -} // namespace TW +} // namespace TW::THORChainSwap diff --git a/src/THORChain/Swap.h b/src/THORChain/Swap.h index 723b4ee83f2..94f35c5b8a3 100644 --- a/src/THORChain/Swap.h +++ b/src/THORChain/Swap.h @@ -1,15 +1,17 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Data.h" +#include "proto/THORChainSwap.pb.h" +#include "uint256.h" +#include #include #include +#include namespace TW::THORChainSwap { @@ -19,34 +21,156 @@ enum Chain { BTC = 1, ETH = 2, BNB = 3, + DOGE = 4, + BCH = 5, + LTC = 6, + ATOM = 7, + AVAX = 8, + BSC = 9, }; -/// Building THORChain cross-chain transactions -class Swap { -public: - /// Logic to build a native transaction on the source chain for a swap - /// Returns serialized SigningInput proto message, on the source chain, - /// and an optional error code + message - static std::tuple build( - Chain fromChain, - Chain toChain, - const std::string& fromAddress, // source address, on source chain, string format - const std::string& toSymbol, // destination coin symbol - const std::string& toTokenId, // destination token ID, on the destination chain, in case destination is a token, empty otherwise - const std::string& toAddress, // destination address, on destination chain, string format - const std::string& vaultAddress, // ThorChainSwap vault, on the source chain. Should be queried afresh, as it may change - const std::string& routerAddress, // ThorChain router, only in case of Ethereum source network - const std::string& fromAmount, // The source amount, as integer in the smallest native unit of the chain - const std::string& toAmountLimit // The minimum accepted destination amount. Actual destination amount will depend on current rates, limit amount can be used to prevent using very unfavorable rates. - ); - -protected: - static std::pair buildBitcoin(Chain toChain, const std::string& toSymbol, const std::string& toTokenId, const std::string& fromAddress, const std::string& toAddress, const std::string& vaultAddress, uint64_t amount, const std::string& memo, Data& out); - static std::pair buildEthereum(Chain toChain, const std::string& toSymbol, const std::string& toTokenId, const std::string& fromAddress, const std::string& toAddress, const std::string& vaultAddress, const std::string& routerAddress, uint64_t amount, const std::string& memo, Data& out); - static std::pair buildBinance(Chain toChain, const std::string& toSymbol, const std::string& toTokenId, const std::string& fromAddress, const std::string& toAddress, const std::string& vaultAddress, uint64_t amount, const std::string& memo, Data& out); +using SwapErrorCode = int; + +struct SwapBundled { + Data out{}; + SwapErrorCode status_code{0}; + std::string error{""}; +}; + +struct StreamParams { + std::string mInterval{"1"}; + std::string mQuantity{"0"}; +}; + +class SwapBuilder { + Proto::Asset mFromAsset; + Proto::Asset mToAsset; + std::string mFromAddress; + std::string mToAddress; + std::string mVaultAddress; + std::optional mRouterAddress{std::nullopt}; + std::string mFromAmount; + std::string mToAmountLimit{"0"}; + std::optional mStreamParams; + std::optional mAffFeeAddress{std::nullopt}; + std::optional mAffFeeRate{std::nullopt}; + std::optional mExtraMemo{std::nullopt}; + std::optional mExpirationPolicy{std::nullopt}; + + SwapBundled buildBitcoin(const uint256_t& amount, const std::string& memo, Chain fromChain); + SwapBundled buildBinance(Proto::Asset fromAsset, const uint256_t& amount, const std::string& memo); + SwapBundled buildEth(const uint256_t& amount, const std::string& memo); + SwapBundled buildAtom(const uint256_t& amount, const std::string& memo); + SwapBundled buildRune(const uint256_t& amount, const std::string& memo); public: - static std::string buildMemo(Chain toChain, const std::string& toSymbol, const std::string& toTokenId, const std::string& toAddress, uint64_t limit); + SwapBuilder() noexcept = default; + + static SwapBuilder builder() noexcept { return {}; } + + SwapBuilder& from(Proto::Asset fromAsset) noexcept { + mFromAsset = std::move(fromAsset); + return *this; + } + + SwapBuilder& fromAddress(std::string fromAddress) noexcept { + mFromAddress = std::move(fromAddress); + return *this; + } + + SwapBuilder& to(Proto::Asset toAsset) noexcept { + mToAsset = std::move(toAsset); + return *this; + } + + SwapBuilder& toAddress(std::string toAddress) noexcept { + mToAddress = std::move(toAddress); + return *this; + } + + SwapBuilder& vault(std::string vaultAddress) noexcept { + mVaultAddress = std::move(vaultAddress); + return *this; + } + + SwapBuilder& router(std::string router) noexcept { + if (!router.empty()) { + mRouterAddress = std::move(router); + } + return *this; + } + + SwapBuilder& affFeeAddress(std::string affFeeAddress) noexcept { + if (!affFeeAddress.empty()) { + mAffFeeAddress = std::move(affFeeAddress); + } else { + mAffFeeAddress = std::nullopt; + } + return *this; + } + + SwapBuilder& affFeeRate(std::string affFeeRate) noexcept { + if (!affFeeRate.empty()) { + mAffFeeRate = std::move(affFeeRate); + } else { + mAffFeeRate = std::nullopt; + } + return *this; + } + + SwapBuilder& extraMemo(std::string extraMemo) noexcept { + if (!extraMemo.empty()) { + mExtraMemo = std::move(extraMemo); + } else { + mExtraMemo = std::nullopt; + } + return *this; + } + + SwapBuilder& fromAmount(std::string fromAmount) noexcept { + mFromAmount = std::move(fromAmount); + return *this; + } + + SwapBuilder& toAmountLimit(std::string toAmountLimit) noexcept { + if (!toAmountLimit.empty()) { + mToAmountLimit = std::move(toAmountLimit); + } + return *this; + } + + SwapBuilder& streamInterval(const std::string& interval) noexcept { + if (!mStreamParams.has_value()) { + mStreamParams = StreamParams(); + } + if (!interval.empty()) { + mStreamParams->mInterval = interval; + } + return *this; + } + + SwapBuilder& streamQuantity(const std::string& quantity) noexcept { + if (!mStreamParams.has_value()) { + mStreamParams = StreamParams(); + } + if (!quantity.empty()) { + mStreamParams->mQuantity = quantity; + } + return *this; + } + + SwapBuilder& expirationPolicy(std::size_t expirationTime) noexcept { + if (expirationTime > 0) { + mExpirationPolicy = expirationTime; + } else { + mExpirationPolicy = std::nullopt; + } + return *this; + } + + std::string buildMemo(bool shortened = true) noexcept; + + SwapBundled build(bool shortened = true); }; -} // namespace TW +} // namespace TW::THORChainSwap diff --git a/src/THORChain/TWSwap.cpp b/src/THORChain/TWSwap.cpp index a13b7823d92..0cdab651fd3 100644 --- a/src/THORChain/TWSwap.cpp +++ b/src/THORChain/TWSwap.cpp @@ -1,100 +1,115 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include +#include "Data.h" #include "Swap.h" #include "proto/THORChainSwap.pb.h" -#include "Data.h" +#include -using namespace TW::THORChainSwap; using namespace TW; -TWData *_Nonnull TWTHORChainSwapBuildSwap(TWData *_Nonnull input) { - Proto::SwapInput inputProto; - Proto::SwapOutput outputProto; +TWData* _Nonnull TWTHORChainSwapBuildSwap(TWData* _Nonnull input) { + THORChainSwap::Proto::SwapInput inputProto; + THORChainSwap::Proto::SwapOutput outputProto; if (!inputProto.ParseFromArray(TWDataBytes(input), static_cast(TWDataSize(input)))) { // error - outputProto.mutable_error()->set_code(Proto::ErrorCode::Error_Input_proto_deserialization); + outputProto.mutable_error()->set_code(THORChainSwap::Proto::ErrorCode::Error_Input_proto_deserialization); outputProto.mutable_error()->set_message("Could not deserialize input proto"); - auto outputData = data(outputProto.SerializeAsString()); + auto outputData = TW::data(outputProto.SerializeAsString()); return TWDataCreateWithBytes(outputData.data(), outputData.size()); } - const auto fromChain = inputProto.from_chain(); + const auto fromChain = inputProto.from_asset().chain(); const auto toChain = inputProto.to_asset().chain(); - auto res = Swap::build( - static_cast(static_cast(fromChain)), - static_cast(static_cast(toChain)), - inputProto.from_address(), - inputProto.to_asset().symbol(), - inputProto.to_asset().token_id(), - inputProto.to_address(), - inputProto.vault_address(), - inputProto.router_address(), - inputProto.from_amount(), - inputProto.to_amount_limit() - ); + auto builder = THORChainSwap::SwapBuilder::builder(); + builder + .from(inputProto.from_asset()) + .to(inputProto.to_asset()) + .fromAddress(inputProto.from_address()) + .toAddress(inputProto.to_address()) + .vault(inputProto.vault_address()) + .router(inputProto.router_address()) + .fromAmount(inputProto.from_amount()) + .toAmountLimit(inputProto.to_amount_limit()) + .affFeeAddress(inputProto.affiliate_fee_address()) + .affFeeRate(inputProto.affiliate_fee_rate_bp()) + .extraMemo(inputProto.extra_memo()) + .expirationPolicy(inputProto.expiration_time()); + if (inputProto.has_stream_params()) { + const auto& streamParams = inputProto.stream_params(); + builder + .streamInterval(streamParams.interval()) + .streamQuantity(streamParams.quantity()); + } + auto&& [txInput, errorCode, error] = builder.build(); outputProto.set_from_chain(fromChain); outputProto.set_to_chain(toChain); - if (std::get<1>(res) != 0) { + if (errorCode != 0) { // error - outputProto.mutable_error()->set_code(static_cast(std::get<1>(res))); - outputProto.mutable_error()->set_message(std::get<2>(res)); + outputProto.mutable_error()->set_code(static_cast(errorCode)); + outputProto.mutable_error()->set_message(error); } else { // no error - outputProto.mutable_error()->set_code(Proto::ErrorCode::OK); + outputProto.mutable_error()->set_code(THORChainSwap::Proto::ErrorCode::OK); outputProto.mutable_error()->set_message(""); - const Data& txInput = std::get<0>(res); switch (fromChain) { - case Proto::BTC: - { - Bitcoin::Proto::SigningInput btcInput; - if (!btcInput.ParseFromArray(txInput.data(), static_cast(txInput.size()))) { - outputProto.mutable_error()->set_code(Proto::ErrorCode::Error_Input_proto_deserialization); - outputProto.mutable_error()->set_message("Could not deserialize BTC input"); - } else { - *outputProto.mutable_bitcoin() = btcInput; - } - } - break; + case THORChainSwap::Proto::BTC: + case THORChainSwap::Proto::DOGE: + case THORChainSwap::Proto::BCH: + case THORChainSwap::Proto::LTC: { + Bitcoin::Proto::SigningInput btcInput; + if (!btcInput.ParseFromArray(txInput.data(), static_cast(txInput.size()))) { + outputProto.mutable_error()->set_code(THORChainSwap::Proto::ErrorCode::Error_Input_proto_deserialization); + outputProto.mutable_error()->set_message("Could not deserialize BTC input"); + } else { + *outputProto.mutable_bitcoin() = btcInput; + } + } break; + + case THORChainSwap::Proto::ETH: + case THORChainSwap::Proto::BSC: + case THORChainSwap::Proto::AVAX: { + Ethereum::Proto::SigningInput ethInput; + if (!ethInput.ParseFromArray(txInput.data(), static_cast(txInput.size()))) { + outputProto.mutable_error()->set_code(THORChainSwap::Proto::ErrorCode::Error_Input_proto_deserialization); + outputProto.mutable_error()->set_message("Could not deserialize ETH input"); + } else { + *outputProto.mutable_ethereum() = ethInput; + } + } break; - case Proto::ETH: - { - Ethereum::Proto::SigningInput ethInput; - if (!ethInput.ParseFromArray(txInput.data(), static_cast(txInput.size()))) { - outputProto.mutable_error()->set_code(Proto::ErrorCode::Error_Input_proto_deserialization); - outputProto.mutable_error()->set_message("Could not deserialize ETH input"); - } else { - *outputProto.mutable_ethereum() = ethInput; - } - } - break; + case THORChainSwap::Proto::BNB: { + Binance::Proto::SigningInput bnbInput; + if (!bnbInput.ParseFromArray(txInput.data(), static_cast(txInput.size()))) { + outputProto.mutable_error()->set_code(THORChainSwap::Proto::ErrorCode::Error_Input_proto_deserialization); + outputProto.mutable_error()->set_message("Could not deserialize BNB input"); + } else { + *outputProto.mutable_binance() = bnbInput; + } + } break; - case Proto::BNB: - { - Binance::Proto::SigningInput bnbInput; - if (!bnbInput.ParseFromArray(txInput.data(), static_cast(txInput.size()))) { - outputProto.mutable_error()->set_code(Proto::ErrorCode::Error_Input_proto_deserialization); - outputProto.mutable_error()->set_message("Could not deserialize BNB input"); - } else { - *outputProto.mutable_binance() = bnbInput; - } - } - break; + case THORChainSwap::Proto::THOR: + case THORChainSwap::Proto::ATOM: { + Cosmos::Proto::SigningInput cosmosInput; + if (!cosmosInput.ParseFromArray(txInput.data(), static_cast(txInput.size()))) { + outputProto.mutable_error()->set_code(THORChainSwap::Proto::ErrorCode::Error_Input_proto_deserialization); + outputProto.mutable_error()->set_message("Could not deserialize ATOM input"); + } else { + *outputProto.mutable_cosmos() = cosmosInput; + } + } break; - default: - outputProto.mutable_error()->set_code(Proto::ErrorCode::Error_Unsupported_from_chain); - outputProto.mutable_error()->set_message(std::string("Unsupported from chain ") + std::to_string(fromChain)); - break; + default: + outputProto.mutable_error()->set_code(THORChainSwap::Proto::ErrorCode::Error_Unsupported_from_chain); + outputProto.mutable_error()->set_message(std::string("Unsupported from chain ") + std::to_string(fromChain)); + break; } } // serialize output - auto outputData = data(outputProto.SerializeAsString()); + auto outputData = TW::data(outputProto.SerializeAsString()); return TWDataCreateWithBytes(outputData.data(), outputData.size()); } diff --git a/src/Tezos/Address.cpp b/src/Tezos/Address.cpp index 046bab28ce6..f079b45ce96 100644 --- a/src/Tezos/Address.cpp +++ b/src/Tezos/Address.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" #include "BinaryCoding.h" @@ -15,16 +13,16 @@ #include -using namespace TW; -using namespace TW::Tezos; +namespace TW::Tezos { /// Address prefixes. -const std::array tz1Prefix{6, 161, 159}; -const std::array tz2Prefix{6, 161, 161}; -const std::array tz3Prefix{6, 161, 164}; +const std::array tz1Prefix{6, 161, 159}; +const std::array tz2Prefix{6, 161, 161}; +const std::array tz3Prefix{6, 161, 164}; +const std::array kt1Prefix{2, 90, 121}; bool Address::isValid(const std::string& string) { - const auto decoded = Base58::bitcoin.decodeCheck(string); + const auto decoded = Base58::decodeCheck(string); if (decoded.size() != Address::size) { return false; } @@ -36,13 +34,25 @@ bool Address::isValid(const std::string& string) { return true; } + // contract prefix + if (std::equal(kt1Prefix.begin(), kt1Prefix.end(), decoded.begin())) { + return true; + } + return false; } Address::Address(const PublicKey& publicKey) { auto encoded = Data(publicKey.bytes.begin(), publicKey.bytes.end()); auto hash = Hash::blake2b(encoded, 20); - auto addressData = Data({6, 161, 159}); + Data addressData; + if (publicKey.type == TWPublicKeyTypeSECP256k1) { + addressData = Data({6, 161, 161}); + } else if (publicKey.type == TWPublicKeyTypeED25519){ + addressData = Data({6, 161, 159}); + } else { + throw std::invalid_argument("unsupported public key type"); + } append(addressData, hash); if (addressData.size() != Address::size) throw std::invalid_argument("Invalid address key data"); @@ -51,7 +61,7 @@ Address::Address(const PublicKey& publicKey) { std::string Address::deriveOriginatedAddress(const std::string& operationHash, int operationIndex) { // Decode and remove 2 byte prefix. - auto decoded = Base58::bitcoin.decodeCheck(operationHash); + auto decoded = Base58::decodeCheck(operationHash); decoded.erase(decoded.begin(), decoded.begin() + 2); TW::encode32BE(operationIndex, decoded); @@ -60,10 +70,41 @@ std::string Address::deriveOriginatedAddress(const std::string& operationHash, i auto prefix = Data({2, 90, 121}); prefix.insert(prefix.end(), hash.begin(), hash.end()); - return Base58::bitcoin.encodeCheck(prefix); + return Base58::encodeCheck(prefix); } -Data Address::forge() const { +Data Address::forgePKH() const { std::string s = string(); return forgePublicKeyHash(s); } + +Data Address::forge() const { + // normal address + // https://github.com/ecadlabs/taquito/blob/master/packages/taquito-local-forging/src/codec.ts#L183 + if (std::equal(tz1Prefix.begin(), tz1Prefix.end(), bytes.begin()) || + std::equal(tz2Prefix.begin(), tz2Prefix.end(), bytes.begin()) || + std::equal(tz3Prefix.begin(), tz3Prefix.end(), bytes.begin())) { + std::string s = string(); + Data forgedPKH = forgePublicKeyHash(s); + Data forged = Data(); + forged.insert(forged.end(), 0x00); + forged.insert(forged.end(), forgedPKH.begin(), forgedPKH.end()); + return forged; + } + + // contract address + // https://github.com/ecadlabs/taquito/blob/master/packages/taquito-local-forging/src/codec.ts#L183 + if (std::equal(kt1Prefix.begin(), kt1Prefix.end(), bytes.begin())) { + std::string s = string(); + Data forgedPrefix = forgePrefix(kt1Prefix, s); + Data forged = Data(); + forged.insert(forged.end(), 0x01); + forged.insert(forged.end(), forgedPrefix.begin(), forgedPrefix.end()); + forged.insert(forged.end(), 0x00); + return forged; + } + + throw std::invalid_argument("invalid address"); +} + +} // namespace TW::Tezos diff --git a/src/Tezos/Address.h b/src/Tezos/Address.h index 88fb46bc79e..b2f83cffcbd 100644 --- a/src/Tezos/Address.h +++ b/src/Tezos/Address.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../Base58Address.h" -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include @@ -33,6 +31,9 @@ class Address : public TW::Base58Address<23> { /// Forge an address to hex bytes. Data forge() const; + + // without type prefix + Data forgePKH() const; }; } // namespace TW::Tezos diff --git a/src/Tezos/BinaryCoding.cpp b/src/Tezos/BinaryCoding.cpp index 35f2d9cee2b..7300157f5ca 100644 --- a/src/Tezos/BinaryCoding.cpp +++ b/src/Tezos/BinaryCoding.cpp @@ -1,11 +1,9 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "../Base58.h" -#include "../Data.h" +#include "Data.h" #include "../HexCoding.h" #include "../PublicKey.h" #include "../PrivateKey.h" @@ -13,38 +11,56 @@ #include #include -using namespace TW; +namespace TW::Tezos { -std::string base58ToHex(const std::string& string, size_t prefixLength, uint8_t* prefix) { - const auto decoded = Base58::bitcoin.decodeCheck(string); +std::string base58ToHex(const std::string& string, size_t prefixLength) { + const auto decoded = Base58::decodeCheck(string); if (decoded.size() < prefixLength) { return ""; } - return TW::hex(decoded.data() + prefixLength, decoded.data() + decoded.size()); + Data v(decoded.data() + prefixLength, decoded.data() + decoded.size()); + return TW::hex(v); } PublicKey parsePublicKey(const std::string& publicKey) { - const auto decoded = Base58::bitcoin.decodeCheck(publicKey); + const auto decoded = Base58::decodeCheck(publicKey); - std::array prefix = {13, 15, 37, 217}; - auto pk = Data(); + std::array prefix; + enum TWPublicKeyType type; + std::array ed25519Prefix = {13, 15, 37, 217}; + std::array secp256k1Prefix = {3, 254, 226, 86}; - if (decoded.size() != 32 + prefix.size()) { + if (std::equal(std::begin(ed25519Prefix), std::end(ed25519Prefix), std::begin(decoded))) { + prefix = ed25519Prefix; + type = TWPublicKeyTypeED25519; + } else if (std::equal(std::begin(secp256k1Prefix), std::end(secp256k1Prefix), std::begin(decoded))) { + prefix = secp256k1Prefix; + type = TWPublicKeyTypeSECP256k1; + } else { + throw std::invalid_argument("Unsupported Public Key Type"); + } + auto pk = Data(); + if (type == TWPublicKeyTypeED25519 && decoded.size() != 32 + prefix.size()) { + throw std::invalid_argument("Invalid Public Key"); + } + if (type == TWPublicKeyTypeSECP256k1 && decoded.size() != 33 + prefix.size()) { throw std::invalid_argument("Invalid Public Key"); } append(pk, Data(decoded.begin() + prefix.size(), decoded.end())); - return PublicKey(pk, TWPublicKeyTypeED25519); + return PublicKey(pk, type); } PrivateKey parsePrivateKey(const std::string& privateKey) { - const auto decoded = Base58::bitcoin.decodeCheck(privateKey); + const auto decoded = Base58::decodeCheck(privateKey); auto pk = Data(); - auto prefix_size = 4; + auto prefix_size = 4ul; if (decoded.size() != 32 + prefix_size) { throw std::invalid_argument("Invalid Public Key"); } append(pk, Data(decoded.begin() + prefix_size, decoded.end())); return PrivateKey(pk); -} \ No newline at end of file +} + +} // namespace TW::Tezos diff --git a/src/Tezos/BinaryCoding.h b/src/Tezos/BinaryCoding.h index 837a4e48b94..24849592375 100644 --- a/src/Tezos/BinaryCoding.h +++ b/src/Tezos/BinaryCoding.h @@ -1,19 +1,19 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include "../PrivateKey.h" #include -using namespace TW; +namespace TW::Tezos { PublicKey parsePublicKey(const std::string& publicKey); PrivateKey parsePrivateKey(const std::string& privateKey); -std::string base58ToHex(const std::string& data, size_t prefixLength, uint8_t* prefix); +std::string base58ToHex(const std::string& data, size_t prefixLength); + +} // namespace TW::Tezos diff --git a/src/Tezos/Entry.cpp b/src/Tezos/Entry.cpp index 2597237bb92..0b197d93e5a 100644 --- a/src/Tezos/Entry.cpp +++ b/src/Tezos/Entry.cpp @@ -1,31 +1,71 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" +#include "proto/TransactionCompiler.pb.h" #include "Signer.h" -using namespace TW::Tezos; -using namespace std; +namespace TW::Tezos { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto operationList = TW::Tezos::OperationList(input.operation_list().branch()); + for (TW::Tezos::Proto::Operation operation : input.operation_list().operations()) { + operationList.addOperation(operation); + } + + auto preImage = Signer().buildUnsignedTx(operationList); + + // get preImage hash + Data watermarkedData = Data(); + watermarkedData.push_back(0x03); + append(watermarkedData, preImage); + auto preImageHash = Hash::blake2b(watermarkedData, 32); + + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, [[maybe_unused]] const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() != 1) { + output.set_error(Common::Proto::Error_signatures_count); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_signatures_count)); + return; + } + + auto operationList = TW::Tezos::OperationList(input.operation_list().branch()); + for (TW::Tezos::Proto::Operation operation : input.operation_list().operations()) { + operationList.addOperation(operation); + } + auto tx = Signer().buildSignedTx(operationList, signatures[0]); + + output.set_encoded(tx.data(), tx.size()); + }); +} + +} // namespace TW::Tezos diff --git a/src/Tezos/Entry.h b/src/Tezos/Entry.h index e5198945574..3354a2ee744 100644 --- a/src/Tezos/Entry.h +++ b/src/Tezos/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,13 +10,16 @@ namespace TW::Tezos { /// Entry point for implementation of Tezos coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + bool supportsJSONSigning() const override { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; }; } // namespace TW::Tezos diff --git a/src/Tezos/Forging.cpp b/src/Tezos/Forging.cpp index b288b0935bf..9c7b22b41ec 100644 --- a/src/Tezos/Forging.cpp +++ b/src/Tezos/Forging.cpp @@ -1,21 +1,27 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. +#include "Forging.h" #include "Address.h" #include "BinaryCoding.h" -#include "../Base58.h" -#include "../Data.h" #include "../HexCoding.h" #include "../proto/Tezos.pb.h" - #include -using namespace TW; -using namespace TW::Tezos; -using namespace TW::Tezos::Proto; +namespace TW::Tezos { + +namespace { + +constexpr const char* gTezosContractAddressPrefix{"KT1"}; + +void encodePrefix(const std::string& address, Data& forged) { + const auto decoded = Base58::decodeCheck(address); + constexpr auto prefixSize{3}; + forged.insert(forged.end(), decoded.begin() + prefixSize, decoded.end()); +} + +} // namespace // Forge the given boolean into a hex encoded string. Data forgeBool(bool input) { @@ -23,6 +29,39 @@ Data forgeBool(bool input) { return Data{result}; } +Data forgeInt32(int value, int len) { + Data out(len); + for (int i = len - 1; i >= 0; i--, value >>= 8) { + out[i] = (value & 0xFF); + } + return out; +} + +Data forgeString(const std::string& value, std::size_t len) { + auto bytes = data(value); + auto result = forgeInt32(static_cast(bytes.size()), static_cast(len)); + append(result, bytes); + return result; +} + +Data forgeEntrypoint(const std::string& value) { + if (value == "default") + return Data{0x00}; + else if (value == "root") + return Data{0x01}; + else if (value == "do") + return Data{0x02}; + else if (value == "set_delegate") + return Data{0x03}; + else if (value == "remove_delegate") + return Data{0x04}; + else { + Data forged{0xff}; + append(forged, forgeString(value, 1)); + return forged; + } +} + // Forge the given public key hash into a hex encoded string. // Note: This function supports tz1, tz2 and tz3 addresses. Data forgePublicKeyHash(const std::string& publicKeyHash) { @@ -41,21 +80,61 @@ Data forgePublicKeyHash(const std::string& publicKeyHash) { default: throw std::invalid_argument("Invalid Prefix"); } - const auto decoded = Base58::bitcoin.decodeCheck(publicKeyHash); + encodePrefix(publicKeyHash, forged); + return forged; +} + +Data forgeAddress(const std::string& address) { + if (address.size() < 3) { + throw std::invalid_argument("Invalid address size"); + } + auto prefix = address.substr(0, 3); + + if (prefix == "tz1" || prefix == "tz2" || prefix == "tz3") { + Data forged{0x00}; + append(forged, forgePublicKeyHash(address)); + return forged; + } + + if (prefix == gTezosContractAddressPrefix) { + Data forged{0x01}; + encodePrefix(address, forged); + forged.emplace_back(0x00); + return forged; + } + throw std::invalid_argument("Invalid Prefix"); +} + +// https://github.com/ecadlabs/taquito/blob/master/packages/taquito-local-forging/src/codec.ts#L19 +Data forgePrefix(std::array prefix, const std::string& val) { + const auto decoded = Base58::decodeCheck(val); + if (!std::equal(prefix.begin(), prefix.end(), decoded.begin())) { + throw std::invalid_argument("prefix not match"); + } + const auto prefixSize = 3; + Data forged = Data(); forged.insert(forged.end(), decoded.begin() + prefixSize, decoded.end()); return forged; } // Forge the given public key into a hex encoded string. Data forgePublicKey(PublicKey publicKey) { - std::array prefix = {13, 15, 37, 217}; + std::string tag; + std::array prefix; + if (publicKey.type == TWPublicKeyTypeED25519) { + prefix = {13, 15, 37, 217}; + tag = "00"; + } else if (publicKey.type == TWPublicKeyTypeSECP256k1) { + prefix = {3, 254, 226, 86}; + tag = "01"; + } auto data = Data(prefix.begin(), prefix.end()); auto bytes = Data(publicKey.bytes.begin(), publicKey.bytes.end()); append(data, bytes); - auto pk = Base58::bitcoin.encodeCheck(data); - auto decoded = "00" + base58ToHex(pk, 4, prefix.data()); + auto pk = Base58::encodeCheck(data); + auto decoded = tag + base58ToHex(pk, 4); return parse_hex(decoded); } @@ -63,27 +142,36 @@ Data forgePublicKey(PublicKey publicKey) { Data forgeZarith(uint64_t input) { Data forged = Data(); while (input >= 0x80) { - forged.push_back(static_cast((input & 0xff) | 0x80)); + forged.push_back(static_cast((input & 0xff) | 0x80)); input >>= 7; } - forged.push_back(static_cast(input)); + forged.push_back(static_cast(input)); return forged; } // Forge the given operation. -Data forgeOperation(const Operation& operation) { +Data forgeOperation(const Proto::Operation& operation) { + using namespace Proto; auto forged = Data(); auto source = Address(operation.source()); - auto forgedSource = source.forge(); + auto forgedSource = source.forgePKH(); //https://github.com/ecadlabs/taquito/blob/master/packages/taquito-local-forging/src/schema/operation.ts#L40 auto forgedFee = forgeZarith(operation.fee()); auto forgedCounter = forgeZarith(operation.counter()); auto forgedGasLimit = forgeZarith(operation.gas_limit()); auto forgedStorageLimit = forgeZarith(operation.storage_limit()); if (operation.kind() == Operation_OperationKind_REVEAL) { - auto publicKey = PublicKey(data(operation.reveal_operation_data().public_key()), TWPublicKeyTypeED25519); + enum TWPublicKeyType type; + if (operation.reveal_operation_data().public_key().size() == 32) { + type = TWPublicKeyTypeED25519; + } else if (operation.reveal_operation_data().public_key().size() == 33) { + type = TWPublicKeyTypeSECP256k1; + } else { + throw std::invalid_argument("unsupported public key type"); + } + auto publicKey = PublicKey(data(operation.reveal_operation_data().public_key()), type); auto forgedPublicKey = forgePublicKey(publicKey); - + forged.push_back(Operation_OperationKind_REVEAL); append(forged, forgedSource); append(forged, forgedFee); @@ -116,20 +204,112 @@ Data forgeOperation(const Operation& operation) { if (operation.kind() == Operation_OperationKind_TRANSACTION) { auto forgedAmount = forgeZarith(operation.transaction_operation_data().amount()); - auto forgedDestination = Address(operation.transaction_operation_data().destination()).forge(); + auto forgedDestination = forgeAddress(operation.transaction_operation_data().destination()); - forged.push_back(Operation_OperationKind_TRANSACTION); + forged.emplace_back(Operation_OperationKind_TRANSACTION); append(forged, forgedSource); append(forged, forgedFee); append(forged, forgedCounter); append(forged, forgedGasLimit); append(forged, forgedStorageLimit); append(forged, forgedAmount); - append(forged, forgeBool(false)); append(forged, forgedDestination); - append(forged, forgeBool(false)); + if (!operation.transaction_operation_data().has_parameters() && operation.transaction_operation_data().encoded_parameter().empty()) { + append(forged, forgeBool(false)); + } else if (operation.transaction_operation_data().has_parameters()) { + append(forged, forgeBool(true)); + auto& parameters = operation.transaction_operation_data().parameters(); + switch (parameters.parameters_case()) { + case OperationParameters::kFa12Parameters: + append(forged, forgeEntrypoint(parameters.fa12_parameters().entrypoint())); + append(forged, forgeArray(forgeMichelson(FA12ParameterToMichelson(parameters.fa12_parameters())))); + break; + case OperationParameters::kFa2Parameters: + append(forged, forgeEntrypoint(parameters.fa2_parameters().entrypoint())); + append(forged, forgeArray(forgeMichelson(FA2ParameterToMichelson(parameters.fa2_parameters())))); + break; + case OperationParameters::PARAMETERS_NOT_SET: + break; + } + } else { + append(forged, TW::data(operation.transaction_operation_data().encoded_parameter())); + } return forged; } throw std::invalid_argument("Invalid operation kind"); } + +Data forgePrim(const PrimValue& value) { + Data forged; + if (value.prim == "Pair") { + // https://tezos.gitlab.io/developer/encodings.html?highlight=pair#pairs + forged.reserve(2); + constexpr uint8_t nbArgs = 2; + // https://github.com/ecadlabs/taquito/blob/fd84d627171d24ce7ba81dd7b18763a95f16a99c/packages/taquito-local-forging/src/michelson/codec.ts#L195 + // https://github.com/baking-bad/netezos/blob/0bfd6db4e85ab1c99fb55503e476fe67cebd2dc5/Netezos/Forging/Local/LocalForge.Forgers.cs#L199 + const uint8_t preamble = static_cast(std::min(2 * nbArgs + static_cast(value.anots.size()) + 0x03, 9)); + forged.emplace_back(preamble); + forged.emplace_back(PrimType::Pair); + Data subForged; + for (auto&& cur : value.args) { + append(subForged, forgeMichelson(cur.value)); + } + append(forged, subForged); + } + return forged; +} + +Data forgeMichelson(const MichelsonValue::MichelsonVariant& value) { + auto visit_functor = [](const MichelsonValue::MichelsonVariant& value) -> Data { + if (std::holds_alternative(value)) { + return forgePrim(std::get(value)); + } else if (std::holds_alternative(value)) { + Data forged{1}; + append(forged, forgeString(std::get(value).string)); + return forged; + } else if (std::holds_alternative(value)) { + Data forged{0}; + auto res = int256_t(std::get(value)._int); + append(forged, forgeMichelInt(res)); + return forged; + } else if (std::holds_alternative(value)) { + return {}; + } else if (std::holds_alternative(value)) { + // array + Data forged{2}; + Data subForged; + auto array = std::get(value); + for (auto&& cur : array) { + std::visit([&subForged](auto&& arg) { append(subForged, forgeMichelson(arg)); }, cur); + } + append(forged, forgeArray(subForged)); + return forged; + } else { + throw std::invalid_argument("Invalid variant"); + } + }; + + return std::visit(visit_functor, value); +} + +Data forgeArray(const Data& data) { + auto forged = forgeInt32(static_cast(data.size())); + append(forged, data); + return forged; +} + +Data forgeMichelInt(const TW::int256_t& value) { + Data forged; + auto abs = boost::multiprecision::abs(value); + forged.emplace_back(static_cast(value.sign() < 0 ? (abs & 0x3f - 0x40) : (abs & 0x3f))); + abs >>= 6; + while (abs > 0) { + forged[forged.size() - 1] |= 0x80; + forged.emplace_back(static_cast(abs & 0x7F)); + abs >>= 7; + } + return forged; +} + +} // namespace TW::Tezos diff --git a/src/Tezos/Forging.h b/src/Tezos/Forging.h index 45f4624e282..878c9e35880 100644 --- a/src/Tezos/Forging.h +++ b/src/Tezos/Forging.h @@ -1,19 +1,35 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. +#pragma once + +#include "Michelson.h" +#include "uint256.h" #include "../PublicKey.h" #include "../proto/Tezos.pb.h" #include +#include +#include using namespace TW; -using namespace TW::Tezos::Proto; + +namespace TW::Tezos { Data forgeBool(bool input); -Data forgeOperation(const Operation& operation); +Data forgeOperation(const Proto::Operation& operation); +Data forgeAddress(const std::string& address); +Data forgeArray(const Data& data); Data forgePublicKeyHash(const std::string& publicKeyHash); +Data forgePrefix(std::array prefix, const std::string& val); Data forgePublicKey(PublicKey publicKey); Data forgeZarith(uint64_t input); +Data forgeInt32(int value, int len = 4); +Data forgeString(const std::string& value, std::size_t len = 4); +Data forgeEntrypoint(const std::string& value); +Data forgeMichelson(const MichelsonValue::MichelsonVariant& value); +Data forgeMichelInt(const TW::int256_t& value); +Data forgePrim(const PrimValue& value); + +} // namespace TW::Tezos diff --git a/src/Tezos/MessageSigner.cpp b/src/Tezos/MessageSigner.cpp new file mode 100644 index 00000000000..25234f170ed --- /dev/null +++ b/src/Tezos/MessageSigner.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include +#include + +#include "Base58.h" +#include "HexCoding.h" +#include "Tezos/MessageSigner.h" + +namespace TW::Tezos { + +static const Data gEdSigPrefix{9, 245, 205, 134, 18}; +static const std::string gMsgPrefix{"Tezos Signed Message:"}; + +std::string MessageSigner::inputToPayload(const std::string& input) { + using namespace std::string_literals; + auto bytes = data(input); + size_t bytesLength = bytes.size(); + std::string addPadding = std::string(8 - std::to_string(bytesLength).size(), '0') + hex(uint64_t(bytesLength)); + std::string paddedBytesLength = addPadding.substr(addPadding.size() - 8); + std::string payloadBytes = "05"s + "01"s + paddedBytesLength + hex(bytes); + return payloadBytes; +} + +std::string MessageSigner::formatMessage(const std::string& message, const std::string& dAppUrl) { + auto now = std::chrono::system_clock::now(); + auto now_time = std::chrono::system_clock::to_time_t(now); + auto now_ms = std::chrono::duration_cast(now.time_since_epoch()) % 1000; + std::ostringstream oss; + oss << gMsgPrefix << " " << dAppUrl << " "; + oss << std::put_time(std::gmtime(&now_time), "%FT%T.") << std::setw(3) << std::setfill('0') << now_ms.count() << "Z"; + oss << " " << message; + return oss.str(); +} + +std::string MessageSigner::signMessage(const PrivateKey& privateKey, const std::string& message) { + auto signature = privateKey.sign(Hash::blake2b(parse_hex(message), 32), TWCurveED25519); + return Base58::encodeCheck(concat(gEdSigPrefix, signature)); +} + +bool MessageSigner::verifyMessage(const PublicKey& publicKey, const std::string& message, const std::string& signature) noexcept { + auto decoded = Base58::decodeCheck(signature); + auto rawSignature = subData(decoded, gEdSigPrefix.size()); + auto msg = Hash::blake2b(parse_hex(message), 32); + return publicKey.verify(rawSignature, msg); +} + +} // namespace TW::Tezos diff --git a/src/Tezos/MessageSigner.h b/src/Tezos/MessageSigner.h new file mode 100644 index 00000000000..98773099c99 --- /dev/null +++ b/src/Tezos/MessageSigner.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "PrivateKey.h" +#include "Data.h" + +namespace TW::Tezos { + class MessageSigner { + public: + /// implement format input as described in https://tezostaquito.io/docs/signing/ + /// \param message message to format e.g: Hello, World + /// \param dAppUrl the app url, e.g: testUrl + /// \return the formatted message as a string + static std::string formatMessage(const std::string& message, const std::string& dAppUrl); + + /// implement input to payload as described in: https://tezostaquito.io/docs/signing/ + /// + /// \param input formatted input to be turned into an hex payload + /// \return the hexpayload of the formated input as a hex string + static std::string inputToPayload(const std::string& input); + + /// implement signing as described in https://tezostaquito.io/docs/signing/ + /// \param privateKey the private key to sign with + /// \param message message to sign + /// \return base58 signed message + static std::string signMessage(const PrivateKey& privateKey, const std::string& message); + + /// implement verification as described in https://tezostaquito.io/docs/signing/ + /// \param publicKey publickey to verify the signed message + /// \param message message to be verified as a string + /// \param signature signature to verify the message against + /// \return true if the message match the signature, false otherwise + static bool verifyMessage(const PublicKey& publicKey, const std::string& message, const std::string& signature) noexcept;; + }; +} diff --git a/src/Tezos/Michelson.cpp b/src/Tezos/Michelson.cpp new file mode 100644 index 00000000000..97fcafcd750 --- /dev/null +++ b/src/Tezos/Michelson.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Michelson.h" + +namespace TW::Tezos { + +MichelsonValue::MichelsonVariant FA12ParameterToMichelson(const Proto::FA12Parameters& data) { + MichelsonValue::MichelsonVariant address = StringValue{.string = data.from()}; + MichelsonValue::MichelsonVariant to = StringValue{.string = data.to()}; + MichelsonValue::MichelsonVariant amount = IntValue{._int = data.value()}; + auto primTransferInfos = PrimValue{.prim = "Pair", .args{{to}, {amount}}}; + return PrimValue{.prim = "Pair", .args{{address}, {primTransferInfos}}}; +} + +MichelsonValue::MichelsonVariant FA2ParameterToMichelson(const Proto::FA2Parameters& data) { + auto& txObj = *data.txs_object().begin(); + MichelsonValue::MichelsonVariant from = StringValue{.string = txObj.from()}; + auto& txTransferInfos = txObj.txs(0); + MichelsonValue::MichelsonVariant tokenId = IntValue{._int = txTransferInfos.token_id()}; + MichelsonValue::MichelsonVariant amount = IntValue{._int = txTransferInfos.amount()}; + auto primTransferInfos = PrimValue{.prim = "Pair", .args{{tokenId}, {amount}}}; + MichelsonValue::MichelsonVariant to = StringValue{.string = txTransferInfos.to()}; + MichelsonValue::MichelsonVariant txs = MichelsonValue::MichelsonArray{PrimValue{.prim = "Pair", .args{{to}, {primTransferInfos}}}}; + auto primTxs = PrimValue{.prim = "Pair", .args{{from}, {txs}}}; + return MichelsonValue::MichelsonArray{primTxs}; +} + +} // namespace TW::Tezos diff --git a/src/Tezos/Michelson.h b/src/Tezos/Michelson.h new file mode 100644 index 00000000000..556383f2e12 --- /dev/null +++ b/src/Tezos/Michelson.h @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include +#include + +#include "../proto/Tezos.pb.h" + +#pragma once + +namespace TW::Tezos { + +enum PrimType : std::uint8_t { + Pair = 7, +}; + +struct MichelsonValue; + +struct PrimValue { + std::string prim; + std::vector args; + std::vector anots; +}; + +struct BytesValue { + std::string bytes; +}; + +struct StringValue { + std::string string; +}; + +struct IntValue { + std::string _int; +}; + +struct MichelsonValue { + using MichelsonArray = std::vector>; + using MichelsonVariant = std::variant< + PrimValue, + BytesValue, + StringValue, + IntValue, + MichelsonArray>; + MichelsonVariant value; +}; + +MichelsonValue::MichelsonVariant FA12ParameterToMichelson(const Proto::FA12Parameters& data); +MichelsonValue::MichelsonVariant FA2ParameterToMichelson(const Proto::FA2Parameters& data); + +} // namespace TW::Tezos diff --git a/src/Tezos/OperationList.cpp b/src/Tezos/OperationList.cpp index ff7780ed385..d293999080a 100644 --- a/src/Tezos/OperationList.cpp +++ b/src/Tezos/OperationList.cpp @@ -1,19 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "OperationList.h" -#include "BinaryCoding.h" #include "Forging.h" -#include "HexCoding.h" #include "../Base58.h" -#include "../proto/Tezos.pb.h" -using namespace TW; -using namespace TW::Tezos; -using namespace TW::Tezos::Proto; +namespace TW::Tezos { Tezos::OperationList::OperationList(const std::string& str) { branch = str; @@ -26,7 +19,7 @@ void Tezos::OperationList::addOperation(const Operation& operation) { // Forge the given branch to a hex encoded string. Data Tezos::OperationList::forgeBranch() const { std::array prefix = {1, 52}; - const auto decoded = Base58::bitcoin.decodeCheck(branch); + const auto decoded = Base58::decodeCheck(branch); if (decoded.size() != 34 || !std::equal(prefix.begin(), prefix.end(), decoded.begin())) { throw std::invalid_argument("Invalid branch for forge"); } @@ -53,3 +46,15 @@ Data Tezos::OperationList::forge(const PrivateKey& privateKey) const { return forged; } + +Data TW::Tezos::OperationList::forge() const { + auto forged = forgeBranch(); + + for (auto operation : operation_list) { + append(forged, forgeOperation(operation)); + } + + return forged; +} + +} // namespace TW::Tezos diff --git a/src/Tezos/OperationList.h b/src/Tezos/OperationList.h index f7ddd5615ed..3c673f50d79 100644 --- a/src/Tezos/OperationList.h +++ b/src/Tezos/OperationList.h @@ -1,15 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + #pragma once -#include "../Data.h" +#include "Data.h" #include "proto/Tezos.pb.h" #include "../PrivateKey.h" #include -using namespace TW::Tezos; -using namespace TW::Tezos::Proto; - namespace TW::Tezos { +using TW::Tezos::Proto::Operation; + class OperationList { public: std::string branch; @@ -18,6 +21,7 @@ class OperationList { void addOperation(const Operation& transaction); /// Returns a data representation of the operations. Data forge(const PrivateKey& privateKey) const; + Data forge() const; Data forgeBranch() const; }; diff --git a/src/Tezos/Signer.cpp b/src/Tezos/Signer.cpp index b0732fd78c9..bd61436b4d7 100644 --- a/src/Tezos/Signer.cpp +++ b/src/Tezos/Signer.cpp @@ -1,12 +1,9 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "OperationList.h" #include "Signer.h" -#include "../Hash.h" +#include "OperationList.h" #include "../HexCoding.h" #include @@ -15,17 +12,22 @@ #include using namespace TW; -using namespace TW::Tezos; -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto operationList = Tezos::OperationList(input.operation_list().branch()); - for (Proto::Operation operation : input.operation_list().operations()) { - operationList.addOperation(operation); - } +namespace TW::Tezos { +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(); PrivateKey key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - Data encoded = signer.signOperationList(key, operationList); + Data encoded; + if (input.encoded_operations().empty()) { + auto operationList = Tezos::OperationList(input.operation_list().branch()); + for (Proto::Operation operation : input.operation_list().operations()) { + operationList.addOperation(operation); + } + encoded = signer.signOperationList(key, operationList); + } else { + encoded = signer.signData(key, TW::data(input.encoded_operations())); + } auto output = Proto::SigningOutput(); output.set_encoded(encoded.data(), encoded.size()); @@ -58,3 +60,21 @@ Data Signer::signData(const PrivateKey& privateKey, const Data& data) { append(signedData, signature); return signedData; } + +Data Signer::buildUnsignedTx(const OperationList& operationList) { + Data txData = operationList.forge(); + return txData; +} + +Data Signer::buildSignedTx(const OperationList& operationList, Data signature) { + Data signedData = Data(); + + Data txData = operationList.forge(); + + append(signedData, txData); + append(signedData, signature); + + return signedData; +} + +} // namespace TW::Tezos diff --git a/src/Tezos/Signer.h b/src/Tezos/Signer.h index 798d703ab21..1ec492b7625 100644 --- a/src/Tezos/Signer.h +++ b/src/Tezos/Signer.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "OperationList.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/Tezos.pb.h" @@ -22,9 +20,12 @@ class Signer { static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; /// Signs a json Proto::SigningInput with private key static std::string signJSON(const std::string& json, const Data& key); + public: /// Signs the given transaction. Data signOperationList(const PrivateKey& privateKey, const OperationList& operationList); + Data buildUnsignedTx(const OperationList& operationList); + Data buildSignedTx(const OperationList& operationList, Data signature); Data signData(const PrivateKey& privateKey, const Data& data); }; diff --git a/src/TheOpenNetwork/Entry.h b/src/TheOpenNetwork/Entry.h new file mode 100644 index 00000000000..ad187a83b6a --- /dev/null +++ b/src/TheOpenNetwork/Entry.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "rust/RustCoinEntry.h" + +namespace TW::TheOpenNetwork { + +class Entry final : public Rust::RustCoinEntry { +}; + +} // namespace TW::TheOpenNetwork diff --git a/src/Theta/Coins.h b/src/Theta/Coins.h index 6f8ab47659a..33fdd8b4b48 100644 --- a/src/Theta/Coins.h +++ b/src/Theta/Coins.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Theta/Entry.cpp b/src/Theta/Entry.cpp index accd33e53f9..f8cff52113e 100644 --- a/src/Theta/Entry.cpp +++ b/src/Theta/Entry.cpp @@ -1,17 +1,55 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" - #include "Ethereum/Address.h" #include "Signer.h" +#include "../proto/Theta.pb.h" +#include "../proto/TransactionCompiler.pb.h" + +namespace TW::Theta { + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Ethereum::Address::isValid(address); +} + +std::string Entry::normalizeAddress([[maybe_unused]] TWCoinType coin, const std::string& address) const { + // normalized with EIP55 checksum + return Ethereum::Address(address).string(); +} + +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Ethereum::Address(publicKey).string(); +} -using namespace TW::Theta; -using namespace std; +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Ethereum::Address(address); + return {addr.bytes.begin(), addr.bytes.end()}; +} -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + const auto signer = Signer(input); + auto preImage = signer.signaturePreimage(); + auto preImageHash = Hash::keccak256(preImage); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + const auto signer = Signer(input); + output = signer.compile(signature, publicKey); + }); +} + +} // namespace TW::Theta diff --git a/src/Theta/Entry.h b/src/Theta/Entry.h index a75766f0fc0..1d6beb82a5b 100644 --- a/src/Theta/Entry.h +++ b/src/Theta/Entry.h @@ -1,21 +1,24 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../CoinEntry.h" -#include "Ethereum/Entry.h" +#include "CoinEntry.h" namespace TW::Theta { /// Entry point for Theta. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public Ethereum::Entry { +class Entry final : public CoinEntry { public: - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + Data addressToData(TWCoinType coin, const std::string& address) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; }; } // namespace TW::Theta diff --git a/src/Theta/Signer.cpp b/src/Theta/Signer.cpp index e2bc16b1188..020965d374a 100755 --- a/src/Theta/Signer.cpp +++ b/src/Theta/Signer.cpp @@ -1,17 +1,15 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "../Ethereum/RLP.h" #include "../Hash.h" -using namespace TW; -using namespace TW::Theta; -using RLP = Ethereum::RLP; +using RLP = TW::Ethereum::RLP; + +namespace TW::Theta { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto pkFrom = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); @@ -31,33 +29,33 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto output = Proto::SigningOutput(); transaction.setSignature(from, signature); - auto encoded = transaction.encode(); + auto encoded = transaction.encodePayload(); output.set_encoded(encoded.data(), encoded.size()); output.set_signature(signature.data(), signature.size()); return output; } -Data Signer::encode(const Transaction& transaction) noexcept { +Data Signer::encode(const Transaction& transaction) const { const uint64_t nonce = 0; - const uint256_t gasPrice = 0; + const uint64_t gasPrice = 0; const uint64_t gasLimit = 0; - const Ethereum::Address to = Ethereum::Address("0x0000000000000000000000000000000000000000"); - const uint256_t amount = 0; + const auto* to = "0x0000000000000000000000000000000000000000"; + const uint64_t amount = 0; + auto txData = transaction.encode(chainID); + + EthereumRlp::Proto::EncodingInput encodingInput; + auto* rlpList = encodingInput.mutable_item()->mutable_list(); - auto encoded = Data(); /// Need to add the following prefix to the tx signbytes to be compatible with /// the Ethereum tx format - append(encoded, RLP::encode(nonce)); - append(encoded, RLP::encode(gasPrice)); - append(encoded, RLP::encode(gasLimit)); - append(encoded, RLP::encode(to.bytes)); - append(encoded, RLP::encode(amount)); - /// Chain ID - auto payload = Data(); - append(payload, RLP::encode(chainID)); - append(payload, transaction.encode()); - append(encoded, RLP::encode(payload)); - return RLP::encodeList(encoded); + rlpList->add_items()->set_number_u64(nonce); + rlpList->add_items()->set_number_u64(gasPrice); + rlpList->add_items()->set_number_u64(gasLimit); + rlpList->add_items()->set_address(to); + rlpList->add_items()->set_number_u64(amount); + rlpList->add_items()->set_data(txData.data(), txData.size()); + + return RLP::encode(encodingInput); } Data Signer::sign(const PrivateKey& privateKey, const Transaction& transaction) noexcept { @@ -66,3 +64,46 @@ Data Signer::sign(const PrivateKey& privateKey, const Transaction& transaction) auto signature = privateKey.sign(hash, TWCurveSECP256k1); return signature; } + +Transaction Signer::buildTransaction() const{ + auto publicKey = PublicKey(Data(input.public_key().begin(), input.public_key().end()), TWPublicKeyTypeSECP256k1Extended); + auto from = Ethereum::Address(publicKey); + + auto transaction = Transaction( + /* from: */ from, + /* to: */ Ethereum::Address(input.to_address()), + /* thetaAmount: */ load(input.theta_amount()), + /* tfuelAmount: */ load(input.tfuel_amount()), + /* sequence: */ input.sequence(), + /* feeAmount: */ load(input.fee())); + return transaction; +} + +Data Signer::signaturePreimage() const { + return encode(this->buildTransaction()); +} + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey) const{ + // validate public key + if (publicKey.type != TWPublicKeyTypeSECP256k1Extended) { + throw std::invalid_argument("Invalid public key"); + } + { + // validate correctness of signature + auto preImage = signaturePreimage(); + auto preImageHash = Hash::keccak256(preImage); + if (!publicKey.verify(signature, preImageHash)) { + throw std::invalid_argument("Invalid signature/hash/publickey combination"); + } + } + auto from = Ethereum::Address(publicKey); + auto protoOutput = Proto::SigningOutput(); + auto transaction = buildTransaction(); + transaction.setSignature(from, signature); + auto encoded = transaction.encodePayload(); + + protoOutput.set_encoded(encoded.data(), encoded.size()); + protoOutput.set_signature(signature.data(), signature.size()); + return protoOutput; +} +} // namespace TW::Theta diff --git a/src/Theta/Signer.h b/src/Theta/Signer.h index 1fe12da5dd3..d4608c6e3df 100644 --- a/src/Theta/Signer.h +++ b/src/Theta/Signer.h @@ -1,15 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/Theta.pb.h" @@ -23,17 +21,19 @@ class Signer { public: std::string chainID; - - Signer() = default; + Proto::SigningInput input; + /// Initializes a transaction signer. + explicit Signer(const Proto::SigningInput& input) : chainID(input.chain_id()), input(input) {} /// Initializes a signer with a chain identifier which could be `mainnet`, `testnet` or /// `privatenet` explicit Signer(std::string chainID) : chainID(std::move(chainID)) {} /// Signs the given transaction Data sign(const PrivateKey& privateKey, const Transaction& transaction) noexcept; - - private: - Data encode(const Transaction& transaction) noexcept; + Data encode(const Transaction& transaction) const; + Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) const; + Data signaturePreimage() const; + Transaction buildTransaction() const; }; } // namespace TW::Theta diff --git a/src/Theta/Transaction.cpp b/src/Theta/Transaction.cpp index 7cfa42e3ad2..c1cb17c11b8 100644 --- a/src/Theta/Transaction.cpp +++ b/src/Theta/Transaction.cpp @@ -1,79 +1,109 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Transaction.h" #include "../Ethereum/RLP.h" -using namespace TW; -using namespace TW::Theta; +namespace TW::Theta { + using RLP = Ethereum::RLP; -Data encode(const Coins& coins) noexcept { - auto encoded = Data(); - append(encoded, RLP::encode(coins.thetaWei)); - append(encoded, RLP::encode(coins.tfuelWei)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem prepare(const Coins& coins) noexcept { + auto thetaWei = store(coins.thetaWei); + auto tfuelWei = store(coins.tfuelWei); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_number_u256(thetaWei.data(), thetaWei.size()); + rlpList->add_items()->set_number_u256(tfuelWei.data(), tfuelWei.size()); + + return item; } -Data encode(const TxInput& input) noexcept { - auto encoded = Data(); - append(encoded, RLP::encode(input.address.bytes)); - append(encoded, encode(input.coins)); - append(encoded, RLP::encode(input.sequence)); - append(encoded, RLP::encode(input.signature)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem prepare(const TxInput& input) noexcept { + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(input.address.bytes.data(), input.address.bytes.size()); + *rlpList->add_items() = prepare(input.coins); + rlpList->add_items()->set_number_u64(input.sequence); + rlpList->add_items()->set_data(input.signature.data(), input.signature.size()); + + return item; } -Data encode(const std::vector& inputs) noexcept { - auto encoded = Data(); +EthereumRlp::Proto::RlpItem prepare(const std::vector& inputs) noexcept { + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + for (const auto& input : inputs) { - append(encoded, encode(input)); + *rlpList->add_items() = prepare(input); } - return RLP::encodeList(encoded); + + return item; } -Data encode(const TxOutput& output) noexcept { - auto encoded = Data(); - append(encoded, RLP::encode(output.address.bytes)); - append(encoded, encode(output.coins)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem prepare(const TxOutput& output) noexcept { + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(output.address.bytes.data(), output.address.bytes.size()); + *rlpList->add_items() = prepare(output.coins); + + return item; } -Data encode(const std::vector& outputs) noexcept { - auto encoded = Data(); +EthereumRlp::Proto::RlpItem prepare(const std::vector& outputs) noexcept { + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + for (const auto& output : outputs) { - append(encoded, encode(output)); + *rlpList->add_items() = prepare(output); } - return RLP::encodeList(encoded); + + return item; } Transaction::Transaction(Ethereum::Address from, Ethereum::Address to, - uint256_t thetaAmount, uint256_t tfuelAmount, - uint64_t sequence, uint256_t feeAmount /* = 1000000000000*/) { + const uint256_t& thetaAmount, const uint256_t& tfuelAmount, + uint64_t sequence, const uint256_t& feeAmount /* = 1000000000000*/) { auto fee = Coins(0, feeAmount); auto coinsInput = Coins(thetaAmount, tfuelAmount + feeAmount); auto coinsOutput = Coins(thetaAmount, tfuelAmount); - auto input = TxInput(std::move(from), coinsInput, sequence); - auto output = TxOutput(std::move(to), coinsOutput); + auto input = TxInput(from, coinsInput, sequence); + auto output = TxOutput(to, coinsOutput); - this->fee = fee; + this->_fee = fee; this->inputs.push_back(input); this->outputs.push_back(output); } -Data Transaction::encode() const noexcept { - auto encoded = Data(); - uint16_t txType = 2; // TxSend - append(encoded, RLP::encode(txType)); - auto encodedData = Data(); - append(encodedData, ::encode(fee)); - append(encodedData, ::encode(inputs)); - append(encodedData, ::encode(outputs)); - append(encoded, RLP::encodeList(encodedData)); +Data Transaction::encodePayload() const noexcept { + const uint64_t txType = 2; // TxSend + + EthereumRlp::Proto::EncodingInput txInput; + auto* txPropertiesList = txInput.mutable_item()->mutable_list(); + + *txPropertiesList->add_items() = prepare(_fee); + *txPropertiesList->add_items() = prepare(inputs); + *txPropertiesList->add_items() = prepare(outputs); + + auto txPropertiesEncoded = RLP::encode(txInput); + + Data payload; + append(payload, RLP::encodeU256(static_cast(txType))); + append(payload, txPropertiesEncoded); + + return payload; +} + +Data Transaction::encode(const std::string& chainId) const noexcept { + Data encoded; + append(encoded, RLP::encodeString(chainId)); + append(encoded, encodePayload()); return encoded; } @@ -86,3 +116,5 @@ bool Transaction::setSignature(const Ethereum::Address& address, const Data& sig } return false; } + +} // namespace TW::Theta diff --git a/src/Theta/Transaction.h b/src/Theta/Transaction.h index 735c21ab1a8..7068cc5fa6e 100644 --- a/src/Theta/Transaction.h +++ b/src/Theta/Transaction.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -10,8 +8,8 @@ #include #include "Coins.h" -#include "../Data.h" -#include "../Ethereum/Address.h" +#include "Data.h" +#include "Ethereum/Address.h" namespace TW::Theta { @@ -39,20 +37,23 @@ class TxOutput { class Transaction { public: - Coins fee; + Coins _fee; std::vector inputs; std::vector outputs; Transaction() = default; Transaction(Coins fee, std::vector inputs, std::vector outputs) - : fee(std::move(fee)), inputs(std::move(inputs)), outputs(std::move(outputs)) {} + : _fee(std::move(fee)), inputs(std::move(inputs)), outputs(std::move(outputs)) {} Transaction(Ethereum::Address from, Ethereum::Address to, - uint256_t thetaAmount, uint256_t tfuelAmount, uint64_t sequence, - uint256_t feeAmount = 1000000000000); + const uint256_t& thetaAmount, const uint256_t& tfuelAmount, uint64_t sequence, + const uint256_t& feeAmount = 1000000000000); - /// Encodes the transaction - Data encode() const noexcept; + /// Encodes the essential part of the transaction without a Chain ID. + Data encodePayload() const noexcept; + + /// Encodes the transaction with the given `chainId`. + Data encode(const std::string& chainId) const noexcept; /// Sets signature bool setSignature(const Ethereum::Address& address, const Data& signature) noexcept; diff --git a/src/TransactionCompiler.cpp b/src/TransactionCompiler.cpp index cc264093075..b9ee21bb760 100644 --- a/src/TransactionCompiler.cpp +++ b/src/TransactionCompiler.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "TransactionCompiler.h" @@ -10,13 +8,6 @@ using namespace TW; - -Data TransactionCompiler::buildInput(TWCoinType coinType, const std::string& from, const std::string& to, const std::string& amount, const std::string& asset, const std::string& memo, const std::string& chainId) { - // parse amount - uint256_t amount256 { amount }; - return anyCoinBuildTransactionInput(coinType, from, to, amount256, asset, memo, chainId); -} - Data TransactionCompiler::preImageHashes(TWCoinType coinType, const Data& txInputData) { return anyCoinPreImageHashes(coinType, txInputData); } @@ -29,7 +20,21 @@ Data TransactionCompiler::compileWithSignatures(TWCoinType coinType, const Data& if (!PublicKey::isValid(p, publicKeyType)) { throw std::invalid_argument("Invalid public key"); } - pubs.push_back(PublicKey(p, publicKeyType)); + pubs.emplace_back(p, publicKeyType); + } + + Data txOutput; + anyCoinCompileWithSignatures(coinType, txInputData, signatures, pubs, txOutput); + return txOutput; +} + +Data TransactionCompiler::compileWithSignaturesAndPubKeyType(TWCoinType coinType, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, enum TWPublicKeyType pubKeyType) { + std::vector pubs; + for (auto& p: publicKeys) { + if (!PublicKey::isValid(p, pubKeyType)) { + throw std::invalid_argument("Invalid public key"); + } + pubs.push_back(PublicKey(p, pubKeyType)); } Data txOutput; diff --git a/src/TransactionCompiler.h b/src/TransactionCompiler.h index 21b968717b6..0c12b4dd23c 100644 --- a/src/TransactionCompiler.h +++ b/src/TransactionCompiler.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -18,9 +16,6 @@ namespace TW { /// Non-core transaction utility methods, like building a transaction using an external signature class TransactionCompiler { public: - /// Build a coin-specific SigningInput protobuf transaction input, from simple transaction parameters - static Data buildInput(TWCoinType coinType, const std::string& from, const std::string& to, const std::string& amount, const std::string& asset, const std::string& memo, const std::string& chainId); - /// Obtain pre-signing hash of a transaction. /// It will return a proto object named `PreSigningOutput` which will include hash. /// We provide a default `PreSigningOutput` in TransactionCompiler.proto. @@ -29,6 +24,9 @@ class TransactionCompiler { /// Compile a complete transation with an external signature, put together from transaction input and provided public key and signature static Data compileWithSignatures(TWCoinType coinType, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys); + + static Data compileWithSignaturesAndPubKeyType(TWCoinType coinType, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, TWPublicKeyType pubKeyType); + }; } // namespace TW diff --git a/src/Tron/Address.cpp b/src/Tron/Address.cpp index 7fdac65b817..1a66a7f9ccd 100644 --- a/src/Tron/Address.cpp +++ b/src/Tron/Address.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" @@ -12,10 +10,10 @@ #include #include -using namespace TW::Tron; +namespace TW::Tron { bool Address::isValid(const std::string& string) { - const auto decoded = Base58::bitcoin.decodeCheck(string); + const auto decoded = Base58::decodeCheck(string); if (decoded.size() != Address::size) { return false; } @@ -36,3 +34,5 @@ Address::Address(const PublicKey& publicKey) { bytes[0] = prefix; std::copy(keyhash.end() - size + 1, keyhash.end(), bytes.begin() + 1); } + +} // namespace TW::Tron diff --git a/src/Tron/Address.h b/src/Tron/Address.h index 7577171a5b3..f19a4a3c164 100644 --- a/src/Tron/Address.h +++ b/src/Tron/Address.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../Base58Address.h" -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/Tron/Entry.cpp b/src/Tron/Entry.cpp index 0f69e92d865..7cf7f756ca5 100644 --- a/src/Tron/Entry.cpp +++ b/src/Tron/Entry.cpp @@ -1,27 +1,54 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" - #include "Address.h" +#include "Serialization.h" #include "Signer.h" +#include "../proto/TransactionCompiler.pb.h" -using namespace TW::Tron; using namespace std; +namespace TW::Tron { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + const auto signer = Signer(input); + auto preImage = signer.signaturePreimage(); + auto preImageHash = Hash::sha256(preImage); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + output.set_data(preImage.data(), preImage.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + [[maybe_unused]] const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + const auto signer = Signer(input); + if (signatures.size() != 1) { + output.set_error(Common::Proto::Error_no_support_n2n); + output.set_error_message( + Common::Proto::SigningError_Name(Common::Proto::Error_no_support_n2n)); + return; + } + output = signer.compile(signatures[0]); + }); +} +} // namespace TW::Tron diff --git a/src/Tron/Entry.h b/src/Tron/Entry.h index 96dfa6a5c9f..e023427340c 100644 --- a/src/Tron/Entry.h +++ b/src/Tron/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,11 +10,17 @@ namespace TW::Tron { /// Entry point for implementation of Tron coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + + void compile(TWCoinType coin, const Data& txInputData, + const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Tron diff --git a/src/Tron/MessageSigner.cpp b/src/Tron/MessageSigner.cpp new file mode 100644 index 00000000000..8fe0d8619a6 --- /dev/null +++ b/src/Tron/MessageSigner.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include + +#include +#include "HexCoding.h" + + +namespace TW::Tron { + +Data generateMessage(const std::string& message) { + std::string prefix(1, MessageSigner::TronPrefix); + std::stringstream ss; + ss << prefix << MessageSigner::MessagePrefix << message; + Data signableMessage = Hash::keccak256(data(ss.str())); + return signableMessage; +} + +std::string MessageSigner::signMessage(const PrivateKey& privateKey, const std::string& message) { + auto signableMessage = generateMessage(message); + auto data = privateKey.sign(signableMessage, TWCurveSECP256k1); + data[64] += 27; + return hex(data); +} + +bool MessageSigner::verifyMessage(const PublicKey& publicKey, const std::string& message, const std::string& signature) noexcept { + Data msg = generateMessage(message); + auto rawSignature = parse_hex(signature); + auto recovered = publicKey.recover(rawSignature, msg); + return recovered == publicKey && publicKey.verify(rawSignature, msg); +} + +} // namespace TW::Ethereum diff --git a/src/Tron/MessageSigner.h b/src/Tron/MessageSigner.h new file mode 100644 index 00000000000..e6e7bf2a9a9 --- /dev/null +++ b/src/Tron/MessageSigner.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include +#include + +namespace TW::Tron { + +class MessageSigner { +public: + /// Sign a message following https://github.com/tronprotocol/tronweb/blob/859253856c79d3aff26ec6c89afefc73840d648d/src/lib/trx.js#L768 + /// \param privateKey the private key to sign with + /// \param message message to sign + /// \return hex signed message + static std::string signMessage(const PrivateKey& privateKey, const std::string& message); + + /// Verify a TRON message + /// \param publicKey publickey to verify the signed message + /// \param message message to be verified as a string + /// \param signature signature to verify the message against + /// \return true if the message match the signature, false otherwise + static bool verifyMessage(const PublicKey& publicKey, const std::string& message, const std::string& signature) noexcept; + static constexpr auto MessagePrefix = "TRON Signed Message:\n32"; + static constexpr std::uint8_t TronPrefix{0x19}; +}; + +} // namespace TW::Ethereum diff --git a/src/Tron/Protobuf/TronInternal.proto b/src/Tron/Protobuf/TronInternal.proto index 88bc5675912..f2be7207561 100644 --- a/src/Tron/Protobuf/TronInternal.proto +++ b/src/Tron/Protobuf/TronInternal.proto @@ -17,6 +17,11 @@ message Transaction { WithdrawBalanceContract = 13; UnfreezeAssetContract = 14; TriggerSmartContract = 31; + FreezeBalanceV2Contract = 54; + UnfreezeBalanceV2Contract = 55; + WithdrawExpireUnfreezeContract = 56; + DelegateResourceContract = 57; + UnDelegateResourceContract = 58; } ContractType type = 1; google.protobuf.Any parameter = 2; @@ -78,6 +83,12 @@ message FreezeBalanceContract { bytes receiver_address = 15; } +message FreezeBalanceV2Contract { + bytes owner_address = 1; + int64 frozen_balance = 2; + ResourceCode resource = 3; +} + message UnfreezeBalanceContract { bytes owner_address = 1; @@ -85,6 +96,31 @@ message UnfreezeBalanceContract { bytes receiver_address = 15; } +message UnfreezeBalanceV2Contract { + bytes owner_address = 1; + int64 unfreeze_balance = 2; + ResourceCode resource = 3; +} + +message WithdrawExpireUnfreezeContract { + bytes owner_address = 1; +} + +message DelegateResourceContract { + bytes owner_address = 1; + ResourceCode resource = 2; + int64 balance = 3; + bytes receiver_address = 4; + bool lock = 5; +} + +message UnDelegateResourceContract { + bytes owner_address = 1; + ResourceCode resource = 2; + int64 balance = 3; + bytes receiver_address = 4; +} + message UnfreezeAssetContract { bytes owner_address = 1; } @@ -117,4 +153,4 @@ message TriggerSmartContract { bytes data = 4; int64 call_token_value = 5; int64 token_id = 6; -} \ No newline at end of file +} diff --git a/src/Tron/Serialization.cpp b/src/Tron/Serialization.cpp index a753e52e5d9..36e92b62878 100644 --- a/src/Tron/Serialization.cpp +++ b/src/Tron/Serialization.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Serialization.h" #include "../HexCoding.h" @@ -10,17 +8,15 @@ #include #include -using namespace TW; -using namespace TW::Tron; -using namespace std; +namespace TW::Tron { using json = nlohmann::json; -string typeName(const protocol::Transaction::Contract::ContractType type) { +std::string typeName(const protocol::Transaction::Contract::ContractType type) { return protocol::Transaction::Contract::ContractType_Name(type); } -string typeUrl(const protocol::Transaction::Contract::ContractType type) { +std::string typeUrl(const protocol::Transaction::Contract::ContractType type) { std::ostringstream stringStream; stringStream << "type.googleapis.com/protocol." << typeName(type); return stringStream.str(); @@ -47,9 +43,9 @@ json valueJSON(const protocol::TransferAssetContract& contract) { json valueJSON(const protocol::VoteAssetContract& contract) { json valueJSON; - - vector vote_address; - for (const string& addr : contract.vote_address()) { + + std::vector vote_address; + for (const std::string& addr : contract.vote_address()) { vote_address.push_back(hex(addr)); } @@ -57,7 +53,7 @@ json valueJSON(const protocol::VoteAssetContract& contract) { valueJSON["vote_address"] = vote_address; valueJSON["support"] = contract.support(); valueJSON["count"] = contract.count(); - + return valueJSON; } @@ -72,7 +68,7 @@ json voteJSON(const protocol::VoteWitnessContract::Vote& vote) { json valueJSON(const protocol::VoteWitnessContract& contract) { json valueJSON; - vector votes; + std::vector votes; for (const protocol::VoteWitnessContract::Vote& vote : contract.votes()) { votes.push_back(voteJSON(vote)); } @@ -95,6 +91,14 @@ json valueJSON(const protocol::FreezeBalanceContract& contract) { return valueJSON; } +json valueJSON(const protocol::FreezeBalanceV2Contract& contract) { + json valueJSON; + valueJSON["owner_address"] = hex(contract.owner_address()); + valueJSON["frozen_balance"] = contract.frozen_balance(); + valueJSON["resource"] = protocol::ResourceCode_Name(contract.resource()); + return valueJSON; +} + json valueJSON(const protocol::UnfreezeBalanceContract& contract) { json valueJSON; valueJSON["owner_address"] = hex(contract.owner_address()); @@ -104,6 +108,40 @@ json valueJSON(const protocol::UnfreezeBalanceContract& contract) { return valueJSON; } +json valueJSON(const protocol::UnfreezeBalanceV2Contract& contract) { + json valueJSON; + valueJSON["owner_address"] = hex(contract.owner_address()); + valueJSON["resource"] = protocol::ResourceCode_Name(contract.resource()); + valueJSON["unfreeze_balance"] = contract.unfreeze_balance(); + + return valueJSON; +} + +json valueJSON(const protocol::DelegateResourceContract& contract) { + json valueJSON; + valueJSON["owner_address"] = hex(contract.owner_address()); + valueJSON["receiver_address"] = hex(contract.receiver_address()); + valueJSON["resource"] = protocol::ResourceCode_Name(contract.resource()); + valueJSON["balance"] = contract.balance(); + valueJSON["lock"] = contract.lock(); + return valueJSON; +} + +json valueJSON(const protocol::UnDelegateResourceContract& contract) { + json valueJSON; + valueJSON["owner_address"] = hex(contract.owner_address()); + valueJSON["receiver_address"] = hex(contract.receiver_address()); + valueJSON["resource"] = protocol::ResourceCode_Name(contract.resource()); + valueJSON["balance"] = contract.balance(); + return valueJSON; +} + +json valueJSON(const protocol::WithdrawExpireUnfreezeContract& contract) { + json valueJSON; + valueJSON["owner_address"] = hex(contract.owner_address()); + return valueJSON; +} + json valueJSON(const protocol::WithdrawBalanceContract& contract) { json valueJSON; valueJSON["owner_address"] = hex(contract.owner_address()); @@ -136,81 +174,111 @@ json valueJSON(const protocol::TriggerSmartContract& contract) { return valueJSON; } -json parameterJSON(const google::protobuf::Any ¶meter, const protocol::Transaction::Contract::ContractType type) { +json parameterJSON(const google::protobuf::Any& parameter, const protocol::Transaction::Contract::ContractType type) { json paramJSON; paramJSON["type_url"] = typeUrl(type); switch (type) { - case protocol::Transaction::Contract::TransferContract: { - protocol::TransferContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::TransferAssetContract: { - protocol::TransferAssetContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::VoteAssetContract: { - protocol::VoteAssetContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::VoteWitnessContract: { - protocol::VoteWitnessContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::FreezeBalanceContract: { - protocol::FreezeBalanceContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::UnfreezeBalanceContract: { - protocol::UnfreezeBalanceContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::WithdrawBalanceContract: { - protocol::WithdrawBalanceContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::UnfreezeAssetContract: { - protocol::UnfreezeAssetContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::TriggerSmartContract: { - protocol::TriggerSmartContract contract; - parameter.UnpackTo(&contract); - paramJSON["value"] = valueJSON(contract); - break; - } - case protocol::Transaction::Contract::AccountCreateContract: - default: - break; + case protocol::Transaction::Contract::TransferContract: { + protocol::TransferContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::TransferAssetContract: { + protocol::TransferAssetContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::VoteAssetContract: { + protocol::VoteAssetContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::VoteWitnessContract: { + protocol::VoteWitnessContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::FreezeBalanceContract: { + protocol::FreezeBalanceContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::FreezeBalanceV2Contract: { + protocol::FreezeBalanceV2Contract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::UnfreezeBalanceContract: { + protocol::UnfreezeBalanceContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::UnfreezeBalanceV2Contract: { + protocol::UnfreezeBalanceV2Contract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::WithdrawExpireUnfreezeContract: { + protocol::WithdrawExpireUnfreezeContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::DelegateResourceContract: { + protocol::DelegateResourceContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::UnDelegateResourceContract: { + protocol::UnDelegateResourceContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::WithdrawBalanceContract: { + protocol::WithdrawBalanceContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::UnfreezeAssetContract: { + protocol::UnfreezeAssetContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::TriggerSmartContract: { + protocol::TriggerSmartContract contract; + parameter.UnpackTo(&contract); + paramJSON["value"] = valueJSON(contract); + break; + } + case protocol::Transaction::Contract::AccountCreateContract: + default: + break; } return paramJSON; } -json contractJSON(const protocol::Transaction::Contract &contract) { +json contractJSON(const protocol::Transaction::Contract& contract) { json contractJSON; contractJSON["type"] = typeName(contract.type()); contractJSON["parameter"] = parameterJSON(contract.parameter(), contract.type()); return contractJSON; } -json raw_dataJSON(const protocol::Transaction::raw &raw) { +json raw_dataJSON(const protocol::Transaction::raw& raw) { json raw_dataJSON; raw_dataJSON["ref_block_bytes"] = hex(raw.ref_block_bytes()); @@ -223,16 +291,18 @@ json raw_dataJSON(const protocol::Transaction::raw &raw) { } raw_dataJSON["timestamp"] = raw.timestamp(); raw_dataJSON["expiration"] = raw.expiration(); - raw_dataJSON["contract"] = json::array({ contractJSON(raw.contract(0)) }); + raw_dataJSON["contract"] = json::array({contractJSON(raw.contract(0))}); return raw_dataJSON; } -json TW::Tron::transactionJSON(const protocol::Transaction& transaction, const TW::Data& txID, const TW::Data& signature) { +json transactionJSON(const protocol::Transaction& transaction, const TW::Data& txID, const TW::Data& signature) { json transactionJSON; transactionJSON["raw_data"] = raw_dataJSON(transaction.raw_data()); transactionJSON["txID"] = hex(txID); - transactionJSON["signature"] = json::array({ hex(signature) }); + transactionJSON["signature"] = json::array({hex(signature)}); return transactionJSON; } + +} // namespace TW::Tron diff --git a/src/Tron/Serialization.h b/src/Tron/Serialization.h index 13181c7ac47..cb807524fcd 100644 --- a/src/Tron/Serialization.h +++ b/src/Tron/Serialization.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "./Protobuf/TronInternal.pb.h" -#include "../Data.h" +#include "Data.h" #include namespace TW::Tron { diff --git a/src/Tron/Signer.cpp b/src/Tron/Signer.cpp index 2680046135f..64ebaa22153 100644 --- a/src/Tron/Signer.cpp +++ b/src/Tron/Signer.cpp @@ -1,38 +1,31 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "Protobuf/TronInternal.pb.h" +#include "Serialization.h" #include "../Base58.h" #include "../BinaryCoding.h" -#include "../Hash.h" #include "../HexCoding.h" -#include "Serialization.h" -#include #include +#include -using namespace TW; -using namespace TW::Tron; -using namespace std::chrono; +namespace TW::Tron { const std::string TRANSFER_TOKEN_FUNCTION = "0xa9059cbb"; -size_t base58Capacity = 128; - /// Converts an external TransferContract to an internal one used for signing. protocol::TransferContract to_internal(const Proto::TransferContract& transfer) { auto internal = protocol::TransferContract(); - const auto ownerAddress = Base58::bitcoin.decodeCheck(transfer.owner_address()); + const auto ownerAddress = Base58::decodeCheck(transfer.owner_address()); internal.set_owner_address(ownerAddress.data(), ownerAddress.size()); - const auto toAddress = Base58::bitcoin.decodeCheck(transfer.to_address()); + const auto toAddress = Base58::decodeCheck(transfer.to_address()); internal.set_to_address(toAddress.data(), toAddress.size()); internal.set_amount(transfer.amount()); @@ -47,10 +40,10 @@ protocol::TransferAssetContract to_internal(const Proto::TransferAssetContract& internal.set_asset_name(transfer.asset_name()); - const auto ownerAddress = Base58::bitcoin.decodeCheck(transfer.owner_address()); + const auto ownerAddress = Base58::decodeCheck(transfer.owner_address()); internal.set_owner_address(ownerAddress.data(), ownerAddress.size()); - const auto toAddress = Base58::bitcoin.decodeCheck(transfer.to_address()); + const auto toAddress = Base58::decodeCheck(transfer.to_address()); internal.set_to_address(toAddress.data(), toAddress.size()); internal.set_amount(transfer.amount()); @@ -61,8 +54,8 @@ protocol::TransferAssetContract to_internal(const Proto::TransferAssetContract& protocol::FreezeBalanceContract to_internal(const Proto::FreezeBalanceContract& freezeContract) { auto internal = protocol::FreezeBalanceContract(); auto resource = protocol::ResourceCode(); - const auto ownerAddress = Base58::bitcoin.decodeCheck(freezeContract.owner_address()); - const auto receiverAddress = Base58::bitcoin.decodeCheck(freezeContract.receiver_address()); + const auto ownerAddress = Base58::decodeCheck(freezeContract.owner_address()); + const auto receiverAddress = Base58::decodeCheck(freezeContract.receiver_address()); protocol::ResourceCode_Parse(freezeContract.resource(), &resource); @@ -75,24 +68,92 @@ protocol::FreezeBalanceContract to_internal(const Proto::FreezeBalanceContract& return internal; } +protocol::FreezeBalanceV2Contract to_internal(const Proto::FreezeBalanceV2Contract& freezeContract) { + auto internal = protocol::FreezeBalanceV2Contract(); + auto resource = protocol::ResourceCode(); + const auto ownerAddress = Base58::decodeCheck(freezeContract.owner_address()); + + protocol::ResourceCode_Parse(freezeContract.resource(), &resource); + + internal.set_resource(resource); + internal.set_owner_address(ownerAddress.data(), ownerAddress.size()); + internal.set_frozen_balance(freezeContract.frozen_balance()); + + return internal; +} + protocol::UnfreezeBalanceContract to_internal(const Proto::UnfreezeBalanceContract& unfreezeContract) { auto internal = protocol::UnfreezeBalanceContract(); auto resource = protocol::ResourceCode(); - const auto ownerAddress = Base58::bitcoin.decodeCheck(unfreezeContract.owner_address()); - const auto receiverAddress = Base58::bitcoin.decodeCheck(unfreezeContract.receiver_address()); + const auto ownerAddress = Base58::decodeCheck(unfreezeContract.owner_address()); + const auto receiverAddress = Base58::decodeCheck(unfreezeContract.receiver_address()); + + protocol::ResourceCode_Parse(unfreezeContract.resource(), &resource); + + internal.set_resource(resource); + internal.set_owner_address(ownerAddress.data(), ownerAddress.size()); + internal.set_receiver_address(receiverAddress.data(), receiverAddress.size()); + + return internal; +} + +protocol::UnfreezeBalanceV2Contract to_internal(const Proto::UnfreezeBalanceV2Contract& unfreezeContract) { + auto internal = protocol::UnfreezeBalanceV2Contract(); + auto resource = protocol::ResourceCode(); + const auto ownerAddress = Base58::decodeCheck(unfreezeContract.owner_address()); protocol::ResourceCode_Parse(unfreezeContract.resource(), &resource); + internal.set_resource(resource); + internal.set_owner_address(ownerAddress.data(), ownerAddress.size()); + internal.set_unfreeze_balance(unfreezeContract.unfreeze_balance()); + + return internal; +} + +protocol::DelegateResourceContract to_internal(const Proto::DelegateResourceContract& delegateContract) { + auto internal = protocol::DelegateResourceContract(); + auto resource = protocol::ResourceCode(); + const auto ownerAddress = Base58::decodeCheck(delegateContract.owner_address()); + const auto receiverAddress = Base58::decodeCheck(delegateContract.receiver_address()); + + protocol::ResourceCode_Parse(delegateContract.resource(), &resource); + + internal.set_resource(resource); + internal.set_owner_address(ownerAddress.data(), ownerAddress.size()); + internal.set_receiver_address(receiverAddress.data(), receiverAddress.size()); + internal.set_balance(delegateContract.balance()); + internal.set_lock(delegateContract.lock()); + + return internal; +} + +protocol::UnDelegateResourceContract to_internal(const Proto::UnDelegateResourceContract& undelegateContract) { + auto internal = protocol::UnDelegateResourceContract(); + auto resource = protocol::ResourceCode(); + const auto ownerAddress = Base58::decodeCheck(undelegateContract.owner_address()); + const auto receiverAddress = Base58::decodeCheck(undelegateContract.receiver_address()); + + protocol::ResourceCode_Parse(undelegateContract.resource(), &resource); + internal.set_resource(resource); internal.set_owner_address(ownerAddress.data(), ownerAddress.size()); internal.set_receiver_address(receiverAddress.data(), receiverAddress.size()); + internal.set_balance(undelegateContract.balance()); return internal; } +protocol::WithdrawExpireUnfreezeContract to_internal(const Proto::WithdrawExpireUnfreezeContract& withdrawExpireUnfreezeContract) { + auto internal = protocol::WithdrawExpireUnfreezeContract(); + const auto ownerAddress = Base58::decodeCheck(withdrawExpireUnfreezeContract.owner_address()); + internal.set_owner_address(ownerAddress.data(), ownerAddress.size()); + return internal; +} + protocol::UnfreezeAssetContract to_internal(const Proto::UnfreezeAssetContract& unfreezeContract) { auto internal = protocol::UnfreezeAssetContract(); - const auto ownerAddress = Base58::bitcoin.decodeCheck(unfreezeContract.owner_address()); + const auto ownerAddress = Base58::decodeCheck(unfreezeContract.owner_address()); internal.set_owner_address(ownerAddress.data(), ownerAddress.size()); @@ -101,13 +162,13 @@ protocol::UnfreezeAssetContract to_internal(const Proto::UnfreezeAssetContract& protocol::VoteAssetContract to_internal(const Proto::VoteAssetContract& voteContract) { auto internal = protocol::VoteAssetContract(); - const auto ownerAddress = Base58::bitcoin.decodeCheck(voteContract.owner_address()); + const auto ownerAddress = Base58::decodeCheck(voteContract.owner_address()); internal.set_owner_address(ownerAddress.data(), ownerAddress.size()); internal.set_support(voteContract.support()); internal.set_count(voteContract.count()); - for(int i = 0; i < voteContract.vote_address_size(); i++) { - auto voteAddress = Base58::bitcoin.decodeCheck(voteContract.vote_address(i)); + for (int i = 0; i < voteContract.vote_address_size(); i++) { + auto voteAddress = Base58::decodeCheck(voteContract.vote_address(i)); internal.add_vote_address(voteAddress.data(), voteAddress.size()); } @@ -116,12 +177,12 @@ protocol::VoteAssetContract to_internal(const Proto::VoteAssetContract& voteCont protocol::VoteWitnessContract to_internal(const Proto::VoteWitnessContract& voteContract) { auto internal = protocol::VoteWitnessContract(); - const auto ownerAddress = Base58::bitcoin.decodeCheck(voteContract.owner_address()); + const auto ownerAddress = Base58::decodeCheck(voteContract.owner_address()); internal.set_owner_address(ownerAddress.data(), ownerAddress.size()); internal.set_support(voteContract.support()); - for(int i = 0; i < voteContract.votes_size(); i++) { - auto voteAddress = Base58::bitcoin.decodeCheck(voteContract.votes(i).vote_address()); + for (int i = 0; i < voteContract.votes_size(); i++) { + auto voteAddress = Base58::decodeCheck(voteContract.votes(i).vote_address()); auto* vote = internal.add_votes(); vote->set_vote_address(voteAddress.data(), voteAddress.size()); @@ -133,7 +194,7 @@ protocol::VoteWitnessContract to_internal(const Proto::VoteWitnessContract& vote protocol::WithdrawBalanceContract to_internal(const Proto::WithdrawBalanceContract& withdrawContract) { auto internal = protocol::WithdrawBalanceContract(); - const auto ownerAddress = Base58::bitcoin.decodeCheck(withdrawContract.owner_address()); + const auto ownerAddress = Base58::decodeCheck(withdrawContract.owner_address()); internal.set_owner_address(ownerAddress.data(), ownerAddress.size()); @@ -142,8 +203,8 @@ protocol::WithdrawBalanceContract to_internal(const Proto::WithdrawBalanceContra protocol::TriggerSmartContract to_internal(const Proto::TriggerSmartContract& triggerSmartContract) { auto internal = protocol::TriggerSmartContract(); - const auto ownerAddress = Base58::bitcoin.decodeCheck(triggerSmartContract.owner_address()); - const auto contractAddress = Base58::bitcoin.decodeCheck(triggerSmartContract.contract_address()); + const auto ownerAddress = Base58::decodeCheck(triggerSmartContract.owner_address()); + const auto contractAddress = Base58::decodeCheck(triggerSmartContract.contract_address()); internal.set_owner_address(ownerAddress.data(), ownerAddress.size()); internal.set_contract_address(contractAddress.data(), contractAddress.size()); @@ -156,7 +217,7 @@ protocol::TriggerSmartContract to_internal(const Proto::TriggerSmartContract& tr } protocol::TriggerSmartContract to_internal(const Proto::TransferTRC20Contract& transferTrc20Contract) { - auto toAddress = Base58::bitcoin.decodeCheck(transferTrc20Contract.to_address()); + auto toAddress = Base58::decodeCheck(transferTrc20Contract.to_address()); // amount is 256 bits, big endian Data amount = data(transferTrc20Contract.amount()); @@ -206,12 +267,11 @@ void setBlockReference(const Proto::Transaction& transaction, protocol::Transact internal.mutable_raw_data()->set_ref_block_bytes(heightData.data() + heightData.size() - 2, 2); } -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto internal = protocol::Transaction(); - auto output = Proto::SigningOutput(); +protocol::Transaction buildTransaction(const Proto::SigningInput& input) noexcept { + auto tx = protocol::Transaction(); if (input.transaction().has_transfer()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_TransferContract); auto transfer = to_internal(input.transaction().transfer()); @@ -219,7 +279,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { any.PackFrom(transfer); *contract->mutable_parameter() = any; } else if (input.transaction().has_transfer_asset()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_TransferAssetContract); auto transfer = to_internal(input.transaction().transfer_asset()); @@ -227,23 +287,57 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { any.PackFrom(transfer); *contract->mutable_parameter() = any; } else if (input.transaction().has_freeze_balance()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_FreezeBalanceContract); auto freeze_balance = to_internal(input.transaction().freeze_balance()); google::protobuf::Any any; any.PackFrom(freeze_balance); *contract->mutable_parameter() = any; + } else if (input.transaction().has_freeze_balance_v2()) { + auto* contract = tx.mutable_raw_data()->add_contract(); + contract->set_type(protocol::Transaction_Contract_ContractType_FreezeBalanceV2Contract); + auto freeze_balance = to_internal(input.transaction().freeze_balance_v2()); + google::protobuf::Any any; + any.PackFrom(freeze_balance); + *contract->mutable_parameter() = any; } else if (input.transaction().has_unfreeze_balance()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_UnfreezeBalanceContract); - auto unfreeze_balance = to_internal(input.transaction().unfreeze_balance()); google::protobuf::Any any; any.PackFrom(unfreeze_balance); *contract->mutable_parameter() = any; + } else if (input.transaction().has_unfreeze_balance_v2()) { + auto* contract = tx.mutable_raw_data()->add_contract(); + contract->set_type(protocol::Transaction_Contract_ContractType_UnfreezeBalanceV2Contract); + auto unfreeze_balance = to_internal(input.transaction().unfreeze_balance_v2()); + google::protobuf::Any any; + any.PackFrom(unfreeze_balance); + *contract->mutable_parameter() = any; + } else if (input.transaction().has_withdraw_expire_unfreeze()) { + auto* contract = tx.mutable_raw_data()->add_contract(); + contract->set_type(protocol::Transaction_Contract_ContractType_WithdrawExpireUnfreezeContract); + auto withdraw_expire_unfreeze = to_internal(input.transaction().withdraw_expire_unfreeze()); + google::protobuf::Any any; + any.PackFrom(withdraw_expire_unfreeze); + *contract->mutable_parameter() = any; + } else if (input.transaction().has_delegate_resource()) { + auto* contract = tx.mutable_raw_data()->add_contract(); + contract->set_type(protocol::Transaction_Contract_ContractType_DelegateResourceContract); + auto delegate_resource = to_internal(input.transaction().delegate_resource()); + google::protobuf::Any any; + any.PackFrom(delegate_resource); + *contract->mutable_parameter() = any; + } else if (input.transaction().has_undelegate_resource()) { + auto* contract = tx.mutable_raw_data()->add_contract(); + contract->set_type(protocol::Transaction_Contract_ContractType_UnDelegateResourceContract); + auto undelegate_resource = to_internal(input.transaction().undelegate_resource()); + google::protobuf::Any any; + any.PackFrom(undelegate_resource); + *contract->mutable_parameter() = any; } else if (input.transaction().has_unfreeze_asset()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_UnfreezeAssetContract); auto unfreeze_asset = to_internal(input.transaction().unfreeze_asset()); @@ -251,7 +345,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { any.PackFrom(unfreeze_asset); *contract->mutable_parameter() = any; } else if (input.transaction().has_vote_asset()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_VoteAssetContract); auto vote_asset = to_internal(input.transaction().vote_asset()); @@ -259,7 +353,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { any.PackFrom(vote_asset); *contract->mutable_parameter() = any; } else if (input.transaction().has_vote_witness()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_VoteWitnessContract); auto vote_witness = to_internal(input.transaction().vote_witness()); @@ -267,7 +361,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { any.PackFrom(vote_witness); *contract->mutable_parameter() = any; } else if (input.transaction().has_withdraw_balance()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_WithdrawBalanceContract); auto withdraw = to_internal(input.transaction().withdraw_balance()); @@ -275,7 +369,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { any.PackFrom(withdraw); *contract->mutable_parameter() = any; } else if (input.transaction().has_trigger_smart_contract()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_TriggerSmartContract); auto trigger_smart_contract = to_internal(input.transaction().trigger_smart_contract()); @@ -283,7 +377,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { any.PackFrom(trigger_smart_contract); *contract->mutable_parameter() = any; } else if (input.transaction().has_transfer_trc20_contract()) { - auto* contract = internal.mutable_raw_data()->add_contract(); + auto contract = tx.mutable_raw_data()->add_contract(); contract->set_type(protocol::Transaction_Contract_ContractType_TriggerSmartContract); auto trigger_smart_contract = to_internal(input.transaction().transfer_trc20_contract()); @@ -292,32 +386,61 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { *contract->mutable_parameter() = any; } + tx.mutable_raw_data()->set_timestamp(input.transaction().timestamp()); + tx.mutable_raw_data()->set_expiration(input.transaction().expiration()); + tx.mutable_raw_data()->set_fee_limit(input.transaction().fee_limit()); + setBlockReference(input.transaction(), tx); + + return tx; +} + +Data serialize(const protocol::Transaction& tx) noexcept { + const auto serialized = tx.raw_data().SerializeAsString(); + return Data(serialized.begin(), serialized.end()); +} + +Proto::SigningOutput signDirect(const Proto::SigningInput& input) { + const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto hash = parse_hex(input.txid()); + const auto signature = key.sign(hash, TWCurveSECP256k1); + auto output = Proto::SigningOutput(); + output.set_signature(signature.data(), signature.size()); + output.set_id(input.txid()); + output.set_id(hash.data(), hash.size()); + return output; +} + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { + if (!input.txid().empty()) { + return signDirect(input); + } + + auto output = Proto::SigningOutput(); + auto tx = buildTransaction(input); + // Get default timestamp and expiration - const uint64_t now = duration_cast< milliseconds >( - system_clock::now().time_since_epoch() - ).count(); + const uint64_t now = duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); const uint64_t timestamp = input.transaction().timestamp() == 0 - ? now - : input.transaction().timestamp(); + ? now + : input.transaction().timestamp(); const uint64_t expiration = input.transaction().expiration() == 0 - ? timestamp + 10 * 60 * 60 * 1000 // 10 hours - : input.transaction().expiration(); + ? timestamp + 10 * 60 * 60 * 1000 // 10 hours + : input.transaction().expiration(); - internal.mutable_raw_data()->set_timestamp(timestamp); - internal.mutable_raw_data()->set_expiration(expiration); - internal.mutable_raw_data()->set_fee_limit(input.transaction().fee_limit()); - setBlockReference(input.transaction(), internal); + tx.mutable_raw_data()->set_timestamp(timestamp); + tx.mutable_raw_data()->set_expiration(expiration); - output.set_ref_block_bytes(internal.raw_data().ref_block_bytes()); - output.set_ref_block_hash(internal.raw_data().ref_block_hash()); + output.set_ref_block_bytes(tx.raw_data().ref_block_bytes()); + output.set_ref_block_hash(tx.raw_data().ref_block_hash()); - const auto serialized = internal.raw_data().SerializeAsString(); - const auto hash = Hash::sha256(Data(serialized.begin(), serialized.end())); + const auto hash = Hash::sha256(serialize(tx)); const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); const auto signature = key.sign(hash, TWCurveSECP256k1); - const auto json = transactionJSON(internal, hash, signature).dump(); + const auto json = transactionJSON(tx, hash, signature).dump(); output.set_id(hash.data(), hash.size()); output.set_signature(signature.data(), signature.size()); @@ -325,3 +448,23 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { return output; } + +Proto::SigningOutput Signer::compile(const Data& signature) const { + Proto::SigningOutput output; + auto preImage = signaturePreimage(); + auto hash = Hash::sha256(preImage); + auto transaction = buildTransaction(input); + const auto json = transactionJSON(transaction, hash, signature).dump(); + output.set_json(json.data(), json.size()); + output.set_ref_block_bytes(transaction.raw_data().ref_block_bytes()); + output.set_ref_block_hash(transaction.raw_data().ref_block_hash()); + output.set_id(hash.data(), hash.size()); + output.set_signature(signature.data(), signature.size()); + return output; +} + +Data Signer::signaturePreimage() const { + return serialize(buildTransaction(input)); +} + +} // namespace TW::Tron diff --git a/src/Tron/Signer.h b/src/Tron/Signer.h index 2336c3c8fc2..1d3063378af 100644 --- a/src/Tron/Signer.h +++ b/src/Tron/Signer.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/Tron.pb.h" @@ -15,10 +13,14 @@ namespace TW::Tron { /// Helper class that performs Tron transaction signing. class Signer { public: + Proto::SigningInput input; Signer() = delete; - + /// Initializes a transaction signer. + explicit Signer(const Proto::SigningInput& input) : input(input) {} /// Signs the given transaction. static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + Proto::SigningOutput compile(const Data& signature) const; + Data signaturePreimage() const; }; } // namespace TW::Tron diff --git a/src/VeChain/Clause.h b/src/VeChain/Clause.h index 9bbb1bc6435..816647d49d5 100644 --- a/src/VeChain/Clause.h +++ b/src/VeChain/Clause.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../Ethereum/Address.h" #include "../proto/VeChain.pb.h" #include "../uint256.h" diff --git a/src/VeChain/Entry.cpp b/src/VeChain/Entry.cpp index cfb51da124f..312007e942a 100644 --- a/src/VeChain/Entry.cpp +++ b/src/VeChain/Entry.cpp @@ -1,17 +1,60 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" +#include #include "Ethereum/Address.h" #include "Signer.h" -using namespace TW::VeChain; -using namespace std; +namespace TW::VeChain { -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Ethereum::Address::isValid(address); +} + +std::string Entry::normalizeAddress([[maybe_unused]] TWCoinType coin, const std::string& address) const { + // normalized with EIP55 checksum + return Ethereum::Address(address).string(); +} + +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Ethereum::Address(publicKey).string(); +} + +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Ethereum::Address(address); + return {addr.bytes.begin(), addr.bytes.end()}; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto unsignedTxBytes = Signer::buildUnsignedTx(input); + auto imageHash = Hash::blake2b(unsignedTxBytes, 32); + output.set_data(unsignedTxBytes.data(), unsignedTxBytes.size()); + output.set_data_hash(imageHash.data(), imageHash.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, [[maybe_unused]] const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() != 1) { + output.set_error(Common::Proto::Error_no_support_n2n); + output.set_error_message(Common::Proto::SigningError_Name(Common::Proto::Error_no_support_n2n)); + return; + } + + Data signedTx = Signer::buildSignedTx(input, signatures[0]); + output.set_encoded(signedTx.data(), signedTx.size()); + output.set_signature(signatures[0].data(), signatures[0].size()); + }); +} + +} // namespace TW::VeChain diff --git a/src/VeChain/Entry.h b/src/VeChain/Entry.h index 0320bb90f19..c15d4896b03 100644 --- a/src/VeChain/Entry.h +++ b/src/VeChain/Entry.h @@ -1,21 +1,24 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../CoinEntry.h" -#include "Ethereum/Entry.h" +#include "CoinEntry.h" namespace TW::VeChain { /// Entry point for VeChain. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public Ethereum::Entry { +class Entry final : public CoinEntry { public: - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + Data addressToData(TWCoinType coin, const std::string& address) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; }; } // namespace TW::VeChain diff --git a/src/VeChain/Signer.cpp b/src/VeChain/Signer.cpp index 3b12d7818bd..78ddc8a81e6 100644 --- a/src/VeChain/Signer.cpp +++ b/src/VeChain/Signer.cpp @@ -1,15 +1,14 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "../Hash.h" using namespace TW; -using namespace TW::VeChain; + +namespace TW::VeChain { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); @@ -41,3 +40,38 @@ Data Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexce auto signature = privateKey.sign(hash, TWCurveSECP256k1); return Data(signature.begin(), signature.end()); } + +Data Signer::buildUnsignedTx(const Proto::SigningInput& input) noexcept { + auto transaction = Transaction(); + transaction.chainTag = static_cast(input.chain_tag()); + transaction.blockRef = input.block_ref(); + transaction.expiration = input.expiration(); + for (auto& clause : input.clauses()) { + transaction.clauses.emplace_back(clause); + } + transaction.gasPriceCoef = static_cast(input.gas_price_coef()); + transaction.gas = input.gas(); + transaction.dependsOn = Data(input.depends_on().begin(), input.depends_on().end()); + transaction.nonce = input.nonce(); + + return transaction.encode(); +} + +Data Signer::buildSignedTx(const Proto::SigningInput& input, const Data& signature) noexcept { + auto transaction = Transaction(); + transaction.chainTag = static_cast(input.chain_tag()); + transaction.blockRef = input.block_ref(); + transaction.expiration = input.expiration(); + for (auto& clause : input.clauses()) { + transaction.clauses.emplace_back(clause); + } + transaction.gasPriceCoef = static_cast(input.gas_price_coef()); + transaction.gas = input.gas(); + transaction.dependsOn = Data(input.depends_on().begin(), input.depends_on().end()); + transaction.nonce = input.nonce(); + transaction.signature = signature; + + return transaction.encode(); +} + +} // namespace TW::VeChain diff --git a/src/VeChain/Signer.h b/src/VeChain/Signer.h index 2ef5bd0dd7b..aec2d1746b9 100644 --- a/src/VeChain/Signer.h +++ b/src/VeChain/Signer.h @@ -1,18 +1,15 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" -#include #include #include #include @@ -27,6 +24,10 @@ class Signer { /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + static Data buildUnsignedTx(const Proto::SigningInput& input) noexcept; + + static Data buildSignedTx(const Proto::SigningInput& input, const Data& signature) noexcept; + /// Signs the given transaction. static Data sign(const PrivateKey& privateKey, Transaction& transaction) noexcept; }; diff --git a/src/VeChain/Transaction.cpp b/src/VeChain/Transaction.cpp index 383531bf8a5..828d662f79e 100644 --- a/src/VeChain/Transaction.cpp +++ b/src/VeChain/Transaction.cpp @@ -1,47 +1,59 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Transaction.h" #include "../Ethereum/RLP.h" -using namespace TW; -using namespace TW::VeChain; +namespace TW::VeChain { + using RLP = Ethereum::RLP; -Data encode(const Clause& clause) noexcept { - auto encoded = Data(); - append(encoded, RLP::encode(clause.to.bytes)); - append(encoded, RLP::encode(clause.value)); - append(encoded, RLP::encode(clause.data)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem prepareClause(const Clause& clause) noexcept { + auto value = store(clause.value); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(clause.to.bytes.data(), clause.to.bytes.size()); + rlpList->add_items()->set_number_u256(value.data(), value.size()); + rlpList->add_items()->set_data(clause.data.data(), clause.data.size()); + + return item; } -Data encodeClauses(std::vector clauses) noexcept { - auto encoded = Data(); +EthereumRlp::Proto::RlpItem prepareClauses(const std::vector& clauses) noexcept { + EthereumRlp::Proto::RlpItem item; + + auto* rlpList = item.mutable_list(); for (const auto& clause : clauses) { - auto encodedClause = encode(clause); - append(encoded, encodedClause); + *rlpList->add_items() = prepareClause(clause); } - return RLP::encodeList(encoded); + + return item; } Data Transaction::encode() const noexcept { - auto encoded = Data(); - append(encoded, RLP::encode(chainTag)); - append(encoded, RLP::encode(blockRef)); - append(encoded, RLP::encode(expiration)); - append(encoded, encodeClauses(clauses)); - append(encoded, RLP::encode(gasPriceCoef)); - append(encoded, RLP::encode(gas)); - append(encoded, RLP::encode(dependsOn)); - append(encoded, RLP::encode(nonce)); - append(encoded, RLP::encodeList(reserved)); + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); + + rlpList->add_items()->set_number_u64(chainTag); + rlpList->add_items()->set_number_u64(blockRef); + rlpList->add_items()->set_number_u64(expiration); + *rlpList->add_items() = prepareClauses(clauses); + rlpList->add_items()->set_number_u64(gasPriceCoef); + rlpList->add_items()->set_number_u64(gas); + rlpList->add_items()->set_data(dependsOn.data(), dependsOn.size()); + rlpList->add_items()->set_number_u64(nonce); + // Put an empty list - reserved field for backward compatibility. + rlpList->add_items()->mutable_list(); + if (!signature.empty()) { - append(encoded, RLP::encode(signature)); + rlpList->add_items()->set_data(signature.data(), signature.size()); } - return RLP::encodeList(encoded); + + return RLP::encode(input); } + +} // namespace TW::VeChain diff --git a/src/VeChain/Transaction.h b/src/VeChain/Transaction.h index ba905028550..3851ac2f7dc 100644 --- a/src/VeChain/Transaction.h +++ b/src/VeChain/Transaction.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Clause.h" -#include "../Data.h" +#include "Data.h" #include #include diff --git a/src/Verge/Entry.cpp b/src/Verge/Entry.cpp new file mode 100644 index 00000000000..fea63ca93f8 --- /dev/null +++ b/src/Verge/Entry.cpp @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Entry.h" + +#include "Bitcoin/Address.h" +#include "Bitcoin/SegwitAddress.h" +#include "Signer.h" + +using namespace std; + +namespace TW::Verge { + +// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, const PrefixVariant& addressPrefix) const { + auto* base58Prefix = std::get_if(&addressPrefix); + auto* hrp = std::get_if(&addressPrefix); + bool isValidBase58 = base58Prefix ? Bitcoin::Address::isValid(address) : false; + bool isValidHrp = hrp ? Bitcoin::SegwitAddress::isValid(address, *hrp) : false; + return isValidBase58 || isValidHrp; +} + +string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const { + byte p2pkh = getFromPrefixPkhOrDefault(addressPrefix, coin); + const char* hrp = getFromPrefixHrpOrDefault(addressPrefix, coin); + + switch (derivation) { + case TWDerivationBitcoinLegacy: + case TWDerivationDefault: + return Bitcoin::Address(publicKey, p2pkh).string(); + default: + return TW::Bitcoin::SegwitAddress(publicKey, hrp).string(); + } +} + +TW::Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto decoded = Bitcoin::SegwitAddress::decode(address); + if (!std::get<2>(decoded)) { + // check if it is a legacy address + if (Bitcoin::Address::isValid(address)) { + const auto addr = Bitcoin::Address(address); + return {addr.bytes.begin() + 1, addr.bytes.end()}; + } + return {Data()}; + } + return std::get<0>(decoded).witnessProgram; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +void Entry::plan([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + planTemplate(dataIn, dataOut); +} + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + output = Signer::preImageHashes(input); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("signatures size and publickeys size not equal"); + return; + } + + HashPubkeyList externalSignatures; + auto n = signatures.size(); + for (auto i = 0ul; i < n; ++i) { + externalSignatures.push_back(std::make_pair(signatures[i], publicKeys[i].bytes)); + } + + output = Signer::sign(input, externalSignatures); + }); +} + +} // namespace TW::Verge diff --git a/src/Verge/Entry.h b/src/Verge/Entry.h new file mode 100644 index 00000000000..f3bb033d560 --- /dev/null +++ b/src/Verge/Entry.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../CoinEntry.h" + +namespace TW::Verge { + +/// Entry point for implementation of Verge coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final: public CoinEntry { +public: + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + Data addressToData(TWCoinType coin, const std::string& address) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; +}; + +} // namespace TW::Verge diff --git a/src/Verge/Signer.cpp b/src/Verge/Signer.cpp new file mode 100644 index 00000000000..72ce2ad9c43 --- /dev/null +++ b/src/Verge/Signer.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Signer.h" +#include "Bitcoin/TransactionSigner.h" +#include "Hash.h" +#include "HexCoding.h" +#include "Transaction.h" +#include "TransactionBuilder.h" + +using namespace TW; +namespace TW::Verge { + +TransactionPlan Signer::plan(const SigningInput& input) noexcept { + auto plan = Bitcoin::TransactionSigner::plan(input); + return plan.proto(); +} + +SigningOutput Signer::sign(const SigningInput& input, std::optional optionalExternalSigs) noexcept { + SigningOutput output; + auto result = Bitcoin::TransactionSigner::sign(input, false, optionalExternalSigs); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + } else { + const auto& tx = result.payload(); + *output.mutable_transaction() = tx.proto(); + + Data encoded; + tx.encode(encoded); + output.set_encoded(encoded.data(), encoded.size()); + + auto txHash = Hash::sha256d(encoded.data(), encoded.size()); + std::reverse(txHash.begin(), txHash.end()); + output.set_transaction_id(hex(txHash)); + } + return output; +} + +PreSigningOutput Signer::preImageHashes(const SigningInput& input) noexcept { + PreSigningOutput output; + auto result = Bitcoin::TransactionSigner::preImageHashes(input); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + return output; + } + + auto hashList = result.payload(); + auto* hashPubKeys = output.mutable_hash_public_keys(); + for (auto& h : hashList) { + auto* hpk = hashPubKeys->Add(); + hpk->set_data_hash(h.first.data(), h.first.size()); + hpk->set_public_key_hash(h.second.data(), h.second.size()); + } + return output; +} + +} // namespace TW::Verge diff --git a/src/Verge/Signer.h b/src/Verge/Signer.h new file mode 100644 index 00000000000..659ab1a894b --- /dev/null +++ b/src/Verge/Signer.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../Data.h" +#include "../proto/Bitcoin.pb.h" + +#include +#include +#include + +namespace TW::Verge { + +using SigningInput = Bitcoin::Proto::SigningInput; +using SigningOutput = Bitcoin::Proto::SigningOutput; +using TransactionPlan = Bitcoin::Proto::TransactionPlan; +using PreSigningOutput = Bitcoin::Proto::PreSigningOutput; + +/// Helper class that performs Verge transaction signing. +class Signer { +public: + /// Hide default constructor + Signer() = delete; + + /// Returns a transaction plan (utxo selection, fee estimation) + static TransactionPlan plan(const SigningInput& input) noexcept; + + /// Signs a SigningInput transaction + static SigningOutput sign(const SigningInput& input, std::optional optionalExternalSigs = {}) noexcept; + + /// Collect pre-image hashes to be signed + static PreSigningOutput preImageHashes(const SigningInput& input) noexcept; +}; + +} // namespace TW::Verge diff --git a/src/Verge/Transaction.cpp b/src/Verge/Transaction.cpp new file mode 100644 index 00000000000..e8469f7ba61 --- /dev/null +++ b/src/Verge/Transaction.cpp @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Transaction.h" +#include "../BinaryCoding.h" +#include "../Hash.h" +#include "../Data.h" +#include "../HexCoding.h" + +#include "../Bitcoin/SegwitAddress.h" +#include "../Bitcoin/SigHashType.h" +#include "../Bitcoin/SignatureVersion.h" + +#include + +using namespace TW; +namespace TW::Verge { + +Data Transaction::getPreImage(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount) const { + assert(index < inputs.size()); + + Data data; + + // Version + encode32LE(_version, data); + + // Time + encode32LE(time, data); + + // Input prevouts (none/all, depending on flags) + if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0) { + auto hashPrevouts = getPrevoutHash(); + std::copy(std::begin(hashPrevouts), std::end(hashPrevouts), std::back_inserter(data)); + } else { + std::fill_n(back_inserter(data), 32, 0); + } + + // Input nSequence (none/all, depending on flags) + if ((hashType & TWBitcoinSigHashTypeAnyoneCanPay) == 0 && + !Bitcoin::hashTypeIsSingle(hashType) && !Bitcoin::hashTypeIsNone(hashType)) { + auto hashSequence = getSequenceHash(); + std::copy(std::begin(hashSequence), std::end(hashSequence), std::back_inserter(data)); + } else { + std::fill_n(back_inserter(data), 32, 0); + } + + // The input being signed (replacing the scriptSig with scriptCode + amount) + // The prevout may already be contained in hashPrevout, and the nSequence + // may already be contain in hashSequence. + inputs[index].previousOutput.encode(data); + scriptCode.encode(data); + + encode64LE(amount, data); + encode32LE(inputs[index].sequence, data); + + // Outputs (none/one/all, depending on flags) + if (!Bitcoin::hashTypeIsSingle(hashType) && !Bitcoin::hashTypeIsNone(hashType)) { + auto hashOutputs = getOutputsHash(); + copy(begin(hashOutputs), end(hashOutputs), back_inserter(data)); + } else if (Bitcoin::hashTypeIsSingle(hashType) && index < outputs.size()) { + Data outputData; + outputs[index].encode(outputData); + auto hashOutputs = Hash::hash(hasher, outputData); + copy(begin(hashOutputs), end(hashOutputs), back_inserter(data)); + } else { + fill_n(back_inserter(data), 32, 0); + } + + // Locktime + encode32LE(lockTime, data); + + // Sighash type + encode32LE(hashType, data); + + return data; +} + +void Transaction::encode(Data& data, enum SegwitFormatMode segwitFormat) const { + bool useWitnessFormat = true; + switch (segwitFormat) { + case NonSegwit: useWitnessFormat = false; break; + case IfHasWitness: useWitnessFormat = hasWitness(); break; + case Segwit: useWitnessFormat = true; break; + } + + encode32LE(_version, data); + + encode32LE(time, data); + + if (useWitnessFormat) { + // Use extended format in case witnesses are to be serialized. + data.push_back(0); // marker + data.push_back(1); // flag + } + + // txins + encodeVarInt(inputs.size(), data); + for (auto& input : inputs) { + input.encode(data); + } + + // txouts + encodeVarInt(outputs.size(), data); + for (auto& output : outputs) { + output.encode(data); + } + + if (useWitnessFormat) { + encodeWitness(data); + } + + encode32LE(lockTime, data); // nLockTime +} + +Data Transaction::getSignatureHash(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount, + enum Bitcoin::SignatureVersion version) const { + switch (version) { + case Bitcoin::BASE: + return getSignatureHashBase(scriptCode, index, hashType); + case Bitcoin::WITNESS_V0: + return getSignatureHashWitnessV0(scriptCode, index, hashType, amount); + } +} + +/// Generates the signature hash for Witness version 0 scripts. +Data Transaction::getSignatureHashWitnessV0(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, + uint64_t amount) const { + auto preimage = getPreImage(scriptCode, index, hashType, amount); + auto hash = Hash::hash(hasher, preimage); + return hash; +} + +/// Generates the signature hash for for scripts other than witness scripts. +Data Transaction::getSignatureHashBase(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType) const { + assert(index < inputs.size()); + + Data data; + + encode32LE(_version, data); + + encode32LE(time, data); + + auto serializedInputCount = + (hashType & TWBitcoinSigHashTypeAnyoneCanPay) != 0 ? 1 : inputs.size(); + encodeVarInt(serializedInputCount, data); + for (auto subindex = 0ul; subindex < serializedInputCount; subindex += 1) { + serializeInput(subindex, scriptCode, index, hashType, data); + } + + auto hashNone = Bitcoin::hashTypeIsNone(hashType); + auto hashSingle = Bitcoin::hashTypeIsSingle(hashType); + auto serializedOutputCount = hashNone ? 0 : (hashSingle ? index + 1 : outputs.size()); + encodeVarInt(serializedOutputCount, data); + for (auto subindex = 0ul; subindex < serializedOutputCount; subindex += 1) { + if (hashSingle && subindex != index) { + auto output = Bitcoin::TransactionOutput(-1, {}); + output.encode(data); + } else { + outputs[subindex].encode(data); + } + } + + // Locktime + encode32LE(lockTime, data); + + // Sighash type + encode32LE(hashType, data); + + auto hash = Hash::hash(hasher, data); + return hash; +} + +} // namespace TW::Verge \ No newline at end of file diff --git a/src/Verge/Transaction.h b/src/Verge/Transaction.h new file mode 100644 index 00000000000..06097df9695 --- /dev/null +++ b/src/Verge/Transaction.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include "../Bitcoin/Transaction.h" +#include "../PrivateKey.h" +#include "../Hash.h" +#include "../Data.h" +#include "../proto/Bitcoin.pb.h" + +#include "../Bitcoin/Script.h" +#include "../Bitcoin/SignatureVersion.h" + +#include + +namespace TW::Verge { + +struct Transaction : public Bitcoin::Transaction { +public: + /// Transaction time + uint32_t time = 0; + +public: + Transaction() = default; + + Transaction(int32_t version, uint32_t time = 0, uint32_t lockTime = 0, TW::Hash::Hasher hasher = TW::Hash::HasherSha256d) + : Bitcoin::Transaction(version, lockTime, hasher) + , time(time) {} + + /// Generates the signature pre-image. + Data getPreImage(const Bitcoin::Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount) const; + + /// Encodes the transaction into the provided buffer. + void encode(Data& data, enum SegwitFormatMode segwitFormat) const; + + /// Default one-parameter version, needed for templated usage. + void encode(Data& data) const { encode(data, SegwitFormatMode::IfHasWitness); } + + /// Generates the signature hash for this transaction. + Data getSignatureHash(const Bitcoin::Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, + uint64_t amount, enum Bitcoin::SignatureVersion version) const; + +private: + /// Generates the signature hash for Witness version 0 scripts. + Data getSignatureHashWitnessV0(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType, uint64_t amount) const; + + /// Generates the signature hash for for scripts other than witness scripts. + Data getSignatureHashBase(const Bitcoin::Script& scriptCode, size_t index, + enum TWBitcoinSigHashType hashType) const; +}; + +} // namespace TW::Verge diff --git a/src/Verge/TransactionBuilder.h b/src/Verge/TransactionBuilder.h new file mode 100644 index 00000000000..a1532dea9ed --- /dev/null +++ b/src/Verge/TransactionBuilder.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Transaction.h" +#include "../Bitcoin/TransactionBuilder.h" +#include "../Bitcoin/TransactionPlan.h" +#include "../proto/Bitcoin.pb.h" +#include "../HexCoding.h" +#include + +#include + +namespace TW::Verge { + +struct TransactionBuilder { + /// Plans a transaction by selecting UTXOs and calculating fees. + static Bitcoin::TransactionPlan plan(const Bitcoin::SigningInput& input) { + return Bitcoin::TransactionBuilder::plan(input); + } + + /// Builds a transaction by selecting UTXOs and calculating fees. + template + static Result build(const Bitcoin::TransactionPlan& plan, + const Bitcoin::SigningInput& input) { + auto tx_result = Bitcoin::TransactionBuilder::build(plan, input); + if (!tx_result) { return Result::failure(tx_result.error()); } + Transaction tx = tx_result.payload(); + + tx.time = input.time; + // if not set, always use latest time + if (tx.time == 0) { + tx.time = (uint32_t)std::time(nullptr); + } + return Result(tx); + } +}; + +} // namespace TW::Verge diff --git a/src/Wasm.h b/src/Wasm.h index afb9c67aa88..666c911d1e0 100644 --- a/src/Wasm.h +++ b/src/Wasm.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #ifndef __USE_WASM #define __USE_WASM diff --git a/src/Waves/Address.cpp b/src/Waves/Address.cpp index 685f4327fb5..9b9ab7a27fa 100644 --- a/src/Waves/Address.cpp +++ b/src/Waves/Address.cpp @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" #include "../Base58.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include @@ -15,8 +13,7 @@ #include #include -using namespace TW; -using namespace TW::Waves; +namespace TW::Waves { template Data Address::secureHash(const T &data) { @@ -40,18 +37,16 @@ bool Address::isValid(const Data& decoded) { const auto data_checksum = Data(decoded.end() - 4, decoded.end()); const auto calculated_hash = secureHash(data); const auto calculated_checksum = Data(calculated_hash.begin(), calculated_hash.begin() + 4); - const auto h = hex(data); - const auto h2 = hex(calculated_hash); return std::memcmp(data_checksum.data(), calculated_checksum.data(), 4) == 0; } bool Address::isValid(const std::string& string) { - const auto decoded = Base58::bitcoin.decode(string); + const auto decoded = Base58::decode(string); return isValid(decoded); } Address::Address(const std::string& string) { - const auto decoded = Base58::bitcoin.decode(string); + const auto decoded = Base58::decode(string); if (!isValid(string)) { throw std::invalid_argument("Invalid address key data"); } @@ -82,5 +77,7 @@ Address::Address(const PublicKey &publicKey) { } std::string Address::string() const { - return Base58::bitcoin.encode(bytes); -} \ No newline at end of file + return Base58::encode(bytes); +} + +} // namespace TW::Waves diff --git a/src/Waves/Address.h b/src/Waves/Address.h index df518415334..376d05057a8 100644 --- a/src/Waves/Address.h +++ b/src/Waves/Address.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "../Base58Address.h" -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include diff --git a/src/Waves/BinaryCoding.h b/src/Waves/BinaryCoding.h index 73e10278ab9..51e6b126c7d 100644 --- a/src/Waves/BinaryCoding.h +++ b/src/Waves/BinaryCoding.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Waves/Entry.cpp b/src/Waves/Entry.cpp index 6162485901d..1b166e3fb41 100644 --- a/src/Waves/Entry.cpp +++ b/src/Waves/Entry.cpp @@ -1,27 +1,28 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" #include "Signer.h" -using namespace TW::Waves; using namespace std; +namespace TW::Waves { + // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } + +} // namespace TW::Waves diff --git a/src/Waves/Entry.h b/src/Waves/Entry.h index 344e4971e20..ac78c44e3fa 100644 --- a/src/Waves/Entry.h +++ b/src/Waves/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,11 +10,11 @@ namespace TW::Waves { /// Entry point for implementation of Waves coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; }; } // namespace TW::Waves diff --git a/src/Waves/Signer.cpp b/src/Waves/Signer.cpp index 701d47e869b..2d4d6955236 100644 --- a/src/Waves/Signer.cpp +++ b/src/Waves/Signer.cpp @@ -1,15 +1,14 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "../Hash.h" using namespace TW; -using namespace TW::Waves; + +namespace TW::Waves { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); @@ -19,12 +18,12 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Data signature = Signer::sign(privateKey, transaction); Proto::SigningOutput output = Proto::SigningOutput(); - output.set_signature(reinterpret_cast(signature.data()), signature.size()); + output.set_signature(reinterpret_cast(signature.data()), signature.size()); output.set_json(transaction.buildJson(signature).dump()); return output; } -Data Signer::sign(const PrivateKey &privateKey, Transaction &transaction) noexcept { +Data Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexcept { try { auto bytesToSign = transaction.serializeToSign(); auto signature = privateKey.sign(bytesToSign, TWCurveCurve25519); @@ -33,3 +32,5 @@ Data Signer::sign(const PrivateKey &privateKey, Transaction &transaction) noexce return Data(); } } + +} // namespace TW::Waves diff --git a/src/Waves/Signer.h b/src/Waves/Signer.h index 4540029e28c..00cd23b0726 100644 --- a/src/Waves/Signer.h +++ b/src/Waves/Signer.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Transaction.h" -#include "../Data.h" +#include "Data.h" #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/Waves.pb.h" diff --git a/src/Waves/Transaction.cpp b/src/Waves/Transaction.cpp index ee9d2a5af44..6e676538fd4 100644 --- a/src/Waves/Transaction.cpp +++ b/src/Waves/Transaction.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Transaction.h" #include "BinaryCoding.h" @@ -11,7 +9,8 @@ #include "../HexCoding.h" using namespace TW; -using namespace TW::Waves; + +namespace TW::Waves { using json = nlohmann::json; @@ -20,7 +19,7 @@ const std::string Transaction::WAVES = "WAVES"; Data serializeTransfer(int64_t amount, std::string asset, int64_t fee, std::string fee_asset, Address to, const Data& attachment, int64_t timestamp, const Data& pub_key) { auto data = Data(); if (asset.empty()) { - asset = Transaction::WAVES; + asset = Transaction::WAVES; } if (fee_asset.empty()) { fee_asset = Transaction::WAVES; @@ -33,20 +32,20 @@ Data serializeTransfer(int64_t amount, std::string asset, int64_t fee, std::stri data.push_back(static_cast(0)); } else { data.push_back(static_cast(1)); - append(data, Base58::bitcoin.decode(asset)); + append(data, Base58::decode(asset)); } if (fee_asset == Transaction::WAVES) { data.push_back(static_cast(0)); } else { data.push_back(static_cast(1)); - append(data, Base58::bitcoin.decode(fee_asset)); + append(data, Base58::decode(fee_asset)); } encode64BE(timestamp, data); encode64BE(amount, data); encode64BE(fee, data); append(data, Data(std::begin(to.bytes), std::end(to.bytes))); encodeDynamicLengthBytes(attachment, data); - + return data; } @@ -61,7 +60,7 @@ Data serializeLease(int64_t amount, int64_t fee, Address to, int64_t timestamp, encode64BE(amount, data); encode64BE(fee, data); encode64BE(timestamp, data); - + return data; } @@ -75,19 +74,19 @@ Data serializeCancelLease(const Data& leaseId, int64_t fee, int64_t timestamp, c encode64BE(fee, data); encode64BE(timestamp, data); append(data, leaseId); - + return data; } json jsonTransfer(const Data& signature, int64_t amount, const std::string& asset, int64_t fee, const std::string& fee_asset, Address to, const Data& attachment, int64_t timestamp, const Data& pub_key) { json jsonTx; - + jsonTx["type"] = TransactionType::transfer; jsonTx["version"] = TransactionVersion::V2; jsonTx["fee"] = fee; - jsonTx["senderPublicKey"] = Base58::bitcoin.encode(pub_key); + jsonTx["senderPublicKey"] = Base58::encode(pub_key); jsonTx["timestamp"] = timestamp; - jsonTx["proofs"] = json::array({Base58::bitcoin.encode(signature)}); + jsonTx["proofs"] = json::array({Base58::encode(signature)}); jsonTx["recipient"] = Address(to).string(); if (asset != Transaction::WAVES) { jsonTx["assetId"] = asset; @@ -96,38 +95,38 @@ json jsonTransfer(const Data& signature, int64_t amount, const std::string& asse jsonTx["feeAssetId"] = fee_asset; } jsonTx["amount"] = amount; - jsonTx["attachment"] = Base58::bitcoin.encode(attachment); - + jsonTx["attachment"] = Base58::encode(attachment); + return jsonTx; } json jsonLease(const Data& signature, int64_t amount, int64_t fee, Address to, int64_t timestamp, const Data& pub_key) { json jsonTx; - + jsonTx["type"] = TransactionType::lease; jsonTx["version"] = TransactionVersion::V2; jsonTx["fee"] = fee; - jsonTx["senderPublicKey"] = Base58::bitcoin.encode(pub_key); + jsonTx["senderPublicKey"] = Base58::encode(pub_key); jsonTx["timestamp"] = timestamp; - jsonTx["proofs"] = json::array({Base58::bitcoin.encode(signature)}); + jsonTx["proofs"] = json::array({Base58::encode(signature)}); jsonTx["recipient"] = Address(to).string(); jsonTx["amount"] = amount; - + return jsonTx; } json jsonCancelLease(const Data& signature, const Data& leaseId, int64_t fee, int64_t timestamp, const Data& pub_key) { json jsonTx; - + jsonTx["type"] = TransactionType::cancelLease; jsonTx["version"] = TransactionVersion::V2; jsonTx["fee"] = fee; - jsonTx["senderPublicKey"] = Base58::bitcoin.encode(pub_key); - jsonTx["leaseId"] = Base58::bitcoin.encode(leaseId); + jsonTx["senderPublicKey"] = Base58::encode(pub_key); + jsonTx["leaseId"] = Base58::encode(leaseId); jsonTx["chainId"] = 87; // mainnet jsonTx["timestamp"] = timestamp; - jsonTx["proofs"] = json::array({Base58::bitcoin.encode(signature)}); - + jsonTx["proofs"] = json::array({Base58::encode(signature)}); + return jsonTx; } @@ -151,52 +150,47 @@ Data Transaction::serializeToSign() const { return serializeLease(message.amount(), message.fee(), Address(message.to()), input.timestamp(), pub_key); } else if (input.has_cancel_lease_message()) { auto message = input.cancel_lease_message(); - auto leaseId = Base58::bitcoin.decode(message.lease_id()); + auto leaseId = Base58::decode(message.lease_id()); return serializeCancelLease(leaseId, message.fee(), input.timestamp(), pub_key); } - + return Data(); } - - - - json Transaction::buildJson(const Data& signature) const { if (input.has_transfer_message()) { auto message = input.transfer_message(); auto attachment = Data(message.attachment().begin(), message.attachment().end()); return jsonTransfer( - signature, - message.amount(), - message.asset(), - message.fee(), - message.fee_asset(), - Address(message.to()), - attachment, - input.timestamp(), - pub_key); + signature, + message.amount(), + message.asset(), + message.fee(), + message.fee_asset(), + Address(message.to()), + attachment, + input.timestamp(), + pub_key); } else if (input.has_lease_message()) { auto message = input.lease_message(); return jsonLease( - signature, - message.amount(), - message.fee(), - Address(message.to()), - input.timestamp(), - pub_key); + signature, + message.amount(), + message.fee(), + Address(message.to()), + input.timestamp(), + pub_key); } else if (input.has_cancel_lease_message()) { auto message = input.cancel_lease_message(); - auto leaseId = Base58::bitcoin.decode(message.lease_id()); + auto leaseId = Base58::decode(message.lease_id()); return jsonCancelLease( - signature, - leaseId, - message.fee(), - input.timestamp(), - pub_key); + signature, + leaseId, + message.fee(), + input.timestamp(), + pub_key); } return nullptr; } - - +} // namespace TW::Waves diff --git a/src/Waves/Transaction.h b/src/Waves/Transaction.h index b22fd54a030..5f775df7faa 100644 --- a/src/Waves/Transaction.h +++ b/src/Waves/Transaction.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Address.h" -#include "../Data.h" +#include "Data.h" #include "../proto/Waves.pb.h" #include diff --git a/src/WebAuthn.cpp b/src/WebAuthn.cpp new file mode 100644 index 00000000000..780b0c0b4f8 --- /dev/null +++ b/src/WebAuthn.cpp @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + + +#include "Cbor.h" +#include "PublicKey.h" + +#include +#include +#include + +namespace TW::WebAuthn { + +// https://www.w3.org/TR/webauthn-2/#authenticator-data +struct AuthData { + Data rpIdHash; + std::array flagsBuf; + struct { + bool up; + bool uv; + bool at; + bool ed; + std::uint8_t flagsInt; + } flags; + std::uint32_t counter; + Data counterBuf; + Data aaguid; + Data credID; + Data COSEPublicKey; +}; + +AuthData parseAuthData(const Data& buffer) { + AuthData authData; + + authData.rpIdHash = subData(buffer, 0, 32); + + auto it = buffer.begin() + 32; + authData.flagsBuf = { *it }; + ++it; + std::uint8_t flagsInt = authData.flagsBuf[0]; + authData.flags.up = !!(flagsInt & 0x01); + authData.flags.uv = !!(flagsInt & 0x04); + authData.flags.at = !!(flagsInt & 0x40); + authData.flags.ed = !!(flagsInt & 0x80); + authData.flags.flagsInt = flagsInt; + + authData.counterBuf = Data(it, it + 4); + authData.counter = static_cast((authData.counterBuf[0] << 24) | + (authData.counterBuf[1] << 16) | + (authData.counterBuf[2] << 8) | + authData.counterBuf[3]); + it += 4; + + if (authData.flags.at) { + authData.aaguid = Data(it, it + 16); + it += 16; + + std::array credIDLenBuf = { *(it), *(it + 1) }; + std::uint16_t credIDLen = static_cast((credIDLenBuf[0] << 8) | + credIDLenBuf[1]); + it += 2; + + authData.credID = Data(it, it + credIDLen); + it += credIDLen; + + authData.COSEPublicKey = Data(it, buffer.end()); + } + + return authData; +} + +auto findIntKey = [](const auto& map, const auto& key) { + return std::find_if(map.begin(), map.end(), [&](const auto& p) { + return p.first.dumpToString() == key; + }); +}; + +auto findStringKey = [](const auto& map, const auto& key) { + return std::find_if(map.begin(), map.end(), [&](const auto& p) { + return p.first.getString() == key; + }); +}; + +std::optional getPublicKey(const Data& attestationObject) { + const Data authData = findStringKey(TW::Cbor::Decode(attestationObject).getMapElements(), "authData")->second.getBytes(); + if (authData.empty()) { + return std::nullopt; + } + + const AuthData authDataParsed = parseAuthData(authData); + const auto COSEPublicKey = TW::Cbor::Decode(authDataParsed.COSEPublicKey).getMapElements(); + + if (COSEPublicKey.empty()) { + return std::nullopt; + } + + // https://www.w3.org/TR/webauthn-2/#sctn-encoded-credPubKey-examples + const std::string xKey = "-2"; + const std::string yKey = "-3"; + + const auto x = findIntKey(COSEPublicKey, xKey); + const auto y = findIntKey(COSEPublicKey, yKey); + + Data publicKey; + append(publicKey, 0x04); + append(publicKey, x->second.getBytes()); + append(publicKey, y->second.getBytes()); + + return PublicKey(publicKey, TWPublicKeyTypeNIST256p1Extended); +} + +Data reconstructSignedMessage(const Data& authenticatorData, const Data& clientDataJSON) { + const auto& clientHash = Hash::sha256(clientDataJSON); + + Data message; + append(message, authenticatorData); + append(message, clientHash); + + return Hash::sha256(message); +} + +} // namespace TW::Webauthn diff --git a/src/WebAuthn.h b/src/WebAuthn.h new file mode 100644 index 00000000000..67291d97f91 --- /dev/null +++ b/src/WebAuthn.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "PublicKey.h" +#include + +namespace TW::WebAuthn { + +std::optional getPublicKey(const Data& attestationObject); +Data reconstructSignedMessage(const Data& authenticatorData, const Data& clientDataJSON); + +} diff --git a/src/XRP/Address.cpp b/src/XRP/Address.cpp new file mode 100644 index 00000000000..6a24147050b --- /dev/null +++ b/src/XRP/Address.cpp @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Address.h" +#include "../Base58.h" +#include + +namespace TW::Ripple { + +bool Address::isValid(const std::string& string) { + const auto decoded = Base58::decodeCheck(string, Rust::Base58Alphabet::Ripple); + if (decoded.size() != Address::size) { + return false; + } + return true; +} + +Address::Address(const std::string& string) { + const auto decoded = Base58::decodeCheck(string, Rust::Base58Alphabet::Ripple); + if (decoded.size() != Address::size) { + throw std::invalid_argument("Invalid address string"); + } + std::copy(decoded.begin(), decoded.end(), bytes.begin()); +} + +Address::Address(const PublicKey& publicKey) { + /// see type prefix: https://developers.ripple.com/base58-encodings.html + bytes[0] = 0x00; + ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, bytes.data() + 1); +} + +std::string Address::string() const { + return Base58::encodeCheck(bytes, Rust::Base58Alphabet::Ripple); +} + +} // namespace TW::Ripple diff --git a/src/XRP/Address.h b/src/XRP/Address.h new file mode 100644 index 00000000000..c03f3a0a746 --- /dev/null +++ b/src/XRP/Address.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Data.h" +#include "../PublicKey.h" + +#include + +namespace TW::Ripple { + +class Address { + public: + /// Number of bytes in an address. + static const size_t size = 21; + + /// Address data consisting of a prefix byte followed by the public key hash + std::array bytes; + + /// Determines whether a string makes a valid address. + static bool isValid(const std::string& string); + + /// Initializes a Ripple address with a string representation. + explicit Address(const std::string& string); + + /// Initializes a Ripple address with a public key. + explicit Address(const PublicKey& publicKey); + + /// Returns a string representation of the address. + std::string string() const; +}; + +inline bool operator==(const Address& lhs, const Address& rhs) { + return lhs.bytes == rhs.bytes; +} + +} // namespace TW::Ripple diff --git a/src/XRP/BinaryCoding.h b/src/XRP/BinaryCoding.h new file mode 100644 index 00000000000..ef1cb8d1cce --- /dev/null +++ b/src/XRP/BinaryCoding.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include +#include +#include "Data.h" + +namespace TW::Ripple { + +enum class FieldType; + +/// Encodes a field type. +inline void encodeType(FieldType type, int key, std::vector& data) { + const auto typeValue = static_cast(type); + if (typeValue <= 0xf) { + if (key <= 0xf) { + data.emplace_back(static_cast((typeValue << 4) | key)); + } else { + data.emplace_back(static_cast(typeValue << 4)); + data.emplace_back(static_cast(key)); + } + } else if (key <= 0xf) { + data.emplace_back(static_cast(key)); + data.emplace_back(static_cast(typeValue)); + } else { + data.emplace_back(0); + data.emplace_back(static_cast(typeValue)); + data.emplace_back(static_cast(key)); + } +} + +/// Encodes a variable length. +inline void encodeVariableLength(size_t length, std::vector& data) { + if (length <= 192) { + data.push_back(static_cast(length)); + } else if (length <= 12480) { + length -= 193; + data.push_back(static_cast(length >> 8)); + data.push_back(static_cast(length & 0xff)); + } else if (length <= 918744) { + length -= 12481; + data.push_back(static_cast(length >> 16)); + data.push_back(static_cast((length >> 8) & 0xff)); + data.push_back(static_cast(length & 0xff)); + } +} + +/// Encodes a variable length bytes. +inline void encodeBytes(std::vector bytes, std::vector& data) { + encodeVariableLength(bytes.size(), data); + data.insert(data.end(), bytes.begin(), bytes.end()); +} + +inline void encodeZeros(std::size_t len, std::vector& data) { + append(data, Data(len)); +} + +} // namespace TW::Ripple diff --git a/src/XRP/Entry.cpp b/src/XRP/Entry.cpp new file mode 100644 index 00000000000..ea7274ef24d --- /dev/null +++ b/src/XRP/Entry.cpp @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Entry.h" + +#include "Address.h" +#include "XAddress.h" +#include "Signer.h" +#include "../proto/TransactionCompiler.pb.h" + +namespace TW::Ripple { + +// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. + +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Address::isValid(address) || XAddress::isValid(address); +} + +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Address(publicKey).string(); +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + auto signer = Signer(input); + auto preimage = signer.preImage(); + output.set_data(preimage.data(), preimage.size()); + auto hash = Hash::sha512(preimage); + auto preimageHash = Data(hash.begin(), hash.begin() + 32); + output.set_data_hash(preimageHash.data(), preimageHash.size()); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, + const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + auto signer = Signer(input); + output = signer.compile(signature, publicKey); + }); +} +} // namespace TW::Ripple diff --git a/src/XRP/Entry.h b/src/XRP/Entry.h new file mode 100644 index 00000000000..394550cdafe --- /dev/null +++ b/src/XRP/Entry.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../CoinEntry.h" + +namespace TW::Ripple { + +/// Entry point for implementation of Ripple (XRP) coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final : public CoinEntry { +public: + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; +}; + +} // namespace TW::Ripple diff --git a/src/XRP/Signer.cpp b/src/XRP/Signer.cpp new file mode 100644 index 00000000000..0a8ff4ff0f2 --- /dev/null +++ b/src/XRP/Signer.cpp @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Signer.h" +#include "../BinaryCoding.h" +#include + +namespace TW::Ripple { + + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { + auto output = Proto::SigningOutput(); + + auto transaction = + Transaction( + input.fee(), + input.flags(), + input.sequence(), + input.last_ledger_sequence(), + Address(input.account())); + switch (input.operation_oneof_case()) { + case Proto::SigningInput::kOpPayment: + signPayment(input, output, transaction); + break; + + case Proto::SigningInput::kOpEscrowCreate: + transaction.createEscrowCreate( + input.op_escrow_create().amount(), + input.op_escrow_create().destination(), + input.op_escrow_create().destination_tag(), + input.op_escrow_create().cancel_after(), + input.op_escrow_create().finish_after(), + input.op_escrow_create().condition()); + break; + + case Proto::SigningInput::kOpEscrowCancel: + transaction.createEscrowCancel( + input.op_escrow_cancel().owner(), + input.op_escrow_cancel().offer_sequence()); + break; + + case Proto::SigningInput::kOpEscrowFinish: + transaction.createEscrowFinish( + input.op_escrow_finish().owner(), + input.op_escrow_finish().offer_sequence(), + input.op_escrow_finish().condition(), + input.op_escrow_finish().fulfillment()); + break; + + case Proto::SigningInput::kOpNftokenBurn: + transaction.createNFTokenBurn(input.op_nftoken_burn().nftoken_id()); + break; + + case Proto::SigningInput::kOpNftokenCreateOffer: + transaction.createNFTokenCreateOffer( + input.op_nftoken_create_offer().nftoken_id(), + input.op_nftoken_create_offer().destination()); + break; + + case Proto::SigningInput::kOpNftokenAcceptOffer: + transaction.createNFTokenAcceptOffer(input.op_nftoken_accept_offer().sell_offer()); + break; + + case Proto::SigningInput::kOpNftokenCancelOffer: + signNfTokenCancelOffer(input, transaction); + break; + + case Proto::SigningInput::kOpTrustSet: + transaction.createTrustSet( + input.op_trust_set().limit_amount().currency(), + input.op_trust_set().limit_amount().value(), + input.op_trust_set().limit_amount().issuer()); + break; + + default: + break; + } + + if (output.error()) { + return output; + } + + auto signer = Signer(); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + signer.sign(key, transaction); + + auto encoded = transaction.serialize(); + output.set_encoded(encoded.data(), encoded.size()); + return output; +} + +void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) const noexcept { + /// See https://github.com/trezor/trezor-core/blob/master/src/apps/ripple/sign_tx.py#L59 + transaction.pub_key = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1).bytes; + + auto unsignedTx = transaction.getPreImage(); + auto hash = Hash::sha512(unsignedTx); + auto half = Data(hash.begin(), hash.begin() + 32); + + transaction.signature = privateKey.signAsDER(half); +} + +TW::Data Signer::preImage() const { + auto output = Proto::SigningOutput(); + + auto transaction = + Transaction( + input.fee(), + input.flags(), + input.sequence(), + input.last_ledger_sequence(), + Address(input.account())); + switch (input.operation_oneof_case()) { + case Proto::SigningInput::kOpPayment: + signPayment(input, output, transaction); + break; + + case Proto::SigningInput::kOpEscrowCreate: + transaction.createEscrowCreate( + input.op_escrow_create().amount(), + input.op_escrow_create().destination(), + input.op_escrow_create().destination_tag(), + input.op_escrow_create().cancel_after(), + input.op_escrow_create().finish_after(), + input.op_escrow_create().condition()); + break; + + case Proto::SigningInput::kOpEscrowCancel: + transaction.createEscrowCancel( + input.op_escrow_cancel().owner(), + input.op_escrow_cancel().offer_sequence()); + break; + + case Proto::SigningInput::kOpEscrowFinish: + transaction.createEscrowFinish( + input.op_escrow_finish().owner(), + input.op_escrow_finish().offer_sequence(), + input.op_escrow_finish().condition(), + input.op_escrow_finish().fulfillment()); + break; + + case Proto::SigningInput::kOpNftokenBurn: + transaction.createNFTokenBurn(input.op_nftoken_burn().nftoken_id()); + break; + + case Proto::SigningInput::kOpNftokenCreateOffer: + transaction.createNFTokenCreateOffer( + input.op_nftoken_create_offer().nftoken_id(), + input.op_nftoken_create_offer().destination()); + break; + + case Proto::SigningInput::kOpNftokenAcceptOffer: + transaction.createNFTokenAcceptOffer(input.op_nftoken_accept_offer().sell_offer()); + break; + + case Proto::SigningInput::kOpNftokenCancelOffer: + signNfTokenCancelOffer(input, transaction); + break; + + case Proto::SigningInput::kOpTrustSet: + transaction.createTrustSet( + input.op_trust_set().limit_amount().currency(), + input.op_trust_set().limit_amount().value(), + input.op_trust_set().limit_amount().issuer()); + break; + + default: + break; + } + + if (output.error()) { + return {}; + } + + auto publicKey = Data(input.public_key().begin(), input.public_key().end()); + transaction.pub_key = publicKey; + + auto unsignedTx = transaction.getPreImage(); + return unsignedTx; +} + +Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey) const { + auto output = Proto::SigningOutput(); + + auto transaction = + Transaction( + input.fee(), + input.flags(), + input.sequence(), + input.last_ledger_sequence(), + Address(input.account())); + switch (input.operation_oneof_case()) { + case Proto::SigningInput::kOpPayment: + signPayment(input, output, transaction); + break; + + case Proto::SigningInput::kOpEscrowCreate: + transaction.createEscrowCreate( + input.op_escrow_create().amount(), + input.op_escrow_create().destination(), + input.op_escrow_create().destination_tag(), + input.op_escrow_create().cancel_after(), + input.op_escrow_create().finish_after(), + input.op_escrow_create().condition()); + break; + + case Proto::SigningInput::kOpEscrowCancel: + transaction.createEscrowCancel( + input.op_escrow_cancel().owner(), + input.op_escrow_cancel().offer_sequence()); + break; + + case Proto::SigningInput::kOpEscrowFinish: + transaction.createEscrowFinish( + input.op_escrow_finish().owner(), + input.op_escrow_finish().offer_sequence(), + input.op_escrow_finish().condition(), + input.op_escrow_finish().fulfillment()); + break; + + case Proto::SigningInput::kOpNftokenBurn: + transaction.createNFTokenBurn(input.op_nftoken_burn().nftoken_id()); + break; + + case Proto::SigningInput::kOpNftokenCreateOffer: + transaction.createNFTokenCreateOffer( + input.op_nftoken_create_offer().nftoken_id(), + input.op_nftoken_create_offer().destination()); + break; + + case Proto::SigningInput::kOpNftokenAcceptOffer: + transaction.createNFTokenAcceptOffer(input.op_nftoken_accept_offer().sell_offer()); + break; + + case Proto::SigningInput::kOpNftokenCancelOffer: + signNfTokenCancelOffer(input, transaction); + break; + + case Proto::SigningInput::kOpTrustSet: + transaction.createTrustSet( + input.op_trust_set().limit_amount().currency(), + input.op_trust_set().limit_amount().value(), + input.op_trust_set().limit_amount().issuer()); + break; + + default: + break; + } + + if (output.error()) { + return output; + } + + auto pubKey = Data(input.public_key().begin(), input.public_key().end()); + transaction.pub_key = pubKey; + + auto unsignedTx = transaction.getPreImage(); + auto hash = Hash::sha512(unsignedTx); + auto half = Data(hash.begin(), hash.begin() + 32); + if (!publicKey.verifyAsDER(signature, half)) { + output.set_error(Common::Proto::SigningError::Error_signing); + output.set_error_message("Signature verification failed"); + return output; + } + + transaction.signature = signature; + + auto encoded = transaction.serialize(); + output.set_encoded(encoded.data(), encoded.size()); + return output; +} + +void Signer::signPayment(const Proto::SigningInput& input, + Proto::SigningOutput& output, + Transaction& transaction) { + const int64_t tag = input.op_payment().destination_tag(); + if (tag > std::numeric_limits::max() || tag < 0) { + output.set_error(Common::Proto::SigningError::Error_invalid_memo); + return; + } + + switch (input.op_payment().amount_oneof_case()) { + case Proto::OperationPayment::kAmount: + transaction.createXrpPayment( + input.op_payment().amount(), + input.op_payment().destination(), + tag); + break; + + case Proto::OperationPayment::kCurrencyAmount: + transaction.createTokenPayment( + input.op_payment().currency_amount().currency(), + input.op_payment().currency_amount().value(), + input.op_payment().currency_amount().issuer(), + input.op_payment().destination(), + tag); + break; + + default: + break; + } +} + +void Signer::signNfTokenCancelOffer(const Proto::SigningInput& input, Transaction& transaction) noexcept { + std::vector token_offers; + for (int i = 0; i < input.op_nftoken_cancel_offer().token_offers_size(); i++) { + token_offers.emplace_back(input.op_nftoken_cancel_offer().token_offers(i)); + } + + transaction.createNFTokenCancelOffer(token_offers); +} + +} // namespace TW::Ripple diff --git a/src/XRP/Signer.h b/src/XRP/Signer.h new file mode 100644 index 00000000000..4721280bf0e --- /dev/null +++ b/src/XRP/Signer.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Transaction.h" +#include "Data.h" +#include "../Hash.h" +#include "../PrivateKey.h" + +namespace TW::Ripple { + +/// Helper class that performs Ripple transaction signing. +class Signer { + public: + Proto::SigningInput input; + + Signer() = default; + + /// Initializes a transaction signer. + explicit Signer(const Proto::SigningInput& input) : input(input) {} + + /// Signs a Proto::SigningInput transaction + static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + + /// Signs the given transaction. + void sign(const PrivateKey& privateKey, Transaction& transaction) const noexcept; + + /// preImage returns the transaction pre-image without hashing. + TW::Data preImage() const; + + /// compile returns the final serialized signed transaction. + Proto::SigningOutput compile(const Data& signatures, const PublicKey& publicKeys) const; + + private: + static void signPayment(const Proto::SigningInput& input, + Proto::SigningOutput& output, + Transaction& transaction); + + static void signNfTokenCancelOffer(const Proto::SigningInput& input, Transaction& transaction) noexcept; +}; + +} // namespace TW::Ripple diff --git a/src/XRP/Transaction.cpp b/src/XRP/Transaction.cpp new file mode 100644 index 00000000000..1ebf5d02732 --- /dev/null +++ b/src/XRP/Transaction.cpp @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "BinaryCoding.h" +#include "Transaction.h" +#include "../BinaryCoding.h" + +#include +#include +#include +#include + +namespace TW::Ripple { + +const int NETWORK_PREFIX = 0x53545800; + +Data Transaction::serialize() const { + // See https://xrpl.org/serialization.html + + auto data = Data(); + + /// fields must be sorted by field type code then by field code (key) + // https://xrpl.org/serialization.html#canonical-field-order + + /// "type" + encodeType(FieldType::int16, 2, data); + encode16BE(uint16_t(transaction_type), data); + + /// "flags" + encodeType(FieldType::int32, 2, data); + encode32BE(static_cast(flags), data); + + /// "sequence" + encodeType(FieldType::int32, 4, data); + encode32BE(sequence, data); + + /// "destinationTag" + if (((transaction_type == TransactionType::payment) || + (transaction_type == TransactionType::EscrowCreate)) && encode_tag) { + encodeType(FieldType::int32, 14, data); + encode32BE(static_cast(destination_tag), data); + } + + /// "OfferSequence" + if ((transaction_type == TransactionType::EscrowCancel) || + (transaction_type == TransactionType::EscrowFinish)) { + encodeType(FieldType::int32, 25, data); + encode32BE(offer_sequence, data); + } + + /// "lastLedgerSequence" + if (last_ledger_sequence > 0) { + encodeType(FieldType::int32, 27, data); + encode32BE(last_ledger_sequence, data); + } + + /// "CancelAfter" + if ((transaction_type == TransactionType::EscrowCreate) && cancel_after > 0) { + encodeType(FieldType::int32, 36, data); + encode32BE(static_cast(cancel_after), data); + } + + /// "FinishAfter" + if ((transaction_type == TransactionType::EscrowCreate) && finish_after > 0) { + encodeType(FieldType::int32, 37, data); + encode32BE(static_cast(finish_after), data); + } + + /// "NFTokenId" + if ((transaction_type == TransactionType::NFTokenCreateOffer) || + (transaction_type == TransactionType::NFTokenBurn)) { + encodeType(FieldType::hash256, 10, data); + data.insert(data.end(), nftoken_id.begin(), nftoken_id.end()); + } + + /// "NFTokenAcceptOffer" + if (transaction_type == TransactionType::NFTokenAcceptOffer) { + encodeType(FieldType::hash256, 29, data); + data.insert(data.end(), sell_offer.begin(), sell_offer.end()); + } + + /// "amount" + if ((transaction_type == TransactionType::payment) || + (transaction_type == TransactionType::NFTokenCreateOffer)) { + encodeType(FieldType::amount, 1, data); + append(data, + (currency_amount.currency.size() > 0) ? + serializeCurrencyAmount(currency_amount) : + serializeAmount(amount)); + } else if (transaction_type == TransactionType::TrustSet) { + encodeType(FieldType::amount, 3, data); + append(data, serializeCurrencyAmount(limit_amount)); + } else if (transaction_type == TransactionType::EscrowCreate) { + encodeType(FieldType::amount, 1, data); + append(data, serializeAmount(amount)); + } + + /// "fee" + encodeType(FieldType::amount, 8, data); + append(data, serializeAmount(fee)); + + /// "signingPubKey" + if (!pub_key.empty()) { + encodeType(FieldType::vl, 3, data); + encodeBytes(pub_key, data); + } + + /// "txnSignature" + if (!signature.empty()) { + encodeType(FieldType::vl, 4, data); + encodeBytes(signature, data); + } + + /// "Fulfillment" + if ((transaction_type == TransactionType::EscrowFinish) && !fulfillment.empty()) { + encodeType(FieldType::vl, 16, data); + encodeBytes(fulfillment, data); + } + + /// "Condition" + if (((transaction_type == TransactionType::EscrowCreate) || + (transaction_type == TransactionType::EscrowFinish)) && !condition.empty()) { + encodeType(FieldType::vl, 17, data); + encodeBytes(condition, data); + } + + /// "account" + encodeType(FieldType::account, 1, data); + encodeBytes(serializeAddress(account), data); + + /// "destination" + if ((transaction_type == TransactionType::payment) || + (transaction_type == TransactionType::NFTokenCreateOffer) || + (transaction_type == TransactionType::EscrowCreate)) { + encodeType(FieldType::account, 3, data); + encodeBytes(destination, data); + } + + /// "Owner" + if ((transaction_type == TransactionType::EscrowCancel) || + (transaction_type == TransactionType::EscrowFinish)) { + encodeType(FieldType::account, 2, data); + encodeBytes(owner, data); + } + + /// "NFTokenOffers" + if (transaction_type == TransactionType::NFTokenCancelOffer) { + // only support one offer + encodeType(FieldType::vector256, 4, data); + encodeBytes(token_offers, data); + } + + return data; +} + +Data Transaction::getPreImage() const { + auto preImage = Data(); + encode32BE(NETWORK_PREFIX, preImage); + append(preImage, serialize()); + return preImage; +} + +Data Transaction::serializeAmount(int64_t amount) { + if (amount < 0) { + return Data(); + } + auto data = Data(); + encode64BE(uint64_t(amount), data); + /// clear first bit to indicate XRP + data[0] &= 0x7F; + /// set second bit to indicate positive number + data[0] |= 0x40; + return data; +} + +Data Transaction::serializeCurrencyAmount(const CurrencyAmount& currency_amount) { + // Calculate value + // https://xrpl.org/serialization.html#token-amount-format + int64_t sign = 0; + int64_t mantissa = 0; + int32_t exp = 0; + try { + int32_t num_after_dot = 0; + bool after_dot = false; + bool after_e = false; + bool has_exp = false; + std::ostringstream mantissa_oss, exp_oss; + for (auto i : currency_amount.value) { + if (i == '.') { + after_dot = true; + } else if (i == 'e') { + after_dot = false; + after_e = true; + } else if (after_e) { + has_exp = true; + exp_oss << i; + } else { + mantissa_oss << i; + if (after_dot) { + num_after_dot++; + } + } + } + + mantissa = std::stoll(mantissa_oss.str()); + sign = (mantissa >= 0) ? 1 : 0; + mantissa = (mantissa < 0) ? (mantissa * -1) : mantissa; + + exp = has_exp ? std::stoi(exp_oss.str()) : 0; + exp -= num_after_dot; + } catch (const std::exception& e) { + return Data(); + } + + int64_t min_mantissa = (uint64_t)std::pow(10, 15); + int64_t max_mantissa = (uint64_t)std::pow(10, 16) - 1; + int32_t min_exp = -96; + int32_t max_exp = 80; + + while ((mantissa < min_mantissa) && (exp > min_exp)) { + mantissa *= 10; + exp--; + } + + while (mantissa > max_mantissa) { + if (exp >= max_exp) { + return Data(); + } + + mantissa /= 10; + exp++; + } + + if (((exp < min_exp) || (mantissa < min_mantissa)) || + ((exp > max_exp) || (mantissa > max_mantissa))) { + return Data(); + } + + typedef union { + uint64_t value; + struct { + uint64_t mantissa : 54; + uint64_t exp : 8; + uint64_t sign : 1; + uint64_t not_xrp : 1; + } parts; + } AmountCast; + + AmountCast amount_cast; + amount_cast.parts.mantissa = mantissa; + amount_cast.parts.exp = exp + 97; + amount_cast.parts.sign = sign; + amount_cast.parts.not_xrp = 1; + + // Serialize fields + // https://xrpl.org/serialization.html#amount-fields + auto data = Data(); + encode64BE(amount_cast.value, data); + + // ISO-4217 currency code + encodeZeros(1, data); // type code (0x00) + encodeZeros(11, data); // reserved + if (currency_amount.currency.size() == 3) { + data.insert(data.end(), currency_amount.currency.begin(), currency_amount.currency.end()); + } else { + encodeZeros(3, data); // none + } + + encodeZeros(5, data); // reserved + data.insert(data.end(), currency_amount.issuer.begin(), currency_amount.issuer.end()); + return data; +} + +Data Transaction::serializeAddress(Address address) { + auto data = Data(20); + std::copy(&address.bytes[0] + 1, &address.bytes[0] + std::min(address.bytes.size(), size_t(21)), &data[0]); + return data; +} + +} // namespace TW::Ripple diff --git a/src/XRP/Transaction.h b/src/XRP/Transaction.h new file mode 100644 index 00000000000..ca453c47919 --- /dev/null +++ b/src/XRP/Transaction.h @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Address.h" +#include "XAddress.h" +#include "Data.h" +#include "../proto/Ripple.pb.h" +#include "../HexCoding.h" + +namespace TW::Ripple { + +// See https://github.com/XRPLF/rippled/blob/master/src/ripple/protocol/SField.h#L57-L74 +enum class FieldType: int { + int16 = 1, + int32 = 2, + hash256 = 5, + amount = 6, + vl = 7, + account = 8, + vector256 = 19 +}; + +// See https://github.com/XRPLF/xrpl.js/blob/main/packages/ripple-binary-codec/src/enums/definitions.json +enum class TransactionType { + no_type = -1, + payment = 0, + EscrowCreate = 1, + EscrowFinish = 2, + EscrowCancel = 4, + TrustSet = 20, + NFTokenBurn = 26, + NFTokenCreateOffer = 27, + NFTokenCancelOffer = 28, + NFTokenAcceptOffer = 29 +}; + +// See https://xrpl.org/nftokencreateoffer.html +enum class NFTokenCreateOfferFlags: int64_t { + tfSellNFToken = 0x00000001 +}; + +class Transaction { + /// Float and negative amounts are not supported. + /// See https://github.com/trezor/trezor-core/tree/master/src/apps/ripple#transactions + public: + struct CurrencyAmount { + Data currency; + Data value; + Data issuer; + }; + + int64_t amount; + CurrencyAmount currency_amount; + CurrencyAmount limit_amount; + int64_t fee; + int64_t flags; + int32_t sequence; + int32_t last_ledger_sequence; + Address account; + Data destination; + bool encode_tag; + int64_t destination_tag; + Data pub_key; + Data signature; + int64_t cancel_after; + int64_t finish_after; + Data owner; + int32_t offer_sequence; + Data condition; + Data fulfillment; + Data nftoken_id; + Data sell_offer; + Data token_offers; + TransactionType transaction_type; + + Transaction(int64_t fee, int64_t flags, int32_t sequence, int32_t last_ledger_sequence, Address p_account) + : amount(0) + , fee(fee) + , flags(flags) + , sequence(sequence) + , last_ledger_sequence(last_ledger_sequence) + , account(p_account) + , encode_tag(false) + , destination_tag(0) + , cancel_after(0) + , finish_after(0) + , offer_sequence(0) + , nftoken_id(0) + , sell_offer(0) + , token_offers(0) + , transaction_type(TransactionType::no_type) {} + + void createXrpPayment(int64_t p_amount, const std::string& p_destination, int64_t p_destination_tag) { + transaction_type = TransactionType::payment; + amount = p_amount; + setDestination(p_destination, p_destination_tag); + } + + void createTrustSet(const std::string& currency, const std::string& issuer) { + // Use maximum amount + // https://xrpl.org/currency-formats.html + std::string value("9999999999999999e80"); + createTrustSet(currency, value, issuer); + } + + void createTrustSet(const std::string& currency, const std::string& value, const std::string& issuer) { + transaction_type = TransactionType::TrustSet; + setCurrencyAmount(limit_amount, currency, value, issuer); + } + + void createTokenPayment(const std::string& currency, const std::string& value, const std::string& issuer, + const std::string& p_destination, int64_t p_destination_tag) { + transaction_type = TransactionType::payment; + setDestination(p_destination, p_destination_tag); + setCurrencyAmount(currency_amount, currency, value, issuer); + } + + void createEscrowCreate(int64_t amount, const std::string& destination, int64_t destination_tag, + int64_t cancel_after, int64_t finish_after, const std::string& condition) { + transaction_type = TransactionType::EscrowCreate; + if (cancel_after == 0 && finish_after == 0) { + throw std::invalid_argument("Either CancelAfter or FinishAfter must be specified"); + } else if (finish_after == 0 && condition.length() == 0) { + throw std::invalid_argument("Either Condition or FinishAfter must be specified"); + } + this->amount = amount; + setDestination(destination, destination_tag); + this->cancel_after = cancel_after; + this->finish_after = finish_after; + this->condition = parse_hex(condition); + } + + void createEscrowCancel(const std::string& owner, int32_t offer_sequence) { + transaction_type = TransactionType::EscrowCancel; + setAccount(owner, this->owner); + this->offer_sequence = offer_sequence; + } + + void createEscrowFinish(const std::string& owner, int32_t offer_sequence, + const std::string& condition, const std::string& fulfillment) { + transaction_type = TransactionType::EscrowFinish; + setAccount(owner, this->owner); + this->offer_sequence = offer_sequence; + this->condition = parse_hex(condition); + this->fulfillment = parse_hex(fulfillment); + } + + void createNFTokenBurn(const std::string& p_nftoken_id) { + transaction_type = TransactionType::NFTokenBurn; + nftoken_id = parse_hex(p_nftoken_id); + } + + void createNFTokenCreateOffer(const std::string& p_nftoken_id, const std::string& p_destination) { + transaction_type = TransactionType::NFTokenCreateOffer; + flags = int64_t(NFTokenCreateOfferFlags::tfSellNFToken); + nftoken_id = parse_hex(p_nftoken_id); + setAccount(p_destination, destination); + } + + void createNFTokenAcceptOffer(const std::string& p_sell_offer) { + transaction_type = TransactionType::NFTokenAcceptOffer; + sell_offer = parse_hex(p_sell_offer); + } + + void createNFTokenCancelOffer(const std::vector p_token_offers) { + transaction_type = TransactionType::NFTokenCancelOffer; + for (auto i : p_token_offers) { + Data token_offer = parse_hex(i); + token_offers.insert(token_offers.end(), token_offer.begin(), token_offer.end()); + } + } + + public: + /// simplified serialization format tailored for Payment transaction type + /// exclusively. + Data serialize() const; + Data getPreImage() const; + + static Data serializeAmount(int64_t amount); + static Data serializeCurrencyAmount(const CurrencyAmount& currency_amount); + static Data serializeAddress(Address address); + + private: + void setCurrencyAmount(CurrencyAmount& p_currency_amount, const std::string& currency, const std::string& value, const std::string& issuer) { + p_currency_amount.currency = Data(currency.begin(), currency.end()); + p_currency_amount.value = Data(value.begin(), value.end()); + setAccount(issuer, p_currency_amount.issuer); + } + + void setDestination(const std::string& p_destination, int64_t p_destination_tag) { + try { + auto address = Address(p_destination); + encode_tag = p_destination_tag > 0; + destination_tag = p_destination_tag; + destination = Data(address.bytes.begin() + 1, address.bytes.end()); + } catch(const std::exception& e) { + auto xAddress = XAddress(p_destination); + encode_tag = xAddress.flag != TagFlag::none; + destination_tag = xAddress.tag; + destination = Data(xAddress.bytes.begin(), xAddress.bytes.end()); + } + } + + void setAccount(const std::string& p_account, Data& data) { + try { + auto address = Address(p_account); + data = Data(address.bytes.begin() + 1, address.bytes.end()); + } catch(const std::exception& e) { + auto xAddress = XAddress(p_account); + data = Data(xAddress.bytes.begin(), xAddress.bytes.end()); + } + } +}; + +} // namespace TW::Ripple diff --git a/src/XRP/XAddress.cpp b/src/XRP/XAddress.cpp new file mode 100644 index 00000000000..95bc26507b3 --- /dev/null +++ b/src/XRP/XAddress.cpp @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "XAddress.h" + +#include "../Base58.h" +#include "../BinaryCoding.h" +#include + +namespace TW::Ripple { + +const Data prefixMainnet = {0x05, 0x44}; + +bool XAddress::isValid(const std::string& string) { + const auto decoded = Base58::decodeCheck(string, Rust::Base58Alphabet::Ripple); + if (decoded.size() != XAddress::size) { + return false; + } + if (!std::equal(decoded.begin(), decoded.begin() + 2, prefixMainnet.begin())) { + return false; + } + if (!(decoded[22] == byte(TagFlag::none) || decoded[22] == byte(TagFlag::classic))) { + return false; + } + return true; +} + +XAddress::XAddress(const std::string& string) { + if (!XAddress::isValid(string)) { + throw std::invalid_argument("Invalid address string"); + } + const auto decoded = Base58::decodeCheck(string, Rust::Base58Alphabet::Ripple); + std::copy(decoded.begin() + prefixMainnet.size(), decoded.begin() + prefixMainnet.size() + XAddress::keyHashSize, bytes.begin()); + if (decoded[22] == byte(TagFlag::classic)) { + tag = decode32LE(Data(decoded.end() - 8, decoded.end() - 4).data()); + } else if (decoded[22] == byte(TagFlag::none)) { + flag = TagFlag::none; + } else { + throw std::invalid_argument("Invalid flag"); + } +} + +XAddress::XAddress(const PublicKey& publicKey, const uint32_t destination) + : tag(destination) { + ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, bytes.data()); +} + +std::string XAddress::string() const { + /// \see https://github.com/ripple/ripple-address-codec/blob/master/src/index.ts + /// base58check(2 bytes prefix + 20 bytes keyhash + 1 byte flag + 4 bytes + 32bit tag + 4 bytes reserved) + Data result; + append(result, prefixMainnet); + append(result, Data{bytes.begin(), bytes.end()}); + append(result, byte(flag)); + encode32LE(tag, result); + append(result, Data{0x00, 0x00, 0x00, 0x00}); + return Base58::encodeCheck(result, Rust::Base58Alphabet::Ripple); +} + +} // namespace TW::Ripple diff --git a/src/Ripple/XAddress.h b/src/XRP/XAddress.h similarity index 79% rename from src/Ripple/XAddress.h rename to src/XRP/XAddress.h index dc60e67a57d..424baed0333 100644 --- a/src/Ripple/XAddress.h +++ b/src/XRP/XAddress.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include "../PublicKey.h" #include @@ -20,7 +18,7 @@ class XAddress { /// Number of bytes in a X-address. static const size_t size = 31; - /// Publick key hash length. + /// Public key hash length. static const size_t keyHashSize = 20; /// Address data consisting of public key hash diff --git a/src/Zcash/Entry.cpp b/src/Zcash/Entry.cpp index 6e1a5fb7ab1..50a45f9f10d 100644 --- a/src/Zcash/Entry.cpp +++ b/src/Zcash/Entry.cpp @@ -1,35 +1,79 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" -#include "TAddress.h" +#include "Bitcoin/Address.h" #include "Signer.h" +#include "TAddress.h" -using namespace TW::Zcash; -using namespace TW; -using namespace std; +namespace TW::Zcash { -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const { +bool Entry::validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const { + if (coin == TWCoinTypeKomodo) { + auto* base58Prefix = std::get_if(&addressPrefix); + return base58Prefix ? Bitcoin::Address::isValid(address, {{base58Prefix->p2pkh}, {base58Prefix->p2sh}}) : false; + } return TAddress::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const { +std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, const PrefixVariant& addressPrefix) const { + byte p2pkh = getFromPrefixPkhOrDefault(addressPrefix, coin); + if (coin == TWCoinTypeKomodo) { + return Bitcoin::Address(publicKey, p2pkh).string(); + } return TAddress(publicKey, p2pkh).string(); } Data Entry::addressToData(TWCoinType coin, const std::string& address) const { + if (coin == TWCoinTypeKomodo) { + const auto addr = Bitcoin::Address(address); + return {addr.bytes.begin() + 1, addr.bytes.end()}; + } + const auto addr = TAddress(address); return {addr.bytes.begin() + 2, addr.bytes.end()}; } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { signTemplate(dataIn, dataOut); } -void Entry::plan(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::plan([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { planTemplate(dataIn, dataOut); } + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + output = Signer::preImageHashes(input); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("signatures size and publickeys size not equal"); + return; + } + + HashPubkeyList externalSignatures; + auto n = signatures.size(); + for (auto i = 0ul; i < n; ++i) { + externalSignatures.push_back(std::make_pair(signatures[i], publicKeys[i].bytes)); + } + + output = Signer::sign(input, externalSignatures); + }); +} + +} // namespace TW::Zcash diff --git a/src/Zcash/Entry.h b/src/Zcash/Entry.h index 54a5f652aad..2b43578fbaf 100644 --- a/src/Zcash/Entry.h +++ b/src/Zcash/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,13 +10,16 @@ namespace TW::Zcash { /// Zcash entry dispatcher. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const; }; } // namespace TW::Zcash diff --git a/src/Zcash/Signer.cpp b/src/Zcash/Signer.cpp index 9387f58a8b7..19099969c65 100644 --- a/src/Zcash/Signer.cpp +++ b/src/Zcash/Signer.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "Bitcoin/TransactionSigner.h" @@ -11,19 +9,19 @@ #include "Transaction.h" #include "TransactionBuilder.h" -using namespace TW; -using namespace TW::Zcash; +namespace TW::Zcash { TransactionPlan Signer::plan(const SigningInput& input) noexcept { auto plan = Bitcoin::TransactionSigner::plan(input); return plan.proto(); } -SigningOutput Signer::sign(const SigningInput& input) noexcept { +SigningOutput Signer::sign(const SigningInput& input, std::optional optionalExternalSigs) noexcept { SigningOutput output; - auto result = Bitcoin::TransactionSigner::sign(input); + auto result = Bitcoin::TransactionSigner::sign(input, false, optionalExternalSigs); if (!result) { output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); } else { const auto& tx = result.payload(); *output.mutable_transaction() = tx.proto(); @@ -38,3 +36,24 @@ SigningOutput Signer::sign(const SigningInput& input) noexcept { } return output; } + +PreSigningOutput Signer::preImageHashes(const SigningInput& input) noexcept { + PreSigningOutput output; + auto result = Bitcoin::TransactionSigner::preImageHashes(input); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + return output; + } + + auto hashList = result.payload(); + auto* hashPubKeys = output.mutable_hash_public_keys(); + for (auto& h : hashList) { + auto* hpk = hashPubKeys->Add(); + hpk->set_data_hash(h.first.data(), h.first.size()); + hpk->set_public_key_hash(h.second.data(), h.second.size()); + } + return output; +} + +} // namespace TW::Zcash diff --git a/src/Zcash/Signer.h b/src/Zcash/Signer.h index f331652764a..cc83b5dd897 100644 --- a/src/Zcash/Signer.h +++ b/src/Zcash/Signer.h @@ -1,17 +1,20 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once + #include "../proto/Bitcoin.pb.h" +#include "Data.h" + +#include namespace TW::Zcash { using SigningInput = Bitcoin::Proto::SigningInput; using SigningOutput = Bitcoin::Proto::SigningOutput; using TransactionPlan = Bitcoin::Proto::TransactionPlan; +using PreSigningOutput = Bitcoin::Proto::PreSigningOutput; class Signer { public: @@ -20,8 +23,11 @@ class Signer { /// Returns a transaction plan (utxo selection, fee estimation) static TransactionPlan plan(const SigningInput& input) noexcept; - /// Signs a Proto::SigningInput transaction - static SigningOutput sign(const SigningInput& input) noexcept; + /// Signs a SigningInput transaction + static SigningOutput sign(const SigningInput& input, std::optional optionalExternalSigs = {}) noexcept; + + /// Collect pre-image hashes to be signed + static PreSigningOutput preImageHashes(const SigningInput& input) noexcept; }; } // namespace TW::Zcash diff --git a/src/Zcash/TAddress.cpp b/src/Zcash/TAddress.cpp deleted file mode 100644 index 9ebb2471ef5..00000000000 --- a/src/Zcash/TAddress.cpp +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "TAddress.h" - -using namespace TW::Zcash; diff --git a/src/Zcash/TAddress.h b/src/Zcash/TAddress.h index 91bb67cde6b..0cef48d16d7 100644 --- a/src/Zcash/TAddress.h +++ b/src/Zcash/TAddress.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Zcash/Transaction.cpp b/src/Zcash/Transaction.cpp index b023b1284f0..a5432ac2a16 100644 --- a/src/Zcash/Transaction.cpp +++ b/src/Zcash/Transaction.cpp @@ -1,33 +1,28 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Transaction.h" #include "../Bitcoin/SigHashType.h" #include "../BinaryCoding.h" -#include "../Hash.h" -#include "../HexCoding.h" #include -using namespace TW; -using namespace TW::Zcash; +namespace TW::Zcash { -const auto sigHashPersonalization = Data({'Z','c','a','s','h','S','i','g','H','a','s','h'}); -const auto prevoutsHashPersonalization = Data({'Z','c','a','s','h','P','r','e','v','o','u','t','H','a','s','h'}); -const auto sequenceHashPersonalization = Data({'Z','c','a','s','h','S','e','q','u','e','n','c','H','a','s','h'}); -const auto outputsHashPersonalization = Data({'Z','c','a','s','h','O','u','t','p','u','t','s','H','a','s','h'}); -const auto joinsplitsHashPersonalization = Data({'Z','c','a','s','h','J','S','p','l','i','t','s','H','a','s','h'}); -const auto shieldedSpendHashPersonalization = Data({'Z','c','a','s','h','S','S','p','e','n','d','s','H','a','s','h'}); -const auto shieldedOutputsHashPersonalization = Data({'Z','c','a','s','h','S','O','u','t','p','u','t','H','a','s','h'}); +const auto sigHashPersonalization = Data({'Z', 'c', 'a', 's', 'h', 'S', 'i', 'g', 'H', 'a', 's', 'h'}); +const auto prevoutsHashPersonalization = Data({'Z', 'c', 'a', 's', 'h', 'P', 'r', 'e', 'v', 'o', 'u', 't', 'H', 'a', 's', 'h'}); +const auto sequenceHashPersonalization = Data({'Z', 'c', 'a', 's', 'h', 'S', 'e', 'q', 'u', 'e', 'n', 'c', 'H', 'a', 's', 'h'}); +const auto outputsHashPersonalization = Data({'Z', 'c', 'a', 's', 'h', 'O', 'u', 't', 'p', 'u', 't', 's', 'H', 'a', 's', 'h'}); +const auto joinsplitsHashPersonalization = Data({'Z', 'c', 'a', 's', 'h', 'J', 'S', 'p', 'l', 'i', 't', 's', 'H', 'a', 's', 'h'}); +const auto shieldedSpendHashPersonalization = Data({'Z', 'c', 'a', 's', 'h', 'S', 'S', 'p', 'e', 'n', 'd', 's', 'H', 'a', 's', 'h'}); +const auto shieldedOutputsHashPersonalization = Data({'Z', 'c', 'a', 's', 'h', 'S', 'O', 'u', 't', 'p', 'u', 't', 'H', 'a', 's', 'h'}); /// See https://github.com/zcash/zips/blob/master/zip-0205.rst#sapling-deployment BRANCH_ID section -const std::array Zcash::SaplingBranchID = {0xbb, 0x09, 0xb8, 0x76}; +const std::array SaplingBranchID = {0xbb, 0x09, 0xb8, 0x76}; /// See https://github.com/zcash/zips/blob/master/zip-0206.rst#blossom-deployment BRANCH_ID section -const std::array Zcash::BlossomBranchID = {0x60, 0x0e, 0xb4, 0x2b}; +const std::array BlossomBranchID = {0x60, 0x0e, 0xb4, 0x2b}; Data Transaction::getPreImage(const Bitcoin::Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount) const { @@ -36,7 +31,7 @@ Data Transaction::getPreImage(const Bitcoin::Script& scriptCode, size_t index, e auto data = Data{}; // header - encode32LE(version, data); + encode32LE(_version, data); // nVersionGroupId encode32LE(versionGroupId, data); @@ -99,7 +94,7 @@ Data Transaction::getPreImage(const Bitcoin::Script& scriptCode, size_t index, e // The input being signed (replacing the scriptSig with scriptCode + amount) // The prevout may already be contained in hashPrevout, and the nSequence // may already be contain in hashSequence. - reinterpret_cast(inputs[index].previousOutput).encode(data); + inputs[index].previousOutput.encode(data); scriptCode.encode(data); encode64LE(amount, data); @@ -152,7 +147,7 @@ Data Transaction::getShieldedOutputsHash() const { } void Transaction::encode(Data& data) const { - encode32LE(version, data); + encode32LE(_version, data); encode32LE(versionGroupId, data); // vin @@ -181,7 +176,7 @@ void Transaction::encode(Data& data) const { Data Transaction::getSignatureHash(const Bitcoin::Script& scriptCode, size_t index, enum TWBitcoinSigHashType hashType, uint64_t amount, - Bitcoin::SignatureVersion version) const { + [[maybe_unused]] Bitcoin::SignatureVersion version) const { Data personalization; personalization.reserve(16); std::copy(sigHashPersonalization.begin(), sigHashPersonalization.begin() + 12, @@ -194,7 +189,7 @@ Data Transaction::getSignatureHash(const Bitcoin::Script& scriptCode, size_t ind Bitcoin::Proto::Transaction Transaction::proto() const { auto protoTx = Bitcoin::Proto::Transaction(); - protoTx.set_version(version); + protoTx.set_version(_version); protoTx.set_locktime(lockTime); for (const auto& input : inputs) { @@ -214,3 +209,5 @@ Bitcoin::Proto::Transaction Transaction::proto() const { return protoTx; } + +} // namespace TW::Zcash diff --git a/src/Zcash/Transaction.h b/src/Zcash/Transaction.h index e42b77ef106..5c0dd6bd154 100644 --- a/src/Zcash/Transaction.h +++ b/src/Zcash/Transaction.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -23,7 +21,7 @@ extern const std::array BlossomBranchID; /// Only supports transparent transaction right now /// See also https://github.com/zcash/zips/blob/master/zip-0243.rst struct Transaction { - uint32_t version = 0x80000004; + uint32_t _version = 0x80000004; uint32_t versionGroupId = 0x892F2085; uint32_t lockTime = 0; uint32_t expiryHeight = 0; @@ -40,7 +38,7 @@ struct Transaction { Transaction(uint32_t version, uint32_t versionGroupId, uint32_t lockTime, uint32_t expiryHeight, uint64_t valueBalance, std::array branchId) - : version(version) + : _version(version) , versionGroupId(versionGroupId) , lockTime(lockTime) , expiryHeight(expiryHeight) diff --git a/src/Zcash/TransactionBuilder.h b/src/Zcash/TransactionBuilder.h index 1c5cd9fde1e..d3940801658 100644 --- a/src/Zcash/TransactionBuilder.h +++ b/src/Zcash/TransactionBuilder.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -11,6 +9,7 @@ #include "../Bitcoin/TransactionPlan.h" #include "../proto/Bitcoin.pb.h" #include "../HexCoding.h" +#include "../Result.h" #include #include @@ -25,18 +24,18 @@ struct TransactionBuilder { /// Builds a transaction by selecting UTXOs and calculating fees. template - static Transaction build(const Bitcoin::TransactionPlan& plan, const std::string& toAddress, - const std::string& changeAddress, enum TWCoinType coin, uint32_t lockTime) { - coin = TWCoinTypeZcash; - Transaction tx = - Bitcoin::TransactionBuilder::build(plan, toAddress, changeAddress, coin, lockTime); + static Result build(const Bitcoin::TransactionPlan& plan, const Bitcoin::SigningInput& input) { + auto tx_result = + Bitcoin::TransactionBuilder::build(plan, input); + if (!tx_result) { return Result::failure(tx_result.error()); } + Transaction tx = tx_result.payload(); // if not set, always use latest consensus branch id if (plan.branchId.empty()) { std::copy(BlossomBranchID.begin(), BlossomBranchID.end(), tx.branchId.begin()); } else { std::copy(plan.branchId.begin(), plan.branchId.end(), tx.branchId.begin()); } - return tx; + return Result(tx); } }; diff --git a/src/Zen/Address.h b/src/Zen/Address.h new file mode 100644 index 00000000000..b387c8c5c15 --- /dev/null +++ b/src/Zen/Address.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../Base58Address.h" +#include "../PublicKey.h" + +#include +#include +#include + +namespace TW::Zen { + +class Address : public TW::Base58Address<22> { +public: + static const TW::byte staticPrefix = 0x20; + static const TW::byte p2pkh = 0x89; // p2pkhPrefix(TWCoinType::TWCoinTypeZcash); + static const TW::byte p2sh = 0x96; // p2shPrefix(TWCoinType::TWCoinTypeZcash); + + /// Determines whether a string makes a valid ZCash address. + static bool isValid(const std::string& string) { + return TW::Base58Address::isValid(string, + {{staticPrefix, p2pkh}, {staticPrefix, p2sh}}); + } + + /// Determines whether a string makes a valid ZCash address, with possible prefixes. + static bool isValid(const std::string& string, const std::vector& validPrefixes) { + return TW::Base58Address::isValid(string, validPrefixes); + } + + /// Initializes an address with a string representation. + explicit Address(const std::string& string) : TW::Base58Address(string) {} + + /// Initializes an address with a collection of bytes. + explicit Address(const Data& data) : TW::Base58Address(data) {} + + /// Initializes a address with a public key and a prefix (2nd byte). + Address(const PublicKey& publicKey, uint8_t prefix = p2pkh) + : TW::Base58Address(publicKey, {staticPrefix, prefix}) {} + +private: + Address() = default; +}; + +} // namespace TW::Zen diff --git a/src/Zen/Entry.cpp b/src/Zen/Entry.cpp new file mode 100644 index 00000000000..09087ca4b20 --- /dev/null +++ b/src/Zen/Entry.cpp @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Entry.h" + +#include "Address.h" +#include "Signer.h" + +using namespace std; + +namespace TW::Zen { + +// Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const string& address, [[maybe_unused]] const PrefixVariant& addressPrefixp) const { + return Address::isValid(address); +} + +string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { + return Address(publicKey).string(); +} + +TW::Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { + const auto addr = Address(address); + return {addr.bytes.begin() + 2, addr.bytes.end()}; +} + +void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + signTemplate(dataIn, dataOut); +} + +void Entry::plan([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { + planTemplate(dataIn, dataOut); +} + +TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + output = Signer::preImageHashes(input); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerTemplate( + txInputData, [&](const auto& input, auto& output) { + if (signatures.size() == 0 || publicKeys.size() == 0) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("empty signatures or publickeys"); + return; + } + + if (signatures.size() != publicKeys.size()) { + output.set_error(Common::Proto::Error_invalid_params); + output.set_error_message("signatures size and publickeys size not equal"); + return; + } + + HashPubkeyList externalSignatures; + auto n = signatures.size(); + for (auto i = 0ul; i < n; ++i) { + externalSignatures.push_back(std::make_pair(signatures[i], publicKeys[i].bytes)); + } + + output = Signer::sign(input, externalSignatures); + }); +} + +} // namespace TW::Zen diff --git a/src/Zen/Entry.h b/src/Zen/Entry.h new file mode 100644 index 00000000000..5e89746c89d --- /dev/null +++ b/src/Zen/Entry.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../CoinEntry.h" + +namespace TW::Zen { + +/// Entry point for implementation of Zen coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry final : public CoinEntry { +public: + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefixp) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + Data addressToData(TWCoinType coin, const std::string& address) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + void plan(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; +}; + +} // namespace TW::Zen diff --git a/src/Zen/Signer.cpp b/src/Zen/Signer.cpp new file mode 100644 index 00000000000..405ec592248 --- /dev/null +++ b/src/Zen/Signer.cpp @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Signer.h" +#include "Bitcoin/TransactionSigner.h" +#include "Hash.h" +#include "Bitcoin/Transaction.h" +#include "TransactionBuilder.h" + +namespace TW::Zen { + +TransactionPlan Signer::plan(const SigningInput& input) noexcept { + auto plan = Bitcoin::TransactionSigner::plan(input); + return plan.proto(); +} + +SigningOutput Signer::sign(const SigningInput& input, std::optional optionalExternalSigs) noexcept { + SigningOutput output; + auto result = Bitcoin::TransactionSigner::sign(input, false, optionalExternalSigs); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + } else { + const auto& tx = result.payload(); + *output.mutable_transaction() = tx.proto(); + + Data encoded; + tx.encode(encoded); + output.set_encoded(encoded.data(), encoded.size()); + + auto txHash = Hash::sha256d(encoded.data(), encoded.size()); + std::reverse(txHash.begin(), txHash.end()); + output.set_transaction_id(hex(txHash)); + } + return output; +} + +PreSigningOutput Signer::preImageHashes(const SigningInput& input) noexcept { + PreSigningOutput output; + auto result = Bitcoin::TransactionSigner::preImageHashes(input); + if (!result) { + output.set_error(result.error()); + output.set_error_message(Common::Proto::SigningError_Name(result.error())); + return output; + } + + auto hashList = result.payload(); + auto* hashPubKeys = output.mutable_hash_public_keys(); + for (const auto& h : hashList) { + auto* hpk = hashPubKeys->Add(); + hpk->set_data_hash(h.first.data(), h.first.size()); + hpk->set_public_key_hash(h.second.data(), h.second.size()); + } + return output; +} + +} // namespace TW::Zen diff --git a/src/Zen/Signer.h b/src/Zen/Signer.h new file mode 100644 index 00000000000..2e4476e4e8e --- /dev/null +++ b/src/Zen/Signer.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "../Data.h" +#include "../proto/Bitcoin.pb.h" + +#include + +namespace TW::Zen { + +using SigningInput = Bitcoin::Proto::SigningInput; +using SigningOutput = Bitcoin::Proto::SigningOutput; +using TransactionPlan = Bitcoin::Proto::TransactionPlan; +using PreSigningOutput = Bitcoin::Proto::PreSigningOutput; + +/// Helper class that performs Zen transaction signing. +class Signer { +public: + /// Hide default constructor + Signer() = delete; + + /// Returns a transaction plan (utxo selection, fee estimation) + static TransactionPlan plan(const SigningInput& input) noexcept; + + /// Signs a SigningInput transaction + static SigningOutput sign(const SigningInput& input, std::optional optionalExternalSigs = {}) noexcept; + + /// Collect pre-image hashes to be signed + static PreSigningOutput preImageHashes(const SigningInput& input) noexcept; +}; + +} // namespace TW::Zen diff --git a/src/Zen/TransactionBuilder.h b/src/Zen/TransactionBuilder.h new file mode 100644 index 00000000000..7abd9d51cec --- /dev/null +++ b/src/Zen/TransactionBuilder.h @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Address.h" +#include "../Bitcoin/Transaction.h" +#include "../Bitcoin/TransactionBuilder.h" +#include "../Bitcoin/TransactionPlan.h" +#include "../Bitcoin/TransactionOutput.h" +#include "../Coin.h" +#include "../proto/Bitcoin.pb.h" +#include "../HexCoding.h" +#include + +#include + +using namespace TW; + +namespace TW::Zen { + +struct TransactionBuilder { + /// Plans a transaction by selecting UTXOs and calculating fees. + static Bitcoin::TransactionPlan plan(const Bitcoin::SigningInput& input) { + return Bitcoin::TransactionBuilder::plan(input); + } + + /// Builds a transaction with the selected input UTXOs, and one main output and an optional change output. + template + static Result build(const Bitcoin::TransactionPlan& plan, const Bitcoin::SigningInput& input) { + Transaction tx; + tx.lockTime = input.lockTime; + + auto blockHash = plan.preBlockHash; + auto blockHeight = plan.preBlockHeight; + + auto outputTo = prepareOutputWithScript(input.toAddress, plan.amount, input.coinType, blockHash, blockHeight); + if (!outputTo.has_value()) { + return Result::failure(Common::Proto::Error_invalid_address); + } + tx.outputs.push_back(outputTo.value()); + + if (plan.change > 0) { + auto outputChange = prepareOutputWithScript(input.changeAddress, plan.change, input.coinType, blockHash, blockHeight); + if (!outputChange.has_value()) { + return Result::failure(Common::Proto::Error_invalid_address); + } + tx.outputs.push_back(outputChange.value()); + } + + const auto emptyScript = Bitcoin::Script(); + for (auto& utxo : plan.utxos) { + tx.inputs.emplace_back(utxo.outPoint, emptyScript, utxo.outPoint.sequence); + } + + // Optional OP_RETURN output + if (!plan.outputOpReturn.empty()) { + auto lockingScriptOpReturn = Bitcoin::Script::buildOpReturnScript(plan.outputOpReturn); + if (lockingScriptOpReturn.bytes.empty()) { + return Result::failure(Common::Proto::Error_invalid_memo); + } + + auto emplace_at = tx.outputs.end(); + if (plan.outputOpReturnIndex.has_value()) { + emplace_at = tx.outputs.begin(); + std::advance(emplace_at, plan.outputOpReturnIndex.value()); + } + const int64_t amount = 0; + tx.outputs.emplace(emplace_at, amount, lockingScriptOpReturn); + } + + // extra outputs (always in the end of the outputs list) + for (auto& o : input.extraOutputs) { + auto output = prepareOutputWithScript(o.first, o.second, input.coinType, blockHash, blockHeight); + if (!output.has_value()) { + return Result::failure(Common::Proto::Error_invalid_address); + } + tx.outputs.push_back(output.value()); + } + + return Result(tx); + } + + /// Prepares a TransactionOutput with given address and amount, prepares script for it + static std::optional prepareOutputWithScript(const std::string& addr, Bitcoin::Amount amount, enum TWCoinType coin, const Data& blockHash, int64_t blockHeight) { + auto lockingScript = Bitcoin::Script::lockScriptForAddress(addr, coin, blockHash, blockHeight); + if (lockingScript.empty()) { + return {}; + } + return Bitcoin::TransactionOutput(amount, lockingScript); + } + +}; + +} // namespace TW::Zen diff --git a/src/Zilliqa/Address.cpp b/src/Zilliqa/Address.cpp index bf010554bd0..8c0c2ab6d0d 100644 --- a/src/Zilliqa/Address.cpp +++ b/src/Zilliqa/Address.cpp @@ -1,13 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" #include -using namespace TW::Zilliqa; +namespace TW::Zilliqa { const std::string Address::hrp = HRP_ZILLIQA; + +} // namespace TW::Zilliqa diff --git a/src/Zilliqa/Address.h b/src/Zilliqa/Address.h index 8214e6aaebb..a46d6a934f3 100644 --- a/src/Zilliqa/Address.h +++ b/src/Zilliqa/Address.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/Zilliqa/AddressChecksum.cpp b/src/Zilliqa/AddressChecksum.cpp index 5332bee2363..60f65f8c61f 100644 --- a/src/Zilliqa/AddressChecksum.cpp +++ b/src/Zilliqa/AddressChecksum.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "AddressChecksum.h" @@ -11,11 +9,10 @@ #include "../uint256.h" #include -using namespace TW; -using namespace TW::Zilliqa; +namespace TW::Zilliqa { -/// see https://github.com/Zilliqa/Zilliqa/blob/1c53b792c7ae44f7b77366536a7e2f73a3eade6a/src/libServer/AddressChecksum.h -std::string Zilliqa::checksum(const Data& bytes) { +/// \see https://github.com/Zilliqa/Zilliqa/blob/1c53b792c7ae44f7b77366536a7e2f73a3eade6a/src/libServer/AddressChecksum.h +std::string checksum(const Data& bytes) { const auto addressString = hex(bytes); const auto hash = hex(Hash::sha256(bytes)); @@ -23,7 +20,7 @@ std::string Zilliqa::checksum(const Data& bytes) { uint256_t v("0x" + hash); std::string string = ""; - for (auto i = 0; i < addressString.size(); i += 1) { + for (auto i = 0ul; i < addressString.size(); i += 1) { const auto a = addressString[i]; if (a >= '0' && a <= '9') { string.push_back(a); @@ -38,3 +35,5 @@ std::string Zilliqa::checksum(const Data& bytes) { return string; } + +} // namespace TW::Zilliqa diff --git a/src/Zilliqa/AddressChecksum.h b/src/Zilliqa/AddressChecksum.h index c2f32e63152..511ab562e16 100644 --- a/src/Zilliqa/AddressChecksum.h +++ b/src/Zilliqa/AddressChecksum.h @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once -#include "../Data.h" +#include "Data.h" #include namespace TW::Zilliqa { diff --git a/src/Zilliqa/Entry.cpp b/src/Zilliqa/Entry.cpp index 9333f1d7f3a..246456d6bb5 100644 --- a/src/Zilliqa/Entry.cpp +++ b/src/Zilliqa/Entry.cpp @@ -1,41 +1,39 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Entry.h" #include "Address.h" #include "Signer.h" -using namespace TW::Zilliqa; -using namespace TW; -using namespace std; +namespace TW::Zilliqa { // Note: avoid business logic from here, rather just call into classes like Address, Signer, etc. -bool Entry::validateAddress(TWCoinType coin, const string& address, TW::byte, TW::byte, const char*) const { +bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address::isValid(address); } -string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte, const char*) const { +std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { return Address(publicKey).string(); } -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { +Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { Address addr; if (!Address::decode(address, addr)) { - return Data(); + return {}; } // data in Zilliqa is a checksummed string without 0x - return TW::data(checksum(addr.getKeyHash())); + return data(checksum(addr.getKeyHash())); } -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { +void Entry::sign([[maybe_unused]] TWCoinType coin, const Data& dataIn, Data& dataOut) const { signTemplate(dataIn, dataOut); } -string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { +std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { return Signer::signJSON(json, key); } + +} // namespace TW::Zilliqa diff --git a/src/Zilliqa/Entry.h b/src/Zilliqa/Entry.h index 69747dfc8f8..fed967de694 100644 --- a/src/Zilliqa/Entry.h +++ b/src/Zilliqa/Entry.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -12,14 +10,14 @@ namespace TW::Zilliqa { /// Entry point for implementation of Zilliqa coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry: public CoinEntry { +class Entry final : public CoinEntry { public: - virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const; - virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const; - virtual Data addressToData(TWCoinType coin, const std::string& address) const; - virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - virtual bool supportsJSONSigning() const { return true; } - virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; + Data addressToData(TWCoinType coin, const std::string& address) const; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool supportsJSONSigning() const { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; }; } // namespace TW::Zilliqa diff --git a/src/Zilliqa/Signer.cpp b/src/Zilliqa/Signer.cpp index 2de41566d73..cf2003ec4f7 100644 --- a/src/Zilliqa/Signer.cpp +++ b/src/Zilliqa/Signer.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Signer.h" #include "Address.h" @@ -18,8 +16,8 @@ #include #include -using namespace TW; -using namespace TW::Zilliqa; +namespace TW::Zilliqa { + using ByteArray = ZilliqaMessage::ByteArray; static inline Data prependZero(Data& data) { @@ -95,7 +93,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { const auto preImage = Signer::getPreImage(input, address); const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); const auto pubKey = key.getPublicKey(TWPublicKeyTypeSECP256k1); - const auto signature = key.signSchnorr(preImage, TWCurveSECP256k1); + const auto signature = key.signZilliqa(preImage); const auto transaction = input.transaction(); // build json @@ -137,3 +135,5 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { input.set_private_key(key.data(), key.size()); return hex(Signer::sign(input).json()); } + +} // namespace TW::Zilliqa diff --git a/src/Zilliqa/Signer.h b/src/Zilliqa/Signer.h index d4089e4e485..46eb12168f9 100644 --- a/src/Zilliqa/Signer.h +++ b/src/Zilliqa/Signer.h @@ -1,13 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once #include "Address.h" -#include "../Data.h" +#include "Data.h" #include "../PrivateKey.h" #include "../proto/Zilliqa.pb.h" diff --git a/src/algorithm/erase.h b/src/algorithm/erase.h index a01fe425ce2..ed3a0bf9fd4 100644 --- a/src/algorithm/erase.h +++ b/src/algorithm/erase.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/algorithm/sort_copy.h b/src/algorithm/sort_copy.h index 0b4dfeefe05..eb1e7b3bf31 100644 --- a/src/algorithm/sort_copy.h +++ b/src/algorithm/sort_copy.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/src/algorithm/string.hpp b/src/algorithm/string.hpp new file mode 100644 index 00000000000..806b14b9899 --- /dev/null +++ b/src/algorithm/string.hpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include +#include + +namespace TW { + +constexpr std::string_view kWitespaces = " \t\n\r\f\v"; + +// trim from end of string (right) +inline void trim_right(std::string& s, std::string_view t = kWitespaces) +{ + s.erase(s.find_last_not_of(t) + 1); +} + +// trim from beginning of string (left) +inline void trim_left(std::string& s, std::string_view t = kWitespaces) +{ + s.erase(0, s.find_first_not_of(t)); +} + +// trim from both ends of string (right then left) +inline void trim(std::string& s, std::string_view t = kWitespaces) +{ + trim_left(s, t); + trim_right(s, t); +} + +// trim from both ends of string (right then left) +inline std::string trim_copy(std::string s, std::string_view t = kWitespaces) { + trim(s, t); + return s; +} + +inline std::vector ssplit(const std::string& input, char delimiter) { + std::istringstream iss(input); + std::vector tokens; + std::string token; + while (std::getline(iss, token, delimiter)) { + if (!token.empty()) { + tokens.emplace_back(token); + } + } + return tokens; +} + +} // namespace TW diff --git a/src/algorithm/to_array.h b/src/algorithm/to_array.h new file mode 100644 index 00000000000..b6588da79af --- /dev/null +++ b/src/algorithm/to_array.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include + +namespace TW { + +template +constexpr std::array to_array(Collection&& collection) { + std::array out{}; + std::copy(begin(collection), end(collection), out.begin()); + return out; +} + +} // namespace TW diff --git a/src/concepts/tw_concepts.h b/src/concepts/tw_concepts.h new file mode 100644 index 00000000000..fd31b0e31da --- /dev/null +++ b/src/concepts/tw_concepts.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include + +namespace TW { + +template +concept integral = std::is_integral_v; + +template +concept floating_point = std::is_floating_point_v; + +} // namespace TW diff --git a/src/interface/TWAES.cpp b/src/interface/TWAES.cpp index 04f86c8faed..7b63e735015 100644 --- a/src/interface/TWAES.cpp +++ b/src/interface/TWAES.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include diff --git a/src/interface/TWAccount.cpp b/src/interface/TWAccount.cpp index 73a624738c7..b29be55591d 100644 --- a/src/interface/TWAccount.cpp +++ b/src/interface/TWAccount.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include @@ -10,7 +8,8 @@ using namespace TW; -struct TWAccount* _Nonnull TWAccountCreate(TWString* _Nonnull address, enum TWCoinType coin, +struct TWAccount* _Nonnull TWAccountCreate(TWString* _Nonnull address, + enum TWCoinType coin, enum TWDerivation derivation, TWString* _Nonnull derivationPath, TWString* _Nonnull publicKey, @@ -33,6 +32,10 @@ TWString* _Nonnull TWAccountAddress(struct TWAccount* _Nonnull account) { return TWStringCreateWithUTF8Bytes(account->impl.address.c_str()); } +enum TWCoinType TWAccountCoin(struct TWAccount* _Nonnull account) { + return account->impl.coin; +} + enum TWDerivation TWAccountDerivation(struct TWAccount* _Nonnull account) { return account->impl.derivation; } @@ -48,7 +51,3 @@ TWString* _Nonnull TWAccountPublicKey(struct TWAccount* _Nonnull account) { TWString* _Nonnull TWAccountExtendedPublicKey(struct TWAccount* _Nonnull account) { return TWStringCreateWithUTF8Bytes(account->impl.extendedPublicKey.c_str()); } - -enum TWCoinType TWAccountCoin(struct TWAccount* _Nonnull account) { - return account->impl.coin; -} diff --git a/src/interface/TWAnyAddress.cpp b/src/interface/TWAnyAddress.cpp index abb7a0e5fdd..b91bb724f1d 100644 --- a/src/interface/TWAnyAddress.cpp +++ b/src/interface/TWAnyAddress.cpp @@ -1,25 +1,18 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include #include - -#include "../Coin.h" - -using namespace TW; - -struct TWAnyAddress { - TWString* address; - enum TWCoinType coin; -}; +#include "Data.h" +#include "Coin.h" +#include "CoinEntry.h" +#include "AnyAddress.h" bool TWAnyAddressEqual(struct TWAnyAddress* _Nonnull lhs, struct TWAnyAddress* _Nonnull rhs) { - return TWStringEqual(lhs->address, rhs->address) && lhs->coin == rhs->coin; + return *lhs->impl == *rhs->impl; } bool TWAnyAddressIsValid(TWString* _Nonnull string, enum TWCoinType coin) { @@ -27,38 +20,98 @@ bool TWAnyAddressIsValid(TWString* _Nonnull string, enum TWCoinType coin) { return TW::validateAddress(coin, address); } +bool TWAnyAddressIsValidSS58([[maybe_unused]] TWString* string, [[maybe_unused]] enum TWCoinType coin, [[maybe_unused]] uint32_t ss58Prefix) { + const auto& address = *reinterpret_cast(string); + return TW::validateAddress(coin, address, ss58Prefix); +} + +bool TWAnyAddressIsValidBech32(TWString* _Nonnull string, enum TWCoinType coin, TWString* _Nonnull hrp) { + const auto& address = *reinterpret_cast(string); + const auto& hrpStr = *reinterpret_cast(hrp); + return TW::validateAddress(coin, address, hrpStr.c_str()); +} + struct TWAnyAddress* _Nullable TWAnyAddressCreateWithString(TWString* _Nonnull string, enum TWCoinType coin) { const auto& address = *reinterpret_cast(string); - auto normalized = TW::normalizeAddress(coin, address); - if (normalized.empty()) { return nullptr; } - return new TWAnyAddress{TWStringCreateWithUTF8Bytes(normalized.c_str()), coin}; + auto *impl = TW::AnyAddress::createAddress(address, coin); + if (impl == nullptr) { + return nullptr; + } + return new TWAnyAddress{impl}; +} + +struct TWAnyAddress* _Nullable TWAnyAddressCreateBech32(TWString* _Nonnull string, + enum TWCoinType coin, TWString* _Nonnull hrp) { + const auto& address = *reinterpret_cast(string); + const auto& hrpStr = *reinterpret_cast(hrp); + auto *impl = TW::AnyAddress::createAddress(address, coin, hrpStr.c_str()); + if (impl == nullptr) { + return nullptr; + } + return new TWAnyAddress{impl}; +} + + +struct TWAnyAddress* TWAnyAddressCreateSS58(TWString* _Nonnull string, enum TWCoinType coin, uint32_t ss58Prefix) { + const auto& address = *reinterpret_cast(string); + auto *impl = TW::AnyAddress::createAddress(address, coin, ss58Prefix); + if (impl == nullptr) { + return nullptr; + } + return new TWAnyAddress{impl}; } struct TWAnyAddress* _Nonnull TWAnyAddressCreateWithPublicKey( struct TWPublicKey* _Nonnull publicKey, enum TWCoinType coin) { - auto address = TW::deriveAddress(coin, publicKey->impl); - return new TWAnyAddress{TWStringCreateWithUTF8Bytes(address.c_str()), coin}; + return new TWAnyAddress{TW::AnyAddress::createAddress(publicKey->impl, coin)}; +} + +struct TWAnyAddress* _Nonnull TWAnyAddressCreateWithPublicKeyDerivation( + struct TWPublicKey* _Nonnull publicKey, enum TWCoinType coin, enum TWDerivation derivation) { + return new TWAnyAddress{TW::AnyAddress::createAddress(publicKey->impl, coin, derivation)}; +} + +struct TWAnyAddress* _Nonnull TWAnyAddressCreateBech32WithPublicKey( + struct TWPublicKey* _Nonnull publicKey, enum TWCoinType coin, TWString* _Nonnull hrp) { + const auto& hrpStr = *reinterpret_cast(hrp); + return new TWAnyAddress{TW::AnyAddress::createAddress(publicKey->impl, coin, TWDerivationDefault, TW::Bech32Prefix(hrpStr.c_str()))}; +} + +struct TWAnyAddress* TWAnyAddressCreateSS58WithPublicKey(struct TWPublicKey* publicKey, enum TWCoinType coin, uint32_t ss58Prefix) { + return new TWAnyAddress{TW::AnyAddress::createAddress(publicKey->impl, coin, TWDerivationDefault, TW::SS58Prefix(ss58Prefix))}; +} + +struct TWAnyAddress* TWAnyAddressCreateWithPublicKeyFilecoinAddressType(struct TWPublicKey* _Nonnull publicKey, enum TWFilecoinAddressType filecoinAddressType) { + TW::PrefixVariant prefix = std::monostate(); + if (filecoinAddressType == TWFilecoinAddressTypeDelegated) { + prefix = TW::DelegatedPrefix(); + } + return new TWAnyAddress{TW::AnyAddress::createAddress(publicKey->impl, TWCoinTypeFilecoin, TWDerivationDefault, prefix)}; +} + +struct TWAnyAddress* TWAnyAddressCreateWithPublicKeyFiroAddressType(struct TWPublicKey* _Nonnull publicKey, enum TWFiroAddressType firoAddressType) { + TW::PrefixVariant prefix = std::monostate(); + if (firoAddressType == TWFiroAddressTypeExchange) { + prefix = TW::ExchangePrefix(); + } + return new TWAnyAddress{TW::AnyAddress::createAddress(publicKey->impl, TWCoinTypeFiro, TWDerivationDefault, prefix)}; } void TWAnyAddressDelete(struct TWAnyAddress* _Nonnull address) { - TWStringDelete(address->address); + delete address->impl; delete address; } TWString* _Nonnull TWAnyAddressDescription(struct TWAnyAddress* _Nonnull address) { - return TWStringCreateWithUTF8Bytes(TWStringUTF8Bytes(address->address)); + return TWStringCreateWithUTF8Bytes(address->impl->address.c_str()); } enum TWCoinType TWAnyAddressCoin(struct TWAnyAddress* _Nonnull address) { - return address->coin; + return address->impl->coin; } TWData* _Nonnull TWAnyAddressData(struct TWAnyAddress* _Nonnull address) { - const auto& string = *reinterpret_cast(address->address); - Data data; - try { - data = TW::addressToData(address->coin, string); - } catch (...) {} + auto data = address->impl->getData(); return TWDataCreateWithBytes(data.data(), data.size()); } diff --git a/src/interface/TWAnySigner.cpp b/src/interface/TWAnySigner.cpp index 64f3dbcc382..97cbfc631e3 100644 --- a/src/interface/TWAnySigner.cpp +++ b/src/interface/TWAnySigner.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include diff --git a/src/interface/TWAsnParser.cpp b/src/interface/TWAsnParser.cpp new file mode 100644 index 00000000000..1dbd9b5a3ad --- /dev/null +++ b/src/interface/TWAsnParser.cpp @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "AsnParser.h" + +using namespace TW; + +TWData *_Nullable TWAsnParserEcdsaSignatureFromDer(TWData *_Nonnull encoded) { + const Data& encodedData = *reinterpret_cast(encoded); + try { + auto decoded = ASN::AsnParser::ecdsa_signature_from_der(encodedData); + if (!decoded) { + return nullptr; + } + return TWDataCreateWithBytes(decoded.value().data(), decoded.value().size()); + } catch (...) { + return nullptr; + } +} diff --git a/src/interface/TWBarz.cpp b/src/interface/TWBarz.cpp new file mode 100644 index 00000000000..60da9690279 --- /dev/null +++ b/src/interface/TWBarz.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include +#include "Ethereum/Barz.h" + +TWString *_Nonnull TWBarzGetCounterfactualAddress(TWData *_Nonnull input) { + TW::Barz::Proto::ContractAddressInput inputProto; + + const auto bytes = TWDataBytes(input); + const auto size = static_cast(TWDataSize(input)); + if (!inputProto.ParseFromArray(bytes, size)) { + return ""; + } + + return TWStringCreateWithUTF8Bytes(TW::Barz::getCounterfactualAddress(inputProto).c_str()); +} + +TWData *_Nonnull TWBarzGetInitCode(TWString* _Nonnull factory, struct TWPublicKey* _Nonnull publicKey, TWString* _Nonnull verificationFacet, uint32_t salt) { + const auto& factoryStr = *reinterpret_cast(factory); + const auto& publicKeyConverted = *reinterpret_cast(publicKey); + const auto& verificationFacetStr = *reinterpret_cast(verificationFacet); + + const auto initCode = TW::Barz::getInitCode(factoryStr, publicKeyConverted, verificationFacetStr, salt); + return TWDataCreateWithData(&initCode); +} + +TWData *_Nonnull TWBarzGetFormattedSignature(TWData* _Nonnull signature, TWData* _Nonnull challenge, TWData* _Nonnull authenticatorData, TWString* _Nonnull clientDataJSON) { + const auto& signatureData = *reinterpret_cast(signature); + const auto& challengeData = *reinterpret_cast(challenge); + const auto& authenticatorDataConverted = *reinterpret_cast(authenticatorData); + const auto& clientDataJSONStr = *reinterpret_cast(clientDataJSON); + + const auto initCode = TW::Barz::getFormattedSignature(signatureData, challengeData, authenticatorDataConverted, clientDataJSONStr); + return TWDataCreateWithData(&initCode); +} + +TWData *_Nonnull TWBarzGetPrefixedMsgHash(TWData* _Nonnull msgHash, TWString* _Nonnull barzAddress, uint32_t chainId) { + const auto& msgHashData = *reinterpret_cast(msgHash); + const auto& barzAddressData = *reinterpret_cast(barzAddress); + + const auto prefixedMsgHash = TW::Barz::getPrefixedMsgHash(msgHashData, barzAddressData, chainId); + return TWDataCreateWithData(&prefixedMsgHash); +} + +TWData *_Nonnull TWBarzGetDiamondCutCode(TWData *_Nonnull input) { + TW::Barz::Proto::DiamondCutInput inputProto; + + const auto bytes = TWDataBytes(input); + const auto size = static_cast(TWDataSize(input)); + if (!inputProto.ParseFromArray(bytes, size)) { + return ""; + } + + const auto diamondCutCode = TW::Barz::getDiamondCutCode(inputProto); + return TWDataCreateWithData(&diamondCutCode); +} \ No newline at end of file diff --git a/src/interface/TWBase32.cpp b/src/interface/TWBase32.cpp new file mode 100644 index 00000000000..1e71325af10 --- /dev/null +++ b/src/interface/TWBase32.cpp @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "../Base32.h" + +#include + +using namespace TW; + +TWData* TWBase32DecodeWithAlphabet(TWString* _Nonnull string, TWString* _Nullable alphabet) { + Data decodedOut; + auto cppString = *reinterpret_cast(string); + const char* alphabetRaw = nullptr; + if (alphabet != nullptr) { + alphabetRaw = TWStringUTF8Bytes(alphabet); + } + auto result = Base32::decode(cppString, decodedOut, alphabetRaw); + return result ? TWDataCreateWithData(&decodedOut) : nullptr; +} + +TWData* _Nullable TWBase32Decode(TWString* _Nonnull string) { + return TWBase32DecodeWithAlphabet(string, nullptr); +} + +TWString* _Nonnull TWBase32EncodeWithAlphabet(TWData* _Nonnull data, TWString* _Nullable alphabet) { + auto cppData = *reinterpret_cast(data); + const char* alphabetRaw = nullptr; + if (alphabet != nullptr) { + alphabetRaw = TWStringUTF8Bytes(alphabet); + } + auto result = Base32::encode(cppData, alphabetRaw); + return TWStringCreateWithUTF8Bytes(result.c_str()); +} + +TWString* _Nonnull TWBase32Encode(TWData* _Nonnull data) { + return TWBase32EncodeWithAlphabet(data, nullptr); +} diff --git a/src/interface/TWBase58.cpp b/src/interface/TWBase58.cpp index 923b8a33bf0..b8a05c34f1c 100644 --- a/src/interface/TWBase58.cpp +++ b/src/interface/TWBase58.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include @@ -14,19 +12,19 @@ using namespace TW; TWString *_Nonnull TWBase58Encode(TWData *_Nonnull data) { const auto& d = *reinterpret_cast(data); - const auto str = Base58::bitcoin.encodeCheck(d); + const auto str = Base58::encodeCheck(d); return TWStringCreateWithUTF8Bytes(str.c_str()); } TWString *_Nonnull TWBase58EncodeNoCheck(TWData *_Nonnull data) { auto& d = *reinterpret_cast(data); - const auto encoded = Base58::bitcoin.encode(d); + const auto encoded = Base58::encode(d); return TWStringCreateWithUTF8Bytes(encoded.c_str()); } TWData *_Nullable TWBase58Decode(TWString *_Nonnull string) { auto& s = *reinterpret_cast(string); - const auto decoded = Base58::bitcoin.decodeCheck(s); + const auto decoded = Base58::decodeCheck(s); if (decoded.empty()) { return nullptr; } @@ -36,7 +34,7 @@ TWData *_Nullable TWBase58Decode(TWString *_Nonnull string) { TWData *_Nullable TWBase58DecodeNoCheck(TWString *_Nonnull string) { auto& s = *reinterpret_cast(string); - const auto decoded = Base58::bitcoin.decode(s); + const auto decoded = Base58::decode(s); if (decoded.empty()) { return nullptr; } diff --git a/src/interface/TWBase64.cpp b/src/interface/TWBase64.cpp new file mode 100644 index 00000000000..0009dab4b06 --- /dev/null +++ b/src/interface/TWBase64.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "../Base64.h" + +#include + +using namespace TW; + +TWData* _Nullable TWBase64Decode(TWString* _Nonnull string) { + auto cppString = *reinterpret_cast(string); + Data decodedOut = Base64::decode(cppString); + return TWDataCreateWithData(&decodedOut); +} + +TWData* _Nullable TWBase64DecodeUrl(TWString* _Nonnull string) { + auto cppString = *reinterpret_cast(string); + Data decodedOut = Base64::decodeBase64Url(cppString); + return TWDataCreateWithData(&decodedOut); +} + +TWString *_Nonnull TWBase64Encode(TWData *_Nonnull data) { + auto cppData = *reinterpret_cast(data); + auto result = Base64::encode(cppData); + return TWStringCreateWithUTF8Bytes(result.c_str()); +} + +TWString *_Nonnull TWBase64EncodeUrl(TWData *_Nonnull data) { + auto cppData = *reinterpret_cast(data); + auto result = Base64::encodeBase64Url(cppData); + return TWStringCreateWithUTF8Bytes(result.c_str()); +} diff --git a/src/interface/TWBitcoin.cpp b/src/interface/TWBitcoin.cpp deleted file mode 100644 index d6cf837ce7e..00000000000 --- a/src/interface/TWBitcoin.cpp +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include "../Bitcoin/SigHashType.h" - -bool TWBitcoinSigHashTypeIsSingle(enum TWBitcoinSigHashType type) { - return TW::Bitcoin::hashTypeIsSingle(type); -} - -bool TWBitcoinSigHashTypeIsNone(enum TWBitcoinSigHashType type) { - return TW::Bitcoin::hashTypeIsNone(type); -} diff --git a/src/interface/TWBitcoinAddress.cpp b/src/interface/TWBitcoinAddress.cpp index 8df8de2a37f..e81dd75a32d 100644 --- a/src/interface/TWBitcoinAddress.cpp +++ b/src/interface/TWBitcoinAddress.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "../Base58.h" #include "../Bitcoin/Address.h" @@ -13,25 +11,23 @@ #include -using namespace TW::Bitcoin; - bool TWBitcoinAddressEqual(struct TWBitcoinAddress *_Nonnull lhs, struct TWBitcoinAddress *_Nonnull rhs) { return lhs->impl == rhs->impl; } bool TWBitcoinAddressIsValid(TWData *_Nonnull data) { - return TWDataSize(data) == Address::size; + return TWDataSize(data) == TW::Bitcoin::Address::size; } bool TWBitcoinAddressIsValidString(TWString *_Nonnull string) { auto& s = *reinterpret_cast(string); - return Address::isValid(s); + return TW::Bitcoin::Address::isValid(s); } struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithString(TWString *_Nonnull string) { auto& s = *reinterpret_cast(string); try { - return new TWBitcoinAddress{ Address(s) }; + return new TWBitcoinAddress{ TW::Bitcoin::Address(s) }; } catch (...) { return nullptr; } @@ -40,7 +36,7 @@ struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithString(TWString *_N struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithData(TWData *_Nonnull data) { const auto& d = *reinterpret_cast(data); try { - return new TWBitcoinAddress{ Address(d) }; + return new TWBitcoinAddress{ TW::Bitcoin::Address(d) }; } catch (...) { return nullptr; } @@ -48,7 +44,7 @@ struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithData(TWData *_Nonnu struct TWBitcoinAddress *_Nullable TWBitcoinAddressCreateWithPublicKey(struct TWPublicKey *_Nonnull publicKey, uint8_t prefix) { try { - return new TWBitcoinAddress{ Address(publicKey->impl, prefix) }; + return new TWBitcoinAddress{ TW::Bitcoin::Address(publicKey->impl, prefix) }; } catch (...) { return nullptr; } @@ -67,5 +63,5 @@ uint8_t TWBitcoinAddressPrefix(struct TWBitcoinAddress *_Nonnull address) { } TWData *_Nonnull TWBitcoinAddressKeyhash(struct TWBitcoinAddress *_Nonnull address) { - return TWDataCreateWithBytes(address->impl.bytes.data() + 1, Address::size - 1); + return TWDataCreateWithBytes(address->impl.bytes.data() + 1, TW::Bitcoin::Address::size - 1); } diff --git a/src/interface/TWBitcoinMessageSigner.cpp b/src/interface/TWBitcoinMessageSigner.cpp new file mode 100644 index 00000000000..03b1bc5f93c --- /dev/null +++ b/src/interface/TWBitcoinMessageSigner.cpp @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "Bitcoin/MessageSigner.h" + +TWString* _Nonnull TWBitcoinMessageSignerSignMessage(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull address, TWString* _Nonnull message) { + try { + const auto signature = TW::Bitcoin::MessageSigner::signMessage(privateKey->impl, TWStringUTF8Bytes(address), TWStringUTF8Bytes(message), true); + return TWStringCreateWithUTF8Bytes(signature.c_str()); + } catch (...) { + return TWStringCreateWithUTF8Bytes(""); + } +} + +bool TWBitcoinMessageSignerVerifyMessage(TWString* _Nonnull address, TWString* _Nonnull message, TWString* _Nonnull signature) { + return TW::Bitcoin::MessageSigner::verifyMessage(TWStringUTF8Bytes(address), TWStringUTF8Bytes(message), TWStringUTF8Bytes(signature)); +} diff --git a/src/interface/TWBitcoinPsbt.cpp b/src/interface/TWBitcoinPsbt.cpp new file mode 100644 index 00000000000..54c52a515f9 --- /dev/null +++ b/src/interface/TWBitcoinPsbt.cpp @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TrustWalletCore/TWBitcoinPsbt.h" +#include "Bitcoin/Psbt.h" + +using namespace TW; + +TWData* _Nonnull TWBitcoinPsbtSign(TWData* _Nonnull input, enum TWCoinType coin) { + const Data& dataIn = *(reinterpret_cast(input)); + const auto dataOut = Bitcoin::Psbt::sign(dataIn, coin); + return TWDataCreateWithBytes(dataOut.data(), dataOut.size()); +} + +TWData* _Nonnull TWBitcoinPsbtPlan(TWData* _Nonnull input, enum TWCoinType coin) { + const Data& dataIn = *(reinterpret_cast(input)); + const auto dataOut = Bitcoin::Psbt::plan(dataIn, coin); + return TWDataCreateWithBytes(dataOut.data(), dataOut.size()); +} diff --git a/src/interface/TWBitcoinScript.cpp b/src/interface/TWBitcoinScript.cpp index f17cbf01bed..3093e132896 100644 --- a/src/interface/TWBitcoinScript.cpp +++ b/src/interface/TWBitcoinScript.cpp @@ -1,18 +1,14 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include - #include "../Bitcoin/Script.h" #include "../Bitcoin/SigHashType.h" +#include "Data.h" #include -using namespace TW::Bitcoin; - struct TWBitcoinScript *_Nonnull TWBitcoinScriptCreate() { auto* script = new TWBitcoinScript{}; return script; @@ -122,37 +118,44 @@ TWData *TWBitcoinScriptEncode(const struct TWBitcoinScript *script) { struct TWBitcoinScript *TWBitcoinScriptBuildPayToPublicKey(TWData *pubkey) { auto* v = reinterpret_cast*>(pubkey); - auto script = Script::buildPayToPublicKey(*v); + auto script = TW::Bitcoin::Script::buildPayToPublicKey(*v); return new TWBitcoinScript{ .impl = script }; } struct TWBitcoinScript *TWBitcoinScriptBuildPayToPublicKeyHash(TWData *hash) { auto* v = reinterpret_cast*>(hash); - auto script = Script::buildPayToPublicKeyHash(*v); + auto script = TW::Bitcoin::Script::buildPayToPublicKeyHash(*v); return new TWBitcoinScript{ .impl = script }; } struct TWBitcoinScript *TWBitcoinScriptBuildPayToScriptHash(TWData *scriptHash) { auto* v = reinterpret_cast*>(scriptHash); - auto script = Script::buildPayToScriptHash(*v); + auto script = TW::Bitcoin::Script::buildPayToScriptHash(*v); return new TWBitcoinScript{ .impl = script }; } struct TWBitcoinScript *TWBitcoinScriptBuildPayToWitnessPubkeyHash(TWData *hash) { auto* v = reinterpret_cast*>(hash); - auto script = Script::buildPayToWitnessPublicKeyHash(*v); + auto script = TW::Bitcoin::Script::buildPayToWitnessPublicKeyHash(*v); return new TWBitcoinScript{ .impl = script }; } struct TWBitcoinScript *TWBitcoinScriptBuildPayToWitnessScriptHash(TWData *scriptHash) { auto* v = reinterpret_cast*>(scriptHash); - auto script = Script::buildPayToWitnessScriptHash(*v); + auto script = TW::Bitcoin::Script::buildPayToWitnessScriptHash(*v); return new TWBitcoinScript{ .impl = script }; } struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddress(TWString *_Nonnull address, enum TWCoinType coin) { auto* s = reinterpret_cast(address); - auto script = Script::lockScriptForAddress(*s, coin); + auto script = TW::Bitcoin::Script::lockScriptForAddress(*s, coin); + return new TWBitcoinScript{ .impl = script }; +} + +struct TWBitcoinScript *_Nonnull TWBitcoinScriptLockScriptForAddressReplay(TWString *_Nonnull address, enum TWCoinType coin, TWData *blockHash, int64_t blockHeight) { + auto* s = reinterpret_cast(address); + auto* v = reinterpret_cast*>(blockHash); + auto script = TW::Bitcoin::Script::lockScriptForAddress(*s, coin, *v, blockHeight); return new TWBitcoinScript{ .impl = script }; } diff --git a/src/interface/TWBitcoinSigHashType.cpp b/src/interface/TWBitcoinSigHashType.cpp new file mode 100644 index 00000000000..131b40c050b --- /dev/null +++ b/src/interface/TWBitcoinSigHashType.cpp @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "../Bitcoin/SigHashType.h" + +bool TWBitcoinSigHashTypeIsSingle(enum TWBitcoinSigHashType type) { + return TW::Bitcoin::hashTypeIsSingle(type); +} + +bool TWBitcoinSigHashTypeIsNone(enum TWBitcoinSigHashType type) { + return TW::Bitcoin::hashTypeIsNone(type); +} diff --git a/src/interface/TWCardano.cpp b/src/interface/TWCardano.cpp index abe0e869be0..798f8392452 100644 --- a/src/interface/TWCardano.cpp +++ b/src/interface/TWCardano.cpp @@ -1,22 +1,99 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include "Cardano/Transaction.h" +#include "Cardano/AddressV3.h" #include "proto/Cardano.pb.h" -using namespace TW::Cardano; using namespace TW; +namespace internal { + +/// May throw an exception if `tokenBundle` can't be deserialzied as `TW::Cardano::Proto::TokenBundle`. +std::optional cardanoMinAdaAmount(const std::string& toAddress, const Data *tokenBundle, uint64_t coinsPerUtxoByte) { + // Set the initial amount to 0. + const uint64_t amount = 0; + const TW::Cardano::AddressV3 cardanoAddress(toAddress); + + TW::Cardano::Proto::TokenBundle bundleProto; + if (!tokenBundle || !bundleProto.ParseFromArray(tokenBundle->data(), (int)tokenBundle->size())) { + // Expect at least an empty `TokenBundle`. + return std::nullopt; + } + const auto tokens = TW::Cardano::TokenBundle::fromProto(bundleProto); + + const TW::Cardano::TxOutput output(cardanoAddress.data(), amount, tokens); + return output.minAdaAmount(coinsPerUtxoByte); +} + +} + +std::optional parseAdaAmount(const std::string &amount) { + const bool onlyDigits = std::all_of( + amount.begin(), + amount.end(), + [](unsigned char c)->bool { return std::isdigit(c); } + ); + if (!onlyDigits) { + return std::nullopt; + } + + std::size_t chars; + uint64_t amountInt = std::stoull(amount, &chars); + if (chars != amount.size()) { + return std::nullopt; + } + + return amountInt; +} + uint64_t TWCardanoMinAdaAmount(TWData *_Nonnull tokenBundle) { const Data* bundleData = static_cast(tokenBundle); - Proto::TokenBundle bundleProto; + TW::Cardano::Proto::TokenBundle bundleProto; if (bundleData && bundleProto.ParseFromArray(bundleData->data(), (int)bundleData->size())) { - return TokenBundle::fromProto(bundleProto).minAdaAmount(); + return TW::Cardano::TokenBundle::fromProto(bundleProto).minAdaAmount(); } return 0; } + +TWString *_Nullable TWCardanoOutputMinAdaAmount(TWString *_Nonnull toAddress, TWData *_Nonnull tokenBundle, TWString *_Nonnull coinsPerUtxoByte) { + const std::string& address = *reinterpret_cast(toAddress); + const std::string& coinsPerUtxoByteStr = *reinterpret_cast(coinsPerUtxoByte); + const auto* bundleData = static_cast(tokenBundle); + + try { + const auto coinsPerUtxoByteInt = parseAdaAmount(coinsPerUtxoByteStr); + if (!coinsPerUtxoByteInt) { + // Couldn't parse the `coinsPerUtxoByte` string. + return nullptr; + } + + const auto minAdaAmount = internal::cardanoMinAdaAmount(address, bundleData, *coinsPerUtxoByteInt); + if (!minAdaAmount) { + return nullptr; + } + return TWStringCreateWithUTF8Bytes(std::to_string(*minAdaAmount).c_str()); + } catch (...) { + return nullptr; + } +} + +TWString *_Nonnull TWCardanoGetStakingAddress(TWString *_Nonnull baseAddress) { + const auto& address = *reinterpret_cast(baseAddress); + try { + return TWStringCreateWithUTF8Bytes(TW::Cardano::AddressV3(address).getStakingAddress().c_str()); + } catch (...) { + return TWStringCreateWithUTF8Bytes(""); + } +} + +TWString *_Nonnull TWCardanoGetByronAddress(struct TWPublicKey *_Nonnull publicKey) { + try { + return TWStringCreateWithUTF8Bytes(TW::Cardano::AddressV2({ publicKey->impl }).string().c_str()); + } catch (...) { + return TWStringCreateWithUTF8Bytes(""); + } +} diff --git a/src/interface/TWCoinType.cpp b/src/interface/TWCoinType.cpp index a86e2a22c2c..908edfc73b9 100644 --- a/src/interface/TWCoinType.cpp +++ b/src/interface/TWCoinType.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include @@ -39,6 +37,12 @@ TWString *_Nonnull TWCoinTypeDerivationPath(enum TWCoinType coin) { return TWStringCreateWithUTF8Bytes(string.c_str()); } +TWString* TWCoinTypeDerivationPathWithDerivation(enum TWCoinType coin, enum TWDerivation derivation) { + const auto path = TW::derivationPath(coin, derivation); + const auto string = path.string(); + return TWStringCreateWithUTF8Bytes(string.c_str()); +} + TWString *_Nonnull TWCoinTypeDeriveAddress(enum TWCoinType coin, struct TWPrivateKey *_Nonnull privateKey) { const auto string = TW::deriveAddress(coin, privateKey->impl); return TWStringCreateWithUTF8Bytes(string.c_str()); @@ -49,6 +53,11 @@ TWString *_Nonnull TWCoinTypeDeriveAddressFromPublicKey(enum TWCoinType coin, st return TWStringCreateWithUTF8Bytes(string.c_str()); } +TWString *_Nonnull TWCoinTypeDeriveAddressFromPublicKeyAndDerivation(enum TWCoinType coin, struct TWPublicKey *_Nonnull publicKey, enum TWDerivation derivation) { + const auto string = TW::deriveAddress(coin, publicKey->impl, derivation); + return TWStringCreateWithUTF8Bytes(string.c_str()); +} + enum TWHRP TWCoinTypeHRP(enum TWCoinType coin) { return TW::hrp(coin); } @@ -75,4 +84,8 @@ uint32_t TWCoinTypeSlip44Id(enum TWCoinType coin) { enum TWPublicKeyType TWCoinTypePublicKeyType(enum TWCoinType coin) { return TW::publicKeyType(coin); -} \ No newline at end of file +} + +uint32_t TWCoinTypeSS58Prefix(enum TWCoinType coin) { + return TW::ss58Prefix(coin); +} diff --git a/src/interface/TWCryptoBox.cpp b/src/interface/TWCryptoBox.cpp new file mode 100644 index 00000000000..35f83bceb8b --- /dev/null +++ b/src/interface/TWCryptoBox.cpp @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TrustWalletCore/TWCryptoBox.h" +#include "CryptoBox.h" + +using namespace TW; + +TWData* _Nonnull TWCryptoBoxEncryptEasy(struct TWCryptoBoxSecretKey* _Nonnull mySecret, struct TWCryptoBoxPublicKey* _Nonnull otherPubkey, TWData* _Nonnull message) { + auto& messageBytes = *reinterpret_cast(message); + auto encrypted = CryptoBox::encryptEasy(mySecret->impl, otherPubkey->impl, messageBytes); + return TWDataCreateWithBytes(encrypted.data(), encrypted.size()); +} + +TWData* _Nullable TWCryptoBoxDecryptEasy(struct TWCryptoBoxSecretKey* _Nonnull mySecret, struct TWCryptoBoxPublicKey* _Nonnull otherPubkey, TWData* _Nonnull encrypted) { + auto& encryptedBytes = *reinterpret_cast(encrypted); + auto decrypted = CryptoBox::decryptEasy(mySecret->impl, otherPubkey->impl, encryptedBytes); + if (!decrypted) { + return nullptr; + } + return TWDataCreateWithBytes(decrypted->data(), decrypted->size()); +} diff --git a/src/interface/TWCryptoBoxPublicKey.cpp b/src/interface/TWCryptoBoxPublicKey.cpp new file mode 100644 index 00000000000..3dc0e10ec08 --- /dev/null +++ b/src/interface/TWCryptoBoxPublicKey.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TrustWalletCore/TWCryptoBoxPublicKey.h" +#include "CryptoBox.h" + +using namespace TW; + +bool TWCryptoBoxPublicKeyIsValid(TWData* _Nonnull data) { + auto& bytes = *reinterpret_cast(data); + return CryptoBox::PublicKey::isValid(bytes); +} + +struct TWCryptoBoxPublicKey* _Nullable TWCryptoBoxPublicKeyCreateWithData(TWData* _Nonnull data) { + auto& bytes = *reinterpret_cast(data); + auto publicKey = CryptoBox::PublicKey::fromBytes(bytes); + if (!publicKey) { + return nullptr; + } + return new TWCryptoBoxPublicKey { publicKey.value() }; +} + +void TWCryptoBoxPublicKeyDelete(struct TWCryptoBoxPublicKey* _Nonnull publicKey) { + delete publicKey; +} + +TWData* _Nonnull TWCryptoBoxPublicKeyData(struct TWCryptoBoxPublicKey* _Nonnull publicKey) { + auto bytes = publicKey->impl.getData(); + return TWDataCreateWithBytes(bytes.data(), bytes.size()); +} diff --git a/src/interface/TWCryptoBoxSecretKey.cpp b/src/interface/TWCryptoBoxSecretKey.cpp new file mode 100644 index 00000000000..0a16fa6a06f --- /dev/null +++ b/src/interface/TWCryptoBoxSecretKey.cpp @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TrustWalletCore/TWCryptoBoxSecretKey.h" +#include "CryptoBox.h" + +using namespace TW; + +bool TWCryptoBoxSecretKeyIsValid(TWData* _Nonnull data) { + auto& bytes = *reinterpret_cast(data); + return CryptoBox::SecretKey::isValid(bytes); +} + +struct TWCryptoBoxSecretKey* _Nullable TWCryptoBoxSecretKeyCreateWithData(TWData* _Nonnull data) { + auto& bytes = *reinterpret_cast(data); + auto secretKey = CryptoBox::SecretKey::fromBytes(bytes); + if (!secretKey) { + return nullptr; + } + return new TWCryptoBoxSecretKey { secretKey.value() }; +} + +struct TWCryptoBoxSecretKey* _Nonnull TWCryptoBoxSecretKeyCreate() { + CryptoBox::SecretKey secretKey; + return new TWCryptoBoxSecretKey { secretKey }; +} + +void TWCryptoBoxSecretKeyDelete(struct TWCryptoBoxSecretKey* _Nonnull key) { + delete key; +} + +struct TWCryptoBoxPublicKey* TWCryptoBoxSecretKeyGetPublicKey(struct TWCryptoBoxSecretKey* _Nonnull key) { + auto publicKey = key->impl.getPublicKey(); + return new TWCryptoBoxPublicKey { publicKey }; +} + +TWData* _Nonnull TWCryptoBoxSecretKeyData(struct TWCryptoBoxSecretKey* _Nonnull secretKey) { + auto bytes = secretKey->impl.getData(); + return TWDataCreateWithBytes(bytes.data(), bytes.size()); +} diff --git a/src/interface/TWData.cpp b/src/interface/TWData.cpp index 561eb5dd9e8..3cb2d364b88 100644 --- a/src/interface/TWData.cpp +++ b/src/interface/TWData.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include @@ -71,7 +69,7 @@ void TWDataReplaceBytes(TWData *_Nonnull data, size_t start, size_t size, const void TWDataAppendBytes(TWData *_Nonnull data, const uint8_t *_Nonnull bytes, size_t size) { auto* v = const_cast(reinterpret_cast(data)); - for (auto i = 0; i < size; i += 1) + for (auto i = 0ul; i < size; i += 1) v->push_back(bytes[i]); } diff --git a/src/interface/TWDataVector.cpp b/src/interface/TWDataVector.cpp index bea89cc1dac..73985924849 100644 --- a/src/interface/TWDataVector.cpp +++ b/src/interface/TWDataVector.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include diff --git a/src/interface/TWDerivationPath.cpp b/src/interface/TWDerivationPath.cpp new file mode 100644 index 00000000000..0b1c853abdd --- /dev/null +++ b/src/interface/TWDerivationPath.cpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include + +#include "../DerivationPath.h" + +using namespace TW; + +struct TWDerivationPath* _Nonnull TWDerivationPathCreate(enum TWPurpose purpose, uint32_t coin, uint32_t account, uint32_t change, uint32_t address) { + return new TWDerivationPath{DerivationPath(purpose, coin, account, change, address)}; +} + +struct TWDerivationPath* _Nullable TWDerivationPathCreateWithString(TWString* _Nonnull string) { + auto& str = *reinterpret_cast(string); + try { + return new TWDerivationPath{DerivationPath(str)}; + } catch (...) { + return nullptr; + } +} + +void TWDerivationPathDelete(struct TWDerivationPath* _Nonnull path) { + delete path; +} + +uint32_t TWDerivationPathIndicesCount(struct TWDerivationPath* _Nonnull path) { + return static_cast(path->impl.indices.size()); +} + +struct TWDerivationPathIndex* _Nullable TWDerivationPathIndexAt(struct TWDerivationPath* _Nonnull path, uint32_t index) { + if (index >= path->impl.indices.size()) { + return nullptr; + } + return new TWDerivationPathIndex{path->impl.indices[index]}; +} + +enum TWPurpose TWDerivationPathPurpose(struct TWDerivationPath* _Nonnull path) { + return path->impl.purpose(); +} + +uint32_t TWDerivationPathCoin(struct TWDerivationPath* _Nonnull path) { + return path->impl.coin(); +} + +uint32_t TWDerivationPathAccount(struct TWDerivationPath* _Nonnull path) { + return path->impl.account(); +} + +uint32_t TWDerivationPathChange(struct TWDerivationPath* _Nonnull path) { + return path->impl.change(); +} + +uint32_t TWDerivationPathAddress(struct TWDerivationPath* _Nonnull path) { + return path->impl.address(); +} + +TWString* _Nonnull TWDerivationPathDescription(struct TWDerivationPath* _Nonnull path) { + return TWStringCreateWithUTF8Bytes(path->impl.string().c_str()); +} diff --git a/src/interface/TWDerivationPathIndex.cpp b/src/interface/TWDerivationPathIndex.cpp new file mode 100644 index 00000000000..6f0a14ea9b2 --- /dev/null +++ b/src/interface/TWDerivationPathIndex.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include + +#include "../DerivationPath.h" + +using namespace TW; + +struct TWDerivationPathIndex* _Nonnull TWDerivationPathIndexCreate(uint32_t value, bool hardened) { + return new TWDerivationPathIndex{DerivationPathIndex(value, hardened)}; +} + +void TWDerivationPathIndexDelete(struct TWDerivationPathIndex* _Nonnull index) { + delete index; +} + +uint32_t TWDerivationPathIndexValue(struct TWDerivationPathIndex* _Nonnull index) { + return index->impl.value; +} + +bool TWDerivationPathIndexHardened(struct TWDerivationPathIndex* _Nonnull index) { + return index->impl.hardened; +} + +TWString* _Nonnull TWDerivationPathIndexDescription(struct TWDerivationPathIndex* _Nonnull index) { + return TWStringCreateWithUTF8Bytes(index->impl.string().c_str()); +} diff --git a/src/interface/TWEthereum.cpp b/src/interface/TWEthereum.cpp new file mode 100644 index 00000000000..2c492c48e3f --- /dev/null +++ b/src/interface/TWEthereum.cpp @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Data.h" +#include "Ethereum/EIP1014.h" +#include "Ethereum/EIP2645.h" +#include + +#include + +TWString* TWEthereumEip2645GetPath(TWString* ethAddress, TWString* layer, TWString* application, TWString* index) { + const auto& ethAddressStr = *reinterpret_cast(ethAddress); + const auto& layerStr = *reinterpret_cast(layer); + const auto& applicationStr = *reinterpret_cast(application); + const auto& indexStr = *reinterpret_cast(index); + return new std::string(TW::Ethereum::accountPathFromAddress(ethAddressStr, layerStr, applicationStr, indexStr)); +} \ No newline at end of file diff --git a/src/interface/TWEthereumAbi.cpp b/src/interface/TWEthereumAbi.cpp index 513d243df14..829eb594a33 100644 --- a/src/interface/TWEthereumAbi.cpp +++ b/src/interface/TWEthereumAbi.cpp @@ -1,53 +1,74 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include #include "Data.h" -#include "Ethereum/ABI.h" +#include "Ethereum/ABI/Function.h" #include "Ethereum/ContractCall.h" +#include "Ethereum/MessageSigner.h" #include "HexCoding.h" -#include "uint256.h" #include -#include #include -using namespace TW::Ethereum::ABI; -using namespace TW::Ethereum; using namespace TW; +namespace EthAbi = TW::Ethereum::ABI; +template +static TWData* _Nonnull ethereumAbiForwardToRust(F rustFunction, enum TWCoinType coin, TWData* _Nonnull input) { + const Data& inputData = *(reinterpret_cast(input)); + + const Rust::TWDataWrapper dataInPtr(inputData); + Rust::TWDataWrapper dataOutPtr = rustFunction(static_cast(coin), dataInPtr.get()); + + auto dataOut = dataOutPtr.toDataOrDefault(); + return TWDataCreateWithBytes(dataOut.data(), dataOut.size()); +} + +TWData* _Nonnull TWEthereumAbiDecodeContractCall(enum TWCoinType coin, TWData* _Nonnull input) { + return ethereumAbiForwardToRust(Rust::tw_ethereum_abi_decode_contract_call, coin, input); +} + +TWData* _Nonnull TWEthereumAbiDecodeParams(enum TWCoinType coin, TWData* _Nonnull input) { + return ethereumAbiForwardToRust(Rust::tw_ethereum_abi_decode_params, coin, input); +} + +TWData* _Nonnull TWEthereumAbiDecodeValue(enum TWCoinType coin, TWData* _Nonnull input) { + return ethereumAbiForwardToRust(Rust::tw_ethereum_abi_decode_value, coin, input); +} + +TWData* _Nonnull TWEthereumAbiEncodeFunction(enum TWCoinType coin, TWData* _Nonnull input) { + return ethereumAbiForwardToRust(Rust::tw_ethereum_abi_encode_function, coin, input); +} TWData* _Nonnull TWEthereumAbiEncode(struct TWEthereumAbiFunction* _Nonnull func_in) { assert(func_in != nullptr); - Function& function = func_in->impl; - - Data encoded; - function.encode(encoded); - return TWDataCreateWithData(&encoded); + Data encodedData; + auto encoded = func_in->impl.encodeInput(); + if (encoded.has_value()) { + encodedData = encoded.value(); + } + return TWDataCreateWithData(&encodedData); } bool TWEthereumAbiDecodeOutput(struct TWEthereumAbiFunction* _Nonnull func_in, TWData* _Nonnull encoded) { assert(func_in != nullptr); - Function& function = func_in->impl; assert(encoded != nullptr); - Data encData = data(TWDataBytes(encoded), TWDataSize(encoded)); + const Data& encData = *(reinterpret_cast(encoded)); - size_t offset = 0; - return function.decodeOutput(encData, offset); + bool isOutput = true; + return func_in->impl.decode(encData, isOutput); } TWString* _Nullable TWEthereumAbiDecodeCall(TWData* _Nonnull callData, TWString* _Nonnull abiString) { const Data& call = *(reinterpret_cast(callData)); const auto& jsonString = *reinterpret_cast(abiString); - try { - auto abi = nlohmann::json::parse(jsonString); - auto string = decodeCall(call, abi); + try { + auto string = EthAbi::decodeCall(call, jsonString); if (!string.has_value()) { return nullptr; } @@ -61,7 +82,7 @@ TWString* _Nullable TWEthereumAbiDecodeCall(TWData* _Nonnull callData, TWString* TWData* _Nonnull TWEthereumAbiEncodeTyped(TWString* _Nonnull messageJson) { Data data; try { - data = ParamStruct::hashStructJson(TWStringUTF8Bytes(messageJson)); + data = Ethereum::MessageSigner::typedDataPreImageHash(TWStringUTF8Bytes(messageJson)); } catch (...) {} // return empty return TWDataCreateWithBytes(data.data(), data.size()); } diff --git a/src/interface/TWEthereumAbiFunction.cpp b/src/interface/TWEthereumAbiFunction.cpp index 93b451c2a51..db792629ee0 100644 --- a/src/interface/TWEthereumAbiFunction.cpp +++ b/src/interface/TWEthereumAbiFunction.cpp @@ -1,26 +1,21 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include -#include "Ethereum/ABI.h" +#include "Ethereum/ABI/Function.h" #include "Data.h" #include "HexCoding.h" -#include "../uint256.h" -#include #include #include using namespace TW; -using namespace TW::Ethereum; -using namespace TW::Ethereum::ABI; +namespace EthAbi = TW::Ethereum::ABI; struct TWEthereumAbiFunction *_Nonnull TWEthereumAbiFunctionCreateWithString(TWString *_Nonnull name) { - auto func = Function(TWStringUTF8Bytes(name)); + auto func = EthAbi::Function(TWStringUTF8Bytes(name)); return new TWEthereumAbiFunction{ func }; } @@ -31,8 +26,7 @@ void TWEthereumAbiFunctionDelete(struct TWEthereumAbiFunction *_Nonnull func_in) TWString *_Nonnull TWEthereumAbiFunctionGetType(struct TWEthereumAbiFunction *_Nonnull func_in) { assert(func_in != nullptr); - auto function = func_in->impl; - std::string sign = function.getType(); + std::string sign = func_in->impl.getType(); return TWStringCreateWithUTF8Bytes(sign.c_str()); } @@ -40,418 +34,369 @@ TWString *_Nonnull TWEthereumAbiFunctionGetType(struct TWEthereumAbiFunction *_N int TWEthereumAbiFunctionAddParamUInt8(struct TWEthereumAbiFunction *_Nonnull func_in, uint8_t val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 8; + auto encodedValue = store(val); + return func_in->impl.addUintParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamUInt16(struct TWEthereumAbiFunction *_Nonnull func_in, uint16_t val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 16; + auto encodedValue = store(val); + return func_in->impl.addUintParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamUInt32(struct TWEthereumAbiFunction *_Nonnull func_in, uint32_t val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 32; + auto encodedValue = store(val); + return func_in->impl.addUintParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamUInt64(struct TWEthereumAbiFunction *_Nonnull func_in, uint64_t val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 64; + auto encodedValue = store(val); + return func_in->impl.addUintParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamUInt256(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - - uint256_t val2 = load(*static_cast(val)); - auto param = std::make_shared(val2); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 256; + const Data& encodedValue = *(reinterpret_cast(val)); + return func_in->impl.addUintParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamUIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int bits, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - - uint256_t val2 = load(*static_cast(val)); - auto param = std::make_shared(bits, val2); - auto idx = function.addParam(param, isOutput); - return idx; + const Data& encodedValue = *(reinterpret_cast(val)); + return func_in->impl.addUintParam(static_cast(bits), encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int8_t val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 8; + auto encodedValue = store(val); + return func_in->impl.addIntParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamInt16(struct TWEthereumAbiFunction *_Nonnull func_in, int16_t val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 16; + auto encodedValue = store(val); + return func_in->impl.addIntParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamInt32(struct TWEthereumAbiFunction *_Nonnull func_in, int32_t val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 32; + auto encodedValue = store(val); + return func_in->impl.addIntParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int64_t val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 64; + auto encodedValue = store(val); + return func_in->impl.addIntParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamInt256(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - - assert(val != nullptr); - int256_t val2 = ValueEncoder::int256FromUint256(load(*static_cast(val))); - auto param = std::make_shared(val2); - auto idx = function.addParam(param, isOutput); - return idx; + uint32_t bits = 256; + const Data& encodedValue = *(reinterpret_cast(val)); + return func_in->impl.addIntParam(bits, encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int bits, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - - assert(val != nullptr); - int256_t val2 = ValueEncoder::int256FromUint256(load(*static_cast(val))); - auto param = std::make_shared(bits, val2); - auto idx = function.addParam(param, isOutput); - return idx; + const Data& encodedValue = *(reinterpret_cast(val)); + return func_in->impl.addIntParam(static_cast(bits), encodedValue, isOutput); } int TWEthereumAbiFunctionAddParamBool(struct TWEthereumAbiFunction *_Nonnull func_in, bool val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - auto param = std::make_shared(val); - auto idx = function.addParam(param, isOutput); - return idx; + EthereumAbi::Proto::Param paramType; + // Declare the `boolean` type. + paramType.mutable_param()->mutable_boolean(); + + EthereumAbi::Proto::Token token; + token.set_boolean(val); + + return func_in->impl.addParam(std::move(paramType), std::move(token), isOutput); } int TWEthereumAbiFunctionAddParamString(struct TWEthereumAbiFunction *_Nonnull func_in, TWString *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - assert(val != nullptr); - auto param = std::make_shared(TWStringUTF8Bytes(val)); - auto idx = function.addParam(param, isOutput); - return idx; + EthereumAbi::Proto::Param paramType; + // Declare the `string` type. + paramType.mutable_param()->mutable_string_param(); + + EthereumAbi::Proto::Token token; + auto* s = reinterpret_cast(val); + token.set_string_value(*s); + + return func_in->impl.addParam(std::move(paramType), std::move(token), isOutput); } int TWEthereumAbiFunctionAddParamAddress(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - assert(val != nullptr); - Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - auto param = std::make_shared(data); - auto idx = function.addParam(param, isOutput); - return idx; + EthereumAbi::Proto::Param paramType; + // Declare the `address` type. + paramType.mutable_param()->mutable_address(); + + EthereumAbi::Proto::Token token; + const Data& addressData = *(reinterpret_cast(val)); + bool prefixed = true; + auto addressStr = hex(addressData, prefixed); + token.set_address(addressStr); + + return func_in->impl.addParam(std::move(paramType), std::move(token), isOutput); } int TWEthereumAbiFunctionAddParamBytes(struct TWEthereumAbiFunction *_Nonnull func_in, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - auto param = std::make_shared(data); - auto idx = function.addParam(param, isOutput); - return idx; + EthereumAbi::Proto::Param paramType; + // Declare the `byte_array` type. + paramType.mutable_param()->mutable_byte_array(); + + EthereumAbi::Proto::Token token; + const Data& bytesData = *(reinterpret_cast(val)); + token.set_byte_array(bytesData.data(), bytesData.size()); + + return func_in->impl.addParam(std::move(paramType), std::move(token), isOutput); } int TWEthereumAbiFunctionAddParamBytesFix(struct TWEthereumAbiFunction *_Nonnull func_in, size_t count, TWData *_Nonnull val, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - auto param = std::make_shared(count, data); - auto idx = function.addParam(param, isOutput); - return idx; + EthereumAbi::Proto::Param paramType; + // Declare the `byte_array_fix` type. + paramType.mutable_param()->mutable_byte_array_fix()->set_size(static_cast(count)); + + EthereumAbi::Proto::Token token; + Data bytesData = *(reinterpret_cast(val)); + bytesData.resize(count); + token.set_byte_array_fix(bytesData.data(), bytesData.size()); + + return func_in->impl.addParam(std::move(paramType), std::move(token), isOutput); } int TWEthereumAbiFunctionAddParamArray(struct TWEthereumAbiFunction *_Nonnull func_in, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - auto param = std::make_shared(); - auto idx = function.addParam(param, isOutput); - return idx; + EthereumAbi::Proto::Param paramType; + // Declare the `array` type. + paramType.mutable_param()->mutable_array(); + + EthereumAbi::Proto::Token token; + // Declare the `array` empty value. + token.mutable_array(); + + return func_in->impl.addParam(std::move(paramType), std::move(token), isOutput); } ///// GetParam uint8_t TWEthereumAbiFunctionGetParamUInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - - std::shared_ptr param; - if (!function.getParam(idx, param, isOutput)) { - return 0; - } - auto param2 = std::dynamic_pointer_cast(param); - if (param2 == nullptr) { - return 0; - } - return param2->getVal(); + return func_in->impl.getUintParam(idx, 8, isOutput); } uint64_t TWEthereumAbiFunctionGetParamUInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - - std::shared_ptr param; - if (!function.getParam(idx, param, isOutput)) { - return 0; - } - auto param2 = std::dynamic_pointer_cast(param); - if (param2 == nullptr) { - return 0; - } - return param2->getVal(); + return func_in->impl.getUintParam(idx, 64, isOutput); } TWData *_Nonnull TWEthereumAbiFunctionGetParamUInt256(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - - uint256_t val256 = 0; - std::shared_ptr param; - if (!function.getParam(idx, param, isOutput)) { - TW::Data valData = TW::store(val256); - return TWDataCreateWithData(&valData); - } - auto param2 = std::dynamic_pointer_cast(param); - if (param2 == nullptr) { - TW::Data valData = TW::store(val256); - return TWDataCreateWithData(&valData); - } - val256 = param2->getVal(); - TW::Data valData = TW::store(val256); + auto valData = func_in->impl.getUintParamData(idx, 256, isOutput); return TWDataCreateWithData(&valData); } bool TWEthereumAbiFunctionGetParamBool(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - - std::shared_ptr param; - if (!function.getParam(idx, param, isOutput)) { + auto param = func_in->impl.getParam(idx, isOutput); + if (!param.has_value() || !param->has_boolean()) { return false; } - auto param2 = std::dynamic_pointer_cast(param); - if (param2 == nullptr) { - return false; - } - return param2->getVal(); + return param->boolean(); } TWString *_Nonnull TWEthereumAbiFunctionGetParamString(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; - std::string valStr; - std::shared_ptr param; - if (!function.getParam(idx, param, isOutput)) { - return TWStringCreateWithUTF8Bytes(valStr.c_str()); - } - auto param2 = std::dynamic_pointer_cast(param); - if (param2 == nullptr) { + + auto param = func_in->impl.getParam(idx, isOutput); + if (!param.has_value() || !param->has_string_value()) { return TWStringCreateWithUTF8Bytes(valStr.c_str()); } - valStr = param2->getVal(); + valStr = param->string_value(); return TWStringCreateWithUTF8Bytes(valStr.c_str()); } TWData *_Nonnull TWEthereumAbiFunctionGetParamAddress(struct TWEthereumAbiFunction *_Nonnull func_in, int idx, bool isOutput) { assert(func_in != nullptr); - Function& function = func_in->impl; + Data addressData; - Data valData; - std::shared_ptr param; - if (!function.getParam(idx, param, isOutput)) { - return TWDataCreateWithData(&valData); + auto param = func_in->impl.getParam(idx, isOutput); + if (!param.has_value() || !param->has_address()) { + return TWDataCreateWithData(&addressData); } - auto param2 = std::dynamic_pointer_cast(param); - if (param2 == nullptr) { - return TWDataCreateWithData(&valData); + auto addressStr = param->address(); + try { + addressData = parse_hex(addressStr); + return TWDataCreateWithData(&addressData); + } catch (...) { + return TWDataCreateWithData(&addressData); } - valData = param2->getData(); - return TWDataCreateWithData(&valData); } ///// AddInArrayParam -int addInArrayParam(Function& function, int arrayIdx, const std::shared_ptr& childParam) { - std::shared_ptr param; - if (!function.getInParam(arrayIdx, param)) { - return -1; - } - std::shared_ptr paramArr = std::dynamic_pointer_cast(param); - if (paramArr == nullptr) { - return -1; // not an array - } - return paramArr->addParam(childParam); -} - int TWEthereumAbiFunctionAddInArrayParamUInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint8_t val) { assert(func_in != nullptr); - Function& function = func_in->impl; - - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + auto encodedVal = store(val); + return func_in->impl.addInArrayUintParam(arrayIdx, 8, encodedVal); } int TWEthereumAbiFunctionAddInArrayParamUInt16(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint16_t val) { assert(func_in != nullptr); - Function& function = func_in->impl; - - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + auto encodedVal = store(val); + return func_in->impl.addInArrayUintParam(arrayIdx, 16, encodedVal); } int TWEthereumAbiFunctionAddInArrayParamUInt32(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint32_t val) { assert(func_in != nullptr); - Function& function = func_in->impl; - - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + auto encodedVal = store(val); + return func_in->impl.addInArrayUintParam(arrayIdx, 32, encodedVal); } int TWEthereumAbiFunctionAddInArrayParamUInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, uint64_t val) { assert(func_in != nullptr); - Function& function = func_in->impl; - - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + auto encodedVal = store(val); + return func_in->impl.addInArrayUintParam(arrayIdx, 64, encodedVal); } int TWEthereumAbiFunctionAddInArrayParamUInt256(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val) { assert(func_in != nullptr); - Function& function = func_in->impl; - - uint256_t val2 = load(*static_cast(val)); - return addInArrayParam(function, arrayIdx, std::make_shared(val2)); + const Data& bytesData = *(reinterpret_cast(val)); + return func_in->impl.addInArrayUintParam(arrayIdx, 256, bytesData); } int TWEthereumAbiFunctionAddInArrayParamUIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int bits, TWData *_Nonnull val) { assert(func_in != nullptr); - Function& function = func_in->impl; - - uint256_t val2 = load(*static_cast(val)); - return addInArrayParam(function, arrayIdx, std::make_shared(bits, val2)); + const Data& bytesData = *(reinterpret_cast(val)); + return func_in->impl.addInArrayUintParam(arrayIdx, bits, bytesData); } int TWEthereumAbiFunctionAddInArrayParamInt8(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int8_t val) { assert(func_in != nullptr); - Function& function = func_in->impl; - - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + auto encodedVal = store(val); + return func_in->impl.addInArrayIntParam(arrayIdx, 8, encodedVal); } int TWEthereumAbiFunctionAddInArrayParamInt16(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int16_t val) { assert(func_in != nullptr); - Function& function = func_in->impl; - - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + auto encodedVal = store(val); + return func_in->impl.addInArrayIntParam(arrayIdx, 16, encodedVal); } int TWEthereumAbiFunctionAddInArrayParamInt32(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int32_t val) { assert(func_in != nullptr); - Function& function = func_in->impl; - - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + auto encodedVal = store(val); + return func_in->impl.addInArrayIntParam(arrayIdx, 32, encodedVal); } int TWEthereumAbiFunctionAddInArrayParamInt64(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int64_t val) { assert(func_in != nullptr); - Function& function = func_in->impl; - - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + auto encodedVal = store(val); + return func_in->impl.addInArrayIntParam(arrayIdx, 64, encodedVal); } int TWEthereumAbiFunctionAddInArrayParamInt256(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val) { assert(func_in != nullptr); - Function& function = func_in->impl; - - assert(val != nullptr); - int256_t val2 = ValueEncoder::int256FromUint256(load(*static_cast(val))); - return addInArrayParam(function, arrayIdx, std::make_shared(val2)); + const Data& bytesData = *(reinterpret_cast(val)); + return func_in->impl.addInArrayIntParam(arrayIdx, 256, bytesData); } int TWEthereumAbiFunctionAddInArrayParamIntN(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, int bits, TWData *_Nonnull val) { assert(func_in != nullptr); - Function& function = func_in->impl; - - assert(val != nullptr); - int256_t val2 = ValueEncoder::int256FromUint256(load(*static_cast(val))); - return addInArrayParam(function, arrayIdx, std::make_shared(bits, val2)); + const Data& bytesData = *(reinterpret_cast(val)); + return func_in->impl.addInArrayIntParam(arrayIdx, bits, bytesData); } int TWEthereumAbiFunctionAddInArrayParamBool(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, bool val) { assert(func_in != nullptr); - Function& function = func_in->impl; - return addInArrayParam(function, arrayIdx, std::make_shared(val)); + EthereumAbi::Proto::ParamType paramType; + // Declare the boolean type. + paramType.mutable_boolean(); + + EthereumAbi::Proto::Token token; + token.set_boolean(val); + + return func_in->impl.addInArrayParam(arrayIdx, std::move(paramType), std::move(token)); } int TWEthereumAbiFunctionAddInArrayParamString(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWString *_Nonnull val) { assert(func_in != nullptr); - Function& function = func_in->impl; - assert(val != nullptr); - return addInArrayParam(function, arrayIdx, std::make_shared(TWStringUTF8Bytes(val))); + EthereumAbi::Proto::ParamType paramType; + // Declare the boolean type. + paramType.mutable_string_param(); + + EthereumAbi::Proto::Token token; + token.set_string_value(TWStringUTF8Bytes(val)); + + return func_in->impl.addInArrayParam(arrayIdx, std::move(paramType), std::move(token)); } int TWEthereumAbiFunctionAddInArrayParamAddress(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val) { assert(func_in != nullptr); - Function& function = func_in->impl; - assert(val != nullptr); - Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - return addInArrayParam(function, arrayIdx, std::make_shared(data)); + EthereumAbi::Proto::ParamType paramType; + // Declare the boolean type. + paramType.mutable_address(); + + EthereumAbi::Proto::Token token; + const Data& addressData = *(reinterpret_cast(val)); + bool prefixed = true; + auto addressStr = hex(addressData, prefixed); + token.set_address(addressStr); + + return func_in->impl.addInArrayParam(arrayIdx, std::move(paramType), std::move(token)); } int TWEthereumAbiFunctionAddInArrayParamBytes(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, TWData *_Nonnull val) { assert(func_in != nullptr); - Function& function = func_in->impl; - Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - return addInArrayParam(function, arrayIdx, std::make_shared(data)); + EthereumAbi::Proto::ParamType paramType; + // Declare the boolean type. + paramType.mutable_byte_array(); + + EthereumAbi::Proto::Token token; + const Data& bytesData = *(reinterpret_cast(val)); + token.set_byte_array(bytesData.data(), bytesData.size()); + + return func_in->impl.addInArrayParam(arrayIdx, std::move(paramType), std::move(token)); } int TWEthereumAbiFunctionAddInArrayParamBytesFix(struct TWEthereumAbiFunction *_Nonnull func_in, int arrayIdx, size_t count, TWData *_Nonnull val) { assert(func_in != nullptr); - Function& function = func_in->impl; - Data data = TW::data(TWDataBytes(val), TWDataSize(val)); - return addInArrayParam(function, arrayIdx, std::make_shared(count, data)); + EthereumAbi::Proto::ParamType paramType; + // Declare the boolean type. + paramType.mutable_byte_array_fix()->set_size(static_cast(count)); + + EthereumAbi::Proto::Token token; + Data bytesData = *(reinterpret_cast(val)); + bytesData.resize(count); + token.set_byte_array_fix(bytesData.data(), bytesData.size()); + + return func_in->impl.addInArrayParam(arrayIdx, std::move(paramType), std::move(token)); } diff --git a/src/interface/TWEthereumAbiValue.cpp b/src/interface/TWEthereumAbiValue.cpp index 7a6843f23b0..828521133d4 100644 --- a/src/interface/TWEthereumAbiValue.cpp +++ b/src/interface/TWEthereumAbiValue.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include @@ -10,63 +8,63 @@ #include #include -using namespace TW::Ethereum; using namespace TW; +namespace EthAbi = TW::Ethereum::ABI; TWData* _Nonnull TWEthereumAbiValueEncodeBool(bool value) { Data data; - ABI::ValueEncoder::encodeBool(value, data); + EthAbi::ValueEncoder::encodeBool(value, data); return TWDataCreateWithBytes(data.data(), data.size()); } TWData* _Nonnull TWEthereumAbiValueEncodeInt32(int32_t value) { Data data; - ABI::ValueEncoder::encodeInt32(value, data); + EthAbi::ValueEncoder::encodeInt32(value, data); return TWDataCreateWithBytes(data.data(), data.size()); } TWData* _Nonnull TWEthereumAbiValueEncodeUInt32(uint32_t value) { Data data; - ABI::ValueEncoder::encodeUInt32(value, data); + EthAbi::ValueEncoder::encodeUInt32(value, data); return TWDataCreateWithBytes(data.data(), data.size()); } TWData* _Nonnull TWEthereumAbiValueEncodeInt256(TWData* _Nonnull value) { Data data; int256_t value256 = static_cast(TW::load(*reinterpret_cast(value))); - ABI::ValueEncoder::encodeInt256(value256, data); + EthAbi::ValueEncoder::encodeInt256(value256, data); return TWDataCreateWithBytes(data.data(), data.size()); } TWData* _Nonnull TWEthereumAbiValueEncodeUInt256(TWData* _Nonnull value) { Data data; uint256_t value256 = TW::load(*reinterpret_cast(value)); - ABI::ValueEncoder::encodeUInt256(value256, data); + EthAbi::ValueEncoder::encodeUInt256(value256, data); return TWDataCreateWithBytes(data.data(), data.size()); } TWData* _Nonnull TWEthereumAbiValueEncodeAddress(TWData* _Nonnull value) { Data data; - ABI::ValueEncoder::encodeAddress(*reinterpret_cast(value), data); + EthAbi::ValueEncoder::encodeAddress(*reinterpret_cast(value), data); return TWDataCreateWithBytes(data.data(), data.size()); } TWData* _Nonnull TWEthereumAbiValueEncodeString(TWString* _Nonnull value) { Data data; - ABI::ValueEncoder::encodeString(TWStringUTF8Bytes(value), data); + EthAbi::ValueEncoder::encodeString(TWStringUTF8Bytes(value), data); return TWDataCreateWithBytes(data.data(), data.size()); } TWData* _Nonnull TWEthereumAbiValueEncodeBytes(TWData* _Nonnull value) { Data data; - ABI::ValueEncoder::encodeBytes(*reinterpret_cast(value), data); + EthAbi::ValueEncoder::encodeBytes(*reinterpret_cast(value), data); return TWDataCreateWithBytes(data.data(), data.size()); } TWData* _Nonnull TWEthereumAbiValueEncodeBytesDyn(TWData* _Nonnull value) { Data data; - ABI::ValueEncoder::encodeBytesDyn(*reinterpret_cast(value), data); + EthAbi::ValueEncoder::encodeBytesDyn(*reinterpret_cast(value), data); return TWDataCreateWithBytes(data.data(), data.size()); } diff --git a/src/interface/TWEthereumMessageSigner.cpp b/src/interface/TWEthereumMessageSigner.cpp new file mode 100644 index 00000000000..9d40be9e34c --- /dev/null +++ b/src/interface/TWEthereumMessageSigner.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "Ethereum/MessageSigner.h" + +namespace TW::internal { + +TWString* _Nonnull TWEthereumMessageSignerSignCommon(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull message, Ethereum::MessageType msgType, Ethereum::MessageSigner::MaybeChainId chainId = std::nullopt) { + try { + const auto signature = TW::Ethereum::MessageSigner::signMessage(privateKey->impl, TWStringUTF8Bytes(message), msgType, chainId); + return TWStringCreateWithUTF8Bytes(signature.c_str()); + } catch (...) { + return TWStringCreateWithUTF8Bytes(""); + } +} + +TWString* _Nonnull TWEthereumMessageSignerSignTypedCommon(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull message, Ethereum::MessageType msgType, Ethereum::MessageSigner::MaybeChainId chainId = std::nullopt) { + try { + const auto signature = TW::Ethereum::MessageSigner::signTypedData(privateKey->impl, TWStringUTF8Bytes(message), msgType, chainId); + return TWStringCreateWithUTF8Bytes(signature.c_str()); + } catch (...) { + return TWStringCreateWithUTF8Bytes(""); + } +} + +} // namespace TW::internal + +TWString* _Nonnull TWEthereumMessageSignerSignTypedMessage(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull messageJson) { + return TW::internal::TWEthereumMessageSignerSignTypedCommon(privateKey, messageJson, TW::Ethereum::MessageType::Legacy); +} + +TWString* _Nonnull TWEthereumMessageSignerSignTypedMessageEip155(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull messageJson, int chainId) { + return TW::internal::TWEthereumMessageSignerSignTypedCommon(privateKey, messageJson, TW::Ethereum::MessageType::Eip155, static_cast(chainId)); +} + +TWString* _Nonnull TWEthereumMessageSignerSignMessage(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull message) { + return TW::internal::TWEthereumMessageSignerSignCommon(privateKey, message, TW::Ethereum::MessageType::Legacy); +} + +TWString* _Nonnull TWEthereumMessageSignerSignMessageImmutableX(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull message) { + return TW::internal::TWEthereumMessageSignerSignCommon(privateKey, message, TW::Ethereum::MessageType::ImmutableX); +} + +TWString* _Nonnull TWEthereumMessageSignerSignMessageEip155(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull message, int chainId) { + return TW::internal::TWEthereumMessageSignerSignCommon(privateKey, message, TW::Ethereum::MessageType::Eip155, static_cast(chainId)); +} + +bool TWEthereumMessageSignerVerifyMessage(const struct TWPublicKey* _Nonnull publicKey, TWString* _Nonnull message, TWString* _Nonnull signature) { + return TW::Ethereum::MessageSigner::verifyMessage(publicKey->impl, TWStringUTF8Bytes(message), TWStringUTF8Bytes(signature)); +} diff --git a/src/interface/TWEthereumRlp.cpp b/src/interface/TWEthereumRlp.cpp new file mode 100644 index 00000000000..16bb66dae32 --- /dev/null +++ b/src/interface/TWEthereumRlp.cpp @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "rust/Wrapper.h" +#include "Data.h" + +using namespace TW; + +TWData* _Nonnull TWEthereumRlpEncode(enum TWCoinType coin, TWData* _Nonnull input) { + const Data& dataIn = *(reinterpret_cast(input)); + + const Rust::TWDataWrapper dataInPtr(dataIn); + Rust::TWDataWrapper dataOutPtr = Rust::tw_ethereum_rlp_encode(static_cast(coin), dataInPtr.get()); + + auto dataOut = dataOutPtr.toDataOrDefault(); + return TWDataCreateWithBytes(dataOut.data(), dataOut.size()); +} diff --git a/src/interface/TWFIOAccount.cpp b/src/interface/TWFIOAccount.cpp index b600d82b493..378113fb24c 100644 --- a/src/interface/TWFIOAccount.cpp +++ b/src/interface/TWFIOAccount.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include @@ -12,7 +10,6 @@ #include using namespace TW; -using namespace TW::FIO; struct TWFIOAccount { std::string description; @@ -20,11 +17,11 @@ struct TWFIOAccount { struct TWFIOAccount *_Nullable TWFIOAccountCreateWithString(TWString *_Nonnull string) { const auto& account = *reinterpret_cast(string); - if (Address::isValid(account)) { - const auto addr = Address(account); - return new TWFIOAccount{Actor::actor(addr)}; + if (FIO::Address::isValid(account)) { + const auto addr = FIO::Address(account); + return new TWFIOAccount{FIO::Actor::actor(addr)}; } - if (Actor::validate(account)) { + if (FIO::Actor::validate(account)) { return new TWFIOAccount{account}; } return nullptr; diff --git a/src/interface/TWFilecoinAddressConverter.cpp b/src/interface/TWFilecoinAddressConverter.cpp new file mode 100644 index 00000000000..10d6722b27a --- /dev/null +++ b/src/interface/TWFilecoinAddressConverter.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include + +TWString* _Nonnull TWFilecoinAddressConverterConvertToEthereum(TWString* _Nonnull filecoinAddress) { + const auto& address = *reinterpret_cast(filecoinAddress); + try { + if (auto eth_opt = TW::Filecoin::AddressConverter::convertToEthereumString(address); eth_opt) { + return TWStringCreateWithUTF8Bytes(eth_opt->c_str()); + } + } catch (...) { + } + + // Return an empty string if an error occurs. + return TWStringCreateWithUTF8Bytes(""); +} + +TWString* _Nonnull TWFilecoinAddressConverterConvertFromEthereum(TWString* _Nonnull ethAddress) { + const auto& address = *reinterpret_cast(ethAddress); + try { + std::string filecoinAddress = TW::Filecoin::AddressConverter::convertFromEthereumString(address); + return TWStringCreateWithUTF8Bytes(filecoinAddress.c_str()); + } catch (...) { + } + + // Return an empty string if an error occurs. + return TWStringCreateWithUTF8Bytes(""); +} diff --git a/src/interface/TWGroestlcoinAddress.cpp b/src/interface/TWGroestlcoinAddress.cpp index 11d28b4780a..164592aba55 100644 --- a/src/interface/TWGroestlcoinAddress.cpp +++ b/src/interface/TWGroestlcoinAddress.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include @@ -11,34 +9,32 @@ #include -using namespace TW::Groestlcoin; - -bool TWGroestlcoinAddressEqual(struct TWGroestlcoinAddress *_Nonnull lhs, struct TWGroestlcoinAddress *_Nonnull rhs) { +bool TWGroestlcoinAddressEqual(struct TWGroestlcoinAddress* _Nonnull lhs, struct TWGroestlcoinAddress* _Nonnull rhs) { return lhs->impl.bytes == rhs->impl.bytes; } -bool TWGroestlcoinAddressIsValidString(TWString *_Nonnull string) { +bool TWGroestlcoinAddressIsValidString(TWString* _Nonnull string) { auto& s = *reinterpret_cast(string); - return Address::isValid(s); + return TW::Groestlcoin::Address::isValid(s); } -struct TWGroestlcoinAddress *_Nullable TWGroestlcoinAddressCreateWithString(TWString *_Nonnull string) { +struct TWGroestlcoinAddress* _Nullable TWGroestlcoinAddressCreateWithString(TWString* _Nonnull string) { auto& s = *reinterpret_cast(string); - if (!Address::isValid(s)) { + if (!TW::Groestlcoin::Address::isValid(s)) { return nullptr; } - return new TWGroestlcoinAddress{ Address(s) }; + return new TWGroestlcoinAddress{TW::Groestlcoin::Address(s)}; } -struct TWGroestlcoinAddress *_Nonnull TWGroestlcoinAddressCreateWithPublicKey(struct TWPublicKey *_Nonnull publicKey, uint8_t prefix) { - return new TWGroestlcoinAddress{ Address(publicKey->impl, prefix) }; +struct TWGroestlcoinAddress* _Nonnull TWGroestlcoinAddressCreateWithPublicKey(struct TWPublicKey* _Nonnull publicKey, uint8_t prefix) { + return new TWGroestlcoinAddress{TW::Groestlcoin::Address(publicKey->impl, prefix)}; } -void TWGroestlcoinAddressDelete(struct TWGroestlcoinAddress *_Nonnull address) { +void TWGroestlcoinAddressDelete(struct TWGroestlcoinAddress* _Nonnull address) { delete address; } -TWString *_Nonnull TWGroestlcoinAddressDescription(struct TWGroestlcoinAddress *_Nonnull address) { +TWString* _Nonnull TWGroestlcoinAddressDescription(struct TWGroestlcoinAddress* _Nonnull address) { const auto str = address->impl.string(); return TWStringCreateWithUTF8Bytes(str.c_str()); } diff --git a/src/interface/TWHDVersion.cpp b/src/interface/TWHDVersion.cpp index e093336537a..5bfeb6d4ccb 100644 --- a/src/interface/TWHDVersion.cpp +++ b/src/interface/TWHDVersion.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include diff --git a/src/interface/TWHDWallet.cpp b/src/interface/TWHDWallet.cpp index 7bdc6e2f232..bce0e819a6b 100644 --- a/src/interface/TWHDWallet.cpp +++ b/src/interface/TWHDWallet.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include @@ -51,7 +49,7 @@ void TWHDWalletDelete(struct TWHDWallet *wallet) { } TWData *_Nonnull TWHDWalletSeed(struct TWHDWallet *_Nonnull wallet) { - return TWDataCreateWithBytes(wallet->impl.getSeed().data(), HDWallet::seedSize); + return TWDataCreateWithBytes(wallet->impl.getSeed().data(), HDWallet<>::mSeedSize); } TWString *_Nonnull TWHDWalletMnemonic(struct TWHDWallet *_Nonnull wallet){ @@ -67,14 +65,17 @@ struct TWPrivateKey *_Nonnull TWHDWalletGetMasterKey(struct TWHDWallet *_Nonnull } struct TWPrivateKey *_Nonnull TWHDWalletGetKeyForCoin(struct TWHDWallet *wallet, TWCoinType coin) { - auto derivationPath = TW::derivationPath(coin); - return new TWPrivateKey{ wallet->impl.getKey(coin, derivationPath) }; + return TWHDWalletGetKeyDerivation(wallet, coin, TWDerivationDefault); } TWString *_Nonnull TWHDWalletGetAddressForCoin(struct TWHDWallet *wallet, TWCoinType coin) { - auto derivationPath = TW::derivationPath(coin); + return TWHDWalletGetAddressDerivation(wallet, coin, TWDerivationDefault); +} + +TWString *_Nonnull TWHDWalletGetAddressDerivation(struct TWHDWallet *wallet, TWCoinType coin, enum TWDerivation derivation) { + auto derivationPath = TW::derivationPath(coin, derivation); PrivateKey privateKey = wallet->impl.getKey(coin, derivationPath); - std::string address = deriveAddress(coin, privateKey); + std::string address = deriveAddress(coin, privateKey, derivation); return TWStringCreateWithUTF8Bytes(address.c_str()); } @@ -84,11 +85,22 @@ struct TWPrivateKey *_Nonnull TWHDWalletGetKey(struct TWHDWallet *_Nonnull walle return new TWPrivateKey{ wallet->impl.getKey(coin, path) }; } +struct TWPrivateKey *_Nonnull TWHDWalletGetKeyDerivation(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin, enum TWDerivation derivation) { + auto derivationPath = TW::derivationPath(coin, derivation); + return new TWPrivateKey{ wallet->impl.getKey(coin, derivationPath) }; +} + struct TWPrivateKey *_Nonnull TWHDWalletGetDerivedKey(struct TWHDWallet *_Nonnull wallet, enum TWCoinType coin, uint32_t account, uint32_t change, uint32_t address) { const auto derivationPath = DerivationPath(TW::purpose(coin), TW::slip44Id(coin), account, change, address); return new TWPrivateKey{ wallet->impl.getKey(coin, derivationPath) }; } +struct TWPrivateKey *_Nonnull TWHDWalletGetKeyByCurve(struct TWHDWallet *_Nonnull wallet, enum TWCurve curve, TWString *_Nonnull derivationPath) { + auto& s = *reinterpret_cast(derivationPath); + const auto path = DerivationPath(s); + return new TWPrivateKey{ wallet->impl.getKeyByCurve(curve, path)}; +} + TWString *_Nonnull TWHDWalletGetExtendedPrivateKey(struct TWHDWallet *wallet, TWPurpose purpose, TWCoinType coin, TWHDVersion version) { return new std::string(wallet->impl.getExtendedPrivateKey(purpose, coin, version)); } @@ -97,17 +109,25 @@ TWString *_Nonnull TWHDWalletGetExtendedPublicKey(struct TWHDWallet *wallet, TWP return new std::string(wallet->impl.getExtendedPublicKey(purpose, coin, version)); } -TWString *_Nonnull TWHDWalletGetExtendedPrivateKeyAccount(struct TWHDWallet *wallet, TWPurpose purpose, TWCoinType coin, TWHDVersion version, uint32_t account) { - return new std::string(wallet->impl.getExtendedPrivateKeyAccount(purpose, coin, version, account)); +TWString *_Nonnull TWHDWalletGetExtendedPrivateKeyAccount(struct TWHDWallet *wallet, TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version, uint32_t account) { + return new std::string(wallet->impl.getExtendedPrivateKeyAccount(purpose, coin, derivation, version, account)); +} + +TWString *_Nonnull TWHDWalletGetExtendedPublicKeyAccount(struct TWHDWallet *wallet, TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version, uint32_t account) { + return new std::string(wallet->impl.getExtendedPublicKeyAccount(purpose, coin, derivation, version, account)); +} + +TWString *_Nonnull TWHDWalletGetExtendedPrivateKeyDerivation(struct TWHDWallet *wallet, TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version) { + return new std::string(wallet->impl.getExtendedPrivateKeyDerivation(purpose, coin, derivation, version)); } -TWString *_Nonnull TWHDWalletGetExtendedPublicKeyAccount(struct TWHDWallet *wallet, TWPurpose purpose, TWCoinType coin, TWHDVersion version, uint32_t account) { - return new std::string(wallet->impl.getExtendedPublicKeyAccount(purpose, coin, version, account)); +TWString *_Nonnull TWHDWalletGetExtendedPublicKeyDerivation(struct TWHDWallet *wallet, TWPurpose purpose, TWCoinType coin, TWDerivation derivation, TWHDVersion version) { + return new std::string(wallet->impl.getExtendedPublicKeyDerivation(purpose, coin, derivation, version)); } TWPublicKey *TWHDWalletGetPublicKeyFromExtended(TWString *_Nonnull extended, enum TWCoinType coin, TWString *_Nonnull derivationPath) { const auto derivationPathObject = DerivationPath(*reinterpret_cast(derivationPath)); - auto publicKey = HDWallet::getPublicKeyFromExtended(*reinterpret_cast(extended), coin, derivationPathObject); + auto publicKey = HDWallet<>::getPublicKeyFromExtended(*reinterpret_cast(extended), coin, derivationPathObject); if (!publicKey) { return nullptr; } diff --git a/src/interface/TWHash.cpp b/src/interface/TWHash.cpp index 0e6019a3f54..b66adc07fb3 100644 --- a/src/interface/TWHash.cpp +++ b/src/interface/TWHash.cpp @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include "../Hash.h" -#include "../Data.h" +#include "Data.h" #include "BinaryCoding.h" #include @@ -83,6 +81,16 @@ TWData* _Nonnull TWHashSHA256SHA256(TWData* _Nonnull data) { return TWDataCreateWithBytes(result.data(), result.size()); } +TWData *_Nonnull TWHashBlake2bPersonal(TWData *_Nonnull data, TWData * _Nonnull personal, size_t outlen) { + auto resultBytes = TW::Data(outlen); + auto dataBytes = TWDataBytes(data); + auto personalBytes = TWDataBytes(personal); + auto personalSize = TWDataSize(personal); + blake2b_Personal(dataBytes, static_cast(TWDataSize(data)), personalBytes, personalSize, resultBytes.data(), outlen); + auto result = TWDataCreateWithBytes(resultBytes.data(), outlen); + return result; +} + TWData* _Nonnull TWHashSHA256RIPEMD(TWData* _Nonnull data) { const auto result = Hash::sha256ripemd(reinterpret_cast(TWDataBytes(data)), TWDataSize(data)); return TWDataCreateWithBytes(result.data(), result.size()); diff --git a/src/interface/TWMnemonic.cpp b/src/interface/TWMnemonic.cpp index f966a26ad4e..e5694b3c9a4 100644 --- a/src/interface/TWMnemonic.cpp +++ b/src/interface/TWMnemonic.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include diff --git a/src/interface/TWNEARAccount.cpp b/src/interface/TWNEARAccount.cpp index 959fffcd049..2c0f55ec0f9 100644 --- a/src/interface/TWNEARAccount.cpp +++ b/src/interface/TWNEARAccount.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include @@ -13,7 +11,6 @@ #include using namespace TW; -using namespace TW::NEAR; struct TWNEARAccount { std::string description; @@ -21,11 +18,11 @@ struct TWNEARAccount { struct TWNEARAccount *_Nullable TWNEARAccountCreateWithString(TWString *_Nonnull string) { const auto& account = *reinterpret_cast(string); - if (Address::isValid(account)) { - const auto addr = Address(account); + if (TW::NEAR::Address::isValid(account)) { + const auto addr = TW::NEAR::Address(account); return new TWNEARAccount{addr.string()}; } - if (Account::isValid(account)) { + if (TW::NEAR::Account::isValid(account)) { return new TWNEARAccount{account}; } return nullptr; diff --git a/src/interface/TWNervosAddress.cpp b/src/interface/TWNervosAddress.cpp new file mode 100644 index 00000000000..925be23fdac --- /dev/null +++ b/src/interface/TWNervosAddress.cpp @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TrustWalletCore/TWData.h" +#include "../Base58.h" +#include "../Nervos/Address.h" + +#include + +#include + +bool TWNervosAddressEqual(struct TWNervosAddress *_Nonnull lhs, struct TWNervosAddress *_Nonnull rhs) { + return lhs->impl == rhs->impl; +} + +bool TWNervosAddressIsValidString(TWString *_Nonnull string) { + auto& s = *reinterpret_cast(string); + return TW::Nervos::Address::isValid(s); +} + +struct TWNervosAddress *_Nullable TWNervosAddressCreateWithString(TWString *_Nonnull string) { + auto& s = *reinterpret_cast(string); + try { + return new TWNervosAddress{ TW::Nervos::Address(s) }; + } catch (...) { + return nullptr; + } +} + +void TWNervosAddressDelete(struct TWNervosAddress *_Nonnull address) { + delete address; +} + +TWString *_Nonnull TWNervosAddressDescription(struct TWNervosAddress *_Nonnull address) { + return TWStringCreateWithUTF8Bytes(address->impl.string().c_str()); +} + +TWData *_Nonnull TWNervosAddressCodeHash(struct TWNervosAddress *_Nonnull address) { + return TWDataCreateWithBytes(address->impl.codeHash.data(), address->impl.codeHash.size()); +} + +TWString *_Nonnull TWNervosAddressHashType(struct TWNervosAddress *_Nonnull address) { + return TWStringCreateWithUTF8Bytes(address->impl.hashTypeString().c_str()); +} + +TWData *_Nonnull TWNervosAddressArgs(struct TWNervosAddress *_Nonnull address) { + return TWDataCreateWithBytes(address->impl.args.data(), address->impl.args.size()); +} diff --git a/src/interface/TWPBKDF2.cpp b/src/interface/TWPBKDF2.cpp index 2ea0e4be9ec..3fae3225482 100644 --- a/src/interface/TWPBKDF2.cpp +++ b/src/interface/TWPBKDF2.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include diff --git a/src/interface/TWPrivateKey.cpp b/src/interface/TWPrivateKey.cpp index e6d2a846d52..37483dca65d 100644 --- a/src/interface/TWPrivateKey.cpp +++ b/src/interface/TWPrivateKey.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "../PrivateKey.h" #include "../PublicKey.h" @@ -11,14 +9,15 @@ #include #include #include +#include #include using namespace TW; struct TWPrivateKey *TWPrivateKeyCreate() { - Data bytes(PrivateKey::size); - random_buffer(bytes.data(), PrivateKey::size); + Data bytes(PrivateKey::_size); + random_buffer(bytes.data(), PrivateKey::_size); if (!PrivateKey::isValid(bytes)) { // Under no circumstance return an invalid private key. We'd rather // crash. This also captures cases where the random generator fails @@ -62,40 +61,31 @@ TWData *TWPrivateKeyData(struct TWPrivateKey *_Nonnull pk) { } struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyNist256p1(struct TWPrivateKey *_Nonnull pk) { - return new TWPublicKey{ pk->impl.getPublicKey(TWPublicKeyTypeNIST256p1) }; + return TWPrivateKeyGetPublicKeyByType(pk, TWPublicKeyTypeNIST256p1); } struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeySecp256k1(struct TWPrivateKey *_Nonnull pk, bool compressed) { if (compressed) { - return new TWPublicKey{ pk->impl.getPublicKey(TWPublicKeyTypeSECP256k1) }; + return TWPrivateKeyGetPublicKeyByType(pk, TWPublicKeyTypeSECP256k1); } else { - return new TWPublicKey{ pk->impl.getPublicKey(TWPublicKeyTypeSECP256k1Extended) }; + return TWPrivateKeyGetPublicKeyByType(pk, TWPublicKeyTypeSECP256k1Extended); } } struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyEd25519(struct TWPrivateKey *_Nonnull pk) { - return new TWPublicKey{ pk->impl.getPublicKey(TWPublicKeyTypeED25519) }; + return TWPrivateKeyGetPublicKeyByType(pk, TWPublicKeyTypeED25519); } struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyEd25519Blake2b(struct TWPrivateKey *_Nonnull pk) { - return new TWPublicKey{ pk->impl.getPublicKey(TWPublicKeyTypeED25519Blake2b) }; + return TWPrivateKeyGetPublicKeyByType(pk, TWPublicKeyTypeED25519Blake2b); } -struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyEd25519Extended(struct TWPrivateKey *_Nonnull pk) { - return new TWPublicKey{ pk->impl.getPublicKey(TWPublicKeyTypeED25519Extended) }; +struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyEd25519Cardano(struct TWPrivateKey *_Nonnull pk) { + return TWPrivateKeyGetPublicKeyByType(pk, TWPublicKeyTypeED25519Cardano); } struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyCurve25519(struct TWPrivateKey *_Nonnull pk) { - return new TWPublicKey{pk->impl.getPublicKey(TWPublicKeyTypeCURVE25519)}; -} - -TWData *_Nullable TWPrivateKeyGetSharedKey(const struct TWPrivateKey *_Nonnull pk, const struct TWPublicKey *_Nonnull publicKey, enum TWCurve curve) { - auto result = pk->impl.getSharedKey(publicKey->impl, curve); - if (result.empty()) { - return nullptr; - } else { - return TWDataCreateWithBytes(result.data(), result.size()); - } + return TWPrivateKeyGetPublicKeyByType(pk, TWPublicKeyTypeCURVE25519); } TWData *TWPrivateKeySign(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull digest, enum TWCurve curve) { @@ -108,9 +98,9 @@ TWData *TWPrivateKeySign(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull dige } } -TWData *TWPrivateKeySignAsDER(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull digest, enum TWCurve curve) { +TWData* TWPrivateKeySignAsDER(struct TWPrivateKey* pk, TWData* digest) { auto& d = *reinterpret_cast(digest); - auto result = pk->impl.signAsDER(d, curve); + auto result = pk->impl.signAsDER(d); if (result.empty()) { return nullptr; } else { @@ -118,9 +108,9 @@ TWData *TWPrivateKeySignAsDER(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull } } -TWData *TWPrivateKeySignSchnorr(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull message, enum TWCurve curve) { +TWData *TWPrivateKeySignZilliqaSchnorr(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull message) { const auto& msg = *reinterpret_cast(message); - auto result = pk->impl.signSchnorr(msg, curve); + auto result = pk->impl.signZilliqa(msg); if (result.empty()) { return nullptr; @@ -128,3 +118,11 @@ TWData *TWPrivateKeySignSchnorr(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnu return TWDataCreateWithBytes(result.data(), result.size()); } } + +struct TWPublicKey* TWPrivateKeyGetPublicKey(struct TWPrivateKey* pk, enum TWCoinType coinType) { + return TWPrivateKeyGetPublicKeyByType(pk, TWCoinTypePublicKeyType(coinType)); +} + +struct TWPublicKey* TWPrivateKeyGetPublicKeyByType(struct TWPrivateKey* pk, enum TWPublicKeyType pubkeyType) { + return new TWPublicKey{ pk->impl.getPublicKey(pubkeyType) }; +} diff --git a/src/interface/TWPublicKey.cpp b/src/interface/TWPublicKey.cpp index 4d08f0a2d50..6459b947a05 100644 --- a/src/interface/TWPublicKey.cpp +++ b/src/interface/TWPublicKey.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include @@ -60,10 +58,10 @@ bool TWPublicKeyVerifyAsDER(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull si return pk->impl.verifyAsDER(s, m); } -bool TWPublicKeyVerifySchnorr(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message) { +bool TWPublicKeyVerifyZilliqaSchnorr(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message) { const auto& s = *reinterpret_cast(signature); const auto& m = *reinterpret_cast(message); - return pk->impl.verifySchnorr(s, m); + return pk->impl.verifyZilliqa(s, m); } enum TWPublicKeyType TWPublicKeyKeyType(struct TWPublicKey *_Nonnull publicKey) { diff --git a/src/interface/TWRippleXAddress.cpp b/src/interface/TWRippleXAddress.cpp index 464e88bafa1..4c74dae0fb6 100644 --- a/src/interface/TWRippleXAddress.cpp +++ b/src/interface/TWRippleXAddress.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../Ripple/XAddress.h" +#include "../XRP/XAddress.h" #include #include @@ -13,7 +11,6 @@ #include using namespace TW; -using namespace TW::Ripple; bool TWRippleXAddressEqual(struct TWRippleXAddress *_Nonnull lhs, struct TWRippleXAddress *_Nonnull rhs) { return lhs->impl == rhs->impl; @@ -21,13 +18,13 @@ bool TWRippleXAddressEqual(struct TWRippleXAddress *_Nonnull lhs, struct TWRippl bool TWRippleXAddressIsValidString(TWString *_Nonnull string) { auto* s = reinterpret_cast(string); - return XAddress::isValid(*s); + return Ripple::XAddress::isValid(*s); } struct TWRippleXAddress *_Nullable TWRippleXAddressCreateWithString(TWString *_Nonnull string) { auto* s = reinterpret_cast(string); try { - const auto address = XAddress(*s); + const auto address = Ripple::XAddress(*s); return new TWRippleXAddress{ std::move(address) }; } catch (...) { return nullptr; @@ -35,7 +32,7 @@ struct TWRippleXAddress *_Nullable TWRippleXAddressCreateWithString(TWString *_N } struct TWRippleXAddress *_Nonnull TWRippleXAddressCreateWithPublicKey(struct TWPublicKey *_Nonnull publicKey, const uint32_t tag) { - return new TWRippleXAddress{ XAddress(publicKey->impl, tag) }; + return new TWRippleXAddress{ Ripple::XAddress(publicKey->impl, tag) }; } void TWRippleXAddressDelete(struct TWRippleXAddress *_Nonnull address) { diff --git a/src/interface/TWSegwitAddress.cpp b/src/interface/TWSegwitAddress.cpp index 22ce2f1da7f..5d74bdb3716 100644 --- a/src/interface/TWSegwitAddress.cpp +++ b/src/interface/TWSegwitAddress.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "../Bitcoin/SegwitAddress.h" @@ -13,7 +11,6 @@ #include using namespace TW; -using namespace TW::Bitcoin; bool TWSegwitAddressEqual(struct TWSegwitAddress *_Nonnull lhs, struct TWSegwitAddress *_Nonnull rhs) { return lhs->impl == rhs->impl; @@ -21,12 +18,12 @@ bool TWSegwitAddressEqual(struct TWSegwitAddress *_Nonnull lhs, struct TWSegwitA bool TWSegwitAddressIsValidString(TWString *_Nonnull string) { auto* s = reinterpret_cast(string); - return SegwitAddress::isValid(*s); + return Bitcoin::SegwitAddress::isValid(*s); } struct TWSegwitAddress *_Nullable TWSegwitAddressCreateWithString(TWString *_Nonnull string) { auto* s = reinterpret_cast(string); - auto dec = SegwitAddress::decode(*s); + auto dec = Bitcoin::SegwitAddress::decode(*s); if (!std::get<2>(dec)) { return nullptr; } @@ -35,7 +32,7 @@ struct TWSegwitAddress *_Nullable TWSegwitAddressCreateWithString(TWString *_Non } struct TWSegwitAddress *_Nonnull TWSegwitAddressCreateWithPublicKey(enum TWHRP hrp, struct TWPublicKey *_Nonnull publicKey) { - const auto address = SegwitAddress(publicKey->impl, stringForHRP(hrp)); + const auto address = Bitcoin::SegwitAddress(publicKey->impl, stringForHRP(hrp)); return new TWSegwitAddress{ std::move(address) }; } diff --git a/src/interface/TWSolanaAddress.cpp b/src/interface/TWSolanaAddress.cpp index 023b9033ed8..41a03176174 100644 --- a/src/interface/TWSolanaAddress.cpp +++ b/src/interface/TWSolanaAddress.cpp @@ -1,32 +1,54 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include #include "Solana/Address.h" +#include -using namespace TW::Solana; using namespace TW; struct TWSolanaAddress* _Nullable TWSolanaAddressCreateWithString(TWString* _Nonnull string) { auto& str = *reinterpret_cast(string); - return new TWSolanaAddress{Address(str)}; + return new TWSolanaAddress{Solana::Address(str)}; } void TWSolanaAddressDelete(struct TWSolanaAddress* _Nonnull address) { delete address; } -TWString *_Nullable TWSolanaAddressDefaultTokenAddress(struct TWSolanaAddress* _Nonnull address, TWString* _Nonnull tokenMintAddress) { +TWString* _Nullable TWSolanaAddressDefaultTokenAddress(struct TWSolanaAddress* _Nonnull address, TWString* _Nonnull tokenMintAddress) { try { if (address == nullptr || tokenMintAddress == nullptr) { return nullptr; } - Address tokenMint = Address(TWStringUTF8Bytes(tokenMintAddress)); - std::string defaultAddress = address->impl.defaultTokenAddress(tokenMint).string(); - return TWStringCreateWithUTF8Bytes(defaultAddress.c_str()); + Rust::TWStringWrapper tokenMint = TWStringUTF8Bytes(tokenMintAddress); + Rust::TWStringWrapper mainAddress = address->impl.string(); + + Rust::TWStringWrapper newTokenAddress = Rust::tw_solana_address_default_token_address(mainAddress.get(), tokenMint.get()); + + if (!newTokenAddress) { + return nullptr; + } + return TWStringCreateWithUTF8Bytes(newTokenAddress.c_str()); + } catch (...) { + return nullptr; + } +} + +TWString* _Nullable TWSolanaAddressToken2022Address(struct TWSolanaAddress* _Nonnull address, TWString* _Nonnull tokenMintAddress) { + try { + if (address == nullptr || tokenMintAddress == nullptr) { + return nullptr; + } + Rust::TWStringWrapper tokenMintAddressWrapper = TWStringUTF8Bytes(tokenMintAddress); + Rust::TWStringWrapper mainAddress = address->impl.string(); + + Rust::TWStringWrapper newTokenAddress = Rust::tw_solana_address_token_2022_address(mainAddress.get(), tokenMintAddressWrapper.get()); + + if (!newTokenAddress) { + return nullptr; + } + return TWStringCreateWithUTF8Bytes(newTokenAddress.c_str()); } catch (...) { return nullptr; } diff --git a/src/interface/TWSolanaTransaction.cpp b/src/interface/TWSolanaTransaction.cpp new file mode 100644 index 00000000000..a585c9f7508 --- /dev/null +++ b/src/interface/TWSolanaTransaction.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TrustWalletCore/TWSolanaTransaction.h" +#include "DataVector.h" +#include "rust/Wrapper.h" + +using namespace TW; + +TWData *_Nonnull TWSolanaTransactionUpdateBlockhashAndSign(TWString *_Nonnull encodedTx, + TWString *_Nonnull recentBlockhash, + const struct TWDataVector *_Nonnull privateKeys) { + + auto& encodedTxRef = *reinterpret_cast(encodedTx); + auto& recentBlockhashRef = *reinterpret_cast(recentBlockhash); + + Rust::TWStringWrapper encodedTxStr = encodedTxRef; + Rust::TWStringWrapper recentBlockhashStr = recentBlockhashRef; + Rust::TWDataVectorWrapper privateKeysVec = createFromTWDataVector(privateKeys); + + Rust::TWDataWrapper output = Rust::tw_solana_transaction_update_blockhash_and_sign(encodedTxStr.get(), + recentBlockhashStr.get(), + privateKeysVec.get()); + + auto outputData = output.toDataOrDefault(); + return TWDataCreateWithBytes(outputData.data(), outputData.size()); +} diff --git a/src/interface/TWStarkExMessageSigner.cpp b/src/interface/TWStarkExMessageSigner.cpp new file mode 100644 index 00000000000..a17df64afc6 --- /dev/null +++ b/src/interface/TWStarkExMessageSigner.cpp @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "StarkEx/MessageSigner.h" + +TWString* _Nonnull TWStarkExMessageSignerSignMessage(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull message) { + try { + const auto signature = TW::StarkEx::MessageSigner::signMessage(privateKey->impl, TWStringUTF8Bytes(message)); + return TWStringCreateWithUTF8Bytes(signature.c_str()); + } catch (...) { + return TWStringCreateWithUTF8Bytes(""); + } +} + +bool TWStarkExMessageSignerVerifyMessage(const struct TWPublicKey* _Nonnull publicKey, TWString* _Nonnull message, TWString* _Nonnull signature) { + return TW::StarkEx::MessageSigner::verifyMessage(publicKey->impl, TWStringUTF8Bytes(message), TWStringUTF8Bytes(signature)); +} diff --git a/src/interface/TWStarkWare.cpp b/src/interface/TWStarkWare.cpp new file mode 100644 index 00000000000..a8f36656b12 --- /dev/null +++ b/src/interface/TWStarkWare.cpp @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include +#include +#include + +struct TWPrivateKey* TWStarkWareGetStarkKeyFromSignature(const struct TWDerivationPath* derivationPath, TWString* signature) { + using namespace TW; + const auto& ethSignatureStr = *reinterpret_cast(signature); + return new TWPrivateKey{ ImmutableX::getPrivateKeyFromRawSignature(parse_hex(ethSignatureStr), derivationPath->impl)}; +} diff --git a/src/interface/TWStoredKey.cpp b/src/interface/TWStoredKey.cpp index 42659dee95a..f756ce5e6b3 100644 --- a/src/interface/TWStoredKey.cpp +++ b/src/interface/TWStoredKey.cpp @@ -1,57 +1,72 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include "../Coin.h" -#include "../Data.h" +#include "Data.h" #include "../HDWallet.h" #include "../Keystore/StoredKey.h" #include #include -using namespace TW::Keystore; +namespace KeyStore = TW::Keystore; struct TWStoredKey* _Nullable TWStoredKeyLoad(TWString* _Nonnull path) { try { const auto& pathString = *reinterpret_cast(path); - return new TWStoredKey{ StoredKey::load(pathString) }; + return new TWStoredKey{ KeyStore::StoredKey::load(pathString) }; } catch (...) { return nullptr; } } -struct TWStoredKey* _Nonnull TWStoredKeyCreateLevel(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel) { +struct TWStoredKey* _Nonnull TWStoredKeyCreateLevelAndEncryption(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel, enum TWStoredKeyEncryption encryption) { const auto& nameString = *reinterpret_cast(name); const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); - return new TWStoredKey{ StoredKey::createWithMnemonicRandom(nameString, passwordData, encryptionLevel) }; + return new TWStoredKey{ KeyStore::StoredKey::createWithMnemonicRandom(nameString, passwordData, encryptionLevel, encryption) }; +} + +struct TWStoredKey* _Nonnull TWStoredKeyCreateLevel(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel) { + return TWStoredKeyCreateLevelAndEncryption(name, password, encryptionLevel, TWStoredKeyEncryptionAes128Ctr); +} + +struct TWStoredKey* _Nonnull TWStoredKeyCreateEncryption(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryption encryption) { + return TWStoredKeyCreateLevelAndEncryption(name, password, TWStoredKeyEncryptionLevelDefault, encryption); } struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password) { - return TWStoredKeyCreateLevel(name, password, TWStoredKeyEncryptionLevelDefault); + return TWStoredKeyCreateLevelAndEncryption(name, password, TWStoredKeyEncryptionLevelDefault, TWStoredKeyEncryptionAes128Ctr); } struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { + return TWStoredKeyImportPrivateKeyWithEncryption(privateKey, name, password, coin, TWStoredKeyEncryptionAes128Ctr); +} + +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyWithEncryption(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption) { try { const auto& privateKeyData = *reinterpret_cast(privateKey); const auto& nameString = *reinterpret_cast(name); const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); - return new TWStoredKey{ StoredKey::createWithPrivateKeyAddDefaultAddress(nameString, passwordData, coin, privateKeyData) }; + return new TWStoredKey{ KeyStore::StoredKey::createWithPrivateKeyAddDefaultAddress(nameString, passwordData, coin, privateKeyData, encryption) }; } catch (...) { return nullptr; } } struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { + return TWStoredKeyImportHDWalletWithEncryption(mnemonic, name, password, coin, TWStoredKeyEncryptionAes128Ctr); +} + + +struct TWStoredKey* _Nullable TWStoredKeyImportHDWalletWithEncryption(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption) { try { const auto& mnemonicString = *reinterpret_cast(mnemonic); const auto& nameString = *reinterpret_cast(name); const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); - return new TWStoredKey{ StoredKey::createWithMnemonicAddDefaultAddress(nameString, passwordData, mnemonicString, coin) }; + return new TWStoredKey{ KeyStore::StoredKey::createWithMnemonicAddDefaultAddress(nameString, passwordData, mnemonicString, coin, encryption) }; } catch (...) { return nullptr; } @@ -61,7 +76,7 @@ struct TWStoredKey* _Nullable TWStoredKeyImportJSON(TWData* _Nonnull json) { try { const auto& d = *reinterpret_cast(json); const auto parsed = nlohmann::json::parse(d); - return new TWStoredKey{ StoredKey::createWithJson(nlohmann::json::parse(d)) }; + return new TWStoredKey{ KeyStore::StoredKey::createWithJson(nlohmann::json::parse(d)) }; } catch (...) { return nullptr; } @@ -83,7 +98,7 @@ TWString* _Nonnull TWStoredKeyName(struct TWStoredKey* _Nonnull key) { } bool TWStoredKeyIsMnemonic(struct TWStoredKey* _Nonnull key) { - return key->impl.type == StoredKeyType::mnemonicPhrase; + return key->impl.type == KeyStore::StoredKeyType::mnemonicPhrase; } size_t TWStoredKeyAccountCount(struct TWStoredKey* _Nonnull key) { @@ -209,6 +224,14 @@ bool TWStoredKeyFixAddresses(struct TWStoredKey* _Nonnull key, TWData* _Nonnull } } +bool TWStoredKeyUpdateAddress(struct TWStoredKey* _Nonnull key, enum TWCoinType coin) { + try { + return key->impl.updateAddress(coin); + } catch (...) { + return false; + } +} + TWString* _Nullable TWStoredKeyEncryptionParameters(struct TWStoredKey* _Nonnull key) { if (!key->impl.id) { return nullptr; diff --git a/src/interface/TWString+Hex.cpp b/src/interface/TWString+Hex.cpp index 65021dcaeb9..f2802f8c6a8 100644 --- a/src/interface/TWString+Hex.cpp +++ b/src/interface/TWString+Hex.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include diff --git a/src/interface/TWString.cpp b/src/interface/TWString.cpp index 2812a1c94d1..3641660b8d8 100644 --- a/src/interface/TWString.cpp +++ b/src/interface/TWString.cpp @@ -1,11 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include +#include #include TWString *_Nonnull TWStringCreateWithUTF8Bytes(const char *_Nonnull bytes) { @@ -34,8 +33,12 @@ const char *_Nonnull TWStringUTF8Bytes(TWString *_Nonnull string) { } void TWStringDelete(TWString *_Nonnull string) { - auto* s = reinterpret_cast(string); - delete s; + auto *sConst = reinterpret_cast(string); + // `const_cast` is safe here despite that the pointer to the string is const + // but `std::string` is not a constant value. + auto *s = const_cast(sConst); + memzero(s->data(), s->size()); + delete sConst; } bool TWStringEqual(TWString *_Nonnull lhs, TWString *_Nonnull rhs) { diff --git a/src/interface/TWTONAddressConverter.cpp b/src/interface/TWTONAddressConverter.cpp new file mode 100644 index 00000000000..4113448ad24 --- /dev/null +++ b/src/interface/TWTONAddressConverter.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "Base64.h" +#include "rust/Wrapper.h" + +using namespace TW; + +TWString *_Nullable TWTONAddressConverterToBoc(TWString *_Nonnull address) { + auto& addressString = *reinterpret_cast(address); + + const Rust::TWStringWrapper addressRustStr = addressString; + const Rust::TWStringWrapper bocRustStr = Rust::tw_ton_address_converter_to_boc(addressRustStr.get()); + if (!bocRustStr) { + return nullptr; + } + + return TWStringCreateWithUTF8Bytes(bocRustStr.c_str()); +} + +TWString *_Nullable TWTONAddressConverterFromBoc(TWString *_Nonnull boc) { + auto& bocEncoded = *reinterpret_cast(boc); + + const Rust::TWStringWrapper bocRustStr = bocEncoded; + const Rust::TWStringWrapper addressRustStr = Rust::tw_ton_address_converter_from_boc(bocRustStr.get()); + if (!addressRustStr) { + return nullptr; + } + + return TWStringCreateWithUTF8Bytes(addressRustStr.c_str()); +} + +TWString *_Nullable TWTONAddressConverterToUserFriendly(TWString *_Nonnull address, bool bounceable, bool testnet) { + auto& addressString = *reinterpret_cast(address); + + const Rust::TWStringWrapper addressRustStr = addressString; + const Rust::TWStringWrapper userFriendlyRustStr = Rust::tw_ton_address_converter_to_user_friendly(addressRustStr.get(), bounceable, testnet); + if (!userFriendlyRustStr) { + return nullptr; + } + + return TWStringCreateWithUTF8Bytes(userFriendlyRustStr.c_str()); +} diff --git a/src/interface/TWTONMessageSigner.cpp b/src/interface/TWTONMessageSigner.cpp new file mode 100644 index 00000000000..3795470fbbe --- /dev/null +++ b/src/interface/TWTONMessageSigner.cpp @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TrustWalletCore/TWTONMessageSigner.h" +#include "rust/Wrapper.h" +#include "PrivateKey.h" + +using namespace TW; + +TWString *_Nullable TWTONMessageSignerSignMessage(struct TWPrivateKey *_Nonnull privateKey, TWString* _Nonnull message) { + auto* privateKeyRustRaw = Rust::tw_private_key_create_with_data(privateKey->impl.bytes.data(), privateKey->impl.bytes.size()); + const auto privateKeyRust = std::shared_ptr(privateKeyRustRaw, Rust::tw_private_key_delete); + + Rust::TWStringWrapper messageRust = TWStringUTF8Bytes(message); + Rust::TWStringWrapper signature = Rust::tw_ton_message_signer_sign_message(privateKeyRust.get(), messageRust.get()); + + if (!signature) { + return nullptr; + } + return TWStringCreateWithUTF8Bytes(signature.c_str()); +} diff --git a/src/interface/TWTONWallet.cpp b/src/interface/TWTONWallet.cpp new file mode 100644 index 00000000000..7e6495491ab --- /dev/null +++ b/src/interface/TWTONWallet.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TrustWalletCore/TWTONWallet.h" +#include "rust/Wrapper.h" +#include "PublicKey.h" + +using namespace TW; + +TWString *_Nullable TWTONWalletBuildV4R2StateInit(struct TWPublicKey *_Nonnull publicKey, int32_t workchain, int32_t walletId) { + auto keyType = static_cast(TWPublicKeyKeyType(publicKey)); + auto* publicKeyRustRaw = Rust::tw_public_key_create_with_data(publicKey->impl.bytes.data(), publicKey->impl.bytes.size(), keyType); + const auto publicKeyRust = Rust::wrapTWPublicKey(publicKeyRustRaw); + + Rust::TWStringWrapper stateInit = Rust::tw_ton_wallet_build_v4_r2_state_init(publicKeyRust.get(), workchain, walletId); + if (!stateInit) { + return nullptr; + } + return TWStringCreateWithUTF8Bytes(stateInit.c_str()); +} diff --git a/src/interface/TWTezosMessageSigner.cpp b/src/interface/TWTezosMessageSigner.cpp new file mode 100644 index 00000000000..af09a2a2e77 --- /dev/null +++ b/src/interface/TWTezosMessageSigner.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "Tezos/MessageSigner.h" + +bool TWTezosMessageSignerVerifyMessage(const struct TWPublicKey* _Nonnull publicKey, TWString* _Nonnull message, TWString* _Nonnull signature) { + return TW::Tezos::MessageSigner::verifyMessage(publicKey->impl, TWStringUTF8Bytes(message), TWStringUTF8Bytes(signature)); +} + +TWString* _Nonnull TWTezosMessageSignerSignMessage(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull message) { + try { + const auto signature = TW::Tezos::MessageSigner::signMessage(privateKey->impl, TWStringUTF8Bytes(message)); + return TWStringCreateWithUTF8Bytes(signature.c_str()); + } catch (...) { + return TWStringCreateWithUTF8Bytes(""); + } +} + +TWString* TWTezosMessageSignerFormatMessage(TWString* _Nonnull message, TWString* _Nonnull url) { + const auto formatedMessage = TW::Tezos::MessageSigner::formatMessage(TWStringUTF8Bytes(message), TWStringUTF8Bytes(url)); + return TWStringCreateWithUTF8Bytes(formatedMessage.c_str()); +} + +TWString* TWTezosMessageSignerInputToPayload(TWString* message) { + const auto payload = TW::Tezos::MessageSigner::inputToPayload(TWStringUTF8Bytes(message)); + return TWStringCreateWithUTF8Bytes(payload.c_str()); +} diff --git a/src/interface/TWTransactionCompiler.cpp b/src/interface/TWTransactionCompiler.cpp index 5e8e72adba1..5db643063ee 100644 --- a/src/interface/TWTransactionCompiler.cpp +++ b/src/interface/TWTransactionCompiler.cpp @@ -1,59 +1,43 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include "TransactionCompiler.h" -#include "Data.h" -#include "uint256.h" +#include "DataVector.h" #include using namespace TW; - -TWData *_Nonnull TWTransactionCompilerBuildInput(enum TWCoinType coinType, TWString *_Nonnull from, TWString *_Nonnull to, TWString *_Nonnull amount, TWString *_Nonnull asset, TWString *_Nonnull memo, TWString *_Nonnull chainId) { +TWData *_Nonnull TWTransactionCompilerPreImageHashes(enum TWCoinType coinType, TWData *_Nonnull txInputData) { Data result; try { - result = TransactionCompiler::buildInput( - coinType, - std::string(TWStringUTF8Bytes(from)), - std::string(TWStringUTF8Bytes(to)), - std::string(TWStringUTF8Bytes(amount)), - std::string(TWStringUTF8Bytes(asset)), - std::string(TWStringUTF8Bytes(memo)), - std::string(TWStringUTF8Bytes(chainId)) - ); + assert(txInputData != nullptr); + const Data inputData = data(TWDataBytes(txInputData), TWDataSize(txInputData)); + + result = TransactionCompiler::preImageHashes(coinType, inputData); } catch (...) {} // return empty return TWDataCreateWithBytes(result.data(), result.size()); } -std::vector createFromTWDataVector(const struct TWDataVector* _Nonnull dataVector) { - std::vector ret; - const auto n = TWDataVectorSize(dataVector); - for (auto i = 0; i < n; ++i) { - auto elem = TWDataVectorGet(dataVector, i); - ret.push_back(*(static_cast(elem))); - TWDataDelete(elem); - } - return ret; -} - -TWData *_Nonnull TWTransactionCompilerPreImageHashes(enum TWCoinType coinType, TWData *_Nonnull txInputData) { +TWData *_Nonnull TWTransactionCompilerCompileWithSignatures(enum TWCoinType coinType, TWData *_Nonnull txInputData, const struct TWDataVector *_Nonnull signatures, const struct TWDataVector *_Nonnull publicKeys) { Data result; try { assert(txInputData != nullptr); const Data inputData = data(TWDataBytes(txInputData), TWDataSize(txInputData)); + assert(signatures != nullptr); + const auto signaturesVec = createFromTWDataVector(signatures); + assert(publicKeys != nullptr); + const auto publicKeysVec = createFromTWDataVector(publicKeys); - result = TransactionCompiler::preImageHashes(coinType, inputData); + result = TransactionCompiler::compileWithSignatures(coinType, inputData, signaturesVec, publicKeysVec); } catch (...) {} // return empty return TWDataCreateWithBytes(result.data(), result.size()); } -TWData *_Nonnull TWTransactionCompilerCompileWithSignatures(enum TWCoinType coinType, TWData *_Nonnull txInputData, const struct TWDataVector *_Nonnull signatures, const struct TWDataVector *_Nonnull publicKeys) { +TWData *_Nonnull TWTransactionCompilerCompileWithSignaturesAndPubKeyType(enum TWCoinType coinType, TWData *_Nonnull txInputData, const struct TWDataVector *_Nonnull signatures, const struct TWDataVector *_Nonnull publicKeys, enum TWPublicKeyType pubKeyType) { Data result; try { assert(txInputData != nullptr); @@ -63,7 +47,7 @@ TWData *_Nonnull TWTransactionCompilerCompileWithSignatures(enum TWCoinType coin assert(publicKeys != nullptr); const auto publicKeysVec = createFromTWDataVector(publicKeys); - result = TransactionCompiler::compileWithSignatures(coinType, inputData, signaturesVec, publicKeysVec); + result = TransactionCompiler::compileWithSignaturesAndPubKeyType(coinType, inputData, signaturesVec, publicKeysVec, pubKeyType); } catch (...) {} // return empty return TWDataCreateWithBytes(result.data(), result.size()); } diff --git a/src/interface/TWTransactionDecoder.cpp b/src/interface/TWTransactionDecoder.cpp new file mode 100644 index 00000000000..b8083494e42 --- /dev/null +++ b/src/interface/TWTransactionDecoder.cpp @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TrustWalletCore/TWTransactionDecoder.h" +#include "rust/Wrapper.h" + +using namespace TW; + +TWData *_Nonnull TWTransactionDecoderDecode(enum TWCoinType coinType, TWData *_Nonnull encodedTx) { + const Data& txData = *(reinterpret_cast(encodedTx)); + + const Rust::TWDataWrapper txDataPtr(txData); + const Rust::TWDataWrapper outputDataPtr = Rust::tw_transaction_decoder_decode(static_cast(coinType), txDataPtr.get()); + + const auto outputData = outputDataPtr.toDataOrDefault(); + return TWDataCreateWithBytes(outputData.data(), outputData.size()); +} diff --git a/src/interface/TWTranscationUtil.cpp b/src/interface/TWTranscationUtil.cpp new file mode 100644 index 00000000000..3f99dd81a83 --- /dev/null +++ b/src/interface/TWTranscationUtil.cpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TrustWalletCore/TWTransactionUtil.h" +#include "rust/Wrapper.h" + +using namespace TW; + +TWString* _Nullable TWTransactionUtilCalcTxHash(enum TWCoinType coinType, TWString* _Nonnull encodedTx) { + try { + if (encodedTx == nullptr) { + return nullptr; + } + const Rust::TWStringWrapper encodedTxWrapper = TWStringUTF8Bytes(encodedTx); + + const Rust::TWStringWrapper outputDataPtr = Rust::tw_transaction_util_calc_tx_hash(static_cast(coinType), encodedTxWrapper.get()); + if (!outputDataPtr) { + return nullptr; + } + + return TWStringCreateWithUTF8Bytes(outputDataPtr.c_str()); + } catch (...) { + return nullptr; + } +} diff --git a/src/interface/TWTronMessageSigner.cpp b/src/interface/TWTronMessageSigner.cpp new file mode 100644 index 00000000000..5fe47eba53b --- /dev/null +++ b/src/interface/TWTronMessageSigner.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "Tron/MessageSigner.h" + +TWString* _Nonnull TWTronMessageSignerSignMessage(const struct TWPrivateKey* _Nonnull privateKey, TWString* _Nonnull message) { + try { + const auto signature = TW::Tron::MessageSigner::signMessage(privateKey->impl, TWStringUTF8Bytes(message)); + return TWStringCreateWithUTF8Bytes(signature.c_str()); + } catch (...) { + return TWStringCreateWithUTF8Bytes(""); + } +} + +bool TWTronMessageSignerVerifyMessage(const struct TWPublicKey* _Nonnull publicKey, TWString* _Nonnull message, TWString* _Nonnull signature) { + return TW::Tron::MessageSigner::verifyMessage(publicKey->impl, TWStringUTF8Bytes(message), TWStringUTF8Bytes(signature)); +} diff --git a/src/interface/TWWalletConnectRequest.cpp b/src/interface/TWWalletConnectRequest.cpp new file mode 100644 index 00000000000..37fc49cbc87 --- /dev/null +++ b/src/interface/TWWalletConnectRequest.cpp @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "rust/Wrapper.h" + +using namespace TW; + +TWData* _Nonnull TWWalletConnectRequestParse(enum TWCoinType coin, TWData* _Nonnull input) { + const Data& inputData = *reinterpret_cast(input); + Rust::TWDataWrapper twInputData = inputData; + + Rust::TWDataWrapper twOutputData = Rust::tw_wallet_connect_request_parse(static_cast(coin), twInputData.get()); + auto outputData = twOutputData.toDataOrDefault(); + return TWDataCreateWithBytes(outputData.data(), outputData.size()); +} diff --git a/src/interface/TWWebAuthn.cpp b/src/interface/TWWebAuthn.cpp new file mode 100644 index 00000000000..7291ec0c7b8 --- /dev/null +++ b/src/interface/TWWebAuthn.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include "WebAuthn.h" +#include "AsnParser.h" + +struct TWPublicKey *_Nullable TWWebAuthnGetPublicKey(TWData *_Nonnull attestationObject) { + const auto& attestationObjectData = *reinterpret_cast(attestationObject); + const auto publicKey = TW::WebAuthn::getPublicKey(attestationObjectData); + if (publicKey.has_value()) { + return new TWPublicKey{ TW::PublicKey(publicKey.value()) }; + } else { + return nullptr; + } +} + +TWData *_Nonnull TWWebAuthnGetRSValues(TWData *_Nonnull signature) { + const auto& signatureData = *reinterpret_cast(signature); + const auto& rsValues = TW::ASN::AsnParser::ecdsa_signature_from_der(signatureData); + return TWDataCreateWithData(&rsValues); +} + +TWData *_Nonnull TWWebAuthnReconstructOriginalMessage(TWData* _Nonnull authenticatorData, TWData* _Nonnull clientDataJSON) { + const auto& authenticatorDataConverted = *reinterpret_cast(authenticatorData); + const auto& clientDataJSONConverted = *reinterpret_cast(clientDataJSON); + const auto& message = TW::WebAuthn::reconstructSignedMessage(authenticatorDataConverted, clientDataJSONConverted); + return TWDataCreateWithData(&message); +} diff --git a/src/memory/memzero_wrapper.h b/src/memory/memzero_wrapper.h new file mode 100644 index 00000000000..71af4291e78 --- /dev/null +++ b/src/memory/memzero_wrapper.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include + +#include + +#include + +namespace TW { + +template +static inline void memzero(T* data, std::size_t len = sizeof(T)) noexcept { + static_assert(std::is_trivial_v, "type should be a pod"); + ::memzero(data, len); +} + +} // namespace TW diff --git a/src/operators/equality_comparable.h b/src/operators/equality_comparable.h new file mode 100644 index 00000000000..4309afbc1ff --- /dev/null +++ b/src/operators/equality_comparable.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +namespace TW::operators::details { + +template +class empty_base {}; + +} // namespace TW::operators::details + +namespace TW { + +template > +struct equality_comparable : B { + friend bool operator!=(const T& x, const T& y) { return !(x == y); } +}; + +} // namespace TW diff --git a/src/proto/.clang-tidy b/src/proto/.clang-tidy new file mode 100644 index 00000000000..2c22f7387dd --- /dev/null +++ b/src/proto/.clang-tidy @@ -0,0 +1,6 @@ +--- +InheritParentConfig: false +Checks: '-*,misc-definitions-in-headers' +CheckOptions: + - { key: HeaderFileExtensions, value: "x" } +... diff --git a/src/proto/Aeternity.proto b/src/proto/Aeternity.proto index 8a20764369a..98c5fe5b28d 100644 --- a/src/proto/Aeternity.proto +++ b/src/proto/Aeternity.proto @@ -11,8 +11,10 @@ message SigningInput { // Address of the recipient with "ak_" prefix string to_address = 2; + // Amount (uint256, serialized big endian) bytes amount = 3; + // Fee amount (uint256, serialized big endian) bytes fee = 4; // Message, optional @@ -21,15 +23,18 @@ message SigningInput { // Time to live until block height uint64 ttl = 6; + // Nonce (should be larger than in the last transaction of the account) uint64 nonce = 7; + // The secret private key used for signing (32 bytes). bytes private_key = 8; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes, Base64 with checksum string encoded = 1; + // Signature, Base58 with checksum string signature = 2; } diff --git a/src/proto/Aion.proto b/src/proto/Aion.proto index 771e6774171..f4ac15ce4e8 100644 --- a/src/proto/Aion.proto +++ b/src/proto/Aion.proto @@ -3,38 +3,46 @@ syntax = "proto3"; package TW.Aion.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Input data necessary to create a signed transaction. message SigningInput { - // Nonce (256-bit number) + // Nonce (uint256, serialized big endian) bytes nonce = 1; - // Gas price (256-bit number) + // Gas price (uint256, serialized big endian) bytes gas_price = 2; - // Gas limit (256-bit number) + // Gas limit (uint256, serialized big endian) bytes gas_limit = 3; // Recipient's address. string to_address = 4; - // Amount to send in wei (256-bit number) + // Amount to send in wei (uint256, serialized big endian) bytes amount = 5; // Optional payload bytes payload = 6; - // Private key. + // The secret private key used for signing (32 bytes). bytes private_key = 7; // Timestamp uint64 timestamp = 8; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; // Signature. bytes signature = 2; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 3; + + // error description + string error_message = 4; } diff --git a/src/proto/Algorand.proto b/src/proto/Algorand.proto index 937493e51a9..744a2562070 100644 --- a/src/proto/Algorand.proto +++ b/src/proto/Algorand.proto @@ -3,18 +3,32 @@ syntax = "proto3"; package TW.Algorand.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + +// Simple transfer message, transfer an amount to an address message Transfer { + // Destination address (string) string to_address = 1; + + // Amount uint64 amount = 2; } +// Asset Transfer message, with assetID message AssetTransfer { + // Destination address (string) string to_address = 1; + + // Amount uint64 amount = 2; + + // ID of the asset being transferred uint64 asset_id = 3; } +// Opt-in message for an asset message AssetOptIn { + // ID of the asset uint64 asset_id = 1; } @@ -22,19 +36,28 @@ message AssetOptIn { message SigningInput { // network / chain id string genesis_id = 1; + // network / chain hash bytes genesis_hash = 2; + // binary note data bytes note = 3; - // private key + + // The secret private key used for signing (32 bytes). bytes private_key = 4; + // network / first round uint64 first_round = 5; + // network / last round uint64 last_round = 6; - // fee + + // fee amount uint64 fee = 7; + // public key + bytes public_key = 8; + // message payload oneof message_oneof { Transfer transfer = 10; AssetTransfer asset_transfer = 11; @@ -42,8 +65,17 @@ message SigningInput { } } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; + + // Signature in base64. + string signature = 2; + + // Error code, 0 is ok, other codes will be treated as errors. + Common.Proto.SigningError error = 3; + + // Error description. + string error_message = 4; } diff --git a/src/proto/Aptos.proto b/src/proto/Aptos.proto new file mode 100644 index 00000000000..6323e2e16d1 --- /dev/null +++ b/src/proto/Aptos.proto @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +syntax = "proto3"; + +package TW.Aptos.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// Necessary fields to process a TransferMessage +message TransferMessage { + // Destination Account address (string) + string to = 1; + // Amount to be transferred (uint64) + uint64 amount = 2; +} + +// Necessary tag for type function argument +message StructTag { + // Address of the account + string account_address = 1; + // Module name + string module = 2; + // Identifier + string name = 3; +} + +// Necessary fields to process a `0x1::coin::transfer` function. +message TokenTransferMessage { + // Destination Account address (string) + string to = 1; + // Amount to be transferred (uint64) + uint64 amount = 2; + // token function to call, e.g BTC: 0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9::coins::BTC + StructTag function = 3; +} + +// Necessary fields to process a `0x1::aptos_account::transfer_coins` function. +// Can be used to transfer tokens with registering the recipient account if needed. +message TokenTransferCoinsMessage { + // Destination Account address (string) + string to = 1; + // Amount to be transferred (uint64) + uint64 amount = 2; + // token function to call, e.g BTC: 0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9::coins::BTC + StructTag function = 3; +} + +// Necessary fields to process a ManagedTokensRegisterMessage +message ManagedTokensRegisterMessage { + // token function to register, e.g BTC: 0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9::coins::BTC + StructTag function = 1; +} + +// Necessary fields to process a CreateAccountMessage +message CreateAccountMessage { + // auth account address to create + string auth_key = 1; +} + +// Necessary fields to process an OfferNftMessage +message OfferNftMessage { + // Receiver address + string receiver = 1; + // Creator address + string creator = 2; + // Name of the collection + string collectionName = 3; + // Name of the NFT + string name = 4; + // Property version (should be often 0) + uint64 property_version = 5; + // Amount of NFT's to transfer (should be often 1) + uint64 amount = 6; +} + +// Necessary fields to process an CancelOfferNftMessage +message CancelOfferNftMessage { + // Receiver address + string receiver = 1; + // Creator address + string creator = 2; + // Name of the collection + string collectionName = 3; + // Name of the NFT + string name = 4; + // Property version (should be often 0) + uint64 property_version = 5; +} + +// Necessary fields to process an ClaimNftMessage +message ClaimNftMessage { + // Sender address + string sender = 1; + // Creator address + string creator = 2; + // Name of the collection + string collectionName = 3; + // Name of the NFT + string name = 4; + // Property version (should be often 0) + uint64 property_version = 5; +} + +message TortugaClaim { + // idx of ticket to claim + uint64 idx = 1; +} + +message TortugaStake { + // Amount to be stake + uint64 amount = 1; +} + +message TortugaUnstake { + // Amount to be stake + uint64 amount = 1; +} + +message LiquidStaking { + // Smart contract address of liquid staking module + string smart_contract_address = 1; + + oneof liquid_stake_transaction_payload { + TortugaStake stake = 2; + TortugaUnstake unstake = 3; + TortugaClaim claim = 4; + } +} + +message NftMessage { + oneof nft_transaction_payload { + OfferNftMessage offer_nft = 1; + CancelOfferNftMessage cancel_offer_nft = 2; + ClaimNftMessage claim_nft = 3; + } +} + +// Input data necessary to create a signed transaction. +message SigningInput { + // Sender Account address (string) + string sender = 1; + // Sequence number, incremented atomically for each tx present on the account, start at 0 (int64) + int64 sequence_number = 2; + // Max gas amount that the user is willing to pay (uint64) + uint64 max_gas_amount = 3; + // Gas unit price - queried through API (uint64) + uint64 gas_unit_price = 4; + // Expiration timestamp for the transaction, can't be in the past (uint64) + uint64 expiration_timestamp_secs = 5; + // Chain id 1 (mainnet) 32(devnet) (uint32 - casted in uint8_t later) + uint32 chain_id = 6; + // Private key to sign the transaction (bytes) + bytes private_key = 7; + // hex encoded function to sign, use it for smart contract approval (string) + string any_encoded = 8; + + oneof transaction_payload { + TransferMessage transfer = 9; + TokenTransferMessage token_transfer = 10; + CreateAccountMessage create_account = 11; + NftMessage nft_message = 12; + ManagedTokensRegisterMessage register_token = 13; + LiquidStaking liquid_staking_message = 14; + TokenTransferCoinsMessage token_transfer_coins = 15; + } +} + +// Information related to the signed transaction +message TransactionAuthenticator { + // Signature part of the signed transaction (bytes) + bytes signature = 1; + // Public key of the signer (bytes) + bytes public_key = 2; +} + +// Transaction signing output. +message SigningOutput { + /// The raw transaction (bytes) + bytes raw_txn = 1; + + /// Public key and signature to authenticate + TransactionAuthenticator authenticator = 2; + + /// Signed and encoded transaction bytes. + bytes encoded = 3; + + // Transaction json format for api broadcasting (string) + string json = 4; + + // Error code, 0 is ok, other codes will be treated as errors. + Common.Proto.SigningError error = 5; + + // Error description. + string error_message = 6; +} diff --git a/src/proto/Barz.proto b/src/proto/Barz.proto new file mode 100644 index 00000000000..6c8342054e7 --- /dev/null +++ b/src/proto/Barz.proto @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +syntax = "proto3"; + +package TW.Barz.Proto; +option java_package = "wallet.core.jni.proto"; + +// Input parameters for calculating a counterfactual address for ERC-4337 based smart contract wallet +message ContractAddressInput { + // ERC-4337 entry point + string entry_point = 1; + // Address of the contract factory + string factory = 2; + + // Diamond proxy facets required for the contract setup + string account_facet = 3; + string verification_facet = 4; + string facet_registry = 5; + string default_fallback = 6; + + // Bytecode of the smart contract to deploy + string bytecode = 7; + // PublicKey of the wallet + string public_key = 8; + + // Salt is used to derive multiple account from the same public key + uint32 salt = 9; +} + +// FacetCutAction represents the action to be performed for a FacetCut +enum FacetCutAction { + ADD = 0; + REPLACE = 1; + REMOVE = 2; +} + +// FacetCut represents a single operation to be performed on a facet +message FacetCut { + string facet_address = 1; // The address of the facet + FacetCutAction action = 2; // The action to perform + repeated bytes function_selectors = 3; // List of function selectors, each is bytes4 +} + +// DiamondCutInput represents the input parameters for a diamondCut operation +message DiamondCutInput { + repeated FacetCut facet_cuts = 1; // List of facet cuts to apply + string init_address = 2; // Address to call with `init` data after applying cuts + bytes init_data = 3; // Data to pass to `init` function call +} + diff --git a/src/proto/Binance.proto b/src/proto/Binance.proto index 375c1ec7586..7fe597ee92e 100644 --- a/src/proto/Binance.proto +++ b/src/proto/Binance.proto @@ -5,140 +5,258 @@ option java_package = "wallet.core.jni.proto"; import "Common.proto"; +// Transaction structure, used internally message Transaction { - // int64 SIZE-OF-ENCODED // varint encoded length of the structure after encoding - // 0xF0625DEE // prefix - repeated bytes msgs = 1; // array of size 1, containing the transaction message, which are one of the transaction type below - repeated bytes signatures = 2; // array of size 1, containing the standard signature structure of the transaction sender - string memo = 3; // a short sentence of remark for the transaction, only for `Transfer` transactions. - int64 source = 4; // an identifier for tools triggerring this transaction, set to zero if unwilling to disclose. - bytes data = 5; // reserved for future use + // array of size 1, containing the transaction message, which are one of the transaction type below + repeated bytes msgs = 1; + + // array of size 1, containing the standard signature structure of the transaction sender + repeated bytes signatures = 2; + + // a short sentence of remark for the transaction, only for `Transfer` transactions. + string memo = 3; + + // an identifier for tools triggering this transaction, set to zero if unwilling to disclose. + int64 source = 4; + + // reserved for future use + bytes data = 5; } +// Signature structure, used internally message Signature { - message PubKey { - // 0xEB5AE987 // prefix - // bytes // public key bytes - } - bytes pub_key = 1; // public key bytes of the signer address - bytes signature = 2; // signature bytes, please check chain access section for signature generation - int64 account_number = 3; // another identifier of signer, which can be read from chain by account REST API or RPC - int64 sequence = 4; // sequence number for the next transaction + // public key bytes of the signer address + bytes pub_key = 1; + + // signature bytes, please check chain access section for signature generation + bytes signature = 2; + + // another identifier of signer, which can be read from chain by account REST API or RPC + int64 account_number = 3; + + // sequence number for the next transaction + int64 sequence = 4; } +// Message for Trade order message TradeOrder { - // 0xCE6DC043 // prefix - bytes sender = 1; // originating address - string id = 2; // order id, optional - string symbol = 3; // symbol for trading pair in full name of the tokens - int64 ordertype = 4; // only accept 2 for now, meaning limit order - int64 side = 5; // 1 for buy and 2 fory sell - int64 price = 6; // price of the order, which is the real price multiplied by 1e8 (10^8) and rounded to integer - int64 quantity = 7; // quantity of the order, which is the real price multiplied by 1e8 (10^8) and rounded to integer - int64 timeinforce = 8; // 1 for Good Till Expire(GTE) order and 3 for Immediate Or Cancel (IOC) + // originating address + bytes sender = 1; + + // order id, optional + string id = 2; + + // symbol for trading pair in full name of the tokens + string symbol = 3; + + // only accept 2 for now, meaning limit order + int64 ordertype = 4; + + // 1 for buy and 2 for sell + int64 side = 5; + + // price of the order, which is the real price multiplied by 1e8 (10^8) and rounded to integer + int64 price = 6; + + // quantity of the order, which is the real price multiplied by 1e8 (10^8) and rounded to integer + int64 quantity = 7; + + // 1 for Good Till Expire(GTE) order and 3 for Immediate Or Cancel (IOC) + int64 timeinforce = 8; } +// Message for CancelTrade order message CancelTradeOrder { - // 0x166E681B // prefix - bytes sender = 1; // originating address - string symbol = 2; // symbol for trading pair in full name of the tokens - string refid = 3; // order id to cancel + // originating address + bytes sender = 1; + + // symbol for trading pair in full name of the tokens + string symbol = 2; + + // order id to cancel + string refid = 3; } +// Message for Send order message SendOrder { - // 0x2A2C87FA - // A symbol-amount pair. Could be moved out of SendOrder; kept here for backward compatibility. + // A token amount, symbol-amount pair. Could be moved out of SendOrder; kept here for backward compatibility. message Token { + // Token ID string denom = 1; + + // Amount int64 amount = 2; } + + // Transaction input message Input { + // source address bytes address = 1; + + // input coin amounts repeated Token coins = 2; } + + // Transaction output message Output { + // destination address bytes address = 1; + + // output coin amounts repeated Token coins = 2; } + + // Send inputs repeated Input inputs = 1; + + // Send outputs repeated Output outputs = 2; } +// Message for TokenIssue order message TokenIssueOrder { - // 0x17EFAB80 // prefix - bytes from = 1; // owner address - string name = 2; // token name - string symbol = 3; // token symbol, in full name with "-" suffix - int64 total_supply = 4; // total supply - bool mintable = 5; // mintable + // owner address + bytes from = 1; + + // token name + string name = 2; + + // token symbol, in full name with "-" suffix + string symbol = 3; + + // total supply + int64 total_supply = 4; + + // mintable + bool mintable = 5; } +// Message for TokenMint order message TokenMintOrder { - // 0x467E0829 // prefix - bytes from = 1; // owner address - string symbol = 2; // token symbol, in full name with "-" suffix - int64 amount = 3; // amount to mint + // owner address + bytes from = 1; + + // token symbol, in full name with "-" suffix + string symbol = 2; + + // amount to mint + int64 amount = 3; } +// Message for TokenBurn order message TokenBurnOrder { - // 0x7ED2D2A0 // prefix - bytes from = 1; // owner address - string symbol = 2; // token symbol, in full name with "-" suffix - int64 amount = 3; // amount to burn + // owner address + bytes from = 1; + + // token symbol, in full name with "-" suffix + string symbol = 2; + + // amount to burn + int64 amount = 3; } +// Message for TokenFreeze order message TokenFreezeOrder { - // 0xE774B32D // prefix - bytes from = 1; // owner address - string symbol = 2; // token symbol, in full name with "-" suffix - int64 amount = 3; // amount of token to freeze + // owner address + bytes from = 1; + + // token symbol, in full name with "-" suffix + string symbol = 2; + + // amount of token to freeze + int64 amount = 3; } +// Message for TokenUnfreeze order message TokenUnfreezeOrder { - // 0x6515FF0D // prefix - bytes from = 1; // owner address - string symbol = 2; // token symbol, in full name with "-" suffix - int64 amount = 3; // amount of token to unfreeze + // owner address + bytes from = 1; + + // token symbol, in full name with "-" suffix + string symbol = 2; + + // amount of token to unfreeze + int64 amount = 3; } +// Message for HashTimeLock order message HTLTOrder { - // 0xB33F9A24 // prefix - bytes from = 1; // signer address - bytes to = 2; // recipient address + // signer address + bytes from = 1; + + // recipient address + bytes to = 2; + + // source on other chain, optional string recipient_other_chain = 3; + + // recipient on other chain, optional string sender_other_chain = 4; - bytes random_number_hash = 5; //hash of a random number and timestamp, based on SHA256 + + // hash of a random number and timestamp, based on SHA256 + bytes random_number_hash = 5; + + // timestamp int64 timestamp = 6; + + // amounts repeated SendOrder.Token amount = 7; - string expected_income = 8; // expected gained token on the other chain + + // expected gained token on the other chain + string expected_income = 8; + + // period expressed in block heights int64 height_span = 9; + + // set for cross-chain send bool cross_chain = 10; } +// Message for Deposit HTLT order message DepositHTLTOrder { - // 0xB33F9A24 // prefix - bytes from = 1; // signer address + // signer address + bytes from = 1; + + // amounts repeated SendOrder.Token amount = 2; + + // swap ID bytes swap_id = 3; } +// Message for Claim HTLT order message ClaimHTLOrder { - // 0xC1665300 // prefix - bytes from = 1; // signer address + // signer address + bytes from = 1; + + // swap ID bytes swap_id = 2; + + // random number input bytes random_number = 3; } +// Message for Refund HTLT order message RefundHTLTOrder { - // 0x3454A27C // prefix - bytes from = 1; // signer address + // signer address + bytes from = 1; + + // swap ID bytes swap_id = 2; } +// Transfer message TransferOut { + // source address bytes from = 1; + + // recipient address bytes to = 2; + + // transfer amount SendOrder.Token amount = 3; + + // expiration time int64 expire_time = 4; } @@ -164,37 +282,83 @@ message SideChainUndelegate { string chain_id = 4; } +// Message for BNB Beacon Chain -> BSC Stake Migration. +// https://github.com/bnb-chain/javascript-sdk/blob/26f6db8b67326e6214e74203ff90c89777b592a1/src/types/msg/stake/stakeMigrationMsg.ts#L13-L18 +message SideChainStakeMigration { + bytes validator_src_addr = 1; + bytes validator_dst_addr = 2; + bytes delegator_addr = 3; + bytes refund_addr = 4; + SendOrder.Token amount = 5; +} + +// Message for TimeLock order message TimeLockOrder { - bytes from_address = 1; // owner address + // owner address + bytes from_address = 1; + + // Description (optional) string description = 2; + // Array of symbol/amount pairs. see SDK https://github.com/binance-chain/javascript-sdk/blob/master/docs/api-docs/classes/tokenmanagement.md#timelock repeated SendOrder.Token amount = 3; + + // lock time int64 lock_time = 4; } +// Message for TimeRelock order message TimeRelockOrder { - bytes from_address = 1; // owner address - int64 id = 2; // order ID + // owner address + bytes from_address = 1; + + // order ID + int64 id = 2; + + // Description (optional) string description = 3; + // Array of symbol/amount pairs. repeated SendOrder.Token amount = 4; + + // lock time int64 lock_time = 5; } +// Message for TimeUnlock order message TimeUnlockOrder { - bytes from_address = 1; // owner address - int64 id = 2; // order ID + // owner address + bytes from_address = 1; + + // order ID + int64 id = 2; } -// Input data necessary to create a signed order. +// Input data necessary to create a signed transaction. message SigningInput { + // Chain ID string chain_id = 1; + + // Source account number int64 account_number = 2; + + // Sequence number (account specific) int64 sequence = 3; + + // Transaction source, see https://github.com/bnb-chain/BEPs/blob/master/BEP10.md + // Some important values: + // 0: Default source value (e.g. for Binance Chain Command Line, or SDKs) + // 1: Binance DEX Web Wallet + // 2: Trust Wallet int64 source = 4; + + // Optional memo string memo = 5; + + // The secret private key used for signing (32 bytes). bytes private_key = 6; + // Payload message oneof order_oneof { TradeOrder trade_order = 8; CancelTradeOrder cancel_trade_order = 9; @@ -215,17 +379,24 @@ message SigningInput { TimeLockOrder time_lock_order = 24; TimeRelockOrder time_relock_order = 25; TimeUnlockOrder time_unlock_order = 26; + SideChainStakeMigration side_stake_migration_order = 27; } } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; - /// error code, 0 is ok, other codes will be treated as errors + // OK (=0) or other codes in case of error Common.Proto.SigningError error = 2; - /// error description + // error description in case of error string error_message = 3; + + // Signature bytes. + bytes signature = 4; + + // Signature JSON string. + string signature_json = 5; } diff --git a/src/proto/Bitcoin.proto b/src/proto/Bitcoin.proto index 1e9e631284a..26fdba4c113 100644 --- a/src/proto/Bitcoin.proto +++ b/src/proto/Bitcoin.proto @@ -3,8 +3,10 @@ syntax = "proto3"; package TW.Bitcoin.Proto; option java_package = "wallet.core.jni.proto"; +import "BitcoinV2.proto"; import "Common.proto"; +// A transaction, with its inputs and outputs message Transaction { // Transaction data format version. sint32 version = 1; @@ -15,7 +17,7 @@ message Transaction { // A list of 1 or more transaction inputs or sources for coins. repeated TransactionInput inputs = 3; - // A list of 1 or more transaction outputs or destinations for coins + // A list of 1 or more transaction outputs or destinations for coins. repeated TransactionOutput outputs = 4; } @@ -33,7 +35,7 @@ message TransactionInput { // Bitcoin transaction out-point reference. message OutPoint { - // The hash of the referenced transaction. + // The hash of the referenced transaction (network byte order, usually needs to be reversed). bytes hash = 1; // The index of the specific output in the transaction. @@ -41,6 +43,9 @@ message OutPoint { // Transaction version as defined by the sender. uint32 sequence = 3; + + // The tree in utxo, only works for DCR + int32 tree = 4; } // Bitcoin transaction output. @@ -50,6 +55,9 @@ message TransactionOutput { // Usually contains the public key as a Bitcoin script setting up conditions to claim this output. bytes script = 2; + + // Optional spending script for P2TR script-path transactions. + bytes spendingScript = 5; } // An unspent transaction output, that can serve as input to a transaction @@ -62,6 +70,34 @@ message UnspentTransaction { // Amount of the UTXO int64 amount = 3; + + // The transaction variant + TransactionVariant variant = 4; + + // Optional spending script for P2TR script-path transactions. + bytes spendingScript = 5; +} + +enum TransactionVariant { + P2PKH = 0; + P2WPKH = 1; + P2TRKEYPATH = 2; + BRC20TRANSFER = 3; + NFTINSCRIPTION = 4; +} + +// Pair of destination address and amount, used for extra outputs +message OutputAddress { + // Destination address + string to_address = 1; + + // Amount to be paid to this output + int64 amount = 2; +} + +// Optional index of a corresponding output in the transaction. +message OutputIndex { + uint32 index = 1; } // Input data necessary to create a signed transaction. @@ -74,31 +110,31 @@ message SigningInput { // If amount is equal or more than the available amount, also max amount will be used. int64 amount = 2; - // Transaction fee per byte. + // Transaction fee rate, satoshis per byte, used to compute required fee (when planning) int64 byte_fee = 3; - // Recipient's address. + // Recipient's address, as string. string to_address = 4; - // Change address. + // Change address, as string. string change_address = 5; - // Available private keys. + // The available secret private key or keys required for signing (32 bytes each). repeated bytes private_key = 6; // Available redeem scripts indexed by script hash. map scripts = 7; - // Available unspent transaction outputs. + // Available input unspent transaction outputs. repeated UnspentTransaction utxo = 8; - // If sending max amount. + // Set if sending max amount is requested. bool use_max_amount = 9; - // Coin type (forks). + // Coin type (used by forks). uint32 coin_type = 10; - // Optional transaction plan + // Optional transaction plan. If missing, plan will be computed. TransactionPlan plan = 11; // Optional lockTime, default value 0 means no time locking. @@ -110,6 +146,33 @@ message SigningInput { // Optional zero-amount, OP_RETURN output bytes output_op_return = 13; + + // Optional index of the OP_RETURN output in the transaction. + // If not set, OP_RETURN output will be pushed as the latest output. + OutputIndex output_op_return_index = 26; + + // Optional additional destination addresses, additional to first to_address output + repeated OutputAddress extra_outputs = 14; + + // If use max utxo. + bool use_max_utxo = 15; + + // If disable dust filter. + bool disable_dust_filter = 16; + + // transaction creation time that will be used for verge(xvg) + uint32 time = 17; + + // If set, uses Bitcoin 2.0 Signing protocol. + // As a result, `Bitcoin.Proto.SigningOutput.signing_result_v2` is set. + BitcoinV2.Proto.SigningInput signing_v2 = 21; + + // One of the "Dust" amount policies. + // Later, we plan to add support for `DynamicDust` policy with a `min_relay_fee` amount. + oneof dust_policy { + // Use a constant "Dust" threshold. + int64 fixed_dust_threshold = 24; + } } // Describes a preliminary transaction plan. @@ -117,16 +180,16 @@ message TransactionPlan { // Amount to be received at the other end. int64 amount = 1; - // Maximum available amount. + // Maximum available amount in all the input UTXOs. int64 available_amount = 2; // Estimated transaction fee. int64 fee = 3; - // Change. + // Remaining change int64 change = 4; - // Selected unspent transaction outputs. + // Selected unspent transaction outputs (subset of all input UTXOs) repeated UnspentTransaction utxos = 5; // Zcash branch id @@ -137,25 +200,46 @@ message TransactionPlan { // Optional zero-amount, OP_RETURN output bytes output_op_return = 8; + + // Optional index of the OP_RETURN output in the transaction. + // If not set, OP_RETURN output will be pushed as the latest output. + OutputIndex output_op_return_index = 14; + + // zen & bitcoin diamond preblockhash + bytes preblockhash = 9; + + // zen preblockheight + int64 preblockheight = 10; + + // Result of a transaction planning using the Bitcoin 2.0 protocol. + // Set if `Bitcoin.Proto.SigningInput.planning_v2` used. + BitcoinV2.Proto.TransactionPlan planning_result_v2 = 12; }; -// Transaction signing output. +// Result containing the signed and encoded transaction. +// Note that the amount may be different than the requested amount to account for fees and available funds. message SigningOutput { - // Resulting transaction. Note that the amount may be different than the requested amount to account for fees and available funds. + // Resulting transaction. Transaction transaction = 1; // Signed and encoded transaction bytes. bytes encoded = 2; - // Transaction id + // Transaction ID (hash) string transaction_id = 3; // Optional error Common.Proto.SigningError error = 4; + // error description string error_message = 5; + + // Result of a transaction signing using the Bitcoin 2.0 protocol. + // Set if `Bitcoin.Proto.SigningInput.signing_v2` used. + BitcoinV2.Proto.SigningOutput signing_result_v2 = 7; } +/// Pre-image hash to be used for signing message HashPublicKey { /// Pre-image data hash that will be used for signing bytes data_hash = 1; @@ -174,4 +258,8 @@ message PreSigningOutput { /// error description string error_message = 3; -} \ No newline at end of file + + // Result of a transaction pre-signing using the Bitcoin 2.0 protocol. + // Set if `Bitcoin.Proto.SigningInput.signing_v2` used. + BitcoinV2.Proto.PreSigningOutput pre_signing_result_v2 = 7; +} diff --git a/src/proto/BitcoinV2.proto b/src/proto/BitcoinV2.proto new file mode 100644 index 00000000000..5f58e161174 --- /dev/null +++ b/src/proto/BitcoinV2.proto @@ -0,0 +1,399 @@ +syntax = "proto3"; + +package TW.BitcoinV2.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +enum InputSelector { + // Automatically select enough inputs in an ascending order to cover the outputs of the transaction. + SelectAscending = 0; + // Automatically select enough inputs in the given order to cover the outputs of the transaction. + SelectInOrder = 1; + // Automatically select enough inputs in an descending order to cover the outputs of the transaction. + SelectDescending = 2; + // Use all the inputs provided in the given order. + UseAll = 10; +} + +// Either a public key or public key hash. +message PublicKeyOrHash { + oneof variant { + // Public key bytes. + bytes pubkey = 1; + // Public key hash. + bytes hash = 2; + } +} + +// Bitcoin transaction out-point reference. +message OutPoint { + // The hash of the referenced transaction (network byte order, usually needs to be reversed). + // The referenced transaction ID in REVERSED order. + bytes hash = 1; + + // The position in the previous transactions output that this input references. + uint32 vout = 2; +} + +message Input { + // Reference to the previous transaction's output. + OutPoint out_point = 1; + // The amount of satoshis of this input. + int64 value = 2; + // The sighash type, normally `All`. + // See `TWBitcoinSigHashType` enum. + uint32 sighash_type = 3; + // Optional sequence number, used for timelocks, replace-by-fee, etc. + // Leave empty to use a default 4294967295 (0xFFFFFFFF) value. + Sequence sequence = 4; + + // Script for claiming this UTXO. + oneof claiming_script { + // Construct claiming script with a builder pattern. + InputBuilder script_builder = 5; + // Spending script pubkey data. + // Use this variant if the UTXO claiming script is known already, otherwise use `InputBuilder`. + // Please note that the signing method (eg "legacy" or "segwit") will be determined by parsing the script data as: + // - P2PK, P2PKH - legacy signing method; + // - P2WPKH - segwit signing method. + bytes script_data = 6; + // Derive a spending script pubkey from a receiver address. + // E.g "bc1" segwit address will be P2WPKH claiming script. + // TODO consider deprecating this because we can't determine if the script pubkey is P2PK or P2PKH actually. + string receiver_address = 7; + } + + // Optional sequence number, used for timelocks, replace-by-fee, etc. + message Sequence { + uint32 sequence = 1; + } + + message InputBuilder { + oneof variant { + // Pay-to-Script-Hash, specify the redeem script. + // Please note that we support standard redeem scripts only, such as P2PKH, P2WPKH, P2TR. + // TODO next iteration. + // bytes p2sh = 1; + + // Pay-to-Public-Key, specify the public key. + bytes p2pk = 2; + // Pay-to-Public-Key-Hash, specify the public key. + PublicKeyOrHash p2pkh = 3; + + // Pay-to-Witness-Script-Hash, specify the redeem script. + // TODO next iteration. + // bytes p2wsh = 4; + + // Pay-to-Public-Key-Hash, specify the public key. + PublicKeyOrHash p2wpkh = 5; + // Pay-to-Taproot-key-path (balance transfers), specify the public key. + bytes p2tr_key_path = 7; + + // Pay-to-Taproot-script-path (complex transfers). + // TODO next iteration. + // InputTaprootScriptPath p2tr_script_path = 8; + + // Create a BRC20 inscription. + InputBrc20Inscription brc20_inscribe = 9; + } + } + + message InputTaprootScriptPath { + // The payload of the Taproot transaction. + bytes payload = 2; + // The control block of the Taproot transaction required for claiming. + bytes control_block = 3; + } + + message InputBrc20Inscription { + // The recipient of the inscription, usually the sender. + bytes inscribe_to = 2; + // The ticker of the BRC20 inscription. + string ticker = 3; + // The BRC20 token transfer amount. + string transfer_amount = 4; + } +} + +message Output { + // The amount of satoshis to send. + int64 value = 1; + + oneof to_recipient { + // Construct output with builder pattern. + OutputBuilder builder = 2; + // Construct output by providing the scriptPubkey directly. + bytes custom_script_pubkey = 3; + // Derive the expected output from the provided address. + string to_address = 4; + } + + message OutputBuilder { + oneof variant { + // Pay-to-Script-Hash, specify the redeem script or its hash. + RedeemScriptOrHash p2sh = 1; + // Pay-to-Public-Key, specify the public key. + bytes p2pk = 2; + // Pay-to-Public-Key-Hash, specify the public key or its hash. + PublicKeyOrHash p2pkh = 3; + // Pay-to-Witness-Script-Hash, specify the redeem script or its hash. + RedeemScriptOrHash p2wsh = 4; + // Pay-to-Public-Key-Hash, specify the public key or its hash. + PublicKeyOrHash p2wpkh = 5; + // Pay-to-Taproot-key-path (balance transfers), specify the public key. + bytes p2tr_key_path = 6; + // Pay-to-Taproot-script-path (complex transfers) + OutputTaprootScriptPath p2tr_script_path = 7; + bytes p2tr_dangerous_assume_tweaked = 8; + OutputBrc20Inscription brc20_inscribe = 9; + // OP_RETURN output. In most cases, with a zero-amount. + bytes op_return = 12; + } + } + + // Either a redeem script or its hash. + message RedeemScriptOrHash { + oneof variant { + // Redeem script bytes. + bytes redeem_script = 1; + // Public key hash. + bytes hash = 2; + } + } + + message OutputTaprootScriptPath { + // The internal key, usually the public key of the recipient. + bytes internal_key = 1; + // The merkle root of the Taproot script(s), required to compute the sighash. + bytes merkle_root = 2; + } + + message OutputBrc20Inscription { + // The recipient of the inscription, usually the sender. + bytes inscribe_to = 1; + // The ticker of the BRC20 inscription. + string ticker = 2; + // The BRC20 token transfer amount. + string transfer_amount = 3; + } +} + +message ChainInfo { + // P2PKH prefix for this chain. + uint32 p2pkh_prefix = 1; + // P2SH prefix for this coin type. + uint32 p2sh_prefix = 2; + // HRP for this coin type if applicable. + string hrp = 3; +} + +enum TransactionVersion { + // V1 is used by default. + UseDefault = 0; + // Original transaction version. + V1 = 1; + // https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki#specification + V2 = 2; +} + +message SigningInput { + // Transaction version. + TransactionVersion version = 1; + // User private keys. + // Only required if the `sign` method is called. + repeated bytes private_keys = 2; + // User public keys. + // Only required if the `plan`, `preImageHash` methods are called. + repeated bytes public_keys = 3; + // (optional) Block height or timestamp indicating at what point transactions can be included in a block. + // Zero by default. + uint32 lock_time = 4; + // The inputs to spend. + repeated Input inputs = 5; + // The output of the transaction. Note that the change output is specified + // in the `change_output` field. + repeated Output outputs = 6; + // How the inputs should be selected. + InputSelector input_selector = 7; + // The amount of satoshis per vbyte ("satVb"), used for fee calculation. + // Can be satoshis per byte ("satB") **ONLY** when transaction does not contain segwit UTXOs. + int64 fee_per_vb = 8; + // (optional) The change output to be added (return to sender) at the end of the outputs list. + // The `Output.value` will be overwritten, leave default. + // Note there can be no change output if the change amount is less than dust threshold. + // Leave empty to explicitly disable change output creation. + Output change_output = 9; + // The only output with a max available amount to be send. + // If set, `SigningInput.outputs` and `SigningInput.change` will be ignored. + // The `Output.value` will be overwritten, leave default. + Output max_amount_output = 10; + // Chain info includes p2pkh, p2sh address prefixes. + // The parameter needs to be set if an input/output has a receiver address pattern. + ChainInfo chain_info = 13; + // One of the "Dust" amount policies. + // Later, we plan to add support for `DynamicDust` policy with a `min_relay_fee` amount. + oneof dust_policy { + // Use a constant "Dust" threshold. + int64 fixed_dust_threshold = 14; + } + // Whether disable auxiliary random data when signing. + // Use for testing **ONLY**. + bool dangerous_use_fixed_schnorr_rng = 20; +} + +message Transaction { + // The protocol version, is currently expected to be 1 or 2 (BIP68). + int32 version = 1; + // Block height or timestamp indicating at what point transactions can be included in a block. + // Zero by default. + uint32 lock_time = 2; + // The transaction inputs. + repeated TransactionInput inputs = 3; + // The transaction outputs. + repeated TransactionOutput outputs = 4; + + message TransactionInput { + // Reference to the previous transaction's output. + OutPoint out_point = 1; + // The sequence number, used for timelocks, replace-by-fee, etc. + uint32 sequence = 2; + // The script for claiming the input (non-Segwit/non-Taproot). + bytes script_sig = 3; + // The script for claiming the input (Segit/Taproot). + repeated bytes witness_items = 4; + } + + message TransactionOutput { + // The condition for claiming the output. + bytes script_pubkey = 1; + // The amount of satoshis to spend. + int64 value = 2; + } +} + +message TransactionPlan { + // A possible error, `OK` if none. + Common.Proto.SigningError error = 1; + // Error description. + string error_message = 2; + // Selected unspent transaction outputs (subset of all input UTXOs). + repeated Input inputs = 3; + // Transaction outputs including a change output if applied. + repeated Output outputs = 4; + // Maximum available amount in all the transaction input UTXOs. + // That is an amount that will be spent by this transaction. + int64 available_amount = 5; + // Total sending amount in all the transaction outputs. + // That is an amount that will be sent (including change output if applied). + int64 send_amount = 6; + // The estimated `vsize` in `vbytes`. + // It is used to compare how much blockweight needs to be allocated to confirm a transaction. + // For non-segwit transactions, `vsize` = `size`. + uint64 vsize_estimate = 7; + // The estimated fees of the transaction in satoshis. + int64 fee_estimate = 8; + // Remaining change. + // Zero if not applied. + int64 change = 9; +} + +message PreSigningOutput { + // A possible error, `OK` if none. + Common.Proto.SigningError error = 1; + // Error description. + string error_message = 2; + // The sighashes to be signed; ECDSA for legacy and Segwit, Schnorr for Taproot. + repeated Sighash sighashes = 4; + + enum SigningMethod { + // Used for P2SH and P2PKH - standard ecdsa secp256k1 signing + Legacy = 0; + // Used for P2WSH and P2WPKH - standard ecdsa secp256k1 signing + Segwit = 1; + // Used for P2TR key-path and P2TR script-pay - schnorr signing + Taproot = 2; + } + + message Sighash { + // Public key used for signing. + // Please note it can be tweaked in case of P2TR scriptPubkey. + bytes public_key = 1; + // The sighash to be signed. + bytes sighash = 2; + // Signing method to be used to sign the sighash. + SigningMethod signing_method = 3; + // Taproot tweak if `Taproot` signing method is used. + // Empty if there is no need to tweak the private to sign the sighash. + TaprootTweak tweak = 4; + } + + message TaprootTweak { + // 32 bytes merkle root of the script tree. + // Empty if there are no scripts, and the private key should be tweaked without a merkle root. + bytes merkle_root = 1; + } +} + +message SigningOutput { + // A possible error, `OK` if none. + Common.Proto.SigningError error = 1; + // Error description. + string error_message = 2; + // Resulting transaction. + Transaction transaction = 3; + // The encoded transaction that can be submitted to the network. + bytes encoded = 4; + // The transaction ID (hash). + bytes txid = 5; + // The total `vsize` in `vbytes`. + // It is used to compare how much blockweight needs to be allocated to confirm a transaction. + // For non-segwit transactions, `vsize` = `size`. + uint64 vsize = 6; + // Transaction weight is defined as Base transaction size * 3 + Total transaction size + // (ie. the same method as calculating Block weight from Base size and Total size). + uint64 weight = 7; + // The total and final fee of the transaction in satoshis. + int64 fee = 8; +} + +message PsbtSigningInput { + // Partly signed transaction to be signed. + bytes psbt = 1; + // User private keys. + // Only required if the `signPSBT` method is called. + repeated bytes private_keys = 2; + // User public keys. + // Only required if the `planPSBT` method is called. + repeated bytes public_keys = 3; + // Chain info includes p2pkh, p2sh, hrp address prefixes. + // The parameter needs to be set when `planPSBT` is called. + ChainInfo chain_info = 4; + // Whether disable auxiliary random data when signing. + // Use for testing **ONLY**. + bool dangerous_use_fixed_schnorr_rng = 5; +} + +message PsbtSigningOutput { + // A possible error, `OK` if none. + Common.Proto.SigningError error = 1; + // Error description. + string error_message = 2; + // Resulting transaction. + Transaction transaction = 3; + // The encoded transaction that can be submitted to the network. + bytes encoded = 4; + // The transaction ID (hash). + bytes txid = 5; + // The total `vsize` in `vbytes`. + // It is used to compare how much blockweight needs to be allocated to confirm a transaction. + // For non-segwit transactions, `vsize` = `size`. + uint64 vsize = 6; + // Transaction weight is defined as Base transaction size * 3 + Total transaction size + // (ie. the same method as calculating Block weight from Base size and Total size). + uint64 weight = 7; + // The total and final fee of the transaction in satoshis. + int64 fee = 8; + // Signed transaction serialized as PSBT. + bytes psbt = 9; +} diff --git a/src/proto/Cardano.proto b/src/proto/Cardano.proto index 398242090b4..e4581e9ab75 100644 --- a/src/proto/Cardano.proto +++ b/src/proto/Cardano.proto @@ -5,30 +5,48 @@ option java_package = "wallet.core.jni.proto"; import "Common.proto"; +// A transaction output that can be used as input message OutPoint { + // The transaction ID bytes tx_hash = 1; + + // The index of this output within the transaction uint64 output_index = 2; } +// Represents a token and an amount. Token is identified by PolicyID and name. message TokenAmount { - string policy_id = 1; // as hex string (28x2 digits) + // Policy ID of the token, as hex string (28x2 digits) + string policy_id = 1; + + // The name of the asset (within the policy) string asset_name = 2; - bytes amount = 3; // 256-bit number + + // The amount (uint256, serialized big endian) + bytes amount = 3; + + // The name of the asset (hex encoded). Ignored if `asset_name` is set + string asset_name_hex = 4; } +// One input for a transaction message TxInput { + // The UTXO OutPoint out_point = 1; + // The owner address (string) string address = 2; - // ADA amount + // ADA amount in the UTXO uint64 amount = 3; - // optional token amounts + // optional token amounts in the UTXO repeated TokenAmount token_amount = 4; } +// One output for a transaction message TxOutput { + // Destination address (string) string address = 1; // ADA amount @@ -43,6 +61,7 @@ message TokenBundle { repeated TokenAmount token = 1; } +// Message for simple Transfer tx message Transfer { // Destination address as string string to_address = 1; @@ -66,6 +85,47 @@ message Transfer { uint64 force_fee = 6; } +// Register a staking key for the account, prerequisite for Staking. +// Note: staking messages are typically used with a 1-output-to-self transaction. +message RegisterStakingKey { + // Staking address (as string) + string staking_address = 1; + + // Amount deposited in this TX. Should be 2 ADA (2000000). If not set correctly, TX will be rejected. See also Delegate.deposit_amount. + uint64 deposit_amount = 2; +} + +// Deregister staking key. can be done when staking is stopped completely. The Staking deposit is returned at this time. +message DeregisterStakingKey { + // Staking address (as string) + string staking_address = 1; + + // Amount undeposited in this TX. Should be 2 ADA (2000000). If not set correctly, TX will be rejected. See also RegisterStakingKey.deposit_amount. + uint64 undeposit_amount = 2; +} + +// Delegate funds in this account to a specified staking pool. +message Delegate { + // Staking address (as string) + string staking_address = 1; + + // PoolID of staking pool + bytes pool_id = 2; + + // Amount deposited in this TX. Should be 0. If not set correctly, TX will be rejected. See also RegisterStakingKey.deposit_amount. + uint64 deposit_amount = 3; +} + +// Withdraw earned staking reward. +message Withdraw { + // Staking address (as string) + string staking_address = 1; + + // Withdrawal amount. Should match the real value of the earned reward. + uint64 withdraw_amount = 2; +} + +// Describes a preliminary transaction plan. message TransactionPlan { // total coins in the utxos uint64 available_amount = 1; @@ -79,6 +139,12 @@ message TransactionPlan { // coins in the change UTXO uint64 change = 4; + // coins deposited (going to deposit) in this TX + uint64 deposit = 10; + + // coins undeposited (coming from deposit) in this TX + uint64 undeposit = 11; + // total tokens in the utxos (optional) repeated TokenAmount available_tokens = 5; @@ -88,30 +154,53 @@ message TransactionPlan { // tokens in the change (optional) repeated TokenAmount change_tokens = 7; + // The selected UTXOs, subset ot the input UTXOs repeated TxInput utxos = 8; + // Optional error Common.Proto.SigningError error = 9; + + // Optional additional destination addresses, additional to first to_address output + repeated TxOutput extra_outputs = 12; } -// Input data necessary to create a signed transaction +// Input data necessary to create a signed transaction. message SigningInput { + // Available input UTXOs repeated TxInput utxos = 1; - // Available private keys (double extended keys); every input UTXO adress should be covered + // Available private keys (double extended keys); every input UTXO address should be covered // In case of Plan only, keys should be present, in correct number repeated bytes private_key = 2; // Later this can be made oneof if more message types are supported Transfer transfer_message = 3; + // Optional, used in case of Staking Key registration (prerequisite for Staking) + RegisterStakingKey register_staking_key = 6; + + // Optional, used in case of (re)delegation + Delegate delegate = 7; + + // Optional, used in case of withdraw + Withdraw withdraw = 8; + + // Optional + DeregisterStakingKey deregister_staking_key = 9; + + // Time-to-live time of the TX uint64 ttl = 4; - // Optional plan + // Optional plan, if missing it will be computed TransactionPlan plan = 5; + + // extra output UTXOs + repeated TxOutput extra_outputs = 10; } -// Transaction signing output +// Result containing the signed and encoded transaction. message SigningOutput { + // Encoded transaction bytes bytes encoded = 1; // TxID, derived from transaction data, also needed for submission @@ -119,4 +208,7 @@ message SigningOutput { // Optional error Common.Proto.SigningError error = 3; + + // error code description + string error_message = 4; } diff --git a/src/proto/Common.proto b/src/proto/Common.proto index 7b24800829d..af13469d47f 100644 --- a/src/proto/Common.proto +++ b/src/proto/Common.proto @@ -3,36 +3,72 @@ syntax = "proto3"; package TW.Common.Proto; option java_package = "wallet.core.jni.proto"; +// Error codes, used in multiple blockchains. enum SigningError { - OK = 0; // OK - // chain-generic, generic + // This is the OK case, with value=0 + OK = 0; + + // Chain-generic codes: + // Generic error (used if there is no suitable specific error is adequate) Error_general = 1; + // Internal error, indicates some very unusual, unexpected case Error_internal = 2; - // chain-generic, input + + // Chain-generic codes, input related: + // Low balance: the sender balance is not enough to cover the send and other auxiliary amount such as fee, deposit, or minimal balance. Error_low_balance = 3; - Error_zero_amount_requested = 4; // Requested amount is zero + // Requested amount is zero, send of 0 makes no sense + Error_zero_amount_requested = 4; + // One required key is missing (too few or wrong keys are provided) Error_missing_private_key = 5; + // A private key provided is invalid (e.g. wrong size, usually should be 32 bytes) Error_invalid_private_key = 15; + // A provided address (e.g. destination address) is invalid Error_invalid_address = 16; + // A provided input UTXO is invalid Error_invalid_utxo = 17; + // The amount of an input UTXO is invalid Error_invalid_utxo_amount = 18; - // chain-generic, fee + + // Chain-generic, fee related: + // Wrong fee is given, probably it is too low to cover minimal fee for the transaction Error_wrong_fee = 6; - // chain-generic, signing + + // Chain-generic, signing related: + // General signing error Error_signing = 7; - Error_tx_too_big = 8; // [NEO] Transaction too big, fee in GAS needed or try send by parts - // UTXO-chain specific, inputs - Error_missing_input_utxos = 9; // No UTXOs provided [BTC] - Error_not_enough_utxos = 10; // Not enough non-dust input UTXOs to cover requested amount (dust UTXOs are filtered out) [BTC] - // UTXO-chain specific, script - Error_script_redeem = 11; // [BTC] Missing redeem script - Error_script_output = 12; // [BTC] Invalid output script - Error_script_witness_program = 13; // [BTC] Unrecognized witness program - - Error_invalid_memo = 14; // e.g. [XRP] Invalid tag - Error_input_parse = 19; // e.g. Invalid input data - Error_no_support_n2n = 20; // e.g. Not support multi-input and multi-output transaction - Error_signatures_count = 21; // Incorrect count of signatures passed to compile - Error_invalid_params = 22; // Incorrect parameters + // Resulting transaction is too large + // [NEO] Transaction too big, fee in GAS needed or try send by parts + Error_tx_too_big = 8; + + // UTXO-chain specific, input related: + // No input UTXOs provided [BTC] + Error_missing_input_utxos = 9; + // Not enough non-dust input UTXOs to cover requested amount (dust UTXOs are filtered out) [BTC] + Error_not_enough_utxos = 10; + + // UTXO-chain specific, script related: + // [BTC] Missing required redeem script + Error_script_redeem = 11; + // [BTC] Invalid required output script + Error_script_output = 12; + // [BTC] Unrecognized witness program + Error_script_witness_program = 13; + + // Invalid memo, e.g. [XRP] Invalid tag + Error_invalid_memo = 14; + // Some input field cannot be parsed + Error_input_parse = 19; + // Multi-input and multi-output transaction not supported + Error_no_support_n2n = 20; + // Incorrect count of signatures passed to compile + Error_signatures_count = 21; + // Incorrect input parameter + Error_invalid_params = 22; + // Invalid input token amount Error_invalid_requested_token_amount = 23; + // Operation not supported for the chain. + Error_not_supported = 24; + // Requested amount is too low (less dust). + Error_dust_amount_requested = 25; } diff --git a/src/proto/Cosmos.proto b/src/proto/Cosmos.proto index 6f44115718e..04384442e93 100644 --- a/src/proto/Cosmos.proto +++ b/src/proto/Cosmos.proto @@ -3,13 +3,23 @@ syntax = "proto3"; package TW.Cosmos.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + +// A denomination and an amount message Amount { + // name of the denomination string denom = 1; + + // amount, number as string string amount = 2; } +// Fee incl. gas message Fee { + // Fee amount(s) repeated Amount amounts = 1; + + // Gas price uint64 gas = 2; } @@ -22,12 +32,27 @@ message Height { uint64 revision_height = 2; } +// Transaction broadcast mode enum BroadcastMode { BLOCK = 0; // Wait for the tx to pass/fail CheckTx, DeliverTx, and be committed in a block SYNC = 1; // Wait for the tx to pass/fail CheckTx ASYNC = 2; // Don't wait for pass/fail CheckTx; send and return tx immediately } +message THORChainAsset { + string chain = 1; + string symbol = 2; + string ticker = 3; + bool synth = 4; +} + +message THORChainCoin { + THORChainAsset asset = 1; + string amount = 2; + int64 decimals = 3; +} + +// A transaction payload message message Message { // cosmos-sdk/MsgSend message Send { @@ -78,6 +103,13 @@ message Message { string type_prefix = 5; } + // cosmos-sdk/MsgSetWithdrawAddress + message SetWithdrawAddress { + string delegator_address = 1; + string withdraw_address = 2; + string type_prefix = 3; + } + // cosmos-sdk/MsgWithdrawDelegationReward message WithdrawDelegationReward { string delegator_address = 1; @@ -85,7 +117,7 @@ message Message { string type_prefix = 3; } - // transfer within wasm/MsgExecuteContract, used by Terra + // transfer within wasm/MsgExecuteContract, used by Terra Classic message WasmTerraExecuteContractTransfer { // sender address string sender_address = 1; @@ -99,7 +131,7 @@ message Message { string recipient_address = 4; } - // send within wasm/MsgExecuteContract, used by Terra + // send within wasm/MsgExecuteContract, used by Terra Classic message WasmTerraExecuteContractSend { // sender address string sender_address = 1; @@ -126,7 +158,14 @@ message Message { repeated Amount amounts = 3; } - // send within wasm/MsgExecuteContract, used by Terra + // thorchain/MsgDeposit + message THORChainDeposit { + repeated THORChainCoin coins = 1; + string memo = 2; + bytes signer = 3; + } + + // execute within wasm/MsgExecuteContract, used by Terra Classic message WasmTerraExecuteContractGeneric { // sender address string sender_address = 1; @@ -142,11 +181,160 @@ message Message { repeated Amount coins = 5; } + // transfer within wasm/MsgExecuteContract + message WasmExecuteContractTransfer { + // sender address + string sender_address = 1; + + // token contract address + string contract_address = 2; + + // size is uint128, as bigint + bytes amount = 3; + + string recipient_address = 4; + } + + // send within wasm/MsgExecuteContract + message WasmExecuteContractSend { + // sender address + string sender_address = 1; + + // token contract address + string contract_address = 2; + + // size is uint128, as bigint + bytes amount = 3; + + string recipient_contract_address = 4; + + // execute_msg to be executed in the context of recipient contract + string msg = 5; + + // used in case you are sending native tokens along with this message + repeated string coin = 6; + } + + // execute within wasm/MsgExecuteContract + // TODO replaces `ExecuteContract`. + message WasmExecuteContractGeneric { + // sender address + string sender_address = 1; + + // token contract address + string contract_address = 2; + + // execute_msg to be executed in the context of recipient contract + string execute_msg = 3; + + // used in case you are sending native tokens along with this message + // Gap in field numbering is intentional + repeated Amount coins = 5; + } + message RawJSON { string type = 1; string value = 2; } + // For signing an already serialized transaction. Account number and chain ID must be set outside. + message SignDirect { + // The prepared serialized TxBody + bytes body_bytes = 1; + // The prepared serialized AuthInfo + bytes auth_info_bytes = 2; + } + + // StakeAuthorization defines authorization for delegate/undelegate/redelegate. + // + // Since: cosmos-sdk 0.43 + message StakeAuthorization { + // max_tokens specifies the maximum amount of tokens can be delegate to a validator. If it is + // empty, there is no spend limit and any amount of coins can be delegated. + Amount max_tokens = 1; + // validators is the oneof that represents either allow_list or deny_list + oneof validators { + // allow_list specifies list of validator addresses to whom grantee can delegate tokens on behalf of granter's + // account. + Validators allow_list = 2; + // deny_list specifies list of validator addresses to whom grantee can not delegate tokens. + Validators deny_list = 3; + } + // Validators defines list of validator addresses. + message Validators { + repeated string address = 1; + } + // authorization_type defines one of AuthorizationType. + AuthorizationType authorization_type = 4; + } + + // AuthorizationType defines the type of staking module authorization type + // + // Since: cosmos-sdk 0.43 + enum AuthorizationType { + // AUTHORIZATION_TYPE_UNSPECIFIED specifies an unknown authorization type + UNSPECIFIED = 0; + // AUTHORIZATION_TYPE_DELEGATE defines an authorization type for Msg/Delegate + DELEGATE = 1; + // AUTHORIZATION_TYPE_UNDELEGATE defines an authorization type for Msg/Undelegate + UNDELEGATE = 2; + // AUTHORIZATION_TYPE_REDELEGATE defines an authorization type for Msg/BeginRedelegate + REDELEGATE = 3; + } + + + // cosmos-sdk/MsgGrant + message AuthGrant { + string granter = 1; + string grantee = 2; + oneof grant_type { + StakeAuthorization grant_stake = 3; + } + int64 expiration = 4; + } + + // cosmos-sdk/MsgRevoke + message AuthRevoke { + string granter = 1; + string grantee = 2; + string msg_type_url = 3; + } + + // VoteOption enumerates the valid vote options for a given governance proposal. + enum VoteOption { + //_UNSPECIFIED defines a no-op vote option. + _UNSPECIFIED = 0; + // YES defines a yes vote option. + YES = 1; + // ABSTAIN defines an abstain vote option. + ABSTAIN = 2; + // NO defines a no vote option. + NO = 3; + // NO_WITH_VETO defines a no with veto vote option. + NO_WITH_VETO = 4; + } + + // cosmos-sdk/MsgVote defines a message to cast a vote. + message MsgVote { + uint64 proposal_id = 1; + string voter = 2; + VoteOption option = 3; + } + + message MsgStrideLiquidStakingStake { + string creator = 1; + string amount = 2; + string host_denom = 3; + } + + message MsgStrideLiquidStakingRedeem { + string creator = 1; + string amount = 2; + string host_zone = 3; + string receiver = 4; + } + + // The payload message oneof message_oneof { Send send_coins_message = 1; Transfer transfer_tokens_message = 2; @@ -158,35 +346,91 @@ message Message { WasmTerraExecuteContractTransfer wasm_terra_execute_contract_transfer_message = 8; WasmTerraExecuteContractSend wasm_terra_execute_contract_send_message = 9; THORChainSend thorchain_send_message = 10; - WasmTerraExecuteContractGeneric wasm_terra_execute_contract_generic = 11; - + WasmTerraExecuteContractGeneric wasm_terra_execute_contract_generic = 12; + WasmExecuteContractTransfer wasm_execute_contract_transfer_message = 13; + WasmExecuteContractSend wasm_execute_contract_send_message = 14; + WasmExecuteContractGeneric wasm_execute_contract_generic = 15; + SignDirect sign_direct_message = 16; + AuthGrant auth_grant = 17; + AuthRevoke auth_revoke = 18; + SetWithdrawAddress set_withdraw_address_message = 19; + MsgVote msg_vote = 20; + MsgStrideLiquidStakingStake msg_stride_liquid_staking_stake = 21; + MsgStrideLiquidStakingRedeem msg_stride_liquid_staking_redeem = 22; + THORChainDeposit thorchain_deposit_message = 23; } } +// Options for transaction encoding: JSON (Amino, older) or Protobuf. enum SigningMode { JSON = 0; // JSON format, Pre-Stargate Protobuf = 1; // Protobuf-serialized (binary), Stargate } -// Input data necessary to create a signed order. +enum TxHasher { + // For Cosmos chain, `Sha256` is used by default. + UseDefault = 0; + Sha256 = 1; + Keccak256 = 2; +} + +enum SignerPublicKeyType { + // Default public key type. + Secp256k1 = 0; + // Mostly used in Cosmos chains with EVM support. + Secp256k1Extended = 1; +} + +// Custom Signer info required to sign a transaction and generate a broadcast JSON message. +message SignerInfo { + // Public key type used to sign a transaction. + // It can be different from the value from `registry.json`. + SignerPublicKeyType public_key_type = 1; + string json_type = 2; + string protobuf_type = 3; +} + +// Input data necessary to create a signed transaction. message SigningInput { - // Specify if Stargate or earlier serialization is used + // Specify if protobuf (a.k.a. Stargate) or earlier JSON serialization is used SigningMode signing_mode = 1; + // Source account number uint64 account_number = 2; + + // Chain ID (string) string chain_id = 3; + + // Transaction fee Fee fee = 4; + + // Optional memo string memo = 5; - uint64 sequence = 6; + // Sequence number (account specific) + uint64 sequence = 6; + + // The secret private key used for signing (32 bytes). bytes private_key = 7; + // Payload message(s) repeated Message messages = 8; + // Broadcast mode (included in output, relevant when broadcasting) BroadcastMode mode = 9; + + bytes public_key = 10; + + TxHasher tx_hasher = 11; + + // Optional. If set, use a different Signer info when signing the transaction. + SignerInfo signer_info = 12; + + // Optional timeout_height + uint64 timeout_height = 13; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signature bytes signature = 1; @@ -198,6 +442,11 @@ message SigningOutput { // wrapped in a ready-to-broadcast json. string serialized = 3; - // Set in case of error - string error = 4; + // signatures array json string + string signature_json = 4; + + // error description + string error_message = 5; + + Common.Proto.SigningError error = 6; } diff --git a/src/proto/Decred.proto b/src/proto/Decred.proto index d8355f5e6b5..82ff3dd429a 100644 --- a/src/proto/Decred.proto +++ b/src/proto/Decred.proto @@ -6,11 +6,12 @@ option java_package = "wallet.core.jni.proto"; import "Bitcoin.proto"; import "Common.proto"; +// A transfer transaction message Transaction { - /// Serialization format + // Serialization format uint32 serializeType = 1; - /// Transaction data format version + // Transaction data format version uint32 version = 2; // A list of 1 or more transaction inputs or sources for coins. @@ -19,10 +20,10 @@ message Transaction { // A list of 1 or more transaction outputs or destinations for coins repeated TransactionOutput outputs = 4; - /// The time when a transaction can be spent (usually zero, in which case it has no effect). + // The time when a transaction can be spent (usually zero, in which case it has no effect). uint32 lockTime = 5; - /// The block height at which the transaction expires and is no longer valid. + // The block height at which the transaction expires and is no longer valid. uint32 expiry = 6; } @@ -34,8 +35,13 @@ message TransactionInput { // Transaction version as defined by the sender. uint32 sequence = 2; + // The amount of the input int64 valueIn = 3; + + // Creation block height uint32 blockHeight = 4; + + // Index within the block uint32 blockIndex = 5; // Computational script for confirming transaction authorization. @@ -47,14 +53,14 @@ message TransactionOutput { // Transaction amount. int64 value = 1; - /// Transaction output version. + // Transaction output version. uint32 version = 2; // Usually contains the public key as a Decred script setting up conditions to claim this output. bytes script = 3; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Resulting transaction. Note that the amount may be different than the requested amount to account for fees and available funds. Transaction transaction = 1; @@ -67,4 +73,6 @@ message SigningOutput { // Optional error Common.Proto.SigningError error = 4; + + string error_message = 5; } diff --git a/src/proto/EOS.proto b/src/proto/EOS.proto index d6a09f94f41..04bec8f5906 100644 --- a/src/proto/EOS.proto +++ b/src/proto/EOS.proto @@ -13,17 +13,22 @@ enum KeyType { // Values for an Asset object. message Asset { + // Total amount int64 amount = 1; + + // Number of decimals defined uint32 decimals = 2; + + // Asset symbol string symbol = 3; } // Input data necessary to create a signed transaction. message SigningInput { - // Chain id (256-bit number) + // Chain id (uint256, serialized big endian) bytes chain_id = 1; - // Reference Block Id (256-bits) + // Reference Block Id (uint256, serialized big endian) bytes reference_block_id = 2; // Timestamp on the reference block @@ -44,18 +49,24 @@ message SigningInput { // Asset details and amount Asset asset = 8; - // Sender's private key's raw bytes + // Sender's secret private key used for signing (32 bytes). bytes private_key = 9; // Type of the private key KeyType private_key_type = 10; + + // Expiration of the transaction, if not set, default is reference_block_time + 3600 seconds + sfixed32 expiration = 11; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // JSON of the packed transaction. string json_encoded = 1; // Optional error Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } diff --git a/src/proto/Elrond.proto b/src/proto/Elrond.proto deleted file mode 100644 index a2f4c59728a..00000000000 --- a/src/proto/Elrond.proto +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -syntax = "proto3"; - -package TW.Elrond.Proto; -option java_package = "wallet.core.jni.proto"; - -// Generic action. Using one of the more specific actions (e.g. transfers, see below) is recommended. -message GenericAction { - Accounts accounts = 1; - string value = 2; - string data = 3; - uint32 version = 4; - // Currently, the "options" field should be ignored (not set) by applications using TW Core. - // In the future, TW Core will handle specific transaction options - // (such as the "SignedWithHash" flag, as seen in https://github.com/ElrondNetwork/elrond-go-core/blob/main/core/versioning/txVersionChecker.go) - // when building and signing transactions. - uint32 options = 5; -} - -// EGLD transfer (move balance). -message EGLDTransfer { - Accounts accounts = 1; - string amount = 2; -} - -// ESDT transfer (transfer regular ESDTs - fungible tokens). -message ESDTTransfer { - Accounts accounts = 1; - string token_identifier = 2; - string amount = 3; -} - -// ESDTNFT transfer (transfer NFTs, SFTs and Meta ESDTs). -message ESDTNFTTransfer { - Accounts accounts = 1; - string token_collection = 2; - uint64 token_nonce = 3; - string amount = 4; -} - -// Transaction sender & receiver etc. -message Accounts { - uint64 sender_nonce = 1; - string sender = 2; - string sender_username = 3; - string receiver = 4; - string receiver_username = 5; -} - -// Input data necessary to create a signed transaction. -message SigningInput { - bytes private_key = 1; - string chain_id = 2; - uint64 gas_price = 3; - uint64 gas_limit = 4; - - oneof message_oneof { - GenericAction generic_action = 5; - EGLDTransfer egld_transfer = 6; - ESDTTransfer esdt_transfer = 7; - ESDTNFTTransfer esdtnft_transfer = 8; - } -} - -// Transaction signing output. -message SigningOutput { - string encoded = 1; - string signature = 2; -} diff --git a/src/proto/Ethereum.proto b/src/proto/Ethereum.proto index f67ea7b4100..cfe413cb54b 100644 --- a/src/proto/Ethereum.proto +++ b/src/proto/Ethereum.proto @@ -9,7 +9,7 @@ import "Common.proto"; message Transaction { // Native coin transfer transaction message Transfer { - // Amount to send in wei (256-bit number) + // Amount to send in wei (uint256, serialized big endian) bytes amount = 1; // Optional payload data @@ -18,40 +18,46 @@ message Transaction { // ERC20 token transfer transaction message ERC20Transfer { + // destination address (string) string to = 1; - // Amount to send (256-bit number) + // Amount to send (uint256, serialized big endian) bytes amount = 2; } // ERC20 approve transaction message ERC20Approve { + // Target of the approval string spender = 1; - // Amount to send (256-bit number) + // Amount to send (uint256, serialized big endian) bytes amount = 2; } // ERC721 NFT transfer transaction message ERC721Transfer { + // Source address string from = 1; + // Destination address string to = 2; - // ID of the token (256-bit number) + // ID of the token (uint256, serialized big endian) bytes token_id = 3; } // ERC1155 NFT transfer transaction message ERC1155Transfer { + // Source address string from = 1; + // Destination address string to = 2; - // ID of the token (256-bit number) + // ID of the token (uint256, serialized big endian) bytes token_id = 3; - // The amount of tokens being transferred + // The amount of tokens being transferred (uint256, serialized big endian) bytes value = 4; bytes data = 5; @@ -59,13 +65,30 @@ message Transaction { // Generic smart contract transaction message ContractGeneric { - // Amount to send in wei (256-bit number) + // Amount to send in wei (uint256, serialized big endian) bytes amount = 1; // Contract call payload data bytes data = 2; } + // Batched transaction for ERC-4337 wallets + message Batch { + message BatchedCall { + // Recipient addresses. + string address = 1; + + // Amounts to send in wei (uint256, serialized big endian) + bytes amount = 2; + + // Contract call payloads data + bytes payload = 3; + } + + repeated BatchedCall calls = 1; + } + + // Payload transfer oneof transaction_oneof { Transfer transfer = 1; ERC20Transfer erc20_transfer = 2; @@ -73,56 +96,103 @@ message Transaction { ERC721Transfer erc721_transfer = 4; ERC1155Transfer erc1155_transfer = 5; ContractGeneric contract_generic = 6; + Batch batch = 7; } } +// Transaction type enum TransactionMode { - Legacy = 0; // Legacy transaction, pre-EIP2718/EIP1559; for fee gasPrice/gasLimit is used - Enveloped = 1; // Enveloped transaction EIP2718 (with type 0x2), fee is according to EIP1559 (base fee, inclusion fee, ...) + // Legacy transaction, pre-EIP2718/EIP1559; for fee gasPrice/gasLimit is used + Legacy = 0; + + // Enveloped transaction EIP2718 (with type 0x2), fee is according to EIP1559 (base fee, inclusion fee, ...) + Enveloped = 1; + + // EIP4337-compatible UserOperation + UserOp = 2; +} + +// ERC-4337 structure that describes a transaction to be sent on behalf of a user +message UserOperation { + // Entry point contract address + string entry_point = 1; + + // Account factory contract address + bytes init_code = 2; + + // Account logic contract address + string sender = 3; + + // The amount of gas to pay for to compensate the bundler for pre-verification execution and calldata + bytes pre_verification_gas = 4; + + // The amount of gas to allocate for the verification step + bytes verification_gas_limit = 5; + + // Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster (empty for self-sponsored transaction) + bytes paymaster_and_data = 6; +} + +// An item of the [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) access list. +message Access { + // Address to be accessed by the transaction. + string address = 1; + // Storage keys to be accessed by the transaction. + repeated bytes stored_keys = 2; } // Input data necessary to create a signed transaction. // Legacy and EIP2718/EIP1559 transactions supported, see TransactionMode. message SigningInput { - // Chain identifier (256-bit number) + // Chain identifier (uint256, serialized big endian) bytes chain_id = 1; - // Nonce (256-bit number) + // Nonce (uint256, serialized big endian) bytes nonce = 2; // Transaction version selector: Legacy or enveloped, has impact on fee structure. // Default is Legacy (value 0) TransactionMode tx_mode = 3; - // Gas price (256-bit number) + // Gas price (uint256, serialized big endian) // Relevant for legacy transactions only (disregarded for enveloped/EIP1559) bytes gas_price = 4; - // Gas limit (256-bit number) + // Gas limit (uint256, serialized big endian) bytes gas_limit = 5; - // Maxinmum optional inclusion fee (aka tip) (256-bit number) + // Maximum optional inclusion fee (aka tip) (uint256, serialized big endian) // Relevant for enveloped/EIP1559 transactions only, tx_mode=Enveloped, (disregarded for legacy) bytes max_inclusion_fee_per_gas = 6; - // Maxinmum fee (256-bit number) + // Maximum fee (uint256, serialized big endian) // Relevant for enveloped/EIP1559 transactions only, tx_mode=Enveloped, (disregarded for legacy) bytes max_fee_per_gas = 7; // Recipient's address. string to_address = 8; - // Private key. + // The secret private key used for signing (32 bytes). bytes private_key = 9; + // The payload transaction Transaction transaction = 10; + + // UserOperation for ERC-4337 wallets + UserOperation user_operation = 11; + + // Optional list of addresses and storage keys that the transaction plans to access. + // Used in `TransactionMode::Enveloped` only. + repeated Access access_list = 12; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { + // Signed and encoded transaction bytes. bytes encoded = 1; + // The V, R, S components of the resulting signature, (each uint256, serialized big endian) bytes v = 2; bytes r = 3; bytes s = 4; @@ -130,9 +200,68 @@ message SigningOutput { // The payload part, supplied in the input or assembled from input parameters bytes data = 5; - /// error code, 0 is ok, other codes will be treated as errors + // error code, 0 is ok, other codes will be treated as errors Common.Proto.SigningError error = 6; - /// error code description + // error code description string error_message = 7; + + // Encoded transaction bytes. + bytes pre_hash = 8; +} + +enum MessageType { + // Sign a message following EIP-191. + MessageType_legacy = 0; + // Sign a message following EIP-191 with EIP-155 replay attack protection. + MessageType_eip155 = 1; + // Sign a typed message EIP-712 V4. + MessageType_typed = 2; + // Sign a typed message EIP-712 V4 with EIP-155 replay attack protection. + MessageType_typed_eip155 = 3; + // Sign a message with Immutable X msg type. + MessageType_immutable_x = 4; +} + +message MaybeChainId { + // Chain ID. + uint64 chain_id = 3; +} + +message MessageSigningInput { + // The secret private key used for signing (32 bytes). + bytes private_key = 1; + + // Message to sign. Either a regular message or a typed data structured message in JSON format. + // Message type should be declared at `message_type`. + string message = 2; + + // Optional. Used in replay protection and to check Typed Structured Data input. + // Eg. should be set if `message_type` is `MessageType_eip155`, or MessageType_typed, or `MessageType_typed_eip155`. + MaybeChainId chain_id = 3; + + // Message type. + MessageType message_type = 4; +} + +message MessageSigningOutput { + // The signature, Hex-encoded. + string signature = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; +} + +message MessageVerifyingInput { + // The message signed. + string message = 1; + + // Public key that will verify and recover the message from the signature. + bytes public_key = 2; + + // The signature, Hex-encoded. + string signature = 3; } diff --git a/src/proto/EthereumAbi.proto b/src/proto/EthereumAbi.proto new file mode 100644 index 00000000000..8ee2c0692a1 --- /dev/null +++ b/src/proto/EthereumAbi.proto @@ -0,0 +1,318 @@ +syntax = "proto3"; + +package TW.EthereumAbi.Proto; +option java_package = "wallet.core.jni.proto"; + +enum AbiError { + // This is the OK case, with value=0 + OK = 0; + + // Internal error. + Error_internal = 1; + + // Unexpected function signature or ABI mismatch. + Error_abi_mismatch = 2; + // Invalid ABI. + Error_invalid_abi = 3; + // Invalid parameter type. + Error_invalid_param_type = 4; + // Invalid address value. + Error_invalid_address_value = 5; + // Invalid UInt value. + Error_invalid_uint_value = 6; + // Missing parameter type. + Error_missing_param_type = 7; + // Missing parameter value. + Error_missing_param_value = 8; + // Invalid encoded data. + Error_decoding_data = 9; + // Invalid empty type. + // For example, bytes0, address[0]. + Error_empty_type = 10; +} + +// ABI type parameters excluding values. + +// Indicates a boolean type. +message BoolType {} + +// Generic number type for all bit sizes, like UInt24, 40, 48, ... 248. +message NumberNType { + // The number of bits of an integer. + uint32 bits = 1; +} + +// Indicates a string type. +message StringType {} + +// Indicates an address type. +message AddressType {} + +// Indicates an array type with an inner `element_type`. +message ArrayType { + // The type of array elements. + ParamType element_type = 1; +} + +// Indicates a fixed-size array type with an inner `element_type`. +message FixedArrayType { + // The fixed-size of the array. + uint64 size = 1; + // The type of array elements. + ParamType element_type = 2; +} + +// Indicates a byte array type. +message ByteArrayType {} + +// Indicates a fixed-size byte array type. +message ByteArrayFixType { + // The fixed-size of the array. + uint64 size = 1; +} + +// Indicates a tuple with inner type parameters. +message TupleType { + // Tuple named parameters. + repeated Param params = 1; +} + +// Named parameter with type. +message Param { + // Name of the parameter. + string name = 1; + + // Type of the parameter. + ParamType param = 2; +} + +message ParamType { + oneof param { + BoolType boolean = 1; + NumberNType number_int = 2; + NumberNType number_uint = 3; + // Nested values. Gap in field numbering is intentional. + StringType string_param = 7; + AddressType address = 8; + ByteArrayType byte_array = 9; + ByteArrayFixType byte_array_fix = 10; + + // Nested values. Gap in field numbering is intentional. + ArrayType array = 14; + FixedArrayType fixed_array = 15; + + // Nested values. Gap in field numbering is intentional. + TupleType tuple = 19; + } +} + +// ABI parameters including values. + +// Generic number parameter for all other bit sizes, like UInt24, 40, 48, ... 248. +message NumberNParam { + // Count of bits of the number. + // 0 < bits <= 256, bits % 8 == 0 + uint32 bits = 1; + + // Serialized big endian. + bytes value = 2; +} + +// A byte array of arbitrary size. +message ArrayParam { + // The type of array elements. + ParamType element_type = 1; + + // Array elements. + repeated Token elements = 2; +} + +// A tuple with various parameters similar to a structure. +message TupleParam { + // Tokens (values) of the tuple parameters. + repeated Token params = 1; +} + +// A value of an ABI parameter. +message Token { + // Optional. Name of a corresponding parameter. + string name = 1; + + oneof token { + // Integer values. + bool boolean = 2; + NumberNParam number_int = 3; + NumberNParam number_uint = 4; + + // Simple values. Gap in field numbering is intentional. + string string_value = 7; + string address = 8; + bytes byte_array = 9; + bytes byte_array_fix = 10; + + // Nested values. Gap in field numbering is intentional. + ArrayParam array = 14; + ArrayParam fixed_array = 15; + + // Nested values. Gap in field numbering is intentional. + TupleParam tuple = 19; + } +} + +//// TWEthereumAbiDecodeContractCall + +// Decode a contract call (function input) according to the given ABI json. +message ContractCallDecodingInput { + // An encoded smart contract call with a prefixed function signature (4 bytes). + bytes encoded = 1; + + // A smart contract ABI in JSON. + // Each ABI function must be mapped to a short signature. + // Expected to be a set of functions mapped to corresponding short signatures. + // Example: + // ``` + // { + // "1896f70a": { + // "name": "setResolver", + // "inputs": [...], + // ... + // }, + // "ac9650d8": { + // "name": "multicall", + // "inputs": [...], + // ... + // } + // } + // ``` + string smart_contract_abi_json = 2; +} + +message ContractCallDecodingOutput { + // Human readable json format, according to the input `ContractCallDecodingInput::smart_contract_abi_json`. + string decoded_json = 1; + + // Decoded parameters. + repeated Token tokens = 2; + + // error code, 0 is ok, other codes will be treated as errors + AbiError error = 3; + + // error code description + string error_message = 4; +} + +//// TWEthereumAbiDecodeParams + +// A set of ABI type parameters. +message AbiParams { + // ABI type parameters. + repeated Param params = 1; +} + +// Decode a function input or output data according to the given ABI json. +message ParamsDecodingInput { + // An encoded ABI. + bytes encoded = 1; + + oneof abi { + // A set of ABI parameters in JSON. + // Expected to be a JSON array at the entry level. + // Example: + // ``` + // [ + // { + // "name": "_to', + // "type": "address" + // }, + // { + // "name": "_value", + // "type": "uint256" + // } + // ] + // ``` + string abi_json = 2; + + // A set of ABI type parameters. + AbiParams abi_params = 3; + } +} + +message ParamsDecodingOutput { + // Decoded parameters. + repeated Token tokens = 1; + + // error code, 0 is ok, other codes will be treated as errors + AbiError error = 2; + + // error code description + string error_message = 3; +} + +//// TWEthereumAbiDecodeValue + +// Decode an Eth ABI value. +message ValueDecodingInput { + // An encoded value to be decoded. + bytes encoded = 1; + + // A type of the parameter. + // Example: "bytes[32]". + // Please note `tuple` is not supported. + string param_type = 2; +} + +message ValueDecodingOutput { + // Decoded parameter. + Token token = 1; + + // Decoded parameter as a string. + string param_str = 2; + + // error code, 0 is ok, other codes will be treated as errors + AbiError error = 3; + + // error code description + string error_message = 4; +} + +//// TWEthereumAbiEncodeFunction + +// Encode a function call to Eth ABI binary. +message FunctionEncodingInput { + // Function name. + string function_name = 1; + + // Parameters to be encoded. + repeated Token tokens = 2; +} + +message FunctionEncodingOutput { + // The function type signature. + // Example: "baz(int32,uint256)" + string function_type = 1; + + // An encoded smart contract call with a prefixed function signature (4 bytes). + bytes encoded = 2; + + // error code, 0 is ok, other codes will be treated as errors + AbiError error = 3; + + // error code description + string error_message = 4; +} + +//// TWEthereumAbiFunctionGetType + +// Return the function type signature, of the form "baz(int32,uint256)". +message FunctionGetTypeInput { + // Function signature. Includes function inputs if they are. + // Examples: + // - `functionName()` + // - `functionName()` + // - `functionName(bool)` + // - `functionName(uint256,bytes32)` + string function_name = 1; + + // A set of ABI type parameters. + repeated Param inputs = 2; +} diff --git a/src/proto/EthereumRlp.proto b/src/proto/EthereumRlp.proto new file mode 100644 index 00000000000..0655ff37064 --- /dev/null +++ b/src/proto/EthereumRlp.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +package TW.EthereumRlp.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// List of elements. +message RlpList { + repeated RlpItem items = 1; +} + +// RLP item. +message RlpItem { + oneof item { + // A string to be encoded. + string string_item = 1; + // A U64 number to be encoded. + uint64 number_u64 = 2; + // A U256 number to be encoded. + bytes number_u256 = 3; + // An address to be encoded. + string address = 4; + // A data to be encoded. + bytes data = 5; + // A list of items to be encoded. + RlpList list = 6; + // An RLP encoded item to be appended as it is. + bytes raw_encoded = 7; + } +} + +// RLP encoding input. +message EncodingInput { + // An item or a list to encode. + RlpItem item = 1; +} + +/// RLP encoding output. +message EncodingOutput { + // An item RLP encoded. + bytes encoded = 1; + + // Error code, 0 is ok, other codes will be treated as errors. + Common.Proto.SigningError error = 2; + + // Error code description. + string error_message = 3; +} diff --git a/src/proto/Everscale.proto b/src/proto/Everscale.proto new file mode 100644 index 00000000000..20ef2644ba8 --- /dev/null +++ b/src/proto/Everscale.proto @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +syntax = "proto3"; + +package TW.Everscale.Proto; +option java_package = "wallet.core.jni.proto"; + + +// Message option +enum MessageBehavior { + // Sends a message with the specified amount. The sender pays a fee from the account balance + SimpleTransfer = 0; + + // Sends the entire account balance along with the message + SendAllBalance = 1; +} + +// Transfer message +message Transfer { + // If set to true, then the message will be returned if there is an error on the recipient's side. + bool bounce = 1; + + // Affect the attached amount and fees + MessageBehavior behavior = 2; + + // Amount to send in nano EVER + uint64 amount = 3; + + // Expiration UNIX timestamp + uint32 expired_at = 4; + + // Recipient address + string to = 5; + + // Account state if there is any + oneof account_state_oneof { + // Just contract data + string encoded_contract_data = 6; + } +} + +// Input data necessary to create a signed transaction. +message SigningInput { + // The payload transfer + oneof action_oneof { + Transfer transfer = 1; + } + + // The secret private key used for signing (32 bytes). + bytes private_key = 2; +} + +// Result containing the signed and encoded transaction. +message SigningOutput { + string encoded = 1; +} diff --git a/src/proto/FIO.proto b/src/proto/FIO.proto index 5579cde39dd..664c041e5f0 100644 --- a/src/proto/FIO.proto +++ b/src/proto/FIO.proto @@ -52,6 +52,7 @@ message Action { } // Acion for adding public chain addresses to a FIO name; add_pub_address + // Note: actor is not needed, computed from private key message AddPubAddress { // The FIO name already registered to the owner. Ex.: "alice@trust" string fio_address = 1; @@ -59,13 +60,35 @@ message Action { // List of public addresses to be registered, ex. {{"BTC", "bc1qv...7v"},{"BNB", "bnb1ts3...9s"}} repeated PublicAddress public_addresses = 2; + // Max fee to spend, can be obtained using get_fee API. + uint64 fee = 3; + } + + // Action for removing public chain addresses from a FIO name; remove_pub_address + // Note: actor is not needed, computed from private key + message RemovePubAddress { + // The FIO name already registered to the owner. Ex.: "alice@trust" + string fio_address = 1; + + // List of public addresses to be unregistered, ex. {{"BTC", "bc1qv...7v"},{"BNB", "bnb1ts3...9s"}} + repeated PublicAddress public_addresses = 2; + // Max fee to spend, can be obtained using get_fee API. uint64 fee = 3; - - // Note: actor is not needed, computed from private key } - // Action for transfering FIO coins; transfer_tokens_pub_key + // Action for removing public chain addresses from a FIO name; remove_pub_address + // Note: actor is not needed, computed from private key + message RemoveAllPubAddress { + // The FIO name already registered to the owner. Ex.: "alice@trust" + string fio_address = 1; + + // Max fee to spend, can be obtained using get_fee API. + uint64 fee = 3; + } + + // Action for transferring FIO coins; transfer_tokens_pub_key + // Note: actor is not needed, computed from private key message Transfer { // FIO address of the payee. Ex.: "FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf" string payee_public_key = 1; @@ -75,11 +98,10 @@ message Action { // Max fee to spend, can be obtained using get_fee API. uint64 fee = 3; - - // Note: actor is not needed, computed from private key } // Action for renewing a FIO name; renew_fio_address + // Note: actor is not needed, computed from private key message RenewFioAddress { // The FIO name to be renewed. Ex.: "alice@trust" string fio_address = 1; @@ -89,11 +111,10 @@ message Action { // Max fee to spend, can be obtained using get_fee API. uint64 fee = 3; - - // Note: actor is not needed, computed from owner_fio_public_key } // Action for creating a new payment request; new_funds_request + // Note: actor is not needed, computed from private key message NewFundsRequest { // The FIO name of the requested payer. Ex.: "alice@trust" string payer_fio_name = 1; @@ -109,16 +130,30 @@ message Action { // Max fee to spend, can be obtained using get_fee API. uint64 fee = 5; + } + + // Action for adding `100 * bundle_sets` bundled transactions to the supplied FIO Handle. When bundles are purchased one or more sets of bundled transactions are added to the existing count. + message AddBundledTransactions { + // The FIO name already registered to the owner. Ex.: "alice@trust" + string fio_address = 1; + + // Number of bundled sets. One set is 100 bundled transactions. + uint64 bundle_sets = 2; - // Note: actor is not needed, computed from private key + // Max fee to spend, can be obtained using get_fee API. + uint64 fee = 3; } + // Payload message oneof message_oneof { RegisterFioAddress register_fio_address_message = 1; AddPubAddress add_pub_address_message = 2; Transfer transfer_message = 3; RenewFioAddress renew_fio_address_message = 4; NewFundsRequest new_funds_request_message = 5; + RemovePubAddress remove_pub_address_message = 6; + RemoveAllPubAddress remove_all_pub_addresses_message = 7; + AddBundledTransactions add_bundled_transactions_message = 8; } } @@ -134,7 +169,7 @@ message ChainParams { uint64 ref_block_prefix = 3; } -// Transaction signing input +// Input data necessary to create a signed transaction. message SigningInput { // Expiry for this message, in unix time. Can be 0, then it is taken from current time with default expiry uint32 expiry = 1; @@ -142,7 +177,7 @@ message SigningInput { // Current parameters of the FIO blockchain ChainParams chain_params = 2; - // The private key matching the address, needed for signing + // The secret private key matching the address, used for signing (32 bytes). bytes private_key = 3; // The FIO name of the originating wallet (project-wide constant) @@ -150,13 +185,22 @@ message SigningInput { // Context-specific action data Action action = 5; + + // FIO address of the owner. Ex.: "FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf" + string owner_public_key = 6; } -// Transaction signing output +// Result containing the signed and encoded transaction. message SigningOutput { // Signed transaction in JSON string json = 1; // Optional error Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; + + // Performed action name, ex. "addaddress", "remaddress", "trnsfiopubky" etc. + string action_name = 4; } diff --git a/src/proto/Filecoin.proto b/src/proto/Filecoin.proto index 0c39da82c72..09a26ce03a4 100644 --- a/src/proto/Filecoin.proto +++ b/src/proto/Filecoin.proto @@ -3,9 +3,20 @@ syntax = "proto3"; package TW.Filecoin.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + +// Defines the type of `from` address derivation. +enum DerivationType { + // Defines a Secp256k1 (`f1`) derivation for the sender address. + // Default derivation type. + SECP256K1 = 0; + // Defines a Delegated (`f4`) derivation for the sender address. + DELEGATED = 1; +} + // Input data necessary to create a signed transaction. message SigningInput { - // Private key of sender account. + // The secret private key of the sender account, used for signing (32 bytes). bytes private_key = 1; // Recipient's address. @@ -14,20 +25,36 @@ message SigningInput { // Transaction nonce. uint64 nonce = 3; - // Transfer value. + // Transfer value (uint256, serialized big endian) bytes value = 4; // Gas limit. int64 gas_limit = 5; - // Gas fee cap. + // Gas fee cap (uint256, serialized big endian) bytes gas_fee_cap = 6; - // Gas premium. + // Gas premium (uint256, serialized big endian) bytes gas_premium = 7; + + // Message params. + bytes params = 8; + + // Sender address derivation type. + DerivationType derivation = 9; + + // Public key secp256k1 extended + bytes public_key = 10; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { + // Resulting transaction, in JSON. string json = 1; + + // Error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // Error description + string error_message = 3; } diff --git a/src/proto/Greenfield.proto b/src/proto/Greenfield.proto new file mode 100644 index 00000000000..20e87548615 --- /dev/null +++ b/src/proto/Greenfield.proto @@ -0,0 +1,128 @@ +syntax = "proto3"; + +package TW.Greenfield.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// A denomination and an amount +message Amount { + // name of the denomination + string denom = 1; + + // amount, number as string + string amount = 2; +} + +// Fee incl. gas +message Fee { + // Fee amount(s) + repeated Amount amounts = 1; + + // Gas price + uint64 gas = 2; +} + +// Transaction broadcast mode +enum BroadcastMode { + SYNC = 0; // Wait for the tx to pass/fail CheckTx + ASYNC = 1; // Don't wait for pass/fail CheckTx; send and return tx immediately +} + +// A transaction payload message +message Message { + // cosmos-sdk/MsgSend + message Send { + string from_address = 1; + string to_address = 2; + repeated Amount amounts = 3; + // Optional. Default `cosmos.bank.v1beta1.MsgSend`. + string type_prefix = 4; + } + + // greenfield/MsgTransferOut + // Used to transfer BNB Greenfield to BSC blockchain. + message BridgeTransferOut { + // In most cases, `from_address` and `to_address` are equal. + string from_address = 1; + string to_address = 2; + Amount amount = 3; + // Optional. Default `greenfield.bridge.MsgTransferOut`. + string type_prefix = 4; + } + + // The payload message + oneof message_oneof { + Send send_coins_message = 1; + BridgeTransferOut bridge_transfer_out = 2; + } +} + +// Options for transaction encoding. +// Consider adding Json mode. +enum EncodingMode { + Protobuf = 0; // Protobuf-serialized (binary) +} + +// Options for transaction signing. +// Consider adding Direct mode when it is supported. +enum SigningMode { + Eip712 = 0; +} + +// Input data necessary to create a signed transaction. +message SigningInput { + // An encoding mode. + EncodingMode encoding_mode = 1; + + // A signing mode. + SigningMode signing_mode = 2; + + // Source account number + uint64 account_number = 3; + + // ETH Chain ID (string). + // Must be set if `signing_mode` is Eip712. + string eth_chain_id = 4; + + // Cosmos Chain ID (string) + string cosmos_chain_id = 5; + + // Transaction fee + Fee fee = 6; + + // Optional memo + string memo = 7; + + // Sequence number (account specific) + uint64 sequence = 8; + + // The secret private key used for signing (32 bytes). + bytes private_key = 9; + + // Message payloads. + repeated Message messages = 10; + + // Broadcast mode (included in output, relevant when broadcasting) + BroadcastMode mode = 11; + + bytes public_key = 12; +} + +// Result containing the signed and encoded transaction. +message SigningOutput { + // Signature + bytes signature = 1; + + // Signed transaction containing protobuf encoded, Base64-encoded form (Stargate case), + // wrapped in a ready-to-broadcast json. + string serialized = 2; + + // signatures array json string + string signature_json = 3; + + // error description + string error_message = 4; + + Common.Proto.SigningError error = 5; +} diff --git a/src/proto/Harmony.proto b/src/proto/Harmony.proto index 72762c24acd..85852dbc348 100644 --- a/src/proto/Harmony.proto +++ b/src/proto/Harmony.proto @@ -3,56 +3,68 @@ syntax = "proto3"; package TW.Harmony.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Input data necessary to create a signed transaction. message SigningInput { - // Chain identifier (256-bit number) + // Chain identifier (uint256, serialized big endian) bytes chain_id = 1; - // Private key. + // The secret private key used for signing (32 bytes). bytes private_key = 2; + // The payload message oneof message_oneof { TransactionMessage transaction_message = 3; StakingMessage staking_message = 4; } } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; + // THE V,R,S components of the signature bytes v = 2; bytes r = 3; bytes s = 4; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 5; + + // error code description + string error_message = 6; } +// A Transfer message message TransactionMessage { - // Nonce (256-bit number) + // Nonce (uint256, serialized big endian) bytes nonce = 1; - // Gas price (256-bit number) + // Gas price (uint256, serialized big endian) bytes gas_price = 2; - // Gas limit (256-bit number) + // Gas limit (uint256, serialized big endian) bytes gas_limit = 3; // Recipient's address. string to_address = 4; - // Amount to send in wei (256-bit number) + // Amount to send in wei (uint256, serialized big endian) bytes amount = 5; // Optional payload bytes payload = 6; - // From shard ID (256-bit number) + // From shard ID (uint256, serialized big endian) bytes from_shard_id = 7; - // To Shard ID (256-bit number) + // To Shard ID (uint256, serialized big endian) bytes to_shard_id = 8; } +// A Staking message. message StakingMessage { // StakeMsg oneof stake_msg { @@ -63,16 +75,17 @@ message StakingMessage { DirectiveCollectRewards collect_rewards = 5; } - // Nonce (256-bit number) + // Nonce (uint256, serialized big endian) bytes nonce = 6; - // Gas price (256-bit number) + // Gas price (uint256, serialized big endian) bytes gas_price = 7; - // Gas limit (256-bit number) + // Gas limit (uint256, serialized big endian) bytes gas_limit = 8; } +// Description for a validator message Description { string name = 1; string identity = 2; @@ -81,21 +94,38 @@ message Description { string details = 5; } +// A variable precision number message Decimal { + // The 'raw' value bytes value = 1; + + // The precision (number of decimals) bytes precision = 2; } +// Represents validator commission rule message CommissionRate { + // The rate Decimal rate = 1; + + // Maximum rate Decimal max_rate = 2; + + // Maximum of rate change Decimal max_change_rate = 3; } +// Create Validator directive message DirectiveCreateValidator { + // Address of validator string validator_address = 1; + + // Description, name etc. Description description = 2; + + // Rates CommissionRate commission_rates = 3; + bytes min_self_delegation = 4; bytes max_total_delegation = 5; repeated bytes slot_pub_keys = 6; @@ -103,7 +133,10 @@ message DirectiveCreateValidator { bytes amount = 8; } + +// Edit Validator directive message DirectiveEditValidator { + // Validator address string validator_address = 1; Description description = 2; Decimal commission_rate = 3; @@ -115,18 +148,33 @@ message DirectiveEditValidator { bytes active = 9; } +// Delegate directive message DirectiveDelegate { + // Delegator address string delegator_address = 1; + + // Validator address string validator_address = 2; + + // Delegate amount (uint256, serialized big endian) bytes amount = 3; } +// Undelegate directive message DirectiveUndelegate { + // Delegator address string delegator_address = 1; + + // Validator address string validator_address = 2; + + // Undelegate amount (uint256, serialized big endian) bytes amount = 3; } + +// Collect reward message DirectiveCollectRewards { + // Delegator address string delegator_address = 1; -} \ No newline at end of file +} diff --git a/src/proto/Hedera.proto b/src/proto/Hedera.proto new file mode 100644 index 00000000000..4f331c77d3e --- /dev/null +++ b/src/proto/Hedera.proto @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +syntax = "proto3"; + +package TW.Hedera.Proto; +option java_package = "wallet.core.jni.proto"; + +// An exact date and time. This is the same data structure as the protobuf Timestamp.proto +// (see the comments in https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto) +message Timestamp { + // Number of complete seconds since the start of the epoch + int64 seconds = 1; + // Number of nanoseconds since the start of the last second + int32 nanos = 2; +} + +// The ID for a transaction. This is used for retrieving receipts and records for a transaction, for +// appending to a file right after creating it, for instantiating a smart contract with bytecode in +// a file just created, and internally by the network for detecting when duplicate transactions are +// submitted. A user might get a transaction processed faster by submitting it to N nodes, each with +// a different node account, but all with the same TransactionID. Then, the transaction will take +// effect when the first of all those nodes submits the transaction and it reaches consensus. The +// other transactions will not take effect. So this could make the transaction take effect faster, +// if any given node might be slow. However, the full transaction fee is charged for each +// transaction, so the total fee is N times as much if the transaction is sent to N nodes. +// +// Applicable to Scheduled Transactions: +// - The ID of a Scheduled Transaction has transactionValidStart and accountIDs inherited from the +// ScheduleCreate transaction that created it. That is to say that they are equal +// - The scheduled property is true for Scheduled Transactions +// - transactionValidStart, accountID and scheduled properties should be omitted +message TransactionID { + // The transaction is invalid if consensusTimestamp < transactionID.transactionStartValid + Timestamp transactionValidStart = 1; + // The Account ID that paid for this transaction + string accountID = 2; + // Whether the Transaction is of type Scheduled or no + bool scheduled = 3; + // The identifier for an internal transaction that was spawned as part + // of handling a user transaction. (These internal transactions share the + // transactionValidStart and accountID of the user transaction, so a + // nonce is necessary to give them a unique TransactionID.) + // + // An example is when a "parent" ContractCreate or ContractCall transaction + // calls one or more HTS precompiled contracts; each of the "child" transactions spawned for a precompile has a id + // with a different nonce. + int32 nonce = 4; +} + +// Necessary fields to process a TransferMessage +message TransferMessage { + // Source Account address (string) + string from = 1; + // Destination Account address (string) + string to = 2; + // Amount to be transferred (sint64) + sint64 amount = 3; +} + +// A single transaction. All transaction types are possible here. +message TransactionBody { + // The ID for this transaction, which includes the payer's account (the account paying the transaction fee). + // If two transactions have the same transactionID, they won't both have an effect + TransactionID transactionID = 1; + // The account of the node that submits the client's transaction to the network + string nodeAccountID = 2; + // The maximum transaction fee the client is willing to pay + uint64 transactionFee = 3; + // The transaction is invalid if consensusTimestamp > transactionID.transactionValidStart + transactionValidDuration + int64 transactionValidDuration = 4; + // Any notes or descriptions that should be put into the record (max length 100) + string memo = 5; + // The choices here are arranged by service in roughly lexicographical order. The field ordinals are non-sequential, + // and a result of the historical order of implementation. + oneof data { + // Transfer amount between accounts + TransferMessage transfer = 6; + } +} + +// Input data necessary to create a signed transaction. +message SigningInput { + // Private key to sign the transaction (bytes) + bytes private_key = 1; + + // The transaction body + TransactionBody body = 2; +} + +// Transaction signing output. +message SigningOutput { + // Signed and encoded transaction bytes. + bytes encoded = 1; +} diff --git a/src/proto/IOST.proto b/src/proto/IOST.proto new file mode 100644 index 00000000000..0566e0fde68 --- /dev/null +++ b/src/proto/IOST.proto @@ -0,0 +1,101 @@ +syntax = "proto3"; + +package TW.IOST.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// The message defines transaction action struct. +message Action { + // contract name + string contract = 1; + // action name + string action_name = 2; + // data + string data = 3; +} + +// The message defines transaction amount limit struct. +message AmountLimit { + // token name + string token = 1; + // limit value + string value = 2; +} + +// The enumeration defines the signature algorithm. +enum Algorithm { + // unknown + UNKNOWN = 0; + // secp256k1 + SECP256K1 = 1; + // ed25519 + ED25519 = 2; +} + +// The message defines signature struct. +message Signature { + // signature algorithm + Algorithm algorithm = 1; + // signature bytes + bytes signature = 2; + // public key + bytes public_key = 3; +} + +// The message defines the transaction request. +message Transaction { + // transaction timestamp + int64 time = 1; + // expiration timestamp + int64 expiration = 2; + // gas price + double gas_ratio = 3; + // gas limit + double gas_limit = 4; + // delay nanoseconds + int64 delay = 5; + // chain id + uint32 chain_id = 6; + // action list + repeated Action actions = 7; + // amount limit + repeated AmountLimit amount_limit = 8; + // signer list + repeated string signers = 9; + // signatures of signers + repeated Signature signatures = 10; + // publisher + string publisher = 11; + // signatures of publisher + repeated Signature publisher_sigs = 12; +} + +message AccountInfo { + string name = 1; + bytes active_key = 2; + bytes owner_key = 3; +} + +// Input data necessary to create a signed transaction. +message SigningInput { + AccountInfo account = 1; + Transaction transaction_template = 2; + string transfer_destination = 3; + string transfer_amount = 4; + string transfer_memo = 5; +} + +// Transaction signing output. +message SigningOutput { + // Signed transaction + Transaction transaction = 1; + // Signed and encoded transaction bytes. + bytes encoded = 2; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 3; + + // error code description + string error_message = 4; +} diff --git a/src/proto/Icon.proto b/src/proto/Icon.proto index 8319b026d7c..9b378f4dc82 100644 --- a/src/proto/Icon.proto +++ b/src/proto/Icon.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.Icon.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Input data necessary to create a signed transaction. message SigningInput { // Sender address. @@ -11,7 +13,7 @@ message SigningInput { // Recipient address. string to_address = 2; - // Transfer amount. + // Transfer amount (uint256, serialized big endian) bytes value = 3; // The amount of step to send with the transaction. @@ -26,15 +28,20 @@ message SigningInput { // Network identifier bytes network_id = 7; - // Private key. + // The secret private key used for signing (32 bytes). bytes private_key = 8; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // JSON-encoded transaction parameters. string encoded = 1; // Signature. bytes signature = 2; + + // error description + string error_message = 3; + + Common.Proto.SigningError error = 4; } diff --git a/src/proto/InternetComputer.proto b/src/proto/InternetComputer.proto new file mode 100644 index 00000000000..a74c86107d3 --- /dev/null +++ b/src/proto/InternetComputer.proto @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +syntax = "proto3"; + +package TW.InternetComputer.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// Internet Computer Transactions +message Transaction { + + // ICP ledger transfer arguments + message Transfer { + string to_account_identifier = 1; + uint64 amount = 2; + uint64 memo = 3; + uint64 current_timestamp_nanos = 4; + uint64 permitted_drift = 5; + } + + // Payload transfer + oneof transaction_oneof { + Transfer transfer = 1; + } +} + +// Input data necessary to create a signed transaction. +message SigningInput { + bytes private_key = 1; + Transaction transaction = 2; +} + +// Transaction signing output. +message SigningOutput { + // Signed and encoded transaction bytes. + // NOTE: Before sending to the Rosetta node, this value should be hex-encoded before using with the JSON structure. + bytes signed_transaction = 1; + + Common.Proto.SigningError error = 2; + + string error_message = 3; +} \ No newline at end of file diff --git a/src/proto/IoTeX.proto b/src/proto/IoTeX.proto index 1df337cf627..47a7238de0d 100644 --- a/src/proto/IoTeX.proto +++ b/src/proto/IoTeX.proto @@ -3,96 +3,164 @@ syntax = "proto3"; package TW.IoTeX.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + +// A transfer message Transfer { + // Amount (as string) string amount = 1; + + // Destination address string recipient = 2; + + // Payload data bytes payload = 3; } +// A Staking message message Staking { - // create stake - message Create { - string candidateName = 1; - string stakedAmount = 2; - uint32 stakedDuration = 3; - bool autoStake = 4; - bytes payload = 5; - } - - // unstake or withdraw - message Reclaim { - uint64 bucketIndex = 1; - bytes payload = 2; - } - - // add the amount of bucket - message AddDeposit { - uint64 bucketIndex = 1; - string amount = 2; - bytes payload = 3; - } - - // restake the duration and autoStake flag of bucket - message Restake { - uint64 bucketIndex = 1; - uint32 stakedDuration = 2; - bool autoStake = 3; - bytes payload = 4; - } - - // move the bucket to vote for another candidate or transfer the ownership of bucket to another voters - message ChangeCandidate { - uint64 bucketIndex = 1; - string candidateName = 2; - bytes payload = 3; - } - - message TransferOwnership { - uint64 bucketIndex = 1; - string voterAddress = 2; - bytes payload = 3; - } - - message CandidateBasicInfo { - string name = 1; - string operatorAddress = 2; - string rewardAddress = 3; - } - - message CandidateRegister { - CandidateBasicInfo candidate = 1; - string stakedAmount = 2; - uint32 stakedDuration = 3; - bool autoStake = 4; - string ownerAddress = 5; // if ownerAddress is absent, owner of candidate is the sender - bytes payload = 6; - } - oneof message { - Create stakeCreate = 1; - Reclaim stakeUnstake = 2; - Reclaim stakeWithdraw = 3; - AddDeposit stakeAddDeposit = 4; - Restake stakeRestake = 5; - ChangeCandidate stakeChangeCandidate = 6; - TransferOwnership stakeTransferOwnership = 7; - CandidateRegister candidateRegister = 8; - CandidateBasicInfo candidateUpdate = 9; - } + // create stake + message Create { + // validator name + string candidateName = 1; + + // amount to be staked + string stakedAmount = 2; + + // duration + uint32 stakedDuration = 3; + + // auto-restake + bool autoStake = 4; + + // payload data + bytes payload = 5; + } + + // unstake or withdraw + message Reclaim { + // index to claim + uint64 bucketIndex = 1; + + // payload data + bytes payload = 2; + } + + // add the amount of bucket + message AddDeposit { + // index + uint64 bucketIndex = 1; + + // amount to add + string amount = 2; + + // payload data + bytes payload = 3; + } + + // restake the duration and autoStake flag of bucket + message Restake { + // index + uint64 bucketIndex = 1; + + // stake duration + uint32 stakedDuration = 2; + + // auto re-stake + bool autoStake = 3; + + // payload data + bytes payload = 4; + } + + // move the bucket to vote for another candidate or transfer the ownership of bucket to another voters + message ChangeCandidate { + // index + uint64 bucketIndex = 1; + + // validator name + string candidateName = 2; + + // payload data + bytes payload = 3; + } + + // transfer ownserhip of stake + message TransferOwnership { + // index + uint64 bucketIndex = 1; + + // address of voter + string voterAddress = 2; + + // payload data + bytes payload = 3; + } + + // Candidate (validator) info + message CandidateBasicInfo { + string name = 1; + string operatorAddress = 2; + string rewardAddress = 3; + } + + // Register a Candidate + message CandidateRegister { + CandidateBasicInfo candidate = 1; + string stakedAmount = 2; + uint32 stakedDuration = 3; + bool autoStake = 4; + string ownerAddress = 5; // if ownerAddress is absent, owner of candidate is the sender + bytes payload = 6; + } + + // the payload message + oneof message { + Create stakeCreate = 1; + Reclaim stakeUnstake = 2; + Reclaim stakeWithdraw = 3; + AddDeposit stakeAddDeposit = 4; + Restake stakeRestake = 5; + ChangeCandidate stakeChangeCandidate = 6; + TransferOwnership stakeTransferOwnership = 7; + CandidateRegister candidateRegister = 8; + CandidateBasicInfo candidateUpdate = 9; + } } +// Arbitrary contract call message ContractCall { + // amount string amount = 1; + + // contract address string contract = 2; + + // payload data bytes data = 3; } -// transaction signing input +// Input data necessary to create a signed transaction. message SigningInput { + // Transaction version uint32 version = 1; + + // Nonce (should be larger than in the last transaction of the account) uint64 nonce = 2; + + // Limit for the gas used uint64 gasLimit = 3; + + // Gas price string gasPrice = 4; - bytes privateKey = 5; + + // The chain id of blockchain + uint32 chainID = 5; + + // The secret private key used for signing (32 bytes). + bytes privateKey = 6; + + // Payload transfer oneof action { Transfer transfer = 10; ContractCall call = 12; @@ -106,41 +174,68 @@ message SigningInput { Staking.TransferOwnership stakeTransferOwnership = 46; Staking.CandidateRegister candidateRegister = 47; Staking.CandidateBasicInfo candidateUpdate = 48; - } + } } -// transaction signing output +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded Action bytes bytes encoded = 1; // Signed Action hash bytes hash = 2; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 3; + + // error code description + string error_message = 4; } +// An Action structure +// Used internally message ActionCore { - uint32 version = 1; - uint64 nonce = 2; - uint64 gasLimit = 3; - string gasPrice = 4; - oneof action { - Transfer transfer = 10; - ContractCall execution = 12; - // Native staking - Staking.Create stakeCreate = 40; - Staking.Reclaim stakeUnstake = 41; - Staking.Reclaim stakeWithdraw = 42; - Staking.AddDeposit stakeAddDeposit = 43; - Staking.Restake stakeRestake = 44; - Staking.ChangeCandidate stakeChangeCandidate = 45; - Staking.TransferOwnership stakeTransferOwnership = 46; - Staking.CandidateRegister candidateRegister = 47; - Staking.CandidateBasicInfo candidateUpdate = 48; - } + // version number + uint32 version = 1; + + // Nonce (should be larger than in the last transaction of the account) + uint64 nonce = 2; + + // Gas limit + uint64 gasLimit = 3; + + // Gas price + string gasPrice = 4; + + // Chain ID + uint32 chainID = 5; + + // action payload + oneof action { + Transfer transfer = 10; + ContractCall execution = 12; + // Native staking + Staking.Create stakeCreate = 40; + Staking.Reclaim stakeUnstake = 41; + Staking.Reclaim stakeWithdraw = 42; + Staking.AddDeposit stakeAddDeposit = 43; + Staking.Restake stakeRestake = 44; + Staking.ChangeCandidate stakeChangeCandidate = 45; + Staking.TransferOwnership stakeTransferOwnership = 46; + Staking.CandidateRegister candidateRegister = 47; + Staking.CandidateBasicInfo candidateUpdate = 48; + } } +// Signed Action +// Used internally message Action { - ActionCore core = 1; - bytes senderPubKey = 2; - bytes signature = 3; -} \ No newline at end of file + // Action details + ActionCore core = 1; + + // public key + bytes senderPubKey = 2; + + // the signature + bytes signature = 3; +} diff --git a/src/proto/LiquidStaking.proto b/src/proto/LiquidStaking.proto new file mode 100644 index 00000000000..b91c1bdf3b4 --- /dev/null +++ b/src/proto/LiquidStaking.proto @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +syntax = "proto3"; + +package TW.LiquidStaking.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Ethereum.proto"; +import "Cosmos.proto"; +import "Aptos.proto"; + +// Enum for supported coins for liquid staking +enum Coin { + // Previously, MATIC. + POL = 0; + ATOM = 1; + BNB = 2; + APT = 3; + ETH = 4; +} + +// Enum for supported target blockchains for liquid staking +enum Blockchain { + ETHEREUM = 0; + POLYGON = 1; + STRIDE = 2; + BNB_BSC = 3; + APTOS = 4; +} + +// Enum for supported liquid staking protocols +enum Protocol { + Strader = 0; + Stride = 1; + Tortuga = 2; + Lido = 3; +} + +// Enum for status codes to indicate the result of an operation +enum StatusCode { + OK = 0; + ERROR_ACTION_NOT_SET = 1; + ERROR_TARGETED_BLOCKCHAIN_NOT_SUPPORTED_BY_PROTOCOL = 2; + ERROR_SMART_CONTRACT_ADDRESS_NOT_SET = 3; + ERROR_INPUT_PROTO_DESERIALIZATION = 4; + ERROR_OPERATION_NOT_SUPPORTED_BY_PROTOCOL = 5; +} + +// Message to represent the status of an operation +message Status { + // Status code of the operation + StatusCode code = 1; + + // Optional error message, populated in case of error + string message = 2; +} + +// Message to represent the asset for staking operations +message Asset { + // Coin to be staked + Coin staking_token = 1; + // Optional, liquid_token to be manipulated: unstake, claim rewards + string liquid_token = 2; + // Denom of the asset to be manipulated, required by some liquid staking protocols + string denom = 3; + + // Address for building the appropriate input + string from_address = 4; +} + +// Message to represent a stake operation +message Stake { + Asset asset = 1; + string amount = 2; +} + +// Message to represent an unstake operation +message Unstake { + Asset asset = 1; + string amount = 2; + // Some cross-chain protocols propose u to setup a receiver_address + string receiver_address = 3; + // Some cross-chain protocols propose u to set the receiver chain_id, it allows auto-claim after probation period + string receiver_chain_id = 4; +} + +// Message to represent a withdraw operation +message Withdraw { + Asset asset = 1; + string amount = 2; + // Sometimes withdraw is just the index of a request, amount is already known by the SC + string idx = 3; +} + +// Message to represent the input for a liquid staking operation +message Input { + // Oneof field to specify the action: stake, unstake or withdraw + oneof action { + Stake stake = 1; + Unstake unstake = 2; + Withdraw withdraw = 3; + } + + // Optional smart contract address for EVM-based chains + string smart_contract_address = 4; + + // Protocol to be used for liquid staking + Protocol protocol = 5; + + // Target blockchain for the liquid staking operation + Blockchain blockchain = 6; +} + +// Message to represent the output of a liquid staking operation +message Output { + // Status of the liquid staking operation + Status status = 1; + + // Unsigned transaction input - needs to be completed and signed + oneof signing_input_oneof { + Ethereum.Proto.SigningInput ethereum = 2; + Cosmos.Proto.SigningInput cosmos = 3; + Aptos.Proto.SigningInput aptos = 4; + } +} diff --git a/src/proto/MultiversX.proto b/src/proto/MultiversX.proto new file mode 100644 index 00000000000..02162ef5796 --- /dev/null +++ b/src/proto/MultiversX.proto @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +syntax = "proto3"; + +package TW.MultiversX.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// Generic action. Using one of the more specific actions (e.g. transfers, see below) is recommended. +message GenericAction { + // Accounts involved + Accounts accounts = 1; + + // amount + string value = 2; + + // additional data + string data = 3; + + // transaction version + uint32 version = 4; + + // Generally speaking, the "options" field can be ignored (not set) by applications using TW Core. + uint32 options = 5; +} + +// EGLD transfer (move balance). +message EGLDTransfer { + // Accounts involved + Accounts accounts = 1; + + // Transfer amount (string) + string amount = 2; + string data = 3; + + // transaction version, if empty, the default value will be used + uint32 version = 4; +} + +// ESDT transfer (transfer regular ESDTs - fungible tokens). +message ESDTTransfer { + // Accounts involved + Accounts accounts = 1; + + // Token ID + string token_identifier = 2; + + // Transfer token amount (string) + string amount = 3; + + // transaction version, if empty, the default value will be used + uint32 version = 4; +} + +// ESDTNFT transfer (transfer NFTs, SFTs and Meta ESDTs). +message ESDTNFTTransfer { + // Accounts involved + Accounts accounts = 1; + + // tokens + string token_collection = 2; + + // nonce of the token + uint64 token_nonce = 3; + + // transfer amount + string amount = 4; + + // transaction version, if empty, the default value will be used + uint32 version = 5; +} + +// Transaction sender & receiver etc. +message Accounts { + // Nonce of the sender + uint64 sender_nonce = 1; + + // Sender address + string sender = 2; + + // Sender username + string sender_username = 3; + + // Receiver address + string receiver = 4; + + // Receiver username + string receiver_username = 5; + + // Guardian address + string guardian = 6; +} + +// Input data necessary to create a signed transaction. +message SigningInput { + // The secret private key used for signing (32 bytes). + bytes private_key = 1; + + // Chain identifier, string + string chain_id = 2; + + // Gas price + uint64 gas_price = 3; + + // Limit for the gas used + uint64 gas_limit = 4; + + // transfer payload + oneof message_oneof { + GenericAction generic_action = 5; + EGLDTransfer egld_transfer = 6; + ESDTTransfer esdt_transfer = 7; + ESDTNFTTransfer esdtnft_transfer = 8; + } +} + +// Result containing the signed and encoded transaction. +message SigningOutput { + string encoded = 1; + string signature = 2; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 3; + + // error code description + string error_message = 4; +} diff --git a/src/proto/NEAR.proto b/src/proto/NEAR.proto index 8e9c4d6f020..920fa57d587 100644 --- a/src/proto/NEAR.proto +++ b/src/proto/NEAR.proto @@ -3,63 +3,114 @@ syntax = "proto3"; package TW.NEAR.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + +// Public key with type message PublicKey { + // Key type uint32 key_type = 1; + + // The public key data bytes data = 2; } +// Permissions for a function call message FunctionCallPermission { - bytes allowance = 1; // uint128 / little endian byte order + // uint128 / big endian byte order + bytes allowance = 1; + string receiver_id = 2; + + repeated string method_names = 3; } +// Full access message FullAccessPermission { } +// Access key: nonce + permission message AccessKey { + // Nonce uint64 nonce = 1; + + // Permission oneof permission { FunctionCallPermission function_call = 2; FullAccessPermission full_access = 3; } } +// Create Account message CreateAccount { } +// Deploying a contract message DeployContract { bytes code = 1; } +// A method/function call message FunctionCall { + // Method/function name string method_name = 1; + + // input arguments bytes args = 2; + + // gas uint64 gas = 3; - bytes deposit = 4; // uint128 / little endian byte order + + // uint128 / big endian byte order + bytes deposit = 4; } +// Transfer message Transfer { - bytes deposit = 1; // uint128 / little endian byte order + // amount; uint128 / big endian byte order + bytes deposit = 1; } +// Stake message Stake { - bytes stake = 1; // uint128 / little endian byte order - string public_key = 2; + // amount; uint128 / big endian byte order + bytes stake = 1; + + // owner public key + PublicKey public_key = 2; } +// Add a key message AddKey { PublicKey public_key = 1; AccessKey access_key = 2; } +// Delete a key message DeleteKey { PublicKey public_key = 1; } +// Delete account message DeleteAccount { string beneficiary_id = 1; } +// Fungible token transfer +message TokenTransfer { + // Token amount. Base-10 decimal string. + string token_amount = 1; + + // ID of the receiver. + string receiver_id = 2; + + // Gas. + uint64 gas = 3; + + // NEAR deposit amount; uint128 / big endian byte order. + bytes deposit = 4; +} + +// Represents an action message Action { oneof payload { CreateAccount create_account = 1; @@ -70,23 +121,46 @@ message Action { AddKey add_key = 6; DeleteKey delete_key = 7; DeleteAccount delete_account = 8; + // Gap in field numbering is intentional as it's not a standard NEAR action. + TokenTransfer token_transfer = 13; } } // Input data necessary to create a signed order. message SigningInput { + // ID of the sender string signer_id = 1; + + // Nonce (should be larger than in the last transaction of the account) uint64 nonce = 2; + + // ID of the receiver string receiver_id = 3; + + // Recent block hash bytes block_hash = 4; + + // Payload action(s) repeated Action actions = 5; + // The secret private key used for signing (32 bytes). bytes private_key = 6; + + // The public key used for compiling a transaction with a signature. + bytes public_key = 7; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed transaction blob bytes signed_transaction = 1; - bytes hash = 2; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; + + // Hash of the transaction + bytes hash = 4; } diff --git a/src/proto/NEO.proto b/src/proto/NEO.proto index 0093145f29f..acb0984d947 100644 --- a/src/proto/NEO.proto +++ b/src/proto/NEO.proto @@ -5,41 +5,113 @@ option java_package = "wallet.core.jni.proto"; import "Common.proto"; +// Input for a transaction (output of a prev tx) message TransactionInput { + // Previous tx hash bytes prev_hash = 1; + + // Output index fixed32 prev_index = 2; // unspent value of UTXO int64 value = 3; + // Asset string asset_id = 4; } +// extra address of Output +message OutputAddress { + // Amount (as string) + sint64 amount = 1; + + // destination address + string to_address = 2; +} + +// Output of a transaction message TransactionOutput { + // Asset string asset_id = 1; + + // Amount (as string) sint64 amount = 2; + + // destination address string to_address = 3; + + // change address string change_address = 4; + + // extra output + repeated OutputAddress extra_outputs = 5; +} + +// Transaction +message Transaction { + // nep5 token transfer transaction + message Nep5Transfer { + string asset_id = 1; + string from = 2; + string to = 3; + + // Amount to send (256-bit number) + bytes amount = 4; + + // determine if putting THROWIFNOT & RET instructions + bool script_with_ret = 5; + } + + // Generic invocation transaction + message InvocationGeneric { + // gas to use + uint64 gas = 1; + + // Contract call payload data + bytes script = 2; + } + + oneof transaction_oneof { + Nep5Transfer nep5_transfer = 1; + InvocationGeneric invocation_generic = 2; + } } // Input data necessary to create a signed transaction. message SigningInput { + // Available transaction inputs repeated TransactionInput inputs = 1; + + // Transaction outputs repeated TransactionOutput outputs = 2; + + // The secret private key used for signing (32 bytes). bytes private_key = 3; + + // Fee int64 fee = 4; + + // Asset ID for gas string gas_asset_id = 5; + + // Address for the change string gas_change_address = 6; + + // Optional transaction plan (if missing it will be computed) TransactionPlan plan = 7; + Transaction transaction = 8; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; // Optional error Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } // Describes a preliminary transaction output plan. @@ -50,12 +122,27 @@ message TransactionOutputPlan { // Maximum available amount. int64 available_amount = 2; + // Amount that is left as change int64 change = 3; + + // Asset string asset_id = 4; + + // Destination address string to_address = 5; + + // Address for the change string change_address = 6; + + // extra output + repeated OutputAddress extra_outputs = 7; }; +message TransactionAttributePlan { + int32 usage = 1; + bytes data = 2; +} + // Describes a preliminary transaction plan. message TransactionPlan { // Used assets @@ -69,4 +156,7 @@ message TransactionPlan { // Optional error Common.Proto.SigningError error = 4; -}; \ No newline at end of file + + // Attribute + repeated TransactionAttributePlan attributes = 5; +}; diff --git a/src/proto/NULS.proto b/src/proto/NULS.proto index 2bcfd9d7a2e..7bfe9d62238 100644 --- a/src/proto/NULS.proto +++ b/src/proto/NULS.proto @@ -3,65 +3,138 @@ syntax = "proto3"; package TW.NULS.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + +// Transaction from address message TransactionCoinFrom { + // Source address string from_address = 1; + + // Chain ID uint32 assets_chainid = 2; + + // ID of the asset uint32 assets_id = 3; - //tranaction out amount (256-bit number) + + // transaction out amount (256-bit number) bytes id_amount = 4; - //8 bytes + + // Nonce, 8 bytes bytes nonce = 5; - //lock status: 1 locked; 0 unlocked + + // lock status: 1 locked; 0 unlocked uint32 locked = 6; } +// Transaction to a destination message TransactionCoinTo { + // destination address string to_address = 1; + + // Chain ID uint32 assets_chainid = 2; + + // ID of the asset uint32 assets_id = 3; - // tranaction amount (256-bit number) + + // transaction amount (uint256, serialized big endian) bytes id_amount = 4; + + // lock time uint32 lock_time = 5; } +// A signature message Signature { + // Length of public key data uint32 pkey_len = 1; + + // The public key bytes public_key = 2; + + // The length of the signature uint32 sig_len = 3; + + // The signature data bytes signature = 4; } +// A transaction message Transaction { + // transaction type uint32 type = 1; + + // Timestamp of the transaction uint32 timestamp = 2; + + // Optional string remark string remark = 3; + + // The raw data bytes tx_data = 4; - //CoinFrom - TransactionCoinFrom input = 5; - //CoinTo - TransactionCoinTo output = 6; + // CoinFrom + repeated TransactionCoinFrom input = 5; + + // CoinTo + repeated TransactionCoinTo output = 6; + + // Signature Signature tx_sigs = 7; + + // Tx hash uint32 hash = 8; } // Input data necessary to create a signed order. message SigningInput { + // The secret private key used for signing (32 bytes). bytes private_key = 1; + + // Source address string from = 2; + + // Destination address string to = 3; + + // Transfer amount (uint256, serialized big endian) bytes amount = 4; + + // Chain ID uint32 chain_id = 5; + + // Asset ID uint32 idassets_id = 6; - //The last 8 bytes of latest transaction hash + + // The last 8 bytes of latest transaction hash bytes nonce = 7; + + // Optional memo remark string remark = 8; + // Account balance bytes balance = 9; + // time, accurate to the second uint32 timestamp = 10; + // external address paying fee, required for token transfer, optional for NULS transfer, depending on if an external fee payer is provided. If provided, it will be the fee paying address. + string fee_payer = 11; + // fee payer address nonce, required for token transfer, optional for NULS transfer, depending on if fee_payer is provided. + bytes fee_payer_nonce = 12; + // fee payer address private key, required for token transfer, optional for NULS transfer, depending on if fee_payer is provided. + bytes fee_payer_private_key = 13; + // fee payer NULS balance, it is required for token transfer. optional for NULS transfer, depending on if fee_payer is provided. + bytes fee_payer_balance = 14; } +// Result containing the signed and encoded transaction. message SigningOutput { + // Encoded transaction bytes encoded = 1; -} \ No newline at end of file + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; +} diff --git a/src/proto/Nano.proto b/src/proto/Nano.proto index 954c7930e28..f9bc057cb13 100644 --- a/src/proto/Nano.proto +++ b/src/proto/Nano.proto @@ -3,14 +3,17 @@ syntax = "proto3"; package TW.Nano.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Input data necessary to create a signed transaction. message SigningInput { - // Private key + // The secret private key used for signing (32 bytes). bytes private_key = 1; // Optional parent block hash bytes parent_block = 2; + // Receive/Send reference oneof link_oneof { // Hash of a block to receive from bytes link_block = 3; @@ -26,14 +29,25 @@ message SigningInput { // Work string work = 7; + + // Pulic key used for building preImage (32 bytes). + bytes public_key = 8; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signature bytes signature = 1; + // Block hash bytes block_hash = 2; - // Json representation of the block + + // JSON representation of the block string json = 3; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 4; + + // error code description + string error_message = 5; } diff --git a/src/proto/Nebulas.proto b/src/proto/Nebulas.proto index fc8e7e9acf9..215bd503495 100644 --- a/src/proto/Nebulas.proto +++ b/src/proto/Nebulas.proto @@ -8,42 +8,47 @@ message SigningInput { // sender's address. string from_address = 1; - // Chain identifier (256-bit number) + // Chain identifier (uint256, serialized big endian) bytes chain_id = 2; - // Nonce (256-bit number) + // Nonce (uint256, serialized big endian) bytes nonce = 3; - // Gas price (256-bit number) + // Gas price (uint256, serialized big endian) bytes gas_price = 4; - // Gas limit (256-bit number) + // Gas limit (uint256, serialized big endian) bytes gas_limit = 5; // Recipient's address. string to_address = 6; - // Amount to send in wei, 1 NAS = 10^18 Wei (256-bit number) + // Amount to send in wei, 1 NAS = 10^18 Wei (uint256, serialized big endian) bytes amount = 7; - // Timestamp to create transaction (256-bit number) + // Timestamp to create transaction (uint256, serialized big endian) bytes timestamp = 8; // Optional payload string payload = 9; - // Private key. + // The secret private key used for signing (32 bytes). bytes private_key = 10; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { + // Algorithm code uint32 algorithm = 1; + + // The signature bytes signature = 2; + + // Encoded transaction string raw = 3; } -// +// Generic data message Data { string type = 1; bytes payload = 2; @@ -51,17 +56,39 @@ message Data { // Raw transaction data message RawTransaction { + // tx hash bytes hash = 1; + + // source address bytes from = 2; + + // destination address bytes to = 3; + + // amount (uint256, serialized big endian) bytes value = 4; + + // Nonce (should be larger than in the last transaction of the account) uint64 nonce = 5; + + // transaction timestamp int64 timestamp = 6; + + // generic data Data data = 7; + + // chain ID (4 bytes) uint32 chain_id = 8; + + // gas price (uint256, serialized big endian) bytes gas_price = 9; + + // gas limit (uint256, serialized big endian) bytes gas_limit = 10; + // algorithm uint32 alg = 11; + + // signature bytes sign = 12; -} \ No newline at end of file +} diff --git a/src/proto/Nervos.proto b/src/proto/Nervos.proto new file mode 100644 index 00000000000..f662d81b1ce --- /dev/null +++ b/src/proto/Nervos.proto @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +syntax = "proto3"; + +package TW.Nervos.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// Nervos transaction plan +message TransactionPlan { + // A list of cell deps. + repeated CellDep cell_deps = 1; + + // A list of header deps. + repeated bytes header_deps = 2; + + // A list of 1 or more selected cells for this transaction + repeated Cell selected_cells = 3; + + // A list of 1 or more outputs by this transaction + repeated CellOutput outputs = 4; + + // A list of outputs data. + repeated bytes outputs_data = 5; + + // Optional error + Common.Proto.SigningError error = 6; +} + +// Nervos cell dep. +message CellDep { + // Prevents the transaction to be mined before an absolute or relative time + string dep_type = 1; + + // Reference to the previous transaction's output. + OutPoint out_point = 2; +} + +// Nervos transaction out-point reference. +message OutPoint { + // The hash of the referenced transaction. + bytes tx_hash = 1; + + // The index of the specific output in the transaction. + uint32 index = 2; +} + +// Nervos cell output. +message CellOutput { + // Transaction amount. + uint64 capacity = 1; + + // Lock script + Script lock = 2; + + // Type script + Script type = 3; +} + +// Nervos script +message Script { + // Code hash + bytes code_hash = 1; + + // Hash type + string hash_type = 2; + + // args + bytes args = 3; +} + +// Transfer of native asset +message NativeTransfer { + // Recipient's address. + string to_address = 1; + + // Change address. + string change_address = 2; + + // Amount to send. + uint64 amount = 3; + + // If sending max amount. + bool use_max_amount = 4; +} + +// Token transfer (SUDT) +message SudtTransfer { + // Recipient's address. + string to_address = 1; + + // Change address. + string change_address = 2; + + // SUDT (Simple User Defined Token) address + bytes sudt_address = 3; + + // Amount to send. + string amount = 4; + + // If sending max amount. + bool use_max_amount = 5; +} + +// Deposit +message DaoDeposit { + // Recipient's address. + string to_address = 1; + + // Change address. + string change_address = 2; + + // Amount to deposit. + uint64 amount = 3; +} + +message DaoWithdrawPhase1 { + // Deposit cell + Cell deposit_cell = 1; + + // Change address. + string change_address = 2; +} + +message DaoWithdrawPhase2 { + // Deposit cell + Cell deposit_cell = 1; + + // Withdrawing cell + Cell withdrawing_cell = 2; + + // Amount + uint64 amount = 3; +} + +// Input data necessary to create a signed transaction. +message SigningInput { + // Transaction fee per byte. + uint64 byte_fee = 1; + + // The available secret private keys used for signing (32 bytes each). + repeated bytes private_key = 2; + + // Available unspent cell outputs. + repeated Cell cell = 3; + + // Optional transaction plan + TransactionPlan plan = 4; + + // The payload transfer + oneof operation_oneof { + NativeTransfer native_transfer = 5; + SudtTransfer sudt_transfer = 6; + DaoDeposit dao_deposit = 7; + DaoWithdrawPhase1 dao_withdraw_phase1 = 8; + DaoWithdrawPhase2 dao_withdraw_phase2 = 9; + } +} + +// An unspent cell output, that can serve as input to a transaction +message Cell { + // The unspent output + OutPoint out_point = 1; + + // Amount of the cell + uint64 capacity = 2; + + // Lock script + Script lock = 3; + + // Type script + Script type = 4; + + // Data + bytes data = 5; + + // Optional block number + uint64 block_number = 6; + + // Optional block hash + bytes block_hash = 7; + + // Optional since the cell is available to spend + uint64 since = 8; + + // Optional input type data to be included in witness + bytes input_type = 9; + + // Optional output type data to be included in witness + bytes output_type = 10; +} + +// Result containing the signed and encoded transaction. +message SigningOutput { + // Resulting transaction. Note that the amount may be different than the requested amount to account for fees and available funds. + string transaction_json = 1; + + // Transaction id + string transaction_id = 2; + + // Optional error + Common.Proto.SigningError error = 3; +} diff --git a/src/proto/Nimiq.proto b/src/proto/Nimiq.proto index 6c96981278d..76d00dd903e 100644 --- a/src/proto/Nimiq.proto +++ b/src/proto/Nimiq.proto @@ -5,18 +5,24 @@ option java_package = "wallet.core.jni.proto"; // Input data necessary to create a signed transaction. message SigningInput { + // The secret private key used for signing (32 bytes). bytes private_key = 1; + // Destination address string destination = 2; + // Amount of the transfer uint64 value = 3; + // Fee amount uint64 fee = 4; + // Validity start, in block height uint32 validity_start_height = 5; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { + // The encoded transaction bytes encoded = 1; } diff --git a/src/proto/Oasis.proto b/src/proto/Oasis.proto index 6b327044b58..cbf406bfe2d 100644 --- a/src/proto/Oasis.proto +++ b/src/proto/Oasis.proto @@ -1,37 +1,78 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. syntax = "proto3"; package TW.Oasis.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + +// Transfer message TransferMessage { + // destination address string to = 1; + + // Gas price uint64 gas_price = 2; // Amount values strings prefixed by zero. e.g. "\u000010000000" string gas_amount = 3; + // Amount values strings prefixed by zero string amount = 4; + // Nonce (should be larger than in the last transaction of the account) uint64 nonce = 5; + + // Context, see https://docs.oasis.dev/oasis-core/common-functionality/crypto#domain-separation string context = 6; } +message EscrowMessage { + uint64 gas_price = 1; + string gas_amount = 2; + uint64 nonce = 3; + + string account = 4; + string amount = 5; + + string context = 6; +} + +message ReclaimEscrowMessage { + uint64 gas_price = 1; + string gas_amount = 2; + uint64 nonce = 3; + + string account = 4; + string shares = 5; + + string context = 6; +} + +// Input data necessary to create a signed transaction. message SigningInput { + // The secret private key used for signing (32 bytes). bytes private_key = 1; + // Transfer payload oneof message { TransferMessage transfer = 2; + EscrowMessage escrow = 3; + ReclaimEscrowMessage reclaimEscrow = 4; } } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } diff --git a/src/proto/Ontology.proto b/src/proto/Ontology.proto index 28992aacfcf..6b837d53212 100644 --- a/src/proto/Ontology.proto +++ b/src/proto/Ontology.proto @@ -3,35 +3,55 @@ syntax = "proto3"; package TW.Ontology.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Input data necessary to create a signed transaction. message SigningInput { - + // Contract ID, e.g. "ONT" string contract = 1; + // Method, e.g. "transfer" string method = 2; + // The secret private key used for signing (32 bytes). bytes owner_private_key = 3; // base58 encode address string (160-bit number) string to_address = 4; + // Transfer amount uint64 amount = 5; + // Private key of the payer bytes payer_private_key = 6; + // Gas price uint64 gas_price = 7; + // Limit for gas used uint64 gas_limit = 8; // base58 encode address string (160-bit number) string query_address = 9; + // Nonce (should be larger than in the last transaction of the account) uint32 nonce = 10; + // base58 encode address string (160-bit number) + string owner_address = 11; + + // base58 encode address string (160-bit number) + string payer_address = 12; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } diff --git a/src/proto/Polkadot.proto b/src/proto/Polkadot.proto index d46afa39725..c25ddbd019e 100644 --- a/src/proto/Polkadot.proto +++ b/src/proto/Polkadot.proto @@ -3,17 +3,16 @@ syntax = "proto3"; package TW.Polkadot.Proto; option java_package = "wallet.core.jni.proto"; -enum Network { - POLKADOT = 0; - KUSAMA = 2; -} +import "Common.proto"; +// Destination options for reward enum RewardDestination { STAKED = 0; STASH = 1; CONTROLLER = 2; } +// An era, a period defined by a starting block and length message Era { // recent block number (called phase in polkadot code), should match block hash uint64 block_number = 1; @@ -22,52 +21,178 @@ message Era { uint64 period = 2; } +// Readable decoded call indices can be found at https://www.subscan.io/ +message CustomCallIndices { + // Module index. + int32 module_index = 4; + + // Method index. + int32 method_index = 5; +} + +// Optional call indices. +// Must be set if `SigningInput::network` is different from `Polkadot` and `Kusama`. +message CallIndices { + oneof variant { + CustomCallIndices custom = 1; + } +} + +// Balance transfer transaction message Balance { + // transfer message Transfer { + // destination address string to_address = 1; - bytes value = 2; // big integer + + // amount (uint256, serialized big endian) + bytes value = 2; + + // max 32 chars + string memo = 3; + + // call indices + CallIndices call_indices = 4; + } + // batch tranfer + message BatchTransfer { + // call indices + CallIndices call_indices = 1; + + repeated Transfer transfers = 2; } + // asset transfer + message AssetTransfer { + // call indices + CallIndices call_indices = 1; + + // destination + string to_address = 2; + + // value - BigInteger + bytes value = 3; + + // asset identifier + uint32 asset_id = 4; + + // fee asset identifier + uint32 fee_asset_id = 5; + } + + // batch asset transfer + message BatchAssetTransfer { + // call indices + CallIndices call_indices = 1; + + // fee asset identifier + uint32 fee_asset_id = 2; + + repeated AssetTransfer transfers = 3; + } + oneof message_oneof { Transfer transfer = 1; + BatchTransfer batchTransfer = 2; + AssetTransfer asset_transfer = 3; + BatchAssetTransfer batch_asset_transfer = 4; } } +// Staking transaction message Staking { + // Bond to a controller message Bond { + // controller ID (optional) string controller = 1; + + // amount (uint256, serialized big endian) bytes value = 2; + + // destination for rewards RewardDestination reward_destination = 3; + + // call indices + CallIndices call_indices = 4; } + // Bond to a controller, with nominators message BondAndNominate { + // controller ID (optional) string controller = 1; + + // amount (uint256, serialized big endian) bytes value = 2; + + // destination for rewards RewardDestination reward_destination = 3; + + // list of nominators repeated string nominators = 4; + + // call indices + CallIndices call_indices = 5; } + // Bond extra amount message BondExtra { + // amount (uint256, serialized big endian) bytes value = 1; + + // call indices + CallIndices call_indices = 2; } + // Unbond message Unbond { + // amount (uint256, serialized big endian) + bytes value = 1; + + // call indices + CallIndices call_indices = 2; + } + + // Rebond + message Rebond { + // amount (uint256, serialized big endian) bytes value = 1; + + // call indices + CallIndices call_indices = 2; } + // Withdraw unbonded amounts message WithdrawUnbonded { int32 slashing_spans = 1; + + // call indices + CallIndices call_indices = 2; } + // Nominate message Nominate { + // list of nominators repeated string nominators = 1; + + // call indices + CallIndices call_indices = 2; } + // Chill and unbound message ChillAndUnbond { + // amount (uint256, serialized big endian) bytes value = 1; + + // call indices + CallIndices call_indices = 2; } - message Chill {} + // Chill + message Chill { + // call indices + CallIndices call_indices = 1; + } + // Payload messsage oneof message_oneof { Bond bond = 1; BondAndNominate bond_and_nominate = 2; @@ -77,6 +202,61 @@ message Staking { Nominate nominate = 6; Chill chill = 7; ChillAndUnbond chill_and_unbond = 8; + Rebond rebond = 9; + } +} + +// Identity module +message Identity { + // Identity::join_identity_as_key call + message JoinIdentityAsKey { + // call indices + CallIndices call_indices = 1; + + // auth id + uint64 auth_id = 2; + } + + // Identity::add_authorization call + message AddAuthorization { + message Data { + bytes data = 1; + } + + message AuthData { + // authorization data, empty means all permissions, null means no permissions + Data asset = 1; + + // authorization data, empty means all permissions, null means no permissions + Data extrinsic = 2; + + // authorization data, empty means all permissions, null means no permissions + Data portfolio = 3; + } + + // call indices + CallIndices call_indices = 1; + + // address that will be added to the Identity + string target = 2; + + // authorization data, null means all permissions + AuthData data = 3; + + // expire time, unix seconds + uint64 expiry = 4; + } + + oneof message_oneof { + JoinIdentityAsKey join_identity_as_key = 1; + AddAuthorization add_authorization = 2; + } +} + +// Polymesh call +message PolymeshCall { + oneof message_oneof { + Identity identity_call = 2; } } @@ -85,29 +265,49 @@ message SigningInput { // Recent block hash, or genesis hash if era is not set bytes block_hash = 1; + // Genesis block hash (identifies the chain) bytes genesis_hash = 2; // Current account nonce uint64 nonce = 3; + // Specification version, e.g. 26. uint32 spec_version = 4; + + // Transaction version, e.g. 5. uint32 transaction_version = 5; - bytes tip = 6; // big integer + + // Optional tip to pay, big integer + bytes tip = 6; // Optional time validity limit, recommended, for replay-protection. Empty means Immortal. Era era = 7; + // The secret private key used for signing (32 bytes). bytes private_key = 8; - Network network = 9; + // Network type + uint32 network = 9; + + // Whether enable MultiAddress + bool multi_address = 10; + + // Payload message oneof message_oneof { - Balance balance_call = 10; - Staking staking_call = 11; + Balance balance_call = 11; + Staking staking_call = 12; + PolymeshCall polymesh_call = 13; } } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } diff --git a/src/proto/Ripple.proto b/src/proto/Ripple.proto index 82f7ebd39a4..d32b58e25c9 100644 --- a/src/proto/Ripple.proto +++ b/src/proto/Ripple.proto @@ -5,31 +5,166 @@ option java_package = "wallet.core.jni.proto"; import "Common.proto"; +// https://xrpl.org/currency-formats.html#token-amounts +message CurrencyAmount { + // Currency code + // https://xrpl.org/currency-formats.html#currency-codes + string currency = 1; + + // String number + // https://xrpl.org/currency-formats.html#string-numbers + string value = 2; + + // Account + // https://xrpl.org/accounts.html + string issuer = 3; +} + +// https://xrpl.org/trustset.html +message OperationTrustSet { + CurrencyAmount limit_amount = 1; +} + +// https://xrpl.org/payment.html +message OperationPayment { + // Transfer amount + oneof amount_oneof { + int64 amount = 1; + CurrencyAmount currency_amount = 2; + } + + // Target account + string destination = 3; + + // A Destination Tag + int64 destination_tag = 4; +} + +// https://xrpl.org/escrowcreate.html +message OperationEscrowCreate { + // Escrow amount + int64 amount = 1; + + // Beneficiary account + string destination = 2; + + // Destination Tag + int64 destination_tag = 3; + + // Escrow expire time + int64 cancel_after = 4; + + // Escrow release time + int64 finish_after = 5; + + // Crypto condition + // https://datatracker.ietf.org/doc/html/draft-thomas-crypto-conditions-02#section-8.1 + string condition = 6; +} + +// https://xrpl.org/escrowcancel.html +message OperationEscrowCancel { + // Funding account + string owner = 1; + + // Escrow transaction sequence + int32 offer_sequence = 2; +} + +// https://xrpl.org/escrowfinish.html +message OperationEscrowFinish { + // Funding account + string owner = 1; + + // Escrow transaction sequence + int32 offer_sequence = 2; + + // Crypto condition + string condition = 3; + + // Fulfillment matching condition + string fulfillment = 4; +} + +// https://xrpl.org/nftokenburn.html +message OperationNFTokenBurn { + // Hash256 NFTokenId + bytes nftoken_id = 1; +} + +// https://xrpl.org/nftokencreateoffer.html +message OperationNFTokenCreateOffer { + // Hash256 NFTokenId + bytes nftoken_id = 1; + + // Destination account + string destination = 2; +} + +// https://xrpl.org/nftokenacceptoffer.html +message OperationNFTokenAcceptOffer { + // Hash256 NFTokenOffer + bytes sell_offer = 1; +} + +// https://xrpl.org/nftokencanceloffer.html +message OperationNFTokenCancelOffer { + // Vector256 NFTokenOffers + repeated bytes token_offers = 1; +} + // Input data necessary to create a signed transaction. message SigningInput { - int64 amount = 1; + // Transfer fee + int64 fee = 1; + + // Account sequence number + int32 sequence = 2; + + // Ledger sequence number + int32 last_ledger_sequence = 3; + + // Source account + string account = 4; - int64 fee = 2; + // Transaction flags, optional + int64 flags = 5; - int32 sequence = 3; + // The secret private key used for signing (32 bytes). + bytes private_key = 6; - int32 last_ledger_sequence = 4; + oneof operation_oneof { + OperationTrustSet op_trust_set = 7; - string account = 5; + OperationPayment op_payment = 8; - string destination = 6; + OperationNFTokenBurn op_nftoken_burn = 9; - int64 destination_tag = 7; + OperationNFTokenCreateOffer op_nftoken_create_offer = 10; - int64 flags = 8; + OperationNFTokenAcceptOffer op_nftoken_accept_offer = 11; - bytes private_key = 9; + OperationNFTokenCancelOffer op_nftoken_cancel_offer = 12; + + OperationEscrowCreate op_escrow_create = 16; + + OperationEscrowCancel op_escrow_cancel = 17; + + OperationEscrowFinish op_escrow_finish = 18; + } + + // Only used by tss chain-integration. + bytes public_key = 15; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { + // Encoded transaction bytes encoded = 1; // Optional error Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } diff --git a/src/proto/Solana.proto b/src/proto/Solana.proto index 45ac259f631..0649cf72671 100644 --- a/src/proto/Solana.proto +++ b/src/proto/Solana.proto @@ -3,41 +3,64 @@ syntax = "proto3"; package TW.Solana.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + +// Transfer transaction message Transfer { + // destination address string recipient = 1; + + // amount uint64 value = 2; - string memo = 3; // optional - repeated string references = 4; // optional referenced public keys + + // optional memo + string memo = 3; + + // optional referenced public keys + repeated string references = 4; } // Create and initialize a stake account, and delegate amount to it. // Recommendation behavior is to not specify a stake account, and a new unique account will be created each time. // Optionally a stake account pubkey can be specified, but it should not exist on chain. message DelegateStake { + // Validator's public key string validator_pubkey = 1; + + // delegation amount uint64 value = 2; + + // staking account string stake_account = 3; } // Deactivate staking on stake account message DeactivateStake { + // staking account string stake_account = 1; } // Deactivate staking on multiple stake account message DeactivateAllStake { + // staking accounts repeated string stake_accounts = 1; } // Withdraw amount from stake account message WithdrawStake { + // staking account string stake_account = 1; + + // withdrawal amount uint64 value = 2; } -// Techinical structure to group a staking account and an amount +// Technical structure to group a staking account and an amount message StakeAccountValue { + // staking account string stake_account = 1; + + // amount uint64 value = 2; } @@ -46,57 +69,254 @@ message WithdrawAllStake { repeated StakeAccountValue stake_accounts = 1; } +enum TokenProgramId { + TokenProgram = 0; + Token2022Program = 1; +} + // Create a token account under a main account for a token type message CreateTokenAccount { // main account -- can be same as signer, or other main account (if done on some other account's behalf) string main_address = 1; + + // Token minting address string token_mint_address = 2; + + // Token address string token_address = 3; + + // optional token program id + TokenProgramId token_program_id = 4; } // Transfer tokens message TokenTransfer { + // Mint address of the token string token_mint_address = 1; + + // Source address string sender_token_address = 2; + + // Destination address string recipient_token_address = 3; + + // Amount uint64 amount = 4; - uint32 decimals = 5; // Note: 8-bit value - string memo = 6; // optional - repeated string references = 7; // optional referenced public keys + + // Note: 8-bit value + uint32 decimals = 5; + + // optional memo§ + string memo = 6; + + // optional referenced public keys + repeated string references = 7; + + // optional token program id + TokenProgramId token_program_id = 8; } // CreateTokenAccount and TokenTransfer combined message CreateAndTransferToken { // main account -- can be same as signer, or other main account (if done on some other account's behalf) string recipient_main_address = 1; + + // Mint address of the token string token_mint_address = 2; + // Token address for the recipient, will be created first string recipient_token_address = 3; + + // Sender's token address string sender_token_address = 4; + + // amount uint64 amount = 5; - uint32 decimals = 6; // Note: 8-bit value - string memo = 7; // optional - repeated string references = 8; // optional referenced public keys + + // Note: 8-bit value + uint32 decimals = 6; + + // optional + string memo = 7; + + // optional referenced public keys + repeated string references = 8; + + // optional token program id + TokenProgramId token_program_id = 9; +} + +message CreateNonceAccount { + // Required for building pre-signing hash of a transaction + string nonce_account = 1; + uint64 rent = 2; + // Optional for building pre-signing hash of a transaction + bytes nonce_account_private_key = 3; +} + +message WithdrawNonceAccount { + string nonce_account = 1; + string recipient = 2; + uint64 value = 3; +} + +message AdvanceNonceAccount { + string nonce_account = 1; +} + +message PubkeySignature { + string pubkey = 1; + // base58 encoded signature. + string signature = 2; +} + +message RawMessage { + message MessageHeader { + uint32 num_required_signatures = 1; + uint32 num_readonly_signed_accounts = 2; + uint32 num_readonly_unsigned_accounts = 3; + } + + message Instruction { + uint32 program_id = 1; + repeated uint32 accounts = 2 [packed = true]; + bytes program_data = 3; + } + + message MessageAddressTableLookup { + string account_key = 1; + repeated uint32 writable_indexes = 2 [packed = true]; + repeated uint32 readonly_indexes = 3 [packed = true]; + } + + message MessageLegacy { + MessageHeader header = 1; + repeated string account_keys = 2; + // Relatively recent block hash (base58 encoded). + string recent_blockhash = 3; + repeated Instruction instructions = 4; + } + + message MessageV0 { + MessageHeader header = 1; + repeated string account_keys = 2; + // Relatively recent block hash (base58 encoded). + string recent_blockhash = 3; + repeated Instruction instructions = 4; + repeated MessageAddressTableLookup address_table_lookups = 5; + } + + // Transaction signatures. + // If private keys are set in `SigningInput`, corresponding signatures will be overriden. + // It's also possible some or all the signatures are be used to compile a transaction if corresponding private keys are not set. + repeated PubkeySignature signatures = 1; + oneof message { + MessageLegacy legacy = 2; + MessageV0 v0 = 3; + } +} + +message DecodingTransactionOutput { + // Decoded transaction info. + RawMessage transaction = 1; + + // Error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // Error code description + string error_message = 3; +} + +enum Encoding { + Base58 = 0; + Base64 = 1; +} + +// Specific compute unit limit that the transaction is allowed to consume. +message PriorityFeePrice { + uint64 price = 1; +} + +// Compute unit price in "micro-lamports" to pay a higher transaction fee for higher transaction prioritization. +message PriorityFeeLimit { + uint32 limit = 2; } // Input data necessary to create a signed transaction. message SigningInput { + // The secret private key used for signing (32 bytes). bytes private_key = 1; + + // Relatively recent block hash string recent_blockhash = 2; + + bool v0_msg = 3; + + // Payload message oneof transaction_type { - Transfer transfer_transaction = 3; - DelegateStake delegate_stake_transaction = 4; - DeactivateStake deactivate_stake_transaction = 5; - DeactivateAllStake deactivate_all_stake_transaction = 6; - WithdrawStake withdraw_transaction = 7; - WithdrawAllStake withdraw_all_transaction = 8; - CreateTokenAccount create_token_account_transaction = 9; - TokenTransfer token_transfer_transaction = 10; - CreateAndTransferToken create_and_transfer_token_transaction = 11; + Transfer transfer_transaction = 4; + DelegateStake delegate_stake_transaction = 5; + DeactivateStake deactivate_stake_transaction = 6; + DeactivateAllStake deactivate_all_stake_transaction = 7; + WithdrawStake withdraw_transaction = 8; + WithdrawAllStake withdraw_all_transaction = 9; + CreateTokenAccount create_token_account_transaction = 10; + TokenTransfer token_transfer_transaction = 11; + CreateAndTransferToken create_and_transfer_token_transaction = 12; + CreateNonceAccount create_nonce_account = 13; + WithdrawNonceAccount withdraw_nonce_account = 16; + AdvanceNonceAccount advance_nonce_account = 19; } + // Required for building pre-signing hash of a transaction + string sender = 14; + // Required for using durable transaction nonce + string nonce_account = 15; + // Optional external fee payer private key. support: TokenTransfer, CreateAndTransferToken + bytes fee_payer_private_key = 17; + // Optional external fee payer. support: TokenTransfer, CreateAndTransferToken + string fee_payer = 18; + // Optional message plan. For signing an already prepared message. + RawMessage raw_message = 20; + // Output transaction encoding. + Encoding tx_encoding = 21; + // Optional. Set a specific compute unit limit that the transaction is allowed to consume. + // https://solana.com/docs/intro/transaction_fees#prioritization-fee + PriorityFeePrice priority_fee_price = 22; + // Optional. Set a compute unit price in "micro-lamports" to pay a higher transaction + // fee for higher transaction prioritization. + // https://solana.com/docs/intro/transaction_fees#prioritization-fee + PriorityFeeLimit priority_fee_limit = 23; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { + // The encoded transaction string encoded = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; + + // The encoded message. Can be used to estimate a transaction fee required to execute the message. + string unsigned_tx = 4; + + // Transaction signatures (may include external signatures). + repeated PubkeySignature signatures = 5; +} + +/// Transaction pre-signing output +message PreSigningOutput { + // Signer list + repeated bytes signers = 1; + + // Pre-image data. There is no hashing for Solana presign image + bytes data = 2; + + // Error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 3; + + // Error code description + string error_message = 4; } diff --git a/src/proto/Stellar.proto b/src/proto/Stellar.proto index 0849f07097f..ab890559337 100644 --- a/src/proto/Stellar.proto +++ b/src/proto/Stellar.proto @@ -3,24 +3,30 @@ syntax = "proto3"; package TW.Stellar.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + +// Represents an asset +// Note: alphanum12 currently not supported message Asset { // Optional in case of non-native asset; the asset issuer address string issuer = 1; // Optional in case of non-native asset; the asset alphanum4 code. string alphanum4 = 2; - - // Note: alphanum12 currently not supported } +// Create a new account message OperationCreateAccount { + // address string destination = 1; // Amount (*10^7) int64 amount = 2; } +// Perform payment message OperationPayment { + // Destination address string destination = 1; // Optional, can be left empty for native asset @@ -30,24 +36,32 @@ message OperationPayment { int64 amount = 3; } +// Change trust message OperationChangeTrust { + // The asset Asset asset = 1; // Validity (time bound to), unix time. Set to (now() + 2 * 365 * 86400) for 2 years; set to 0 for missing. int64 valid_before = 2; } +// A predicate (used in claim) +// Rest of predicates not currently supported +// See https://github.com/stellar/stellar-protocol/blob/master/core/cap-0023.md enum ClaimPredicate { Predicate_unconditional = 0; - // Rest of predicates not currently supported - // See https://github.com/stellar/stellar-protocol/blob/master/core/cap-0023.md } +// Claimant: account & predicate message Claimant { + // Claimant account string account = 1; + + // predicate ClaimPredicate predicate = 2; } +// Create a claimable balance (2-phase transfer) message OperationCreateClaimableBalance { // Optional, can be left empty for native asset Asset asset = 1; @@ -55,42 +69,53 @@ message OperationCreateClaimableBalance { // Amount (*10^7) int64 amount = 2; + // One or more claimants repeated Claimant claimants = 3; } +// Claim a claimable balance message OperationClaimClaimableBalance { // 32-byte balance ID hash bytes balance_id = 1; } +// Empty memo (placeholder) message MemoVoid { } +// Memo with text message MemoText { string text = 1; } +// Memo with an ID message MemoId { int64 id = 1; } +// Memo with a hash message MemoHash { bytes hash = 1; } // Input data necessary to create a signed transaction. message SigningInput { + // Transaction fee int32 fee = 1; + // Account sequence int64 sequence = 2; + // Source account string account = 3; - // Private key. + // The secret private key used for signing (32 bytes). bytes private_key = 4; + // Wellknown passphrase, specific to the chain string passphrase = 5; + // Payload message oneof operation_oneof { OperationCreateAccount op_create_account = 6; OperationPayment op_payment = 7; @@ -99,6 +124,7 @@ message SigningInput { OperationClaimClaimableBalance op_claim_claimable_balance = 15; } + // Memo oneof memo_type_oneof { MemoVoid memo_void = 9; MemoText memo_text = 10; @@ -106,10 +132,18 @@ message SigningInput { MemoHash memo_hash = 12; MemoHash memo_return_hash = 13; } + + int64 time_bounds = 16; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signature. string signature = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } diff --git a/src/proto/Sui.proto b/src/proto/Sui.proto new file mode 100644 index 00000000000..1efb8798b8c --- /dev/null +++ b/src/proto/Sui.proto @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +syntax = "proto3"; + +package TW.Sui.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// Object info (including Coins). +message ObjectRef { + // Hex string representing the object ID. + string object_id = 1; + // Object version. + uint64 version = 2; + // Base58 string representing the object digest. + string object_digest = 3; +} + +// Optional amount. +message Amount { + uint64 amount = 1; +} + +// Base64 encoded msg to sign (string) +message SignDirect { + // Obtain by calling any write RpcJson on SUI + string unsigned_tx_msg = 1; +} + +// Send `Coin` to a list of addresses, where T can be any coin type, following a list of amounts. +// The object specified in the gas field will be used to pay the gas fee for the transaction. +// The gas object can not appear in input_coins. +// https://docs.sui.io/sui-api-ref#unsafe_pay +message Pay { + // The Sui coins to be used in this transaction, including the coin for gas payment. + repeated ObjectRef input_coins = 1; + + // The recipients' addresses, the length of this vector must be the same as amounts. + repeated string recipients = 2; + + // The amounts to be transferred to recipients, following the same order. + repeated uint64 amounts = 3; + + // Gas object to be used in this transaction. + ObjectRef gas = 4; +} + +// Send SUI coins to a list of addresses, following a list of amounts. +// This is for SUI coin only and does not require a separate gas coin object. +// https://docs.sui.io/sui-api-ref#unsafe_paysui +message PaySui { + // The Sui coins to be used in this transaction, including the coin for gas payment. + repeated ObjectRef input_coins = 1; + + // The recipients' addresses, the length of this vector must be the same as amounts. + repeated string recipients = 2; + + // The amounts to be transferred to recipients, following the same order. + repeated uint64 amounts = 3; +} + +// Send all SUI coins to one recipient. +// This is for SUI coin only and does not require a separate gas coin object. +// https://docs.sui.io/sui-api-ref#unsafe_payallsui +message PayAllSui { + // The Sui coins to be used in this transaction, including the coin for gas payment. + repeated ObjectRef input_coins = 1; + + // The recipient address. + string recipient = 2; +} + +// Add stake to a validator's staking pool using multiple coins and amount. +// https://docs.sui.io/sui-api-ref#unsafe_requestaddstake +message RequestAddStake { + // Coin objects to stake. + repeated ObjectRef coins = 1; + + // Optional stake amount. + Amount amount = 2; + + // The validator's Sui address. + string validator = 3; + + // Gas object to be used in this transaction. + ObjectRef gas = 4; +} + +// Withdraw stake from a validator's staking pool. +// https://docs.sui.io/sui-api-ref#unsafe_requestwithdrawstake +message RequestWithdrawStake { + // StakedSui object ID. + ObjectRef staked_sui = 1; + + // Gas object to be used in this transaction. + ObjectRef gas = 2; +} + +/// Transfer an object from one address to another. The object's type must allow public transfers. +/// https://docs.sui.io/sui-api-ref#unsafe_transferobject +message TransferObject { + // Object ID to be transferred. + ObjectRef object = 1; + + // The recipient address. + string recipient = 2; + + // Gas object to be used in this transaction. + ObjectRef gas = 3; +} + +// Input data necessary to create a signed transaction. +message SigningInput { + // Private key to sign the transaction (bytes). + bytes private_key = 1; + + // Optional transaction signer. + // Needs to be set if no private key provided at `TransactionCompiler` module. + string signer = 2; + + oneof transaction_payload { + SignDirect sign_direct_message = 3; + Pay pay = 4; + PaySui pay_sui = 5; + PayAllSui pay_all_sui = 6; + RequestAddStake request_add_stake = 7; + RequestWithdrawStake request_withdraw_stake = 8; + TransferObject transfer_object = 9; + } + + // The gas budget, the transaction will fail if the gas cost exceed the budget. + uint64 gas_budget = 12; + + // Reference gas price. + uint64 reference_gas_price = 13; +} + +// Transaction signing output. +message SigningOutput { + /// The raw transaction without indent in base64 + string unsigned_tx = 1; + + /// The signature encoded in base64 + string signature = 2; + + // Error code, 0 is ok, other codes will be treated as errors. + Common.Proto.SigningError error = 3; + + // Error description. + string error_message = 4; +} diff --git a/src/proto/THORChainSwap.proto b/src/proto/THORChainSwap.proto index a5c943b4a65..69b6d257e16 100644 --- a/src/proto/THORChainSwap.proto +++ b/src/proto/THORChainSwap.proto @@ -6,6 +6,7 @@ option java_package = "wallet.core.jni.proto"; import "Bitcoin.proto"; import "Ethereum.proto"; import "Binance.proto"; +import "Cosmos.proto"; // Supported blockchains enum Chain { @@ -13,11 +14,18 @@ enum Chain { BTC = 1; ETH = 2; BNB = 3; + DOGE = 4; + BCH = 5; + LTC = 6; + ATOM = 7; + AVAX = 8; + BSC = 9; } // Predefined error codes enum ErrorCode { - OK = 0; // OK + // OK + OK = 0; Error_general = 1; Error_Input_proto_deserialization = 2; Error_Unsupported_from_chain = 13; @@ -30,21 +38,39 @@ enum ErrorCode { // An error code + a free text message Error { + // code of the error ErrorCode code = 1; + + // optional error message string message = 2; } // Represents an asset. Examples: BNB.BNB, RUNE.RUNE, BNB.RUNE-67C message Asset { + // Chain ID Chain chain = 1; + + // Symbol string symbol = 2; + + // The ID of the token (blockchain-specific format) string token_id = 3; } +message StreamParams { + // Optional Swap Interval ncy in blocks. + // The default is 1 - time-optimised means getting the trade done quickly, regardless of the cost. + string interval = 1; + + // Optional Swap Quantity. Swap interval times every Interval blocks. + // The default is 0 - network will determine the number of swaps. + string quantity = 2; +} + // Input for a swap between source and destination chains; for creating a TX on the source chain. message SwapInput { // Source chain - Chain from_chain = 1; + Asset from_asset = 1; // Source address, on source chain string from_address = 2; @@ -64,15 +90,36 @@ message SwapInput { // The source amount, integer as string, in the smallest native unit of the chain string from_amount = 7; - // The minimum accepted destination amount. Actual destination amount will depend on current rates, limit amount can be used to prevent using very unfavorable rates. + // Optional minimum accepted destination amount. Actual destination amount will depend on current rates, limit amount can be used to prevent using very unfavorable rates. + // The default is 0 - no price limit. string to_amount_limit = 8; + + // Optional affiliate fee destination address. A Rune address. + string affiliate_fee_address = 9; + + // Optional affiliate fee, percentage base points, e.g. 100 means 1%, 0 - 1000, as string. Empty means to ignore it. + string affiliate_fee_rate_bp = 10; + + // Optional extra custom memo, reserved for later use. + string extra_memo = 11; + + // Optional expirationTime, will be now() + 15 min if not set + uint64 expiration_time = 12; + + // Optional streaming parameters. Use Streaming Swaps and Swap Optimisation strategy if set. + // https://docs.thorchain.org/thorchain-finance/continuous-liquidity-pools#streaming-swaps-and-swap-optimisation + StreamParams stream_params = 13; } +// Result of the swap, a SigningInput struct for the specific chain message SwapOutput { + // Source chain Chain from_chain = 1; + + // Destination chain Chain to_chain = 2; - // Error code, filled in case of error, OK&"" on success + // Error code, filled in case of error, OK/empty on success Error error = 3; // Prepared unsigned transaction input, on the source chain, to THOR. Some fields must be completed, and it has to be signed. @@ -80,5 +127,6 @@ message SwapOutput { Bitcoin.Proto.SigningInput bitcoin = 4; Ethereum.Proto.SigningInput ethereum = 5; Binance.Proto.SigningInput binance = 6; + Cosmos.Proto.SigningInput cosmos = 7; } } diff --git a/src/proto/Tezos.proto b/src/proto/Tezos.proto index c09f70bd674..7d632d2e90a 100644 --- a/src/proto/Tezos.proto +++ b/src/proto/Tezos.proto @@ -3,35 +3,63 @@ syntax = "proto3"; package TW.Tezos.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Input data necessary to create a signed Tezos transaction. // Next field: 3 message SigningInput { + // One or more operations OperationList operation_list = 1; - bytes private_key = 2; + + // Encoded operation bytes obtained with $RPC_URL/chains/main/blocks/head/helpers/forge/operations, operation_list will be ignored. + bytes encoded_operations = 2; + + // The secret private key used for signing (32 bytes). + bytes private_key = 3; } -// Transaction signing output. +// Result containing the signed and encoded transaction. // Next field: 2 message SigningOutput { + // The encoded transaction bytes encoded = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; } // A list of operations and a branch. // Next field: 3 message OperationList { + // branch string branch = 1; + + // list of operations repeated Operation operations = 2; } // An operation that can be applied to the Tezos blockchain. // Next field: 12 message Operation { + // counter int64 counter = 1; + + // source account string source = 2; + + // fee int64 fee = 3; + + // gas limit int64 gas_limit = 4; + + // storage limit int64 storage_limit = 5; + // Operation types enum OperationKind { // Note: Proto3 semantics require a zero value. ENDORSEMENT = 0; @@ -40,6 +68,7 @@ message Operation { TRANSACTION = 108; DELEGATION = 110; } + // Operation type OperationKind kind = 7; // Operation specific data depending on the type of the operation. @@ -50,11 +79,44 @@ message Operation { } } +message FA12Parameters { + string entrypoint = 1; + string from = 2; + string to = 3; + string value = 4; +} + +message Txs { + string to = 1; + string token_id = 2; + string amount = 3; +} + +message TxObject { + string from = 1; + repeated Txs txs = 2; +} + +message FA2Parameters { + string entrypoint = 1; + repeated TxObject txs_object = 2; +} + +// Generic operation parameters +message OperationParameters { + oneof parameters { + FA12Parameters fa12_parameters = 1; + FA2Parameters fa2_parameters = 2; + } +} + // Transaction operation specific data. // Next field: 3 message TransactionOperationData { string destination = 1; int64 amount = 2; + bytes encoded_parameter = 3; + OperationParameters parameters = 4; } // Reveal operation specific data. @@ -67,4 +129,4 @@ message RevealOperationData { // Next field: 2 message DelegationOperationData { string delegate = 1; -} \ No newline at end of file +} diff --git a/src/proto/TheOpenNetwork.proto b/src/proto/TheOpenNetwork.proto new file mode 100644 index 00000000000..092ec3bcd9b --- /dev/null +++ b/src/proto/TheOpenNetwork.proto @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +syntax = "proto3"; + +package TW.TheOpenNetwork.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +enum WalletVersion { + WALLET_V3_R1 = 0; + WALLET_V3_R2 = 1; + WALLET_V4_R2 = 2; + WALLET_V5_R1 = 3; +}; + +enum SendMode { + DEFAULT = 0; + PAY_FEES_SEPARATELY = 1; + IGNORE_ACTION_PHASE_ERRORS = 2; + DESTROY_ON_ZERO_BALANCE = 32; + ATTACH_ALL_INBOUND_MESSAGE_VALUE = 64; + ATTACH_ALL_CONTRACT_BALANCE = 128; +}; + +message Transfer { + // Recipient address + string dest = 1; + + // Amount to send in nanotons + uint64 amount = 2; + + // Send mode (optional, 0 by default) + // Learn more: https://ton.org/docs/develop/func/stdlib#send_raw_message + uint32 mode = 3; + + // Transfer comment message (optional, empty by default) + // Ignored if `custom_payload` is specified + string comment = 4; + + // If the address is bounceable + bool bounceable = 5; + + // One of the Transfer message payloads (optional). + oneof payload { + // Jetton transfer payload. + JettonTransfer jetton_transfer = 6; + // TON transfer with custom stateInit and payload (contract call). + CustomPayload custom_payload = 7; + } +} + +message JettonTransfer { + // Arbitrary request number. Default is 0. Optional field. + uint64 query_id = 1; + + // Amount of transferred jettons in elementary integer units. The real value transferred is jetton_amount multiplied by ten to the power of token decimal precision + uint64 jetton_amount = 2; + + // Address of the new owner of the jettons. + string to_owner = 3; + + // Address where to send a response with confirmation of a successful transfer and the rest of the incoming message Toncoins. Usually the sender should get back their toncoins. + string response_address = 4; + + // Amount in nanotons to forward to recipient. Basically minimum amount - 1 nanoton should be used + uint64 forward_amount = 5; +} + +message CustomPayload { + // (string base64, optional): raw one-cell BoC encoded in Base64. + // Can be used to deploy a smart contract. + string state_init = 1; + + // (string base64, optional): raw one-cell BoC encoded in Base64. + string payload = 2; +} + +message SigningInput { + // The secret private key used for signing (32 bytes). + bytes private_key = 1; + + // Public key of the signer (32 bytes). Used when transaction is going to be signed externally. + bytes public_key = 2; + + // Up to 4 internal messages. + repeated Transfer messages = 3; + + // Message counter (optional, 0 by default used for the first deploy) + // This field is required, because we need to protect the smart contract against "replay attacks" + // Learn more: https://ton.org/docs/develop/smart-contracts/guidelines/external-messages + uint32 sequence_number = 4; + + // Expiration UNIX timestamp (optional, now() + 60 by default) + uint32 expire_at = 5; + + // Wallet version + WalletVersion wallet_version = 6; +} + +// Transaction signing output. +message SigningOutput { + // Signed and base64 encoded BOC message + string encoded = 1; + + // Transaction Cell hash + bytes hash = 2; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 3; + + // error code description + string error_message = 4; +} diff --git a/src/proto/Theta.proto b/src/proto/Theta.proto index f779c0b5b0d..7191c6cc5ae 100644 --- a/src/proto/Theta.proto +++ b/src/proto/Theta.proto @@ -3,6 +3,8 @@ syntax = "proto3"; package TW.Theta.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + /// Input data necessary to create a signed transaction message SigningInput { /// Chain ID string, mainnet, testnet and privatenet @@ -11,27 +13,36 @@ message SigningInput { /// Recipient address string to_address = 2; - /// Theta token amount to send in wei (256-bit number) + /// Theta token amount to send in wei (uint256, serialized big endian) bytes theta_amount = 3; - /// TFuel token amount to send in wei (256-bit number) + /// TFuel token amount to send in wei (uint256, serialized big endian) bytes tfuel_amount = 4; /// Sequence number of the transaction for the sender address uint64 sequence = 5; - /// Fee amount in TFuel wei for the transaction (256-bit number) + /// Fee amount in TFuel wei for the transaction (uint256, serialized big endian) bytes fee = 6; - /// Private key + /// The secret private key used for signing (32 bytes). bytes private_key = 7; + + /// Public key + bytes public_key = 8; } -/// Transaction signing output +// Result containing the signed and encoded transaction. message SigningOutput { /// Signed and encoded transaction bytes bytes encoded = 1; /// Signature bytes signature = 2; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 3; + + // error code description + string error_message = 4; } diff --git a/src/proto/Tron.proto b/src/proto/Tron.proto index 3a6b635b169..22abe490c8a 100644 --- a/src/proto/Tron.proto +++ b/src/proto/Tron.proto @@ -3,6 +3,9 @@ syntax = "proto3"; package TW.Tron.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + +// A transfer transaction message TransferContract { // Sender address. string owner_address = 1; @@ -14,6 +17,7 @@ message TransferContract { int64 amount = 3; } +// Asset transfer message TransferAssetContract { // Asset name. string asset_name = 1; @@ -28,6 +32,7 @@ message TransferAssetContract { int64 amount = 4; } +// TRC20 token transfer message TransferTRC20Contract { // Contract name. string contract_address = 1; @@ -38,79 +43,181 @@ message TransferTRC20Contract { // Recipient address. string to_address = 3; - // Amount to send, uint256, big-endian. + // Amount to send, (uint256, serialized big endian) bytes amount = 4; } +// Freeze balance message FreezeBalanceContract { // Sender address. string owner_address = 1; + // Frozen balance. Minimum 1 int64 frozen_balance = 2; + // Frozen duration int64 frozen_duration = 3; + // Resource type: BANDWIDTH | ENERGY string resource = 10; + // Receiver address string receiver_address = 15; } +// stake TRX to obtain TRON Power (voting rights) and bandwidth or energy. +message FreezeBalanceV2Contract { + // Address of transaction initiator, data type is string + string owner_address = 1; + + // Amount of TRX to be staked, unit is sun, data type is uint256 + int64 frozen_balance = 2; + + // Resource type, "BANDWIDTH" or "ENERGY", data type is string + string resource = 3; +} + +// Unstake TRX to release bandwidth and energy and at the same time TRON Power will be reduced and all corresponding votes will be canceled. +message UnfreezeBalanceV2Contract { + // Address of transaction initiator, data type is string + string owner_address = 1; + // Amount of TRX to be unstaked, unit is sun, data type is uint256 + int64 unfreeze_balance = 2; + // Resource type, "BANDWIDTH" or "ENERGY", data type is string + string resource = 3; +} + +// withdraw unfrozen balance +message WithdrawExpireUnfreezeContract { + // Address of transaction initiator, data type is string + string owner_address = 1; +} + +// delegate resource +message DelegateResourceContract { + // Address of transaction initiator, data type is string + string owner_address = 1; + // Resource type, "BANDWIDTH" or "ENERGY", data type is string + string resource = 2; + // Amount of TRX staked for resource to be delegated, unit is sun, data type is uint256 + int64 balance = 3; + // Receiver address of resource to be delegated to + string receiver_address = 4; + // Whether it is locked, if it is set to true, the delegated resources cannot be undelegated within 3 days. + // When the lock time is not over, if the owner delegates the same resources using the lock to the same address, + // the lock time will be reset to 3 days + bool lock = 5; +} + +// undelegate resource +message UnDelegateResourceContract { + // Address of transaction initiator, data type is string + string owner_address = 1; + // Resource type, "BANDWIDTH" or "ENERGY", data type is string + string resource = 2; + // Amount of TRX staked for resource to be undelegated, unit is sun, data type is uint256 + int64 balance = 3; + // Receiver address of resource to be delegated to, data type is string + string receiver_address = 4; +} + +// Unfreeze balance message UnfreezeBalanceContract { // Sender address string owner_address = 1; + // Resource type: BANDWIDTH | ENERGY string resource = 10; + // Receiver address string receiver_address = 15; } +// Unfreeze asset message UnfreezeAssetContract { // Sender address string owner_address = 1; } +// Vote asset message VoteAssetContract { // Sender address string owner_address = 1; + // Vote addresses repeated string vote_address = 2; + bool support = 3; + int32 count = 5; } +// Vote witness message VoteWitnessContract { + // A vote message Vote { + // address string vote_address = 1; + + // vote count int64 vote_count = 2; } + + // Owner string owner_address = 1; + + // The votes repeated Vote votes = 2; + bool support = 3; } +// Withdraw balance message WithdrawBalanceContract { // Sender address string owner_address = 1; } +// Smart contract call message TriggerSmartContract { + // Owner string owner_address = 1; + + // Contract address string contract_address = 2; + + // amount int64 call_value = 3; + + // call data bytes data = 4; + + // token value int64 call_token_value = 5; + + // ID of the token int64 token_id = 6; } +// Info from block header message BlockHeader { + // creation timestamp int64 timestamp = 1; + + // root bytes tx_trie_root = 2; + + // hash of the parent bytes parent_hash = 3; + int64 number = 7; + bytes witness_address = 9; + int32 version = 10; } +// Transaction message Transaction { // Transaction timestamp in milliseconds. int64 timestamp = 1; @@ -136,18 +243,27 @@ message Transaction { VoteWitnessContract vote_witness = 17; TriggerSmartContract trigger_smart_contract = 18; TransferTRC20Contract transfer_trc20_contract = 19; + FreezeBalanceV2Contract freeze_balance_v2 = 20; + UnfreezeBalanceV2Contract unfreeze_balance_v2 = 21; + WithdrawExpireUnfreezeContract withdraw_expire_unfreeze = 23; + DelegateResourceContract delegate_resource = 24; + UnDelegateResourceContract undelegate_resource = 25; } } +// Input data necessary to create a signed transaction. message SigningInput { // Transaction. Transaction transaction = 1; - // Private key. + // The secret private key used for signing (32 bytes). bytes private_key = 2; + + // For direct sign in Tron, we just have to sign the txId returned by the DApp json payload. + string txId = 3; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Transaction identifier. bytes id = 1; @@ -158,5 +274,12 @@ message SigningOutput { bytes ref_block_bytes = 3; bytes ref_block_hash = 4; + // Result in JSON string json = 5; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 6; + + // error code description + string error_message = 7; } diff --git a/src/proto/VeChain.proto b/src/proto/VeChain.proto index c89d9f0bfc5..26d4086c1db 100644 --- a/src/proto/VeChain.proto +++ b/src/proto/VeChain.proto @@ -3,11 +3,14 @@ syntax = "proto3"; package TW.VeChain.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + +// A clause, between a sender and destination message Clause { /// Recipient address. string to = 1; - /// Transaction amount. + /// Transaction amount (uint256, serialized big endian) bytes value = 2; /// Payload data. @@ -43,15 +46,21 @@ message SigningInput { /// Number set by user. uint64 nonce = 8; - // Private key. + /// The secret private key used for signing (32 bytes). bytes private_key = 9; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed and encoded transaction bytes. bytes encoded = 1; // Signature. bytes signature = 2; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 3; + + // error code description + string error_message = 4; } diff --git a/src/proto/WalletConnect.proto b/src/proto/WalletConnect.proto new file mode 100644 index 00000000000..7d9139b70d5 --- /dev/null +++ b/src/proto/WalletConnect.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; + +package TW.WalletConnect.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Binance.proto"; +import "Common.proto"; +import "Solana.proto"; + +// The transaction protocol may differ from version to version. +enum Protocol { + V2 = 0; +} + +// WalletConnect request method. +enum Method { + Unknown = 0; + // cosmos_signAmino + CosmosSignAmino = 1; + // solana_signTransaction + SolanaSignTransaction = 2; +} + +message ParseRequestInput { + // A protocol version. + Protocol protocol = 1; + + // A signing method like "cosmos_signAmino" or "eth_signTransaction". + Method method = 2; + + // Transaction payload to sign. + // Basically, a JSON object. + string payload = 3; +} + +message ParseRequestOutput { + // OK (=0) or other codes in case of error + Common.Proto.SigningError error = 1; + + // error description in case of error + string error_message = 2; + + // Prepared unsigned transaction input, on the source chain. Some fields must be completed, and it has to be signed. + oneof signing_input_oneof { + Binance.Proto.SigningInput binance = 3; + Solana.Proto.SigningInput solana = 4; + } +} diff --git a/src/proto/Waves.proto b/src/proto/Waves.proto index 011fcf1138b..a2acc2ef599 100644 --- a/src/proto/Waves.proto +++ b/src/proto/Waves.proto @@ -3,29 +3,45 @@ syntax = "proto3"; package TW.Waves.Proto; option java_package = "wallet.core.jni.proto"; -//Transfer transaction +// Transfer transaction message TransferMessage { + // amount int64 amount = 1; + + // asset ID string asset = 2; + // minimum 0.001 Waves (100000 Wavelets) for now int64 fee = 3; + + // asset of the fee string fee_asset = 4; + + // destination address string to = 5; + // any 140 bytes payload, will be displayed to the client as utf-8 string bytes attachment = 6; } -//Lease transaction +// Lease transaction message LeaseMessage { + // amount int64 amount = 1; + + // destination string to = 2; + // minimum 0.001 Waves (100000 Wavelets) for now int64 fee = 3; } -//Lease transaction +// Cancel Lease transaction message CancelLeaseMessage { + // Lease ID to cancel string lease_id = 1; + + // Fee used int64 fee = 2; } @@ -34,7 +50,11 @@ message CancelLeaseMessage { message SigningInput { // in millis int64 timestamp = 1; + + // The secret private key used for signing (32 bytes). bytes private_key = 2; + + // Payload message oneof message_oneof { TransferMessage transfer_message = 3; LeaseMessage lease_message = 4; @@ -42,9 +62,12 @@ message SigningInput { } } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { + // signature data bytes signature = 1; + + // transaction in JSON format string json = 2; } diff --git a/src/proto/Zilliqa.proto b/src/proto/Zilliqa.proto index ea5d3eff4c9..4e4e661fc98 100644 --- a/src/proto/Zilliqa.proto +++ b/src/proto/Zilliqa.proto @@ -3,14 +3,17 @@ syntax = "proto3"; package TW.Zilliqa.Proto; option java_package = "wallet.core.jni.proto"; +// Generic transaction message Transaction { + // Transfer transaction message Transfer { - // Amount to send (256-bit number) + // Amount to send (uint256, serialized big endian) bytes amount = 1; } + // Generic contract call message Raw { - // Amount to send (256-bit number) + // Amount to send (uint256, serialized big endian) bytes amount = 1; // Smart contract code @@ -43,13 +46,14 @@ message SigningInput { // GasLimit uint64 gas_limit = 5; - // Private Key + // The secret private key used for signing (32 bytes). bytes private_key = 6; + // The payload transaction Transaction transaction = 7; } -// Transaction signing output. +// Result containing the signed and encoded transaction. message SigningOutput { // Signed signature bytes. bytes signature = 1; diff --git a/src/rust/RustCoinEntry.cpp b/src/rust/RustCoinEntry.cpp new file mode 100644 index 00000000000..870bbe2c076 --- /dev/null +++ b/src/rust/RustCoinEntry.cpp @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "RustCoinEntry.h" +#include "Wrapper.h" + +namespace TW::Rust { + +bool RustCoinEntry::validateAddress(TWCoinType coin, const std::string &address, const PrefixVariant &addressPrefix) const { + Rust::TWStringWrapper addressStr = address; + + if (std::holds_alternative(addressPrefix)) { + return Rust::tw_any_address_is_valid(addressStr.get(), static_cast(coin)); + } else if (const auto* hrpPrefix = std::get_if(&addressPrefix); hrpPrefix) { + Rust::TWStringWrapper hrpStr = std::string(*hrpPrefix); + return Rust::tw_any_address_is_valid_bech32(addressStr.get(), static_cast(coin), hrpStr.get()); + } else { + throw std::invalid_argument("`Rust::tw_any_address_is_valid_ss58`, `Rust::tw_any_address_create_with_public_key_filecoin_address_type` are not supported yet"); + } +} + +std::string RustCoinEntry::normalizeAddress(TWCoinType coin, const std::string& address) const { + Rust::TWStringWrapper addressStr = address; + + // `CoinEntry::normalizeAddress` is used when a `TWAnyAddress` has been created already, therefore validated. + auto anyAddress = Rust::wrapTWAnyAddress( + Rust::tw_any_address_create_with_string_unchecked(addressStr.get(), static_cast(coin))); + if (!anyAddress) { + return {}; + } + + Rust::TWStringWrapper normalized = Rust::tw_any_address_description(anyAddress.get()); + return normalized.toStringOrDefault(); +} + +std::string RustCoinEntry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const { + auto *twPublicKeyRaw = Rust::tw_public_key_create_with_data(publicKey.bytes.data(), + publicKey.bytes.size(), + static_cast(publicKey.type)); + auto twPublicKey = Rust::wrapTWPublicKey(twPublicKeyRaw); + if (!twPublicKey) { + return {}; + } + + Rust::TWAnyAddress* anyAddressRaw = nullptr; + if (std::holds_alternative(addressPrefix)) { + anyAddressRaw = Rust::tw_any_address_create_with_public_key_derivation(twPublicKey.get(), + static_cast(coin), + static_cast(derivation)); + } else if (const auto* hrpPrefix = std::get_if(&addressPrefix); hrpPrefix) { + Rust::TWStringWrapper hrpStr = std::string(*hrpPrefix); + anyAddressRaw = Rust::tw_any_address_create_bech32_with_public_key(twPublicKey.get(), + static_cast(coin), + hrpStr.get()); + } else { + throw std::invalid_argument("`Rust::tw_any_address_is_valid_ss58`, `Rust::tw_any_address_create_with_public_key_filecoin_address_type` are not supported yet"); + } + + auto anyAddress = Rust::wrapTWAnyAddress(anyAddressRaw); + if (!anyAddress) { + return {}; + } + + Rust::TWStringWrapper derivedAddress = Rust::tw_any_address_description(anyAddress.get()); + return derivedAddress.toStringOrDefault(); +} + +Data RustCoinEntry::addressToData(TWCoinType coin, const std::string& address) const { + Rust::TWStringWrapper addressStr = address; + + // `CoinEntry::normalizeAddress` is used when a `TWAnyAddress` has been created already, therefore validated. + auto anyAddress = Rust::wrapTWAnyAddress( + Rust::tw_any_address_create_with_string_unchecked(addressStr.get(), static_cast(coin))); + if (!anyAddress) { + return {}; + } + + Rust::TWDataWrapper data = Rust::tw_any_address_data(anyAddress.get()); + return data.toDataOrDefault(); +} + +void RustCoinEntry::sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const { + Rust::TWDataWrapper input = Rust::tw_data_create_with_bytes(dataIn.data(), dataIn.size()); + Rust::TWDataWrapper output = Rust::tw_any_signer_sign(input.get(), static_cast(coin)); + + dataOut = output.toDataOrDefault(); +} + +Data RustCoinEntry::preImageHashes(TWCoinType coin, const Data& txInputData) const { + Rust::TWDataWrapper input = txInputData; + Rust::TWDataWrapper output = Rust::tw_transaction_compiler_pre_image_hashes(static_cast(coin), input.get()); + + return output.toDataOrDefault(); +} + +void RustCoinEntry::compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + Rust::TWDataWrapper input = txInputData; + + std::vector publicKeysData; + for (const auto& publicKey : publicKeys) { + publicKeysData.push_back(publicKey.bytes); + } + + Rust::TWDataVectorWrapper signaturesVec = signatures; + Rust::TWDataVectorWrapper publicKeysVec = publicKeysData; + + Rust::TWDataWrapper output = Rust::tw_transaction_compiler_compile(static_cast(coin), input.get(), signaturesVec.get(), publicKeysVec.get()); + dataOut = output.toDataOrDefault(); +} + +} // namespace TW::Rust diff --git a/src/rust/RustCoinEntry.h b/src/rust/RustCoinEntry.h new file mode 100644 index 00000000000..371c483883a --- /dev/null +++ b/src/rust/RustCoinEntry.h @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "CoinEntry.h" + +#include + +namespace TW::Rust { + +/// The function takes a Protobuf output message `const Output&` and returns `std::string`. +template +using SignJSONOutputHandler = std::function; + +class RustCoinEntry : public CoinEntry { +public: + ~RustCoinEntry() noexcept override = default; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string normalizeAddress(TWCoinType coin, const std::string& address) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + Data addressToData(TWCoinType coin, const std::string& address) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; +}; + +class RustCoinEntryWithSignJSON: public RustCoinEntry { +public: + ~RustCoinEntryWithSignJSON() noexcept override = default; + +protected: + /// Helper function that can be used by [`Entry::signJSON`] to: + /// 1. Deserialize the given `json` as an `Input` object. + /// 2. Put the given `key` as `Input::private_key`. + /// 3. Serialize the input as bytes and call `Entry::sign`. + /// 4. Deserialize the output bytes as an `Output` object. + /// 5. Map the output object to a string. + template + std::string signJSONHelper(TWCoinType coin, const std::string &json, const Data &key, + SignJSONOutputHandler mapOutput) const { + Input input; + google::protobuf::util::JsonStringToMessage(json, &input); + input.set_private_key(key.data(), key.size()); + + auto inputData = data(input.SerializeAsString()); + Data dataOut; + sign(coin, inputData, dataOut); + + if (dataOut.empty()) { + return {}; + } + + Output output; + output.ParseFromArray(dataOut.data(), static_cast(dataOut.size())); + + return mapOutput(output); + } +}; + +} // namespace TW::Rust diff --git a/src/rust/Wrapper.h b/src/rust/Wrapper.h new file mode 100644 index 00000000000..04d97a9104c --- /dev/null +++ b/src/rust/Wrapper.h @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include + +#include "../Data.h" +#include "bindgen/WalletCoreRSBindgen.h" + +namespace TW::Rust { + +inline std::shared_ptr wrapTWAnyAddress(TWAnyAddress* anyAddress) { + return std::shared_ptr(anyAddress, tw_any_address_delete); +} + +inline std::shared_ptr wrapTWPublicKey(TWPublicKey* publicKey) { + return std::shared_ptr(publicKey, tw_public_key_delete); +} + +struct TWDataVectorWrapper { + TWDataVectorWrapper(): + ptr(std::shared_ptr(tw_data_vector_create(), Rust::tw_data_vector_delete)) { + } + + /// Implicit constructor. + TWDataVectorWrapper(const std::vector& vec) { + ptr = std::shared_ptr(tw_data_vector_create(), Rust::tw_data_vector_delete); + + for (const auto& item : vec) { + auto* itemData = tw_data_create_with_bytes(item.data(), item.size()); + Rust::tw_data_vector_add(ptr.get(), itemData); + Rust::tw_data_delete(itemData); + } + } + + ~TWDataVectorWrapper() = default; + + void push(const Data& item) { + auto* itemData = tw_data_create_with_bytes(item.data(), item.size()); + Rust::tw_data_vector_add(ptr.get(), itemData); + Rust::tw_data_delete(itemData); + } + + TWDataVector* get() const { + return ptr.get(); + } + + std::shared_ptr ptr; +}; + +struct TWDataWrapper { + /// Implicit constructor. + TWDataWrapper(const Data& bytes) { + auto* dataRaw = tw_data_create_with_bytes(bytes.data(), bytes.size()); + ptr = std::shared_ptr(dataRaw, tw_data_delete); + } + + /// Implicit constructor. + TWDataWrapper(TWData *ptr): ptr(std::shared_ptr(ptr, tw_data_delete)) { + } + + ~TWDataWrapper() = default; + + TWData* get() const { + return ptr.get(); + } + + Data toDataOrDefault() const { + if (!ptr) { + return {}; + } + + auto* bytes = tw_data_bytes(ptr.get()); + Data out(bytes, bytes + tw_data_size(ptr.get())); + return out; + } + + std::shared_ptr ptr; +}; + +struct TWStringWrapper { + /// Implicit constructor. + TWStringWrapper(const std::string& string) { + auto* stringRaw = tw_string_create_with_utf8_bytes(string.c_str()); + ptr = std::shared_ptr(stringRaw, tw_string_delete); + } + + /// Implicit constructor. + TWStringWrapper(TWString *ptr): ptr(std::shared_ptr(ptr, tw_string_delete)) { + } + + /// Implicit constructor. + TWStringWrapper(const char* string) { + auto* stringRaw = tw_string_create_with_utf8_bytes(string); + ptr = std::shared_ptr(stringRaw, tw_string_delete); + } + + ~TWStringWrapper() = default; + + TWString* get() const { + return ptr.get(); + } + + std::string toStringOrDefault() const { + if (!ptr) { + return {}; + } + + auto* bytes = tw_string_utf8_bytes(ptr.get()); + return {bytes}; + } + + const char* c_str() const { + return ptr ? tw_string_utf8_bytes(ptr.get()) : nullptr; + } + + explicit operator bool() const { + return static_cast(ptr); + } + + std::shared_ptr ptr; +}; + +struct CByteArrayWrapper { + CByteArrayWrapper() = default; + + /// Implicit constructor. + CByteArrayWrapper(const CByteArray &rawArray) { + *this = rawArray; + } + + CByteArrayWrapper& operator=(CByteArray rawArray) { + if (rawArray.data == nullptr || rawArray.size == 0) { + return *this; + } + Data result(&rawArray.data[0], &rawArray.data[rawArray.size]); + free_c_byte_array(&rawArray); + data = std::move(result); + return *this; + } + + Data data; +}; + +struct CStringWrapper { + /// Implicit move constructor. + CStringWrapper(const char* c_str) { + *this = c_str; + } + + CStringWrapper& operator=(const char* c_str) { + if (c_str == nullptr) { + return *this; + } + str = c_str; + Rust::free_string(c_str); + return *this; + } + + std::string str; +}; + +struct CUInt8Wrapper { + /// Implicit move constructor. + CUInt8Wrapper(uint8_t c_u8) { + *this = c_u8; + } + + CUInt8Wrapper& operator=(uint8_t c_u8) { + value = c_u8; + return *this; + } + + uint8_t value; +}; + +struct CUInt64Wrapper { + /// Implicit move constructor. + CUInt64Wrapper(uint64_t c_u64) { + *this = c_u64; + } + + CUInt64Wrapper& operator=(uint64_t c_u64) { + value = c_u64; + return *this; + } + + uint64_t value; +}; + +template +class CResult { +public: + /// Implicit move constructor. + /// This constructor is not fired if `R` type is `CResult`, i.e not a move constructor. + template + CResult(const R& result) { + *this = result; + } + + template + CResult& operator=(const R& result) { + code = result.code; + if (code == OK_CODE) { + inner = result.result; + } + return *this; + } + + /// Returns an inner value. + /// This method must be used if only `CResult::isOk` returns true, + /// otherwise it throws an exception. + T unwrap() { + return *inner; + } + + /// Returns the result value if `CResult::isOk` return true, otherwise returns a default value. + T unwrap_or_default() { + return inner ? *inner : T {}; + } + + /// Whether the result contains a value. + bool isOk() const { + return code == OK_CODE; + } + + /// Whether the result contains an error. + bool isErr() const { + return !isOk(); + } + +private: + ErrorCode code; + std::optional inner; +}; + +using CByteArrayResultWrapper = CResult; +using CUInt8ResultWrapper = CResult; +using CUInt64ResultWrapper = CResult; + +} // namespace TW::Rust diff --git a/src/rust/bindgen/.gitignore b/src/rust/bindgen/.gitignore new file mode 100644 index 00000000000..3e5286443dc --- /dev/null +++ b/src/rust/bindgen/.gitignore @@ -0,0 +1 @@ +WalletCoreRSBindgen.h diff --git a/src/uint256.h b/src/uint256.h index 46159abb2cd..43c6999a21a 100644 --- a/src/uint256.h +++ b/src/uint256.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -13,6 +11,7 @@ namespace TW { +using uint128_t = boost::multiprecision::uint128_t; using int256_t = boost::multiprecision::int256_t; using uint256_t = boost::multiprecision::uint256_t; @@ -28,20 +27,6 @@ inline uint256_t load(const Data& data) { return result; } -/// Loads a `uint256_t` from a collection of bytes. -/// The leftmost offset bytes are skipped, and the next 32 bytes are taken. At least 32 (+offset) -/// bytes are needed. -inline uint256_t loadWithOffset(const Data& data, size_t offset) { - using boost::multiprecision::cpp_int; - if (data.empty() || (data.size() < (256 / 8 + offset))) { - // not enough bytes in data - return uint256_t(0); - } - uint256_t result; - import_bits(result, data.begin() + offset, data.begin() + offset + 256 / 8); - return result; -} - /// Loads a `uint256_t` from Protobuf bytes (which are wrongly represented as /// std::string). inline uint256_t load(const std::string& data) { diff --git a/swift/.gitignore b/swift/.gitignore index 7166ea4da50..f392a48ec06 100644 --- a/swift/.gitignore +++ b/swift/.gitignore @@ -14,3 +14,4 @@ cpp.xcconfig # fastlane fastlane/README.md fastlane/report.xml +WalletCoreRs.xcframework diff --git a/swift/Example/Example/ContentView.swift b/swift/Example/Example/ContentView.swift index 6918ce3fb59..b5da0ca723d 100644 --- a/swift/Example/Example/ContentView.swift +++ b/swift/Example/Example/ContentView.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import SwiftUI import WalletCore diff --git a/swift/Example/Example/ExampleApp.swift b/swift/Example/Example/ExampleApp.swift index 938063a069d..f3f442eefcd 100644 --- a/swift/Example/Example/ExampleApp.swift +++ b/swift/Example/Example/ExampleApp.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import SwiftUI diff --git a/swift/Example/ExampleMac/ContentView.swift b/swift/Example/ExampleMac/ContentView.swift index 41e49a49f7a..80981c59ac9 100644 --- a/swift/Example/ExampleMac/ContentView.swift +++ b/swift/Example/ExampleMac/ContentView.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import SwiftUI import WalletCore diff --git a/swift/Example/ExampleMac/ExampleMacApp.swift b/swift/Example/ExampleMac/ExampleMacApp.swift index 911a53f8948..040a390bd88 100644 --- a/swift/Example/ExampleMac/ExampleMacApp.swift +++ b/swift/Example/ExampleMac/ExampleMacApp.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import SwiftUI diff --git a/swift/Example/ExampleTests/ExampleTests.swift b/swift/Example/ExampleTests/ExampleTests.swift index c719046e6d8..427609b0096 100644 --- a/swift/Example/ExampleTests/ExampleTests.swift +++ b/swift/Example/ExampleTests/ExampleTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Gemfile b/swift/Gemfile new file mode 100644 index 00000000000..b734015f820 --- /dev/null +++ b/swift/Gemfile @@ -0,0 +1,10 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +source "https://rubygems.org" + +gem 'fastlane' + +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/swift/Gemfile.lock b/swift/Gemfile.lock new file mode 100644 index 00000000000..6005b99e515 --- /dev/null +++ b/swift/Gemfile.lock @@ -0,0 +1,220 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.5) + rexml + addressable (2.8.0) + public_suffix (>= 2.0.2, < 5.0) + artifactory (3.0.15) + atomos (0.1.3) + aws-eventstream (1.2.0) + aws-partitions (1.600.0) + aws-sdk-core (3.131.1) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.525.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.57.0) + aws-sdk-core (~> 3, >= 3.127.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.114.0) + aws-sdk-core (~> 3, >= 3.127.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.4) + aws-sigv4 (1.5.0) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + declarative (0.0.20) + digest-crc (0.6.4) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.7.6) + emoji_regex (3.2.3) + excon (0.92.3) + faraday (1.10.0) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.4) + multipart-post (~> 2) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.0) + faraday (~> 1.0) + fastimage (2.2.6) + fastlane (2.206.2) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander (~> 4.6) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (~> 2.0.0) + naturally (~> 2.2) + optparse (~> 0.1.1) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.3) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (>= 1.4.5, < 2.0.0) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3) + fastlane-plugin-create_xcframework (1.1.2) + gh_inspector (1.1.3) + google-apis-androidpublisher_v3 (0.22.0) + google-apis-core (>= 0.5, < 2.a) + google-apis-core (0.6.0) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + webrick + google-apis-iamcredentials_v1 (0.11.0) + google-apis-core (>= 0.5, < 2.a) + google-apis-playcustomapp_v1 (0.8.0) + google-apis-core (>= 0.5, < 2.a) + google-apis-storage_v1 (0.15.0) + google-apis-core (>= 0.5, < 2.a) + google-cloud-core (1.6.0) + google-cloud-env (~> 1.0) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.2.0) + google-cloud-storage (1.36.2) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.1) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.1.3) + faraday (>= 0.17.3, < 3.a) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.5) + domain_name (~> 0.5) + httpclient (2.8.3) + jmespath (1.6.1) + json (2.6.2) + jwt (2.4.1) + memoist (0.16.2) + mini_magick (4.11.0) + mini_mime (1.1.2) + multi_json (1.15.0) + multipart-post (2.0.0) + nanaimo (0.3.0) + naturally (2.2.1) + optparse (0.1.1) + os (1.1.4) + plist (3.6.0) + public_suffix (4.0.7) + rake (13.0.6) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.2.5) + rouge (2.0.7) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + security (0.1.3) + signet (0.16.1) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.0) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.8) + CFPropertyList + naturally + terminal-notifier (2.0.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.1) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.8.2) + unicode-display_width (1.8.0) + webrick (1.7.0) + word_wrap (1.0.0) + xcodeproj (1.21.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + ruby + +DEPENDENCIES + fastlane + fastlane-plugin-create_xcframework + +BUNDLED WITH + 2.1.4 diff --git a/swift/Playground.playground/Contents.swift b/swift/Playground.playground/Contents.swift index 832b3e213a6..4c886c35bc6 100644 --- a/swift/Playground.playground/Contents.swift +++ b/swift/Playground.playground/Contents.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import UIKit import TrustWalletCore diff --git a/swift/PrivacyInfo.xcprivacy b/swift/PrivacyInfo.xcprivacy new file mode 100644 index 00000000000..19df86cbf91 --- /dev/null +++ b/swift/PrivacyInfo.xcprivacy @@ -0,0 +1,33 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + 85F4.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + + diff --git a/swift/Sources/AnySigner.swift b/swift/Sources/AnySigner.swift index 9a6f5862cd6..5abf730b95d 100644 --- a/swift/Sources/AnySigner.swift +++ b/swift/Sources/AnySigner.swift @@ -10,7 +10,15 @@ import SwiftProtobuf public typealias SigningInput = Message public typealias SigningOutput = Message +/// Represents a signer to sign transactions for any blockchain. public final class AnySigner { + + /// Signs a transaction by SigningInput message and coin type + /// + /// - Parameters: + /// - input: The generic SigningInput SwiftProtobuf message + /// - coin: CoinType + /// - Returns: The generic SigningOutput SwiftProtobuf message public static func sign(input: SigningInput, coin: CoinType) -> SigningOutput { do { let outputData = nativeSign(data: try input.serializedData(), coin: coin) @@ -20,6 +28,12 @@ public final class AnySigner { } } + /// Signs a transaction by serialized data of a SigningInput and coin type + /// + /// - Parameters: + /// - data: The serialized data of a SigningInput + /// - coin: CoinType + /// - Returns: The serialized data of a SigningOutput public static func nativeSign(data: Data, coin: CoinType) -> Data { let inputData = TWDataCreateWithNSData(data) defer { @@ -28,10 +42,18 @@ public final class AnySigner { return TWDataNSData(TWAnySignerSign(inputData, TWCoinType(rawValue: coin.rawValue))) } + /// Check if AnySigner supports signing JSON representation of SigningInput for a given coin. public static func supportsJSON(coin: CoinType) -> Bool { return TWAnySignerSupportsJSON(TWCoinType(rawValue: coin.rawValue)) } + /// Signs a transaction specified by the JSON representation of a SigningInput, coin type and a private key + /// + /// - Parameters: + /// - json: JSON representation of a SigningInput + /// - key: The private key data + /// - coin: CoinType + /// - Returns: The JSON representation of a SigningOutput. public static func signJSON(_ json: String, key: Data, coin: CoinType) -> String { let jsonString = TWStringCreateWithNSString(json) let keyData = TWDataCreateWithNSData(key) @@ -41,6 +63,12 @@ public final class AnySigner { return TWStringNSString(TWAnySignerSignJSON(jsonString, keyData, TWCoinType(rawValue: coin.rawValue))) } + /// Plans a transaction (for UTXO chains only). + /// + /// - Parameters: + /// - input: The generic SigningInput SwiftProtobuf message + /// - coin: CoinType + /// - Returns: TransactionPlan SwiftProtobuf message public static func plan(input: SigningInput, coin: CoinType) -> TransactionPlan { do { let outputData = nativePlan(data: try input.serializedData(), coin: coin) @@ -50,6 +78,12 @@ public final class AnySigner { } } + /// Plans a transaction (for UTXO chains only). + /// + /// - Parameters: + /// - input: The serialized data of a SigningInput + /// - coin: CoinType + /// - Returns: The serialized data of a TransactionPlan public static func nativePlan(data: Data, coin: CoinType) -> Data { let inputData = TWDataCreateWithNSData(data) defer { diff --git a/swift/Sources/DerivationPath.Index.swift b/swift/Sources/DerivationPath.Index.swift deleted file mode 100644 index 09a43cc45c3..00000000000 --- a/swift/Sources/DerivationPath.Index.swift +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -import Foundation - -extension DerivationPath { - /// Derivation path index. - public struct Index: Codable, Hashable, CustomStringConvertible { - /// Index value. - public var value: UInt32 - - /// Whether the index is hardened. - public var hardened: Bool - - /// The derivation index. - public var derivationIndex: UInt32 { - if hardened { - return UInt32(value) | 0x80000000 - } else { - return UInt32(value) - } - } - - public init(_ value: UInt32, hardened: Bool = true) { - self.value = value - self.hardened = hardened - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(value) - hasher.combine(hardened) - } - - public static func == (lhs: Index, rhs: Index) -> Bool { - return lhs.value == rhs.value && lhs.hardened == rhs.hardened - } - - public var description: String { - if hardened { - return "\(value)'" - } else { - return value.description - } - } - } -} diff --git a/swift/Sources/DerivationPath.swift b/swift/Sources/DerivationPath.swift deleted file mode 100644 index 5e15395d7f6..00000000000 --- a/swift/Sources/DerivationPath.swift +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -import Foundation - -/// Represents a hierarchical deterministic derivation path. -public struct DerivationPath: Codable, Hashable, CustomStringConvertible { - var indexCount = 5 - - /// List of indices in the derivation path. - public private(set) var indices = [Index]() - - /// Address purpose, each coin will have a different value. - public var purpose: Purpose { - get { - return Purpose(rawValue: indices[0].value)! - } - set { - indices[0] = Index(newValue.rawValue, hardened: true) - } - } - - /// Coin type distinguishes between main net, test net, and forks. - public var coinType: UInt32 { - get { - return indices[1].value - } - set { - indices[1] = Index(newValue, hardened: true) - } - } - - /// Account number. - public var account: UInt32 { - get { - return indices[2].value - } - set { - indices[2] = Index(newValue, hardened: true) - } - } - - /// Change or private addresses will set this to 1. - public var change: UInt32 { - get { - return indices[3].value - } - set { - indices[3] = Index(newValue, hardened: false) - } - } - - /// Address number - public var address: UInt32 { - get { - return indices[4].value - } - set { - indices[4] = Index(newValue, hardened: false) - } - } - - init(indices: [Index]) { - precondition(indices.count == indexCount, "Not enough indices") - self.indices = indices - } - - /// Creates a `DerivationPath` by components. - public init(purpose: Purpose, coin: UInt32, account: UInt32 = 0, change: UInt32 = 0, address: UInt32 = 0) { - self.indices = [Index](repeating: Index(0), count: indexCount) - self.purpose = purpose - self.coinType = coin - self.account = account - self.change = change - self.address = address - } - - /// Creates a derivation path with a string description like `m/10/0/2'/3` - public init?(_ string: String) { - let components = string.split(separator: "/") - for component in components { - if component == "m" { - continue - } - if component.hasSuffix("'") { - guard let index = UInt32(component.dropLast()) else { - return nil - } - indices.append(Index(index, hardened: true)) - } else { - guard let index = UInt32(component) else { - return nil - } - indices.append(Index(index, hardened: false)) - } - } - guard indices.count == indexCount else { - return nil - } - } - - /// String representation. - public var description: String { - return "m/" + indices.map({ $0.description }).joined(separator: "/") - } - - public func hash(into hasher: inout Hasher) { - indices.forEach { hasher.combine($0) } - } - - public static func == (lhs: DerivationPath, rhs: DerivationPath) -> Bool { - return lhs.indices == rhs.indices - } -} diff --git a/swift/Sources/Dummy.cpp b/swift/Sources/Dummy.cpp index e433c2b6009..d495a10a6b7 100644 --- a/swift/Sources/Dummy.cpp +++ b/swift/Sources/Dummy.cpp @@ -1,7 +1,5 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // Dummy C++ file to force inclusion of the C++ STL when compiling. diff --git a/swift/Sources/Extensions/Account+Codable.swift b/swift/Sources/Extensions/Account+Codable.swift index 39b48c3d0c5..75449402873 100644 --- a/swift/Sources/Extensions/Account+Codable.swift +++ b/swift/Sources/Extensions/Account+Codable.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import Foundation diff --git a/swift/Sources/Extensions/AddressProtocol.swift b/swift/Sources/Extensions/AddressProtocol.swift index 273e71cea0f..5a522687283 100644 --- a/swift/Sources/Extensions/AddressProtocol.swift +++ b/swift/Sources/Extensions/AddressProtocol.swift @@ -1,11 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import Foundation +/// Generic Address protocol for AnyAddress / SegwitAddress / SolanaAddress public protocol Address: CustomStringConvertible {} extension AnyAddress: Equatable {} diff --git a/swift/Sources/Extensions/BitcoinAddress+Extension.swift b/swift/Sources/Extensions/BitcoinAddress+Extension.swift index 48b799a6064..eb11bd99cdd 100644 --- a/swift/Sources/Extensions/BitcoinAddress+Extension.swift +++ b/swift/Sources/Extensions/BitcoinAddress+Extension.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import Foundation diff --git a/swift/Sources/Extensions/CoinType+Address.swift b/swift/Sources/Extensions/CoinType+Address.swift index f0dbc2ea6ae..0cb5e2e0fff 100644 --- a/swift/Sources/Extensions/CoinType+Address.swift +++ b/swift/Sources/Extensions/CoinType+Address.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import Foundation diff --git a/swift/Sources/Extensions/Data+Hex.swift b/swift/Sources/Extensions/Data+Hex.swift index 3888716e089..f57ac017624 100644 --- a/swift/Sources/Extensions/Data+Hex.swift +++ b/swift/Sources/Extensions/Data+Hex.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import Foundation diff --git a/swift/Sources/Extensions/DerivationPath+Extension.swift b/swift/Sources/Extensions/DerivationPath+Extension.swift new file mode 100644 index 00000000000..a97d0fb0687 --- /dev/null +++ b/swift/Sources/Extensions/DerivationPath+Extension.swift @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import Foundation + +extension DerivationPath: Equatable, Hashable, CustomStringConvertible { + + public typealias Index = DerivationPathIndex + + public static func == (lhs: DerivationPath, rhs: DerivationPath) -> Bool { + return lhs.description == rhs.description + } + + public var coinType: UInt32 { + coin + } + + public var indices: [Index] { + var result = [Index]() + for i in 0.. DerivationPathIndex? { + return self.indexAt(index: UInt32(index)) + } + + public func hash(into hasher: inout Hasher) { + let count = indicesCount() + for i in 0.. Bool { + return lhs.value == rhs.value && lhs.hardened == rhs.hardened + } + + public convenience init(_ value: UInt32, hardened: Bool) { + self.init(value: value, hardened: hardened) + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(value) + hasher.combine(hardened) + } +} + +extension DerivationPathIndex: Codable { + private enum CodingKeys: String, CodingKey { + case value + case hardened + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(value, forKey: .value) + try container.encode(hardened, forKey: .hardened) + } + + public convenience init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let value = try container.decode(UInt32.self, forKey: .value) + let hardened = try container.decode(Bool.self, forKey: .hardened) + self.init(value: value, hardened: hardened) + } +} diff --git a/swift/Sources/Extensions/Mnemonic+Extension.swift b/swift/Sources/Extensions/Mnemonic+Extension.swift index 8eecce8b1ad..e9d0ebd825d 100644 --- a/swift/Sources/Extensions/Mnemonic+Extension.swift +++ b/swift/Sources/Extensions/Mnemonic+Extension.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import Foundation diff --git a/swift/Sources/Extensions/PublicKey+Bitcoin.swift b/swift/Sources/Extensions/PublicKey+Bitcoin.swift index 8694b7dbba0..421097f1c75 100644 --- a/swift/Sources/Extensions/PublicKey+Bitcoin.swift +++ b/swift/Sources/Extensions/PublicKey+Bitcoin.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. public extension PublicKey { /// Returns the ripemd160 hash of the sha2 hash of the compressed public key data. diff --git a/swift/Sources/KeyStore.swift b/swift/Sources/KeyStore.swift index 815843c87de..0b6c3368d77 100644 --- a/swift/Sources/KeyStore.swift +++ b/swift/Sources/KeyStore.swift @@ -74,8 +74,8 @@ public final class KeyStore { } /// Creates a new wallet. HD default by default - public func createWallet(name: String, password: String, coins: [CoinType]) throws -> Wallet { - let key = StoredKey(name: name, password: Data(password.utf8)) + public func createWallet(name: String, password: String, coins: [CoinType], encryption: StoredKeyEncryption = .aes128Ctr) throws -> Wallet { + let key = StoredKey(name: name, password: Data(password.utf8), encryption: encryption) return try saveCreatedWallet(for: key, password: password, coins: coins) } @@ -158,8 +158,8 @@ public final class KeyStore { /// - password: password to use for the imported private key /// - coin: coin to use for this wallet /// - Returns: new wallet - public func `import`(privateKey: PrivateKey, name: String, password: String, coin: CoinType) throws -> Wallet { - guard let newKey = StoredKey.importPrivateKey(privateKey: privateKey.data, name: name, password: Data(password.utf8), coin: coin) else { + public func `import`(privateKey: PrivateKey, name: String, password: String, coin: CoinType, encryption: StoredKeyEncryption = .aes128Ctr) throws -> Wallet { + guard let newKey = StoredKey.importPrivateKeyWithEncryption(privateKey: privateKey.data, name: name, password: Data(password.utf8), coin: coin, encryption: encryption) else { throw Error.invalidKey } let url = makeAccountURL() @@ -179,8 +179,8 @@ public final class KeyStore { /// - encryptPassword: password to use for encrypting /// - coins: coins to add /// - Returns: new account - public func `import`(mnemonic: String, name: String, encryptPassword: String, coins: [CoinType]) throws -> Wallet { - guard let key = StoredKey.importHDWallet(mnemonic: mnemonic, name: name, password: Data(encryptPassword.utf8), coin: coins.first ?? .ethereum) else { + public func `import`(mnemonic: String, name: String, encryptPassword: String, coins: [CoinType], encryption: StoredKeyEncryption = .aes128Ctr) throws -> Wallet { + guard let key = StoredKey.importHDWalletWithEncryption(mnemonic: mnemonic, name: name, password: Data(encryptPassword.utf8), coin: coins.first ?? .ethereum, encryption: encryption) else { throw Error.invalidMnemonic } let url = makeAccountURL() @@ -201,7 +201,7 @@ public final class KeyStore { /// - password: account password /// - newPassword: password to use for exported key /// - Returns: encrypted JSON key - public func export(wallet: Wallet, password: String, newPassword: String) throws -> Data { + public func export(wallet: Wallet, password: String, newPassword: String, encryption: StoredKeyEncryption = .aes128Ctr) throws -> Data { var privateKeyData = try exportPrivateKey(wallet: wallet, password: password) defer { privateKeyData.resetBytes(in: 0 ..< privateKeyData.count) @@ -211,12 +211,12 @@ public final class KeyStore { throw Error.accountNotFound } - if let mnemonic = checkMnemonic(privateKeyData), let newKey = StoredKey.importHDWallet(mnemonic: mnemonic, name: "", password: Data(newPassword.utf8), coin: coin) { + if let mnemonic = checkMnemonic(privateKeyData), let newKey = StoredKey.importHDWalletWithEncryption(mnemonic: mnemonic, name: "", password: Data(newPassword.utf8), coin: coin, encryption: encryption) { guard let json = newKey.exportJSON() else { throw Error.invalidKey } return json - } else if let newKey = StoredKey.importPrivateKey(privateKey: privateKeyData, name: "", password: Data(newPassword.utf8), coin: coin) { + } else if let newKey = StoredKey.importPrivateKeyWithEncryption(privateKey: privateKeyData, name: "", password: Data(newPassword.utf8), coin: coin, encryption: encryption) { guard let json = newKey.exportJSON() else { throw Error.invalidKey } @@ -269,11 +269,11 @@ public final class KeyStore { /// - wallet: wallet to update /// - password: current password /// - newName: new name - public func update(wallet: Wallet, password: String, newName: String) throws { - try update(wallet: wallet, password: password, newPassword: password, newName: newName) + public func update(wallet: Wallet, password: String, newName: String, encryption: StoredKeyEncryption = .aes128Ctr) throws { + try update(wallet: wallet, password: password, newPassword: password, newName: newName, encryption: encryption) } - private func update(wallet: Wallet, password: String, newPassword: String, newName: String) throws { + private func update(wallet: Wallet, password: String, newPassword: String, newName: String, encryption: StoredKeyEncryption = .aes128Ctr) throws { guard let index = wallets.firstIndex(of: wallet) else { fatalError("Missing wallet") } @@ -291,10 +291,10 @@ public final class KeyStore { } if let mnemonic = checkMnemonic(privateKeyData), - let key = StoredKey.importHDWallet(mnemonic: mnemonic, name: newName, password: Data(newPassword.utf8), coin: coins[0]) { + let key = StoredKey.importHDWalletWithEncryption(mnemonic: mnemonic, name: newName, password: Data(newPassword.utf8), coin: coins[0], encryption: encryption) { wallets[index].key = key - } else if let key = StoredKey.importPrivateKey( - privateKey: privateKeyData, name: newName, password: Data(newPassword.utf8), coin: coins[0]) { + } else if let key = StoredKey.importPrivateKeyWithEncryption( + privateKey: privateKeyData, name: newName, password: Data(newPassword.utf8), coin: coins[0], encryption: encryption) { wallets[index].key = key } else { throw Error.invalidKey diff --git a/swift/Sources/TWCardano.swift b/swift/Sources/TWCardano.swift index 1eb456be7ce..88670f92ab4 100644 --- a/swift/Sources/TWCardano.swift +++ b/swift/Sources/TWCardano.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import Foundation diff --git a/swift/Sources/TWData.swift b/swift/Sources/TWData.swift index f5cef1d23a2..01116fc0460 100644 --- a/swift/Sources/TWData.swift +++ b/swift/Sources/TWData.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import Foundation diff --git a/swift/Sources/TWString.swift b/swift/Sources/TWString.swift index a9225577c9a..30fb0b7ae3c 100644 --- a/swift/Sources/TWString.swift +++ b/swift/Sources/TWString.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import Foundation diff --git a/swift/Tests/AESTests.swift b/swift/Tests/AESTests.swift index 868300617de..2326b2bf3cf 100644 --- a/swift/Tests/AESTests.swift +++ b/swift/Tests/AESTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Addresses/BinanceAddressTests.swift b/swift/Tests/Addresses/BinanceAddressTests.swift new file mode 100644 index 00000000000..a950cf3c30e --- /dev/null +++ b/swift/Tests/Addresses/BinanceAddressTests.swift @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class BinanceAddressTests: XCTestCase { + func testIsValid() { + XCTAssertTrue(AnyAddress.isValid(string: "bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw", coin: .binance)) + + XCTAssertFalse(AnyAddress.isValid(string: "bad1devga6q804tx9fqrnx0vtu5r36kxgp9tqx8h9k", coin: .binance)) + XCTAssertFalse(AnyAddress.isValid(string: "tbnb1devga6q804tx9fqrnx0vtu5r36kxgp9t4ruzk2", coin: .binance)) + } + + func testIsValidBech32() { + XCTAssertTrue(AnyAddress.isValidBech32(string: "bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw", coin: .binance, hrp: "bnb")); + XCTAssertTrue(AnyAddress.isValidBech32(string: "tbnb1devga6q804tx9fqrnx0vtu5r36kxgp9t4ruzk2", coin: .binance, hrp: "tbnb")); + + XCTAssertFalse(AnyAddress.isValidBech32(string: "bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw", coin: .binance, hrp: "tbnb")); + XCTAssertFalse(AnyAddress.isValidBech32(string: "tbnb1devga6q804tx9fqrnx0vtu5r36kxgp9t4ruzk2", coin: .binance, hrp: "bnb")); + } +} diff --git a/swift/Tests/Addresses/BitcoinAddressTests.swift b/swift/Tests/Addresses/BitcoinAddressTests.swift index 14341165f07..da079e80027 100644 --- a/swift/Tests/Addresses/BitcoinAddressTests.swift +++ b/swift/Tests/Addresses/BitcoinAddressTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Addresses/JunoAddressTests.swift b/swift/Tests/Addresses/JunoAddressTests.swift new file mode 100644 index 00000000000..eb6e0739ba7 --- /dev/null +++ b/swift/Tests/Addresses/JunoAddressTests.swift @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class JunoAddressTests: XCTestCase { + func testAnyAddressValidation() { + let addr = AnyAddress(string: "juno1gckvjxau7k56f8wg8c8xj80khyp83y8x8eqc94", coin: .cosmos, hrp: "juno")!; + XCTAssertTrue(AnyAddress.isValidBech32(string: addr.description, coin: .cosmos, hrp: "juno")); + XCTAssertFalse(AnyAddress.isValidBech32(string: addr.description, coin: .bitcoin, hrp: "juno")); + XCTAssertFalse(AnyAddress.isValid(string: addr.description, coin: .bitcoin)); + XCTAssertFalse(AnyAddress.isValid(string: addr.description, coin: .cosmos)); + } + + func testAnyAddressFromPubkey() { + let data = Data(hexString: "02753f5c275e1847ba4d2fd3df36ad00af2e165650b35fe3991e9c9c46f68b12bc")!; + let pubkey = PublicKey(data: data, type: .secp256k1)!; + let anyAddr = AnyAddress(publicKey: pubkey, coin: .cosmos, hrp: "juno"); + XCTAssertEqual(anyAddr.description, "juno1cj2vfjec3c3luf9fx9vddnglhh9gawmncn4k5n"); + } +} diff --git a/swift/Tests/Addresses/NEOAddressTests.swift b/swift/Tests/Addresses/NEOAddressTests.swift index 35c4b2ea57b..fffdc401277 100644 --- a/swift/Tests/Addresses/NEOAddressTests.swift +++ b/swift/Tests/Addresses/NEOAddressTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Addresses/OntologyAddressTests.swift b/swift/Tests/Addresses/OntologyAddressTests.swift index 616aa19e309..760465b3ecd 100644 --- a/swift/Tests/Addresses/OntologyAddressTests.swift +++ b/swift/Tests/Addresses/OntologyAddressTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Addresses/TronAddressTests.swift b/swift/Tests/Addresses/TronAddressTests.swift index c8d875a38d0..cc2b3a6773f 100644 --- a/swift/Tests/Addresses/TronAddressTests.swift +++ b/swift/Tests/Addresses/TronAddressTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/AnyAddressTests.swift b/swift/Tests/AnyAddressTests.swift new file mode 100644 index 00000000000..85893b6ba17 --- /dev/null +++ b/swift/Tests/AnyAddressTests.swift @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class AnyAddressTests: XCTestCase { + let any_address_test_address = "bc1qcj2vfjec3c3luf9fx9vddnglhh9gawmncmgxhz" + let any_address_test_pubkey = "02753f5c275e1847ba4d2fd3df36ad00af2e165650b35fe3991e9c9c46f68b12bc" + + func testCreateWithString() { + let coin = CoinType.bitcoin + let address = AnyAddress(string: any_address_test_address, coin: coin)! + XCTAssertEqual(address.coin, coin) + XCTAssertEqual(address.description, any_address_test_address) + } + + func testCreateWithStringBech32() { + let coin = CoinType.bitcoin + let address1 = AnyAddress(string: any_address_test_address, coin: coin, hrp: "bc")! + XCTAssertEqual(address1.description, any_address_test_address) + + let address2 = AnyAddress(string: "tb1qcj2vfjec3c3luf9fx9vddnglhh9gawmnjan4v3", coin: coin, hrp: "tb")! + XCTAssertEqual(address2.description, "tb1qcj2vfjec3c3luf9fx9vddnglhh9gawmnjan4v3") + } + + func testCreateWithPublicKey() { + let coin = CoinType.bitcoin + let pubkey = PublicKey(data: Data(hexString: any_address_test_pubkey)!, type: .secp256k1)! + let address = AnyAddress(publicKey: pubkey, coin: coin) + XCTAssertEqual(address.description, any_address_test_address) + } + + func testCreateWithPublicKeyDerivation() { + let coin = CoinType.bitcoin + let pubkey = PublicKey(data: Data(hexString: any_address_test_pubkey)!, type: .secp256k1)! + let address1 = AnyAddress(publicKey: pubkey, coin: coin, derivation: .bitcoinSegwit) + XCTAssertEqual(address1.description, any_address_test_address) + + let address2 = AnyAddress(publicKey: pubkey, coin: coin, derivation: .bitcoinLegacy) + XCTAssertEqual(address2.description, "1JvRfEQFv5q5qy9uTSAezH7kVQf4hqnHXx") + } + + func testCreateBech32WithPublicKey() { + let coin = CoinType.bitcoin + let pubkey = PublicKey(data: Data(hexString: any_address_test_pubkey)!, type: .secp256k1)! + let address1 = AnyAddress(publicKey: pubkey, coin: coin, hrp: "bc") + XCTAssertEqual(address1.description, any_address_test_address) + + let address2 = AnyAddress(publicKey: pubkey, coin: coin, hrp: "tb") + XCTAssertEqual(address2.description, "tb1qcj2vfjec3c3luf9fx9vddnglhh9gawmnjan4v3") + } + + func testIsValid() { + let coin = CoinType.bitcoin + XCTAssertTrue(AnyAddress.isValid(string: any_address_test_address, coin: coin)); + XCTAssertFalse(AnyAddress.isValid(string: any_address_test_address, coin: .ethereum)); + XCTAssertFalse(AnyAddress.isValid(string: "__INVALID_ADDRESS__", coin: .ethereum)); + } + + func testIsValidBech32() { + let coin = CoinType.bitcoin + XCTAssertTrue(AnyAddress.isValidBech32(string: any_address_test_address, coin: coin, hrp: "bc")); + XCTAssertFalse(AnyAddress.isValidBech32(string: any_address_test_address, coin: coin, hrp: "tb")); + } +} diff --git a/swift/Tests/AsnParserTests.swift b/swift/Tests/AsnParserTests.swift new file mode 100644 index 00000000000..38965bb8b5f --- /dev/null +++ b/swift/Tests/AsnParserTests.swift @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class AsnParserTests: XCTestCase { + func testEcdsaSignatureFromDer() { + let encoded = Data(hexString: "3046022100db421231f23d0320dbb8f1284b600cd34b8e9218628139539ff4f1f6c05495da022100ff715aab70d5317dbf8ee224eb18bec3120cfb9db1000dbb31eadaf96c71c1b1")! + let expected = Data(hexString: "db421231f23d0320dbb8f1284b600cd34b8e9218628139539ff4f1f6c05495daff715aab70d5317dbf8ee224eb18bec3120cfb9db1000dbb31eadaf96c71c1b1")! + let actual = AsnParser.ecdsaSignatureFromDer(encoded: encoded) + XCTAssertEqual(actual, expected); + } +} diff --git a/swift/Tests/BarzTests.swift b/swift/Tests/BarzTests.swift new file mode 100644 index 00000000000..c5a40f64363 --- /dev/null +++ b/swift/Tests/BarzTests.swift @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class BarzTests: XCTestCase { + + // https://testnet.bscscan.com/tx/0x6c6e1fe81c722c0abce1856b9b4e078ab2cad06d51f2d1b04945e5ba2286d1b4 + func testInitCode() { + let factoryAddress = "0x3fC708630d85A3B5ec217E53100eC2b735d4f800" + let publicKeyData = Data(hexString: "0x04e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b02")! + let publicKey = PublicKey(data: publicKeyData, type: .nist256p1Extended)! + let verificationFacet = "0x6BF22ff186CC97D88ECfbA47d1473a234CEBEFDf" + let result = Barz.getInitCode(factory: factoryAddress, publicKey: publicKey, verificationFacet: verificationFacet, salt: 0) + XCTAssertEqual(result.hexString, "3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000") + } + + func testInitCodeNoneZeroSalt() { + let factoryAddress = "0x3fC708630d85A3B5ec217E53100eC2b735d4f800" + let publicKeyData = Data(hexString: "0x04e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b02")! + let publicKey = PublicKey(data: publicKeyData, type: .nist256p1Extended)! + let verificationFacet = "0x6BF22ff186CC97D88ECfbA47d1473a234CEBEFDf" + let result = Barz.getInitCode(factory: factoryAddress, publicKey: publicKey, verificationFacet: verificationFacet, salt: 1) + XCTAssertEqual(result.hexString, "3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000") + } + + func testCounterfactualAddress() { + let input = BarzContractAddressInput.with { + $0.factory = "0x2c97f4a366Dd5D91178ec9E36c5C1fcA393A538C" + $0.accountFacet = "0x3322C04EAe11B9b14c6c289f2668b6f07071b591" + $0.verificationFacet = "0x90A6fE0A938B0d4188e9013C99A0d7D9ca6bFB63" + $0.entryPoint = entryPoint + $0.facetRegistry = "0xFd1A8170c12747060324D9079a386BD4290e6f93" + $0.defaultFallback = "0x22eB0720d9Fc4bC90BB812B309e939880B71c20d" + $0.bytecode = bytecode + $0.publicKey = "0xB5547FBdC56DCE45e1B8ef75569916D438e09c46" + $0.salt = 0 + } + + XCTAssertEqual(Barz.getCounterfactualAddress(input: try! input.serializedData()), "0x77F62bb3E43190253D4E198199356CD2b25063cA"); + } + + func testCounterfactualAddressNonZeroSalt() { + let input = BarzContractAddressInput.with { + $0.factory = "0x96C489979E39F877BDb8637b75A25C1a5B2DE14C" + $0.accountFacet = "0xF6F5e5fC74905e65e3FF53c6BacEba8535dd14d1" + $0.verificationFacet = "0xaB84813cbf26Fd951CB3d7E33Dccb8995027e490" + $0.entryPoint = entryPoint + $0.facetRegistry = "0x9a95d201BB8F559771784D12c01F8084278c65E5" + $0.defaultFallback = "0x522cDc7558b5f798dF5D61AB09B6D95Ebd342EF9" + $0.bytecode = "0x60806040526040516104c83803806104c883398101604081905261002291610163565b6000858585858560405160240161003d959493929190610264565b60408051601f198184030181529181526020820180516001600160e01b0316634a93641760e01b1790525190915060009081906001600160a01b038a16906100869085906102c3565b600060405180830381855af49150503d80600081146100c1576040519150601f19603f3d011682016040523d82523d6000602084013e6100c6565b606091505b50915091508115806100e157506100dc816102df565b600114155b156100ff57604051636ff35f8960e01b815260040160405180910390fd5b505050505050505050610306565b80516001600160a01b038116811461012457600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561015a578181015183820152602001610142565b50506000910152565b60008060008060008060c0878903121561017c57600080fd5b6101858761010d565b95506101936020880161010d565b94506101a16040880161010d565b93506101af6060880161010d565b92506101bd6080880161010d565b60a08801519092506001600160401b03808211156101da57600080fd5b818901915089601f8301126101ee57600080fd5b81518181111561020057610200610129565b604051601f8201601f19908116603f0116810190838211818310171561022857610228610129565b816040528281528c602084870101111561024157600080fd5b61025283602083016020880161013f565b80955050505050509295509295509295565b600060018060a01b0380881683528087166020840152808616604084015280851660608401525060a0608083015282518060a08401526102ab8160c085016020870161013f565b601f01601f19169190910160c0019695505050505050565b600082516102d581846020870161013f565b9190910192915050565b80516020808301519190811015610300576000198160200360031b1b821691505b50919050565b6101b3806103156000396000f3fe60806040523661000b57005b600080356001600160e01b03191681527f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f6020819052604090912054819060601c806100cf576004838101546040516366ffd66360e11b81526000356001600160e01b031916928101929092526001600160a01b03169063cdffacc690602401602060405180830381865afa1580156100a8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100cc919061014d565b90505b6001600160a01b0381166101295760405162461bcd60e51b815260206004820152601d60248201527f4261727a3a2046756e6374696f6e20646f6573206e6f74206578697374000000604482015260640160405180910390fd5b3660008037600080366000845af43d6000803e808015610148573d6000f35b3d6000fd5b60006020828403121561015f57600080fd5b81516001600160a01b038116811461017657600080fd5b939250505056fea2646970667358221220d35db061bb6ecdb7688c3674af669ce44d527cae4ded59214d06722d73da62be64736f6c63430008120033" + $0.publicKey = "0xB5547FBdC56DCE45e1B8ef75569916D438e09c46" + $0.salt = 123456 + } + + XCTAssertEqual(Barz.getCounterfactualAddress(input: try! input.serializedData()), "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"); + } + + func testFormatSignature() { + let signature = Data(hexString: "0x3044022012d89e3b41e253dc9e90bd34dc1750d059b76d0b1d16af2059aa26e90b8960bf0220256d8a05572c654906ce422464693e280e243e6d9dbc5f96a681dba846bca276")! + let challenge = Data(hexString: "0xcf267a78c5adaf96f341a696eb576824284c572f3e61be619694d539db1925f9")! + let authenticatorData = Data(hexString: "0x1a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d00000000")! + let clientDataJSON = "{\"type\":\"webauthn.get\",\"challenge\":\"zyZ6eMWtr5bzQaaW61doJChMVy8-Yb5hlpTVOdsZJfk\",\"origin\":\"https://trustwallet.com\"}"; + let result = Barz.getFormattedSignature(signature: signature, challenge: challenge, authenticatorData: authenticatorData, clientDataJSON: clientDataJSON); + XCTAssertEqual(result.hexString, "12d89e3b41e253dc9e90bd34dc1750d059b76d0b1d16af2059aa26e90b8960bf256d8a05572c654906ce422464693e280e243e6d9dbc5f96a681dba846bca27600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000251a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025222c226f726967696e223a2268747470733a2f2f747275737477616c6c65742e636f6d227d000000000000000000000000000000000000000000000000000000") + } + + // https://testnet.bscscan.com/tx/0x43fc13dfdf06bbb09da8ce070953753764f1e43782d0c8b621946d8b45749419 + func testSignK1TransferAccountDeployed() { + let input = EthereumSigningInput.with { + $0.chainID = Data(hexString: "61")! + $0.nonce = Data(hexString: "02")! + $0.txMode = .userOp + $0.gasLimit = Data(hexString: "0186A0")! + $0.maxFeePerGas = Data(hexString: "01a339c9e9")! + $0.maxInclusionFeePerGas = Data(hexString: "01a339c9e9")! + $0.toAddress = "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9" + $0.privateKey = Data(hexString: "3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483")! + + $0.userOperation = EthereumUserOperation.with { + $0.verificationGasLimit = Data(hexString: "0186a0")! + $0.preVerificationGas = Data(hexString: "b708")! + $0.entryPoint = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" + $0.sender = "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f" + } + + $0.transaction = EthereumTransaction.with { + $0.transfer = EthereumTransaction.Transfer.with { + $0.amount = Data(hexString: "2386f26fc10000")! + } + } + } + let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) + XCTAssertEqual(output.preHash.hexString, "2d37191a8688f69090451ed90a0a9ba69d652c2062ee9d023b3ebe964a3ed2ae") + XCTAssertEqual(String(data: output.encoded, encoding: .utf8), "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"100000\",\"initCode\":\"0x\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"2\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0xb16Db98B365B1f89191996942612B14F1Da4Bd5f\",\"signature\":\"0x80e84992ebf8d5f71180231163ed150a7557ed0aa4b4bcee23d463a09847e4642d0fbf112df2e5fa067adf4b2fa17fc4a8ac172134ba5b78e3ec9c044e7f28d71c\",\"verificationGasLimit\":\"100000\"}") + } + + // https://testnet.bscscan.com/tx/0xea1f5cddc0653e116327cbcb3bc770360a642891176eff2ec69c227e46791c31 + func testSignR1TransferAccountNotDeployed() { + let attestationObject = Data(hexString: "0xa363666d74646e6f6e656761747453746d74a068617574684461746158981a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e075d00000000000000000000000000000000000000000014c14f8a2dfd8f451581fad6e4e1c11821abcaacd6a5010203262001215820b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2225820116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f")! + let publicKey = WebAuthn.getPublicKey(attestationObject: attestationObject)! + + let input = EthereumSigningInput.with { + $0.chainID = Data(hexString: "61")! + $0.nonce = Data(hexString: "00")! + $0.txMode = .userOp + $0.gasLimit = Data(hexString: "2625A0")! + $0.maxFeePerGas = Data(hexString: "01a339c9e9")! + $0.maxInclusionFeePerGas = Data(hexString: "01a339c9e9")! + $0.toAddress = "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9" + $0.privateKey = Data(hexString: "3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483")! + + $0.userOperation = EthereumUserOperation.with { + $0.verificationGasLimit = Data(hexString: "2DC6C0")! + $0.preVerificationGas = Data(hexString: "b708")! + $0.entryPoint = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" + $0.sender = "0x1392Ae041BfBdBAA0cFF9234a0C8F64df97B7218" + $0.initCode = Barz.getInitCode( + factory: factory, + publicKey: publicKey, + verificationFacet: "0x5034534Efe9902779eD6eA6983F435c00f3bc510", + salt: 0 + ) + } + + $0.transaction = EthereumTransaction.with { + $0.transfer = EthereumTransaction.Transfer.with { + $0.amount = Data(hexString: "2386f26fc10000")! + } + } + } + let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) + XCTAssertEqual(output.preHash.hexString, "548c13a0bb87981d04a3a24a78ad5e4ba8d0afbf3cfe9311250e07b54cd38937") + XCTAssertEqual(String(data: output.encoded, encoding: .utf8), "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"2500000\",\"initCode\":\"0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000005034534efe9902779ed6ea6983f435c00f3bc51000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f00000000000000000000000000000000000000000000000000000000000000\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"0\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0x1392Ae041BfBdBAA0cFF9234a0C8F64df97B7218\",\"signature\":\"0xbf1b68323974e71ad9bd6dfdac07dc062599d150615419bb7876740d2bcf3c8909aa7e627bb0e08a2eab930e2e7313247c9b683c884236dd6ea0b6834fb2cb0a1b\",\"verificationGasLimit\":\"3000000\"}"); + } + + // https://testnet.bscscan.com/tx/0x872f709815a9f79623a349f2f16d93b52c4d5136967bab53a586f045edbe9203 + func testSignR1BatchedTransferAccountDeployed() { + let approveFunc = EthereumAbiFunction(name: "approve") + approveFunc.addParamAddress(val: Data(hexString: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789")!, isOutput: false) + approveFunc.addParamUInt256(val: Data(hexString: "8AC7230489E80000")!, isOutput: false) + let approveCall = EthereumAbi.encode(fn: approveFunc) + + let transferFunc = EthereumAbiFunction(name: "transfer") + transferFunc.addParamAddress(val: Data(hexString: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789")!, isOutput: false) + transferFunc.addParamUInt256(val: Data(hexString: "8AC7230489E80000")!, isOutput: false) + let transferCall = EthereumAbi.encode(fn: transferFunc) + + let input = EthereumSigningInput.with { + $0.chainID = Data(hexString: "61")! + $0.nonce = Data(hexString: "03")! + $0.txMode = .userOp + $0.gasLimit = Data(hexString: "015A61")! + $0.maxFeePerGas = Data(hexString: "02540BE400")! + $0.maxInclusionFeePerGas = Data(hexString: "02540BE400")! + $0.privateKey = Data(hexString: "3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483")! + + $0.userOperation = EthereumUserOperation.with { + $0.verificationGasLimit = Data(hexString: "07F7C4")! + $0.preVerificationGas = Data(hexString: "DAFC")! + $0.entryPoint = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" + $0.sender = "0x1e6c542ebc7c960c6a155a9094db838cef842cf5" + } + + $0.transaction = EthereumTransaction.with { + $0.batch = EthereumTransaction.Batch.with { + $0.calls = [ + EthereumTransaction.Batch.BatchedCall.with { + $0.address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266" + $0.amount = Data(hexString: "00")! + $0.payload = approveCall + }, + EthereumTransaction.Batch.BatchedCall.with { + $0.address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266" + $0.amount = Data(hexString: "00")! + $0.payload = transferCall + } + ] + } + } + } + let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) + XCTAssertEqual(output.preHash.hexString, "84d0464f5a2b191e06295443970ecdcd2d18f565d0d52b5a79443192153770ab") + XCTAssertEqual(String(data: output.encoded, encoding: .utf8), "{\"callData\":\"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"88673\",\"initCode\":\"0x\",\"maxFeePerGas\":\"10000000000\",\"maxPriorityFeePerGas\":\"10000000000\",\"nonce\":\"3\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"56060\",\"sender\":\"0x1E6c542ebC7c960c6A155A9094DB838cEf842cf5\",\"signature\":\"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c\",\"verificationGasLimit\":\"522180\"}") + } + + let factory = "0x3fC708630d85A3B5ec217E53100eC2b735d4f800" + let diamondCutFacet = "0x312382b3B302bDcC0711fe710314BE298426296f" + let accountFacet = "0x84E684272903737d807375197f9a581FEa094Bc3" + let tokenReceiverFacet = "0x77E64E56966430a5B7A4F4A20C9fe039afb6ec21" + let diamondLoupeFacet = "0x518834B7EE4461d703ED2de8bCdfC5eCf761bBCA" + let entryPoint = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" + let diamondInit = "0x02a3C76D089c50615139B904c4dbD62F20e74a1b" + let facetRegistry = "0x77A4259d76897bA1eC8D6c3EFc5c35e0D7572A8f" + let bytecode = "0x608060405260405162003cc638038062003cc68339818101604052810190620000299190620019ad565b60008673ffffffffffffffffffffffffffffffffffffffff16633253960f6040518163ffffffff1660e01b81526004016020604051808303816000875af115801562000079573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200009f919062001aec565b9050600060e01b817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191603620000fe576040517f5a5b4d3900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600267ffffffffffffffff8111156200011e576200011d6200196b565b5b6040519080825280602002602001820160405280156200015b57816020015b62000147620018ff565b8152602001906001900390816200013d5790505b5090506000600167ffffffffffffffff8111156200017e576200017d6200196b565b5b604051908082528060200260200182016040528015620001ad5781602001602082028036833780820191505090505b5090506000600167ffffffffffffffff811115620001d057620001cf6200196b565b5b604051908082528060200260200182016040528015620001ff5781602001602082028036833780820191505090505b509050631f931c1c60e01b8260008151811062000221576200022062001b21565b5b60200260200101907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152505060405180606001604052808d73ffffffffffffffffffffffffffffffffffffffff16815260200160006002811115620002ab57620002aa62001b37565b5b81526020018381525083600081518110620002cb57620002ca62001b21565b5b60200260200101819052508381600081518110620002ee57620002ed62001b21565b5b60200260200101907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152505060405180606001604052808b73ffffffffffffffffffffffffffffffffffffffff1681526020016000600281111562000378576200037762001b37565b5b8152602001828152508360018151811062000398576200039762001b21565b5b6020026020010181905250620003b9846200047e60201b620001671760201c565b6200046c838c8b8e8e8d8d8d8d604051602401620003de979695949392919062001b8c565b6040516020818303038152906040527f95a21aec000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050620004b060201b620001911760201c565b5050505050505050505050506200211f565b806200048f6200073460201b60201c565b60010160016101000a81548163ffffffff021916908360e01c021790555050565b60005b8351811015620006df576000848281518110620004d557620004d462001b21565b5b602002602001015160200151905060006002811115620004fa57620004f962001b37565b5b81600281111562000510576200050f62001b37565b5b0362000570576200056a85838151811062000530576200052f62001b21565b5b60200260200101516000015186848151811062000552576200055162001b21565b5b6020026020010151604001516200073960201b60201c565b620006c8565b6001600281111562000587576200058662001b37565b5b8160028111156200059d576200059c62001b37565b5b03620005fd57620005f7858381518110620005bd57620005bc62001b21565b5b602002602001015160000151868481518110620005df57620005de62001b21565b5b602002602001015160400151620009db60201b60201c565b620006c7565b60028081111562000613576200061262001b37565b5b81600281111562000629576200062862001b37565b5b0362000689576200068385838151811062000649576200064862001b21565b5b6020026020010151600001518684815181106200066b576200066a62001b21565b5b60200260200101516040015162000c8f60201b60201c565b620006c6565b6040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620006bd9062001be7565b60405180910390fd5b5b5b508080620006d69062001c61565b915050620004b3565b507f8faa70878671ccd212d20771b795c50af8fd3ff6cf27f4bde57e5d4de0aeb673838383604051620007159392919062001c82565b60405180910390a16200072f828262000e3760201b60201c565b505050565b600090565b600081511162000780576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620007779062001d97565b60405180910390fd5b60006200079262000f6b60201b60201c565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160362000806576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620007fd9062001dfb565b60405180910390fd5b60008160010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054905090506000816bffffffffffffffffffffffff16036200087c576200087b828562000f9860201b60201c565b5b60005b8351811015620009d4576000848281518110620008a157620008a062001b21565b5b602002602001015190506000846000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161462000998576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200098f9062001e5f565b60405180910390fd5b620009ac8583868a6200107c60201b60201c565b8380620009b99062001ec3565b94505050508080620009cb9062001c61565b9150506200087f565b5050505050565b600081511162000a22576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000a199062001d97565b60405180910390fd5b600062000a3462000f6b60201b60201c565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160362000aa8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000a9f9062001dfb565b60405180910390fd5b60008160010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054905090506000816bffffffffffffffffffffffff160362000b1e5762000b1d828562000f9860201b60201c565b5b60005b835181101562000c8857600084828151811062000b435762000b4262001b21565b5b602002602001015190506000846000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508673ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160362000c39576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000c309062001eef565b60405180910390fd5b62000c4c8582846200122960201b60201c565b62000c608583868a6200107c60201b60201c565b838062000c6d9062001ec3565b9450505050808062000c7f9062001c61565b91505062000b21565b5050505050565b600081511162000cd6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000ccd9062001d97565b60405180910390fd5b600062000ce862000f6b60201b60201c565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161462000d5c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000d539062001f53565b60405180910390fd5b60005b825181101562000e3157600083828151811062000d815762000d8062001b21565b5b602002602001015190506000836000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905062000e198482846200122960201b60201c565b5050808062000e289062001c61565b91505062000d5f565b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16031562000f675762000e988260405180606001604052806028815260200162003c7a60289139620018aa60201b60201c565b6000808373ffffffffffffffffffffffffffffffffffffffff168360405162000ec2919062001fb7565b600060405180830381855af49150503d806000811462000eff576040519150601f19603f3d011682016040523d82523d6000602084013e62000f04565b606091505b50915091508162000f645760008151111562000f235780518082602001fd5b83836040517f192105d700000000000000000000000000000000000000000000000000000000815260040162000f5b92919062001fd7565b60405180910390fd5b50505b5050565b6000807f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f90508091505090565b62000fc38160405180606001604052806024815260200162003ca260249139620018aa60201b60201c565b81600201805490508260010160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001018190555081600201819080600181540180825580915050600190039060005260206000200160009091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b81846000016000857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508360010160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018390806001815401808255809150506001900390600052602060002090600891828204019190066004029091909190916101000a81548163ffffffff021916908360e01c021790555080846000016000857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036200129b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620012929062002003565b60405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036200130c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620013039062002067565b60405180910390fd5b6000836000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160149054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff169050600060018560010160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000180549050620013e59190620020cb565b9050808214620015805760008560010160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000182815481106200144a576200144962001b21565b5b90600052602060002090600891828204019190066004029054906101000a900460e01b9050808660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018481548110620014c957620014c862001b21565b5b90600052602060002090600891828204019190066004026101000a81548163ffffffff021916908360e01c021790555082866000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550505b8460010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600001805480620015d757620015d6620020ec565b5b60019003818190600052602060002090600891828204019190066004026101000a81549063ffffffff02191690559055846000016000847bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001908152602001600020600080820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556000820160146101000a8154906bffffffffffffffffffffffff0219169055505060008103620018a357600060018660020180549050620016c49190620020cb565b905060008660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001015490508181146200180c57600087600201838154811062001732576200173162001b21565b5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508088600201838154811062001779576200177862001b21565b5b9060005260206000200160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550818860010160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010181905550505b86600201805480620018235762001822620020ec565b5b6001900381819060005260206000200160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905590558660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001016000905550505b5050505050565b6000823b9050600081118290620018f9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620018f0919062002102565b60405180910390fd5b50505050565b6040518060600160405280600073ffffffffffffffffffffffffffffffffffffffff168152602001600060028111156200193e576200193d62001b37565b5b8152602001606081525090565b60008151905060018060a01b03811681146200196657600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b83811015620019a157808201518184015260208101905062001984565b50600083830152505050565b600080600080600080600080610100898b031215620019cb57600080fd5b620019d6896200194b565b9750620019e660208a016200194b565b9650620019f660408a016200194b565b955062001a0660608a016200194b565b945062001a1660808a016200194b565b935062001a2660a08a016200194b565b925062001a3660c08a016200194b565b915060e089015160018060401b038082111562001a5257600080fd5b818b0191508b601f83011262001a6757600080fd5b81518181111562001a7d5762001a7c6200196b565b5b601f1960405181603f83601f860116011681019150808210848311171562001aaa5762001aa96200196b565b5b816040528281528e602084870101111562001ac457600080fd5b62001ad783602083016020880162001981565b80955050505050509295985092959890939650565b60006020828403121562001aff57600080fd5b815163ffffffff60e01b8116811462001b1757600080fd5b8091505092915050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052602160045260246000fd5b60018060a01b03811682525050565b6000815180845262001b7681602086016020860162001981565b6020601f19601f83011685010191505092915050565b600060018060a01b03808a168352808916602084015280881660408401528087166060840152808616608084015280851660a08401525060e060c083015262001bd960e083018462001b5c565b905098975050505050505050565b60208152602760208201527f4c69624469616d6f6e644375743a20496e636f7272656374204661636574437560408201527f74416374696f6e0000000000000000000000000000000000000000000000000060608201526000608082019050919050565b634e487b7160e01b600052601160045260246000fd5b60008019820362001c775762001c7662001c4b565b5b600182019050919050565b60006060808301818452808751808352608092508286019150828160051b8701016020808b0160005b8481101562001d6357607f198a8503018652815188850160018060a01b038251168652848201516003811062001cf157634e487b7160e01b600052602160045260246000fd5b80868801525060408083015192508a81880152508082518083528a880191508684019350600092505b8083101562001d465763ffffffff60e01b84511682528682019150868401935060018301925062001d1a565b508096505050508282019150828601955060018101905062001cab565b505062001d738189018b62001b4d565b50868103604088015262001d88818962001b5c565b95505050505050949350505050565b60208152602b60208201527f4c69624469616d6f6e644375743a204e6f2073656c6563746f727320696e206660408201527f6163657420746f2063757400000000000000000000000000000000000000000060608201526000608082019050919050565b60208152602c60208201527f4c69624469616d6f6e644375743a204164642066616365742063616e2774206260408201527f652061646472657373283029000000000000000000000000000000000000000060608201526000608082019050919050565b60208152603560208201527f4c69624469616d6f6e644375743a2043616e2774206164642066756e6374696f60408201527f6e207468617420616c726561647920657869737473000000000000000000000060608201526000608082019050919050565b600060018060601b0380831681810362001ee25762001ee162001c4b565b5b6001810192505050919050565b60208152603860208201527f4c69624469616d6f6e644375743a2043616e2774207265706c6163652066756e60408201527f6374696f6e20776974682073616d652066756e6374696f6e000000000000000060608201526000608082019050919050565b60208152603660208201527f4c69624469616d6f6e644375743a2052656d6f7665206661636574206164647260408201527f657373206d75737420626520616464726573732830290000000000000000000060608201526000608082019050919050565b6000825162001fcb81846020870162001981565b80830191505092915050565b60018060a01b038316815260406020820152600062001ffa604083018462001b5c565b90509392505050565b60208152603760208201527f4c69624469616d6f6e644375743a2043616e27742072656d6f76652066756e6360408201527f74696f6e207468617420646f65736e277420657869737400000000000000000060608201526000608082019050919050565b60208152602e60208201527f4c69624469616d6f6e644375743a2043616e27742072656d6f766520696d6d7560408201527f7461626c652066756e6374696f6e00000000000000000000000000000000000060608201526000608082019050919050565b6000828203905081811115620020e657620020e562001c4b565b5b92915050565b634e487b7160e01b600052603160045260246000fd5b60208152600062002117602083018462001b5c565b905092915050565b611b4b806200212f6000396000f3fe60806040523661000b57005b6000807f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f9050809150600082600001600080357fffffffff00000000000000000000000000000000000000000000000000000000167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610141576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610138906114d3565b60405180910390fd5b3660008037600080366000845af43d6000803e8060008114610162573d6000f35b3d6000fd5b806101706103c0565b60010160016101000a81548163ffffffff021916908360e01c021790555050565b60005b83518110156103755760008482815181106101b2576101b1611510565b5b6020026020010151602001519050600060028111156101d4576101d3611526565b5b8160028111156101e7576101e6611526565b5b036102375761023285838151811061020257610201611510565b5b60200260200101516000015186848151811061022157610220611510565b5b6020026020010151604001516103c5565b610361565b6001600281111561024b5761024a611526565b5b81600281111561025e5761025d611526565b5b036102ae576102a985838151811061027957610278611510565b5b60200260200101516000015186848151811061029857610297611510565b5b60200260200101516040015161063c565b610360565b6002808111156102c1576102c0611526565b5b8160028111156102d4576102d3611526565b5b036103245761031f8583815181106102ef576102ee611510565b5b60200260200101516000015186848151811061030e5761030d611510565b5b6020026020010151604001516108bd565b61035f565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103569061153c565b60405180910390fd5b5b5b50808061036d906115b6565b915050610194565b507f8faa70878671ccd212d20771b795c50af8fd3ff6cf27f4bde57e5d4de0aeb6738383836040516103a99392919061163b565b60405180910390a16103bb8282610a48565b505050565b600090565b6000815111610409576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161040090611747565b60405180910390fd5b6000610413610b6a565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610484576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161047b906117ab565b60405180910390fd5b60008160010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054905090506000816bffffffffffffffffffffffff16036104f1576104f08285610b97565b5b60005b835181101561063557600084828151811061051257610511611510565b5b602002602001015190506000846000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610606576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105fd9061180f565b60405180910390fd5b6106128583868a610c72565b838061061d90611873565b9450505050808061062d906115b6565b9150506104f4565b5050505050565b6000815111610680576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067790611747565b60405180910390fd5b600061068a610b6a565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036106fb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106f2906117ab565b60405180910390fd5b60008160010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054905090506000816bffffffffffffffffffffffff1603610768576107678285610b97565b5b60005b83518110156108b657600084828151811061078957610788611510565b5b602002602001015190506000846000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508673ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361087c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610873906118a2565b60405180910390fd5b610887858284610e1f565b6108938583868a610c72565b838061089e90611873565b945050505080806108ae906115b6565b91505061076b565b5050505050565b6000815111610901576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108f890611747565b60405180910390fd5b600061090b610b6a565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161461097c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161097390611906565b60405180910390fd5b60005b8251811015610a4257600083828151811061099d5761099c611510565b5b602002602001015190506000836000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050610a2d848284610e1f565b50508080610a3a906115b6565b91505061097f565b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160315610b6657610a9f82604051806060016040528060288152602001611aca60289139611481565b6000808373ffffffffffffffffffffffffffffffffffffffff1683604051610ac7919061196a565b600060405180830381855af49150503d8060008114610b02576040519150601f19603f3d011682016040523d82523d6000602084013e610b07565b606091505b509150915081610b6357600081511115610b245780518082602001fd5b83836040517f192105d7000000000000000000000000000000000000000000000000000000008152600401610b5a929190611988565b60405180910390fd5b50505b5050565b6000807f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f90508091505090565b610bb981604051806060016040528060248152602001611af260249139611481565b81600201805490508260010160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001018190555081600201819080600181540180825580915050600190039060005260206000200160009091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b81846000016000857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508360010160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018390806001815401808255809150506001900390600052602060002090600891828204019190066004029091909190916101000a81548163ffffffff021916908360e01c021790555080846000016000857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610e8e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e85906119b2565b60405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610efc576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ef390611a16565b60405180910390fd5b6000836000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160149054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff169050600060018560010160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000180549050610fd39190611a7a565b90508082146111675760008560010160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600001828154811061103457611033611510565b5b90600052602060002090600891828204019190066004029054906101000a900460e01b9050808660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000184815481106110b0576110af611510565b5b90600052602060002090600891828204019190066004026101000a81548163ffffffff021916908360e01c021790555082866000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550505b8460010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054806111bb576111ba611a98565b5b60019003818190600052602060002090600891828204019190066004026101000a81549063ffffffff02191690559055846000016000847bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001908152602001600020600080820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556000820160146101000a8154906bffffffffffffffffffffffff021916905550506000810361147a576000600186600201805490506112a59190611a7a565b905060008660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001015490508181146113e657600087600201838154811061130f5761130e611510565b5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508088600201838154811061135357611352611510565b5b9060005260206000200160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550818860010160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010181905550505b866002018054806113fa576113f9611a98565b5b6001900381819060005260206000200160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905590558660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001016000905550505b5050505050565b6000823b90506000811182906114cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114c49190611aae565b60405180910390fd5b50505050565b602081526020808201527f4469616d6f6e643a2046756e6374696f6e20646f6573206e6f7420657869737460408201526000606082019050919050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052602160045260246000fd5b60208152602760208201527f4c69624469616d6f6e644375743a20496e636f7272656374204661636574437560408201527f74416374696f6e0000000000000000000000000000000000000000000000000060608201526000608082019050919050565b634e487b7160e01b600052601160045260246000fd5b6000801982036115c9576115c86115a0565b5b600182019050919050565b60018060a01b03811682525050565b60005b838110156116015780820151818401526020810190506115e6565b50600083830152505050565b600081518084526116258160208601602086016115e3565b6020601f19601f83011685010191505092915050565b60006060808301818452808751808352608092508286019150828160051b8701016020808b0160005b8481101561171757607f198a8503018652815188850160018060a01b03825116865284820151600381106116a857634e487b7160e01b600052602160045260246000fd5b80868801525060408083015192508a81880152508082518083528a880191508684019350600092505b808310156116fb5763ffffffff60e01b8451168252868201915086840193506001830192506116d1565b5080965050505082820191508286019550600181019050611664565b50506117258189018b6115d4565b508681036040880152611738818961160d565b95505050505050949350505050565b60208152602b60208201527f4c69624469616d6f6e644375743a204e6f2073656c6563746f727320696e206660408201527f6163657420746f2063757400000000000000000000000000000000000000000060608201526000608082019050919050565b60208152602c60208201527f4c69624469616d6f6e644375743a204164642066616365742063616e2774206260408201527f652061646472657373283029000000000000000000000000000000000000000060608201526000608082019050919050565b60208152603560208201527f4c69624469616d6f6e644375743a2043616e2774206164642066756e6374696f60408201527f6e207468617420616c726561647920657869737473000000000000000000000060608201526000608082019050919050565b60006bffffffffffffffffffffffff808316818103611895576118946115a0565b5b6001810192505050919050565b60208152603860208201527f4c69624469616d6f6e644375743a2043616e2774207265706c6163652066756e60408201527f6374696f6e20776974682073616d652066756e6374696f6e000000000000000060608201526000608082019050919050565b60208152603660208201527f4c69624469616d6f6e644375743a2052656d6f7665206661636574206164647260408201527f657373206d75737420626520616464726573732830290000000000000000000060608201526000608082019050919050565b6000825161197c8184602087016115e3565b80830191505092915050565b60018060a01b03831681526040602082015260006119a9604083018461160d565b90509392505050565b60208152603760208201527f4c69624469616d6f6e644375743a2043616e27742072656d6f76652066756e6360408201527f74696f6e207468617420646f65736e277420657869737400000000000000000060608201526000608082019050919050565b60208152602e60208201527f4c69624469616d6f6e644375743a2043616e27742072656d6f766520696d6d7560408201527f7461626c652066756e6374696f6e00000000000000000000000000000000000060608201526000608082019050919050565b6000828203905081811115611a9257611a916115a0565b5b92915050565b634e487b7160e01b600052603160045260246000fd5b602081526000611ac1602083018461160d565b90509291505056fe4c69624469616d6f6e644375743a205f696e6974206164647265737320686173206e6f20636f64654c69624469616d6f6e644375743a204e657720666163657420686173206e6f20636f6465a264697066735822122045b771fb2128a1a34c5b052e9a86464933844b34929cf0d65bbea6a4e76e3b2764736f6c634300081200334c69624469616d6f6e644375743a205f696e6974206164647265737320686173206e6f20636f64654c69624469616d6f6e644375743a204e657720666163657420686173206e6f20636f6465" +} diff --git a/swift/Tests/Base32Tests.swift b/swift/Tests/Base32Tests.swift new file mode 100644 index 00000000000..d02a6ebc1d9 --- /dev/null +++ b/swift/Tests/Base32Tests.swift @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class Base32Tests: XCTestCase { + func testEncode() { + let encoded = Base32.encode(data: Data.init(bytes: "HelloWorld", count: 10)); + XCTAssertEqual(encoded, "JBSWY3DPK5XXE3DE"); + } + + func testEncodeWithAlphabet() { + let encoded = Base32.encodeWithAlphabet(data: Data.init(bytes: "7uoq6tp427uzv7fztkbsnn64iwotfrristwpryy", count: 39), alphabet: "abcdefghijklmnopqrstuvwxyz234567"); + XCTAssertEqual(encoded, "g52w64jworydimrxov5hmn3gpj2gwyttnzxdmndjo5xxiztsojuxg5dxobzhs6i"); + } + + func testDecode() { + guard let decoded = Base32.decode(string: "JBSWY3DPK5XXE3DE") else { + return XCTFail(); + } + let toCompare = String(data: decoded, encoding:.utf8); + + XCTAssertEqual(toCompare, "HelloWorld"); + } + + func testDecodeWithAlphabet() { + guard let decoded = Base32.decodeWithAlphabet(string: "g52w64jworydimrxov5hmn3gpj2gwyttnzxdmndjo5xxiztsojuxg5dxobzhs6i", alphabet:"abcdefghijklmnopqrstuvwxyz234567") else { + return XCTFail(); + } + let toCompare = String(data: decoded, encoding:.utf8); + + XCTAssertEqual(toCompare, "7uoq6tp427uzv7fztkbsnn64iwotfrristwpryy"); + } +} diff --git a/swift/Tests/Base64Tests.swift b/swift/Tests/Base64Tests.swift new file mode 100644 index 00000000000..dd310795b78 --- /dev/null +++ b/swift/Tests/Base64Tests.swift @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class Base64Tests: XCTestCase { + func testEncode() { + let encoded = Base64.encode(data: Data.init(bytes: "HelloWorld", count: 10)); + XCTAssertEqual(encoded, "SGVsbG9Xb3JsZA=="); + } + + func testDecode() { + guard let decoded = Base64.decode(string: "SGVsbG9Xb3JsZA==") else { + return XCTFail(); + } + let toCompare = String(data: decoded, encoding:.utf8); + + XCTAssertEqual(toCompare, "HelloWorld"); + } + + func testUrlEncode() { + let encoded = Base64.encodeUrl(data: Data.init(bytes: "+\\?ab", count: 5)); + XCTAssertEqual(encoded, "K1w_YWI="); + } + + func testUrlDecode() { + guard let decoded = Base64.decodeUrl(string: "K1w_YWI=") else { + return XCTFail(); + } + let toCompare = String(data: decoded, encoding:.utf8); + + XCTAssertEqual(toCompare, "+\\?ab"); + } +} diff --git a/swift/Tests/Blockchains/AcalaEVMTests.swift b/swift/Tests/Blockchains/AcalaEVMTests.swift new file mode 100644 index 00000000000..dd4577c23a1 --- /dev/null +++ b/swift/Tests/Blockchains/AcalaEVMTests.swift @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class AcalaEVMTests: XCTestCase { + func testAddress() { + let key = PrivateKey(data: Data(hexString: "828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .acalaEVM) + let expected = AnyAddress(string: "0xe32DC46bfBF78D1eada7b0a68C96903e01418D64", coin: .acalaEVM)! + + XCTAssertEqual(address.description, expected.description) + } +} diff --git a/swift/Tests/Blockchains/AcalaTests.swift b/swift/Tests/Blockchains/AcalaTests.swift new file mode 100644 index 00000000000..4ef108e7f2c --- /dev/null +++ b/swift/Tests/Blockchains/AcalaTests.swift @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class AcalaTests: XCTestCase { + + let genesisHash = Data(hexString: "0xfc41b9bd8ef8fe53d58c7ea67c794c7ec9a73daf05e6d54b14ff6342c99ba64c")! + + func testAddress() { + let key = PrivateKey(data: Data(hexString: "0x9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0")!)! + let pubkey = key.getPublicKeyEd25519() + let address = AnyAddress(publicKey: pubkey, coin: .acala) + let addressFromString = AnyAddress(string: "269ZCS3WLGydTN8ynhyhZfzJrXkePUcdhwgLQs6TWFs5wVL5", coin: .acala)! + + XCTAssertEqual(address.description, addressFromString.description) + XCTAssertEqual(address.data, pubkey.data) + } + + func testSigning() { + // real key in 1p test + let key = PrivateKey(data: Data(hexString: "9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0")!)! + + let input = PolkadotSigningInput.with { + $0.genesisHash = genesisHash + $0.blockHash = Data(hexString: "0x707ffa05b7dc6cdb6356bd8bd51ff20b2757c3214a76277516080a10f1bc7537")! + $0.era = PolkadotEra.with { + $0.blockNumber = 3893613 + $0.period = 64 + } + $0.nonce = 0 + $0.specVersion = 2170 + $0.network = CoinType.acala.ss58Prefix + $0.transactionVersion = 2 + $0.privateKey = key.data + $0.balanceCall.transfer = PolkadotBalance.Transfer.with { + $0.value = Data(hexString: "0xe8d4a51000")! // 1 ACA + $0.toAddress = "25Qqz3ARAvnZbahGZUzV3xpP1bB3eRrupEprK7f2FNbHbvsz" + $0.callIndices = PolkadotCallIndices.with { + $0.custom = PolkadotCustomCallIndices.with { + $0.moduleIndex = 0x0a + $0.methodIndex = 0x00 + } + } + } + $0.multiAddress = true + } + let output: PolkadotSigningOutput = AnySigner.sign(input: input, coin: .polkadot) + + // https://acala.subscan.io/extrinsic/3893620-3 + XCTAssertEqual("0x" + output.encoded.hexString, "0x41028400e9590e4d99264a14a85e21e69537e4a64f66a875d38cb8f76b305f41fabe24a900dd54466dffd1e3c80b76013e9459fbdcd17805bd5fdbca0961a643bad1cbd2b7fe005c62c51c18b67f31eb9e61b187a911952fee172ef18402d07c703eec3100d50200000a0000c8c602ded977c56076ae38d98026fa669ca10d6a2b5a0bfc4086ae7668ed1c60070010a5d4e8") + } +} diff --git a/swift/Tests/Blockchains/AeternityTests.swift b/swift/Tests/Blockchains/AeternityTests.swift index e92183f72ce..b5a7a619f0d 100644 --- a/swift/Tests/Blockchains/AeternityTests.swift +++ b/swift/Tests/Blockchains/AeternityTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Blockchains/AgoricTests.swift b/swift/Tests/Blockchains/AgoricTests.swift new file mode 100644 index 00000000000..a35e0ed4948 --- /dev/null +++ b/swift/Tests/Blockchains/AgoricTests.swift @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class AgoricTests: XCTestCase { + + func testAddress() { + let key = PrivateKey(data: Data(hexString: "037048190544fa57651452f477c096de4f3073e7835cf3845b04602563a73f73")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let address = AnyAddress(publicKey: pubkey, coin: .agoric) + let addressFromString = AnyAddress(string: "agoric18zvvgk6j3eq5wd7mqxccgt20gz2w94cy88aek5", coin: .agoric)! + + XCTAssertEqual(pubkey.data.hexString, "03df9a5e4089f89d45913fb2b856de984c7e8bf1344cc6444cc9705899a48c939d") + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSign() { + let privateKey = PrivateKey(data: Data(hexString: "037048190544fa57651452f477c096de4f3073e7835cf3845b04602563a73f73")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .agoric) + + let sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress.description + $0.toAddress = "agoric1cqvwa8jr6pmt45jndanc8lqmdsxjkhw0yertc0" + $0.amounts = [CosmosAmount.with { + $0.amount = "1" + $0.denom = "ubld" + }] + } + + + let fee = CosmosFee.with { + $0.gas = 100000 + $0.amounts = [CosmosAmount.with { + $0.amount = "2000" + $0.denom = "ubld" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 62972 + $0.chainID = "agoric-3" + $0.sequence = 1 + $0.messages = [ + CosmosMessage.with { + $0.sendCoinsMessage = sendCoinsMessage + } + ] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .agoric) + + XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWFnb3JpYzE4enZ2Z2s2ajNlcTV3ZDdtcXhjY2d0MjBnejJ3OTRjeTg4YWVrNRItYWdvcmljMWNxdndhOGpyNnBtdDQ1am5kYW5jOGxxbWRzeGpraHcweWVydGMwGgkKBHVibGQSATESZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA9+aXkCJ+J1FkT+yuFbemEx+i/E0TMZETMlwWJmkjJOdEgQKAggBGAESEgoMCgR1YmxkEgQyMDAwEKCNBhpAenbGO4UBK610dwSY6l5pl58qwHW1OujQ/9vF9unQdrA1SE0b/2mZxnevy5y3u6pJfBffWUfCx68PcVEu7D3EYQ==\"}") + } +} diff --git a/swift/Tests/Blockchains/AionTests.swift b/swift/Tests/Blockchains/AionTests.swift index c65188e041a..89c731de13f 100644 --- a/swift/Tests/Blockchains/AionTests.swift +++ b/swift/Tests/Blockchains/AionTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Blockchains/AlgorandTests.swift b/swift/Tests/Blockchains/AlgorandTests.swift index 0df5b148700..7c03fbb1a07 100644 --- a/swift/Tests/Blockchains/AlgorandTests.swift +++ b/swift/Tests/Blockchains/AlgorandTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest @@ -18,6 +16,29 @@ class AlgorandTests: XCTestCase { XCTAssertEqual(pubkey.data.hexString, "00d1857babdde3d70dad15110a9093e77abef991524f10dfa6a727946bfdd411") XCTAssertEqual(address.description, addressFromString.description) } + + func testSignNFTTransfer() { + // Successfully broadcasted: https://algoexplorer.io/tx/FFLUH4QKZHG744RIQ2AZNWZUSIIH262KZ4MEWSY4RXMWN5NMOOJA + let round: UInt64 = 27963950 + let transaction = AlgorandAssetTransfer.with { + $0.toAddress = "362T7CSXNLIOBX6J3H2SCPS4LPYFNV6DDWE6G64ZEUJ6SY5OJIR6SB5CVE" + $0.amount = 1 + $0.assetID = 989643841 + } + let input = AlgorandSigningInput.with { + $0.genesisID = "mainnet-v1.0" + $0.genesisHash = Data(base64Encoded: "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=")! + $0.note = "TWT TO THE MOON".data(using: .utf8)! + $0.privateKey = Data(hexString: "dc6051ffc7b3ec601bde432f6dea34d40fe3855e4181afa0f0524c42194a6da7")! + $0.firstRound = round + $0.lastRound = round + 1000 + $0.fee = 1000 + $0.assetTransfer = transaction + } + let output: AlgorandSigningOutput = AnySigner.sign(input: input, coin: .algorand) + + XCTAssertEqual(output.signature, "nXQsDH1ilG3DIo2VQm5tdYKXe9o599ygdqikmROpZiNXAvQeK3avJqgjM5o+iByCdq6uOxlbveDyVmL9nZxxBg==") + } func testSign() { let round: UInt64 = 1937767 diff --git a/swift/Tests/Blockchains/AptosTests.swift b/swift/Tests/Blockchains/AptosTests.swift new file mode 100644 index 00000000000..335b02aad87 --- /dev/null +++ b/swift/Tests/Blockchains/AptosTests.swift @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class AptosTests: XCTestCase { + func testAddress() { + let anyAddress = AnyAddress(string: "0x6af7d07b8a541913dfa87a9f99628faa255c70241ef9ebd9b82a7e715ee13108", coin: .aptos) + + XCTAssertEqual(anyAddress?.description, "0x6af7d07b8a541913dfa87a9f99628faa255c70241ef9ebd9b82a7e715ee13108") + XCTAssertEqual(anyAddress?.coin, .aptos) + + let invalid = "MQqpqMQgCBuiPkoXfgZZsJvuzCeI1zc00z6vHJj4" + XCTAssertNil(Data(hexString: invalid)) + XCTAssertNil(AnyAddress(string: invalid, coin: .aptos)) + XCTAssertFalse(AnyAddress.isValid(string: invalid, coin: .aptos)) + } + + func testBlindSign() { + // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x7efd69e7f9462774b932ce500ab51c0d0dcc004cf272e09f8ffd5804c2a84e33?network=mainnet + let payloadJson = """ + { + "function": "0x16fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c::AnimeSwapPoolV1::swap_exact_coins_for_coins_3_pair_entry", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin", + "0x881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f4::coin::MOJO", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC" + ], + "arguments": ["1000000", "49329"], + "type": "entry_function_payload" + } + """ + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467?network=devnet + let privateKeyData = Data(hexString: "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")! + let input = AptosSigningInput.with { + $0.chainID = 1 + $0.anyEncoded = payloadJson + $0.expirationTimestampSecs = 3664390082 + $0.gasUnitPrice = 100 + $0.maxGasAmount = 100011 + $0.sequenceNumber = 42 + $0.sender = "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30" + $0.privateKey = privateKeyData + } + let output: AptosSigningOutput = AnySigner.sign(input: input, coin: .aptos) + let expectedRawTx = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada0000000001" + let expectedSignature = "42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b" + let expectedSignedTx = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c4042cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b" + XCTAssertEqual(output.rawTxn.hexString, expectedRawTx) + XCTAssertEqual(output.authenticator.signature.hexString, expectedSignature) + XCTAssertEqual(output.encoded.hexString, expectedSignedTx) + } + + func testSign() { + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467?network=devnet + let privateKeyData = Data(hexString: "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")! + let transferMsg = AptosTransferMessage.with { + $0.to = "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30" + $0.amount = 1000 + } + let input = AptosSigningInput.with { + $0.chainID = 33 + $0.sender = "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30" + $0.expirationTimestampSecs = 3664390082 + $0.gasUnitPrice = 100 + $0.maxGasAmount = 3296766 + $0.sequenceNumber = 99 + $0.transfer = transferMsg + $0.privateKey = privateKeyData + } + let output: AptosSigningOutput = AnySigner.sign(input: input, coin: .aptos) + let expectedRawTx = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021" + let expectedSignature = "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01" + let expectedSignedTx = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01" + XCTAssertEqual(output.rawTxn.hexString, expectedRawTx) + XCTAssertEqual(output.authenticator.signature.hexString, expectedSignature) + XCTAssertEqual(output.encoded.hexString, expectedSignedTx) + } +} diff --git a/swift/Tests/Blockchains/AvalancheTests.swift b/swift/Tests/Blockchains/AvalancheTests.swift index 5f6d6bb595c..cc0da61aaae 100644 --- a/swift/Tests/Blockchains/AvalancheTests.swift +++ b/swift/Tests/Blockchains/AvalancheTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest @@ -18,4 +16,20 @@ class AvalancheTests: XCTestCase { XCTAssertEqual(address.description, addressETH.description) XCTAssertEqual(address.data.hexString, addressETH.data.hexString) } + + func testXPub() { + let wallet = HDWallet(mnemonic: "liquid spider narrow follow black west cabbage intact stadium resource gentle raccoon", passphrase: "")! + + let xpub1 = wallet.getExtendedPublicKey(purpose: .bip44, coin: .ethereum, version: .xpub) + let xpub2 = wallet.getExtendedPublicKey(purpose: .bip44, coin: .avalancheCChain, version: .xpub) + + XCTAssertEqual(xpub1, xpub2) + XCTAssertEqual(xpub2, "xpub6Bmo35QfCNvffj8tZsTVRvFxA5ULv2aHDV8dDCa7q6SzkMLJffxWRNE5vSJ4hANoKpmSp3p7Nbcp1vEz5F785HV4Aq2A6jWwHoyWp3EFgYp") + + let xpri1 = wallet.getExtendedPrivateKey(purpose: .bip44, coin: .ethereum, version: .xprv) + let xpri2 = wallet.getExtendedPrivateKey(purpose: .bip44, coin: .smartChain, version: .xprv) + + XCTAssertEqual(xpri1, xpri2) + XCTAssertEqual(xpri2, "xprv9xnSdZsmN1NNTF4RTqvV4nKDc3drWZrRrGD2QpAWGkv1sZ1A88eFsZuc59vKRr4mxJELH7C18ymaHENodYzEbeLq1JmPUAy3CmpA2inVCwo") + } } diff --git a/swift/Tests/Blockchains/BandChainTests.swift b/swift/Tests/Blockchains/BandChainTests.swift index 03200907d37..60fb141add8 100644 --- a/swift/Tests/Blockchains/BandChainTests.swift +++ b/swift/Tests/Blockchains/BandChainTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Blockchains/BinanceChainTests.swift b/swift/Tests/Blockchains/BinanceChainTests.swift index a1e0a021715..8f16331ee46 100644 --- a/swift/Tests/Blockchains/BinanceChainTests.swift +++ b/swift/Tests/Blockchains/BinanceChainTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore @@ -27,6 +25,16 @@ class BinanceChainTests: XCTestCase { XCTAssertEqual("bnb1devga6q804tx9fqrnx0vtu5r36kxgp9tmk4xkm", address.description) } + func testBinanceTestnet() { + let wallet = HDWallet(mnemonic: "rabbit tilt arm protect banner ill produce vendor april bike much identify pond upset front easily glass gallery address hair priority focus forest angle", passphrase: "")! + let privateKey = wallet.getKeyForCoin(coin: .binance) + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let address = AnyAddress(publicKey: publicKey, coin: .binance, hrp: "tbnb") + + XCTAssertEqual(privateKey.data.hexString, "727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06") + XCTAssertEqual("tbnb1devga6q804tx9fqrnx0vtu5r36kxgp9t4ruzk2", address.description) + } + func testSignSendOrder() { let privateKey = PrivateKey(data: Data(hexString: "95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832")!)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) @@ -228,4 +236,35 @@ class BinanceChainTests: XCTestCase { } queue.waitUntilAllOperationsAreFinished() } + + func testSignFromWalletConnectRequest() throws { + // Step 1: Parse a signing request received through WalletConnect. + + let requestPayload = """ + {"signerAddress":"bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2","signDoc":{"account_number":"19","chain_id":"chain-bnb","memo":"","data":null,"msgs":[{"inputs":[{"address":"bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2","coins":[{"amount":1001000000,"denom":"BNB"}]}],"outputs":[{"address":"bnb13zeh6hs97d5eu2s5qerguhv8ewwue6u4ywa6yf","coins":[{"amount":1001000000,"denom":"BNB"}]}]}],"sequence":"23","source":"1"}} + """ + let parsingInput = WalletConnectParseRequestInput.with { + $0.method = .cosmosSignAmino + $0.payload = requestPayload + } + let parsingInputBytes = try parsingInput.serializedData() + + let parsingOutputBytes = WalletConnectRequest.parse(coin: .binance, input: parsingInputBytes) + let parsingOutput = try WalletConnectParseRequestOutput(serializedData: parsingOutputBytes) + + var signingInput = parsingOutput.binance + + // Step 2: Set missing fields. + + signingInput.privateKey = Data(hexString: "95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832")! + + // Step 3: Sign the transaction. + + let output: BinanceSigningOutput = AnySigner.sign(input: signingInput, coin: .binance) + + let expected = """ + {"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Amo1kgCI2Yw4iMpoxT38k/RWRgJgbLuH8P5e5TPbOOUC"},"signature":"PCTHhMa7+Z1U/6uxU+3LbTxKd0k231xypdMolyVvjgYvMg+0dTMC+wqW8IxHWXTSDt/Ronu+7ac1h/WN3JWJdQ=="} + """ + XCTAssertEqual(output.signatureJson, expected) + } } diff --git a/swift/Tests/Blockchains/BinanceSmartChainTests.swift b/swift/Tests/Blockchains/BinanceSmartChainTests.swift index 972c46c178e..a1d82e098e8 100644 --- a/swift/Tests/Blockchains/BinanceSmartChainTests.swift +++ b/swift/Tests/Blockchains/BinanceSmartChainTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Blockchains/BitcoinDiamondTests.swift b/swift/Tests/Blockchains/BitcoinDiamondTests.swift new file mode 100644 index 00000000000..1afbbdc18f2 --- /dev/null +++ b/swift/Tests/Blockchains/BitcoinDiamondTests.swift @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class BitcoinDiamondTests: XCTestCase { + func testAddress() { + let key = PrivateKey(data: Data(hexString: "d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let address = AnyAddress(publicKey: pubkey, coin: .bitcoinDiamond) + let addressFromString = AnyAddress(string: "1G15VvshDxwFTnahZZECJfFwEkq9fP79o8", coin: .bitcoinDiamond)! + + XCTAssertEqual(pubkey.data.hexString, "02485a209514cc896f8ed736e205bc4c35bd5299ef3f9e84054475336b964c02a3") + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSign() { + let key = PrivateKey(data: Data(hexString: "d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")!)! + + let script = BitcoinScript.lockScriptForAddress(address: "1G15VvshDxwFTnahZZECJfFwEkq9fP79o8", coin: .bitcoinDiamond) + let utxos = [ + BitcoinUnspentTransaction.with { + $0.outPoint.hash = Data(hexString: "034f4667301711e8a69236a93476ed798f9c11aaae472da5b315191a0453461d")! + $0.outPoint.index = 0 + $0.outPoint.sequence = UINT32_MAX + $0.script = script.data + $0.amount = 27615 + } + ] + + let plan = BitcoinTransactionPlan.with { + $0.amount = 17615 + $0.fee = 10000 + $0.change = 0 + $0.utxos = utxos + $0.preblockhash = Data(hexString: "99668daf7c77c6462f3ba8fab61b4f62abaabd62911e7e59729ee9e1929dfa4b")! + } + + let input = BitcoinSigningInput.with { + $0.amount = 17615 + $0.toAddress = "1HevQVTSEc8cEpDm65UpfCdj5erd4xxBhx" + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .bitcoinDiamond) + $0.coinType = CoinType.bitcoinDiamond.rawValue + $0.privateKey = [key.data] + $0.plan = plan + } + + let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .bitcoinDiamond) + XCTAssertEqual(output.error, .ok) + XCTAssertEqual(output.encoded.hexString, "0c00000099668daf7c77c6462f3ba8fab61b4f62abaabd62911e7e59729ee9e1929dfa4b01034f4667301711e8a69236a93476ed798f9c11aaae472da5b315191a0453461d000000006a473044022078e0d3a9e1eb270ab02c15f8fcf1d3bfc95a324839690b7de4f011a4266132ff02204679e8103c4d3f0bb5192a5f53cc273732fd0e8392ab3b00dc708fd24d0160b3012102485a209514cc896f8ed736e205bc4c35bd5299ef3f9e84054475336b964c02a3ffffffff01cf440000000000001976a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac00000000") + } +} diff --git a/swift/Tests/Blockchains/BitcoinTests.swift b/swift/Tests/Blockchains/BitcoinTests.swift index e4b7255fc76..c33fc2a27f8 100644 --- a/swift/Tests/Blockchains/BitcoinTests.swift +++ b/swift/Tests/Blockchains/BitcoinTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore @@ -11,6 +9,217 @@ class BitcoinTransactionSignerTests: XCTestCase { override func setUp() { continueAfterFailure = false } + + func testSignBrc20Commit() throws { + // Successfully broadcasted: https://www.blockchain.com/explorer/transactions/btc/797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1 + let privateKeyData = Data(hexString: "e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129")! + let dustAmount = 546 as Int64 + let txId = Data.reverse(hexString: "8ec895b4d30adb01e38471ca1019bfc8c3e5fbd1f28d9e7b5653260d89989008") + + let privateKey = PrivateKey(data: privateKeyData)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + + let utxo0 = BitcoinV2Input.with { + $0.outPoint = BitcoinV2OutPoint.with { + $0.hash = txId + $0.vout = 1 + } + $0.value = 26_400 + $0.sighashType = BitcoinSigHashType.all.rawValue + $0.scriptBuilder = BitcoinV2Input.InputBuilder.with { + $0.p2Wpkh = BitcoinV2PublicKeyOrHash.with { + $0.pubkey = publicKey.data + } + } + } + + let out0 = BitcoinV2Output.with { + $0.value = 7_000 + $0.builder = BitcoinV2Output.OutputBuilder.with { + $0.brc20Inscribe = BitcoinV2Output.OutputBrc20Inscription.with { + $0.inscribeTo = publicKey.data + $0.ticker = "oadf" + $0.transferAmount = "20" + } + } + } + + // Change/return transaction. Set it explicitly. + let changeOut = BitcoinV2Output.with { + $0.value = 16_400 + $0.builder = BitcoinV2Output.OutputBuilder.with { + $0.p2Wpkh = BitcoinV2PublicKeyOrHash.with { + $0.pubkey = publicKey.data + } + } + } + + let signingInput = BitcoinV2SigningInput.with { + $0.version = .v2 + $0.privateKeys = [privateKeyData] + $0.inputs = [utxo0] + $0.outputs = [out0, changeOut] + $0.inputSelector = .useAll + $0.chainInfo = BitcoinV2ChainInfo.with { + $0.p2PkhPrefix = 0 + $0.p2ShPrefix = 5 + } + $0.fixedDustThreshold = dustAmount + } + + let legacySigningInput = BitcoinSigningInput.with { + $0.signingV2 = signingInput + } + + let output: BitcoinSigningOutput = AnySigner.sign(input: legacySigningInput, coin: .bitcoin) + XCTAssertEqual(output.error, .ok) + let outputV2 = output.signingResultV2 + XCTAssertEqual(outputV2.error, .ok) + XCTAssertEqual(outputV2.encoded.hexString, "02000000000101089098890d2653567b9e8df2d1fbe5c3c8bf1910ca7184e301db0ad3b495c88e0100000000ffffffff02581b000000000000225120e8b706a97732e705e22ae7710703e7f589ed13c636324461afa443016134cc051040000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d02483045022100a44aa28446a9a886b378a4a65e32ad9a3108870bd725dc6105160bed4f317097022069e9de36422e4ce2e42b39884aa5f626f8f94194d1013007d5a1ea9220a06dce0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"); + XCTAssertEqual(outputV2.txid.hexString, "797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1") + } + + func testSignBrc20Reveal() throws { + // Successfully broadcasted: https://www.blockchain.com/explorer/transactions/btc/7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca + let privateKeyData = Data(hexString: "e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129")! + let dustAmount = 546 as Int64 + // Now spend just created `797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1` commit output. + let txIdCommit = Data.reverse(hexString: "797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1") + + let privateKey = PrivateKey(data: privateKeyData)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + + let utxo0 = BitcoinV2Input.with { + $0.outPoint = BitcoinV2OutPoint.with { + $0.hash = txIdCommit + $0.vout = 0 + } + $0.value = 7_000 + $0.sighashType = BitcoinSigHashType.all.rawValue + $0.scriptBuilder = BitcoinV2Input.InputBuilder.with { + $0.brc20Inscribe = BitcoinV2Input.InputBrc20Inscription.with { + $0.inscribeTo = publicKey.data + $0.ticker = "oadf" + $0.transferAmount = "20" + } + } + } + + let out0 = BitcoinV2Output.with { + $0.value = dustAmount + $0.builder = BitcoinV2Output.OutputBuilder.with { + $0.p2Wpkh = BitcoinV2PublicKeyOrHash.with { + $0.pubkey = publicKey.data + } + } + } + + let signingInput = BitcoinV2SigningInput.with { + $0.version = .v2 + $0.privateKeys = [privateKeyData] + $0.inputs = [utxo0] + $0.outputs = [out0] + $0.inputSelector = .useAll + $0.chainInfo = BitcoinV2ChainInfo.with { + $0.p2PkhPrefix = 0 + $0.p2ShPrefix = 5 + } + $0.dangerousUseFixedSchnorrRng = true + $0.fixedDustThreshold = dustAmount + } + + let legacySigningInput = BitcoinSigningInput.with { + $0.signingV2 = signingInput + } + + let output: BitcoinSigningOutput = AnySigner.sign(input: legacySigningInput, coin: .bitcoin) + XCTAssertEqual(output.error, .ok) + let outputV2 = output.signingResultV2 + XCTAssertEqual(outputV2.error, .ok) + XCTAssertEqual(outputV2.encoded.hexString, "02000000000101b11f1782607a1fe5f033ccf9dc17404db020a0dedff94183596ee67ad4177d790000000000ffffffff012202000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d03406a35548b8fa4620028e021a944c1d3dc6e947243a7bfc901bf63fefae0d2460efa149a6440cab51966aa4f09faef2d1e5efcba23ab4ca6e669da598022dbcfe35b0063036f7264010118746578742f706c61696e3b636861727365743d7574662d3800377b2270223a226272632d3230222c226f70223a227472616e73666572222c227469636b223a226f616466222c22616d74223a223230227d6821c00f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"); + XCTAssertEqual(outputV2.txid.hexString, "7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca") + } + + func testSignBrc20Transfer() throws { + // Successfully broadcasted: https://www.blockchain.com/explorer/transactions/btc/3e3576eb02667fac284a5ecfcb25768969680cc4c597784602d0a33ba7c654b7 + let privateKeyData = Data(hexString: "e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129")! + let dustAmount = 546 as Int64 + // Now spend just created `7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca` reveal output. + let txIdReveal = Data.reverse(hexString: "7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca") + let txIdForFee = Data.reverse(hexString: "797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1") + + let privateKey = PrivateKey(data: privateKeyData)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + + let bobAddress = "bc1qazgc2zhu2kmy42py0vs8d7yff67l3zgpwfzlpk" + + let utxo0 = BitcoinV2Input.with { + $0.outPoint = BitcoinV2OutPoint.with { + $0.hash = txIdReveal + $0.vout = 0 + } + $0.value = dustAmount + $0.sighashType = BitcoinSigHashType.all.rawValue + $0.scriptBuilder = BitcoinV2Input.InputBuilder.with { + $0.p2Wpkh = BitcoinV2PublicKeyOrHash.with { + $0.pubkey = publicKey.data + } + } + } + + let utxo1 = BitcoinV2Input.with { + $0.outPoint = BitcoinV2OutPoint.with { + $0.hash = txIdForFee + $0.vout = 1 + } + $0.value = 16_400 + $0.sighashType = BitcoinSigHashType.all.rawValue + $0.scriptBuilder = BitcoinV2Input.InputBuilder.with { + $0.p2Wpkh = BitcoinV2PublicKeyOrHash.with { + $0.pubkey = publicKey.data + } + } + } + + let out0 = BitcoinV2Output.with { + $0.value = dustAmount + $0.toAddress = bobAddress + } + + // Change/return transaction. Set it explicitly. + let changeOut = BitcoinV2Output.with { + $0.value = 13_400 + $0.builder = BitcoinV2Output.OutputBuilder.with { + $0.p2Wpkh = BitcoinV2PublicKeyOrHash.with { + $0.pubkey = publicKey.data + } + } + } + + let signingInput = BitcoinV2SigningInput.with { + $0.version = .v2 + $0.privateKeys = [privateKeyData] + $0.inputs = [utxo0, utxo1] + $0.outputs = [out0, changeOut] + $0.inputSelector = .useAll + $0.chainInfo = BitcoinV2ChainInfo.with { + $0.p2PkhPrefix = 0 + $0.p2ShPrefix = 5 + } + $0.fixedDustThreshold = dustAmount + } + + let legacySigningInput = BitcoinSigningInput.with { + $0.signingV2 = signingInput + } + + let output: BitcoinSigningOutput = AnySigner.sign(input: legacySigningInput, coin: .bitcoin) + XCTAssertEqual(output.error, .ok) + let outputV2 = output.signingResultV2 + XCTAssertEqual(outputV2.error, .ok) + XCTAssertEqual(outputV2.encoded.hexString, "02000000000102ca3edda74a46877efa5364ab85947e148508713910ada23e147ea28926dc46700000000000ffffffffb11f1782607a1fe5f033ccf9dc17404db020a0dedff94183596ee67ad4177d790100000000ffffffff022202000000000000160014e891850afc55b64aa8247b2076f8894ebdf889015834000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d024830450221008798393eb0b7390217591a8c33abe18dd2f7ea7009766e0d833edeaec63f2ec302200cf876ff52e68dbaf108a3f6da250713a9b04949a8f1dcd1fb867b24052236950121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb0248304502210096bbb9d1f0596d69875646689e46f29485e8ceccacde9d0025db87fd96d3066902206d6de2dd69d965d28df3441b94c76e812384ab9297e69afe3480ee4031e1b2060121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"); + XCTAssertEqual(outputV2.txid.hexString, "3e3576eb02667fac284a5ecfcb25768969680cc4c597784602d0a33ba7c654b7") + } func testSignP2WSH() throws { // set up input @@ -20,6 +229,9 @@ class BitcoinTransactionSignerTests: XCTestCase { $0.byteFee = 1 $0.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx" $0.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU" + // Set the very low fixed Dust threshold just to fix the tests. + // Actually, the transaction in this test has change=79 that will lead to Dust error when broadcasting it. + $0.fixedDustThreshold = 79; } input.scripts["593128f9f90e38b706c18623151e37d2da05c229"] = Data(hexString: "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac")! @@ -106,6 +318,7 @@ class BitcoinTransactionSignerTests: XCTestCase { // redeem p2wpkh nested in p2sh let scriptHash = lockScript.matchPayToScriptHash()! let input = BitcoinSigningInput.with { + $0.amount = 43980 $0.toAddress = "3NqULUrjZ7NL36YtBGsSVzqr5q1x9CJWwu" $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .bitcoin) $0.coinType = CoinType.bitcoin.rawValue @@ -174,4 +387,72 @@ class BitcoinTransactionSignerTests: XCTestCase { XCTAssertEqual(output.error, .ok) XCTAssertEqual(output.encoded.hexString, "01000000026c90312e53a3411347a197bfd637c2583d617dd2317262a70e1b5245d2f1e36a000000008a47304402201a631068ea5ddea19467ef7c932a0f3b04f366ca2beaf70e18958e47456124980220614816c449e39cf6acc6625e1cf3100db1db7c0b755bdbb6804d4fa3c4b735d10141041b3937fac1f14074447cde9d3a324ed292d2865ed0d7a7da26cb43558ce4db4ef33c47e820e53031ae16bb0c39205def059a5ca8e1d617650eabc72c5206a81dffffffff13bf27945c669cf3c1d70cf3048f4ab14f1ab6acf06d10d425e8288217a81efd000000008a473044022051d381d8f48a9a4866ca4109f12647922514604a4733e8da8aac046e19275f700220797c3ebf20df7d2a9fed283f9d0ad14cbd656cafb5ec70a2b1c85646ea7485190141041b3937fac1f14074447cde9d3a324ed292d2865ed0d7a7da26cb43558ce4db4ef33c47e820e53031ae16bb0c39205def059a5ca8e1d617650eabc72c5206a81dffffffff0194590000000000001976a914a0c0a50f986924e65ae9bd18eafae448f83117ed88ac00000000") } + + func testSignPsbtThorSwap() throws { + let privateKey = Data(hexString: "f00ffbe44c5c2838c13d2778854ac66b75e04eb6054f0241989e223223ad5e55")! + let psbt = Data(hexString: "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000")! + + let input = BitcoinV2PsbtSigningInput.with { + $0.psbt = psbt + $0.privateKeys = [privateKey] + } + + let outputData = try BitcoinPsbt.sign(input: input.serializedData(), coin: .bitcoin) + let output = try BitcoinV2PsbtSigningOutput(serializedData: outputData) + + XCTAssertEqual(output.error, .ok) + XCTAssertEqual(output.psbt.hexString, "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d01086c02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000") + XCTAssertEqual(output.encoded.hexString, "02000000000101147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000") + XCTAssertEqual(output.txid.hexString, "634a416e82ac710166725f6a4090ac7b5db69687e86b2d2e38dcb3d91c956c32") + } + + func testPlanPsbtThorSwap() throws { + let privateKeyBytes = Data(hexString: "f00ffbe44c5c2838c13d2778854ac66b75e04eb6054f0241989e223223ad5e55")! + let privateKey = PrivateKey(data: privateKeyBytes)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + + let psbt = Data(hexString: "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000")! + + let input = BitcoinV2PsbtSigningInput.with { + $0.psbt = psbt + $0.publicKeys = [publicKey.data] + } + + let outputData = try BitcoinPsbt.plan(input: input.serializedData(), coin: .bitcoin) + let output = try BitcoinV2TransactionPlan(serializedData: outputData) + + XCTAssertEqual(output.error, .ok) + + XCTAssertEqual(output.inputs[0].receiverAddress, "bc1qkyu3n8k8jmekl3pwvdl59k5w8enjp25akz2r3z") + XCTAssertEqual(output.inputs[0].value, 66_406) + + // Vault transfer + XCTAssertEqual(output.outputs[0].toAddress, "bc1q7g48qdshqd000aysws74pun2uzxrp598gcfum0") + XCTAssertEqual(output.outputs[0].value, 60_000) + + // OP_RETURN + XCTAssertEqual( + output.outputs[1].customScriptPubkey.hexString, + "6a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a3530" + ) + XCTAssertEqual(output.outputs[1].value, 0) + + // Change output + XCTAssertEqual(output.outputs[2].toAddress, "bc1qkyu3n8k8jmekl3pwvdl59k5w8enjp25akz2r3z") + XCTAssertEqual(output.outputs[2].value, 4_670) + + XCTAssertEqual(output.feeEstimate, 1736) + // Please note that `change` in PSBT planning is always 0. + // That's because we aren't able to determine which output is an actual change from PSBT. + XCTAssertEqual(output.change, 0) + } + + func testBitcoinMessageSigner() { + let verifyResult = BitcoinMessageSigner.verifyMessage( + address: "1B8Qea79tsxmn4dTiKKRVvsJpHwL2fMQnr", + message: "test signature", + signature: "H+3L5IbSVcejp4S2VwLXCxLEMQAWDvKbE8lQyq0ocdvyM1aoEudkzN/S/qLI3vnNOFY6V13BXWSFrPr3OjGa5Dk=" + ) + XCTAssertTrue(verifyResult) + } } diff --git a/swift/Tests/Blockchains/BitconCashTests.swift b/swift/Tests/Blockchains/BitconCashTests.swift index c5cec4484d5..406dc3026af 100644 --- a/swift/Tests/Blockchains/BitconCashTests.swift +++ b/swift/Tests/Blockchains/BitconCashTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Blockchains/BluzelleTests.swift b/swift/Tests/Blockchains/BluzelleTests.swift index fde2d9cc4a4..78d32c8843f 100644 --- a/swift/Tests/Blockchains/BluzelleTests.swift +++ b/swift/Tests/Blockchains/BluzelleTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/CardanoTests.swift b/swift/Tests/Blockchains/CardanoTests.swift index ef18ca9e65e..4acef413ba3 100644 --- a/swift/Tests/Blockchains/CardanoTests.swift +++ b/swift/Tests/Blockchains/CardanoTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest @@ -10,7 +8,7 @@ import XCTest class CardanoTests: XCTestCase { func testAddress() { let key = PrivateKey(data: Data(hexString: "e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276")!)! - let pubkey = key.getPublicKeyEd25519Extended() + let pubkey = key.getPublicKeyEd25519Cardano() let address = AnyAddress(publicKey: pubkey, coin: .cardano) let addressFromString = AnyAddress(string: "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq", coin: .cardano)! @@ -62,6 +60,56 @@ class CardanoTests: XCTestCase { let txid = output.txID XCTAssertEqual(txid.hexString, "9b5b15e133cd73ccaa85307d2986aebc846505118a2eb4e6111e6b4b67d1f389") } + + /// Successfully broadcasted: + /// https://cardanoscan.io/transaction/0203ce2c91f59f169a26e9ef91254639d2b7911afac9c7c0ae64539f88ba46a5 + func testSignTransferFromLegacy() throws { + let privateKey = PrivateKey(data: Data(hexString: "98f266d1aac660179bc2f456033941238ee6b2beb8ed0f9f34c9902816781f5a9903d1d395d6ab887b65ea5e344ef09b449507c21a75f0ce8c59d0ed1c6764eba7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f4e890ca4eb6bec44bf751b5a843174534af64d6ad1f44e0613db78a7018781f5aa151d2997f52059466b715d8eefab30a78b874ae6ef4931fa58bb21ef8ce2423d46f19d0fbf75afb0b9a24e31d533f4fd74cee3b56e162568e8defe37123afc4")!)! + let publicKey = privateKey.getPublicKeyEd25519Cardano() + let byronAddress = Cardano.getByronAddress(publicKey: publicKey) + + XCTAssertEqual( + publicKey.data.hexString, + "d163c8c4f0be7c22cd3a1152abb013c855ea614b92201497a568c5d93ceeb41ea7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f40b5aaa6103dc10842894a1eeefc5447b9bcb9bcf227d77e57be195d17bc03263d46f19d0fbf75afb0b9a24e31d533f4fd74cee3b56e162568e8defe37123afc4" + ) + + XCTAssertEqual( + byronAddress, + "Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8" + ) + var input = CardanoSigningInput.with { + $0.transferMessage.toAddress = "addr1q90uh2eawrdc9vaemftgd50l28yrh9lqxtjjh4z6dnn0u7ggasexxdyyk9f05atygnjlccsjsggtc87hhqjna32fpv5qeq96ls" + $0.transferMessage.changeAddress = "addr1qx55ymlqemndq8gluv40v58pu76a2tp4mzjnyx8n6zrp2vtzrs43a0057y0edkn8lh9su8vh5lnhs4npv6l9tuvncv8swc7t08" + $0.transferMessage.amount = 3000000 + $0.ttl = 190000000 + } + + input.privateKey.append(privateKey.data) + + let utxo1 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "8316e5007d61fb90652cabb41141972a38b5bc60954d602cf843476aa3f67f63")! + $0.outPoint.outputIndex = 0 + $0.address = "Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8" + $0.amount = 2500000 + } + input.utxos.append(utxo1) + + let utxo2 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "e29392c59c903fefb905730587d22cae8bda30bd8d9aeec3eca082ae77675946")! + $0.outPoint.outputIndex = 0 + $0.address = "Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8" + $0.amount = 1700000 + } + input.utxos.append(utxo2) + + // Sign + let output: CardanoSigningOutput = AnySigner.sign(input: input, coin: .cardano) + XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) + + XCTAssertEqual(output.encoded.hexString, "83a400828258208316e5007d61fb90652cabb41141972a38b5bc60954d602cf843476aa3f67f6300825820e29392c59c903fefb905730587d22cae8bda30bd8d9aeec3eca082ae77675946000182825839015fcbab3d70db82b3b9da5686d1ff51c83b97e032e52bd45a6ce6fe7908ec32633484b152fa756444e5fc62128210bc1fd7b8253ec5490b281a002dc6c082583901a9426fe0cee6d01d1fe32af650e1e7b5d52c35d8a53218f3d0861531621c2b1ebdf4f11f96da67fdcb0e1d97a7e778566166be55f193c30f1a000f9ec1021a0002b0bf031a0b532b80a20081825820d163c8c4f0be7c22cd3a1152abb013c855ea614b92201497a568c5d93ceeb41e58406a23ab9267867fbf021c1cb2232bc83d2cdd663d651d22d59b6cddbca5cb106d4db99da50672f69a2309ca8a329a3f9576438afe4538b013de4591a6dfcd4d090281845820d163c8c4f0be7c22cd3a1152abb013c855ea614b92201497a568c5d93ceeb41e58406a23ab9267867fbf021c1cb2232bc83d2cdd663d651d22d59b6cddbca5cb106d4db99da50672f69a2309ca8a329a3f9576438afe4538b013de4591a6dfcd4d095820a7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f441a0f6") + + XCTAssertEqual(output.txID.hexString, "0203ce2c91f59f169a26e9ef91254639d2b7911afac9c7c0ae64539f88ba46a5") + } func testSignTransferToken1() throws { let toToken = CardanoTokenAmount.with { @@ -109,7 +157,7 @@ class CardanoTests: XCTestCase { } let token1 = CardanoTokenAmount.with { $0.policyID = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77" - $0.assetName = "SUNDAE" + $0.assetNameHex = "53554e444145" $0.amount = Data(hexString: "04d3e8d9")! // 80996569 } utxo2.tokenAmount.append(token1) @@ -127,9 +175,163 @@ class CardanoTests: XCTestCase { let encoded = output.encoded XCTAssertEqual(encoded.hexString, - "83a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76702018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5821a00160a5ba1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a01312d00825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468821a0080a574a2581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a144435542591a004c4b40581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a03a2bbd9021a0002af5e031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290584000feb412442f8851faa59742eb2c37f3994b0d143a424367143490cf828246991e504fa8eac61c403bfa7634bd1f0adc44f3f54f6a474856701e2cbb15fb5b04f6") + "83a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76702018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5821a00160a5ba1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a01312d00825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468821a0080aac9a1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a244435542591a004c4b404653554e4441451a03a2bbd9021a0002aa09031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840d90dcfbd190cbe59c42094e59eeb49b3de9d80a85b786cc311f932c5c9302d1c8c6c577b22aa70ff7955c139c700ea918f8cb425c3ba43a27980e1d238e4e908f6") + + let txid = output.txID + XCTAssertEqual(txid.hexString, "201c537693b005b64a0f0528e366ec67a84be0119ed4363b547f141f2a7770c2") + } + + func testGetStakingAddress() throws { + let stakingAddress = Cardano.getStakingAddress(baseAddress: "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23") + XCTAssertEqual(stakingAddress, "stake1u80jysjtdzqt88jt4jx93h5lumfr67d273r4vwyasfa2pxcwxllmx") + } + + func testSignStakingRegisterAndDelegate() throws { + let ownAddress = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + let stakingAddress = Cardano.getStakingAddress(baseAddress: ownAddress) + let poolIdNufi = "7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6" + + var input = CardanoSigningInput.with { + $0.transferMessage.toAddress = ownAddress + $0.transferMessage.changeAddress = ownAddress + $0.transferMessage.amount = 4000000 // not relevant as we use MaxAmount + $0.transferMessage.useMaxAmount = true + $0.ttl = 69885081 + // Register staking key, 2 ADA desposit + $0.registerStakingKey.stakingAddress = stakingAddress + $0.registerStakingKey.depositAmount = 2000000 + // Delegate + $0.delegate.stakingAddress = stakingAddress + $0.delegate.poolID = Data(hexString: poolIdNufi)! + $0.delegate.depositAmount = 0 + } + input.privateKey.append(Data(hexString: "089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e")!) + + let utxo1 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e")! + $0.outPoint.outputIndex = 0 + $0.address = ownAddress + $0.amount = 4000000 + } + let utxo2 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e")! + $0.outPoint.outputIndex = 1 + $0.address = ownAddress + $0.amount = 26651312 + } + input.utxos.append(utxo1) + input.utxos.append(utxo2) + + // Sign + let output: CardanoSigningOutput = AnySigner.sign(input: input, coin: .cardano) + XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) + + let encoded = output.encoded + XCTAssertEqual(encoded.hexString, + "83a500828258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e008258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e01018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a01b27ef5021a0002b03b031a042a5c99048282008200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b83028200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b581c7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6a100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840677c901704be027d9a1734e8aa06f0700009476fa252baaae0de280331746a320a61456d842d948ea5c0e204fc36f3bd04c88ca7ee3d657d5a38014243c37c07825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e0693258401fa21bdc62b85ca217bf08cbacdeba2fadaf33dc09ee3af9cc25b40f24822a1a42cfbc03585cc31a370ef75aaec4d25db6edcf329e40a4e725ec8718c94f220af6") + + let txid = output.txID + XCTAssertEqual(txid.hexString, "96a781fd6481b6a7fd3926da110265e8c44b53947b81daa84da5b148825d02aa") + } + + func testSignStakingWithdraw() throws { + let ownAddress = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + let stakingAddress = Cardano.getStakingAddress(baseAddress: "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23") + + var input = CardanoSigningInput.with { + $0.transferMessage.toAddress = ownAddress + $0.transferMessage.changeAddress = ownAddress + $0.transferMessage.amount = 6000000 // not relevant as we use MaxAmount + $0.transferMessage.useMaxAmount = true + $0.ttl = 71678326 + // Withdraw available amount + $0.withdraw.stakingAddress = stakingAddress + $0.withdraw.withdrawAmount = 3468 + } + input.privateKey.append(Data(hexString: "089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e")!) + + let utxo1 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "7dfd2c579794314b1f84efc9db932a098e440ccefb874945591f1d4e85a9152a")! + $0.outPoint.outputIndex = 0 + $0.address = ownAddress + $0.amount = 6305913 + } + input.utxos.append(utxo1) + + // Sign + let output: CardanoSigningOutput = AnySigner.sign(input: input, coin: .cardano) + XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) + + let encoded = output.encoded + XCTAssertEqual(encoded.hexString, + "83a500818258207dfd2c579794314b1f84efc9db932a098e440ccefb874945591f1d4e85a9152a00018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a005da6ff021a00029f06031a0445b97605a1581de1df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b190d8ca100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058401ebaca2876fd17122404912a2558a98109cdf0f990a938d2917fa2c3b8c4e55e18a2cbabfa82fff03fa0d7ab8b88ca01ed18e42af3bfc4cda7f423a3aa30c00b825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e069325840777f04fa8f083fe562aecf78898aaaaac36e2cc6ca962f6ffb01e84a421cae1860496db79b2c5fb2879524c3d5121060b9ea1e693336230c6e5338e14c4c3303f6") + + let txid = output.txID + XCTAssertEqual(txid.hexString, "6dcf3956232953fc25b8355fb1ded1e912b5802090fd21434d789087d6329683") + } + + // Successfully broadcasted: + // https://cardanoscan.io/transaction/87ca43a36b09c0b140f0ef2b71fbdcfcf1fdc88f7aa378b861e8eed3e8974628 + func testSignNftTransfer() throws { + let fromAddress = "addr1qy5eme9r6frr0m6q2qpncg282jtrhq5lg09uxy2j0545hj8rv7v2ntdxuv6p4s3eq4lqzg39lewgvt6fk5kmpa0zppesufzjud" + let toAddress = "addr1qy9wjfn6nd8kak6dd8z53u7t5wt9f4lx0umll40px5hnq05avwcsq5r3ytdp36wttzv4558jaq8lvhgqhe3y8nuf5xrquju7z4" + let coinsPerUtxoByte = "4310"; + + let tokenAmount = CardanoTokenAmount.with { + $0.policyID = "219820e6cb04316f41a337fea356480f412e7acc147d28f175f21b5e" + $0.assetNameHex = "636f6f6c63617473736f636965747934353637" + $0.amount = Data(hexString: "01")! // 1 + }; + var input = CardanoSigningInput.with { + $0.transferMessage.toAddress = toAddress + $0.transferMessage.changeAddress = fromAddress + $0.ttl = 89130965 + } + input.transferMessage.tokenAmount.token.append(tokenAmount); + + // check min ADA amount, set it + let inputTokenAmountSerialized = try input.transferMessage.tokenAmount.serializedData() + let minAmount = Cardano.outputMinAdaAmount(toAddress: toAddress, tokenBundle: inputTokenAmountSerialized, coinsPerUtxoByte: coinsPerUtxoByte)!; + let minAmountInt = UInt64(minAmount)! + XCTAssertEqual(minAmountInt, 1202490) + input.transferMessage.amount = minAmountInt + + input.privateKey.append(Data(hexString: "d09831a668db6b36ffb747600cb1cd3e3d34f36e1e6feefc11b5f988719b7557a7029ab80d3e6fe4180ad07a59ddf742ea9730f3c4145df6365fa4ae2ee49c3392e19444caf461567727b7fefec40a3763bdb6ce5e0e8c05f5e340355a8fef4528dfe7502cfbda49e38f5a0021962d52dc3dee82834a23abb6750981799b75577d1ed9af9853707f0ef74264274e71b2f12e86e3c91314b6efa75ef750d9711b84cedd742ab873ef2f9566ad20b3fc702232c6d2f5d83ff425019234037d1e58")!) + + let utxo1 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "aba499ec2f23529e70bb256ceaffcc6274a882cf02f29e5670c75ee980d7c2b8")! + $0.outPoint.outputIndex = 0 + $0.address = fromAddress + $0.amount = 1202490 + $0.tokenAmount = [tokenAmount] + } + input.utxos.append(utxo1) + + let utxo2 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "ee414d635b3bc67831907354d274a31174664777c57c21ae923b9459e5644840")! + $0.outPoint.outputIndex = 0 + $0.address = fromAddress + $0.amount = 1000000 + } + input.utxos.append(utxo2) + + let utxo3 = CardanoTxInput.with { + $0.outPoint.txHash = Data(hexString: "6a7221dcc28353ed69b733391ffeb984a34c1e72293af111d59f9ddfa8639167")! + $0.outPoint.outputIndex = 0 + $0.address = fromAddress + $0.amount = 2000000 + } + input.utxos.append(utxo3) + + // Sign + let output: CardanoSigningOutput = AnySigner.sign(input: input, coin: .cardano) + XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) + + let encoded = output.encoded + XCTAssertEqual(encoded.hexString, + "83a400838258206a7221dcc28353ed69b733391ffeb984a34c1e72293af111d59f9ddfa863916700825820aba499ec2f23529e70bb256ceaffcc6274a882cf02f29e5670c75ee980d7c2b800825820ee414d635b3bc67831907354d274a31174664777c57c21ae923b9459e5644840000182825839010ae9267a9b4f6edb4d69c548f3cba39654d7e67f37ffd5e1352f303e9d63b100507122da18e9cb58995a50f2e80ff65d00be6243cf89a186821a0012593aa1581c219820e6cb04316f41a337fea356480f412e7acc147d28f175f21b5ea153636f6f6c63617473736f6369657479343536370182583901299de4a3d24637ef4050033c214754963b829f43cbc311527d2b4bc8e36798a9ada6e3341ac239057e012225fe5c862f49b52db0f5e208731a002b1525021a0002b19b031a055007d5a1008182582088bd26e8656fa7dead846c3373588f0192da5bfb90bf5d3fb877decfb3b3fd085840da8656aca0dacc57d4c2d957fc7dff03908f6dcf60c48f1e40b3006e2fd0cfacfa4c24fa02e35a310572526586d4ce0d30bf660ba274c8efd507848cbe177d09f6") let txid = output.txID - XCTAssertEqual(txid.hexString, "dacb3a0c5b3b7fa36b49f25a0a59b941ab8a21f0db5770e9e6982ff120122649") + XCTAssertEqual(txid.hexString, "87ca43a36b09c0b140f0ef2b71fbdcfcf1fdc88f7aa378b861e8eed3e8974628") } } diff --git a/swift/Tests/Blockchains/CeloTests.swift b/swift/Tests/Blockchains/CeloTests.swift index 886490bba7c..367d9531e60 100644 --- a/swift/Tests/Blockchains/CeloTests.swift +++ b/swift/Tests/Blockchains/CeloTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/ConfluxeSpaceTests.swift b/swift/Tests/Blockchains/ConfluxeSpaceTests.swift new file mode 100644 index 00000000000..77751907f35 --- /dev/null +++ b/swift/Tests/Blockchains/ConfluxeSpaceTests.swift @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class ConfluxeSpaceTests: XCTestCase { + func testAddress() { + let key = PrivateKey(data: Data(hexString: "828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .confluxeSpace) + let expected = AnyAddress(string: "0xe32DC46bfBF78D1eada7b0a68C96903e01418D64", coin: .confluxeSpace)! + + XCTAssertEqual(address.description, expected.description) + } +} diff --git a/swift/Tests/Blockchains/CosmosTests.swift b/swift/Tests/Blockchains/CosmosTests.swift index 1730a20fadf..90cfd33423c 100644 --- a/swift/Tests/Blockchains/CosmosTests.swift +++ b/swift/Tests/Blockchains/CosmosTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore @@ -65,7 +63,79 @@ class CosmosSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) XCTAssertJSONEqual(output.serialized, "{\"tx_bytes\": \"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\", \"mode\": \"BROADCAST_MODE_BLOCK\"}") - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") + } + + func testAuthCompounding() { + let authMessage = CosmosMessage.AuthGrant.with { + $0.granter = "cosmos13k0q0l7lg2kr32kvt7ly236ppldy8v9dzwh3gd" + $0.grantee = "cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf" + $0.grantStake = CosmosMessage.StakeAuthorization.with { + $0.allowList.address = ["cosmosvaloper1gjtvly9lel6zskvwtvlg5vhwpu9c9waw7sxzwx"] + $0.authorizationType = CosmosMessage.AuthorizationType.delegate + } + $0.expiration = 1692309600 + } + let message = CosmosMessage.with { + $0.authGrant = authMessage + } + let fee = CosmosFee.with { + $0.gas = 96681 + $0.amounts = [CosmosAmount.with { + $0.amount = "2418" + $0.denom = "uatom" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 1290826 + $0.chainID = "cosmoshub-4" + $0.memo = "" + $0.sequence = 5 + $0.messages = [message] + $0.fee = fee + $0.privateKey = PrivateKey(data: Data(hexString: "c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc")!)!.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) + + XCTAssertJSONEqual(output.serialized, "{\"tx_bytes\": \"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjogARI2CjRjb3Ntb3N2YWxvcGVyMWdqdHZseTlsZWw2enNrdnd0dmxnNXZod3B1OWM5d2F3N3N4end4EgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQEAN1nIfDawlHnep2bNEm14w+g7tYybJJT3htcGVS6s9D7va3ed1OUEIk9LZoc3G//VenJ+KLw26SRVBaRukgVI=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}") + XCTAssertEqual(output.errorMessage, "") + } + + func testRevokeAuthCompounding() { + let revokeAuthMessage = CosmosMessage.AuthRevoke.with { + $0.granter = "cosmos13k0q0l7lg2kr32kvt7ly236ppldy8v9dzwh3gd" + $0.grantee = "cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf" + $0.msgTypeURL = "/cosmos.staking.v1beta1.MsgDelegate" + } + let message = CosmosMessage.with { + $0.authRevoke = revokeAuthMessage + } + let fee = CosmosFee.with { + $0.gas = 87735 + $0.amounts = [CosmosAmount.with { + $0.amount = "2194" + $0.denom = "uatom" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 1290826 + $0.chainID = "cosmoshub-4" + $0.memo = "" + $0.sequence = 4 + $0.messages = [message] + $0.fee = fee + $0.privateKey = PrivateKey(data: Data(hexString: "c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc")!)!.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) + + XCTAssertJSONEqual(output.serialized, "{\"tx_bytes\": \"CqoBCqcBCh8vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnUmV2b2tlEoMBCi1jb3Ntb3MxM2swcTBsN2xnMmtyMzJrdnQ3bHkyMzZwcGxkeTh2OWR6d2gzZ2QSLWNvc21vczFmczdsdTI4aHg1bTlha203cnAwYzI0MjJjbjhyMmY3Z3VydWpoZhojL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuTXNnRGVsZWdhdGUSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAQSEwoNCgV1YXRvbRIEMjE5NBC3rQUaQI7K+W7MMBoD6FbFZxRBqs9VTjErztjWTy57+fvrLaTCIZ+eBs7CuaKqfUZdSN8otjubSHVTQID3k9DpPAX0yDo=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}") + XCTAssertEqual(output.errorMessage, "") } func testStaking() { @@ -104,7 +174,7 @@ class CosmosSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpsBCpgBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJxCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3AaCgoEbXVvbhICMTASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpA8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA==\"}") - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } func testWithdraw() { @@ -149,7 +219,7 @@ class CosmosSignerTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CukDCqABCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmUKLWNvc21vczEwMHJoeGNscWFzeTZ2bnJjZXJ2Z2g5OWFseDV4dzdsa2ZwNHU1NBI0Y29zbW9zdmFsb3BlcjFleTY5cjM3Z2Z4dnhnNjJzaDRyMGt0cHVjNDZwempybTg3M2FlOAqgAQo3L2Nvc21vcy5kaXN0cmlidXRpb24udjFiZXRhMS5Nc2dXaXRoZHJhd0RlbGVnYXRvclJld2FyZBJlCi1jb3Ntb3MxMDByaHhjbHFhc3k2dm5yY2VydmdoOTlhbHg1eHc3bGtmcDR1NTQSNGNvc21vc3ZhbG9wZXIxc2psbHNucmFtdGczZXd4cXd3cndqeGZnYzRuNGVmOXUybGNuajAKoAEKNy9jb3Ntb3MuZGlzdHJpYnV0aW9uLnYxYmV0YTEuTXNnV2l0aGRyYXdEZWxlZ2F0b3JSZXdhcmQSZQotY29zbW9zMTAwcmh4Y2xxYXN5NnZucmNlcnZnaDk5YWx4NXh3N2xrZnA0dTU0EjRjb3Ntb3N2YWxvcGVyMTY0OHlubHBkdzdmcWEyYXh0MHcyeXAzZms1NDJqdW5sN3JzdnE2EmUKUQpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQJXKG7D830zVXu7qgALJ3RKyQI6qZZ8rnWhgdH/kfqdxRIECgIIARi+AhIQCgoKBXVhdG9tEgExEOC2DRpAXLgJ+8xEMUn7nkFj3ukg2V65Vh5ob7HKeCaNpMM6OPQrpW2r6askfssIFcOd8ThiBEz65bJz81Fmb5MtDTGv4g==\"}") - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } func testIbcTransfer() { @@ -198,6 +268,6 @@ class CosmosSignerTests: XCTestCase { // https://www.mintscan.io/cosmos/txs/817101F3D96314AD028733248B28BAFAD535024D7D2C8875D3FE31DC159F096B XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Cr4BCrsBCikvaWJjLmFwcGxpY2F0aW9ucy50cmFuc2Zlci52MS5Nc2dUcmFuc2ZlchKNAQoIdHJhbnNmZXISC2NoYW5uZWwtMTQxGg8KBXVhdG9tEgYxMDAwMDAiLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseCorb3NtbzE4czBoZG5zbGxnY2Nsd2V1OWF5bXc0bmdrdHIyazBya3ZuN2ptbjIHCAEQgI6ZBBJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEC7O9c5DejAsZ/lUaN5LMfNukR9GfX5qUrQcHhPh1WNkkSBAoCCAEYAhIUCg4KBXVhdG9tEgUxMjUwMBCgwh4aQK0HIWdFMk+C6Gi1KG/vELe1ffcc1aEWUIqz2t/ZhwqNNHxUUSp27wteiugHEMVTEIOBhs84t2gIcT/nD/1yKOU=\"}") - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } } diff --git a/swift/Tests/Blockchains/CronosTests.swift b/swift/Tests/Blockchains/CronosTests.swift index b28a89849bb..a1e917f30ce 100644 --- a/swift/Tests/Blockchains/CronosTests.swift +++ b/swift/Tests/Blockchains/CronosTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/CryptoorgTests.swift b/swift/Tests/Blockchains/CryptoorgTests.swift index 7926940b6b5..9f781e24982 100644 --- a/swift/Tests/Blockchains/CryptoorgTests.swift +++ b/swift/Tests/Blockchains/CryptoorgTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest @@ -60,6 +58,6 @@ class CryptoorgTests: XCTestCase { // https://crypto.org/explorer/tx/BCB213B0A121F0CF11BECCF52475F1C8328D6070F3CFDA9E14C42E6DB30E847E XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpABCo0BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm0KKmNybzFjdHd0Y3dwZ2tza3k5ODhkaHRoNmpzbHh2ZXVtZ3UwZDQ1emdmMBIqY3JvMXhwYWh5NmM3d2xkeGFjdjZsZDk5aDQzNW1odmZuc3VwMjR2Y3VzGhMKB2Jhc2Vjcm8SCDUwMDAwMDAwEmkKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQOIMbBhYj5+i+WdiIxxCEpFyNCJMHy/XsVdxiwde9Vr4xIECgIIARgCEhUKDwoHYmFzZWNybxIENTAwMBDAmgwaQAcxK9xk6r69gmz+1UWaCnYxNuXPXZdp59YcqKPJE5d6fp+IICTBOwd2rs8MiApcf8kNSrbZ6oECxcGQAdxF0SI=\"}") - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } } diff --git a/swift/Tests/Blockchains/DashTests.swift b/swift/Tests/Blockchains/DashTests.swift index 655594b9a1c..94ebddfccc1 100644 --- a/swift/Tests/Blockchains/DashTests.swift +++ b/swift/Tests/Blockchains/DashTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Blockchains/Data b/swift/Tests/Blockchains/Data index f50c5d874d3..154e08de1f2 120000 --- a/swift/Tests/Blockchains/Data +++ b/swift/Tests/Blockchains/Data @@ -1 +1 @@ -../../../tests/Ethereum/Data \ No newline at end of file +../../../tests/chains/Ethereum/Data \ No newline at end of file diff --git a/swift/Tests/Blockchains/DecredTests.swift b/swift/Tests/Blockchains/DecredTests.swift index 2f7b3d4e54b..5dc0a9d4814 100644 --- a/swift/Tests/Blockchains/DecredTests.swift +++ b/swift/Tests/Blockchains/DecredTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/DogeTests.swift b/swift/Tests/Blockchains/DogeTests.swift index fd4708d10b8..991b8c570fc 100644 --- a/swift/Tests/Blockchains/DogeTests.swift +++ b/swift/Tests/Blockchains/DogeTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/DydxTests.swift b/swift/Tests/Blockchains/DydxTests.swift new file mode 100644 index 00000000000..a464c834c8a --- /dev/null +++ b/swift/Tests/Blockchains/DydxTests.swift @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class DydxTests: XCTestCase { + func testAddress() { + let key = PrivateKey(data: Data(hexString: "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let address = AnyAddress(publicKey: pubkey, coin: .dydx) + let addressFromString = AnyAddress(string: "dydx1mry47pkga5tdswtluy0m8teslpalkdq0hc72uz", coin: .dydx)! + + XCTAssertEqual(address.description, addressFromString.description) + } +} diff --git a/swift/Tests/Blockchains/ECashTests.swift b/swift/Tests/Blockchains/ECashTests.swift index 7826b92e44f..7f07d59b0ba 100644 --- a/swift/Tests/Blockchains/ECashTests.swift +++ b/swift/Tests/Blockchains/ECashTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Blockchains/EOSTests.swift b/swift/Tests/Blockchains/EOSTests.swift index 30ea50eda7c..4f5373c46ad 100644 --- a/swift/Tests/Blockchains/EOSTests.swift +++ b/swift/Tests/Blockchains/EOSTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore @@ -60,8 +58,8 @@ class EOSTests: XCTestCase { { "compression": "none", "packed_context_free_data": "", - "packed_trx": "7c59a35cd6679a1f3d4800000000010000000080a920cd000000572d3ccdcd010000000080a920cd00000000a8ed3232330000000080a920cd0000000000ea3055e09304000000000004544b4e00000000126d79207365636f6e64207472616e7366657200", - "signatures": ["SIG_K1_KfCdjsrTnx5cBpbA5cUdHZAsRYsnC9uKzuS1shFeqfMCfdZwX4PBm9pfHwGRT6ffz3eavhtkyNci5GoFozQAx8P8PBnDmj"] + "packed_trx": "6e67a35cd6679a1f3d4800000000010000000080a920cd000000572d3ccdcd010000000080a920cd00000000a8ed3232330000000080a920cd0000000000ea3055e09304000000000004544b4e00000000126d79207365636f6e64207472616e7366657200", + "signatures": ["SIG_K1_K9RdLC7DEDWjTfR64GU8BtDHcAjzR1ntcT651JMcfHNTpdsvDrUwfyzF1FkvL9fxEi2UCtGJZ9zYoNbJoMF1fbU64cRiJ7"] } """ XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) diff --git a/swift/Tests/Blockchains/ElrondTests.swift b/swift/Tests/Blockchains/ElrondTests.swift deleted file mode 100644 index 06718ed42a3..00000000000 --- a/swift/Tests/Blockchains/ElrondTests.swift +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -import WalletCore -import XCTest - -class ElrondTests: XCTestCase { - - let aliceBech32 = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" - let alicePubKeyHex = "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1" - let aliceSeedHex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" - let bobBech32 = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx" - - func testAddress() { - let key = PrivateKey(data: Data(hexString: aliceSeedHex)!)! - let pubkey = key.getPublicKeyEd25519() - let address = AnyAddress(publicKey: pubkey, coin: .elrond) - let addressFromString = AnyAddress(string: aliceBech32, coin: .elrond)! - - XCTAssertEqual(pubkey.data.hexString, alicePubKeyHex) - XCTAssertEqual(address.description, addressFromString.description) - } - - func testSignGenericAction() { - let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! - - let input = ElrondSigningInput.with { - $0.genericAction = ElrondGenericAction.with { - $0.accounts = ElrondAccounts.with { - $0.senderNonce = 7 - $0.sender = aliceBech32 - $0.receiver = bobBech32 - } - $0.value = "0" - $0.data = "foo" - $0.version = 1 - } - $0.gasPrice = 1000000000 - $0.gasLimit = 50000 - $0.chainID = "1" - $0.privateKey = privateKey.data - } - - let output: ElrondSigningOutput = AnySigner.sign(input: input, coin: .elrond) - let expectedSignature = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700" - let expectedEncoded = #"{"nonce":7,"value":"0","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"\#(expectedSignature)"}"# - - XCTAssertEqual(output.signature, expectedSignature) - XCTAssertEqual(output.encoded, expectedEncoded) - } - - func testSignEGLDTransfer() { - let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! - - let input = ElrondSigningInput.with { - $0.egldTransfer = ElrondEGLDTransfer.with { - $0.accounts = ElrondAccounts.with { - $0.senderNonce = 7 - $0.sender = aliceBech32 - $0.receiver = bobBech32 - } - $0.amount = "1000000000000000000" - } - $0.chainID = "1" - $0.privateKey = privateKey.data - } - - let output: ElrondSigningOutput = AnySigner.sign(input: input, coin: .elrond) - let expectedSignature = "7e1c4c63b88ea72dcf7855a54463b1a424eb357ac3feb4345221e512ce07c7a50afb6d7aec6f480b554e32cf2037082f3bc17263d1394af1f3ef240be53c930b" - let expectedEncoded = #"{"nonce":7,"value":"1000000000000000000","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1,"signature":"\#(expectedSignature)"}"# - - XCTAssertEqual(output.signature, expectedSignature) - XCTAssertEqual(output.encoded, expectedEncoded) - } - - func testSignESDTTransfer() { - let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! - - let input = ElrondSigningInput.with { - $0.esdtTransfer = ElrondESDTTransfer.with { - $0.accounts = ElrondAccounts.with { - $0.senderNonce = 7 - $0.sender = aliceBech32 - $0.receiver = bobBech32 - } - $0.amount = "10000000000000" - $0.tokenIdentifier = "MYTOKEN-1234" - } - - $0.privateKey = privateKey.data - } - - let output: ElrondSigningOutput = AnySigner.sign(input: input, coin: .elrond) - let expectedSignature = "9add6d9ac3f1a1fddb07b934e8a73cad3b8c232bdf29d723c1b38ad619905f03e864299d06eb3fe3bbb48a9f1d9b7f14e21dc5eaffe0c87f5718ad0c4198bb0c" - let expectedData = "RVNEVFRyYW5zZmVyQDRkNTk1NDRmNGI0NTRlMmQzMTMyMzMzNEAwOTE4NGU3MmEwMDA=" - let expectedEncoded = #"{"nonce":7,"value":"0","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":425000,"data":"\#(expectedData)","chainID":"1","version":1,"signature":"\#(expectedSignature)"}"# - - XCTAssertEqual(output.signature, expectedSignature) - XCTAssertEqual(output.encoded, expectedEncoded) - } - - func testSignESDTNFTTransfer() { - let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! - - let input = ElrondSigningInput.with { - $0.esdtnftTransfer = ElrondESDTNFTTransfer.with { - $0.accounts = ElrondAccounts.with { - $0.senderNonce = 7 - $0.sender = aliceBech32 - $0.receiver = bobBech32 - } - $0.tokenCollection = "LKMEX-aab910" - $0.tokenNonce = 4 - $0.amount = "184300000000000000" - } - - $0.privateKey = privateKey.data - } - - let output: ElrondSigningOutput = AnySigner.sign(input: input, coin: .elrond) - let expectedSignature = "cc935685d5b31525e059a16a832cba98dee751983a5a93de4198f6553a2c55f5f1e0b4300fe9077376fa754546da0b0f6697e66462101a209aafd0fc775ab60a" - let expectedData = "RVNEVE5GVFRyYW5zZmVyQDRjNGI0ZDQ1NTgyZDYxNjE2MjM5MzEzMEAwNEAwMjhlYzNkZmEwMWFjMDAwQDgwNDlkNjM5ZTVhNjk4MGQxY2QyMzkyYWJjY2U0MTAyOWNkYTc0YTE1NjM1MjNhMjAyZjA5NjQxY2MyNjE4Zjg=" - let expectedEncoded = #"{"nonce":7,"value":"0","receiver":"\#(aliceBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":937500,"data":"\#(expectedData)","chainID":"1","version":1,"signature":"\#(expectedSignature)"}"# - - XCTAssertEqual(output.signature, expectedSignature) - XCTAssertEqual(output.encoded, expectedEncoded) - } -} diff --git a/swift/Tests/Blockchains/EthereumAbiTests.swift b/swift/Tests/Blockchains/EthereumAbiTests.swift index 3edc89d55c2..0fbd2e07be2 100644 --- a/swift/Tests/Blockchains/EthereumAbiTests.swift +++ b/swift/Tests/Blockchains/EthereumAbiTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore @@ -57,7 +55,7 @@ class EthereumAbiTests: XCTestCase { func testValueDecoderValue() { XCTAssertEqual("42", EthereumAbiValue.decodeValue(input: Data(hexString: "000000000000000000000000000000000000000000000000000000000000002a")!, type: "uint")) XCTAssertEqual("24", EthereumAbiValue.decodeValue(input: Data(hexString: "0000000000000000000000000000000000000000000000000000000000000018")!, type: "uint8")) - XCTAssertEqual("0xf784682c82526e245f50975190ef0fff4e4fc077", EthereumAbiValue.decodeValue(input: Data(hexString: "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077")!, type: "address")) + XCTAssertEqual("0xF784682C82526e245F50975190EF0fff4E4fC077", EthereumAbiValue.decodeValue(input: Data(hexString: "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077")!, type: "address")) XCTAssertEqual("Hello World! Hello World! Hello World!", EthereumAbiValue.decodeValue(input: Data(hexString: "000000000000000000000000000000000000000000000000000000000000002c48656c6c6f20576f726c64212020202048656c6c6f20576f726c64212020202048656c6c6f20576f726c64210000000000000000000000000000000000000000" )!, type: "string")) @@ -71,7 +69,7 @@ class EthereumAbiTests: XCTestCase { func testValueDecoderArray_address() { let input = Data(hexString: "0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc0770000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3") - XCTAssertEqual("[\"0xf784682c82526e245f50975190ef0fff4e4fc077\",\"0x2e00cd222cb42b616d86d037cc494e8ab7f5c9a3\"]", EthereumAbiValue.decodeArray(input: input!, type: "address[]")) + XCTAssertEqual("[\"0xF784682C82526e245F50975190EF0fff4E4fC077\",\"0x2e00CD222Cb42B616D86D037Cc494e8ab7F5c9a3\"]", EthereumAbiValue.decodeArray(input: input!, type: "address[]")) } func testValueDecoderArray_bytes() { @@ -90,7 +88,7 @@ class EthereumAbiTests: XCTestCase { "inputs": [{ "name": "_spender", "type": "address", - "value": "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed" + "value": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed" }, { "name": "_value", "type": "uint256", @@ -113,24 +111,24 @@ class EthereumAbiTests: XCTestCase { "inputs": [{ "name": "caller", "type": "address", - "value": "0x27239549dd40e1d60f5b80b0c4196923745b1fd2" + "value": "0x27239549DD40E1D60F5B80B0C4196923745B1FD2" }, { "components": [{ "name": "srcToken", "type": "address", - "value": "0x2b591e99afe9f32eaa6214f7b7629768c40eeb39" + "value": "0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39" }, { "name": "dstToken", "type": "address", - "value": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" }, { "name": "srcReceiver", "type": "address", - "value": "0x27239549dd40e1d60f5b80b0c4196923745b1fd2" + "value": "0x27239549DD40E1D60F5B80B0C4196923745B1FD2" }, { "name": "dstReceiver", "type": "address", - "value": "0x1611c227725c5e420ef058275ae772b41775e261" + "value": "0x1611C227725c5E420Ef058275AE772b41775e261" }, { "name": "amount", "type": "uint256", @@ -213,4 +211,143 @@ class EthereumAbiTests: XCTestCase { let hash = EthereumAbi.encodeTyped(messageJson: message) XCTAssertEqual(hash.hexString, "a85c2e2b118698e88db68a8105b794a8cc7cec074e89ef991cb4f5f533819cc2") } + + func testEncodeSeaportMessage() throws { + let url = Bundle(for: EthereumAbiTests.self).url(forResource: "seaport_712", withExtension: "json")! + let json = try String(contentsOf: url) + let hash = EthereumAbi.encodeTyped(messageJson: json) + + XCTAssertEqual(hash.hexString, "54140d99a864932cbc40fd8a2d1d1706c3923a79c183a3b151e929ac468064db") + } + + func testEthereumAbiEncodeFunction() throws { + let amountIn = EthereumAbiNumberNParam.with { + $0.bits = 256 + $0.value = Data(hexString: "0x0de0b6b3a7640000")! // 1000000000000000000 + } + let amountOutMin = EthereumAbiNumberNParam.with { + $0.bits = 256 + $0.value = Data(hexString: "0x229f7e501ad62bdb")! // 2494851601099271131 + } + let deadline = EthereumAbiNumberNParam.with { + $0.bits = 256 + $0.value = Data(hexString: "0x5f0ed070")! // 1594806384 + } + + let arr = EthereumAbiArrayParam.with { + $0.elementType = EthereumAbiParamType.with { + $0.address = EthereumAbiAddressType.init() + } + $0.elements = [ + EthereumAbiToken.with { $0.address = "0x6B175474E89094C44Da98b954EedeAC495271d0F" }, + EthereumAbiToken.with { $0.address = "0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2" }, + EthereumAbiToken.with { $0.address = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" }, + EthereumAbiToken.with { $0.address = "0xE41d2489571d322189246DaFA5ebDe1F4699F498" }, + ] + } + let encodingInput = EthereumAbiFunctionEncodingInput.with { + $0.functionName = "swapExactTokensForTokens" + $0.tokens = [ + EthereumAbiToken.with { $0.numberUint = amountIn }, + EthereumAbiToken.with { $0.numberUint = amountOutMin }, + EthereumAbiToken.with { $0.array = arr }, + EthereumAbiToken.with { $0.address = "0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1" }, + EthereumAbiToken.with { $0.numberUint = deadline }, + ] + } + + let inputData = try encodingInput.serializedData() + let outputData = EthereumAbi.encodeFunction(coin: .ethereum, input: inputData) + + let encodingOutput = try EthereumAbiFunctionEncodingOutput(serializedData: outputData) + XCTAssertEqual(encodingOutput.error, EthereumAbiAbiError.ok) + XCTAssertEqual(encodingOutput.functionType, "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)") + + let expectedEncoded = Data(hexString: "0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000229f7e501ad62bdb00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000007d8bf18c7ce84b3e175b339c4ca93aed1dd166f1000000000000000000000000000000000000000000000000000000005f0ed07000000000000000000000000000000000000000000000000000000000000000040000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498")! + XCTAssertEqual(encodingOutput.encoded, expectedEncoded) + } + + func testEthereumAbiDecodeParams() throws { + let encoded = Data(hexString: "00000000000000000000000088341d1a8f672d2780c8dc725902aae72f143b0c0000000000000000000000000000000000000000000000000000000000000001")! + + let abiParams = [ + EthereumAbiParam.with { + $0.name = "to" + $0.param = EthereumAbiParamType.with { $0.address = EthereumAbiAddressType.init() } + }, + EthereumAbiParam.with { + $0.name = "approved" + $0.param = EthereumAbiParamType.with { $0.boolean = EthereumAbiBoolType.init() } + } + ] + let decodingInput = EthereumAbiParamsDecodingInput.with { + $0.encoded = encoded + $0.abiParams = EthereumAbiAbiParams.with { $0.params = abiParams } + } + + let inputData = try decodingInput.serializedData() + let outputData = EthereumAbi.decodeParams(coin: .ethereum, input: inputData) + + let decodingOutput = try EthereumAbiParamsDecodingOutput(serializedData: outputData) + XCTAssertEqual(decodingOutput.error, EthereumAbiAbiError.ok) + + XCTAssertEqual(decodingOutput.tokens[0].name, "to") + XCTAssertEqual(decodingOutput.tokens[0].address, "0x88341d1a8F672D2780C8dC725902AAe72F143B0c") + XCTAssertEqual(decodingOutput.tokens[1].name, "approved") + XCTAssertEqual(decodingOutput.tokens[1].boolean, true) + } + + func testEthereumAbiDecodeValue() throws { + let encoded = Data(hexString: "000000000000000000000000000000000000000000000000000000000000002c48656c6c6f20576f726c64212020202048656c6c6f20576f726c64212020202048656c6c6f20576f726c64210000000000000000000000000000000000000000")! + + let decodingInput = EthereumAbiValueDecodingInput.with { + $0.encoded = encoded + $0.paramType = "string" + } + + let inputData = try decodingInput.serializedData() + let outputData = EthereumAbi.decodeValue(coin: .ethereum, input: inputData) + + let decodingOutput = try EthereumAbiValueDecodingOutput(serializedData: outputData) + XCTAssertEqual(decodingOutput.error, EthereumAbiAbiError.ok) + XCTAssertEqual(decodingOutput.token.stringValue, "Hello World! Hello World! Hello World!") + } + + func testEthereumAbiDecodeContractCall() throws { + let encoded = Data(hexString: "c47f0027000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000086465616462656566000000000000000000000000000000000000000000000000")! + let abiJson = """ + { + "c47f0027": { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "string" + } + ], + "name": "setName", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + } + """ + + let decodingInput = EthereumAbiContractCallDecodingInput.with { + $0.encoded = encoded + $0.smartContractAbiJson = abiJson + } + + let inputData = try decodingInput.serializedData() + let outputData = EthereumAbi.decodeContractCall(coin: .ethereum, input: inputData) + + let decodingOutput = try EthereumAbiContractCallDecodingOutput(serializedData: outputData) + XCTAssertEqual(decodingOutput.error, EthereumAbiAbiError.ok) + + let expectedJson = #"{"function":"setName(string)","inputs":[{"name":"name","type":"string","value":"deadbeef"}]}"# + XCTAssertEqual(decodingOutput.decodedJson, expectedJson) + XCTAssertEqual(decodingOutput.tokens[0].name, "name") + XCTAssertEqual(decodingOutput.tokens[0].stringValue, "deadbeef") + } } diff --git a/swift/Tests/Blockchains/EthereumRlpTests.swift b/swift/Tests/Blockchains/EthereumRlpTests.swift new file mode 100644 index 00000000000..b62e8d92e59 --- /dev/null +++ b/swift/Tests/Blockchains/EthereumRlpTests.swift @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class EthereumRlpTests: XCTestCase { + func testRlpEncodeEip1559() throws { + let chainId = Data(hexString: "0x0a")! + let nonce = Data(hexString: "0x06")! + let maxInclusionFeePerGas = Data(hexString: "0x77359400")! // 2000000000 + let maxFeePerGas = Data(hexString: "0xb2d05e00")! // 3000000000 + let gasLimit = Data(hexString: "0x526c")! // 21100 + let to = "0x6b175474e89094c44da98b954eedeac495271d0f" + let amount = Data(hexString: "0x00")! + let payload = Data(hexString: "a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1")! + // Empty `access_list`. + let accessList = EthereumRlpRlpList() + + let rlpList = EthereumRlpRlpList.with { + $0.items = [ + EthereumRlpRlpItem.with { $0.numberU256 = chainId }, + EthereumRlpRlpItem.with { $0.numberU256 = nonce }, + EthereumRlpRlpItem.with { $0.numberU256 = maxInclusionFeePerGas }, + EthereumRlpRlpItem.with { $0.numberU256 = maxFeePerGas }, + EthereumRlpRlpItem.with { $0.numberU256 = gasLimit }, + EthereumRlpRlpItem.with { $0.address = to }, + EthereumRlpRlpItem.with { $0.numberU256 = amount }, + EthereumRlpRlpItem.with { $0.data = payload }, + EthereumRlpRlpItem.with { $0.list = accessList }, + ] + } + + let encodingInput = EthereumRlpEncodingInput.with { + $0.item.list = rlpList + } + let inputData = try encodingInput.serializedData() + let outputData = EthereumRlp.encode(coin: .ethereum, input: inputData) + + let encodingOutput = try EthereumRlpEncodingOutput(serializedData: outputData) + XCTAssertEqual(encodingOutput.error, CommonSigningError.ok) + XCTAssertEqual(encodingOutput.encoded.hexString, "f86c0a06847735940084b2d05e0082526c946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1c0") + } +} diff --git a/swift/Tests/Blockchains/EthereumTests.swift b/swift/Tests/Blockchains/EthereumTests.swift index b16ba0bf7db..9d5e73a326a 100644 --- a/swift/Tests/Blockchains/EthereumTests.swift +++ b/swift/Tests/Blockchains/EthereumTests.swift @@ -1,14 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore class EthereumTests: XCTestCase { - + func testAddress() { let anyAddress = AnyAddress(string: "0x7d8bf18c7ce84b3e175b339c4ca93aed1dd166f1", coin: .ethereum) @@ -158,6 +156,67 @@ class EthereumTests: XCTestCase { XCTAssertEqual(output.data.hexString, "f242432a000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee50000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000") } + func testSignStakeRocketPool() { + let function = EthereumAbiFunction(name: "deposit") + + let input = EthereumSigningInput.with { + $0.chainID = Data(hexString: "01")! + $0.nonce = Data(hexString: "01")! + $0.txMode = .enveloped + $0.gasPrice = Data(hexString: "77541880")! // 2002000000 + $0.gasLimit = Data(hexString: "0320c8")! // 205000 + $0.maxFeePerGas = Data(hexString: "067ef83700")! // 27900000000 + $0.maxInclusionFeePerGas = Data(hexString: "3b9aca00")! // 1000000000 + $0.toAddress = "0x2cac916b2a963bf162f076c0a8a4a8200bcfbfb4" // contract + $0.privateKey = Data(hexString: "9f56448d33de406db1561aae15fce64bdf0e9706ff15c45d4409e8fcbfd1a498")! + + $0.transaction = EthereumTransaction.with { + $0.transfer = EthereumTransaction.Transfer.with { + $0.amount = Data(hexString: "2386f26fc10000")! // 0.01 ETH + $0.data = Data(hexString: EthereumAbi.encode(fn: function).hexString)! + } + } + } + let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) + + // https://etherscan.io/tx/0xfeba0c579f3e964fbc4eafa500e86891b9f4113735b1364edd4433d765506f1e + XCTAssertEqual(output.r.hexString, "fb39e5079d7a0598ec45785d73a06b91fe1db707b9c6a150c87ffce2492c66d6") + XCTAssertEqual(output.v.hexString, "00") + XCTAssertEqual(output.s.hexString, "7fbd43a6f4733b2b4f98ad1bc4678ea2615f5edf56ad91408337adec2f07c0ac") + XCTAssertEqual(output.encoded.hexString, "02f8770101843b9aca0085067ef83700830320c8942cac916b2a963bf162f076c0a8a4a8200bcfbfb4872386f26fc1000084d0e30db0c080a0fb39e5079d7a0598ec45785d73a06b91fe1db707b9c6a150c87ffce2492c66d6a07fbd43a6f4733b2b4f98ad1bc4678ea2615f5edf56ad91408337adec2f07c0ac") + } + + func testSignUnstakeRocketPool() { + let function = EthereumAbiFunction(name: "burn") + function.addParamUInt256(val: Data(hexString: "0x21faa32ab2502b")!, isOutput: false) + + let input = EthereumSigningInput.with { + $0.chainID = Data(hexString: "01")! + $0.nonce = Data(hexString: "03")! + $0.txMode = .enveloped + $0.gasPrice = Data(hexString: "77541880")! // 2002000000 + $0.gasLimit = Data(hexString: "055730")! // 350000 + $0.maxFeePerGas = Data(hexString: "067ef83700")! // 27900000000 + $0.maxInclusionFeePerGas = Data(hexString: "3b9aca00")! // 1000000000 + $0.toAddress = "0xae78736Cd615f374D3085123A210448E74Fc6393" // contract + $0.privateKey = Data(hexString: "9f56448d33de406db1561aae15fce64bdf0e9706ff15c45d4409e8fcbfd1a498")! + + $0.transaction = EthereumTransaction.with { + $0.contractGeneric = EthereumTransaction.ContractGeneric.with { + $0.amount = Data(hexString: "00")! + $0.data = Data(hexString: EthereumAbi.encode(fn: function).hexString)! + } + } + } + let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) + + // https://etherscan.io/tx/0x7fd3c0e9b8b309b4258baa7677c60f5e00e8db7b647fbe3a52adda25058a4b37 + XCTAssertEqual(output.r.hexString, "1fc6e94908107584357799e952b4e3fb87f088aeb66d7930a7015643f19c9e7f") + XCTAssertEqual(output.v.hexString, "00") + XCTAssertEqual(output.s.hexString, "2c56a0b70ff2e52bf374a3dcd404bc42317d5ca15d319f5e33665352eb48f06f") + XCTAssertEqual(output.encoded.hexString, "02f8900103843b9aca0085067ef837008305573094ae78736cd615f374d3085123a210448e74fc639380a442966c680000000000000000000000000000000000000000000000000021faa32ab2502bc080a01fc6e94908107584357799e952b4e3fb87f088aeb66d7930a7015643f19c9e7fa02c56a0b70ff2e52bf374a3dcd404bc42317d5ca15d319f5e33665352eb48f06f") + } + func testSignJSON() { let json = """ { @@ -213,4 +272,101 @@ class EthereumTests: XCTestCase { XCTAssertEqual(ethAddress, "0xa4531dE99E22B2166d340E7221669DF565c52024") XCTAssertEqual(btcAddress, "bc1q97jc0jdgsyvvhxydxxd6np8sa920c39l3qpscf") } + + func testMessageAndVerifySignerImmutableX() { + let privateKey = PrivateKey(data: Data(hexString: "3b0a61f46fdae924007146eacb6db6642de7a5603ad843ec58e10331d89d4b84")!)! + let msg = "Only sign this request if you’ve initiated an action with Immutable X.\n\nFor internal use:\nbd717ba31dca6e0f3f136f7c4197babce5f09a9f25176044c0b3112b1b6017a3" + let signature = EthereumMessageSigner.signMessageImmutableX(privateKey: privateKey, message: msg) + XCTAssertEqual(signature, "32cd5a58f3419fc5db672e3d57f76199b853eda0856d491b38f557b629b0a0814ace689412bf354a1af81126d2749207dffae8ae8845160f33948a6b787e17ee01") + let pubKey = privateKey.getPublicKey(coinType: .ethereum) + XCTAssertTrue(EthereumMessageSigner.verifyMessage(pubKey: pubKey, message: msg, signature: signature)) + } + + func testMessageAndVerifySignerLegacy() { + let privateKey = PrivateKey(data: Data(hexString: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")!)! + let msg = "Foo" + let signature = EthereumMessageSigner.signMessage(privateKey: privateKey, message: msg) + XCTAssertEqual(signature, "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be71101b") + let pubKey = privateKey.getPublicKey(coinType: .ethereum) + XCTAssertTrue(EthereumMessageSigner.verifyMessage(pubKey: pubKey, message: msg, signature: signature)) + } + + func testMessageAndVerifySignerEip155() { + let privateKey = PrivateKey(data: Data(hexString: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")!)! + let msg = "Foo" + let signature = EthereumMessageSigner.signMessageEip155(privateKey: privateKey, message: msg, chainId: 0) + XCTAssertEqual(signature, "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be711023") + let pubKey = privateKey.getPublicKey(coinType: .ethereum) + XCTAssertTrue(EthereumMessageSigner.verifyMessage(pubKey: pubKey, message: msg, signature: signature)) + } + + func testMessageAndVerifySigner712Legacy() { + let privateKey = PrivateKey(data: Data(hexString: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")!)! + let msg = """ + { + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"} + ], + "Person": [ + {"name": "name", "type": "string"}, + {"name": "wallet", "type": "address"} + ] + }, + "primaryType": "Person", + "domain": { + "name": "Ether Person", + "version": "1", + "chainId": 0, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "name": "Cow", + "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + } + } + """ + let signature = EthereumMessageSigner.signTypedMessage(privateKey: privateKey, messageJson: msg) + XCTAssertEqual(signature, "446434e4c34d6b7456e5f07a1b994b88bf85c057234c68d1e10c936b1c85706c4e19147c0ac3a983bc2d56ebfd7146f8b62bcea6114900fe8e7d7351f44bf3761c") + let pubKey = privateKey.getPublicKey(coinType: .ethereum) + XCTAssertTrue(EthereumMessageSigner.verifyMessage(pubKey: pubKey, message: msg, signature: signature)) + } + + func testMessageAndVerifySigner712Eip155() { + let privateKey = PrivateKey(data: Data(hexString: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")!)! + let msg = """ + { + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"} + ], + "Person": [ + {"name": "name", "type": "string"}, + {"name": "wallet", "type": "address"} + ] + }, + "primaryType": "Person", + "domain": { + "name": "Ether Person", + "version": "1", + "chainId": 0, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "name": "Cow", + "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + } + } + """ + let signature = EthereumMessageSigner.signTypedMessageEip155(privateKey: privateKey, messageJson: msg, chainId: 0) + XCTAssertEqual(signature, "446434e4c34d6b7456e5f07a1b994b88bf85c057234c68d1e10c936b1c85706c4e19147c0ac3a983bc2d56ebfd7146f8b62bcea6114900fe8e7d7351f44bf37624") + let pubKey = privateKey.getPublicKey(coinType: .ethereum) + XCTAssertTrue(EthereumMessageSigner.verifyMessage(pubKey: pubKey, message: msg, signature: signature)) + } } diff --git a/swift/Tests/Blockchains/EverscaleTests.swift b/swift/Tests/Blockchains/EverscaleTests.swift new file mode 100644 index 00000000000..2fc44ddf7b5 --- /dev/null +++ b/swift/Tests/Blockchains/EverscaleTests.swift @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class EverscaleTests: XCTestCase { + func testAddressFromPrivateKey() { + let privateKey = PrivateKey(data: Data(hexString: "15d126cb1a84acdbcd1d9c3f6975968c2beb18cc43c95849d4b0226e1c8552aa")!)! + let publicKey = privateKey.getPublicKeyEd25519() + let address = AnyAddress(publicKey: publicKey, coin: .everscale) + XCTAssertEqual(address.description, "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04") + } + + func testAddressFromPublicKey() { + let publicKey = PublicKey(data: Data(hexString: "a0303f8fc89a3c2124f5dc6f3ab9a9cb246b7d1e24897eaf5e63eeee20085db0")!, type: PublicKeyType.ed25519)! + let address = AnyAddress(publicKey: publicKey, coin: .everscale) + XCTAssertEqual(address.description, "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04") + } + + func testAddressFromString() { + let addressString = "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04" + let address = AnyAddress(string: addressString, coin: .everscale) + XCTAssertEqual(address!.description, "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04") + } + + func testSign() throws { + let privateKeyData = Data(hexString: "542bd4288352f1c6b270046f153d406aec054a0a06000ab9b36b5c6dd3050ad4")! + + let transfer = EverscaleTransfer.with { + $0.bounce = false + $0.behavior = EverscaleMessageBehavior.simpleTransfer + $0.amount = 100000000 + $0.expiredAt = 1680770631 + $0.to = "0:db18a67f4626f15ac0537a18445937f685f9b30184f0d7b28be4bdeb92d2fd90" + $0.encodedContractData = "te6ccgEBAQEAKgAAUAAAAAFLqS2KOWKN+7Y5OSiKhKisiw6t/h2ovvR3WbapyAtrdctwupw=" + } + + let input = EverscaleSigningInput.with { + $0.transfer = transfer + $0.privateKey = privateKeyData + } + + let output: EverscaleSigningOutput = AnySigner.sign(input: input, coin: .everscale) + + // Link to the message: https://everscan.io/messages/73807b0a3ca2d8564c023dccd5b9da222a270f68338c6fc2c064dda376a2c59d + let expectedString = "te6ccgICAAIAAQAAAKoAAAHfiAG+Ilaz1wTyTEauoymMGl6o+NGqhszIlHS8BXAmXniYrAImASIQKH2jIwoA65IGC6aua4gAA4fFo/Nuxgb3sIRELhZnSXIS7IsE2E4D+8hk3EWGVZX+ICqlN/ka9DvXduhaXUlsUyF0MjgAAAAIHAABAGhCAG2MUz+jE3itYCm9DCIsm/tC/NmAwnhr2UXyXvXJaX7IIC+vCAAAAAAAAAAAAAAAAAAA" + + XCTAssertEqual(output.encoded, expectedString) + } +} diff --git a/swift/Tests/Blockchains/EvmosTests.swift b/swift/Tests/Blockchains/EvmosTests.swift index ae10bdc08aa..e1df1d44943 100644 --- a/swift/Tests/Blockchains/EvmosTests.swift +++ b/swift/Tests/Blockchains/EvmosTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore @@ -71,6 +69,6 @@ class EvmosTests: XCTestCase { {"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpoBCpcBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEncKLGV2bW9zMXJrMzlkazN3ZmY1bnBzN2VtdWh2M250a24zbnN6NnoyZXJxZnIwEixldm1vczEwazlscnJydWFwOW51OTZteHd3eWUyZjZhNXdhemVoMzNrcTY3ehoZCgZhZXZtb3MSDzIwMDAwMDAwMDAwMDAwMBJ7ClcKTwooL2V0aGVybWludC5jcnlwdG8udjEuZXRoc2VjcDI1NmsxLlB1YktleRIjCiEClHXJ+iPsaTZnuqdsTaabSczP3wWMTcsnumfPvJCC2e0SBAoCCAESIAoaCgZhZXZtb3MSEDE0MDAwMDAwMDAwMDAwMDAQ4MUIGkAz9vh1EutbLrLZmRA4eK72bA6bhfMX0YnhtRl5jeaL3AYmk0qdrwG9XzzleBsZ++IokJIk47cgOOyvEjl92Jhj"} """ XCTAssertJSONEqual(output.serialized, expected) - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } } diff --git a/swift/Tests/Blockchains/FIOTests.swift b/swift/Tests/Blockchains/FIOTests.swift index 3edbc7cd9d1..4a35e145ea8 100644 --- a/swift/Tests/Blockchains/FIOTests.swift +++ b/swift/Tests/Blockchains/FIOTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/FilecoinTests.swift b/swift/Tests/Blockchains/FilecoinTests.swift index ab5a83f6f53..2998bdfec56 100644 --- a/swift/Tests/Blockchains/FilecoinTests.swift +++ b/swift/Tests/Blockchains/FilecoinTests.swift @@ -1,21 +1,36 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore class FilecoinTests: XCTestCase { - func testAddress() { + func testCreateAddress() { let privateKey = PrivateKey(data: Data(hexString: "1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe")!)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: publicKey, coin: .filecoin) XCTAssertEqual(address.description, "f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq") } + func testCreateDelegatedAddress() { + let privateKey = PrivateKey(data: Data(hexString: "825d2bb32965764a98338139412c7591ed54c951dd65504cd8ddaeaa0fea7b2a")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: publicKey, filecoinAddressType: .delegated) + XCTAssertEqual(address.description, "f410fvak24cyg3saddajborn6idt7rrtfj2ptauk5pbq") + } + + func testAddressConverter() { + let actualEth = FilecoinAddressConverter.convertToEthereum(filecoinAddress: "f410frw6wy7w6sbsguyn3yzeygg34fgf72n5ao5sxyky") + XCTAssertEqual(actualEth, "0x8dbD6c7Ede90646a61Bbc649831b7c298BFd37A0") + XCTAssert(AnyAddress.isValid(string: actualEth, coin: .ethereum)) + + let actualFilecoin = FilecoinAddressConverter.convertFromEthereum(ethAddress:"0x8dbD6c7Ede90646a61Bbc649831b7c298BFd37A0") + XCTAssertEqual(actualFilecoin, "f410frw6wy7w6sbsguyn3yzeygg34fgf72n5ao5sxyky") + XCTAssert(AnyAddress.isValid(string: actualFilecoin, coin: .filecoin)) + } + func testSigner() { let input = FilecoinSigningInput.with { $0.privateKey = Data(hexString: "1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe")! @@ -36,6 +51,7 @@ class FilecoinTests: XCTestCase { "GasFeeCap": "700000000000000000000", "GasLimit": 1000, "GasPremium": "800000000000000000000", + "Method": 0, "Nonce": 2, "To": "f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq", "Value": "600000000000000000000" @@ -48,4 +64,39 @@ class FilecoinTests: XCTestCase { """ XCTAssertJSONEqual(output.json, json) } + + /// Successfully broadcasted: + /// https://filfox.info/en/message/bafy2bzaceczvto7d2af7cq3kuwlvmanlh5xica4apl3vwxu37yaeozq72mvgm + func testSignerToDelegated() { + let input = FilecoinSigningInput.with { + $0.privateKey = Data(hexString: "d3d6ed8b97dcd4661f62a1162bee6949401fd3935f394e6eacf15b6d5005483c")! + $0.to = "f410frw6wy7w6sbsguyn3yzeygg34fgf72n5ao5sxyky" + $0.nonce = 0 + $0.value = Data(hexString: "038d7ea4c68000")! // 0.001 FIL + $0.gasLimit = 6152567 + $0.gasFeeCap = Data(hexString: "01086714e9")! // 4435940585 + $0.gasPremium = Data(hexString: "b0f553")! // 11597139 + } + + let output: FilecoinSigningOutput = AnySigner.sign(input: input, coin: .filecoin) + let json = """ + { + "Message": { + "From": "f1mzyorxlcvdoqn5cto7urefbucugrcxxghpjc5hi", + "GasFeeCap": "4435940585", + "GasLimit": 6152567, + "GasPremium": "11597139", + "Method": 3844450837, + "Nonce": 0, + "To": "f410frw6wy7w6sbsguyn3yzeygg34fgf72n5ao5sxyky", + "Value": "1000000000000000" + }, + "Signature": { + "Data": "bxZhnsOYjdArPa3W0SpggwqtXPgvfRSoM2dU5lXYar9lWhTGc6FvPWk2RTUGyA8UtzMIdOPSUKfzU1iA2eA3YwA=", + "Type": 1 + } + } + """ + XCTAssertJSONEqual(output.json, json) + } } diff --git a/swift/Tests/Blockchains/GreenfieldTests.swift b/swift/Tests/Blockchains/GreenfieldTests.swift new file mode 100644 index 00000000000..dce11dcfc84 --- /dev/null +++ b/swift/Tests/Blockchains/GreenfieldTests.swift @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class GreenfieldTests: XCTestCase { + func testSignSend() { + // Successfully broadcasted: https://greenfieldscan.com/tx/ED8508F3C174C4430B8EE718A6D6F0B02A8C516357BE72B1336CF74356529D19 + + let sendCoinsMessage = GreenfieldMessage.Send.with { + $0.fromAddress = "0xA815ae0b06dC80318121745BE40e7F8c6654e9f3" + $0.toAddress = "0x8dbD6c7Ede90646a61Bbc649831b7c298BFd37A0" + $0.amounts = [GreenfieldAmount.with { + $0.amount = "1234500000000000" + $0.denom = "BNB" + }] + } + + let message = GreenfieldMessage.with { + $0.sendCoinsMessage = sendCoinsMessage + } + + let fee = GreenfieldFee.with { + $0.gas = 1200 + $0.amounts = [GreenfieldAmount.with { + $0.amount = "6000000000000" + $0.denom = "BNB" + }] + } + + let input = GreenfieldSigningInput.with { + $0.signingMode = .eip712; + $0.encodingMode = .protobuf + $0.mode = .sync + $0.accountNumber = 15952 + $0.ethChainID = "5600" + $0.cosmosChainID = "greenfield_5600-1" + $0.memo = "Trust Wallet test memo" + $0.sequence = 0 + $0.messages = [message] + $0.fee = fee + $0.privateKey = Data(hexString: "825d2bb32965764a98338139412c7591ed54c951dd65504cd8ddaeaa0fea7b2a")! + } + + let output: GreenfieldSigningOutput = AnySigner.sign(input: input, coin: .greenfield) + + XCTAssertJSONEqual(output.serialized, "{\"tx_bytes\": \"CqwBCpEBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnEKKjB4QTgxNWFlMGIwNmRDODAzMTgxMjE3NDVCRTQwZTdGOGM2NjU0ZTlmMxIqMHg4ZGJENmM3RWRlOTA2NDZhNjFCYmM2NDk4MzFiN2MyOThCRmQzN0EwGhcKA0JOQhIQMTIzNDUwMDAwMDAwMDAwMBIWVHJ1c3QgV2FsbGV0IHRlc3QgbWVtbxJzClYKTQomL2Nvc21vcy5jcnlwdG8uZXRoLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohAhm/mQgs8vzaqBLW66HrqQNv86PYTBgXyElU1OiuKD/sEgUKAwjIBRIZChQKA0JOQhINNjAwMDAwMDAwMDAwMBCwCRpBwbRb1qEAWwaqVfmp1Mn7iMi7wwV/oPi2J2eW9NBIdNoky+ZL+uegS/kY+funCOrqVZ+Kbol9/djAV+bQaNUB0xw=\", \"mode\": \"BROADCAST_MODE_SYNC\"}") + XCTAssertEqual(output.errorMessage, "") + } + + func testSignTransferOut() { + // Successfully broadcasted Greenfield: https://greenfieldscan.com/tx/38C29C530A74946CFD22EE07DA642F5EF9399BC9AEA59EC56A9B5BE16DE16CE7 + // BSC (parent transaction): https://testnet.bscscan.com/tx/0x7f73c8a362e14e58cb5e0ec17616afc50eff7aa398db472383a6d017c8a5861a + + let transferOutMessage = GreenfieldMessage.BridgeTransferOut.with { + $0.fromAddress = "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1" + $0.toAddress = "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1" + $0.amount = GreenfieldAmount.with { + $0.amount = "5670000000000000" + $0.denom = "BNB" + } + } + + let message = GreenfieldMessage.with { + $0.bridgeTransferOut = transferOutMessage + } + + let fee = GreenfieldFee.with { + $0.gas = 1200 + $0.amounts = [GreenfieldAmount.with { + $0.amount = "6000000000000" + $0.denom = "BNB" + }] + } + + let input = GreenfieldSigningInput.with { + $0.signingMode = .eip712; + $0.encodingMode = .protobuf + $0.mode = .sync + $0.accountNumber = 15560 + $0.ethChainID = "5600" + $0.cosmosChainID = "greenfield_5600-1" + $0.sequence = 7 + $0.messages = [message] + $0.fee = fee + $0.privateKey = Data(hexString: "9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0")! + } + + let output: GreenfieldSigningOutput = AnySigner.sign(input: input, coin: .greenfield) + + XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_SYNC\",\"tx_bytes\":\"CpkBCpYBCiEvZ3JlZW5maWVsZC5icmlkZ2UuTXNnVHJhbnNmZXJPdXQScQoqMHg5ZDFkOTdhREZjZDMyNEJiZDYwM0QzODcyQkQ3OGUwNDA5ODUxMGIxEioweDlkMWQ5N2FERmNkMzI0QmJkNjAzRDM4NzJCRDc4ZTA0MDk4NTEwYjEaFwoDQk5CEhA1NjcwMDAwMDAwMDAwMDAwEnUKWApNCiYvY29zbW9zLmNyeXB0by5ldGguZXRoc2VjcDI1NmsxLlB1YktleRIjCiECee80Bk2hDbBGPHBIBha6AgcD7DpFAm3ve+vSCC9db8gSBQoDCMgFGAcSGQoUCgNCTkISDTYwMDAwMDAwMDAwMDAQsAkaQc4DDByhu80Uy/M3sQePvAmhmbFWZeGq359rugtskEiXKfCzSB86XmBi+l+Q5ETDS2folMxbufHSE8gM4WBCHzgc\"}") + XCTAssertEqual(output.errorMessage, "") + } +} diff --git a/swift/Tests/Blockchains/GroestlcoinTests.swift b/swift/Tests/Blockchains/GroestlcoinTests.swift index ed0d9b250d5..5490f79e1b3 100644 --- a/swift/Tests/Blockchains/GroestlcoinTests.swift +++ b/swift/Tests/Blockchains/GroestlcoinTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Blockchains/GroestlcoinTransactionSignerTests.swift b/swift/Tests/Blockchains/GroestlcoinTransactionSignerTests.swift index 09bdb7119ee..b1e89358d4c 100644 --- a/swift/Tests/Blockchains/GroestlcoinTransactionSignerTests.swift +++ b/swift/Tests/Blockchains/GroestlcoinTransactionSignerTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore @@ -14,6 +12,7 @@ class GroestlcoinTransactionSignerTests: XCTestCase { func testSignP2WPKH() throws { var input = BitcoinSigningInput.with { + $0.coinType = CoinType.groestlcoin.rawValue $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .groestlcoin) $0.amount = 2500 $0.byteFee = 1 @@ -67,6 +66,7 @@ class GroestlcoinTransactionSignerTests: XCTestCase { func testSignP2PKH() throws { var input = BitcoinSigningInput.with { + $0.coinType = CoinType.groestlcoin.rawValue $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .groestlcoin) $0.amount = 2500 $0.byteFee = 1 @@ -118,6 +118,7 @@ class GroestlcoinTransactionSignerTests: XCTestCase { func testSignP2SH_P2WPKH() throws { var input = BitcoinSigningInput.with { + $0.coinType = CoinType.groestlcoin.rawValue $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .groestlcoin) $0.amount = 5000 $0.byteFee = 1 diff --git a/swift/Tests/Blockchains/HarmonyTests.swift b/swift/Tests/Blockchains/HarmonyTests.swift index c74eca95520..e0faa2c289d 100644 --- a/swift/Tests/Blockchains/HarmonyTests.swift +++ b/swift/Tests/Blockchains/HarmonyTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Blockchains/HederaTests.swift b/swift/Tests/Blockchains/HederaTests.swift new file mode 100644 index 00000000000..7db774a98b9 --- /dev/null +++ b/swift/Tests/Blockchains/HederaTests.swift @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class HederaTests: XCTestCase { + func testAddress() { + let anyAddress = AnyAddress(string: "0.0.1377988", coin: .hedera) + + XCTAssertEqual(anyAddress?.description, "0.0.1377988") + XCTAssertEqual(anyAddress?.coin, .hedera) + + let invalid = "0.0.a" + XCTAssertNil(Data(hexString: invalid)) + XCTAssertNil(AnyAddress(string: invalid, coin: .hedera)) + XCTAssertFalse(AnyAddress.isValid(string: invalid, coin: .hedera)) + } + + func testSignSimpleTransfer() { + // Successfully broadcasted: https://hashscan.io/testnet/transaction/0.0.48694347-1667222879-749068449?t=1667222891.440398729&p=1 + let privateKeyData = Data(hexString: "e87a5584c0173263e138db689fdb2a7389025aaae7cb1a18a1017d76012130e8")! + + let transfer = HederaTransferMessage.with { + $0.amount = 100000000 + $0.from = "0.0.48694347" + $0.to = "0.0.48462050" + } + + let transactionID = HederaTransactionID.with { + $0.accountID = "0.0.48694347" + $0.transactionValidStart = HederaTimestamp.with { + $0.seconds = 1667222879 + $0.nanos = 749068449 + } + } + + let body = HederaTransactionBody.with { + $0.memo = "" + $0.nodeAccountID = "0.0.9" + $0.transactionFee = 100000000 + $0.transactionValidDuration = 120 + $0.transactionID = transactionID + $0.transfer = transfer + } + + let input = HederaSigningInput.with { + $0.privateKey = privateKeyData + $0.body = body + } + + let output: HederaSigningOutput = AnySigner.sign(input: input, coin: .hedera) + let expectedEncoded = "0a440a150a0c08df9aff9a0610a1c197e502120518cb889c17120218091880c2d72f22020878721e0a1c0a0c0a0518e2f18d17108084af5f0a0c0a0518cb889c1710ff83af5f12660a640a205d3a70d08b2beafb72c7a68986b3ff819a306078b8c359d739e4966e82e6d40e1a40612589c3b15f1e3ed6084b5a3a5b1b81751578cac8d6c922f31731b3982a5bac80a22558b2197276f5bae49b62503a4d39448ceddbc5ef3ba9bee4c0f302f70c" + XCTAssertEqual(output.encoded.hexString, expectedEncoded) + } +} diff --git a/swift/Tests/Blockchains/IconTests.swift b/swift/Tests/Blockchains/IconTests.swift index 4745b55be81..3e19c5d48bb 100644 --- a/swift/Tests/Blockchains/IconTests.swift +++ b/swift/Tests/Blockchains/IconTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Blockchains/InternetComputerTests.swift b/swift/Tests/Blockchains/InternetComputerTests.swift new file mode 100644 index 00000000000..e4942450abf --- /dev/null +++ b/swift/Tests/Blockchains/InternetComputerTests.swift @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class InternetComputerTests: XCTestCase { + // TODO: Check and finalize implementation + + func testAddress() { + // TODO: Check and finalize implementation + + let key = PrivateKey(data: Data(hexString: "ee42eaada903e20ef6e5069f0428d552475c1ea7ed940842da6448f6ef9d48e7")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .internetComputer) + let addressFromString = AnyAddress(string: "2f25874478d06cf68b9833524a6390d0ba69c566b02f46626979a3d6a4153211", coin: .internetComputer)! + + XCTAssertEqual(pubkey.data.hexString, "048542e6fb4b17d6dfcac3948fe412c00d626728815ee7cc70509603f1bc92128a6e7548f3432d6248bc49ff44a1e50f6389238468d17f7d7024de5be9b181dbc8") + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSign() { + let key = PrivateKey(data: Data(hexString: "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be")!)! + let input = InternetComputerSigningInput.with { + $0.privateKey = key.data + $0.transaction = InternetComputerTransaction.with { + $0.transfer = InternetComputerTransaction.Transfer.with { + $0.toAccountIdentifier = "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a" + $0.amount = 100000000 + $0.memo = 0 + $0.currentTimestampNanos = 1691709940000000000 + } + } + + } + let output: InternetComputerSigningOutput = AnySigner.sign(input: input, coin: .internetComputer) + XCTAssertEqual(output.signedTransaction.hexString, "81826b5452414e53414354494f4e81a266757064617465a367636f6e74656e74a66c726571756573745f747970656463616c6c6e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d026b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f706263617267583b0a0012070a050880c2d72f2a220a20943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a3a0a088090caa5a3a78abd176d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f736967984013186f18b9181c189818b318a8186518b2186118d418971618b1187d18eb185818e01826182f1873183b185018cb185d18ef18d81839186418b3183218da1824182f184e18a01880182718c0189018c918a018fd18c418d9189e189818b318ef1874183b185118e118a51864185918e718ed18c71889186c1822182318ca6a726561645f7374617465a367636f6e74656e74a46c726571756573745f747970656a726561645f73746174656e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d0265706174687381824e726571756573745f7374617475735820e8fbc2d5b0bf837b3a369249143e50d4476faafb2dd620e4e982586a51ebcf1e6d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f7369679840182d182718201888188618ce187f0c182a187a18d718e818df18fb18d318d41118a5186a184b18341842185318d718e618e8187a1828186c186a183618461418f3183318bd18a618a718bc18d918c818b7189d186e1865188418ff18fd18e418e9187f181b18d705184818b21872187818d6181c161833184318a2") + } + + func testSignWithInvalidToAccountIdentifier() { + let key = PrivateKey(data: Data(hexString: "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be")!)! + let input = InternetComputerSigningInput.with { + $0.privateKey = key.data + $0.transaction = InternetComputerTransaction.with { + $0.transfer = InternetComputerTransaction.Transfer.with { + $0.toAccountIdentifier = "643d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826b" + $0.amount = 100000000 + $0.memo = 0 + $0.currentTimestampNanos = 1691709940000000000 + } + } + + } + let output: InternetComputerSigningOutput = AnySigner.sign(input: input, coin: .internetComputer) + XCTAssertEqual(output.error.rawValue, 16) + } + + func testSignWithInvalidAmount() { + let key = PrivateKey(data: Data(hexString: "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be")!)! + let input = InternetComputerSigningInput.with { + $0.privateKey = key.data + $0.transaction = InternetComputerTransaction.with { + $0.transfer = InternetComputerTransaction.Transfer.with { + $0.toAccountIdentifier = "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a" + $0.amount = 0 + $0.memo = 0 + $0.currentTimestampNanos = 1691709940000000000 + } + } + + } + let output: InternetComputerSigningOutput = AnySigner.sign(input: input, coin: .internetComputer) + XCTAssertEqual(output.error.rawValue, 23) + } +} diff --git a/swift/Tests/Blockchains/IoTeXTests.swift b/swift/Tests/Blockchains/IoTeXTests.swift index 0c91d1f18f1..017df545223 100644 --- a/swift/Tests/Blockchains/IoTeXTests.swift +++ b/swift/Tests/Blockchains/IoTeXTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/KavaTests.swift b/swift/Tests/Blockchains/KavaTests.swift index f9a5f8957a5..1b23b5e3632 100644 --- a/swift/Tests/Blockchains/KavaTests.swift +++ b/swift/Tests/Blockchains/KavaTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/KinTests.swift b/swift/Tests/Blockchains/KinTests.swift index 7dfec26b99b..2f6bf523852 100644 --- a/swift/Tests/Blockchains/KinTests.swift +++ b/swift/Tests/Blockchains/KinTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/KuCoinCommunityChainTests.swift b/swift/Tests/Blockchains/KuCoinCommunityChainTests.swift index df42dfc52fb..6faee34cac3 100644 --- a/swift/Tests/Blockchains/KuCoinCommunityChainTests.swift +++ b/swift/Tests/Blockchains/KuCoinCommunityChainTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Blockchains/KusamaTests.swift b/swift/Tests/Blockchains/KusamaTests.swift index 0b93a1cd118..b881a2451fb 100644 --- a/swift/Tests/Blockchains/KusamaTests.swift +++ b/swift/Tests/Blockchains/KusamaTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest @@ -51,7 +49,7 @@ class KusamaTests: XCTestCase { $0.value = Data(hexString: "0x02540be400")! } } - $0.network = .kusama + $0.network = CoinType.kusama.ss58Prefix $0.transactionVersion = 2 $0.privateKey = key.data } diff --git a/swift/Tests/Blockchains/LitecoinTests.swift b/swift/Tests/Blockchains/LitecoinTests.swift index 01fb5b275b0..f4087fe1a04 100644 --- a/swift/Tests/Blockchains/LitecoinTests.swift +++ b/swift/Tests/Blockchains/LitecoinTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Blockchains/MonacoinTests.swift b/swift/Tests/Blockchains/MonacoinTests.swift index e34a22a022b..b08d139ab98 100644 --- a/swift/Tests/Blockchains/MonacoinTests.swift +++ b/swift/Tests/Blockchains/MonacoinTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Blockchains/MultiversXTests.swift b/swift/Tests/Blockchains/MultiversXTests.swift new file mode 100644 index 00000000000..7b8cbf09ffc --- /dev/null +++ b/swift/Tests/Blockchains/MultiversXTests.swift @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class MultiversXTests: XCTestCase { + + let aliceBech32 = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + let alicePubKeyHex = "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1" + let aliceSeedHex = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9" + let bobBech32 = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx" + let carolBech32 = "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8" + + func testAddress() { + let key = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + let pubkey = key.getPublicKeyEd25519() + let address = AnyAddress(publicKey: pubkey, coin: .multiversX) + let addressFromString = AnyAddress(string: aliceBech32, coin: .multiversX)! + + XCTAssertEqual(pubkey.data.hexString, alicePubKeyHex) + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSignGenericAction() { + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + + let input = MultiversXSigningInput.with { + $0.genericAction = MultiversXGenericAction.with { + $0.accounts = MultiversXAccounts.with { + $0.senderNonce = 7 + $0.sender = aliceBech32 + $0.receiver = bobBech32 + } + $0.value = "0" + $0.data = "foo" + $0.version = 1 + } + $0.gasPrice = 1000000000 + $0.gasLimit = 50000 + $0.chainID = "1" + $0.privateKey = privateKey.data + } + + let output: MultiversXSigningOutput = AnySigner.sign(input: input, coin: .multiversX) + let expectedSignature = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700" + let expectedEncoded = #"{"nonce":7,"value":"0","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"\#(expectedSignature)"}"# + + XCTAssertEqual(output.signature, expectedSignature) + XCTAssertEqual(output.encoded, expectedEncoded) + } + + func testSignGenericActionWithGuardian() { + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + + let input = MultiversXSigningInput.with { + $0.genericAction = MultiversXGenericAction.with { + $0.accounts = MultiversXAccounts.with { + $0.senderNonce = 42 + $0.sender = aliceBech32 + $0.receiver = bobBech32 + $0.guardian = carolBech32 + } + $0.value = "1000000000000000000" + $0.data = "" + $0.version = 2 + $0.options = 2 + } + $0.gasPrice = 1000000000 + $0.gasLimit = 100000 + $0.chainID = "1" + $0.privateKey = privateKey.data + } + + let output: MultiversXSigningOutput = AnySigner.sign(input: input, coin: .multiversX) + let expectedSignature = "dae30e5cddb4a1f050009f939ce2c90843770870f9e6c77366be07e5cd7b3ebfdda38cd45d04e9070037d57761b6a68cee697e6043057f9dc565a4d0e632480d" + let expectedEncoded = #"{"nonce":42,"value":"1000000000000000000","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":100000,"chainID":"1","version":2,"signature":"\#(expectedSignature)","options":2,"guardian":"\#(carolBech32)"}"# + + XCTAssertEqual(output.signature, expectedSignature) + XCTAssertEqual(output.encoded, expectedEncoded) + } + + func testSignGenericActionUndelegate() { + // Successfully broadcasted https://explorer.multiversx.com/transactions/3301ae5a6a77f0ab9ceb5125258f12539a113b0c6787de76a5c5867f2c515d65 + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + + let input = MultiversXSigningInput.with { + $0.genericAction = MultiversXGenericAction.with { + $0.accounts = MultiversXAccounts.with { + $0.senderNonce = 6 + $0.sender = "erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa" + $0.receiver = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r" + } + $0.value = "0" + $0.data = "unDelegate@0de0b6b3a7640000" + $0.version = 1 + } + $0.gasPrice = 1000000000 + $0.gasLimit = 12000000 + $0.chainID = "1" + $0.privateKey = privateKey.data + } + + let output: MultiversXSigningOutput = AnySigner.sign(input: input, coin: .multiversX) + let expectedSignature = "89f9683af92f7b835bff4e1d0dbfcff5245b3367df4d23538eb799e0ad0a90be29ac3bd3598ce55b35b35ebef68bfa5738eed39fd01adc33476f65bd1b966e0b" + let expectedEncoded = #"{"nonce":6,"value":"0","receiver":"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r","sender":"erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa","gasPrice":1000000000,"gasLimit":12000000,"data":"dW5EZWxlZ2F0ZUAwZGUwYjZiM2E3NjQwMDAw","chainID":"1","version":1,"signature":"\#(expectedSignature)"}"# + + XCTAssertEqual(output.signature, expectedSignature) + XCTAssertEqual(output.encoded, expectedEncoded) + } + + func testSignGenericActionDelegate() { + // Successfully broadcasted https://explorer.multiversx.com/transactions/e5007662780f8ed677b37b156007c24bf60b7366000f66ec3525cfa16a4564e7 + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + + let input = MultiversXSigningInput.with { + $0.genericAction = MultiversXGenericAction.with { + $0.accounts = MultiversXAccounts.with { + $0.senderNonce = 1 + $0.sender = "erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa" + $0.receiver = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r" + } + $0.value = "1" + $0.data = "delegate" + $0.version = 1 + } + $0.gasPrice = 1000000000 + $0.gasLimit = 12000000 + $0.chainID = "1" + $0.privateKey = privateKey.data + } + + let output: MultiversXSigningOutput = AnySigner.sign(input: input, coin: .multiversX) + let expectedSignature = "3b9164d47a4e3c0330ae387cd29ba6391f9295acf5e43a16a4a2611645e66e5fa46bf22294ca68fe1948adf45cec8cb47b8792afcdb248bd9adec7c6e6c27108" + let expectedEncoded = #"{"nonce":1,"value":"1","receiver":"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r","sender":"erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa","gasPrice":1000000000,"gasLimit":12000000,"data":"ZGVsZWdhdGU=","chainID":"1","version":1,"signature":"\#(expectedSignature)"}"# + + XCTAssertEqual(output.signature, expectedSignature) + XCTAssertEqual(output.encoded, expectedEncoded) + } + + func testSignEGLDTransfer() { + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + + let input = MultiversXSigningInput.with { + $0.egldTransfer = MultiversXEGLDTransfer.with { + $0.accounts = MultiversXAccounts.with { + $0.senderNonce = 7 + $0.sender = aliceBech32 + $0.receiver = bobBech32 + } + $0.amount = "1000000000000000000" + } + $0.chainID = "1" + $0.privateKey = privateKey.data + } + + let output: MultiversXSigningOutput = AnySigner.sign(input: input, coin: .multiversX) + let expectedSignature = "0f40dec9d37bde3c67803fc535088e536344e271807bb7c1aa24af3c69bffa9b705e149ff7bcaf21678f4900c4ee72741fa6ef08bf4c67fc6da1c6b0f337730e" + let expectedEncoded = #"{"nonce":7,"value":"1000000000000000000","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":2,"signature":"\#(expectedSignature)"}"# + + XCTAssertEqual(output.signature, expectedSignature) + XCTAssertEqual(output.encoded, expectedEncoded) + } + + func testSignEGLDTransferWithGuardian() { + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + + let input = MultiversXSigningInput.with { + $0.egldTransfer = MultiversXEGLDTransfer.with { + $0.accounts = MultiversXAccounts.with { + $0.senderNonce = 7 + $0.sender = aliceBech32 + $0.receiver = bobBech32 + $0.guardian = carolBech32 + } + $0.amount = "1000000000000000000" + } + $0.chainID = "1" + $0.privateKey = privateKey.data + } + + let output: MultiversXSigningOutput = AnySigner.sign(input: input, coin: .multiversX) + let expectedSignature = "741dd0d24db4df37a050f693f8481b6e51b8dd6dfc2f01a4f90aa1af3e59c89a8b0ef9d710af33103970e353d9f0cb9fd128a2e174731cbc88265d9737ed5604" + let expectedEncoded = #"{"nonce":7,"value":"1000000000000000000","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":100000,"chainID":"1","version":2,"signature":"\#(expectedSignature)","options":2,"guardian":"\#(carolBech32)"}"# + + XCTAssertEqual(output.signature, expectedSignature) + XCTAssertEqual(output.encoded, expectedEncoded) + } + + func testSignESDTTransfer() { + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + + let input = MultiversXSigningInput.with { + $0.esdtTransfer = MultiversXESDTTransfer.with { + $0.accounts = MultiversXAccounts.with { + $0.senderNonce = 7 + $0.sender = aliceBech32 + $0.receiver = bobBech32 + } + $0.amount = "10000000000000" + $0.tokenIdentifier = "MYTOKEN-1234" + } + + $0.privateKey = privateKey.data + } + + let output: MultiversXSigningOutput = AnySigner.sign(input: input, coin: .multiversX) + let expectedSignature = "dd7cdc90aa09da6034b00a99e3ba0f1a2a38fa788fad018d53bf2e706f99e1a42c80063c28e6b48a5f2574c4054986f34c8eb36b1da63a22d19cf3ea5990b306" + let expectedData = "RVNEVFRyYW5zZmVyQDRkNTk1NDRmNGI0NTRlMmQzMTMyMzMzNEAwOTE4NGU3MmEwMDA=" + let expectedEncoded = #"{"nonce":7,"value":"0","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":425000,"data":"\#(expectedData)","chainID":"1","version":2,"signature":"\#(expectedSignature)"}"# + + XCTAssertEqual(output.signature, expectedSignature) + XCTAssertEqual(output.encoded, expectedEncoded) + } + + func testSignESDTNFTTransfer() { + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + + let input = MultiversXSigningInput.with { + $0.esdtnftTransfer = MultiversXESDTNFTTransfer.with { + $0.accounts = MultiversXAccounts.with { + $0.senderNonce = 7 + $0.sender = aliceBech32 + $0.receiver = bobBech32 + } + $0.tokenCollection = "LKMEX-aab910" + $0.tokenNonce = 4 + $0.amount = "184300000000000000" + } + + $0.privateKey = privateKey.data + } + + let output: MultiversXSigningOutput = AnySigner.sign(input: input, coin: .multiversX) + let expectedSignature = "59af89d9a9ece1f35bc34323c42061cae27bb5f9830f5eb26772e680732cbd901a86caa7c3eadacd392fe1024bef4c1f08ce1dfcafec257d6f41444ccea30a0c" + let expectedData = "RVNEVE5GVFRyYW5zZmVyQDRjNGI0ZDQ1NTgyZDYxNjE2MjM5MzEzMEAwNEAwMjhlYzNkZmEwMWFjMDAwQDgwNDlkNjM5ZTVhNjk4MGQxY2QyMzkyYWJjY2U0MTAyOWNkYTc0YTE1NjM1MjNhMjAyZjA5NjQxY2MyNjE4Zjg=" + let expectedEncoded = #"{"nonce":7,"value":"0","receiver":"\#(aliceBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":937500,"data":"\#(expectedData)","chainID":"1","version":2,"signature":"\#(expectedSignature)"}"# + + XCTAssertEqual(output.signature, expectedSignature) + XCTAssertEqual(output.encoded, expectedEncoded) + } +} diff --git a/swift/Tests/Blockchains/NEARTests.swift b/swift/Tests/Blockchains/NEARTests.swift index d275f2059ec..e145db8c625 100644 --- a/swift/Tests/Blockchains/NEARTests.swift +++ b/swift/Tests/Blockchains/NEARTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/NEOTests.swift b/swift/Tests/Blockchains/NEOTests.swift index 89754e3e28e..21b881831ff 100644 --- a/swift/Tests/Blockchains/NEOTests.swift +++ b/swift/Tests/Blockchains/NEOTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest @@ -95,7 +93,12 @@ class NEOTests: XCTestCase { let result = signedTx.encoded.hexString // https://testnet-explorer.o3.network/transactions/0x7b138c753c24f474d0f70af30a9d79756e0ee9c1f38c12ed07fbdf6fc5132eaf + XCTAssertEqual("8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf122956bc88746dc666759a2d67f120fe3ce1659f916d22a91e0b02421d3bddbd1232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac", result) + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // XCTAssertEqual("8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf1dd6a943678b9239a98a65d2980edf01beed0a0b4904573f31309a6a128a54980232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac", + // result) } } diff --git a/swift/Tests/Blockchains/NULSTests.swift b/swift/Tests/Blockchains/NULSTests.swift index 3bafd7b654a..458162f164b 100644 --- a/swift/Tests/Blockchains/NULSTests.swift +++ b/swift/Tests/Blockchains/NULSTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest @@ -36,4 +34,73 @@ class NULSTests: XCTestCase { XCTAssertEqual(output.encoded.hexString, "0200f885885d00008c0117010001f7ec6473df12e751d64cf20a8baa7edd50810f8101000100201d9a0000000000000000000000000000000000000000000000000000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120100010080969800000000000000000000000000000000000000000000000000000000000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae143c1d7e3463044022028019c0099e2233c7adb84bb03a9a5666ece4a5b65a026a090fa460f3679654702204df0fcb8762b5944b3aba033fa1a287ccb098150035dd8b66f52dc58d3d0843a") } + + func testTokenSign() { + let input = NULSSigningInput.with { + $0.privateKey = Data(hexString: "0x9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b")! + $0.from = "NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H" + $0.to = "NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe" + $0.amount = Data(hexString: "0x989680")! + $0.chainID = 9 + $0.idassetsID = 1 + $0.nonce = "0000000000000000".data(using: .utf8)! + $0.remark = "" + $0.balance = Data(hexString: "0x05f5e100")! + $0.timestamp = 1569228280 + $0.feePayer = "NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H" + $0.feePayerBalance = Data(hexString: "0x0f4240")! + $0.feePayerNonce = "0000000000000000".data(using: .utf8)! + + } + + let output: NULSSigningOutput = AnySigner.sign(input: input, coin: .nuls) + + XCTAssertEqual(output.encoded.hexString, "0200f885885d0000d20217010001f7ec6473df12e751d64cf20a8baa7edd50810f810900010080969800000000000000000000000000000000000000000000000000000000000800000000000000000017010001f7ec6473df12e751d64cf20a8baa7edd50810f8101000100a086010000000000000000000000000000000000000000000000000000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120900010080969800000000000000000000000000000000000000000000000000000000000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae143c1d7e346304402204629e7e39708468a405f8d8dd208d0133a686beb5d3ae829b7a2f5867c74480902203d0dffac8189b1caa9087e480fd57581e8c382cc4e17034b492dd2178dac851d") + } + + func testSignWithFeePayer() { + let input = NULSSigningInput.with { + $0.privateKey = Data(hexString: "0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002")! + $0.from = "NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac" + $0.to = "NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV" + $0.amount = Data(hexString: "0x0186a0")! + $0.chainID = 1 + $0.idassetsID = 1 + $0.nonce = "0000000000000000".data(using: .utf8)! + $0.remark = "" + $0.balance = Data(hexString: "0x0f4240")! + $0.timestamp = 1660632991 + $0.feePayer = "NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA" + $0.feePayerNonce = "0000000000000000".data(using: .utf8)! + $0.feePayerBalance = Data(hexString: "0x0f4240")! + $0.feePayerPrivateKey = Data(hexString: "0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6")! + } + + let output: NULSSigningOutput = AnySigner.sign(input: input, coin: .nuls) + + XCTAssertEqual(output.encoded.hexString, "02009f3ffb620000d202170100014f019a4227bff89d51590fbf53fbd98d994485f801000100a086010000000000000000000000000000000000000000000000000000000000080000000000000000001701000152a6388c8bf54e8fcd73cc824813bfef0912299b01000100a086010000000000000000000000000000000000000000000000000000000000080000000000000000000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b701000100a0860100000000000000000000000000000000000000000000000000000000000000000000000000d22102764370693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022040b5820b93fd5e272f2a00518af45a722571834934ba20d9a866de8e6d6409ab022003c610c647648444c1e2193634b2c51817a5a6ac3fe781da1a9ea773506afd8221025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff4630440220140e46b260942a8475f38df39bf54a2eea56c77199fc7ba775aa4b7f147d0d2102206c82705cba509f37ba0e35520a97bccb71a9e35cadcb8d95dd7fde5c8aa9e428") + } + + func testTokenSignWithFeePayer() { + let input = NULSSigningInput.with { + $0.privateKey = Data(hexString: "0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002")! + $0.from = "NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac" + $0.to = "NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV" + $0.amount = Data(hexString: "0x0186a0")! + $0.chainID = 9 + $0.idassetsID = 1 + $0.nonce = "0000000000000000".data(using: .utf8)! + $0.remark = "" + $0.balance = Data(hexString: "0x0dbba0")! + $0.timestamp = 1660636748 + $0.feePayer = "NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA" + $0.feePayerNonce = "e05d03df6ede0e22".data(using: .utf8)! + $0.feePayerBalance = Data(hexString: "0x0f4240")! + $0.feePayerPrivateKey = Data(hexString: "0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6")! + } + + let output: NULSSigningOutput = AnySigner.sign(input: input, coin: .nuls) + + XCTAssertEqual(output.encoded.hexString, "02004c4efb620000d2021701000152a6388c8bf54e8fcd73cc824813bfef0912299b09000100a08601000000000000000000000000000000000000000000000000000000000008000000000000000000170100014f019a4227bff89d51590fbf53fbd98d994485f801000100a08601000000000000000000000000000000000000000000000000000000000008e05d03df6ede0e22000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b709000100a0860100000000000000000000000000000000000000000000000000000000000000000000000000d32102764370693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022025cb5b40bda4e6fc0ba719bb0c1a907b2a0faa91316ef2c836310d49f906b6a802207d530a56a6c56d974036c86125da06d6e47f9d8ba1544ac3e620cebd883a707821025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff473045022100ff6f45a1c3856f9ea954baca6b2988295bbb22c958f87f0d3baf9989930549530220426ecb1520513710b99ab50e1f6c7e21b0175adef08aa05070bb9bfca8a001d8") + } } diff --git a/swift/Tests/Blockchains/NativeInjectiveTests.swift b/swift/Tests/Blockchains/NativeInjectiveTests.swift new file mode 100644 index 00000000000..f8686ff84bb --- /dev/null +++ b/swift/Tests/Blockchains/NativeInjectiveTests.swift @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class NativeInjectiveTests: XCTestCase { + + func testAddress() { + let key = PrivateKey(data: Data(hexString: "9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .nativeInjective) + let addressFromString = AnyAddress(string: "inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a", coin: .nativeInjective)! + + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSign() { + let privateKey = PrivateKey(data: Data(hexString: "9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .nativeInjective) + + let message = CosmosMessage.with { + $0.sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress.description + $0.toAddress = "inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd" + $0.amounts = [CosmosAmount.with { + $0.amount = "10000000000" + $0.denom = "inj" + }] + } + } + + let fee = CosmosFee.with { + $0.gas = 110000 + $0.amounts = [CosmosAmount.with { + $0.amount = "100000000000000" + $0.denom = "inj" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 17396 + $0.chainID = "injective-1" + $0.sequence = 1 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .nativeInjective) + + XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Co8BCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKKmluajEzdTZnN3ZxZ3cwNzRtZ21mMnplMmNhZHp2a3o5c25sd2NydHE4YRIqaW5qMXhtcGtteHI0YXMwMGVtMjN0YzJ6Z211eXkyZ3I0aDN3Z2NsNnZkGhIKA2luahILMTAwMDAwMDAwMDASngEKfgp0Ci0vaW5qZWN0aXZlLmNyeXB0by52MWJldGExLmV0aHNlY3AyNTZrMS5QdWJLZXkSQwpBBFoMa4O4vZgn5QcnDK20mbfjqQlSRvaiITKB94PYd8mLJWdCdBsGOfMXdo/k9MJ2JmDCESKDp2hdgVUH3uMikXMSBAoCCAEYARIcChYKA2luahIPMTAwMDAwMDAwMDAwMDAwELDbBhpAx2vkplmzeK7n3puCFGPWhLd0l/ZC/CYkGl+stH+3S3hiCvIe7uwwMpUlNaSwvT8HwF1kNUp+Sx2m0Uo1x5xcFw==\"}") + } +} diff --git a/swift/Tests/Blockchains/NativeZetaChainTests.swift b/swift/Tests/Blockchains/NativeZetaChainTests.swift new file mode 100644 index 00000000000..5c5a8650098 --- /dev/null +++ b/swift/Tests/Blockchains/NativeZetaChainTests.swift @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class NativeZetaChainTests: XCTestCase { + func testAddress() { + let key = PrivateKey(data: Data(hexString: "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .nativeZetaChain) + let addressFromString = AnyAddress(string: "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne", coin: .nativeZetaChain)! + + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSign() { + let privateKey = PrivateKey(data: Data(hexString: "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .nativeZetaChain) + + let message = CosmosMessage.with { + $0.sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress.description + $0.toAddress = "zeta1cscf4ldnkkz7f0wpveur6dpd0d6p2zxnsuu70y" + $0.amounts = [CosmosAmount.with { + $0.amount = "300000000000000000" + $0.denom = "azeta" + }] + } + } + + let fee = CosmosFee.with { + $0.gas = 200000 + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 2726346 + $0.chainID = "athens_7001-1" + $0.sequence = 2 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + $0.txHasher = CosmosTxHasher.keccak256 + $0.signerInfo = CosmosSignerInfo.with { + $0.publicKeyType = CosmosSignerPublicKeyType.secp256K1 + $0.jsonType = "ethermint/PubKeyEthSecp256k1" + $0.protobufType = "/ethermint.crypto.v1.ethsecp256k1.PubKey" + } + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .nativeZetaChain) + + XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpoBCpcBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEncKK3pldGExNHB5MzZzeDU3dWQ4MnQ5eXJrczl6Nmhkc3JwbjV4NmtteHMwbmUSK3pldGExY3NjZjRsZG5ra3o3ZjB3cHZldXI2ZHBkMGQ2cDJ6eG5zdXU3MHkaGwoFYXpldGESEjMwMDAwMDAwMDAwMDAwMDAwMBJhClkKTwooL2V0aGVybWludC5jcnlwdG8udjEuZXRoc2VjcDI1NmsxLlB1YktleRIjCiECho5+FjRBfbKt/Z/jggW/oP6gGJin/TBWXRP3BWo3wGUSBAoCCAEYAhIEEMCaDBpAgGvqca0w2N9wnHnnxS9HiVud4aQ9lNCumzgNIW6wOR4kvPScacGS1G3kwCw7wyI2NJL8M1eVYjafFIt2FpKl3w==\"}") + } +} diff --git a/swift/Tests/Blockchains/NervosTests.swift b/swift/Tests/Blockchains/NervosTests.swift new file mode 100644 index 00000000000..c1716b0775f --- /dev/null +++ b/swift/Tests/Blockchains/NervosTests.swift @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class NervosTests: XCTestCase { + + func testDerive() throws { + let wallet = HDWallet(mnemonic: "disorder wolf eager ladder fence renew dynamic idea metal camera bread obscure", passphrase: "")! + let address = wallet.getAddressForCoin(coin: .nervos) + + XCTAssertEqual(address, "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqga4k4agxexsd3zdq0wvrlyumfz7n5r7fsjxtnw8") + } + + func testAddress() throws { + + let key = PrivateKey(data: Data(hexString: "8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let address = AnyAddress(publicKey: pubkey, coin: .nervos) + let addressFromString = AnyAddress(string: "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras980hksatlslfaktks7epf25", coin: .nervos)! + + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSign() throws { + + let string = "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras980hksatlslfaktks7epf25" + let address = NervosAddress(string: string)! + let lockScript = NervosScript.with { + $0.codeHash = address.codeHash + $0.hashType = address.hashType + $0.args = address.args + } + + let input = NervosSigningInput.with { + $0.nativeTransfer = NervosNativeTransfer.with { + $0.toAddress = "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3" + $0.changeAddress = "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78yze6eyfyvd537z66ur22c9mmrgz82ama" + $0.amount = 10000000000 + } + $0.byteFee = 1 + $0.cell = [ + NervosCell.with { + $0.capacity = 100000000000 + $0.outPoint = NervosOutPoint.with { + $0.txHash = Data(hexString: "71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3")! + $0.index = 1 + } + $0.lock = lockScript + }, + NervosCell.with { + $0.capacity = 20000000000 + $0.outPoint = NervosOutPoint.with { + $0.txHash = Data(hexString: "71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3")! + $0.index = 0 + } + $0.lock = lockScript + } + ] + $0.privateKey = [ + Data(hexString: "8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb")! + ] + } + + let output: NervosSigningOutput = AnySigner.sign(input: input, coin: .nervos) + let json = """ + { + "cell_deps": [ + { + "dep_type": "dep_group", + "out_point": { + "index": "0x0", + "tx_hash": "0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c" + } + } + ], + "header_deps": [], + "inputs": [ + { + "previous_output": { + "index": "0x0", + "tx_hash": "0x71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3" + }, + "since": "0x0" + } + ], + "outputs": [ + { + "capacity": "0x2540be400", + "lock": { + "args": "0xab201f55b02f53b385f79b34dfad548e549b48fc", + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type" + }, + "type": null + }, + { + "capacity": "0x2540be230", + "lock": { + "args": "0xb0d65be39059d6489231b48f85ad706a560bbd8d", + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type" + }, + "type": null + } + ], + "outputs_data": ["0x", "0x"], + "version": "0x0", + "witnesses": [ + "0x55000000100000005500000055000000410000002a9ef2ad7829e5ea0c7a32735d29a0cb2ec20434f6fd5bf6e29cda56b28e08140156191cbbf80313d3c9cae4b74607acce7b28eb21d52ef058ed8491cdde70b700" + ] + } + """ + + XCTAssertEqual(output.transactionID, "0xf2c32afde7e72011985583873bc16c0a3c01fc01fc161eb4b914fcf84c53cdf8") + XCTAssertEqual(output.error, CommonSigningError.ok) + XCTAssertJSONEqual(output.transactionJson, json) + } +} diff --git a/swift/Tests/Blockchains/NimiqTests.swift b/swift/Tests/Blockchains/NimiqTests.swift index d056302a5e3..8ee8dac51ed 100644 --- a/swift/Tests/Blockchains/NimiqTests.swift +++ b/swift/Tests/Blockchains/NimiqTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Blockchains/OasisTests.swift b/swift/Tests/Blockchains/OasisTests.swift index fc9f4df1bd7..994d0aff619 100644 --- a/swift/Tests/Blockchains/OasisTests.swift +++ b/swift/Tests/Blockchains/OasisTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore @@ -36,6 +34,6 @@ class OasisTests: XCTestCase { let output: OasisSigningOutput = AnySigner.sign(input: input, coin: .oasis) - XCTAssertEqual(output.encoded.hexString, "a273756e747275737465645f7261775f76616c7565585ea4656e6f6e636500666d6574686f64707374616b696e672e5472616e7366657263666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680697369676e6174757265a26a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b697369676e61747572655840e331ce731ed819106586152b13cd98ecf3248a880bdc71174ee3d83f6d5f3f8ee8fc34c19b22032f2f1e3e06d382720125d7a517fba9295c813228cc2b63170b") + XCTAssertEqual(output.encoded.hexString, "a2697369676e6174757265a2697369676e617475726558406e51c18c9b2015c9b49414b3307336597f51ff331873d214ce2db81c9651a34d99529ccaa294a39ccd01c6b0bc2c2239d87c624e5ba4840cf99ac8f9283e240c6a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b73756e747275737465645f7261775f76616c7565585ea463666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680656e6f6e636500666d6574686f64707374616b696e672e5472616e73666572") } } diff --git a/swift/Tests/Blockchains/OntologyTests.swift b/swift/Tests/Blockchains/OntologyTests.swift index 3779918b0f7..a81c6744980 100644 --- a/swift/Tests/Blockchains/OntologyTests.swift +++ b/swift/Tests/Blockchains/OntologyTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest @@ -53,7 +51,11 @@ class OntologyTests: XCTestCase { let output: OntologySigningOutput = AnySigner.sign(input: input, coin: .ontology) let result = output.encoded.hexString + XCTAssertEqual("00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6ebb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb7cff28ddaf7f1048822c0ca21a0c4926323a2497875b963f3b8cbd3717aa6e7c2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", result) + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // XCTAssertEqual("00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6ebb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb8300d7215080efb87dd3f35de5f3b6d98aacd6161fbc0845b82d0d8be4b8b6d52321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", result) } func testSignOngTransfer() { @@ -72,7 +74,11 @@ class OntologyTests: XCTestCase { let output: OntologySigningOutput = AnySigner.sign(input: input, coin: .ontology) let result = output.encoded.hexString + XCTAssertEqual("00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efad62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f73b8ab199a4d757b4c7b9ed46c4ff8cfa8aefaa90b7fb6485e358034448cba752321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d743b078bd4e21bb4404c0182a32ee05260e22454dffb34dacccf458dfbee6d32db232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", result) + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // XCTAssertEqual("00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efad62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f8c4754e565b28a85b384612b93b00730143800049b97e83c95844a8eb7d66adc2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d74c4f8742a1de44bc0b3fe7d5cd11fad9edac2a5cdabe2c3b824743cc70df5f276232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", result) } } diff --git a/swift/Tests/Blockchains/OsmosisTests.swift b/swift/Tests/Blockchains/OsmosisTests.swift index bd7303dbe51..45fc92bc3e8 100644 --- a/swift/Tests/Blockchains/OsmosisTests.swift +++ b/swift/Tests/Blockchains/OsmosisTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest @@ -58,6 +56,6 @@ class OsmosisTests: XCTestCase { let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .osmosis) XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKK29zbW8xbWt5Njljbjhla3R3eTA4NDV2ZWM5dXBzZHBoa3R4dDBlbjk3ZjUSK29zbW8xOHMwaGRuc2xsZ2NjbHdldTlheW13NG5na3RyMmswcmt2bjdqbW4aDgoFdW9zbW8SBTk5ODAwEmQKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLs71zkN6MCxn+VRo3ksx826RH0Z9fmpStBweE+HVY2SRIECgIIARISCgwKBXVvc21vEgMyMDAQwJoMGkAMY//Md5GRUR4lVZhk558hFS3kii9QZYoYKfg4+ac/xgNeyoiEweVDhcmEvlH1orVwjLUOnYs4ly2a/yIurYVj\"}") - XCTAssertEqual(output.error, "") + XCTAssertEqual(output.errorMessage, "") } } diff --git a/swift/Tests/Blockchains/PolkadotTests.swift b/swift/Tests/Blockchains/PolkadotTests.swift index aae9c432a70..fe1d96c74d8 100644 --- a/swift/Tests/Blockchains/PolkadotTests.swift +++ b/swift/Tests/Blockchains/PolkadotTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest @@ -44,7 +42,7 @@ class PolkadotTests: XCTestCase { $0.blockHash = Data(hexString: "0x7d5fa17b70251d0806f26156b1b698dfd09e040642fa092595ce0a78e9e84fcd")! $0.nonce = 1 $0.specVersion = 28 - $0.network = .polkadot + $0.network = CoinType.polkadot.ss58Prefix $0.transactionVersion = 6 $0.privateKey = key.data $0.era = PolkadotEra.with { @@ -73,7 +71,7 @@ class PolkadotTests: XCTestCase { $0.blockHash = genesisHash $0.nonce = 0 $0.specVersion = 17 - $0.network = .polkadot + $0.network = CoinType.polkadot.ss58Prefix $0.transactionVersion = 3 $0.privateKey = key.data $0.stakingCall.bond = PolkadotStaking.Bond.with { @@ -97,7 +95,7 @@ class PolkadotTests: XCTestCase { $0.blockHash = genesisHash $0.nonce = 4 $0.specVersion = 30 - $0.network = .polkadot + $0.network = CoinType.polkadot.ss58Prefix $0.transactionVersion = 7 $0.privateKey = key.data $0.stakingCall.bondAndNominate = PolkadotStaking.BondAndNominate.with { @@ -125,7 +123,7 @@ class PolkadotTests: XCTestCase { $0.blockHash = genesisHash $0.nonce = 5 $0.specVersion = 30 - $0.network = .polkadot + $0.network = CoinType.polkadot.ss58Prefix $0.transactionVersion = 7 $0.privateKey = key.data $0.stakingCall.bondExtra = PolkadotStaking.BondExtra.with { @@ -151,7 +149,7 @@ class PolkadotTests: XCTestCase { } $0.nonce = 6 $0.specVersion = 9200 - $0.network = .polkadot + $0.network = CoinType.polkadot.ss58Prefix $0.transactionVersion = 12 $0.privateKey = key.data $0.stakingCall.chillAndUnbond = PolkadotStaking.ChillAndUnbond.with { @@ -163,4 +161,40 @@ class PolkadotTests: XCTestCase { // https://polkadot.subscan.io/extrinsic/10541383-2 XCTAssertEqual("0x" + output.encoded.hexString, "0xd10184008361bd08ddca5fda28b5e2aa84dc2621de566e23e089e555a42194c3eaf2da7900c891ba102db672e378945d74cf7f399226a76b43cab502436971599255451597fc2599902e4b62c7ce85ecc3f653c693fef3232be620984b5bb5bcecbbd7b209d50318001a02080706070207004d446617") } + + func testAcalaSigning() { + // real key in 1p test + let key = PrivateKey(data: Data(hexString: "9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0")!)! + + let acalaGenesisHash = Data(hexString: "0xfc41b9bd8ef8fe53d58c7ea67c794c7ec9a73daf05e6d54b14ff6342c99ba64c")! + + let input = PolkadotSigningInput.with { + $0.genesisHash = acalaGenesisHash + $0.blockHash = Data(hexString: "0x707ffa05b7dc6cdb6356bd8bd51ff20b2757c3214a76277516080a10f1bc7537")! + $0.era = PolkadotEra.with { + $0.blockNumber = 3893613 + $0.period = 64 + } + $0.nonce = 0 + $0.specVersion = 2170 + $0.network = 10 // Acala + $0.transactionVersion = 2 + $0.privateKey = key.data + $0.balanceCall.transfer = PolkadotBalance.Transfer.with { + $0.value = Data(hexString: "0xe8d4a51000")! // 1 ACA + $0.toAddress = "25Qqz3ARAvnZbahGZUzV3xpP1bB3eRrupEprK7f2FNbHbvsz" + $0.callIndices = PolkadotCallIndices.with { + $0.custom = PolkadotCustomCallIndices.with { + $0.moduleIndex = 0x0a + $0.methodIndex = 0x00 + } + } + } + $0.multiAddress = true + } + let output: PolkadotSigningOutput = AnySigner.sign(input: input, coin: .polkadot) + + // https://acala.subscan.io/extrinsic/3893620-3 + XCTAssertEqual("0x" + output.encoded.hexString, "0x41028400e9590e4d99264a14a85e21e69537e4a64f66a875d38cb8f76b305f41fabe24a900dd54466dffd1e3c80b76013e9459fbdcd17805bd5fdbca0961a643bad1cbd2b7fe005c62c51c18b67f31eb9e61b187a911952fee172ef18402d07c703eec3100d50200000a0000c8c602ded977c56076ae38d98026fa669ca10d6a2b5a0bfc4086ae7668ed1c60070010a5d4e8") + } } diff --git a/swift/Tests/Blockchains/PolygonTests.swift b/swift/Tests/Blockchains/PolygonTests.swift index cba7eb17b76..5c0e06fff09 100644 --- a/swift/Tests/Blockchains/PolygonTests.swift +++ b/swift/Tests/Blockchains/PolygonTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Blockchains/QtumTests.swift b/swift/Tests/Blockchains/QtumTests.swift index b6cd3394538..1c9fdaf3da1 100644 --- a/swift/Tests/Blockchains/QtumTests.swift +++ b/swift/Tests/Blockchains/QtumTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/Blockchains/RippleTests.swift b/swift/Tests/Blockchains/RippleTests.swift index e42151c04e3..7e5787d4f2f 100644 --- a/swift/Tests/Blockchains/RippleTests.swift +++ b/swift/Tests/Blockchains/RippleTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore @@ -29,25 +27,33 @@ class RippleTests: XCTestCase { } func testSigner() { - let input = RippleSigningInput.with { - $0.amount = 29_000_000 - $0.fee = 200_000 - $0.sequence = 1 // from account info api - $0.account = "rDpysuumkweqeC7XdNgYNtzL5GxbdsmrtF" + let operation = RippleOperationPayment.with { $0.destination = "rU893viamSnsfP3zjzM2KPxjqZjXSXK6VF" - $0.privateKey = Data(hexString: "ba005cd605d8a02e3d5dfd04234cef3a3ee4f76bfbad2722d1fb5af8e12e6764")! + $0.amount = 10 + } + let input = RippleSigningInput.with { + $0.fee = 10 + $0.sequence = 32268248 // from account info api + $0.lastLedgerSequence = 32268269 + $0.account = "rfxdLwsZnoespnTDDb1Xhvbc8EFNdztaoq" + $0.privateKey = Data(hexString: "a5576c0f63da10e584568c8d134569ff44017b0a249eb70657127ae04f38cc77")! + $0.opPayment = operation } let output: RippleSigningOutput = AnySigner.sign(input: input, coin: .xrp) - XCTAssertEqual(output.encoded.hexString, "12000022800000002400000001614000000001ba8140684000000000030d407321026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6744630440220067f20b3eebfc7107dd0bcc72337a236ac3be042c0469f2341d76694a17d4bb9022048393d7ee7dcb729783b33f5038939ddce1bb8337e66d752974626854556bbb681148400b6b6d08d5d495653d73eda6804c249a5148883148132e4e20aecf29090ac428a9c43f230a829220d") + XCTAssertEqual(output.encoded.hexString, "12000022000000002401ec5fd8201b01ec5fed61400000000000000a68400000000000000a732103d13e1152965a51a4a9fd9a8b4ea3dd82a4eba6b25fcad5f460a2342bb650333f74463044022037d32835c9394f39b2cfd4eaf5b0a80e0db397ace06630fa2b099ff73e425dbc02205288f780330b7a88a1980fa83c647b5908502ad7de9a44500c08f0750b0d9e8481144c55f5a78067206507580be7bb2686c8460adff983148132e4e20aecf29090ac428a9c43f230a829220d") - let input2 = RippleSigningInput.with { - $0.amount = 29_000_000 - $0.fee = 200_000 - $0.sequence = 1 // from account info api - $0.account = "rDpysuumkweqeC7XdNgYNtzL5GxbdsmrtF" + let operation2 = RippleOperationPayment.with { $0.destination = "XVfvixWZQKkcenFRYApCjpTUyJ4BePMjMaPqnob9QVPiVJV" - $0.privateKey = Data(hexString: "ba005cd605d8a02e3d5dfd04234cef3a3ee4f76bfbad2722d1fb5af8e12e6764")! + $0.amount = 10 + } + let input2 = RippleSigningInput.with { + $0.fee = 10 + $0.sequence = 32268248 // from account info api + $0.lastLedgerSequence = 32268269 + $0.account = "rfxdLwsZnoespnTDDb1Xhvbc8EFNdztaoq" + $0.privateKey = Data(hexString: "a5576c0f63da10e584568c8d134569ff44017b0a249eb70657127ae04f38cc77")! + $0.opPayment = operation2 } let output2: RippleSigningOutput = AnySigner.sign(input: input2, coin: .xrp) XCTAssertEqual(output2.encoded, output.encoded) diff --git a/swift/Tests/Blockchains/RoninTests.swift b/swift/Tests/Blockchains/RoninTests.swift index 2cb54acb389..fc8e102ca12 100644 --- a/swift/Tests/Blockchains/RoninTests.swift +++ b/swift/Tests/Blockchains/RoninTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/ScrollTests.swift b/swift/Tests/Blockchains/ScrollTests.swift new file mode 100644 index 00000000000..c890debc03e --- /dev/null +++ b/swift/Tests/Blockchains/ScrollTests.swift @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class ScrollTests: XCTestCase { + func testAddress() { + let key = PrivateKey(data: Data(hexString: "828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .scroll) + let expected = AnyAddress(string: "0xe32DC46bfBF78D1eada7b0a68C96903e01418D64", coin: .scroll)! + + XCTAssertEqual(address.description, expected.description) + } +} diff --git a/swift/Tests/Blockchains/SecretTests.swift b/swift/Tests/Blockchains/SecretTests.swift new file mode 100644 index 00000000000..2acf4b61247 --- /dev/null +++ b/swift/Tests/Blockchains/SecretTests.swift @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class SecretTests: XCTestCase { + func testAddress() { + let key = PrivateKey(data: Data(hexString: "87201512d132ef7a1e57f9e24905fbc24300bd73f676b5716182be5f3e39dada")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let address = AnyAddress(publicKey: pubkey, coin: .secret) + let addressFromString = AnyAddress(string: "secret18mdrja40gfuftt5yx6tgj0fn5lurplezyp894y", coin: .secret)! + + XCTAssertEqual(pubkey.data.hexString, "02466ac5d28cb4fab6c349060c6c1619e8d301e7741fb6b33cc1edac25f45d8646") + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSigningTransaction() { + let privateKey = PrivateKey(data: Data(hexString: "87201512d132ef7a1e57f9e24905fbc24300bd73f676b5716182be5f3e39dada")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .secret) + + let sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress.description + $0.toAddress = "secret1rnq6hjfnalxeef87rmdeya3nu9dhpc7k9pujs3" + $0.amounts = [CosmosAmount.with { + $0.amount = "100000" + $0.denom = "uscrt" + }] + } + + let message = CosmosMessage.with { + $0.sendCoinsMessage = sendCoinsMessage + } + + let fee = CosmosFee.with { + $0.gas = 25000 + $0.amounts = [CosmosAmount.with { + $0.amount = "2500" + $0.denom = "uscrt" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 265538 + $0.chainID = "secret-4" + $0.memo = "" + $0.sequence = 1 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .secret) + + XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLXNlY3JldDE4bWRyamE0MGdmdWZ0dDV5eDZ0Z2owZm41bHVycGxlenlwODk0eRItc2VjcmV0MXJucTZoamZuYWx4ZWVmODdybWRleWEzbnU5ZGhwYzdrOXB1anMzGg8KBXVzY3J0EgYxMDAwMDASZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAkZqxdKMtPq2w0kGDGwWGejTAed0H7azPMHtrCX0XYZGEgQKAggBGAESEwoNCgV1c2NydBIEMjUwMBCowwEaQOcHd2gHpa5WKZ/5RRerEtrHlyDlojIEzUGhC9xMFgs7UQMWy+kTTN+NRf7zQ8rx3cPkIKeZhv0u1KRc8uRCc4o=\"}") + XCTAssertEqual(output.errorMessage, "") + } +} diff --git a/swift/Tests/Blockchains/SmartBitcoinCashTests.swift b/swift/Tests/Blockchains/SmartBitcoinCashTests.swift index 131f13d14b6..5385549a3b8 100644 --- a/swift/Tests/Blockchains/SmartBitcoinCashTests.swift +++ b/swift/Tests/Blockchains/SmartBitcoinCashTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest @@ -10,12 +8,12 @@ import XCTest class SmartBitcoinCashTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "ab4accc9310d90a61fc354d8f353bca4a2b3c0590685d3eb82d0216af3badddc")!)! + let key = PrivateKey(data: Data(hexString: "155cbd57319f3d938977b4c18000473eb3c432c4e31b667b63e88559c497d82d")!)! let pubkey = key.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: pubkey, coin: .smartBitcoinCash) - let addressFromString = AnyAddress(string: "0xA3Dcd899C0f3832DFDFed9479a9d828c6A4EB2A7", coin: .smartBitcoinCash)! + let addressFromString = AnyAddress(string: "0x8bFC9477684987dcAf0970b9bce5E3D9267C99C0", coin: .smartBitcoinCash)! - XCTAssertEqual(pubkey.data.hexString, "0448a9ffac8022f1c7eb5253746e24d11d9b6b2737c0aecd48335feabb95a179916b1f3a97bed6740a85a2d11c663d38566acfb08af48a47ce0c835c65c9b23d0d") + XCTAssertEqual(pubkey.data.hexString, "046439f94100c802691c53ef18523be2c24d301f0e2bd3b425e832378a5405eff4331d5e57303785969073321fc76a8504a3854bdb21e6ab7b268a1737882a29c0") XCTAssertEqual(address.description, addressFromString.description) } diff --git a/swift/Tests/Blockchains/SolanaTests.swift b/swift/Tests/Blockchains/SolanaTests.swift index a0632941a0c..e813fe6c245 100644 --- a/swift/Tests/Blockchains/SolanaTests.swift +++ b/swift/Tests/Blockchains/SolanaTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore @@ -228,4 +226,68 @@ class SolanaTests: XCTestCase { XCTAssertEqual(result, "3p2kzZ1DvquqC6LApPuxpTg5CCDVPqJFokGSnGhnBHrta4uq7S2EyehV1XNUVXp51D69GxGzQZUjikfDzbWBG2aFtG3gHT1QfLzyFKHM4HQtMQMNXqay1NAeiiYZjNhx9UvMX4uAQZ4Q6rx6m2AYfQ7aoMUrejq298q1wBFdtS9XVB5QTiStnzC7zs97FUEK2T4XapjF1519EyFBViTfHpGpnf5bfizDzsW9kYUtRDW1UC2LgHr7npgq5W9TBmHf9hSmRgM9XXucjXLqubNWE7HUMhbKjuBqkirRM") } + + func testDecodeUpdateBlockhashAndSign() throws { + // https://explorer.solana.com/tx/3KbvREZUat76wgWMtnJfWbJL74Vzh4U2eabVJa3Z3bb2fPtW8AREP5pbmRwUrxZCESbTomWpL41PeKDcPGbojsej?cluster=devnet + let encodedTx = "AnQTYwZpkm3fs4SdLxnV6gQj3hSLsyacpxDdLMALYWObm722f79IfYFTbZeFK9xHtMumiDOWAM2hHQP4r/GtbARpncaXgOVFv7OgbRLMbuCEJHO1qwcdCbtH72VzyzU8yw9sqqHIAaCUE8xaQTgT6Z5IyZfeyMe2QGJIfOjz65UPAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8Aqe6sdLXiXSDILEtzckCjkjchiSf6zVGpMYiAE5BE2IqHAQUEAgQDAQoMoA8AAAAAAAAG" + let newBlockhash = "CyPYVsYWrsJNfVpi8aazu7WsrswNFuDd385z6GNoBGUg" + let encodedTxData = Base64.decode(string: encodedTx)! + + let senderPrivateKey = Data(hexString: "7f0932159226ddec9e1a4b0b8fe7cdc135049f9e549a867d722aa720dd64f32e")! + let feePayerPrivateKey = Data(hexString: "4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7")! + + // Step 1: Decode the transaction. + + let decodeOutputData = TransactionDecoder.decode(coinType: .solana, encodedTx: encodedTxData) + var decodeOutput = try SolanaDecodingTransactionOutput(serializedData: decodeOutputData) + + XCTAssertEqual(decodeOutput.error, .ok) + + // Step 2: Update recent blockhash. + + decodeOutput.transaction.legacy.recentBlockhash = newBlockhash + + // Step 3: Re-sign the updated transaction. + + let signingInput = SolanaSigningInput.with { + $0.privateKey = senderPrivateKey + $0.feePayerPrivateKey = feePayerPrivateKey + $0.rawMessage = decodeOutput.transaction + $0.txEncoding = .base64 + } + + let output: SolanaSigningOutput = AnySigner.sign(input: signingInput, coin: .solana) + XCTAssertEqual(output.error, .ok) + XCTAssertEqual(output.encoded, "Ajzc/Tke0CG8Cew5qFa6xZI/7Ya3DN0M8Ige6tKPsGzhg8Bw9DqL18KUrEZZ1F4YqZBo4Rv+FsDT8A7Nss7p4A6BNVZzzGprCJqYQeNg0EVIbmPc6mDitNniHXGeKgPZ6QZbM4FElw9O7IOFTpOBPvQFeqy0vZf/aayncL8EK/UEAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqbHiki6ThNH3auuyZPQpJntnN0mA//56nMpK/6HIuu8xAQUEAgQDAQoMoA8AAAAAAAAG") + } + + func testSignFromWalletConnectRequest() throws { + // Step 1: Parse a signing request received through WalletConnect. + + let requestPayload = """ + {"transaction":"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDZsL1CMnFVcrMn7JtiOiN1U4hC7WovOVof2DX51xM0H/GizyJTHgrBanCf8bGbrFNTn0x3pCGq30hKbywSTr6AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgIAAQwCAAAAKgAAAAAAAAA="} + """ + let parsingInput = WalletConnectParseRequestInput.with { + $0.method = .solanaSignTransaction + $0.payload = requestPayload + } + let parsingInputBytes = try parsingInput.serializedData() + + let parsingOutputBytes = WalletConnectRequest.parse(coin: .solana, input: parsingInputBytes) + let parsingOutput = try WalletConnectParseRequestOutput(serializedData: parsingOutputBytes) + + var signingInput = parsingOutput.solana + + // Step 2: Set missing fields. + + signingInput.privateKey = Base58.decodeNoCheck(string: "A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr")! + signingInput.txEncoding = .base64 + + // Step 3: Sign the transaction. + + let output: SolanaSigningOutput = AnySigner.sign(input: signingInput, coin: .solana) + + let expected = "AQPWaOi7dMdmQpXi8HyQQKwiqIftrg1igGQxGtZeT50ksn4wAnyH4DtDrkkuE0fqgx80LTp4LwNN9a440SrmoA8BAAEDZsL1CMnFVcrMn7JtiOiN1U4hC7WovOVof2DX51xM0H/GizyJTHgrBanCf8bGbrFNTn0x3pCGq30hKbywSTr6AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgIAAQwCAAAAKgAAAAAAAAA=" + XCTAssertEqual(output.encoded, expected) + } } diff --git a/swift/Tests/Blockchains/StargazeTests.swift b/swift/Tests/Blockchains/StargazeTests.swift new file mode 100644 index 00000000000..16bc5f5c3c0 --- /dev/null +++ b/swift/Tests/Blockchains/StargazeTests.swift @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class StargazeTests: XCTestCase { + func testAddress() { + let key = PrivateKey(data: Data(hexString: "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let address = AnyAddress(publicKey: pubkey, coin: .stargaze) + let addressFromString = AnyAddress(string: "stars1mry47pkga5tdswtluy0m8teslpalkdq02a8nhy", coin: .stargaze)! + + XCTAssertEqual(pubkey.data.hexString, "02cbfdb5e472893322294e60cf0883d43df431e1089d29ecb447a9e6d55045aae5") + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSignCW721() { + let privateKey = PrivateKey(data: Data(hexString: "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433")!)! + + let txMessage = "{\"transfer_nft\": {\"recipient\": \"stars1kd5q7qejlqz94kpmd9pvr4v2gzgnca3lvt6xnp\",\"token_id\": \"1209\"}}"; + let wasmMsg = CosmosMessage.WasmExecuteContractGeneric.with { + $0.contractAddress = "stars14gmjlyfz5mpv5d8zrksn0tjhz2wwvdc4yk06754alfasq9qen7fsknry42" + $0.senderAddress = "stars1mry47pkga5tdswtluy0m8teslpalkdq02a8nhy" + $0.executeMsg = txMessage + } + + let message = CosmosMessage.with { + $0.wasmExecuteContractGeneric = wasmMsg + } + + let fee = CosmosFee.with { + $0.gas = 666666 + $0.amounts = [CosmosAmount.with { + $0.amount = "1000" + $0.denom = "ustars" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 188393 + $0.chainID = "stargaze-1" + $0.memo = "" + $0.sequence = 5 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .stargaze) + + // Successfully broadcasted: https://www.mintscan.io/stargaze/txs/300836A5BF9002CF38EE34A8C56E8E7E6854FA64F1DEB3AE108F381A48150F7C + let expected = """ + { + "mode":"BROADCAST_MODE_BLOCK", + "tx_bytes":"CoACCv0BCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QS1AEKLHN0YXJzMW1yeTQ3cGtnYTV0ZHN3dGx1eTBtOHRlc2xwYWxrZHEwMmE4bmh5EkBzdGFyczE0Z21qbHlmejVtcHY1ZDh6cmtzbjB0amh6Mnd3dmRjNHlrMDY3NTRhbGZhc3E5cWVuN2Zza25yeTQyGmJ7InRyYW5zZmVyX25mdCI6IHsicmVjaXBpZW50IjogInN0YXJzMWtkNXE3cWVqbHF6OTRrcG1kOXB2cjR2Mmd6Z25jYTNsdnQ2eG5wIiwidG9rZW5faWQiOiAiMTIwOSJ9fRJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECy/215HKJMyIpTmDPCIPUPfQx4QidKey0R6nm1VBFquUSBAoCCAEYBRIUCg4KBnVzdGFycxIEMTAwMBCq2CgaQMx+l2sdM5DAPbDyY1p173MLnjGyNWIcRmaFiVNphLuTV3tjhwPbsXEA0hyRxyWS3vN0/xUF/JEsO9wRspj2aJ4=" + } + """; + XCTAssertJSONEqual(output.serialized, expected) + XCTAssertEqual(output.errorMessage, "") + } + + func testSign() { + let privateKey = PrivateKey(data: Data(hexString: "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .stargaze) + + let sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress.description + $0.toAddress = "stars1mry47pkga5tdswtluy0m8teslpalkdq02a8nhy" + $0.amounts = [CosmosAmount.with { + $0.amount = "10000" + $0.denom = "ustars" + }] + } + + let message = CosmosMessage.with { + $0.sendCoinsMessage = sendCoinsMessage + } + + let fee = CosmosFee.with { + $0.gas = 80000 + $0.amounts = [CosmosAmount.with { + $0.amount = "1000" + $0.denom = "ustars" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 188393 + $0.chainID = "stargaze-1" + $0.memo = "" + $0.sequence = 0 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .stargaze) + + // Successfully broadcasted: https://www.mintscan.io/stargaze/txs/98D5E36CA7080DDB286FE924A5A9976ABD4EBE49C92A09D322F29AD30DE4BE4D + let expected = """ + { + "mode":"BROADCAST_MODE_BLOCK", + "tx_bytes":"CpABCo0BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm0KLHN0YXJzMW1yeTQ3cGtnYTV0ZHN3dGx1eTBtOHRlc2xwYWxrZHEwMmE4bmh5EixzdGFyczFtcnk0N3BrZ2E1dGRzd3RsdXkwbTh0ZXNscGFsa2RxMDJhOG5oeRoPCgZ1c3RhcnMSBTEwMDAwEmYKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLL/bXkcokzIilOYM8Ig9Q99DHhCJ0p7LRHqebVUEWq5RIECgIIARIUCg4KBnVzdGFycxIEMTAwMBCA8QQaQHAkntxzC1oH7Yde4+KEmnB+K3XbJIYw0q6MqMPEY65YAwBDNDOdaTu/rpehus/20MvBfbAEZiw9+whzXLpkQ5A=" + } + """; + XCTAssertJSONEqual(output.serialized, expected) + XCTAssertEqual(output.errorMessage, "") + } +} diff --git a/swift/Tests/Blockchains/StarkExTests.swift b/swift/Tests/Blockchains/StarkExTests.swift new file mode 100644 index 00000000000..56dd47f5839 --- /dev/null +++ b/swift/Tests/Blockchains/StarkExTests.swift @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class StarkExTests: XCTestCase { + func testMessageAndVerifySigner() { + let privateKey = PrivateKey(data: Data(hexString: "04be51a04e718c202e4dca60c2b72958252024cfc1070c090dd0f170298249de")!)! + let msg = "463a2240432264a3aa71a5713f2a4e4c1b9e12bbb56083cd56af6d878217cf" + let signature = StarkExMessageSigner.signMessage(privateKey: privateKey, message: msg) + XCTAssertEqual(signature, "04cf5f21333dd189ada3c0f2a51430d733501a9b1d5e07905273c1938cfb261e05b6013d74adde403e8953743a338c8d414bb96bf69d2ca1a91a85ed2700a528") + let pubKey = privateKey.getPublicKeyByType(pubkeyType: .starkex) + XCTAssertTrue(StarkExMessageSigner.verifyMessage(pubKey: pubKey, message: msg, signature: signature)) + } +} diff --git a/swift/Tests/Blockchains/StellarTests.swift b/swift/Tests/Blockchains/StellarTests.swift index 9ff0754b748..159d1575fdb 100644 --- a/swift/Tests/Blockchains/StellarTests.swift +++ b/swift/Tests/Blockchains/StellarTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/SuiTests.swift b/swift/Tests/Blockchains/SuiTests.swift new file mode 100644 index 00000000000..868cb6947ec --- /dev/null +++ b/swift/Tests/Blockchains/SuiTests.swift @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class SuiTests: XCTestCase { + func testAddress() { + let anyAddress = AnyAddress(string: "0x259ff8074ab425cbb489f236e18e08f03f1a7856bdf7c7a2877bd64f738b5015", coin: .sui) + + XCTAssertEqual(anyAddress?.description, "0x259ff8074ab425cbb489f236e18e08f03f1a7856bdf7c7a2877bd64f738b5015") + XCTAssertEqual(anyAddress?.coin, .sui) + + let invalid = "MQqpqMQgCBuiPkoXfgZZsJvuzCeI1zc00z6vHJj4" + XCTAssertNil(Data(hexString: invalid)) + XCTAssertNil(AnyAddress(string: invalid, coin: .sui)) + XCTAssertFalse(AnyAddress.isValid(string: invalid, coin: .sui)) + } + + func testSignDirect() { + // Successfully broadcasted: https://suiscan.xyz/mainnet/tx/D4Ay9TdBJjXkGmrZSstZakpEWskEQHaWURP6xWPRXbAm + let privateKeyData = Data(hexString: "7e6682f7bf479ef0f627823cffd4e1a940a7af33e5fb39d9e0f631d2ecc5daff")! + let txBytes = """ +AAAEAAjoAwAAAAAAAAAIUMMAAAAAAAAAIKcXWr3V7ZLr4605DbNmxqcGR4zfUXzebPmGMAZc2jd6ACBU6A1215DCd/WkTzzpL1PSb1iUiSvzld7mN1mIh2vmsgMCAAIBAAABAQABAQMAAAAAAQIAAQEDAAABAAEDAFToDXbXkMJ39aRPPOkvU9JvWJSJK/OV3uY3WYiHa+ayAWNgILOn3HsRw6pvQZsX+KnBLn95ox0b3S3mcLTt1jAFeHEaBQAAAAAgGGuNnxrqusosgjP3gQ3jBjnhapGNBlcU0yTaupXpa0BU6A1215DCd/WkTzzpL1PSb1iUiSvzld7mN1mIh2vmsu4CAAAAAAAAwMYtAAAAAAAA +""" + + let input = SuiSigningInput.with { + $0.paySui = SuiPaySui.with { + $0.inputCoins = [SuiObjectRef.with { + $0.objectID = "0x636020b3a7dc7b11c3aa6f419b17f8a9c12e7f79a31d1bdd2de670b4edd63005" + $0.version = 85619064 + $0.objectDigest = "2eKuWbZSVfpFVfg8FXY9wP6W5AFXnTchSoUdp7obyYZ5" + }] + $0.recipients = [ + "0xa7175abdd5ed92ebe3ad390db366c6a706478cdf517cde6cf98630065cda377a", + "0x54e80d76d790c277f5a44f3ce92f53d26f5894892bf395dee6375988876be6b2" + ] + $0.amounts = [1000, 50000] + } + $0.privateKey = privateKeyData + // 0.003 SUI + $0.gasBudget = 3000000 + $0.referenceGasPrice = 750 + } + let output: SuiSigningOutput = AnySigner.sign(input: input, coin: .sui) + XCTAssertEqual(output.unsignedTx, txBytes) + let expectedSignature = "AEh44B7iGArEHF1wOLAQJMLNgGnaIwn3gKPC92vtDJqITDETAM5z9plaxio1xomt6/cZReQ5FZaQsMC6l7E0BwmF69FEH+T5VPvl3GB3vwCOEZpeJpKXxvcIPQAdKsh2/g==" + XCTAssertEqual(output.signature, expectedSignature) + } + + func testTransferSui() { + // Successfully broadcasted https://explorer.sui.io/txblock/HkPo6rYPyDY53x1MBszvSZVZyixVN7CHvCJGX381czAh?network=devnet + let privateKeyData = Data(hexString: "3823dce5288ab55dd1c00d97e91933c613417fdb282a0b8b01a7f5f5a533b266")! + let txBytes = """ +AAACAAgQJwAAAAAAAAAgJZ/4B0q0Jcu0ifI24Y4I8D8aeFa998eih3vWT3OLUBUCAgABAQAAAQEDAAAAAAEBANV1rX8Y6UhGKlz2mPVk7zlKdSpx/sYkk6+KBVwBLA1QAQbywsjB2JZN8QGdZhbpcFcZvrq9kx2idVy5SM635olk7AIAAAAAAAAgYEVuxmf1zRBGdoDr+VDtMpIFF12s2Ua7I2ru1XyGF8/Vda1/GOlIRipc9pj1ZO85SnUqcf7GJJOvigVcASwNUAEAAAAAAAAA0AcAAAAAAAAA +""" + let input = SuiSigningInput.with { + $0.signDirectMessage = SuiSignDirect.with { + $0.unsignedTxMsg = txBytes + } + $0.privateKey = privateKeyData + } + let output: SuiSigningOutput = AnySigner.sign(input: input, coin: .sui) + XCTAssertEqual(output.unsignedTx, txBytes) + let expectedSignature = "APxPduNVvHj2CcRcHOtiP2aBR9qP3vO2Cb0g12PI64QofDB6ks33oqe/i/iCTLcop2rBrkczwrayZuJOdi7gvwNqfN7sFqdcD/Z4e8I1YQlGkDMCK7EOgmydRDqfH8C9jg==" + XCTAssertEqual(output.signature, expectedSignature) + } +} diff --git a/swift/Tests/Blockchains/SyscoinTests.swift b/swift/Tests/Blockchains/SyscoinTests.swift new file mode 100644 index 00000000000..9668e7de5de --- /dev/null +++ b/swift/Tests/Blockchains/SyscoinTests.swift @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class SyscoinTests: XCTestCase { + func testAddress() { + let privateKey1 = PrivateKey(data: Data(hexString: "a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730")!)! + let publicKey1 = privateKey1.getPublicKeySecp256k1(compressed: true) + + let legacyAddress = BitcoinAddress(publicKey: publicKey1, prefix: CoinType.syscoin.p2pkhPrefix)! + XCTAssertEqual(BitcoinAddress(string: "SXBPFk2PFDAP13qyKSdC4yptsJ8kJRAT3T")!.description, legacyAddress.description) + + let privateKey2 = PrivateKey(data: Data(hexString: "f6ee7e6c9bd2f4dc8f0db0dc4679de06c998afc42d825edf7966dd4488b0aa1f")!)! + let publicKey2 = privateKey2.getPublicKeySecp256k1(compressed: true) + let compatibleAddress = BitcoinAddress.compatibleAddress(publicKey: publicKey2, prefix: CoinType.syscoin.p2shPrefix) + XCTAssertEqual(BitcoinAddress(string: "32SKP7HqJLPRNEUNUDddNAXCxyMinjbX8g")!.description, compatibleAddress.description) + + let privateKey3 = PrivateKey(data: Data(hexString: "55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625")!)! + let publicKey3 = privateKey3.getPublicKeySecp256k1(compressed: true) + let bech32Address = SegwitAddress(hrp: .syscoin, publicKey: publicKey3) + XCTAssertEqual(SegwitAddress(string: "sys1qytnqzjknvv03jwfgrsmzt0ycmwqgl0as7lkcf7")!.description, bech32Address.description) + } + + func testSyscoinBlockchain() { + let chain = CoinType.syscoin + XCTAssertTrue(chain.validate(address: "sys1qytnqzjknvv03jwfgrsmzt0ycmwqgl0as7lkcf7")) + XCTAssertTrue(chain.validate(address: "SXBPFk2PFDAP13qyKSdC4yptsJ8kJRAT3T")) + XCTAssertTrue(chain.validate(address: "32SKP7HqJLPRNEUNUDddNAXCxyMinjbX8g")) + XCTAssertFalse(chain.validate(address: "Xm1iDLBP5tdxTxc6t7uJBCVjC4L2A5vB2J")) + XCTAssertFalse(chain.validate(address: "bitcoincash:qq07l6rr5lsdm3m80qxw80ku2ex0tj76vvsxpvmgme")) + XCTAssertFalse(chain.validate(address: "bc1qvtvte5tzlqlfhcdmph94lxk8jcz54q6psyvgla")) + } + + func testExtendedKeys() { + let wallet = HDWallet.test + + // .bip44 + let xprv = wallet.getExtendedPrivateKey(purpose: .bip44, coin: .syscoin, version: .xprv) + let xpub = wallet.getExtendedPublicKey(purpose: .bip44, coin: .syscoin, version: .xpub) + + XCTAssertEqual(xprv, "xprv9yFNgN7z81uG6QtwFt7gvbmLeDGeGfS2ar3DunwEkZcC7uLBXyy4eaaV3ir769zMLe3eHuTaGUtWVXwp6dkunLsfmA7bf3XqEFpTjHxSijx") + XCTAssertEqual(xpub, "xpub6CEj5sesxPTZJtyQMuehHji5CF78g89sx4xpiBLrJu9AzhfL5XHKCNtxtzPzyGxqb6jMbZfmbHeSGZZArL4hLttmdC6KEMuiWy7VocTYjzR") + + // .bip49 + let yprv = wallet.getExtendedPrivateKey(purpose: .bip49, coin: .syscoin, version: .yprv) + let ypub = wallet.getExtendedPublicKey(purpose: .bip49, coin: .syscoin, version: .ypub) + XCTAssertEqual(yprv, "yprvAJAofBFEEQ1DLJJVMkPr4pufHLUKZ2VSbtHqPpphEgwgfvG8exgadM8vtW8AW52N7tqU4qM8JHk5xZkq3icnzoph5QA5kRVHBnhXuRMGw2b") + XCTAssertEqual(ypub, "ypub6XAA4gn84mZWYnNxTmvrRxrPqNJoxVDHy7DSCDEJo2UfYibHCVzqB9TQjmL2TKSEZVFmTNcmdJXunEu6oV2AFQNeiszjzcnX4nbG27s4SgS") + + // .bip84 + let zprv = wallet.getExtendedPrivateKey(purpose: .bip84, coin: .syscoin, version: .zprv) + let zpub = wallet.getExtendedPublicKey(purpose: .bip84, coin: .syscoin, version: .zpub) + XCTAssertEqual(zprv, "zprvAcdCiLx9ooAFnC1hXh7stnobLnnu7u25rqfLeJ9v632xdCXJrc8KvgNk2eZeQQbPQHvcUpsfJzgyDkRdfnkT6vjpYqkxFv1LsPxQ7uFwLGy") + XCTAssertEqual(zpub, "zpub6qcZ7rV3eAiYzg6AdietFvkKtpdPXMjwE4awSgZXeNZwVzrTQ9SaUUhDswmdA4A5riyimx322es7niQvJ1Fbi3mJNSVz3d3f9GBsYBb8Wky") + } + + func testDeriveFromXpub() { + let xpub = "xpub6CEj5sesxPTZJtyQMuehHji5CF78g89sx4xpiBLrJu9AzhfL5XHKCNtxtzPzyGxqb6jMbZfmbHeSGZZArL4hLttmdC6KEMuiWy7VocTYjzR" + let syscoin = CoinType.syscoin + let xpubAddr2 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: syscoin, + derivationPath: DerivationPath(purpose: .bip44, coin: syscoin.slip44Id, account: 0, change: 0, address: 2).description + )! + let xpubAddr9 = HDWallet.getPublicKeyFromExtended( + extended: xpub, + coin: syscoin, + derivationPath: DerivationPath(purpose: .bip44, coin: syscoin.slip44Id, account: 0, change: 0, address: 9).description + )! + + XCTAssertEqual(BitcoinAddress(publicKey: xpubAddr2, prefix: CoinType.syscoin.p2pkhPrefix)!.description, "SkXxaA1GQu6D49qxrjSMCgsybWVsWMZb32") + XCTAssertEqual(BitcoinAddress(publicKey: xpubAddr9, prefix: CoinType.syscoin.p2pkhPrefix)!.description, "ST8SwH6wp6qx6RnQcnUfTJCHznDPsdatzC") + } + + func testDeriveFromYpub() { + let ypub = "ypub6XAA4gn84mZWYnNxTmvrRxrPqNJoxVDHy7DSCDEJo2UfYibHCVzqB9TQjmL2TKSEZVFmTNcmdJXunEu6oV2AFQNeiszjzcnX4nbG27s4SgS" + + let syscoin = CoinType.syscoin + let ypubAddr3 = HDWallet.getPublicKeyFromExtended( + extended: ypub, + coin: syscoin, + derivationPath: DerivationPath(purpose: .bip49, coin: syscoin.slip44Id, account: 0, change: 0, address: 3).description + )! + let ypubAddr10 = HDWallet.getPublicKeyFromExtended( + extended: ypub, + coin: syscoin, + derivationPath: DerivationPath(purpose: .bip49, coin: syscoin.slip44Id, account: 0, change: 0, address: 10).description + )! + + XCTAssertEqual(BitcoinAddress.compatibleAddress(publicKey: ypubAddr3, prefix: CoinType.syscoin.p2shPrefix).description, "31hP9bQFV1iX49yGaaz2ZwoxXzqpPx2vbk") + XCTAssertEqual(BitcoinAddress.compatibleAddress(publicKey: ypubAddr10, prefix: CoinType.syscoin.p2shPrefix).description, "3FhBf4MUNWFiMz3RTbpKb7eie4WHhErSs5") + } + + func testDeriveFromZPub() { + let zpub = "zpub6qcZ7rV3eAiYzg6AdietFvkKtpdPXMjwE4awSgZXeNZwVzrTQ9SaUUhDswmdA4A5riyimx322es7niQvJ1Fbi3mJNSVz3d3f9GBsYBb8Wky" + let syscoin = CoinType.syscoin + let zpubAddr4 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: syscoin, + derivationPath: DerivationPath(purpose: .bip49, coin: syscoin.slip44Id, account: 0, change: 0, address: 4).description + )! + let zpubAddr11 = HDWallet.getPublicKeyFromExtended( + extended: zpub, + coin: syscoin, + derivationPath: DerivationPath(purpose: .bip49, coin: syscoin.slip44Id, account: 0, change: 0, address: 11).description + )! + + XCTAssertEqual(SegwitAddress(hrp: .syscoin, publicKey: zpubAddr4).description, "sys1q54ylw7uztxagxq5hz93cmdrawthhrd00knkp23") + XCTAssertEqual(SegwitAddress(hrp: .syscoin, publicKey: zpubAddr11).description, "sys1q7yzsja5wtkswdc6rleupsvzny3gnyhye0qvdda") + } + + func testPlanAndSign_8435() throws { + let address = "sys1qytnqzjknvv03jwfgrsmzt0ycmwqgl0as7lkcf7" + let lockScript = BitcoinScript.lockScriptForAddress(address: address, coin: .syscoin) + let utxos = [ + BitcoinUnspentTransaction.with { + $0.outPoint.hash = Data.reverse(hexString: "a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407") + $0.outPoint.index = 0 + $0.outPoint.sequence = UINT32_MAX + $0.script = lockScript.data + $0.amount = 3899774 + } + ] + + // redeem p2pwkh + let scriptHash = lockScript.matchPayToWitnessPublicKeyHash()! + var input = BitcoinSigningInput.with { + $0.toAddress = "sys1q54ylw7uztxagxq5hz93cmdrawthhrd00knkp23" + $0.changeAddress = address + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .syscoin) + $0.amount = 1200000 + $0.coinType = CoinType.syscoin.rawValue + $0.byteFee = 1 + $0.utxo = utxos + $0.useMaxAmount = false + $0.scripts = [ + scriptHash.hexString: BitcoinScript.buildPayToPublicKeyHash(hash: scriptHash).data + ] + } + + // Plan + let plan: BitcoinTransactionPlan = AnySigner.plan(input: input, coin: .syscoin) + + XCTAssertEqual(plan.amount, 1200000) + XCTAssertEqual(plan.fee, 141) + XCTAssertEqual(plan.change, 2699633) + + // Extend input with private key + input.privateKey = [Data(hexString: "55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625")!] + input.plan = plan + + // Sign + let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .syscoin) + XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) + + let txId = output.transactionID + XCTAssertEqual(txId, "cb92bae012ebdd88b720198e40d470746d1af2e8b9b875bb763c831341cb2ded") + + let encoded = output.encoded + XCTAssertEqual(encoded.hexString, + "0100000000010107c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa80000000000ffffffff02804f120000000000160014a549f77b8259ba83029711638db47d72ef71b5ef713129000000000016001422e6014ad3631f1939281c3625bc98db808fbfb00247304402201fff942424755a4ecc84c916c3045c73efab03f9e13e55b27a6ecf6d2027d88602205e54d2fd728e0cfedeecb987dcb346e6e14c5b24ffb26d3db543d90f6571f7080121025cf26d221b01ca4d6040893b96f1dabfd2a108d449b3fa62854421f98a42562b00000000" + ) + } +} diff --git a/swift/Tests/Blockchains/THORChainSwapTests.swift b/swift/Tests/Blockchains/THORChainSwapTests.swift index cb51dd73711..b13699a857b 100644 --- a/swift/Tests/Blockchains/THORChainSwapTests.swift +++ b/swift/Tests/Blockchains/THORChainSwapTests.swift @@ -1,18 +1,18 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore class THORSwapTests: XCTestCase { - func testSignerEthBnb() throws { + func testSignerEthBnbWithFee() throws { // prepare swap input let input = THORChainSwapSwapInput.with { - $0.fromChain = .eth + $0.fromAsset = THORChainSwapAsset.with { + $0.chain = .eth + } $0.fromAddress = "0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7" $0.toAsset = THORChainSwapAsset.with { $0.chain = .bnb @@ -24,15 +24,17 @@ class THORSwapTests: XCTestCase { $0.routerAddress = "0x42A5Ed456650a09Dc10EBc6361A7480fDd61f27B" $0.fromAmount = "50000000000000000" $0.toAmountLimit = "600003" + $0.affiliateFeeAddress = "tthor1ql2tcqyrqsgnql2tcqyj2n8kfdmt9lh0yzql2tcqy" + $0.affiliateFeeRateBp = "10" } // serialize input let inputSerialized = try input.serializedData() - XCTAssertEqual(inputSerialized.hexString, "0802122a3078623966353737316332373636346266323238326439386530396437663530636563376362303161371a0708031203424e42222a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372782a2a307831303931633444653661336346303943644130304162444165443432633763334236394338334543322a3078343241354564343536363530613039446331304542633633363141373438306644643631663237423a1135303030303030303030303030303030304206363030303033") + XCTAssertEqual(inputSerialized.hexString, "0a020802122a3078623966353737316332373636346266323238326439386530396437663530636563376362303161371a0708031203424e42222a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372782a2a307831303931633444653661336346303943644130304162444165443432633763334236394338334543322a3078343241354564343536363530613039446331304542633633363141373438306644643631663237423a11353030303030303030303030303030303042063630303030334a2f7474686f7231716c3274637179727173676e716c32746371796a326e386b66646d74396c6830797a716c327463717952023130") // invoke swap let outputData = THORChainSwap.buildSwap(input: inputSerialized) - XCTAssertEqual(outputData.count, 311) + XCTAssertEqual(outputData.count, 192) // parse result in proto let outputProto = try THORChainSwapSwapOutput(serializedData: outputData) @@ -51,13 +53,15 @@ class THORSwapTests: XCTestCase { // sign and encode resulting input let output: EthereumSigningOutput = AnySigner.sign(input: txInput, coin: .ethereum) - XCTAssertEqual(output.encoded.hexString, "f90151038506fc23ac00830138809442a5ed456650a09dc10ebc6361a7480fdd61f27b87b1a2bc2ec50000b8e41fece7b40000000000000000000000001091c4de6a3cf09cda00abdaed42c7c3b69c83ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000003e535741503a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a363030303033000025a06ae104be3201baca38315352f81fac70ca4dd47339981914e64e91149813e780a066a3f0b2c44ddf5a96a38481274f623f552a593d723237d6742185f4885c0064") + XCTAssertEqual(output.encoded.hexString, "02f8d90103808083013880941091c4de6a3cf09cda00abdaed42c7c3b69c83ec87b1a2bc2ec50000b86e3d3a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a3630303030333a7474686f7231716c3274637179727173676e716c32746371796a326e386b66646d74396c6830797a716c32746371793a3130c001a05c16871b66fd0fa8f658d6f171310bab332d09e0533d6c97329a59ddc93a9a11a05ed2be94e6dbb640e58920c8be4fa597cd5f0a918123245acb899042dd43777f") } func testSignerBnbBtc() throws { // prepare swap input let input = THORChainSwapSwapInput.with { - $0.fromChain = .bnb + $0.fromAsset = THORChainSwapAsset.with { + $0.chain = .bnb + } $0.fromAddress = "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx" $0.toAsset = THORChainSwapAsset.with { $0.chain = .btc @@ -73,11 +77,11 @@ class THORSwapTests: XCTestCase { // serialize input let inputSerialized = try input.serializedData() - XCTAssertEqual(inputSerialized.hexString, "0803122a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372781a0708011203425443222a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070382a2a626e62316e396573787577386361377473386c367736366b64683830307330396d7376756c36766c73653a08313030303030303042083130303030303030") + XCTAssertEqual(inputSerialized.hexString, "0a020803122a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372781a0708011203425443222a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070382a2a626e62316e396573787577386361377473386c367736366b64683830307330396d7376756c36766c73653a08313030303030303042083130303030303030") // invoke swap let outputData = THORChainSwap.buildSwap(input: inputSerialized) - XCTAssertEqual(outputData.count, 149) + XCTAssertEqual(outputData.count, 146) // parse result in proto let outputProto = try THORChainSwapSwapOutput(serializedData: outputData) @@ -94,6 +98,6 @@ class THORSwapTests: XCTestCase { // sign and encode resulting input let output: BinanceSigningOutput = AnySigner.sign(input: txInput, coin: .binance) - XCTAssertEqual(output.encoded.hexString, "8002f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e421080ade204126a0a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e1912404836ee8659caa86771281d3f104424d95977bdedf644ec8585f1674796fde525669a6d446f72da89ee90fb0e064473b0a2159a79630e081592c52948d03d67071a40535741503a4254432e4254433a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070383a3130303030303030") + XCTAssertEqual(output.encoded.hexString, "fd01f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e421080ade204126a0a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e19124008455a84c90e73981ae098578d2ab2b498fe17b0436723c596501b9236d96697514467ed4c22ba8f1cb7506172b368a2ca8be0eb82cb93b5320f938209041f2c1a3d3d3a4254432e4254433a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070383a3130303030303030") } } diff --git a/swift/Tests/Blockchains/THORChainTests.swift b/swift/Tests/Blockchains/THORChainTests.swift index 45d9cb3ac80..a0ae0db1d45 100644 --- a/swift/Tests/Blockchains/THORChainTests.swift +++ b/swift/Tests/Blockchains/THORChainTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/TerraClassicTests.swift b/swift/Tests/Blockchains/TerraClassicTests.swift new file mode 100644 index 00000000000..66ab6f11816 --- /dev/null +++ b/swift/Tests/Blockchains/TerraClassicTests.swift @@ -0,0 +1,603 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class TerraClassicTests: XCTestCase { + + let privateKey = PrivateKey(data: Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")!)! + + func testAddress() { + let address = CoinType.terra.deriveAddress(privateKey: privateKey) + + XCTAssertEqual(address, "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe") + XCTAssertTrue(CoinType.terra.validate(address: "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms")) + XCTAssertTrue(CoinType.terra.validate(address: "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk")) + XCTAssertFalse(CoinType.terra.validate(address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")) + } + + func testRawJSON() { + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra).description + + let message = CosmosMessage.with { + $0.rawJsonMessage = CosmosMessage.RawJSON.with { + $0.type = "bank/MsgSend" + $0.value = """ + { + "amount": [{ + "amount": "1000000", + "denom": "uluna" + }], + "from_address": "\(fromAddress)", + "to_address": "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms" + } + """ + } + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "3000" + $0.denom = "uluna" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 158 + $0.chainID = "soju-0013" + $0.memo = "" + $0.sequence = 0 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + let expectedJSON: String = +""" +{ + "mode": "block", + "tx": { + "msg": [{ + "type": "bank/MsgSend", + "value": { + "from_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", + "to_address": "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms", + "amount": [{ + "denom": "uluna", + "amount": "1000000" + }] + } + }], + "fee": { + "amount": [{ + "denom": "uluna", + "amount": "3000" + }], + "gas": "200000" + }, + "signatures": [{ + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "KPdiVsKpY12JG/VKEJVa/FpMKclxlS0qNNG6VOAypj10R5vY5UX5IgRJET1zNYnH0wvcXxfNXV+s8jtwN2UXiQ==" + }], + "memo": "" + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testSigningTransaction() { + // https://finder.terra.money/soju-0013/tx/1403B07F2D218BCE961CB92D83377A924FEDB54C1F0B62E25C8B93B63470EBF7 + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra).description + + let message = CosmosMessage.with { + $0.sendCoinsMessage = CosmosMessage.Send.with { + $0.fromAddress = fromAddress + $0.toAddress = "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms" + $0.amounts = [CosmosAmount.with { + $0.amount = "1000000" + $0.denom = "uluna" + }] + $0.typePrefix = "bank/MsgSend" + } + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "3000" + $0.denom = "uluna" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 158 + $0.chainID = "soju-0013" + $0.memo = "" + $0.sequence = 0 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + let expectedJSON: String = +""" +{ + "mode": "block", + "tx": { + "msg": [{ + "type": "bank/MsgSend", + "value": { + "from_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", + "to_address": "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms", + "amount": [{ + "denom": "uluna", + "amount": "1000000" + }] + } + }], + "fee": { + "amount": [{ + "denom": "uluna", + "amount": "3000" + }], + "gas": "200000" + }, + "signatures": [{ + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "KPdiVsKpY12JG/VKEJVa/FpMKclxlS0qNNG6VOAypj10R5vY5UX5IgRJET1zNYnH0wvcXxfNXV+s8jtwN2UXiQ==" + }], + "memo": "" + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testStaking() { + // https://finder.terra.money/soju-0013/tx/4C0A6690ECB601ACB42D3ECAF4C24C0555B5E32E45B09C3B1607B144CD191F87 + let stakeMessage = CosmosMessage.Delegate.with { + $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" + $0.validatorAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + $0.amount = CosmosAmount.with { + $0.amount = "1000000" + $0.denom = "uluna" + } + $0.typePrefix = "staking/MsgDelegate" + } + + let message = CosmosMessage.with { + $0.stakeMessage = stakeMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "3000" + $0.denom = "uluna" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 158 + $0.chainID = "soju-0013" + $0.memo = "" + $0.sequence = 1 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + let expectedJSON = """ +{ + "mode": "block", + "tx": { + "fee": { + "amount": [{ + "amount": "3000", + "denom": "uluna" + }], + "gas": "200000" + }, + "memo": "", + "msg": [{ + "type": "staking/MsgDelegate", + "value": { + "amount": { + "amount": "1000000", + "denom": "uluna" + }, + "delegator_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", + "validator_address": "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + } + }], + "signatures": [{ + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "F8UJxbkqa0j6dYTk8PymrudBKI3WYhZImRxMFCw0ecFCmPGgNTg7yfpKZo6K6JtnoJaP7bQ4db5e4wnhMCJyAQ==" + }] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testWithdraw() { + // https://finder.terra.money/soju-0013/tx/AE0E4F2B254449950A3A7F41FABCE0B3C846D70F809380313CE3BB323E490BBD + let withdrawMessage = CosmosMessage.WithdrawDelegationReward.with { + $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" + $0.validatorAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + $0.typePrefix = "distribution/MsgWithdrawDelegationReward" + } + + let message = CosmosMessage.with { + $0.withdrawStakeRewardMessage = withdrawMessage + } + + let fee = CosmosFee.with { + $0.amounts = [CosmosAmount.with { + $0.amount = "3000" + $0.denom = "uluna" + }] + $0.gas = 200000 + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 158 + $0.chainID = "soju-0013" + $0.memo = "" + $0.sequence = 2 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + let expectedJSON = """ +{ + "mode": "block", + "tx": { + "fee": { + "amount": [{ + "amount": "3000", + "denom": "uluna" + }], + "gas": "200000" + }, + "memo": "", + "msg": [{ + "type": "distribution/MsgWithdrawDelegationReward", + "value": { + "delegator_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", + "validator_address": "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + } + }], + "signatures": [{ + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "Kfwi1uJplzLucXDyQZsJI9v8lMFJFUBLD46+MpwBwYwPJgqPRzSOfyjRpmNou0G/Qe1hbsGEgqb85FQpsgLz+g==" + }] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testUndelegate() { + // https://finder.terra.money/soju-0013/tx/FCF50C180303AECA97F916D0CE0E0937BA4C4D2F6777FFF2AA0D52A9DAF9CCBA + let unstakeMessage = CosmosMessage.Undelegate.with { + $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" + $0.validatorAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + $0.amount = CosmosAmount.with { + $0.amount = "500000" + $0.denom = "uluna" + } + $0.typePrefix = "staking/MsgUndelegate" + } + + let message = CosmosMessage.with { + $0.unstakeMessage = unstakeMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "3000" + $0.denom = "uluna" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 158 + $0.chainID = "soju-0013" + $0.memo = "" + $0.sequence = 3 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + let expectedJSON = """ +{ + "mode": "block", + "tx": { + "fee": { + "amount": [{ + "amount": "3000", + "denom": "uluna" + }], + "gas": "200000" + }, + "memo": "", + "msg": [{ + "type": "staking/MsgUndelegate", + "value": { + "amount": { + "amount": "500000", + "denom": "uluna" + }, + "delegator_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", + "validator_address": "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + } + }], + "signatures": [{ + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "THf/RxsBr2EhHE2OMHLXfv+qSP9ORbvHgo4OSOS2P95xxGH73wW+t1zIl9cGlIVvcoChwaCg5/iEuvbgXUWpNw==" + }] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testRedlegate() { + // https://finder.terra.money/soju-0013/tx/36CE381BDF72AD7407EEE3859E3349F83B723BE9AD407E9D8C38DEE0C4434D29 + let restakeMessage = CosmosMessage.BeginRedelegate.with { + $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" + $0.validatorSrcAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + $0.validatorDstAddress = "terravaloper1rhrptnx87ufpv62c7ngt9yqlz2hr77xr9nkcr9" + $0.amount = CosmosAmount.with { + $0.amount = "500000" + $0.denom = "uluna" + } + $0.typePrefix = "staking/MsgBeginRedelegate" + } + + let message = CosmosMessage.with { + $0.restakeMessage = restakeMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "3000" + $0.denom = "uluna" + }] + } + + let input = CosmosSigningInput.with { + $0.accountNumber = 158 + $0.chainID = "soju-0013" + $0.memo = "" + $0.sequence = 4 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + let expectedJSON = """ +{ + "mode": "block", + "tx": { + "fee": { + "amount": [{ + "amount": "3000", + "denom": "uluna" + }], + "gas": "200000" + }, + "memo": "", + "msg": [{ + "type": "staking/MsgBeginRedelegate", + "value": { + "amount": { + "amount": "500000", + "denom": "uluna" + }, + "delegator_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", + "validator_dst_address": "terravaloper1rhrptnx87ufpv62c7ngt9yqlz2hr77xr9nkcr9", + "validator_src_address": "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + } + }], + "signatures": [{ + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" + }, + "signature": "HyEpSz48dkebmBFvwh5xDiiZD0jUdOvzTD3ACMw0rOQ9F3JhK2cPaEx6/ZmYNIrdsPqMNkUnHcDYD1o4IztoEg==" + }] + } +} +""" + XCTAssertJSONEqual(expectedJSON, output.json) + } + + func testSigningWasmTerraTransferTxProtobuf() { + let privateKey = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra) + + let wasmTransferMessage = CosmosMessage.WasmTerraExecuteContractTransfer.with { + $0.senderAddress = fromAddress.description + $0.contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" // ANC + $0.amount = Data(hexString: "03D090")! // 250000 + $0.recipientAddress = "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" + } + + let message = CosmosMessage.with { + $0.wasmTerraExecuteContractTransferMessage = wasmTransferMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "3000" + $0.denom = "uluna" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 3407705 + $0.chainID = "columbus-5" + $0.memo = "" + $0.sequence = 3 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + XCTAssertJSONEqual(output.serialized, +""" +{ + "tx_bytes": "CucBCuQBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBK5AQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Glt7InRyYW5zZmVyIjp7ImFtb3VudCI6IjI1MDAwMCIsInJlY2lwaWVudCI6InRlcnJhMWpsZ2FxeTludm4yaGY1dDJzcmE5eWN6OHM3N3duZjlsMGttZ2NwIn19EmcKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNwZjrHsPmJKW/rXOWfukpQ1+lOHOJW3/IlFFnKLNmsABIECgIIARgDEhMKDQoFdWx1bmESBDMwMDAQwJoMGkAaprIEMLPH2HmFdwFGoaipb2GIyhXt6ombz+WMnG2mORBI6gFt0M+IymYgzZz6w1SW52R922yafDnn7yXfutRw", + "mode": "BROADCAST_MODE_BLOCK" +} +""" + ) + XCTAssertEqual(output.errorMessage, "") + } + + func testSigningWasmTerraGenericProtobuf() { + let privateKey = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra) + + let wasmGenericMessage = CosmosMessage.WasmTerraExecuteContractGeneric.with { + $0.senderAddress = fromAddress.description + $0.contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" // ANC + $0.executeMsg = """ + {"transfer": { "amount": "250000", "recipient": "terra1d7048csap4wzcv5zm7z6tdqem2agyp9647vdyj" } } + """ + } + + let message = CosmosMessage.with { + $0.wasmTerraExecuteContractGeneric = wasmGenericMessage + } + + let fee = CosmosFee.with { + $0.gas = 200000 + $0.amounts = [CosmosAmount.with { + $0.amount = "3000" + $0.denom = "uluna" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 3407705 + $0.chainID = "columbus-5" + $0.memo = "" + $0.sequence = 7 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + XCTAssertJSONEqual(output.serialized, + """ + { + "tx_bytes": "Cu4BCusBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLAAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2GmJ7InRyYW5zZmVyIjogeyAiYW1vdW50IjogIjI1MDAwMCIsICJyZWNpcGllbnQiOiAidGVycmExZDcwNDhjc2FwNHd6Y3Y1em03ejZ0ZHFlbTJhZ3lwOTY0N3ZkeWoiIH0gfRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYBxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAkPsS7xlSng2LMc9KiD1soN5NLaDcUh8I9okPmsdJN3le1B7yxRGNB4aQfhaRl/8Z0r5vitRT0AWuxDasd8wcFw==", + "mode": "BROADCAST_MODE_BLOCK" + } + """ + ) + XCTAssertEqual(output.errorMessage, "") + } + + func testSigningWasmTerraGenericWithCoins() { + let privateKey = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra) + + let wasmGenericMessage = CosmosMessage.WasmTerraExecuteContractGeneric.with { + $0.senderAddress = fromAddress.description + $0.contractAddress = "terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s" // ANC Market + $0.executeMsg = """ + { "deposit_stable": {} } + """ + $0.coins = [CosmosAmount.with { + $0.amount = "1000" + $0.denom = "uusd" + }] + } + + let message = CosmosMessage.with { + $0.wasmTerraExecuteContractGeneric = wasmGenericMessage + } + + let fee = CosmosFee.with { + $0.gas = 600000 + $0.amounts = [CosmosAmount.with { + $0.amount = "7000" + $0.denom = "uluna" + }] + } + + let input = CosmosSigningInput.with { + $0.signingMode = .protobuf; + $0.accountNumber = 3407705 + $0.chainID = "columbus-5" + $0.memo = "" + $0.sequence = 9 + $0.messages = [message] + $0.fee = fee + $0.privateKey = privateKey.data + } + + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + + XCTAssertJSONEqual(output.serialized, + """ + { + "tx_bytes": "CrIBCq8BCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBKEAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMXNlcGZqN3MwYWVnNTk2N3V4bmZrNHRoemxlcnJza3RrcGVsbTVzGhh7ICJkZXBvc2l0X3N0YWJsZSI6IHt9IH0qDAoEdXVzZBIEMTAwMBJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYCRITCg0KBXVsdW5hEgQ3MDAwEMDPJBpAGyi7f1ioY8XV6pjFq1s86Om4++CIUnd3rLHif2iopCcYvX0mLkTlQ6NUERg8nWTYgXcj6fOTO/ptgPuAtv0NWg==", + "mode": "BROADCAST_MODE_BLOCK" + } + """ + ) + XCTAssertEqual(output.errorMessage, "") + } + } diff --git a/swift/Tests/Blockchains/TerraTests.swift b/swift/Tests/Blockchains/TerraTests.swift index d6bcceeb2aa..af8b1d0e593 100644 --- a/swift/Tests/Blockchains/TerraTests.swift +++ b/swift/Tests/Blockchains/TerraTests.swift @@ -1,190 +1,87 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore class TerraTests: XCTestCase { - let privateKey = PrivateKey(data: Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")!)! + let privateKey80e8 = PrivateKey(data: Data(hexString: "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005")!)! // terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2 + let privateKeycf08 = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!)! // terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf + let privateKey1037 = PrivateKey(data: Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")!)! func testAddress() { - let address = CoinType.terra.deriveAddress(privateKey: privateKey) + let address = CoinType.terraV2.deriveAddress(privateKey: privateKey1037) XCTAssertEqual(address, "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe") - XCTAssertTrue(CoinType.terra.validate(address: "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms")) - XCTAssertTrue(CoinType.terra.validate(address: "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk")) - XCTAssertFalse(CoinType.terra.validate(address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")) + XCTAssertTrue(CoinType.terraV2.validate(address: "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms")) + XCTAssertTrue(CoinType.terraV2.validate(address: "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk")) + XCTAssertFalse(CoinType.terraV2.validate(address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")) } - func testRawJSON() { - let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) - let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra).description - - let message = CosmosMessage.with { - $0.rawJsonMessage = CosmosMessage.RawJSON.with { - $0.type = "bank/MsgSend" - $0.value = """ - { - "amount": [{ - "amount": "1000000", - "denom": "uluna" - }], - "from_address": "\(fromAddress)", - "to_address": "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms" - } - """ - } - } - - let fee = CosmosFee.with { - $0.gas = 200000 - $0.amounts = [CosmosAmount.with { - $0.amount = "3000" - $0.denom = "uluna" - }] - } - - let input = CosmosSigningInput.with { - $0.accountNumber = 158 - $0.chainID = "soju-0013" - $0.memo = "" - $0.sequence = 0 - $0.messages = [message] - $0.fee = fee - $0.privateKey = privateKey.data - } - - let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) - - let expectedJSON: String = -""" -{ - "mode": "block", - "tx": { - "msg": [{ - "type": "bank/MsgSend", - "value": { - "from_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", - "to_address": "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms", - "amount": [{ - "denom": "uluna", - "amount": "1000000" - }] - } - }], - "fee": { - "amount": [{ - "denom": "uluna", - "amount": "3000" - }], - "gas": "200000" - }, - "signatures": [{ - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" - }, - "signature": "KPdiVsKpY12JG/VKEJVa/FpMKclxlS0qNNG6VOAypj10R5vY5UX5IgRJET1zNYnH0wvcXxfNXV+s8jtwN2UXiQ==" - }], - "memo": "" - } -} -""" - XCTAssertJSONEqual(expectedJSON, output.json) - } - - func testSigningTransaction() { - // https://finder.terra.money/soju-0013/tx/1403B07F2D218BCE961CB92D83377A924FEDB54C1F0B62E25C8B93B63470EBF7 - let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) - let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra).description + func testSignSendTx() { + let publicKey = privateKey80e8.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .terraV2).description + XCTAssertEqual(fromAddress, "terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2") let message = CosmosMessage.with { $0.sendCoinsMessage = CosmosMessage.Send.with { $0.fromAddress = fromAddress - $0.toAddress = "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms" + $0.toAddress = "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" $0.amounts = [CosmosAmount.with { $0.amount = "1000000" $0.denom = "uluna" }] - $0.typePrefix = "bank/MsgSend" } } let fee = CosmosFee.with { $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = "3000" + $0.amount = "30000" $0.denom = "uluna" }] } let input = CosmosSigningInput.with { - $0.accountNumber = 158 - $0.chainID = "soju-0013" + $0.signingMode = .protobuf; + $0.accountNumber = 1037 + $0.chainID = "phoenix-1" $0.memo = "" - $0.sequence = 0 + $0.sequence = 1 $0.messages = [message] $0.fee = fee - $0.privateKey = privateKey.data + $0.privateKey = privateKey80e8.data } - let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terraV2) let expectedJSON: String = -""" -{ - "mode": "block", - "tx": { - "msg": [{ - "type": "bank/MsgSend", - "value": { - "from_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", - "to_address": "terra1hdp298kaz0eezpgl6scsykxljrje3667d233ms", - "amount": [{ - "denom": "uluna", - "amount": "1000000" - }] - } - }], - "fee": { - "amount": [{ - "denom": "uluna", - "amount": "3000" - }], - "gas": "200000" - }, - "signatures": [{ - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" - }, - "signature": "KPdiVsKpY12JG/VKEJVa/FpMKclxlS0qNNG6VOAypj10R5vY5UX5IgRJET1zNYnH0wvcXxfNXV+s8jtwN2UXiQ==" - }], - "memo": "" - } -} -""" - XCTAssertJSONEqual(expectedJSON, output.json) + """ + { + "tx_bytes": "CpEBCo4BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm4KLHRlcnJhMWhzazZqcnl5cWpmaHA1ZGhjNTV0YzlqdGNreWd4MGVwMzdoZGQyEix0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcBoQCgV1bHVuYRIHMTAwMDAwMBJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYARIUCg4KBXVsdW5hEgUzMDAwMBDAmgwaQPh0C3rjzdixIUiyPx3FlWAxzbKILNAcSRVeQnaTl1vsI5DEfYa2oYlUBLqyilcMCcU/iaJLhex30No2ak0Zn1Q=", + "mode": "BROADCAST_MODE_BLOCK" + } + """ + XCTAssertJSONEqual(expectedJSON, output.serialized) + XCTAssertEqual(output.errorMessage, "") } - func testStaking() { - // https://finder.terra.money/soju-0013/tx/4C0A6690ECB601ACB42D3ECAF4C24C0555B5E32E45B09C3B1607B144CD191F87 - let stakeMessage = CosmosMessage.Delegate.with { - $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" - $0.validatorAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" - $0.amount = CosmosAmount.with { - $0.amount = "1000000" - $0.denom = "uluna" - } - $0.typePrefix = "staking/MsgDelegate" + func testSignWasmTransferTx() { + let publicKey = privateKeycf08.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .terraV2).description + XCTAssertEqual(fromAddress, "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf") + + let wasmTransferMessage = CosmosMessage.WasmExecuteContractTransfer.with { + $0.senderAddress = fromAddress.description + $0.contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" + $0.amount = Data(hexString: "03D090")! // 250000 + $0.recipientAddress = "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" } let message = CosmosMessage.with { - $0.stakeMessage = stakeMessage + $0.wasmExecuteContractTransferMessage = wasmTransferMessage } let fee = CosmosFee.with { @@ -196,410 +93,123 @@ class TerraTests: XCTestCase { } let input = CosmosSigningInput.with { - $0.accountNumber = 158 - $0.chainID = "soju-0013" + $0.signingMode = .protobuf; + $0.accountNumber = 3407705 + $0.chainID = "phoenix-1" $0.memo = "" - $0.sequence = 1 + $0.sequence = 3 $0.messages = [message] $0.fee = fee - $0.privateKey = privateKey.data + $0.privateKey = privateKeycf08.data } - let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) - - let expectedJSON = """ -{ - "mode": "block", - "tx": { - "fee": { - "amount": [{ - "amount": "3000", - "denom": "uluna" - }], - "gas": "200000" - }, - "memo": "", - "msg": [{ - "type": "staking/MsgDelegate", - "value": { - "amount": { - "amount": "1000000", - "denom": "uluna" - }, - "delegator_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", - "validator_address": "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" - } - }], - "signatures": [{ - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" - }, - "signature": "F8UJxbkqa0j6dYTk8PymrudBKI3WYhZImRxMFCw0ecFCmPGgNTg7yfpKZo6K6JtnoJaP7bQ4db5e4wnhMCJyAQ==" - }] - } -} -""" - XCTAssertJSONEqual(expectedJSON, output.json) - } - - func testWithdraw() { - // https://finder.terra.money/soju-0013/tx/AE0E4F2B254449950A3A7F41FABCE0B3C846D70F809380313CE3BB323E490BBD - let withdrawMessage = CosmosMessage.WithdrawDelegationReward.with { - $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" - $0.validatorAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" - $0.typePrefix = "distribution/MsgWithdrawDelegationReward" - } - - let message = CosmosMessage.with { - $0.withdrawStakeRewardMessage = withdrawMessage - } + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terraV2) - let fee = CosmosFee.with { - $0.amounts = [CosmosAmount.with { - $0.amount = "3000" - $0.denom = "uluna" - }] - $0.gas = 200000 - } - - let input = CosmosSigningInput.with { - $0.accountNumber = 158 - $0.chainID = "soju-0013" - $0.memo = "" - $0.sequence = 2 - $0.messages = [message] - $0.fee = fee - $0.privateKey = privateKey.data + XCTAssertJSONEqual(output.serialized, + """ + { + "tx_bytes": "CuUBCuIBCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QSuQEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTE0ejU2bDBmcDJsc2Y4Nnp5M2h0eTJ6NDdlemtobnRodHI5eXE3NhpbeyJ0cmFuc2ZlciI6eyJhbW91bnQiOiIyNTAwMDAiLCJyZWNpcGllbnQiOiJ0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcCJ9fRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYAxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAiBGbQaj+jsXE6/FssD3fC77QOxpli9GqsPea+KoNyMIEgVj89Hii+oU1bAEQS4qV0SaE2V6RNy24uCcFTIRbcQ==", + "mode": "BROADCAST_MODE_BLOCK" } - - let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) - - let expectedJSON = """ -{ - "mode": "block", - "tx": { - "fee": { - "amount": [{ - "amount": "3000", - "denom": "uluna" - }], - "gas": "200000" - }, - "memo": "", - "msg": [{ - "type": "distribution/MsgWithdrawDelegationReward", - "value": { - "delegator_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", - "validator_address": "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" - } - }], - "signatures": [{ - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" - }, - "signature": "Kfwi1uJplzLucXDyQZsJI9v8lMFJFUBLD46+MpwBwYwPJgqPRzSOfyjRpmNou0G/Qe1hbsGEgqb85FQpsgLz+g==" - }] - } -} -""" - XCTAssertJSONEqual(expectedJSON, output.json) + """ + ) + XCTAssertEqual(output.errorMessage, "") } - func testUndelegate() { - // https://finder.terra.money/soju-0013/tx/FCF50C180303AECA97F916D0CE0E0937BA4C4D2F6777FFF2AA0D52A9DAF9CCBA - let unstakeMessage = CosmosMessage.Undelegate.with { - $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" - $0.validatorAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" + func testSignStakeTx() throws { + + let stakeMessage = CosmosMessage.Delegate.with { + $0.delegatorAddress = "terra1ncfyexz3nrrdru37ahqpp4wen48v7p5nany478" + $0.validatorAddress = "terravaloper1ekq8xuypdxtf3nfmffmydnhny59pjuy0p8wpn7" $0.amount = CosmosAmount.with { - $0.amount = "500000" + $0.amount = "1000000" // 5 Luna $0.denom = "uluna" } - $0.typePrefix = "staking/MsgUndelegate" } let message = CosmosMessage.with { - $0.unstakeMessage = unstakeMessage + $0.stakeMessage = stakeMessage } let fee = CosmosFee.with { - $0.gas = 200000 + $0.gas = 254682 $0.amounts = [CosmosAmount.with { - $0.amount = "3000" + $0.amount = "38203" $0.denom = "uluna" }] } let input = CosmosSigningInput.with { - $0.accountNumber = 158 - $0.chainID = "soju-0013" - $0.memo = "" - $0.sequence = 3 + $0.signingMode = .protobuf; + $0.accountNumber = 127185 + $0.chainID = "phoenix-1" + $0.sequence = 6 $0.messages = [message] $0.fee = fee - $0.privateKey = privateKey.data + $0.privateKey = privateKey1037.data // real key is terra: define... } - let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) - - let expectedJSON = """ -{ - "mode": "block", - "tx": { - "fee": { - "amount": [{ - "amount": "3000", - "denom": "uluna" - }], - "gas": "200000" - }, - "memo": "", - "msg": [{ - "type": "staking/MsgUndelegate", - "value": { - "amount": { - "amount": "500000", - "denom": "uluna" - }, - "delegator_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", - "validator_address": "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" - } - }], - "signatures": [{ - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" - }, - "signature": "THf/RxsBr2EhHE2OMHLXfv+qSP9ORbvHgo4OSOS2P95xxGH73wW+t1zIl9cGlIVvcoChwaCg5/iEuvbgXUWpNw==" - }] - } -} -""" - XCTAssertJSONEqual(expectedJSON, output.json) - } - - func testRedlegate() { - // https://finder.terra.money/soju-0013/tx/36CE381BDF72AD7407EEE3859E3349F83B723BE9AD407E9D8C38DEE0C4434D29 - let restakeMessage = CosmosMessage.BeginRedelegate.with { - $0.delegatorAddress = "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe" - $0.validatorSrcAddress = "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" - $0.validatorDstAddress = "terravaloper1rhrptnx87ufpv62c7ngt9yqlz2hr77xr9nkcr9" - $0.amount = CosmosAmount.with { - $0.amount = "500000" - $0.denom = "uluna" - } - $0.typePrefix = "staking/MsgBeginRedelegate" + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terraV2) + let expected = """ + { + "mode": "BROADCAST_MODE_BLOCK", + "tx_bytes": "Cp8BCpwBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJ1Cix0ZXJyYTFuY2Z5ZXh6M25ycmRydTM3YWhxcHA0d2VuNDh2N3A1bmFueTQ3OBIzdGVycmF2YWxvcGVyMWVrcTh4dXlwZHh0ZjNuZm1mZm15ZG5obnk1OXBqdXkwcDh3cG43GhAKBXVsdW5hEgcxMDAwMDAwEmgKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNd8YVWZSHWp4AjGe4G4aKOl7d3Lftf3RPKbwV1UYlo5BIECgIIARgGEhQKDgoFdWx1bmESBTM4MjAzENrFDxpAamKyAvWhWCv0nUKz7yiYETpkZETflDvfe1vmuFIy31g+s0u1cgLNo+7jBRXRuzYJXukigtwoLUrxY/C8rowiJw==" } + """ - let message = CosmosMessage.with { - $0.restakeMessage = restakeMessage - } - - let fee = CosmosFee.with { - $0.gas = 200000 - $0.amounts = [CosmosAmount.with { - $0.amount = "3000" - $0.denom = "uluna" - }] - } - - let input = CosmosSigningInput.with { - $0.accountNumber = 158 - $0.chainID = "soju-0013" - $0.memo = "" - $0.sequence = 4 - $0.messages = [message] - $0.fee = fee - $0.privateKey = privateKey.data - } - - let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) - - let expectedJSON = """ -{ - "mode": "block", - "tx": { - "fee": { - "amount": [{ - "amount": "3000", - "denom": "uluna" - }], - "gas": "200000" - }, - "memo": "", - "msg": [{ - "type": "staking/MsgBeginRedelegate", - "value": { - "amount": { - "amount": "500000", - "denom": "uluna" - }, - "delegator_address": "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe", - "validator_dst_address": "terravaloper1rhrptnx87ufpv62c7ngt9yqlz2hr77xr9nkcr9", - "validator_src_address": "terravaloper1pdx498r0hrc2fj36sjhs8vuhrz9hd2cw0yhqtk" - } - }], - "signatures": [{ - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjk" - }, - "signature": "HyEpSz48dkebmBFvwh5xDiiZD0jUdOvzTD3ACMw0rOQ9F3JhK2cPaEx6/ZmYNIrdsPqMNkUnHcDYD1o4IztoEg==" - }] - } -} -""" - XCTAssertJSONEqual(expectedJSON, output.json) + // https://finder.terra.money/mainnet/tx/4441c65f24783b8f59b20b1b80ee43f1f4f6ff827597d87b6bbc94982b45be0c + XCTAssertJSONEqual(output.serialized, expected) + XCTAssertEqual(output.errorMessage, "") } - func testSigningWasmTerraTransferTxProtobuf() { - let privateKey = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!)! - let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) - let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra) + func testSignClaimRewards() throws { + let delegator = "terra1ncfyexz3nrrdru37ahqpp4wen48v7p5nany478" + let validators = [ + "terravaloper1ekq8xuypdxtf3nfmffmydnhny59pjuy0p8wpn7" + ] - let wasmTransferMessage = CosmosMessage.WasmTerraExecuteContractTransfer.with { - $0.senderAddress = fromAddress.description - $0.contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" // ANC - $0.amount = Data(hexString: "03D090")! // 250000 - $0.recipientAddress = "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" - } - - let message = CosmosMessage.with { - $0.wasmTerraExecuteContractTransferMessage = wasmTransferMessage + let withdrawals = validators.map { validator in + CosmosMessage.WithdrawDelegationReward.with { + $0.delegatorAddress = delegator + $0.validatorAddress = validator + } + }.map { withdraw in + CosmosMessage.with { + $0.withdrawStakeRewardMessage = withdraw + } } let fee = CosmosFee.with { - $0.gas = 200000 $0.amounts = [CosmosAmount.with { - $0.amount = "3000" + $0.amount = "29513" $0.denom = "uluna" }] + $0.gas = 196749 } let input = CosmosSigningInput.with { $0.signingMode = .protobuf; - $0.accountNumber = 3407705 - $0.chainID = "columbus-5" - $0.memo = "" - $0.sequence = 3 - $0.messages = [message] $0.fee = fee - $0.privateKey = privateKey.data + $0.accountNumber = 127185 + $0.chainID = "phoenix-1" + $0.sequence = 5 + $0.messages = withdrawals + $0.fee = fee + $0.privateKey = privateKey1037.data // real key is terra: define... } - let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) - - XCTAssertJSONEqual(output.serialized, -""" -{ - "tx_bytes": "CucBCuQBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBK5AQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Glt7InRyYW5zZmVyIjp7ImFtb3VudCI6IjI1MDAwMCIsInJlY2lwaWVudCI6InRlcnJhMWpsZ2FxeTludm4yaGY1dDJzcmE5eWN6OHM3N3duZjlsMGttZ2NwIn19EmcKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNwZjrHsPmJKW/rXOWfukpQ1+lOHOJW3/IlFFnKLNmsABIECgIIARgDEhMKDQoFdWx1bmESBDMwMDAQwJoMGkAaprIEMLPH2HmFdwFGoaipb2GIyhXt6ombz+WMnG2mORBI6gFt0M+IymYgzZz6w1SW52R922yafDnn7yXfutRw", - "mode": "BROADCAST_MODE_BLOCK" -} -""" - ) - XCTAssertEqual(output.error, "") - } - - func testSigningWasmTerraGenericProtobuf() { - let privateKey = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!)! - let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) - let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra) - - let wasmGenericMessage = CosmosMessage.WasmTerraExecuteContractGeneric.with { - $0.senderAddress = fromAddress.description - $0.contractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76" // ANC - $0.executeMsg = """ - {"transfer": { "amount": "250000", "recipient": "terra1d7048csap4wzcv5zm7z6tdqem2agyp9647vdyj" } } - """ - } - - let message = CosmosMessage.with { - $0.wasmTerraExecuteContractGeneric = wasmGenericMessage - } - - let fee = CosmosFee.with { - $0.gas = 200000 - $0.amounts = [CosmosAmount.with { - $0.amount = "3000" - $0.denom = "uluna" - }] - } - - let input = CosmosSigningInput.with { - $0.signingMode = .protobuf; - $0.accountNumber = 3407705 - $0.chainID = "columbus-5" - $0.memo = "" - $0.sequence = 7 - $0.messages = [message] - $0.fee = fee - $0.privateKey = privateKey.data - } + let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terraV2) - let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) - - XCTAssertJSONEqual(output.serialized, - """ - { - "tx_bytes": "Cu4BCusBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLAAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2GmJ7InRyYW5zZmVyIjogeyAiYW1vdW50IjogIjI1MDAwMCIsICJyZWNpcGllbnQiOiAidGVycmExZDcwNDhjc2FwNHd6Y3Y1em03ejZ0ZHFlbTJhZ3lwOTY0N3ZkeWoiIH0gfRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYBxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAkPsS7xlSng2LMc9KiD1soN5NLaDcUh8I9okPmsdJN3le1B7yxRGNB4aQfhaRl/8Z0r5vitRT0AWuxDasd8wcFw==", - "mode": "BROADCAST_MODE_BLOCK" - } - """ - ) - XCTAssertEqual(output.error, "") + let expected = """ + { + "mode": "BROADCAST_MODE_BLOCK", + "tx_bytes": "CqEBCp4BCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmMKLHRlcnJhMW5jZnlleHozbnJyZHJ1MzdhaHFwcDR3ZW40OHY3cDVuYW55NDc4EjN0ZXJyYXZhbG9wZXIxZWtxOHh1eXBkeHRmM25mbWZmbXlkbmhueTU5cGp1eTBwOHdwbjcSaApQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA13xhVZlIdangCMZ7gbhoo6Xt3ct+1/dE8pvBXVRiWjkEgQKAggBGAUSFAoOCgV1bHVuYRIFMjk1MTMQjYEMGkA/bh2va6RRZvkSLnej84dJgSSvbgcHgYDbkRt8wDge03W747BZcuBcg/U5EuE7zBqSJrKUTZl7oUCp//rYlJKV" } + """ - func testSigningWasmTerraGenericWithCoins() { - let privateKey = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!)! - let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) - let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra) - - let wasmGenericMessage = CosmosMessage.WasmTerraExecuteContractGeneric.with { - $0.senderAddress = fromAddress.description - $0.contractAddress = "terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s" // ANC Market - $0.executeMsg = """ - { "deposit_stable": {} } - """ - $0.coins = [CosmosAmount.with { - $0.amount = "1000" - $0.denom = "uusd" - }] - } - - let message = CosmosMessage.with { - $0.wasmTerraExecuteContractGeneric = wasmGenericMessage - } - - let fee = CosmosFee.with { - $0.gas = 600000 - $0.amounts = [CosmosAmount.with { - $0.amount = "7000" - $0.denom = "uluna" - }] - } - - let input = CosmosSigningInput.with { - $0.signingMode = .protobuf; - $0.accountNumber = 3407705 - $0.chainID = "columbus-5" - $0.memo = "" - $0.sequence = 9 - $0.messages = [message] - $0.fee = fee - $0.privateKey = privateKey.data - } - - let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .terra) - - XCTAssertJSONEqual(output.serialized, - """ - { - "tx_bytes": "CrIBCq8BCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBKEAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMXNlcGZqN3MwYWVnNTk2N3V4bmZrNHRoemxlcnJza3RrcGVsbTVzGhh7ICJkZXBvc2l0X3N0YWJsZSI6IHt9IH0qDAoEdXVzZBIEMTAwMBJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYCRITCg0KBXVsdW5hEgQ3MDAwEMDPJBpAGyi7f1ioY8XV6pjFq1s86Om4++CIUnd3rLHif2iopCcYvX0mLkTlQ6NUERg8nWTYgXcj6fOTO/ptgPuAtv0NWg==", - "mode": "BROADCAST_MODE_BLOCK" - } - """ - ) - XCTAssertEqual(output.error, "") - } + // https://finder.terra.money/mainnet/tx/0e62170ed5407992251d7e161f23c3467e1bea54c7f601953953bdabc7f0c30c + XCTAssertJSONEqual(output.serialized, expected) + XCTAssertEqual(output.errorMessage, "") } + +} diff --git a/swift/Tests/Blockchains/TezosTests.swift b/swift/Tests/Blockchains/TezosTests.swift index a9416b97322..b7374aa2e9d 100644 --- a/swift/Tests/Blockchains/TezosTests.swift +++ b/swift/Tests/Blockchains/TezosTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest @@ -26,6 +24,114 @@ class TezosTests: XCTestCase { XCTAssertEqual(address.description, "tz1cG2jx3W4bZFeVGBjsTxUAG8tdpTXtE8PT") } + + public func testSigningFA12() { + let privateKeyData = Data(hexString: "363265a0b3f06661001cab8b4f3ca8fd97ae70608184979cf7300836f57ec2d6")! + + let branch = "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp" + var operationList = TezosOperationList() + operationList.branch = branch + + let transactionOperationData = TezosTransactionOperationData.with { + $0.amount = 0 + $0.destination = "KT1EwXFWoG9bYebmF4pYw72aGjwEnBWefgW5" + $0.parameters.fa12Parameters.entrypoint = "transfer"; + $0.parameters.fa12Parameters.from = "tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"; + $0.parameters.fa12Parameters.to = "tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"; + $0.parameters.fa12Parameters.value = "123"; + } + + let transactionOperation = TezosOperation.with { + $0.source = "tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP" + $0.fee = 100000 + $0.counter = 2993172 + $0.gasLimit = 100000 + $0.storageLimit = 0 + $0.kind = .transaction + $0.transactionOperationData = transactionOperationData + } + + operationList.operations = [ transactionOperation ] + + let input = TezosSigningInput.with { + $0.operationList = operationList + $0.privateKey = privateKeyData + } + + let output: TezosSigningOutput = AnySigner.sign(input: input, coin: .tezos) + let expected = "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016c00fe2ce0cccc0214af521ad60c140c5589b4039247a08d0694d8b601a08d0600000145bd8a65cc48159d8ea60a55df735b7c5ad45f0e00ffff087472616e736665720000005907070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555000bb012914d768155fba2df319a81136e8e3e573b9cadb1676834490c90212615d271da029b6b0531e290e9063bcdb40bea43627af048b18e036f02be2b6b22fc8b307" + + XCTAssertEqual(output.encoded.hexString, expected) + } + + public func testSigningFA2() { + let privateKeyData = Data(hexString: "363265a0b3f06661001cab8b4f3ca8fd97ae70608184979cf7300836f57ec2d6")! + + let branch = "BKvEAX9HXfJZWYfTQbR1C7B3ADoKY6a1aKVRF7qQqvc9hS8Rr3m" + var operationList = TezosOperationList() + operationList.branch = branch + + let transferInfos = TezosTxs.with{ + $0.to = "tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP" + $0.tokenID = "0" + $0.amount = "10" + } + + let transactionOperationData = TezosTransactionOperationData.with { + $0.amount = 0 + $0.destination = "KT1DYk1XDzHredJq1EyNkDindiWDqZyekXGj" + $0.parameters.fa2Parameters.entrypoint = "transfer"; + $0.parameters.fa2Parameters.txsObject = [TezosTxObject.with{ + $0.from = "tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP" + $0.txs = [transferInfos] + }] + } + + + + let transactionOperation = TezosOperation.with { + $0.source = "tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP" + $0.fee = 100000 + $0.counter = 2993173 + $0.gasLimit = 100000 + $0.storageLimit = 0 + $0.kind = .transaction + $0.transactionOperationData = transactionOperationData + } + + operationList.operations = [ transactionOperation ] + + let input = TezosSigningInput.with { + $0.operationList = operationList + $0.privateKey = privateKeyData + } + + let output: TezosSigningOutput = AnySigner.sign(input: input, coin: .tezos) + let expected = "1b1f9345dc9f77bd24b09034d1d2f9a28f02ac837f49db54b8d68341f53dc4b76c00fe2ce0cccc0214af521ad60c140c5589b4039247a08d0695d8b601a08d0600000136767f88850bae28bfb9f46b73c5e87ede4de12700ffff087472616e7366657200000066020000006107070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b5550020000003107070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070000000a552d24710d6c59383286700c6c2917b25a6c1fa8b587e593c289dd47704278796792f1e522c1623845ec991e292b0935445e6994850bd03f035a006c5ed93806" + + XCTAssertEqual(output.encoded.hexString, expected) + } + + public func testMessageSignerSignAndVerify() { + let privateKey = PrivateKey(data: Data(hexString: "91b4fb8d7348db2e7de2693f58ce1cceb966fa960739adac1d9dba2cbaa0940a")!)! + let msg = "05010000004254657a6f73205369676e6564204d6573736167653a207465737455726c20323032332d30322d30385431303a33363a31382e3435345a2048656c6c6f20576f726c64" + let signature = TezosMessageSigner.signMessage(privateKey: privateKey, message: msg) + XCTAssertEqual(signature, "edsigu3se2fcEJUCm1aqxjzbHdf7Wsugr4mLaA9YM2UVZ9Yy5meGv87VqHN3mmDeRwApTj1JKDaYjqmLZifSFdWCqBoghqaowwJ") + let pubKey = privateKey.getPublicKey(coinType: .tezos) + XCTAssertTrue(TezosMessageSigner.verifyMessage(pubKey: pubKey, message: msg, signature: signature)) + } + + public func testMessageSignerInputToPayload() { + let payload = TezosMessageSigner.inputToPayload(message: "Tezos Signed Message: testUrl 2023-02-08T10:36:18.454Z Hello World"); + let expected = "05010000004254657a6f73205369676e6564204d6573736167653a207465737455726c20323032332d30322d30385431303a33363a31382e3435345a2048656c6c6f20576f726c64"; + XCTAssertEqual(payload, expected); + } + + public func testMessageSignerFormatMessage() { + let formatedMsg = TezosMessageSigner.formatMessage(message: "Hello World", url: "testUrl") + let regex = try! NSRegularExpression(pattern: "Tezos Signed Message: \\S+ \\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z .+") + XCTAssertTrue(regex.firstMatch(in: formatedMsg, range: NSRange(location: 0, length: formatedMsg.utf16.count)) != nil) + } public func testSigning() { let privateKeyData = Data(hexString: "c6377a4cc490dc913fc3f0d9cf67d293a32df4547c46cb7e9e33c3b7b97c64d8")! @@ -74,4 +180,34 @@ class TezosTests: XCTestCase { XCTAssertEqual(output.encoded.hexString, expected) } + + public func testSignEncodedBytes() throws { + + let key = Data(hexString: "3caf5afaed067890cd850efd1555df351aa482badb4a541c29261f1acf261bf5")! + let bytes = Data(hexString: "64aa7792af40de41371a72b3342daa7bf3d2b5a84511e9074341fdd52148dd9d6c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542850f96c3a1079d780080ade2040155959998da7e79231e2be8ed8ff373ac1b1574b000ffff04737761700000009e070703060707020000000807070508030b000007070100000018323032332d30322d32345431333a34303a32322e3332385a07070100000024747a31625443473754415535523736356f4458694c4d63385a4537546a7376617868484807070100000024747a315377326d4641557a626b6d37646b47434472626542734a54547456374a4438457907070080dac409070700bdf892a1a291e196aa0503066c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542cd0497c3a107f10f180001543aa1803f0bbe2099809ab067dfa8a4cbc1c26a00ffff07617070726f76650000002d070701000000244b5431516f64676b5974754e79664a726a72673854515a586d64544643616d373268533900006c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542cd0498c3a107f70f090001543aa1803f0bbe2099809ab067dfa8a4cbc1c26a00ffff07617070726f766500000036070701000000244b5431516f64676b5974754e79664a726a72673854515a586d64544643616d373268533900bdf892a1a291e196aa056c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542e71599c3a107fabb01400001b1f0d7affc39861f7f5c75f917f683d2e9f55e3100ffff04737761700000009a070700000707000007070001070700bdf892a1a291e196aa05070700a3f683c2a6d80a07070100000018323032332d30322d32345431333a34303a32322e3332385a070705090100000024747a31625443473754415535523736356f4458694c4d63385a4537546a7376617868484805090100000024747a315377326d4641557a626b6d37646b47434472626542734a54547456374a443845796c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542cd049ac3a107f50f1b000193d22b59c496c94504729be1c671ec1d1d7a9cf000ffff107570646174655f6f70657261746f72730000005f020000005a050507070100000024747a31625443473754415535523736356f4458694c4d63385a4537546a73766178684848070701000000244b543147504a44546638475a73704363616e6147324b684d764775334e4a52717572617400006c00ad756cb46ba6f59efa8bd10ff544ba9d20d0954285109bc3a107a0820100000155959998da7e79231e2be8ed8ff373ac1b1574b000ffff0473776170000000a1070703060707020000000807070508030b000807070100000018323032332d30322d32345431333a34303a32322e3332385a07070100000024747a31625443473754415535523736356f4458694c4d63385a4537546a7376617868484807070100000024747a315377326d4641557a626b6d37646b47434472626542734a54547456374a44384579070700a3f683c2a6d80a070700a4f096bfbe9df6f0e00603066c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542cd049cc3a107ed0f00000193d22b59c496c94504729be1c671ec1d1d7a9cf000ffff107570646174655f6f70657261746f72730000005f020000005a050807070100000024747a31625443473754415535523736356f4458694c4d63385a4537546a73766178684848070701000000244b543147504a44546638475a73704363616e6147324b684d764775334e4a5271757261740000")! + let input = TezosSigningInput.with { + $0.privateKey = key + $0.encodedOperations = bytes + } + + let output: TezosSigningOutput = AnySigner.sign(input: input, coin: .tezos) + + let expected = "64aa7792af40de41371a72b3342daa7bf3d2b5a84511e9074341fdd52148dd9d6c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542850f96c3a1079d780080ade2040155959998da7e79231e2be8ed8ff373ac1b1574b000ffff04737761700000009e070703060707020000000807070508030b000007070100000018323032332d30322d32345431333a34303a32322e3332385a07070100000024747a31625443473754415535523736356f4458694c4d63385a4537546a7376617868484807070100000024747a315377326d4641557a626b6d37646b47434472626542734a54547456374a4438457907070080dac409070700bdf892a1a291e196aa0503066c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542cd0497c3a107f10f180001543aa1803f0bbe2099809ab067dfa8a4cbc1c26a00ffff07617070726f76650000002d070701000000244b5431516f64676b5974754e79664a726a72673854515a586d64544643616d373268533900006c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542cd0498c3a107f70f090001543aa1803f0bbe2099809ab067dfa8a4cbc1c26a00ffff07617070726f766500000036070701000000244b5431516f64676b5974754e79664a726a72673854515a586d64544643616d373268533900bdf892a1a291e196aa056c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542e71599c3a107fabb01400001b1f0d7affc39861f7f5c75f917f683d2e9f55e3100ffff04737761700000009a070700000707000007070001070700bdf892a1a291e196aa05070700a3f683c2a6d80a07070100000018323032332d30322d32345431333a34303a32322e3332385a070705090100000024747a31625443473754415535523736356f4458694c4d63385a4537546a7376617868484805090100000024747a315377326d4641557a626b6d37646b47434472626542734a54547456374a443845796c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542cd049ac3a107f50f1b000193d22b59c496c94504729be1c671ec1d1d7a9cf000ffff107570646174655f6f70657261746f72730000005f020000005a050507070100000024747a31625443473754415535523736356f4458694c4d63385a4537546a73766178684848070701000000244b543147504a44546638475a73704363616e6147324b684d764775334e4a52717572617400006c00ad756cb46ba6f59efa8bd10ff544ba9d20d0954285109bc3a107a0820100000155959998da7e79231e2be8ed8ff373ac1b1574b000ffff0473776170000000a1070703060707020000000807070508030b000807070100000018323032332d30322d32345431333a34303a32322e3332385a07070100000024747a31625443473754415535523736356f4458694c4d63385a4537546a7376617868484807070100000024747a315377326d4641557a626b6d37646b47434472626542734a54547456374a44384579070700a3f683c2a6d80a070700a4f096bfbe9df6f0e00603066c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542cd049cc3a107ed0f00000193d22b59c496c94504729be1c671ec1d1d7a9cf000ffff107570646174655f6f70657261746f72730000005f020000005a050807070100000024747a31625443473754415535523736356f4458694c4d63385a4537546a73766178684848070701000000244b543147504a44546638475a73704363616e6147324b684d764775334e4a5271757261740000e10077fc3068aaaf1c7779e1dc2c396b3b40d73ddda04648bf4b16ac2e747c89b461771488e80da3aa30fc18c90de99fd358bfb76683f3c3ec250b1ee09b6d07" + + XCTAssertEqual(output.encoded.hexString, expected) + + // How to do it without AnySigner + var watermark = Data([0x03]) + watermark.append(bytes) + + let hash = Hash.blake2b(data: watermark, size: 32) + let privateKey = PrivateKey(data: key)! + let signature = privateKey.sign(digest: hash, curve: .ed25519)! + + var signed = Data() + signed.append(bytes) + signed.append(signature) + + XCTAssertEqual(signed.hexString, expected) + } } diff --git a/swift/Tests/Blockchains/TheOpenNetworkTests.swift b/swift/Tests/Blockchains/TheOpenNetworkTests.swift new file mode 100644 index 00000000000..f27e3a2fbd8 --- /dev/null +++ b/swift/Tests/Blockchains/TheOpenNetworkTests.swift @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class TheOpenNetworkTests: XCTestCase { + func testAddressFromPrivateKey() { + let data = Data(hexString: "63474e5fe9511f1526a50567ce142befc343e71a49b865ac3908f58667319cb8") + let privateKey = PrivateKey(data: data!)! + let publicKey = privateKey.getPublicKeyEd25519() + let address = AnyAddress(publicKey: publicKey, coin: .ton) + XCTAssertEqual(address.description, "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") + } + + func testAddressFromPublicKey() { + let data = Data(hexString: "f42c77f931bea20ec5d0150731276bbb2e2860947661245b2319ef8133ee8d41") + let publicKey = PublicKey(data: data!, type: PublicKeyType.ed25519)! + let address = AnyAddress(publicKey: publicKey, coin: .ton) + XCTAssertEqual(address.description, "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") + } + + func testAddressFromRawString() { + let addressString = "0:66fbe3c5c03bf5c82792f904c9f8bf28894a6aa3d213d41c20569b654aadedb3" + let address = AnyAddress(string: addressString, coin: .ton) + XCTAssertEqual(address!.description, "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") + } + + func testAddressFromBounceableString() { + let addressString = "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q" + let address = AnyAddress(string: addressString, coin: .ton) + XCTAssertEqual(address!.description, "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") + } + + func testAddressFromUserFriendlyString() { + let addressString = "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV" + let address = AnyAddress(string: addressString, coin: .ton) + XCTAssertEqual(address!.description, "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") + } + + func testAddressToBounceable() { + let addressString = "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV" + let address = TONAddressConverter.toUserFriendly(address: addressString, bounceable: true, testnet: false) + XCTAssertEqual(address, "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q") + } + + func testGenerateJettonAddress() { + let mainAddress = "UQBjKqthWBE6GEcqb_epTRFrQ1niS6Z1Z1MHMwR-mnAYRoYr" + let mainAddressBoc = TONAddressConverter.toBoc(address: mainAddress) + XCTAssertEqual(mainAddressBoc, "te6cckEBAQEAJAAAQ4AMZVVsKwInQwjlTf71KaItaGs8SXTOrOpg5mCP004DCNAptHQU") + + // curl --location 'https://toncenter.com/api/v2/runGetMethod' --header 'Content-Type: application/json' --data \ + // '{"address":"EQAvlWFDxGF2lXm67y4yzC17wYKD9A0guwPkMs1gOsM__NOT","method":"get_wallet_address","method":"get_wallet_address","stack":[["tvm.Slice","te6ccgICAAEAAQAAACQAAABDgAxlVWwrAidDCOVN/vUpoi1oazxJdM6s6mDmYI/TTgMI0A=="]]}' + + // Parse the `get_wallet_address` RPC response. + let jettonAddressBocEncoded = "te6cckEBAQEAJAAAQ4AFvT5rqwxcbKfITqnkwL+go4Zi9bulRHAtLt4cjjFdK7B8L+Cq" + let jettonAddress = TONAddressConverter.fromBoc(boc: jettonAddressBocEncoded) + XCTAssertEqual(jettonAddress, "UQAt6fNdWGLjZT5CdU8mBf0FHDMXrd0qI4FpdvDkcYrpXV5H") + } + + func testBuildV4R2StateInit() { + let publicKeyData = Data(hexString: "f229a9371fa7c2108b3d90ea22c9be705ff5d0cfeaee9cbb9366ff0171579357")! + let publicKey = PublicKey(data: publicKeyData, type: .ed25519)! + let stateInit = TONWallet.buildV4R2StateInit(publicKey: publicKey, workchain: 0, walletId: 0x29a9a317)! + XCTAssertEqual(stateInit, "te6cckECFgEAAwQAAgE0AQIBFP8A9KQT9LzyyAsDAFEAAAAAKamjF/IpqTcfp8IQiz2Q6iLJvnBf9dDP6u6cu5Nm/wFxV5NXQAIBIAQFAgFIBgcE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8ICQoLAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNDA0CASAODwBu0gf6ANTUIvkABcjKBxXL/8nQd3SAGMjLBcsCIs8WUAX6AhTLaxLMzMlz+wDIQBSBAQj0UfKnAgBwgQEI1xj6ANM/yFQgR4EBCPRR8qeCEG5vdGVwdIAYyMsFywJQBs8WUAT6AhTLahLLH8s/yXP7AAIAbIEBCNcY+gDTPzBSJIEBCPRZ8qeCEGRzdHJwdIAYyMsFywJQBc8WUAP6AhPLassfEss/yXP7AAAK9ADJ7VQAeAH6APQEMPgnbyIwUAqhIb7y4FCCEHBsdWeDHrFwgBhQBMsFJs8WWPoCGfQAy2kXyx9SYMs/IMmAQPsABgCKUASBAQj0WTDtRNCBAUDXIMgBzxb0AMntVAFysI4jghBkc3Rygx6xcIAYUAXLBVADzxYj+gITy2rLH8s/yYBA+wCSXwPiAgEgEBEAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAIBWBITABG4yX7UTQ1wsfgAPbKd+1E0IEBQNch9AQwAsjKB8v/ydABgQEI9ApvoTGACASAUFQAZrc52omhAIGuQ64X/wAAZrx32omhAEGuQ64WPwEXtMkg=") + } + + func testMessageSigner() { + // The private key has been derived by using [ton-mnemonic](https://www.npmjs.com/package/tonweb-mnemonic/v/0.0.2) + // from the following mnemonic: + // document shield addict crime broom point story depend suit satisfy test chicken valid tail speak fortune sound drill seek cube cheap body music recipe + let privateKeyData = Data(hexString: "112d4e2e700a468f1eae699329202f1ee671d6b665caa2d92dea038cf3868c18")! + let privateKey = PrivateKey(data: privateKeyData)! + let message = "Hello world" + let signature = TONMessageSigner.signMessage(privateKey: privateKey, message: message)! + // The following signature has been computed by calling `window.ton.send("ton_personalSign", { data: "Hello world" });`. + XCTAssertEqual(signature, "2490fbaa72aec0b77b19162bbbe0b0e3f7afd42cc9ef469f0494cd4a366a4bf76643300cd5991f66bce6006336742b8d1d435d541d244dcc013d428472e89504") + } + + func testSign() { + let privateKeyData = Data(hexString: "c38f49de2fb13223a9e7d37d5d0ffbdd89a5eb7c8b0ee4d1c299f2cefe7dc4a0")! + + let transfer = TheOpenNetworkTransfer.with { + $0.dest = "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q" + $0.amount = 10 + $0.mode = UInt32(TheOpenNetworkSendMode.payFeesSeparately.rawValue | TheOpenNetworkSendMode.ignoreActionPhaseErrors.rawValue) + $0.bounceable = true + } + + let input = TheOpenNetworkSigningInput.with { + $0.messages = [transfer] + $0.privateKey = privateKeyData + $0.sequenceNumber = 6 + $0.expireAt = 1671132440 + $0.walletVersion = TheOpenNetworkWalletVersion.walletV4R2 + } + + let output: TheOpenNetworkSigningOutput = AnySigner.sign(input: input, coin: .ton) + + // tx: https://tonscan.org/tx/3Z4tHpXNLyprecgu5aTQHWtY7dpHXEoo11MAX61Xyg0= + let expectedString = "te6cckEBBAEArQABRYgBsLf6vJOEq42xW0AoyWX0K+uBMUcXFDLFqmkDg6k1Io4MAQGcEUPkil2aZ4s8KKparSep/OKHMC8vuXafFbW2HGp/9AcTRv0J5T4dwyW1G0JpHw+g5Ov6QI3Xo0O9RFr3KidICimpoxdjm3UYAAAABgADAgFiYgAzffHi4B365BPJfIJk/F+URKU1UekJ6g4QK02ypVb22YhQAAAAAAAAAAAAAAAAAQMAAA08Nzs=" + + XCTAssertEqual(output.encoded, expectedString) + } + + func testJettonTransferSign() { + let privateKeyData = Data(hexString: "c054900a527538c1b4325688a421c0469b171c29f23a62da216e90b0df2412ee")! + + let jettonTransfer = TheOpenNetworkJettonTransfer.with { + $0.jettonAmount = 500 * 1000 * 1000 + $0.toOwner = "EQAFwMs5ha8OgZ9M4hQr80z9NkE7rGxUpE1hCFndiY6JnDx8" + $0.responseAddress = "EQBaKIMq5Am2p_rfR1IFTwsNWHxBkOpLTmwUain5Fj4llTXk" + $0.forwardAmount = 1 + } + + let transfer = TheOpenNetworkTransfer.with { + $0.dest = "EQBiaD8PO1NwfbxSkwbcNT9rXDjqhiIvXWymNO-edV0H5lja" + $0.amount = 100 * 1000 * 1000 + $0.mode = UInt32(TheOpenNetworkSendMode.payFeesSeparately.rawValue | TheOpenNetworkSendMode.ignoreActionPhaseErrors.rawValue) + $0.comment = "test comment" + $0.bounceable = true + $0.jettonTransfer = jettonTransfer + } + + let input = TheOpenNetworkSigningInput.with { + $0.messages = [transfer] + $0.privateKey = privateKeyData + $0.sequenceNumber = 1 + $0.expireAt = 1787693046 + $0.walletVersion = TheOpenNetworkWalletVersion.walletV4R2 + } + + let output: TheOpenNetworkSigningOutput = AnySigner.sign(input: input, coin: .ton) + + // tx: https://testnet.tonscan.org/tx/Er_oT5R3QK7D-qVPBKUGkJAOOq6ayVls-mgEphpI9Ck= + let expectedString = "te6cckECBAEAARUAAUWIALRRBlXIE21P9b6OpAqeFhqw+IMh1Jac2CjUU/IsfEsqDAEBnGiFlaLItV573gJqBvctP5j3jVKlLuxmO+pnW0QGlXjXgzjw5YeTNwRG9upJHOl6GA3pFetKNojqGzfkxku+owUpqaMXao4H9gAAAAEAAwIBaGIAMTQfh52puD7eKUmDbhqfta4cdUMRF662Uxp3zzqug/MgL68IAAAAAAAAAAAAAAAAAAEDAMoPin6lAAAAAAAAAABB3NZQCAALgZZzC14dAz6ZxChX5pn6bIJ3WNipSJrCELO7Ex0TOQAWiiDKuQJtqf630dSBU8LDVh8QZDqS05sFGop+RY+JZUICAAAAAHRlc3QgY29tbWVudG/bd5c=" + + XCTAssertEqual(output.encoded, expectedString) + } + + func testTransferCustomPayloadSign() { + let privateKeyData = Data(hexString: "5525e673087587bc0efd7ab09920ef7d3c1bf6b854a661430244ca59ab19e9d1")! + + // Doge chatbot contract payload to be deployed. + // Docs: https://docs.ton.org/develop/dapps/ton-connect/transactions#smart-contract-deployment + let dogeChatbotStateInit = "te6cckEBBAEAUwACATQBAgEU/wD0pBP0vPLICwMAEAAAAZDrkbgQAGrTMAGCCGlJILmRMODQ0wMx+kAwi0ZG9nZYcCCAGMjLBVAEzxaARfoCE8tqEssfAc8WyXP7AO4ioYU=" + // Doge chatbot's address after the contract is deployed. + let dogeChatbotDeployingAddress = "0:3042cd5480da232d5ac1d9cbe324e3c9eb58f167599f6b7c20c6e638aeed0335" + + // The comment has nothing to do with Doge chatbot. + // It's just used to attach the following ASCII comment to the transaction: + // "This transaction deploys Doge Chatbot contract" + let commentPayload = "te6cckEBAQEANAAAZAAAAABUaGlzIHRyYW5zYWN0aW9uIGRlcGxveXMgRG9nZSBDaGF0Ym90IGNvbnRyYWN0v84vSg==" + + let customPayload = TheOpenNetworkCustomPayload.with { + $0.stateInit = dogeChatbotStateInit + $0.payload = commentPayload + } + + let transfer = TheOpenNetworkTransfer.with { + $0.dest = dogeChatbotDeployingAddress + // 0.069 TON + $0.amount = 69_000_000 + $0.mode = UInt32(TheOpenNetworkSendMode.payFeesSeparately.rawValue | TheOpenNetworkSendMode.ignoreActionPhaseErrors.rawValue) + $0.bounceable = false + $0.customPayload = customPayload + } + + let input = TheOpenNetworkSigningInput.with { + $0.messages = [transfer] + $0.privateKey = privateKeyData + $0.sequenceNumber = 4 + $0.expireAt = 1721939714 + $0.walletVersion = TheOpenNetworkWalletVersion.walletV4R2 + } + + let output: TheOpenNetworkSigningOutput = AnySigner.sign(input: input, coin: .ton) + + // Successfully broadcasted: https://tonviewer.com/transaction/f4b7ed2247b1adf54f33dd2fd99216fbd61beefb281542d0b330ccea9b8d0338 + let expectedString = "te6cckECCAEAATcAAUWIAfq4NsPLegfou/MPhtHE9YuzV3gnI/q6jm3MRJh2PtpaDAEBnPbyCSsWrOZpEjb7ZFxz5yYi+an6M6Lnq7rI7TFWdDS76LEtGBrVVrhMGziwxuy6LCVtsMBikI7RPVQ89FCIAAYpqaMXZqK3AgAAAAQAAwICaUIAGCFmqkBtEZatYOzl8ZJx5PWseLOsz7W+EGNzHFd2gZqgIObaAAAAAAAAAAAAAAAAAAPAAwQCATQFBgBkAAAAAFRoaXMgdHJhbnNhY3Rpb24gZGVwbG95cyBEb2dlIENoYXRib3QgY29udHJhY3QBFP8A9KQT9LzyyAsHABAAAAGQ65G4EABq0zABgghpSSC5kTDg0NMDMfpAMItGRvZ2WHAggBjIywVQBM8WgEX6AhPLahLLHwHPFslz+wAa2r/S" + + XCTAssertEqual(output.encoded, expectedString) + } +} diff --git a/swift/Tests/Blockchains/ThetaFuelTests.swift b/swift/Tests/Blockchains/ThetaFuelTests.swift new file mode 100644 index 00000000000..e0c42392f32 --- /dev/null +++ b/swift/Tests/Blockchains/ThetaFuelTests.swift @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class ThetaFuelTests: XCTestCase { + func testAddress() { + let key = PrivateKey(data: Data(hexString: "4646464646464646464646464646464646464646464646464646464646464646")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: false) + let address = AnyAddress(publicKey: pubkey, coin: .thetaFuel) + let expected = AnyAddress(string: "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", coin: .thetaFuel)! + + XCTAssertEqual(address.description, expected.description) + } +} diff --git a/swift/Tests/Blockchains/ThetaTests.swift b/swift/Tests/Blockchains/ThetaTests.swift index 17176fdbc71..37e3508cd12 100644 --- a/swift/Tests/Blockchains/ThetaTests.swift +++ b/swift/Tests/Blockchains/ThetaTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/TronTests.swift b/swift/Tests/Blockchains/TronTests.swift index 897c9787973..9c5c7d6ebea 100644 --- a/swift/Tests/Blockchains/TronTests.swift +++ b/swift/Tests/Blockchains/TronTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore @@ -13,6 +11,18 @@ class TronTests: XCTestCase { let address = AnyAddress(string: "TLWEciM1CjP5fJqM2r9wymAidkkYtTU5k3", coin: .tron)! XCTAssertEqual(address.description, "TLWEciM1CjP5fJqM2r9wymAidkkYtTU5k3") } + + func testSignDirect() { + let input = TronSigningInput.with { + $0.privateKey = Data(hexString: "2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")! + $0.txID = "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb" + } + + let output: TronSigningOutput = AnySigner.sign(input: input, coin: .tron) + + XCTAssertEqual(output.id, Data(hexString: "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb")) + XCTAssertEqual(output.signature, Data(hexString: "77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00")) + } func testSign() { let contract = TronTransferContract.with { @@ -64,4 +74,13 @@ class TronTests: XCTestCase { """ XCTAssertJSONEqual(output.json, expectedJSON) } + + func testMessageAndVerifySigner() { + let privateKey = PrivateKey(data: Data(hexString: "75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")!)! + let msg = "Hello World" + let signature = TronMessageSigner.signMessage(privateKey: privateKey, message: msg) + XCTAssertEqual(signature, "9bb6d11ec8a6a3fb686a8f55b123e7ec4e9746a26157f6f9e854dd72f5683b450397a7b0a9653865658de8f9243f877539882891bad30c7286c3bf5622b900471b") + let pubKey = privateKey.getPublicKey(coinType: .tron) + XCTAssertTrue(TronMessageSigner.verifyMessage(pubKey: pubKey, message: msg, signature: signature)) + } } diff --git a/swift/Tests/Blockchains/WanchainTests.swift b/swift/Tests/Blockchains/WanchainTests.swift index 8718ce3cd9b..f4b2b1b87a0 100644 --- a/swift/Tests/Blockchains/WanchainTests.swift +++ b/swift/Tests/Blockchains/WanchainTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/WavesTests.swift b/swift/Tests/Blockchains/WavesTests.swift index 75c9f4b6154..3f16f7bdfd0 100644 --- a/swift/Tests/Blockchains/WavesTests.swift +++ b/swift/Tests/Blockchains/WavesTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/ZcashTests.swift b/swift/Tests/Blockchains/ZcashTests.swift index bd7a861a0f4..afcd0ef80d0 100644 --- a/swift/Tests/Blockchains/ZcashTests.swift +++ b/swift/Tests/Blockchains/ZcashTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore @@ -74,4 +72,12 @@ class ZcashTests: XCTestCase { XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok) XCTAssertEqual(output.encoded.hexString, "0400008085202f890153685b8809efc50dd7d5cb0906b307a1b8aa5157baa5fc1bd6fe2d0344dd193a000000006b483045022100ca0be9f37a4975432a52bb65b25e483f6f93d577955290bb7fb0060a93bfc92002203e0627dff004d3c72a957dc9f8e4e0e696e69d125e4d8e275d119001924d3b48012103b243171fae5516d1dc15f9178cfcc5fdc67b0a883055c117b01ba8af29b953f6ffffffff0140720700000000001976a91449964a736f3713d64283fd0018626ba50091c7e988ac00000000000000000000000000000000000000") } + + func testLockScript() { + let script = BitcoinScript.lockScriptForAddress(address: "t1NsqaL1G2XD6xWfVmeAi9gLUVFct59zJu4", coin: .zcash) + XCTAssertTrue(!script.data.isEmpty) + + let script2 = BitcoinScript.lockScriptForAddress(address: "t1XeXHdaaXbdEaBCz1cMib5rvg34RjTWR6N", coin: .zelcash) + XCTAssertTrue(!script2.data.isEmpty) + } } diff --git a/swift/Tests/Blockchains/ZcoinTests.swift b/swift/Tests/Blockchains/ZcoinTests.swift index 7014d4e274d..42b20607823 100644 --- a/swift/Tests/Blockchains/ZcoinTests.swift +++ b/swift/Tests/Blockchains/ZcoinTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Blockchains/ZenTests.swift b/swift/Tests/Blockchains/ZenTests.swift new file mode 100644 index 00000000000..ab7adddffd9 --- /dev/null +++ b/swift/Tests/Blockchains/ZenTests.swift @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class ZenTests: XCTestCase { + func testAddress() { + let key = PrivateKey(data: Data(hexString: "3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")!)! + let pubkey = key.getPublicKeySecp256k1(compressed: true) + let address = AnyAddress(publicKey: pubkey, coin: .zen) + let addressFromString = AnyAddress(string: "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg", coin: .zen)! + + XCTAssertEqual(pubkey.data.hexString, "02b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfe") + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSign() { + let key = PrivateKey(data: Data(hexString: "3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")!)! + let blockHash = Data(hexString: "81dc725fd33fada1062323802eefb54d3325d924d4297a692214560400000000")! + let blockHeight = Int64(1147624) + let utxos = [ + BitcoinUnspentTransaction.with { + $0.outPoint.hash = Data(hexString: "a39e13b5ab406547e31284cd96fb40ed271813939c195ae7a86cd67fb8a4de62")! + $0.outPoint.index = 0 + $0.outPoint.sequence = UINT32_MAX + $0.script = Data(hexString: "76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4")! + $0.amount = 17600 + } + ] + + // Plan + let plan = BitcoinTransactionPlan.with { + $0.amount = 10000 + $0.fee = 226 + $0.change = 7374 + $0.utxos = utxos + $0.preblockhash = blockHash + $0.preblockheight = blockHeight + } + + let input = BitcoinSigningInput.with { + $0.amount = 10000 + $0.toAddress = "zngBGZGKaaBamofSuFw5WMnvU2HQAtwGeb5" + $0.changeAddress = "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg" + $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .zen) + $0.coinType = CoinType.zen.rawValue + $0.privateKey = [key.data] + $0.plan = plan + } + + // Sign + let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .zen) + XCTAssertEqual(output.error, .ok) + XCTAssertEqual(output.encoded.hexString, "0100000001a39e13b5ab406547e31284cd96fb40ed271813939c195ae7a86cd67fb8a4de62000000006a473044022014d687c0bee0b7b584db2eecbbf73b545ee255c42b8edf0944665df3fa882cfe02203bce2412d93c5a56cb4806ddd8297ff05f8fc121306e870bae33377a58a02f05012102b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfeffffffff0210270000000000003f76a914a58d22659b1082d1fa8698fc51996b43281bfce788ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4ce1c0000000000003f76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b400000000") + + } +} diff --git a/swift/Tests/Blockchains/ZilliqaTests.swift b/swift/Tests/Blockchains/ZilliqaTests.swift index a523cd26c97..060db939033 100644 --- a/swift/Tests/Blockchains/ZilliqaTests.swift +++ b/swift/Tests/Blockchains/ZilliqaTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index 1c932aa79bd..6a572ab6048 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore @@ -19,6 +17,9 @@ class CoinAddressDerivationTests: XCTestCase { let address = coin.address(string: derivedAddress) switch coin { + case .acala: + let expectedResult = "25GGezx3LWFQj6HZpYzoWoVzLsHojGtybef3vthC9nd19ms3" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .aeternity: let expectedResult = "ak_QDHJSfvHG9sDHBobaWt2TAGhuhipYjEqZEH34bWugpJfJc3GN" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -34,9 +35,15 @@ class CoinAddressDerivationTests: XCTestCase { case .binance: let expectedResult = "bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .tbinance: + let expectedResult = "tbnb12vtaxl9952zm6rwf7v8jerq74pvaf77fkw9xhl" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .bitcoin: let expectedResult = "bc1quvuarfksewfeuevuc6tn0kfyptgjvwsvrprk9d" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .bitcoinDiamond: + let expectedResult = "1KaRW9xPPtCTZ9FdaTHduCPck4YvSeEWNn" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .bitcoinCash: let expectedResult = "bitcoincash:qpzl3jxkzgvfd9flnd26leud5duv795fnv7vuaha70" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -64,17 +71,21 @@ class CoinAddressDerivationTests: XCTestCase { case .dogecoin: let expectedResult = "DJRFZNg8jkUtjcpo2zJd92FUAzwRjitw6f" assertCoinDerivation(coin, expectedResult, derivedAddress, address) - case .elrond: + case .multiversX: let expectedResult = "erd1jfcy8aeru6vlx4fe6h3pc3vlpe2cnnur5zetxdhp879yagq7vqvs8na4f8" assertCoinDerivation(coin, expectedResult, derivedAddress, address) - case .eos: + case .eos, .wax: let expectedResult = "EOS6hs8sRvGSzuQtq223zwJipMzqTJpXUVjyvHPvPwBSZWWrJTJkg" assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ethereum, .smartChain, .polygon, .optimism, + .zksync, + .polygonzkEVM, + .scroll, .arbitrum, + .arbitrumNova, .ecochain, .avalancheCChain, .xdai, @@ -89,7 +100,26 @@ class CoinAddressDerivationTests: XCTestCase { .evmos, .moonriver, .moonbeam, - .klaytn: + .kavaEvm, + .kaia, + .meter, + .okxchain, + .confluxeSpace, + .opBNB, + .acalaEVM, + .neon, + .base, + .linea, + .greenfield, + .mantle, + .zenEON, + .mantaPacific, + .zetaEVM, + .merlin, + .lightlink, + .blast, + .bounceBit, + .zkLinkNova: let expectedResult = "0x8f348F300873Fd5DA36950B2aC75a26584584feE" assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .ronin: @@ -98,6 +128,9 @@ class CoinAddressDerivationTests: XCTestCase { case .ethereumClassic: let expectedResult = "0x078bA3228F3E6C08bEEac9A005de0b7e7089aD1c" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .rootstock: + let expectedResult = "0xA2D7065F94F838a3aB9C04D67B312056846424Df" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .filecoin: let expectedResult = "f1zzykebxldfcakj5wdb5n3n7priul522fnmjzori" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -119,6 +152,9 @@ class CoinAddressDerivationTests: XCTestCase { case .ioTeX: let expectedResult = "io1qw9cccecw09q7p5kzyqtuhfhvah2mhfrc84jfk" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .ioTeXEVM: + let expectedResult = "0x038B8C633873Ca0f06961100BE5d37676EADDD23" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .litecoin: let expectedResult = "ltc1qhd8fxxp2dx3vsmpac43z6ev0kllm4n53t5sk0u" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -137,6 +173,9 @@ class CoinAddressDerivationTests: XCTestCase { case .neo: let expectedResult = "AT6w7PJvwPcSqHvtbNBY2aHPDv12eW5Uuf" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .nervos: + let expectedResult = "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3"; + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .nimiq: let expectedResult = "NQ76 7AVR EHDA N05U X7SY XB14 XJU7 8ERV GM6H" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -158,6 +197,9 @@ class CoinAddressDerivationTests: XCTestCase { case .poanetwork: let expectedResult = "0xe8a3e8bE17E172B6926130eAfB521e9D2849aca9" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .pivx: + let expectedResult = "D81AqC8zKma3Cht4TbVuh4jyVVyLkZULCm" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .polkadot: let expectedResult = "13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -173,19 +215,21 @@ class CoinAddressDerivationTests: XCTestCase { case .stellar: let expectedResult = "GA3H6I4C5XUBYGVB66KXR27JV5KS3APSTKRUWOIXZ5MVWZKVTLXWKZ2P" assertCoinDerivation(coin, expectedResult, derivedAddress, address) - case .terra: + case .terra, + .terraV2: let expectedResult = "terra1rh402g98t7sly8trzqw5cyracntlep6qe3smug" assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .tezos: let expectedResult = "tz1acnY9VbMagps26Kj3RfoGRWD9nYG5qaRX" assertCoinDerivation(coin, expectedResult, derivedAddress, address) - case .theta: + case .theta, + .thetaFuel: let expectedResult = "0x0d1fa20c218Fec2f2C55d52aB267940485fa5DA4" assertCoinDerivation(coin, expectedResult, derivedAddress, address) - case .thunderToken: + case .thunderCore: let expectedResult = "0x4b92b3ED6d8b24575Bf5ce4C6a86ED261DA0C8d7" assertCoinDerivation(coin, expectedResult, derivedAddress, address) - case .tomoChain: + case .viction: let expectedResult = "0xC74b6D8897cBa9A4b659d43fEF73C9cA852cE424" assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .tron: @@ -197,6 +241,9 @@ class CoinAddressDerivationTests: XCTestCase { case .viacoin: let expectedResult = "via1qnmsgjd6cvfprnszdgmyg9kewtjfgqflz67wwhc" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .verge: + let expectedResult = "DPb3Xz4vjB6QGLKDmrbprrtv4XzNqkADc2" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .wanchain: let expectedResult = "0xD5ca90b928279FE5D06144136a25DeD90127aC15" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -209,6 +256,12 @@ class CoinAddressDerivationTests: XCTestCase { case .zcash: let expectedResult = "t1YYnByMzdGhQv3W3rnjHMrJs6HH4Y231gy" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .komodo: + let expectedResult = "RCWJLXE5CSXydxdSnwcghzPgkFswERegyb" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .zen: + let expectedResult = "znUmzvod1f4P9LYsBhNxjqCDQvNSStAmYEX" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .firo: let expectedResult = "aEd5XFChyXobvEics2ppAqgK3Bgusjxtik" assertCoinDerivation(coin, expectedResult, derivedAddress, address) @@ -239,9 +292,114 @@ class CoinAddressDerivationTests: XCTestCase { case .ecash: let expectedResult = "ecash:qpelrdn7a0hcucjlf9ascz3lkxv7r3rffgzn6x5377" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .iost: + let expectedResult = "4av8w81EyzUgHonsVWqfs15WM4Vrpgox4BYYQWhNQDVu" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .syscoin: + let expectedResult = "sys1qkl640se3mwpt666e3lyywnwh09e9jquvx9x8qj" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .stratis: + let expectedResult = "strax1q0caanaw4nkf6fzwnzq2p7yum680e57pdg05zkm" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .nativeEvmos: let expectedResult = "evmos13u6g7vqgw074mgmf2ze2cadzvkz9snlwstd20d" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .everscale: + let expectedResult = "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04"; + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .ton: + let expectedResult = "UQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUaT4"; + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .aptos: + let expectedResult = "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"; + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .nebl: + let expectedResult = "NgDVaXAwNgBwb88xLiFKomfBmPkEh9F2d7"; + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .sui: + let expectedResult = "0xada112cfb90b44ba889cc5d39ac2bf46281e4a91f7919c693bcd9b8323e81ed2"; + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .hedera: + let expectedResult = "0.0.302a300506032b657003210049eba62f64d0d941045595d9433e65d84ecc46bcdb1421de55e05fcf2d8357d5"; + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .secret: + let expectedResult = "secret1f69sk5033zcdr2p2yf3xjehn7xvgdeq09d2llh" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .nativeInjective: + let expectedResult = "inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .agoric: + let expectedResult = "agoric18zvvgk6j3eq5wd7mqxccgt20gz2w94cy88aek5" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .stargaze: + let expectedResult = "stars142j9u5eaduzd7faumygud6ruhdwme98qycayaz" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .juno: + let expectedResult = "juno142j9u5eaduzd7faumygud6ruhdwme98qxkfz30" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .stride: + let expectedResult = "stride142j9u5eaduzd7faumygud6ruhdwme98qn029zl" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .axelar: + let expectedResult = "axelar142j9u5eaduzd7faumygud6ruhdwme98q52u3aj" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .crescent: + let expectedResult = "cre142j9u5eaduzd7faumygud6ruhdwme98q5veur7" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .kujira: + let expectedResult = "kujira142j9u5eaduzd7faumygud6ruhdwme98qpvgpme" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .nativeCanto: + let expectedResult = "canto13u6g7vqgw074mgmf2ze2cadzvkz9snlwqua5pd" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .comdex: + let expectedResult = "comdex142j9u5eaduzd7faumygud6ruhdwme98qhtgm0y" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .neutron: + let expectedResult = "neutron142j9u5eaduzd7faumygud6ruhdwme98q5mrmv5" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .sommelier: + let expectedResult = "somm142j9u5eaduzd7faumygud6ruhdwme98quc948e" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .fetchAI: + let expectedResult = "fetch142j9u5eaduzd7faumygud6ruhdwme98qrera5y" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .mars: + let expectedResult = "mars142j9u5eaduzd7faumygud6ruhdwme98qdenqrg" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .umee: + let expectedResult = "umee142j9u5eaduzd7faumygud6ruhdwme98qzjhxjp" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .coreum: + let expectedResult = "core1rawf376jz2lnchgc4wzf4h9c77neg3zldc7xa8" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .quasar: + let expectedResult = "quasar142j9u5eaduzd7faumygud6ruhdwme98q78symk" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .persistence: + let expectedResult = "persistence142j9u5eaduzd7faumygud6ruhdwme98q7gv2ch" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .akash: + let expectedResult = "akash142j9u5eaduzd7faumygud6ruhdwme98qal870f" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .noble: + let expectedResult = "noble142j9u5eaduzd7faumygud6ruhdwme98qc8l3wa" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .sei: + let expectedResult = "sei142j9u5eaduzd7faumygud6ruhdwme98qagm0sj" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .internetComputer: + let expectedResult = "6f8e568160a3c8362789848dc0fa52891964473c045cc25208a305fb35b7c4ab" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .tia: + let expectedResult = "celestia142j9u5eaduzd7faumygud6ruhdwme98qpwmfv7" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .nativeZetaChain: + let expectedResult = "zeta13u6g7vqgw074mgmf2ze2cadzvkz9snlwywj304" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .dydx: + let expectedResult = "dydx142j9u5eaduzd7faumygud6ruhdwme98qeayaky" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) @unknown default: fatalError() } diff --git a/swift/Tests/CoinTypeTests.swift b/swift/Tests/CoinTypeTests.swift index 5b49e9325d5..63b4f8e9b6a 100644 --- a/swift/Tests/CoinTypeTests.swift +++ b/swift/Tests/CoinTypeTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore @@ -14,7 +12,7 @@ class CoinTypeTests: XCTestCase { XCTAssertEqual(CoinType.litecoin.rawValue, 2) XCTAssertEqual(CoinType.tron.rawValue, 195) XCTAssertEqual(CoinType.ethereum.rawValue, 60) - XCTAssertEqual(CoinType.thunderToken.rawValue, 1001) + XCTAssertEqual(CoinType.thunderCore.rawValue, 1001) XCTAssertEqual(CoinType.wanchain.rawValue, 5718350) XCTAssertEqual(CoinType.callisto.rawValue, 820) XCTAssertEqual(CoinType.ethereumClassic.rawValue, 61) @@ -23,11 +21,25 @@ class CoinTypeTests: XCTestCase { XCTAssertEqual(CoinType.poanetwork.rawValue, 178) XCTAssertEqual(CoinType.veChain.rawValue, 818) XCTAssertEqual(CoinType.icon.rawValue, 74) - XCTAssertEqual(CoinType.tomoChain.rawValue, 889) + XCTAssertEqual(CoinType.viction.rawValue, 889) XCTAssertEqual(CoinType.tezos.rawValue, 1729) XCTAssertEqual(CoinType.qtum.rawValue, 2301) XCTAssertEqual(CoinType.nebulas.rawValue, 2718) XCTAssertEqual(CoinType.avalancheCChain.rawValue, 10009000) XCTAssertEqual(CoinType.xdai.rawValue, 10000100) } + + func testCoinDerivation() { + XCTAssertEqual(CoinType.bitcoin.derivationPath(), "m/84'/0'/0'/0/0") + XCTAssertEqual(CoinType.bitcoin.derivationPathWithDerivation(derivation: Derivation.bitcoinLegacy), "m/44'/0'/0'/0/0") + XCTAssertEqual(CoinType.solana.derivationPathWithDerivation(derivation: Derivation.solanaSolana), "m/44'/501'/0'/0'") + } + + func testDeriveAddressFromPublicKeyAndDerivation() { + let pkData = Data(hexString: "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798")! + let publicKey = PublicKey(data: pkData, type: .secp256k1)! + + let address = CoinType.bitcoin.deriveAddressFromPublicKeyAndDerivation(publicKey: publicKey, derivation: Derivation.bitcoinSegwit) + XCTAssertEqual(address, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4") + } } diff --git a/swift/Tests/CryptoBoxTests.swift b/swift/Tests/CryptoBoxTests.swift new file mode 100644 index 00000000000..03632513e81 --- /dev/null +++ b/swift/Tests/CryptoBoxTests.swift @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class CryptoBoxTests: XCTestCase { + func testEncryptDecryptEasy() { + let mySecret = CryptoBoxSecretKey() + let myPubkey = mySecret.getPublicKey() + + let otherSecret = CryptoBoxSecretKey() + let otherPubkey = otherSecret.getPublicKey() + + let message = "Well done is better than well said. -Benjamin Franklin" + let encrypted = CryptoBox.encryptEasy(mySecret: mySecret, otherPubkey: otherPubkey, message: Data(message.utf8)) + + // Step 2. Make sure the Box can be decrypted by the other side. + let decrypted = CryptoBox.decryptEasy(mySecret: otherSecret, otherPubkey: myPubkey, encrypted: encrypted)! + let decryptedStr = String(bytes: decrypted, encoding: .utf8) + XCTAssertEqual(decryptedStr, message) + } + + func testSecretKeyFromToBytes() { + let secretBytes = Data(hexString: "dd87000d4805d6fbd89ae1352f5e4445648b79d5e901c92aebcb610e9be468e4")! + XCTAssert(CryptoBoxSecretKey.isValid(data: secretBytes)) + let secret = CryptoBoxSecretKey(data: secretBytes) + XCTAssertEqual(secret?.data, secretBytes) + } + + func testPublicKeyFromToBytes() { + let publicBytes = Data(hexString: "afccabc5b28a8a1fd1cd880516f9c854ae2498d0d1b978b53a59f38e4ae55747")! + XCTAssert(CryptoBoxPublicKey.isValid(data: publicBytes)) + let pubkey = CryptoBoxPublicKey(data: publicBytes) + XCTAssertEqual(pubkey?.data, publicBytes) + } +} diff --git a/swift/Tests/DataTests.swift b/swift/Tests/DataTests.swift index eee71d6743a..d003c6e303d 100644 --- a/swift/Tests/DataTests.swift +++ b/swift/Tests/DataTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/DerivationPathTests.swift b/swift/Tests/DerivationPathTests.swift index e24ab38190a..7ca9110c591 100644 --- a/swift/Tests/DerivationPathTests.swift +++ b/swift/Tests/DerivationPathTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest @@ -10,6 +8,7 @@ import XCTest class DerivationPathTests: XCTestCase { func testInitWithIndices() { let path = DerivationPath(purpose: .bip44, coin: CoinType.ethereum.slip44Id, account: 0, change: 0, address: 0) + XCTAssertEqual(path.indices[0], DerivationPath.Index(44, hardened: true)) XCTAssertEqual(path.indices[1], DerivationPath.Index(60, hardened: true)) XCTAssertEqual(path.indices[2], DerivationPath.Index(0, hardened: true)) @@ -18,20 +17,20 @@ class DerivationPathTests: XCTestCase { } func testInitWithString() { - let path = DerivationPath("m/44'/60'/0'/0/0") - - XCTAssertNotNil(path) - XCTAssertEqual(path?.indices[0], DerivationPath.Index(44, hardened: true)) - XCTAssertEqual(path?.indices[1], DerivationPath.Index(60, hardened: true)) - XCTAssertEqual(path?.indices[2], DerivationPath.Index(0, hardened: true)) - XCTAssertEqual(path?.indices[3], DerivationPath.Index(0, hardened: false)) - XCTAssertEqual(path?.indices[4], DerivationPath.Index(0, hardened: false)) - - XCTAssertEqual(path?.purpose, Purpose(rawValue: 44)!) - XCTAssertEqual(path?.coinType, 60) - XCTAssertEqual(path?.account, 0) - XCTAssertEqual(path?.change, 0) - XCTAssertEqual(path?.address, 0) + let path = DerivationPath(string: "m/44'/60'/0'/0/0")! + let indices = path.indices + + XCTAssertEqual(indices[0], DerivationPath.Index(value: 44, hardened: true)) + XCTAssertEqual(indices[1], DerivationPath.Index(value: 60, hardened: true)) + XCTAssertEqual(indices[2], DerivationPath.Index(value: 0, hardened: true)) + XCTAssertEqual(indices[3], DerivationPath.Index(value: 0, hardened: false)) + XCTAssertEqual(indices[4], DerivationPath.Index(value: 0, hardened: false)) + + XCTAssertEqual(path.purpose, Purpose(rawValue: 44)!) + XCTAssertEqual(path.coinType, 60) + XCTAssertEqual(path.account, 0) + XCTAssertEqual(path.change, 0) + XCTAssertEqual(path.address, 0) } func testInitInvalid() { @@ -41,12 +40,14 @@ class DerivationPathTests: XCTestCase { func testDescription() { let path = DerivationPath("m/44'/60'/0'/0/0") + XCTAssertEqual(path?.description, "m/44'/60'/0'/0/0") } func testEqual() { let path1 = DerivationPath("m/44'/60'/0'/0/0") let path2 = DerivationPath("44'/60'/0'/0/0") + XCTAssertNotNil(path1) XCTAssertNotNil(path2) XCTAssertEqual(path1, path2) diff --git a/swift/Tests/HDWalletTests.swift b/swift/Tests/HDWalletTests.swift index 4267fed457d..789dad6a6c4 100644 --- a/swift/Tests/HDWalletTests.swift +++ b/swift/Tests/HDWalletTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest @@ -12,6 +10,38 @@ extension HDWallet { } class HDWalletTests: XCTestCase { + + func testFromMnemonicImmutableXMainnetFromSignature() { + let wallet = HDWallet(mnemonic: "obscure opera favorite shuffle mail tip age debate dirt pact cement loyal", passphrase: "")! + let starkDerivationPath = Ethereum.eip2645GetPath(ethAddress: "0xd0972E2312518Ca15A2304D56ff9cc0b7ea0Ea37", layer: "starkex", application: "immutablex", index: "1") + XCTAssertEqual(starkDerivationPath, "m/2645'/579218131'/211006541'/2124474935'/1609799702'/1") + + // Retrieve eth private key + let ethPrivateKey = wallet.getKeyForCoin(coin: CoinType.ethereum) + XCTAssertEqual(ethPrivateKey.data.hexString, "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"); + + // StarkKey Derivation Path + let derivationPath = DerivationPath(string: starkDerivationPath)! + + // Retrieve Stark Private key part + let ethMsg = "Only sign this request if you’ve initiated an action with Immutable X." + let ethSignature = EthereumMessageSigner.signMessageImmutableX(privateKey: ethPrivateKey, message: ethMsg) + XCTAssertEqual(ethSignature, "18b1be8b78807d3326e28bc286d7ee3d068dcd90b1949ce1d25c1f99825f26e70992c5eb7f44f76b202aceded00d74f771ed751f2fe538eec01e338164914fe001") + let starkPrivateKey = StarkWare.getStarkKeyFromSignature(derivationPath: derivationPath, signature: ethSignature) + XCTAssertEqual(starkPrivateKey.data.hexString, "04be51a04e718c202e4dca60c2b72958252024cfc1070c090dd0f170298249de") + let starkPublicKey = starkPrivateKey.getPublicKeyByType(pubkeyType: .starkex) + XCTAssertEqual(starkPublicKey.data.hexString, "00e5b9b11f8372610ef35d647a1dcaba1a4010716588d591189b27bf3c2d5095") + + // Account Register + let ethMsgToRegister = "Only sign this key linking request from Immutable X" + let ethSignatureToRegister = EthereumMessageSigner.signMessageImmutableX(privateKey: ethPrivateKey, message: ethMsgToRegister) + XCTAssertEqual(ethSignatureToRegister, "646da4160f7fc9205e6f502fb7691a0bf63ecbb74bbb653465cd62388dd9f56325ab1e4a9aba99b1661e3e6251b42822855a71e60017b310b9f90e990a12e1dc01") + let starkMsg = "463a2240432264a3aa71a5713f2a4e4c1b9e12bbb56083cd56af6d878217cf" + let starkSignature = StarkExMessageSigner.signMessage(privateKey: starkPrivateKey, message: starkMsg) + XCTAssertEqual(starkSignature, "04cf5f21333dd189ada3c0f2a51430d733501a9b1d5e07905273c1938cfb261e05b6013d74adde403e8953743a338c8d414bb96bf69d2ca1a91a85ed2700a528") + XCTAssertTrue(StarkExMessageSigner.verifyMessage(pubKey: starkPublicKey, message: starkMsg, signature: starkSignature)) + } + func testCreateFromMnemonic() { let wallet = HDWallet.test @@ -49,6 +79,51 @@ class HDWalletTests: XCTestCase { XCTAssertEqual(masterKey.data.hexString, "e120fc1ef9d193a851926ebd937c3985dc2c4e642fb3d0832317884d5f18f3b3") } + func testGetKeyForCoin() { + let coin = CoinType.bitcoin + let wallet = HDWallet.test + let key = wallet.getKeyForCoin(coin: coin) + + let address = coin.deriveAddress(privateKey: key) + XCTAssertEqual(address, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85") + } + + func testGetKeyDerivation() { + let coin = CoinType.bitcoin + let wallet = HDWallet.test + + let key1 = wallet.getKeyDerivation(coin: coin, derivation: .bitcoinSegwit) + XCTAssertEqual(key1.data.hexString, "1901b5994f075af71397f65bd68a9fff8d3025d65f5a2c731cf90f5e259d6aac") + + let key2 = wallet.getKeyDerivation(coin: coin, derivation: .bitcoinLegacy) + XCTAssertEqual(key2.data.hexString, "28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4") + + let key3 = wallet.getKeyDerivation(coin: coin, derivation: .bitcoinTestnet) + XCTAssertEqual(key3.data.hexString, "ca5845e1b43e3adf577b7f110b60596479425695005a594c88f9901c3afe864f") + } + + func testGetAddressForCoin() { + let coin = CoinType.bitcoin + let wallet = HDWallet.test + + let address = wallet.getAddressForCoin(coin: coin) + XCTAssertEqual(address, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85") + } + + func testGetAddressDerivation() { + let coin = CoinType.bitcoin + let wallet = HDWallet.test + + let address1 = wallet.getAddressDerivation(coin: coin, derivation: .bitcoinSegwit) + XCTAssertEqual(address1, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85") + + let address2 = wallet.getAddressDerivation(coin: coin, derivation: .bitcoinLegacy) + XCTAssertEqual(address2, "1PeUvjuxyf31aJKX6kCXuaqxhmG78ZUdL1") + + let address3 = wallet.getAddressDerivation(coin: coin, derivation: .bitcoinTestnet) + XCTAssertEqual(address3, "tb1qwgpxgwn33z3ke9s7q65l976pseh4edrzfmyvl0") + } + func testDerive() { let wallet = HDWallet.test @@ -169,6 +244,15 @@ class HDWalletTests: XCTestCase { XCTAssertEqual("bnb1wk7kxw0qrvxe2pj9mk6ydjx0t4j9jla8pja0td", address.description) } + func testDeriveBinanceTestnet() { + let binance = CoinType.binance + let wallet = HDWallet.test + let key = wallet.getKeyForCoin(coin: binance) + let address = AnyAddress(publicKey: key.getPublicKeySecp256k1(compressed: true), coin: binance, hrp: "tbnb") + + XCTAssertEqual("tbnb1wk7kxw0qrvxe2pj9mk6ydjx0t4j9jla8085ttu", address.description) + } + func testDeriveZcash() { let zcash = CoinType.zcash let wallet = HDWallet.test @@ -386,10 +470,10 @@ class HDWalletTests: XCTestCase { XCTAssertEqual("RHQmrg7nNFnRUwg2mH7GafhRY3ZaF6FB2x", address) } - func testDeriveTerra() { - let coin = CoinType.terra + func testDeriveTerraV2() { + let coin = CoinType.terraV2 let key = HDWallet.test.getKeyForCoin(coin: coin) - let address = CoinType.terra.deriveAddress(privateKey: key) + let address = CoinType.terraV2.deriveAddress(privateKey: key) XCTAssertEqual(address, "terra1jf9aaj9myrzsnmpdr7twecnaftzmku2mhs2hfe") } diff --git a/swift/Tests/HashTests.swift b/swift/Tests/HashTests.swift index fc192dceebe..da1c58574d2 100644 --- a/swift/Tests/HashTests.swift +++ b/swift/Tests/HashTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/Keystore/KeyStoreTests.swift b/swift/Tests/Keystore/KeyStoreTests.swift index 3ae5529e79e..3017d10c304 100755 --- a/swift/Tests/Keystore/KeyStoreTests.swift +++ b/swift/Tests/Keystore/KeyStoreTests.swift @@ -183,6 +183,20 @@ class KeyStoreTests: XCTestCase { XCTAssertNotNil(storedData) XCTAssertNotNil(PrivateKey(data: storedData!)) } + + func testImportPrivateKeyAES256() throws { + let keyStore = try KeyStore(keyDirectory: keyDirectory) + let privateKeyData = Data(hexString: "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c")! + let key = StoredKey.importPrivateKeyWithEncryption(privateKey: privateKeyData, name: "name", password: Data("password".utf8), coin: .ethereum, encryption: StoredKeyEncryption.aes256Ctr)! + let json = key.exportJSON()! + + let wallet = try keyStore.import(json: json, name: "name", password: "password", newPassword: "newPassword", coins: [.ethereum]) + let storedData = wallet.key.decryptPrivateKey(password: Data("newPassword".utf8)) + + XCTAssertNotNil(keyStore.keyWallet) + XCTAssertNotNil(storedData) + XCTAssertNotNil(PrivateKey(data: storedData!)) + } func testImportPrivateKey() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) @@ -208,9 +222,18 @@ class KeyStoreTests: XCTestCase { XCTAssertEqual(wallet.accounts.count, 1) XCTAssertNotNil(keyStore.hdWallet) } + + func testImportWalletAES256() throws { + let keyStore = try KeyStore(keyDirectory: keyDirectory) + let wallet = try keyStore.import(mnemonic: mnemonic, name: "name", encryptPassword: "newPassword", coins: [.ethereum], encryption: .aes256Ctr) + let storedData = wallet.key.decryptMnemonic(password: Data("newPassword".utf8)) - func testImportJSON() throws { + XCTAssertNotNil(storedData) + XCTAssertEqual(wallet.accounts.count, 1) + XCTAssertNotNil(keyStore.hdWallet) + } + func testImportJSON() throws { let expected = """ { "activeAccounts": [{ diff --git a/swift/Tests/LiquidStakingTests.swift b/swift/Tests/LiquidStakingTests.swift new file mode 100644 index 00000000000..009ad2759d1 --- /dev/null +++ b/swift/Tests/LiquidStakingTests.swift @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class LiquidStakingTests: XCTestCase { + + func testStraderStakePolygon() throws { + let input = LiquidStakingInput.with { + $0.blockchain = .polygon + $0.protocol = .strader + $0.smartContractAddress = "0xfd225c9e6601c9d38d8f98d8731bf59efcf8c0e3" + $0.stake = LiquidStakingStake.with { + $0.amount = "1000000000000000000" + $0.asset = LiquidStakingAsset.with { + $0.stakingToken = .pol + } + } + } + let inputSerialized = try input.serializedData() + XCTAssertEqual(inputSerialized.hexString, "0a170a00121331303030303030303030303030303030303030222a3078666432323563396536363031633964333864386639386438373331626635396566636638633065333001") + let outputData = LiquidStaking.buildRequest(input: inputSerialized) + XCTAssertEqual(outputData.count, 68) + let outputProto = try LiquidStakingOutput(serializedData: outputData) + var txInput = outputProto.ethereum + + txInput.chainID = Data(hexString: "89")! + txInput.nonce = Data(hexString: "01")! + txInput.maxFeePerGas = Data(hexString: "8fbcc8fcd8")! + txInput.maxInclusionFeePerGas = Data(hexString: "085e42c7c0")! + txInput.gasLimit = Data(hexString: "01c520")! + txInput.privateKey = Data(hexString: "4a160b803c4392ea54865d0c5286846e7ad5e68fbd78880547697472b22ea7ab")! + + let output: EthereumSigningOutput = AnySigner.sign(input: txInput, coin: .ethereum) + + XCTAssertEqual(output.encoded.hexString, "02f87a81890185085e42c7c0858fbcc8fcd88301c52094fd225c9e6601c9d38d8f98d8731bf59efcf8c0e3880de0b6b3a764000084c78cf1a0c001a04bcf92394d53d4908130cc6d4f7b2491967f9d6c59292b84c1f56adc49f6c458a073e09f45d64078c41a7946ffdb1dee8e604eb76f318088490f8f661bb7ddfc54") + // Successfully broadcasted: https://polygonscan.com/tx/0x0f6c4f7a893c3f08be30d2ea24479d7ed4bdba40875d07cfd607cf97980b7cf0 + } +} diff --git a/swift/Tests/MnemonicTests.swift b/swift/Tests/MnemonicTests.swift index e76baa79185..3d2f043714d 100644 --- a/swift/Tests/MnemonicTests.swift +++ b/swift/Tests/MnemonicTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest diff --git a/swift/Tests/PBKDF2Tests.swift b/swift/Tests/PBKDF2Tests.swift index c5ccbadc1af..cb3645cdf95 100644 --- a/swift/Tests/PBKDF2Tests.swift +++ b/swift/Tests/PBKDF2Tests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/PrivateKeyTests.swift b/swift/Tests/PrivateKeyTests.swift index 6ecdf044344..1fd2ce348bb 100644 --- a/swift/Tests/PrivateKeyTests.swift +++ b/swift/Tests/PrivateKeyTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest @@ -20,6 +18,22 @@ class PrivateKeyTests: XCTestCase { let privateKey = PrivateKey(data: Data(hexString: "0xdeadbeef")!) XCTAssertNil(privateKey) } + + func testStarkKeyCreation() { + let data = Data(hexString: "06cf0a8bf113352eb863157a45c5e5567abb34f8d32cddafd2c22aa803f4892c")! + XCTAssertTrue(PrivateKey.isValid(data: data, curve: .starkex)) + let privateKey = PrivateKey(data: data)! + let pubKey = privateKey.getPublicKeyByType(pubkeyType: .starkex) + XCTAssertEqual(pubKey.data.hexString, "02d2bbdc1adaf887b0027cdde2113cfd81c60493aa6dc15d7887ddf1a82bc831") + } + + func testStarkKeySigning() { + let data = Data(hexString: "0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79")! + let privateKey = PrivateKey(data: data)! + let digest = Data(hexString: "06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76")! + let signature = privateKey.sign(digest: digest, curve: .starkex)! + XCTAssertEqual(signature.hexString, "061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a") + } func testIsValidString() { let data = Data(hexString: "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")! @@ -35,52 +49,14 @@ class PrivateKeyTests: XCTestCase { XCTAssertEqual(publicKey.data.hexString, "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91") } - func testGetSharedKey() { - let privateKey = PrivateKey(data: Data(hexString: "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0")!)! - let publicKey = PublicKey(data: Data(hexString: "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992")!, type: .secp256k1)! - - let derivedData = privateKey.getSharedKey(publicKey: publicKey, curve: .secp256k1)! - - XCTAssertEqual(derivedData.hexString, "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a") - } - - func testGetSharedKeyWycherproof() { - let privateKey = PrivateKey(data: Data(hexString: "f4b7ff7cccc98813a69fae3df222bfe3f4e28f764bf91b4a10d8096ce446b254")!)! - let publicKey = PublicKey(data: Data(hexString: "02d8096af8a11e0b80037e1ee68246b5dcbb0aeb1cf1244fd767db80f3fa27da2b")!, type: .secp256k1)! - - let derivedData = privateKey.getSharedKey(publicKey: publicKey, curve: .secp256k1)! - - XCTAssertEqual(derivedData.hexString, "81165066322732362ca5d3f0991d7f1f7d0aad7ea533276496785d369e35159a") - } - - func testGetSharedKeyBidirectional() { - let privateKey1 = PrivateKey(data: Data(hexString: "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0")!)! - let publicKey1 = privateKey1.getPublicKeySecp256k1(compressed: false) - let privateKey2 = PrivateKey(data: Data(hexString: "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a")!)! - let publicKey2 = privateKey2.getPublicKeySecp256k1(compressed: false) - - let derivedData1 = privateKey1.getSharedKey(publicKey: publicKey2, curve: .secp256k1)! - let derivedData2 = privateKey2.getSharedKey(publicKey: publicKey1, curve: .secp256k1)! - - XCTAssertEqual(derivedData1.hexString, derivedData2.hexString) - } - - func testGetSharedKeyError() { - let privateKey = PrivateKey(data: Data(hexString: "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0")!)! - let publicKey = PublicKey(data: Data(hexString: "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992")!, type: .secp256k1)! - - let derivedData = privateKey.getSharedKey(publicKey: publicKey, curve: .ed25519) - XCTAssertNil(derivedData) - } - func testSignSchnorr() { let privateKey = PrivateKey(data: Data(hexString: "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")!)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let message = "hello schnorr".data(using: .utf8)! - let sig = privateKey.signSchnorr(message: message, curve: .secp256k1)! - let verified = publicKey.verifySchnorr(signature: sig, message: message) + let sig = privateKey.signZilliqaSchnorr(message: message)! + let verified = publicKey.verifyZilliqaSchnorr(signature: sig, message: message) XCTAssertEqual(sig.hexString, "d166b1ae7892c5ef541461dc12a50214d0681b63d8037cda29a3fe6af8bb973e4ea94624d85bc0010bdc1b38d05198328fae21254adc2bf5feaf2804d54dba55") XCTAssertTrue(verified) diff --git a/swift/Tests/PublicKeyTests.swift b/swift/Tests/PublicKeyTests.swift index b6844ea38c8..026935dbb6d 100644 --- a/swift/Tests/PublicKeyTests.swift +++ b/swift/Tests/PublicKeyTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import WalletCore import XCTest @@ -35,4 +33,14 @@ class PublicKeyTests: XCTestCase { XCTAssertTrue(result2) XCTAssertTrue(result3) } + + func testVerifyStarkey() { + let data = Data(hexString: "02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159")! + let publicKey = PublicKey(data: data, type: .starkex)! + let signature = Data(hexString: "061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a")! + let hash = Data(hexString: "06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76")! + XCTAssertTrue(publicKey.verify(signature: signature, message: hash)) + let invalidSignature = Data(hexString: "061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9b")! + XCTAssertFalse(publicKey.verify(signature: invalidSignature, message: hash)) + } } diff --git a/swift/Tests/TransactionCompilerTests.swift b/swift/Tests/TransactionCompilerTests.swift new file mode 100644 index 00000000000..3a45d34b0b7 --- /dev/null +++ b/swift/Tests/TransactionCompilerTests.swift @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class TransactionCompilerTests: XCTestCase { + override func setUp() { + continueAfterFailure = false + } + + func testBitcoinCompileWithSignatures() throws { + // Test external signining with a Bitcoin transaction with 3 input UTXOs, all used, but only using 2 public keys. + // Three signatures are neeeded. This illustrates that order of UTXOs/hashes is not always the same. + + let revUtxoHash0 = Data(hexString: "07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8")! + let revUtxoHash1 = Data(hexString: "d6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e")! + let revUtxoHash2 = Data(hexString: "6021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d")! + let inPubKey0 = Data(hexString: "024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382")! + let inPubKey1 = Data(hexString: "0217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc")! + let inPubKeyHash0 = Data(hexString: "bd92088bb7e82d611a9b94fbb74a0908152b784f")! + let inPubKeyHash1 = Data(hexString: "6641abedacf9483b793afe1718689cc9420bbb1c")! + + // Test data: Input UTXO infos + struct UtxoInfo { + let revUtxoHash: Data + let publicKey: Data + let amount: Int64 + let index: UInt32 + } + let utxoInfos: [UtxoInfo] = [ + // first + UtxoInfo(revUtxoHash: revUtxoHash0, publicKey: inPubKey0, amount: 600000, index: 0), + // second UTXO, with same pubkey + UtxoInfo(revUtxoHash: revUtxoHash1, publicKey: inPubKey0, amount: 500000, index: 1), + // third UTXO, with different pubkey + UtxoInfo(revUtxoHash: revUtxoHash2, publicKey: inPubKey1, amount: 400000, index: 0), + ] + + // Signature infos, indexed by pubkeyhash+hash + struct SignatureInfo { + let signature: Data + let publicKey: Data + } + let signatureInfos: [String: SignatureInfo] = [ + inPubKeyHash0.hexString + "+" + "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7": + SignatureInfo(signature: Data(hexString: "304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a40")!, publicKey: inPubKey0), + inPubKeyHash1.hexString + "+" + "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6": + SignatureInfo(signature: Data(hexString: "3044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e4")!, publicKey: inPubKey1), + inPubKeyHash0.hexString + "+" + "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101": + SignatureInfo(signature: Data(hexString: "30440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc")!, publicKey: inPubKey0), + ] + + let coin = CoinType.bitcoin + let ownAddress = "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv" + + // Setup input for Plan + var signingInput = BitcoinSigningInput.with { + $0.coinType = coin.rawValue + $0.hashType = BitcoinSigHashType.all.rawValue + $0.amount = 1200000 + $0.useMaxAmount = false + $0.byteFee = 1 + $0.toAddress = "bc1q2dsdlq3343vk29runkgv4yc292hmq53jedfjmp" + $0.changeAddress = ownAddress + } + + // process UTXOs + var count: UInt32 = 0 + for u in utxoInfos { + let publicKey = PublicKey(data: u.publicKey, type: PublicKeyType.secp256k1) + let address = SegwitAddress(hrp: .bitcoin, publicKey: publicKey!) + if (count == 0) { XCTAssertEqual(address.description, ownAddress) } + if (count == 1) { XCTAssertEqual(address.description, ownAddress) } + if (count == 2) { XCTAssertEqual(address.description, "bc1qveq6hmdvl9yrk7f6lct3s6yue9pqhwcuxedggg") } + + let utxoScript = BitcoinScript.lockScriptForAddress(address: address.description, coin: coin) + if (count == 0) { XCTAssertEqual(utxoScript.data.hexString, "0014bd92088bb7e82d611a9b94fbb74a0908152b784f") } + if (count == 1) { XCTAssertEqual(utxoScript.data.hexString, "0014bd92088bb7e82d611a9b94fbb74a0908152b784f") } + if (count == 2) { XCTAssertEqual(utxoScript.data.hexString, "00146641abedacf9483b793afe1718689cc9420bbb1c") } + + let keyHash: Data = utxoScript.matchPayToWitnessPublicKeyHash()! + if (count == 0) { XCTAssertEqual(keyHash.hexString, inPubKeyHash0.hexString) } + if (count == 1) { XCTAssertEqual(keyHash.hexString, inPubKeyHash0.hexString) } + if (count == 2) { XCTAssertEqual(keyHash.hexString, inPubKeyHash1.hexString) } + + let redeemScript = BitcoinScript.buildPayToPublicKeyHash(hash: keyHash) + if (count == 0) { XCTAssertEqual(redeemScript.data.hexString, "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac") } + if (count == 1) { XCTAssertEqual(redeemScript.data.hexString, "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac") } + if (count == 2) { XCTAssertEqual(redeemScript.data.hexString, "76a9146641abedacf9483b793afe1718689cc9420bbb1c88ac") } + signingInput.scripts[keyHash.hexString] = redeemScript.data + + let outPoint = BitcoinOutPoint.with { + $0.hash = u.revUtxoHash + $0.index = u.index + $0.sequence = UInt32.max + } + let utxo = BitcoinUnspentTransaction.with { + $0.script = utxoScript.data + $0.amount = u.amount + $0.outPoint = outPoint + } + signingInput.utxo.append(utxo) + + count += 1 + } + XCTAssertEqual(count, 3) + XCTAssertEqual(signingInput.utxo.count, 3) + + // Plan + let plan: BitcoinTransactionPlan = AnySigner.plan(input: signingInput, coin: coin) + + // At this point plan can be checked, assume it is accepted unmodified + XCTAssertEqual(plan.amount, 1200000) + XCTAssertEqual(plan.fee, 277) + XCTAssertEqual(plan.change, 299723) + XCTAssertEqual(plan.utxos.count, 3) + // Note that UTXOs happen to be in reverse order compared to the input + XCTAssertEqual(plan.utxos[0].outPoint.hash.hexString, revUtxoHash2.hexString) + XCTAssertEqual(plan.utxos[1].outPoint.hash.hexString, revUtxoHash1.hexString) + XCTAssertEqual(plan.utxos[2].outPoint.hash.hexString, revUtxoHash0.hexString) + + // Extend input with accepted plan + signingInput.plan = plan + + // Serialize input + let txInputData = try signingInput.serializedData() + XCTAssertEqual(txInputData.count, 692) + + /// Step 2: Obtain preimage hashes + let preImageHashes = TransactionCompiler.preImageHashes(coinType: coin, txInputData: txInputData) + let preSigningOutput: BitcoinPreSigningOutput = try BitcoinPreSigningOutput(serializedData: preImageHashes) + + XCTAssertEqual(preSigningOutput.error, CommonSigningError.ok) + XCTAssertEqual(preSigningOutput.hashPublicKeys[0].dataHash.hexString, "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6") + XCTAssertEqual(preSigningOutput.hashPublicKeys[1].dataHash.hexString, "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7") + XCTAssertEqual(preSigningOutput.hashPublicKeys[2].dataHash.hexString, "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101") + XCTAssertEqual(preSigningOutput.hashPublicKeys[0].publicKeyHash.hexString, inPubKeyHash1.hexString) + XCTAssertEqual(preSigningOutput.hashPublicKeys[1].publicKeyHash.hexString, inPubKeyHash0.hexString) + XCTAssertEqual(preSigningOutput.hashPublicKeys[2].publicKeyHash.hexString, inPubKeyHash0.hexString) + + // Simulate signatures, normally they are obtained from external source, e.g. a signature server. + let signatureVec = DataVector() + let pubkeyVec = DataVector() + for h in preSigningOutput.hashPublicKeys { + let preImageHash = h.dataHash + let pubkeyHash = h.publicKeyHash + + let key: String = pubkeyHash.hexString + "+" + preImageHash.hexString + XCTAssertTrue(signatureInfos.contains { $0.key == key }) + let sigInfo: SignatureInfo = signatureInfos[key]! + let publicKeyData = sigInfo.publicKey + let publicKey = PublicKey(data: publicKeyData, type: PublicKeyType.secp256k1)! + let signature = sigInfo.signature + + signatureVec.add(data: signature) + pubkeyVec.add(data: publicKeyData) + + // Verify signature (pubkey & hash & signature) + XCTAssertTrue(publicKey.verifyAsDER(signature: signature, message: preImageHash)) + } + + /// Step 3: Compile transaction info + let compileWithSignatures = TransactionCompiler.compileWithSignatures(coinType: coin, txInputData: txInputData, signatures: signatureVec, publicKeys: pubkeyVec) + + let ExpectedTx = "010000000001036021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d0000000000ffffffffd6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e0100000000ffffffff07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa80000000000ffffffff02804f1200000000001600145360df8231ac5965147c9d90ca930a2aafb05232cb92040000000000160014bd92088bb7e82d611a9b94fbb74a0908152b784f02473044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e401210217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc0247304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a400121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382024730440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc0121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb49338200000000" + + XCTAssertEqual(compileWithSignatures.count, 786) + let output: BitcoinSigningOutput = try BitcoinSigningOutput(serializedData: compileWithSignatures) + XCTAssertEqual(output.encoded.count, 518) + XCTAssertEqual(output.encoded.hexString, ExpectedTx) + } + + // Test if `compileWithSignaturesAndPubKeyType` binding is available in Swift. + func testBitcoinCompileWithSignaturesAndPubKeyType() { + let txInputData = Data() + let signatureVec = DataVector() + let pubkeyVec = DataVector() + + let _ = TransactionCompiler.compileWithSignaturesAndPubKeyType(coinType: .bitcoin, txInputData: txInputData, signatures: signatureVec, publicKeys: pubkeyVec, pubKeyType: .secp256k1) + } +} diff --git a/swift/Tests/UniversalAssetIDTests.swift b/swift/Tests/UniversalAssetIDTests.swift index 73ad5ac8358..e2d2c3ca6d8 100644 --- a/swift/Tests/UniversalAssetIDTests.swift +++ b/swift/Tests/UniversalAssetIDTests.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest import WalletCore diff --git a/swift/Tests/WebAuthnTests.swift b/swift/Tests/WebAuthnTests.swift new file mode 100644 index 00000000000..d57140b48e2 --- /dev/null +++ b/swift/Tests/WebAuthnTests.swift @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import XCTest +import WalletCore + +class WebAuthnTests: XCTestCase { + + func testGetPubicKey() { + let attestationObject = Data(hexString: "0xa363666d74646e6f6e656761747453746d74a068617574684461746158a4f95bc73828ee210f9fd3bbe72d97908013b0a3759e9aea3d0ae318766cd2e1ad4500000000adce000235bcc60a648b0b25f1f055030020c720eb493e167ce93183dd91f5661e1004ed8cc1be23d3340d92381da5c0c80ca5010203262001215820a620a8cfc88fd062b11eab31663e56cad95278bef612959be214d98779f645b82258204e7b905b42917570148b0432f99ba21f2e7eebe018cbf837247e38150a89f771")! + let result = WebAuthn.getPublicKey(attestationObject: attestationObject)!.data + XCTAssertEqual(result.hexString, "04a620a8cfc88fd062b11eab31663e56cad95278bef612959be214d98779f645b84e7b905b42917570148b0432f99ba21f2e7eebe018cbf837247e38150a89f771") + } + + func testGetRSValues() { + let signature = Data(hexString: "0x30440220766589b461a838748708cdf88444b21b1fa52b57d70671b4f9bf60ad14b372ec022020cc439c9c20661bfa39bbea24a900ec1484b2395eb065ead8ef4e273144a57d")! + let result = WebAuthn.getRSValues(signature: signature) + XCTAssertEqual(result.hexString, "766589b461a838748708cdf88444b21b1fa52b57d70671b4f9bf60ad14b372ec20cc439c9c20661bfa39bbea24a900ec1484b2395eb065ead8ef4e273144a57d") + } + + func testReconstructOriginalMessage() { + let authenticatorData = Data(hexString: "0x1a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d00000000")! + let clientDataJSON = Data(hexString: "0x7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a224e5549794f5545774d6b45744e554535517930304d6b5a424c546847516a4174517a52474f4441794d3045304f546b30222c226f726967696e223a2268747470733a2f2f747275737477616c6c65742e636f6d227d")! + let result = WebAuthn.reconstructOriginalMessage(authenticatorData: authenticatorData, clientDataJSON: clientDataJSON) + XCTAssertEqual(result.hexString, "3254cdbd677e6e31e75d2135bad0cf56440d7c6b108c141a3509d76ce45c6731") + } +} diff --git a/swift/Tests/XCTAssert+Extension.swift b/swift/Tests/XCTAssert+Extension.swift index 048c712951c..15aa17f5eb9 100644 --- a/swift/Tests/XCTAssert+Extension.swift +++ b/swift/Tests/XCTAssert+Extension.swift @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import XCTest diff --git a/swift/common-xcframework.yml b/swift/common-xcframework.yml index 3fb188cec67..dae0d4b55c7 100644 --- a/swift/common-xcframework.yml +++ b/swift/common-xcframework.yml @@ -8,10 +8,9 @@ options: macOS: 10.14 settings: base: - ENABLE_BITCODE: YES HEADER_SEARCH_PATHS: $(SRCROOT)/wallet-core ${SRCROOT}/trezor-crypto/crypto SYSTEM_HEADER_SEARCH_PATHS: ${SRCROOT}/include ${SRCROOT}/../build/local/include ${SRCROOT}/trezor-crypto/include $(SRCROOT)/protobuf /usr/local/include /opt/homebrew/include - CLANG_CXX_LANGUAGE_STANDARD: c++17 + CLANG_CXX_LANGUAGE_STANDARD: c++20 GCC_WARN_64_TO_32_BIT_CONVERSION: NO targets: @@ -35,12 +34,15 @@ targets: - "proto/*.proto" - "Tron/Protobuf/*.proto" - "Zilliqa/Protobuf/*.proto" + - "Cosmos/Protobuf/*.proto" + - "Hedera/Protobuf/*.proto" dependencies: - target: trezor-crypto_${platform} link: true - target: protobuf_${platform} link: true - sdk: libc++.tbd + - framework: WalletCoreRs.xcframework settings: SKIP_INSTALL: false SUPPORTS_MACCATALYST: true @@ -109,7 +111,8 @@ targets: - trezor-crypto/crypto/groestl.c - trezor-crypto/crypto/hmac_drbg.c - trezor-crypto/crypto/rfc6979.c - - trezor-crypto/crypto/schnorr.c + - trezor-crypto/crypto/zilliqa.c + - trezor-crypto/crypto/cardano.c - trezor-crypto/crypto/shamir.c - trezor-crypto/crypto/sodium/private/fe_25_5/fe.c - trezor-crypto/crypto/sodium/private/ed25519_ref10.c diff --git a/swift/cpp.xcconfig.in b/swift/cpp.xcconfig.in index 41c80610459..09e3c0f91af 100644 --- a/swift/cpp.xcconfig.in +++ b/swift/cpp.xcconfig.in @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. HEADER_SEARCH_PATHS = $(SRCROOT)/../src @Boost_INCLUDE_DIRS@ SYSTEM_HEADER_SEARCH_PATHS = $(SRCROOT)/../src/build/local/include $(SRCROOT)/../build/local/src/protobuf/protobuf-3.19.2/src diff --git a/swift/fastlane/Fastfile b/swift/fastlane/Fastfile index 54bb2511daf..5fa18ea4945 100644 --- a/swift/fastlane/Fastfile +++ b/swift/fastlane/Fastfile @@ -13,8 +13,9 @@ platform :ios do create_xcframework( workspace: 'TrustWalletCore.xcworkspace', scheme: 'WalletCore', - destinations: ['iOS', 'macOS'], - xcframework_output_directory: 'build' + destinations: ['iOS'], + xcframework_output_directory: 'build', + enable_bitcode: false ) end @@ -23,8 +24,9 @@ platform :ios do create_xcframework( workspace: 'TrustWalletCore.xcworkspace', scheme: 'SwiftProtobuf', - destinations: ['iOS', 'macOS'], - xcframework_output_directory: 'build' + destinations: ['iOS'], + xcframework_output_directory: 'build', + enable_bitcode: false ) end end diff --git a/swift/project.yml b/swift/project.yml index 81534762937..df46f6a29e5 100644 --- a/swift/project.yml +++ b/swift/project.yml @@ -6,7 +6,7 @@ settings: base: HEADER_SEARCH_PATHS: $(SRCROOT)/wallet-core ${SRCROOT}/trezor-crypto/crypto SYSTEM_HEADER_SEARCH_PATHS: ${SRCROOT}/include ${SRCROOT}/../build/local/include ${SRCROOT}/trezor-crypto/include $(SRCROOT)/protobuf /usr/local/include /opt/homebrew/include - CLANG_CXX_LANGUAGE_STANDARD: c++17 + CLANG_CXX_LANGUAGE_STANDARD: c++20 SWIFT_VERSION: 5.1 IPHONEOS_DEPLOYMENT_TARGET: 13.0 configs: @@ -27,9 +27,10 @@ targets: excludes: - ".vscode" - "proto/*.proto" - - "Cosmos/Protobuf/*.proto" - "Tron/Protobuf/*.proto" - "Zilliqa/Protobuf/*.proto" + - "Cosmos/Protobuf/*.proto" + - "Hedera/Protobuf/*.proto" - Sources dependencies: - target: trezor-crypto @@ -37,6 +38,7 @@ targets: - target: protobuf link: true - sdk: libc++.tbd + - framework: WalletCoreRs.xcframework scheme: testTargets: - WalletCoreTests @@ -60,6 +62,7 @@ targets: fi name: SwiftLint shell: /bin/bash + basedOnDependencyAnalysis: false WalletCoreTests: type: bundle.unit-test @@ -126,7 +129,8 @@ targets: - trezor-crypto/crypto/groestl.c - trezor-crypto/crypto/hmac_drbg.c - trezor-crypto/crypto/rfc6979.c - - trezor-crypto/crypto/schnorr.c + - trezor-crypto/crypto/zilliqa.c + - trezor-crypto/crypto/cardano.c - trezor-crypto/crypto/shamir.c - trezor-crypto/crypto/sodium/private/fe_25_5/fe.c - trezor-crypto/crypto/sodium/private/ed25519_ref10.c diff --git a/tests/Aeternity/AddressTests.cpp b/tests/Aeternity/AddressTests.cpp deleted file mode 100644 index 40c99814889..00000000000 --- a/tests/Aeternity/AddressTests.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include -#include -#include - -using namespace TW; -using namespace TW::Aeternity; - -TEST(AeternityAddress, FromPublicKey) { - auto publicKey = PublicKey(parse_hex("ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148"),TWPublicKeyTypeED25519); - auto address = Address(publicKey); - ASSERT_EQ(address.string(), "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); -} - -TEST(AeternityAddress, FromString) { - auto address = Address("ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); - ASSERT_EQ(address.string(), "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); -} \ No newline at end of file diff --git a/tests/Aeternity/SignerTests.cpp b/tests/Aeternity/SignerTests.cpp deleted file mode 100644 index aab6d85abfb..00000000000 --- a/tests/Aeternity/SignerTests.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Aeternity/Signer.h" -#include "Aeternity/Transaction.h" -#include "HexCoding.h" - -#include "Aeternity/Address.h" -#include -#include "uint256.h" - -using namespace TW; -using namespace TW::Aeternity; - -TEST(AeternitySigner, Sign) { - std::string sender_id = "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"; - std::string recipient_id = "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"; - uint256_t amount = 10; - uint256_t fee = 20000000000000; - std::string payload = "Hello World"; - uint64_t ttl = 82757; - uint64_t nonce = 49; - - auto transaction = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); - auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); - - auto result = Signer::sign(privateKey, transaction); - EXPECT_EQ(result.signature(), "sg_VW42qDPP3MMNFAStYaumjZz7mC7BZYpbNa15E57ejqUe7JdQFWCiX65eLNUpGMpt8tSpfgCfkYzcaFppqx7W75CrcWdC8"); - EXPECT_EQ(result.encoded(), "tx_+KkLAfhCuEDZ2XDV5OuHv1iuLn66sFLBUwnzp1K8JW1Zz+fEgmuEh6HEvNu0R112M3IYkVzvTSnT0pJ3TWhVOumgJ+IWwW8HuGH4XwwBoQHuk6T2b40WuBm7m+uf/M383BQS6H/uajJMKpmh4OZxSKEBHxOjsIvwAUAGYqaLadh194A87EwIZH9u1dhMeJe9UKMKhhIwnOVAAIMBQ0Uxi0hlbGxvIFdvcmxkDZqNSg=="); -} - -TEST(AeternitySigner, SignTxWithZeroTtl) { - std::string sender_id = "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"; - std::string recipient_id = "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"; - uint256_t amount = 10; - uint256_t fee = 20000000000000; - std::string payload = "Hello World"; - uint64_t ttl = 0; - uint64_t nonce = 49; - - auto transaction = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); - auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); - - auto result = Signer::sign(privateKey, transaction); - EXPECT_EQ(result.signature(), "sg_7qJK868bqEZ5ciC2P3WCKYfhayvKTHvPsz3bdPgpfF3Ky7yNg9f8k22A3gxjjSm9afa6JmP8TJpF4GJkFh2k7gGaog9KS"); - EXPECT_EQ(result.encoded(), "tx_+KYLAfhCuEA0OgWhpq/VfS6ksMS+Df4ewZxIITEhjaaMOiyT0aRuAEe6b5+d2cQtzoyz58NNr+N4MFowctrGXrCrrkhNIywLuF74XAwBoQHuk6T2b40WuBm7m+uf/M383BQS6H/uajJMKpmh4OZxSKEBHxOjsIvwAUAGYqaLadh194A87EwIZH9u1dhMeJe9UKMKhhIwnOVAAAAxi0hlbGxvIFdvcmxkjoDNvQ=="); -} - -TEST(AeternitySigner, SignTxWithZeroAmount) { - std::string sender_id = "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"; - std::string recipient_id = "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"; - uint256_t amount = 0; - uint256_t fee = 20000000000000; - std::string payload = "Zero amount test"; - uint64_t ttl = 113579; - uint64_t nonce = 7; - - auto transaction = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); - auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); - - auto result = Signer::sign(privateKey, transaction); - EXPECT_EQ(result.signature(), "sg_ShWvujPnyKBT1Ng2X5k6XSchVK8Bq7LYEisPMH11DUoPkXZcooBzqw81j9j5JewoFFpT9xEhUptj1azcLA21ogURYh4Lz"); - EXPECT_EQ(result.encoded(), "tx_+K4LAfhCuEDEbeoiVYmJCXm91KNfZXOvZMoT9x/sZja09EXZmErFBxm52b1IVoM4806Zr+TsliAYzUyKfUUFo3jGfXEPdZ8PuGb4ZAwBoQHuk6T2b40WuBm7m+uf/M383BQS6H/uajJMKpmh4OZxSKEBHxOjsIvwAUAGYqaLadh194A87EwIZH9u1dhMeJe9UKMAhhIwnOVAAIMBu6sHkFplcm8gYW1vdW50IHRlc3S5L3Vn"); -} - -TEST(AeternitySigner, SignTxWithZeroNonce) { - std::string sender_id = "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"; - std::string recipient_id = "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"; - uint256_t amount = 3369980000000000000; - uint256_t fee = 20000000000000; - std::string payload = "Zero nonce test"; - uint64_t ttl = 113579; - uint64_t nonce = 0; - - auto transaction = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); - auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); - - auto result = Signer::sign(privateKey, transaction); - EXPECT_EQ(result.signature(), "sg_MaJc4ptSUhq5kH6mArszDAvu4f7PejyuhmgM6U8GEr8bRUTaSFbdFPx4C6FEYA5v5Lgwu9EToaWnHgR2xkqZ9JjHnaBpA"); - EXPECT_EQ(result.encoded(), "tx_+LULAfhCuECdQsgcE8bp+9CANdasxkt5gxfjBSI1ztyPl1aNJbm+MwUvE7Lu/qvAkHijfe+Eui2zrqhZRYc5mblRa+oLOIIEuG34awwBoQHuk6T2b40WuBm7m+uf/M383BQS6H/uajJMKpmh4OZxSKEBHxOjsIvwAUAGYqaLadh194A87EwIZH9u1dhMeJe9UKOILsSS9IArwACGEjCc5UAAgwG7qwCPWmVybyBub25jZSB0ZXN0piWfFA=="); -} diff --git a/tests/Aeternity/TWAnySignerTests.cpp b/tests/Aeternity/TWAnySignerTests.cpp deleted file mode 100644 index 01b4fcf80be..00000000000 --- a/tests/Aeternity/TWAnySignerTests.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "uint256.h" -#include "proto/Aeternity.pb.h" -#include - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; -using namespace TW::Aeternity; - -TEST(TWAnySignerAeternity, Sign) { - auto privateKey = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); - - Proto::SigningInput input; - input.set_from_address("ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); - input.set_to_address("ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"); - auto amount = store(uint256_t(10)); - input.set_amount(amount.data(), amount.size()); - auto fee = store(uint256_t(20000000000000)); - input.set_fee(fee.data(), fee.size()); - input.set_payload("Hello World"); - input.set_ttl(82757); - input.set_nonce(49); - input.set_private_key(privateKey.data(), privateKey.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeAeternity); - - ASSERT_EQ(output.encoded(), "tx_+KkLAfhCuEDZ2XDV5OuHv1iuLn66sFLBUwnzp1K8JW1Zz+fEgmuEh6HEvNu0R112M3IYkVzvTSnT0pJ3TWhVOumgJ+IWwW8HuGH4XwwBoQHuk6T2b40WuBm7m+uf/M383BQS6H/uajJMKpmh4OZxSKEBHxOjsIvwAUAGYqaLadh194A87EwIZH9u1dhMeJe9UKMKhhIwnOVAAIMBQ0Uxi0hlbGxvIFdvcmxkDZqNSg=="); -} diff --git a/tests/Aeternity/TWCoinTypeTests.cpp b/tests/Aeternity/TWCoinTypeTests.cpp deleted file mode 100644 index 7db96db7589..00000000000 --- a/tests/Aeternity/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWAeternityCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeAeternity)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeAeternity, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeAeternity, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeAeternity)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeAeternity)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeAeternity), 18); - ASSERT_EQ(TWBlockchainAeternity, TWCoinTypeBlockchain(TWCoinTypeAeternity)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeAeternity)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeAeternity)); - assertStringsEqual(symbol, "AE"); - assertStringsEqual(txUrl, "https://explorer.aepps.com/transactions/t123"); - assertStringsEqual(accUrl, "https://explorer.aepps.com/account/transactions/a12"); - assertStringsEqual(id, "aeternity"); - assertStringsEqual(name, "Aeternity"); -} diff --git a/tests/Aeternity/TransactionTests.cpp b/tests/Aeternity/TransactionTests.cpp deleted file mode 100644 index 025eb2dc056..00000000000 --- a/tests/Aeternity/TransactionTests.cpp +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Aeternity/Address.cpp" -#include "Aeternity/Transaction.cpp" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "../interface/TWTestUtilities.h" - -#include "HexCoding.h" -#include -#include - -TEST(AeternityTransaction, EncodeRlp) { - std::string sender_id = "ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi"; - std::string recipient_id = "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"; - uint64_t amount = 10; - uint64_t fee = 2e13; - std::string payload = "Hello World"; - uint64_t ttl = 82757; - uint64_t nonce = 49; - - auto tx = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); - auto encodedTx = tx.encode(); - auto encodedTxHex = hex(encodedTx); - - ASSERT_EQ(encodedTxHex, "f85f0c01a101cea7ade470c9f99d9d4e400880a86f1d49bb444b62f11a9ebb64bbcfeb73fef3a1011f13a3b08bf001400662a68b69d875f7803cec4c08647f6ed5d84c7897bd50a30a8612309ce5400083014345318b48656c6c6f20576f726c64"); -} - -TEST(AeternityTransaction, EncodeRlpWithZeroAmount) { - std::string sender_id = "ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi"; - std::string recipient_id = "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"; - uint64_t amount = 0; - uint64_t fee = 2e13; - std::string payload = "Hello World"; - uint64_t ttl = 82757; - uint64_t nonce = 49; - - auto tx = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); - auto encodedTx = tx.encode(); - auto encodedTxHex = hex(encodedTx); - - ASSERT_EQ(encodedTxHex, "f85f0c01a101cea7ade470c9f99d9d4e400880a86f1d49bb444b62f11a9ebb64bbcfeb73fef3a1011f13a3b08bf001400662a68b69d875f7803cec4c08647f6ed5d84c7897bd50a3008612309ce5400083014345318b48656c6c6f20576f726c64"); -} - -TEST(AeternityTransaction, EncodeRlpWithZeroTtl) { - std::string sender_id = "ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi"; - std::string recipient_id = "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"; - uint64_t amount = 10; - uint64_t fee = 2e13; - std::string payload = "Hello World"; - uint64_t ttl = 0; - uint64_t nonce = 49; - - auto tx = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); - auto encodedTx = tx.encode(); - auto encodedTxHex = hex(encodedTx); - - ASSERT_EQ(encodedTxHex, "f85c0c01a101cea7ade470c9f99d9d4e400880a86f1d49bb444b62f11a9ebb64bbcfeb73fef3a1011f13a3b08bf001400662a68b69d875f7803cec4c08647f6ed5d84c7897bd50a30a8612309ce5400000318b48656c6c6f20576f726c64"); -} - diff --git a/tests/Aion/AddressTests.cpp b/tests/Aion/AddressTests.cpp deleted file mode 100644 index 5942e0ce0b6..00000000000 --- a/tests/Aion/AddressTests.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Aion/Address.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Aion; - -TEST(AionAddress, FromPublicKey) { - auto publicKey = PublicKey(parse_hex("01a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7"), TWPublicKeyTypeED25519); - auto address = Address(publicKey); - ASSERT_EQ(address.string(), "0xa0d2312facea71b740679c926d040c9056a65a4bfa2ddd18ec160064f82909e7"); -} - -TEST(AionAddress, FromString) { - std::string aionAddress = "0xa0d2312facea71b740679c926d040c9056a65a4bfa2ddd18ec160064f82909e7"; - const auto address = Address(aionAddress); - ASSERT_EQ(address.string(), aionAddress); -} - -TEST(AionAddress, isValid) { - std::string validAddress = "0xa0d2312facea71b740679c926d040c9056a65a4bfa2ddd18ec160064f82909e7"; - std::string invalidAddress = "0xzzd2312facea71b740679c926d040c9056a65a4bfa2ddd18ec160064f82909e7"; - - ASSERT_TRUE(Address::isValid(validAddress)); - ASSERT_FALSE(Address::isValid(invalidAddress)); -} diff --git a/tests/Aion/RLPTests.cpp b/tests/Aion/RLPTests.cpp deleted file mode 100644 index 977fbdbda65..00000000000 --- a/tests/Aion/RLPTests.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Aion/RLP.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Aion; -using boost::multiprecision::uint128_t; - -TEST(AionRLP, EncodeLong) { - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(1))), "01"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(21000))), "825208"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(1000000))), "830f4240"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(20000000000))), "8800000004a817c800"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(9007199254740991))), "88001fffffffffffff"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(9007199254740990))), "88001ffffffffffffe"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(4294967296L))), "880000000100000000"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(4295000060L))), "880000000100007ffc"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(72057594037927935L))), "8800ffffffffffffff"); -} diff --git a/tests/Aion/SignerTests.cpp b/tests/Aion/SignerTests.cpp deleted file mode 100644 index b8a7970f248..00000000000 --- a/tests/Aion/SignerTests.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Aion/Signer.h" -#include "Aion/Transaction.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Aion; - -TEST(AionSigner, Sign) { - auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); - auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, {}); - - auto privateKey = PrivateKey(parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9")); - Signer::sign(privateKey, transaction); - - EXPECT_EQ(hex(transaction.signature), "a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); - - // Raw transaction - EXPECT_EQ(hex(transaction.encode()), "f89b09a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108085242019b04d8252088800000004a817c80001b860a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); -} - -TEST(AionSigner, SignWithData) { - auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); - auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, parse_hex("41494f4e0000")); - - auto privateKey = PrivateKey(parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9")); - Signer::sign(privateKey, transaction); - - EXPECT_EQ(hex(transaction.signature), "a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a736fc2642c2d62900204779aa274dba3b8712eff7a8464aa78ea52b09ece20679fe3f5edf94c84a7e0c5f93213be891bc279af927086f455167f5bc73d3046c0d"); - - // Raw transaction - EXPECT_EQ(hex(transaction.encode()), "f8a109a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108641494f4e000085242019b04d8252088800000004a817c80001b860a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a736fc2642c2d62900204779aa274dba3b8712eff7a8464aa78ea52b09ece20679fe3f5edf94c84a7e0c5f93213be891bc279af927086f455167f5bc73d3046c0d"); -} diff --git a/tests/Aion/TWAnySignerTests.cpp b/tests/Aion/TWAnySignerTests.cpp deleted file mode 100644 index 1f30f761d03..00000000000 --- a/tests/Aion/TWAnySignerTests.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "uint256.h" -#include "proto/Aion.pb.h" -#include - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; -using namespace TW::Aion; - -TEST(TWAnySignerAion, Sign) { - auto privateKey = parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9"); - - Proto::SigningInput input; - input.set_to_address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); - auto amount = store(uint256_t(10000)); - input.set_amount(amount.data(), amount.size()); - auto gasPrice = store(uint256_t(20000000000)); - input.set_gas_price(gasPrice.data(), gasPrice.size()); - auto gasLimit = store(uint256_t(21000)); - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - auto nonce = store(uint256_t(9)); - input.set_nonce(nonce.data(), nonce.size()); - input.set_timestamp(155157377101); - input.set_private_key(privateKey.data(), privateKey.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeAion); - - ASSERT_EQ(hex(output.encoded()), "f89b09a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108085242019b04d8252088800000004a817c80001b860a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); -} diff --git a/tests/Aion/TWCoinTypeTests.cpp b/tests/Aion/TWCoinTypeTests.cpp deleted file mode 100644 index e87d983d8d8..00000000000 --- a/tests/Aion/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWAionCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeAion)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeAion, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeAion, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeAion)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeAion)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeAion), 18); - ASSERT_EQ(TWBlockchainAion, TWCoinTypeBlockchain(TWCoinTypeAion)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeAion)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeAion)); - assertStringsEqual(symbol, "AION"); - assertStringsEqual(txUrl, "https://mainnet.aion.network/#/transaction/t123"); - assertStringsEqual(accUrl, "https://mainnet.aion.network/#/account/a12"); - assertStringsEqual(id, "aion"); - assertStringsEqual(name, "Aion"); -} diff --git a/tests/Aion/TransactionTests.cpp b/tests/Aion/TransactionTests.cpp deleted file mode 100644 index ad6b8546c2b..00000000000 --- a/tests/Aion/TransactionTests.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Aion/Transaction.h" - -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Aion; - -TEST(AionTransaction, Encode) { - auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); - auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, {}); - ASSERT_EQ(hex(transaction.encode()), "f83909a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108085242019b04d8252088800000004a817c80001"); -} - -TEST(AionTransaction, EncodeWithSignature) { - auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); - auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, {}); - transaction.signature = parse_hex("a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); - ASSERT_EQ(hex(transaction.encode()), "f89b09a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108085242019b04d8252088800000004a817c80001b860a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); -} diff --git a/tests/Algorand/AddressTests.cpp b/tests/Algorand/AddressTests.cpp deleted file mode 100644 index 0ed495d060e..00000000000 --- a/tests/Algorand/AddressTests.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "Algorand/Address.h" -#include "PublicKey.h" -#include "PrivateKey.h" -#include -#include - -using namespace TW; -using namespace TW::Algorand; - -TEST(AlgorandAddress, Validation) { - // empty address - ASSERT_FALSE(Address::isValid("")); - // invalid checksum - ASSERT_FALSE(Address::isValid("JBCQYJ2FREG667NAN7BFKH4RFIKPT7CYDQJNW3SNN5Z7F7ILFLKQ346TS3")); - // wrong length - ASSERT_FALSE(Address::isValid("JBCQYJ2FREG667NAN7BFKH4RFIKPT7CYDQJNW3SNN5Z7F7ILFLKQ346TSU ")); - // Stellar address - ASSERT_FALSE(Address::isValid("GABQHYQOY22KHGTCTAK24AWAUE4TXERF4O4JBSXELNM7IL5CTPUWM3SC")); - - ASSERT_TRUE(Address::isValid("HXIWBVQGOO6ZWE5NYJO22XMYRUGZ6TGNX2K2EERPT3ZIWPHE5CLJGB2GEA")); -} - -TEST(AlgorandAddress, FromPrivateKey) { - auto privateKey = PrivateKey(parse_hex("526d96fffdbfe787b2f00586298538f9a019e97f6587964dc61aae9ad1d7fa23")); - auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519)); - ASSERT_EQ(address.string(), "JBCQYJ2FREG667NAN7BFKH4RFIKPT7CYDQJNW3SNN5Z7F7ILFLKQ346TSU"); -} - -TEST(AlgorandAddress, FromPublicKey) { - auto publicKey = PublicKey(parse_hex("c2b423afa8b0095e5ae105668b91b2132db4dadbf38acfc64908d3476a00191f"), TWPublicKeyTypeED25519); - auto address = Address(publicKey); - ASSERT_EQ(address.string(), "YK2CHL5IWAEV4WXBAVTIXENSCMW3JWW36OFM7RSJBDJUO2QADEP5QYVO5I"); -} - -TEST(AlgorandAddress, FromString) { - auto address = Address("PITDOF57RHOVLT37KM7DCXDCETLDL3OA5CBAN7LQ44Z36LGFC27IJ2IQ64"); - ASSERT_EQ(address.string(), "PITDOF57RHOVLT37KM7DCXDCETLDL3OA5CBAN7LQ44Z36LGFC27IJ2IQ64"); -} diff --git a/tests/Algorand/SignerTests.cpp b/tests/Algorand/SignerTests.cpp deleted file mode 100644 index f9cfcecb2bf..00000000000 --- a/tests/Algorand/SignerTests.cpp +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Algorand/Address.h" -#include "Algorand/Signer.h" -#include "Algorand/BinaryCoding.h" -#include "HexCoding.h" -#include "Base64.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include -#include - -using namespace TW; -using namespace TW::Algorand; - -TEST(AlgorandSigner, EncodeNumbers) { - auto tests = { - std::make_tuple(100ull, "64"), - std::make_tuple(200ull, "ccc8"), - std::make_tuple(55536ull, "cdd8f0"), - std::make_tuple(3294967296ull, "cec4653600"), - std::make_tuple(14294967296ull, "cf00000003540be400"), - }; - - for (auto& test : tests) { - Data data; - encodeNumber(std::get<0>(test), data); - ASSERT_EQ(hex(data), std::get<1>(test)); - } -} - -TEST(AlgorandSigner, EncodeStrings) { - auto tests = { - std::make_tuple("algo", "a4616c676f"), - std::make_tuple("It's like JSON. but fast and small.", "d92349742773206c696b65204a534f4e2e20627574206661737420616e6420736d616c6c2e"), - std::make_tuple( - "MessagePack is an efficient binary serialization format. It lets you exchange data among multiple languages like JSON. But it's faster and smaller. Small integers are encoded into a single byte, and typical short strings require only one extra byte in addition to the strings themselves.", - "da011f4d6573736167655061636b20697320616e20656666696369656e742062696e6172792073657269616c697a6174696f6e20666f726d61742e204974206c65747320796f752065786368616e6765206461746120616d6f6e67206d756c7469706c65206c616e677561676573206c696b65204a534f4e2e2042757420697427732066617374657220616e6420736d616c6c65722e20536d616c6c20696e7465676572732061726520656e636f64656420696e746f20612073696e676c6520627974652c20616e64207479706963616c2073686f727420737472696e67732072657175697265206f6e6c79206f6e65206578747261206279746520696e206164646974696f6e20746f2074686520737472696e6773207468656d73656c7665732e" - ) - }; - - for (auto& test : tests) { - Data data; - Algorand::encodeString(std::get<0>(test), data); - ASSERT_EQ(hex(data), std::get<1>(test)); - } -} - -TEST(AlgorandSigner, EncodeBytes) { - auto rawtx = "010000000001029294c2b3bd4d25483c4c12432df01a856a38cc0cb48da1a7dd590b7d893392a90000000000ffffffffded892ea55bf1c6ccc495d3493767d7c24497f612b9edc9ab8d30eb671ea76750000000000ffffffff021027000000000000160014b96bacd6f729ef8ac1dd30d159433c0917ba8d3db00f00000000000016001476cd9d430de6db162fc3db509920255ff6d2bdb002483045022100eb8675ff6775e9c399dddba9f178002b745872e541617d690cbce7c933adb87602205de8074c173696de65d4c644a84ea1337c9e9928c7052fddcf9d99e35815e2f20121032858d3a5f9825408ea3959800c5daf22e7a91e459ef168df45071266501d28e102473044022025f1cf362a9c09bd351769f1918ab9f0a6c3f6c4682f29fdbfc08354554ea37b02203f62345b3da4d7a29f58c7c741682be4108a0fb2013980332cc3e081aad7423f01210237d83670da2d3947a58752dab95d59b592c78f2e734d1c14dbf75b29bbe4116100000000"; - Data data; - encodeBytes(parse_hex(rawtx), data); - ASSERT_EQ(hex(data), "c50173010000000001029294c2b3bd4d25483c4c12432df01a856a38cc0cb48da1a7dd590b7d893392a90000000000ffffffffded892ea55bf1c6ccc495d3493767d7c24497f612b9edc9ab8d30eb671ea76750000000000ffffffff021027000000000000160014b96bacd6f729ef8ac1dd30d159433c0917ba8d3db00f00000000000016001476cd9d430de6db162fc3db509920255ff6d2bdb002483045022100eb8675ff6775e9c399dddba9f178002b745872e541617d690cbce7c933adb87602205de8074c173696de65d4c644a84ea1337c9e9928c7052fddcf9d99e35815e2f20121032858d3a5f9825408ea3959800c5daf22e7a91e459ef168df45071266501d28e102473044022025f1cf362a9c09bd351769f1918ab9f0a6c3f6c4682f29fdbfc08354554ea37b02203f62345b3da4d7a29f58c7c741682be4108a0fb2013980332cc3e081aad7423f01210237d83670da2d3947a58752dab95d59b592c78f2e734d1c14dbf75b29bbe4116100000000"); -} - -TEST(AlgorandSigner, Sign) { - auto key = PrivateKey(parse_hex("c9d3cc16fecabe2747eab86b81528c6ed8b65efc1d6906d86aabc27187a1fe7c")); - auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); - auto from = Address(publicKey); - auto to = Address("UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM"); - Data note; - std::string genesisId = "mainnet-v1.0"; - auto genesisHash = Base64::decode("wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="); - auto transaction = Transfer( - /* from */ from, - /* to */ to, - /* fee */ 488931, - /* amount */ 847, - /* first round */ 51, - /* last round */ 61, - /* note */ note, - /* type */ "pay", - /* genesis id*/ genesisId, - /* genesis hash*/ genesisHash - ); - - auto serialized = transaction.serialize(); - auto signature = Signer::sign(key, transaction); - auto result = transaction.BaseTransaction::serialize(signature); - - ASSERT_EQ(hex(serialized), "89a3616d74cd034fa3666565ce000775e3a2667633a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c763da3726376c420a089aa6922e3b998fadff6cd4808ddf9e021e4944e389ea3d5c638786689197ea3736e64c42074b000b6368551a6066d713e2866002e8dab34b69ede09a72e85a39bbb1f7928a474797065a3706179"); - ASSERT_EQ(hex(signature), "de73363dbdeda0682adca06f6268a16a6ec47253c94d5692dc1c49a84a05847812cf66d7c4cf07c7e2f50f143ec365d405e30b35117b264a994626054d2af604"); - ASSERT_EQ(hex(result), "82a3736967c440de73363dbdeda0682adca06f6268a16a6ec47253c94d5692dc1c49a84a05847812cf66d7c4cf07c7e2f50f143ec365d405e30b35117b264a994626054d2af604a374786e89a3616d74cd034fa3666565ce000775e3a2667633a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c763da3726376c420a089aa6922e3b998fadff6cd4808ddf9e021e4944e389ea3d5c638786689197ea3736e64c42074b000b6368551a6066d713e2866002e8dab34b69ede09a72e85a39bbb1f7928a474797065a3706179"); -} - -TEST(AlgorandSigner, SignAsset) { - // https://testnet.algoexplorer.io/tx/NJ62HYO2LC222AVLIN2GW5LKIWKLGC7NZLIQ3DUL2RDVRYO2UW7A - auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04")); - auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); - auto from = Address(publicKey); - auto to = Address("GJIWJSX2EU5RC32LKTDDXWLA2YICBHKE35RV2ZPASXZYKWUWXFLKNFSS4U"); - Data note; - std::string genesisId = "testnet-v1.0"; - auto genesisHash = Base64::decode("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="); - auto transaction = AssetTransfer( - /* from */ from, - /* to */ to, - /* fee */ 2340, - /* amount */ 1000000, - /* asset id */13379146, - /* first round */ 15775683, - /* last round */ 15776683, - /* note */ note, - /* type */ "axfer", - /* genesis id*/ genesisId, - /* genesis hash*/ genesisHash - ); - - auto serialized = transaction.serialize(); - auto signature = Signer::sign(key, transaction); - auto result = transaction.BaseTransaction::serialize(signature); - - ASSERT_EQ(hex(serialized), "8aa461616d74ce000f4240a461726376c420325164cafa253b116f4b54c63bd960d610209d44df635d65e095f3855a96b956a3666565cd0924a26676ce00f0b7c3a367656eac746573746e65742d76312e30a26768c4204863b518a4b3c84ec810f22d4f1081cb0f71f059a7ac20dec62f7f70e5093a22a26c76ce00f0bbaba3736e64c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a474797065a56178666572a478616964ce00cc264a"); - ASSERT_EQ(hex(signature), "412720eff99a17280a437bdb8eeba7404b855d6433fffd5dde7f7966c1f9ae531a1af39e18b8a58b4a6c6acb709cca92f8a18c36d8328be9520c915311027005"); - ASSERT_EQ(hex(result), "82a3736967c440412720eff99a17280a437bdb8eeba7404b855d6433fffd5dde7f7966c1f9ae531a1af39e18b8a58b4a6c6acb709cca92f8a18c36d8328be9520c915311027005a374786e8aa461616d74ce000f4240a461726376c420325164cafa253b116f4b54c63bd960d610209d44df635d65e095f3855a96b956a3666565cd0924a26676ce00f0b7c3a367656eac746573746e65742d76312e30a26768c4204863b518a4b3c84ec810f22d4f1081cb0f71f059a7ac20dec62f7f70e5093a22a26c76ce00f0bbaba3736e64c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a474797065a56178666572a478616964ce00cc264a"); -} - -TEST(AlgorandSigner, SignAssetOptIn) { - // https://testnet.algoexplorer.io/tx/47LE2QS4B5N6IFHXOUN2MJUTCOQCHNY6AB3AJYECK4IM2VYKJDKQ - auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04")); - auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); - auto address = Address(publicKey); - Data note; - std::string genesisId = "testnet-v1.0"; - auto genesisHash = Base64::decode("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="); - auto transaction = OptInAssetTransaction( - /* from */ address, - /* fee */ 2340, - /* asset id */13379146, - /* first round */ 15775553, - /* last round */ 15776553, - /* note */ note, - /* type */ "axfer", - /* genesis id*/ genesisId, - /* genesis hash*/ genesisHash - ); - - auto serialized = transaction.serialize(); - auto signature = Signer::sign(key, transaction); - auto result = transaction.BaseTransaction::serialize(signature); - - ASSERT_EQ(hex(serialized), "89a461726376c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a3666565cd0924a26676ce00f0b741a367656eac746573746e65742d76312e30a26768c4204863b518a4b3c84ec810f22d4f1081cb0f71f059a7ac20dec62f7f70e5093a22a26c76ce00f0bb29a3736e64c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a474797065a56178666572a478616964ce00cc264a"); - ASSERT_EQ(hex(signature), "f3a29d9a40271c00b542b38ab2ccb4967015ae6609368d4b8eb2f5e2b5348577cf9e0f62b0777ccb2d8d9b943b15c24c0cf1db312cb01a3c198d9d9c6c5bb00b"); - ASSERT_EQ(hex(result), "82a3736967c440f3a29d9a40271c00b542b38ab2ccb4967015ae6609368d4b8eb2f5e2b5348577cf9e0f62b0777ccb2d8d9b943b15c24c0cf1db312cb01a3c198d9d9c6c5bb00ba374786e89a461726376c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a3666565cd0924a26676ce00f0b741a367656eac746573746e65742d76312e30a26768c4204863b518a4b3c84ec810f22d4f1081cb0f71f059a7ac20dec62f7f70e5093a22a26c76ce00f0bb29a3736e64c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a474797065a56178666572a478616964ce00cc264a"); -} - -TEST(AlgorandSigner, ProtoSignerOptIn) { - // https://testnet.algoexplorer.io/tx/47LE2QS4B5N6IFHXOUN2MJUTCOQCHNY6AB3AJYECK4IM2VYKJDKQ - auto optIn = new Proto::AssetOptIn(); - optIn -> set_asset_id(13379146); - - auto privateKey = parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04"); - - auto input = Proto::SigningInput(); - auto genesisHash = Base64::decode("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="); - std::string str(genesisHash.begin(), genesisHash.end()); - input.set_allocated_asset_opt_in(optIn); - input.set_genesis_hash(str); - input.set_genesis_id("testnet-v1.0"); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_first_round(15775553); - input.set_last_round(15776553); - input.set_fee(2340); - - auto result = Signer::sign(input); - auto encoded = result.encoded(); - - ASSERT_EQ(hex(encoded), "82a3736967c440f3a29d9a40271c00b542b38ab2ccb4967015ae6609368d4b8eb2f5e2b5348577cf9e0f62b0777ccb2d8d9b943b15c24c0cf1db312cb01a3c198d9d9c6c5bb00ba374786e89a461726376c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a3666565cd0924a26676ce00f0b741a367656eac746573746e65742d76312e30a26768c4204863b518a4b3c84ec810f22d4f1081cb0f71f059a7ac20dec62f7f70e5093a22a26c76ce00f0bb29a3736e64c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a474797065a56178666572a478616964ce00cc264a"); -} - -TEST(AlgorandSigner, ProtoSignerAssetTransaction) { - // https://testnet.algoexplorer.io/tx/NJ62HYO2LC222AVLIN2GW5LKIWKLGC7NZLIQ3DUL2RDVRYO2UW7A - auto transaction = new Proto::AssetTransfer(); - transaction -> set_asset_id(13379146); - transaction -> set_amount(1000000); - transaction -> set_to_address("GJIWJSX2EU5RC32LKTDDXWLA2YICBHKE35RV2ZPASXZYKWUWXFLKNFSS4U"); - - auto privateKey = parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04"); - - auto input = Proto::SigningInput(); - auto genesisHash = Base64::decode("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="); - std::string str(genesisHash.begin(), genesisHash.end()); - input.set_allocated_asset_transfer(transaction); - input.set_genesis_hash(str); - input.set_genesis_id("testnet-v1.0"); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_first_round(15775683); - input.set_last_round(15776683); - input.set_fee(2340); - - auto result = Signer::sign(input); - auto encoded = result.encoded(); - - ASSERT_EQ(hex(encoded), "82a3736967c440412720eff99a17280a437bdb8eeba7404b855d6433fffd5dde7f7966c1f9ae531a1af39e18b8a58b4a6c6acb709cca92f8a18c36d8328be9520c915311027005a374786e8aa461616d74ce000f4240a461726376c420325164cafa253b116f4b54c63bd960d610209d44df635d65e095f3855a96b956a3666565cd0924a26676ce00f0b7c3a367656eac746573746e65742d76312e30a26768c4204863b518a4b3c84ec810f22d4f1081cb0f71f059a7ac20dec62f7f70e5093a22a26c76ce00f0bbaba3736e64c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a474797065a56178666572a478616964ce00cc264a"); -} diff --git a/tests/Algorand/TWAnySignerTests.cpp b/tests/Algorand/TWAnySignerTests.cpp deleted file mode 100644 index 63723876d9e..00000000000 --- a/tests/Algorand/TWAnySignerTests.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "Base64.h" -#include "proto/Algorand.pb.h" -#include - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; -using namespace TW::Algorand; - -TEST(TWAnySignerAlgorand, Sign) { - auto privateKey = parse_hex("d5b43d706ef0cb641081d45a2ec213b5d8281f439f2425d1af54e2afdaabf55b"); - auto note = parse_hex("68656c6c6f"); - auto genesisHash = Base64::decode("wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="); - - Proto::SigningInput input; - auto& transaction = *input.mutable_transfer(); - transaction.set_to_address("CRLADAHJZEW2GFY2UPEHENLOGCUOU74WYSTUXQLVLJUJFHEUZOHYZNWYR4"); - transaction.set_amount(1000000000000ull); - input.set_first_round(1937767ull); - input.set_last_round(1938767ull); - input.set_fee(263000ull); - input.set_genesis_id("mainnet-v1.0"); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_note(note.data(), note.size()); - input.set_private_key(privateKey.data(), privateKey.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeAlgorand); - - ASSERT_EQ(hex(output.encoded()), "82a3736967c440baa00062adcdcb5875e4435cdc6885d26bfe5308ab17983c0fda790b7103051fcb111554e5badfc0ac7edf7e1223a434342a9eeed5cdb047690827325051560ba374786e8aa3616d74cf000000e8d4a51000a3666565ce00040358a26676ce001d9167a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce001d954fa46e6f7465c40568656c6c6fa3726376c42014560180e9c92da3171aa3c872356e30a8ea7f96c4a74bc1755a68929c94cb8fa3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179"); -} - -TEST(TWAnySignerAlgorand, SignJSON) { - auto json = STRING(R"({"genesisId":"mainnet-v1.0","genesisHash":"wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=","note":"aGVsbG8=","firstRound":"1937767","lastRound":"1938767","fee":"263000","transfer":{"toAddress":"CRLADAHJZEW2GFY2UPEHENLOGCUOU74WYSTUXQLVLJUJFHEUZOHYZNWYR4","amount":"1000000000000"}})"); - auto key = DATA("d5b43d706ef0cb641081d45a2ec213b5d8281f439f2425d1af54e2afdaabf55b"); - - auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeAlgorand)); - - ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeAlgorand)); - assertStringsEqual(result, "82a3736967c440baa00062adcdcb5875e4435cdc6885d26bfe5308ab17983c0fda790b7103051fcb111554e5badfc0ac7edf7e1223a434342a9eeed5cdb047690827325051560ba374786e8aa3616d74cf000000e8d4a51000a3666565ce00040358a26676ce001d9167a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce001d954fa46e6f7465c40568656c6c6fa3726376c42014560180e9c92da3171aa3c872356e30a8ea7f96c4a74bc1755a68929c94cb8fa3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179"); -} diff --git a/tests/Algorand/TWCoinTypeTests.cpp b/tests/Algorand/TWCoinTypeTests.cpp deleted file mode 100644 index f95721fa727..00000000000 --- a/tests/Algorand/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWAlgorandCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeAlgorand)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("CR7POXFTYDLC7TV3IXHA7AZKWABUJC52BACLHJQNXAKZJGRPQY3A")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeAlgorand, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("J4AEINCSSLDA7LNBNWM4ZXFCTLTOZT5LG3F5BLMFPJYGFWVCMU37EZI2AM")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeAlgorand, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeAlgorand)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeAlgorand)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeAlgorand), 6); - ASSERT_EQ(TWBlockchainAlgorand, TWCoinTypeBlockchain(TWCoinTypeAlgorand)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeAlgorand)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeAlgorand)); - assertStringsEqual(symbol, "ALGO"); - assertStringsEqual(txUrl, "https://algoexplorer.io/tx/CR7POXFTYDLC7TV3IXHA7AZKWABUJC52BACLHJQNXAKZJGRPQY3A"); - assertStringsEqual(accUrl, "https://algoexplorer.io/address/J4AEINCSSLDA7LNBNWM4ZXFCTLTOZT5LG3F5BLMFPJYGFWVCMU37EZI2AM"); - assertStringsEqual(id, "algorand"); - assertStringsEqual(name, "Algorand"); -} diff --git a/tests/Arbitrum/TWCoinTypeTests.cpp b/tests/Arbitrum/TWCoinTypeTests.cpp deleted file mode 100644 index 35c6faa4ab6..00000000000 --- a/tests/Arbitrum/TWCoinTypeTests.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWArbitrumCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeArbitrum)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xa1e319be22c08155e5904aa211fb87df5f4ade48de79c6578b1cf3dfda9e498c")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeArbitrum, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xecf9ffa7f51e1194f89c25ad8c484f6bfd04e1ac")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeArbitrum, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeArbitrum)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeArbitrum)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeArbitrum), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeArbitrum)); - - assertStringsEqual(symbol, "ETH"); - assertStringsEqual(txUrl, "https://arbiscan.io/tx/0xa1e319be22c08155e5904aa211fb87df5f4ade48de79c6578b1cf3dfda9e498c"); - assertStringsEqual(accUrl, "https://arbiscan.io/address/0xecf9ffa7f51e1194f89c25ad8c484f6bfd04e1ac"); - assertStringsEqual(id, "arbitrum"); - assertStringsEqual(name, "Arbitrum"); -} diff --git a/tests/Aurora/TWCoinTypeTests.cpp b/tests/Aurora/TWCoinTypeTests.cpp deleted file mode 100644 index 5dff7ccd8fb..00000000000 --- a/tests/Aurora/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWAuroraCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeAurora)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x99deebdb70f8027037abb3d3d0f3c7523daee857d85e9056d2671593ff2f2f28")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeAurora, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x8707cdE20dd43E3dB1F74c28fcd509ef38B0bA51")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeAurora, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeAurora)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeAurora)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeAurora), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeAurora)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeAurora)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeAurora)); - assertStringsEqual(symbol, "ETH"); - assertStringsEqual(txUrl, "https://aurorascan.dev/tx/0x99deebdb70f8027037abb3d3d0f3c7523daee857d85e9056d2671593ff2f2f28"); - assertStringsEqual(accUrl, "https://aurorascan.dev/address/0x8707cdE20dd43E3dB1F74c28fcd509ef38B0bA51"); - assertStringsEqual(id, "aurora"); - assertStringsEqual(name, "Aurora"); -} diff --git a/tests/Avalanche/TWCoinTypeTests.cpp b/tests/Avalanche/TWCoinTypeTests.cpp deleted file mode 100644 index 49220a3a60b..00000000000 --- a/tests/Avalanche/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWAvalancheCoinType, TWCoinTypeCChain) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeAvalancheCChain)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x9243890b844219accefd8798271052f5a056453ec18984a56e81c92921330d54")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeAvalancheCChain, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xa664325f36Ec33E66323fe2620AF3f2294b2Ef3A")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeAvalancheCChain, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeAvalancheCChain)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeAvalancheCChain)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeAvalancheCChain), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeAvalancheCChain)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeAvalancheCChain)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeAvalancheCChain)); - assertStringsEqual(symbol, "AVAX"); - assertStringsEqual(txUrl, "https://cchain.explorer.avax.network/tx/0x9243890b844219accefd8798271052f5a056453ec18984a56e81c92921330d54"); - assertStringsEqual(accUrl, "https://cchain.explorer.avax.network/address/0xa664325f36Ec33E66323fe2620AF3f2294b2Ef3A"); - assertStringsEqual(id, "avalanchec"); - assertStringsEqual(name, "Avalanche C-Chain"); -} diff --git a/tests/BandChain/TWCoinTypeTests.cpp b/tests/BandChain/TWCoinTypeTests.cpp deleted file mode 100644 index 4fdd7e8f90b..00000000000 --- a/tests/BandChain/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWBandChainCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBandChain)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("473264551D3063A9EC64EC251C61BE92DDDFCF6CC46D026D1E574D83D5447173")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBandChain, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("band12nmsm9khdsv0tywge43q3zwj8kkj3hvup9rltp")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBandChain, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBandChain)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBandChain)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBandChain), 6); - ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeBandChain)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeBandChain)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBandChain)); - assertStringsEqual(symbol, "BAND"); - assertStringsEqual(txUrl, "https://scan-wenchang-testnet2.bandchain.org//tx/473264551D3063A9EC64EC251C61BE92DDDFCF6CC46D026D1E574D83D5447173"); - assertStringsEqual(accUrl, "https://scan-wenchang-testnet2.bandchain.org//account/band12nmsm9khdsv0tywge43q3zwj8kkj3hvup9rltp"); - assertStringsEqual(id, "band"); - assertStringsEqual(name, "BandChain"); -} diff --git a/tests/Base64Tests.cpp b/tests/Base64Tests.cpp deleted file mode 100644 index 9a918d7fd46..00000000000 --- a/tests/Base64Tests.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Base64.h" -#include "Data.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Base64; - -TEST(Base64, encode) { - auto encoded = encode(data("Hello, world!")); - EXPECT_EQ("SGVsbG8sIHdvcmxkIQ==", encoded); - encoded = encode(data("1")); - EXPECT_EQ("MQ==", encoded); - encoded = encode(data("12")); - EXPECT_EQ("MTI=", encoded); - encoded = encode(data("123")); - EXPECT_EQ("MTIz", encoded); - encoded = encode(data("1234")); - EXPECT_EQ("MTIzNA==", encoded); - encoded = encode(data("")); - EXPECT_EQ("", encoded); - encoded = encode(data("Lorem ipsum dolor sit amet, consectetur adipiscing elit")); - EXPECT_EQ("TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdA==", encoded); - encoded = encode(parse_hex("11ff8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d291b")); - EXPECT_EQ("Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb", encoded); -} - -TEST(Base64, decode) { - auto decoded = decode("SGVsbG8sIHdvcmxkIQ=="); - EXPECT_EQ(hex(data("Hello, world!")), hex(decoded)); - decoded = decode("MQ=="); - EXPECT_EQ(hex(data("1")), hex(decoded)); - decoded = decode("MTI="); - EXPECT_EQ(hex(data("12")), hex(decoded)); - decoded = decode("MTIz"); - EXPECT_EQ(hex(data("123")), hex(decoded)); - decoded = decode(""); - EXPECT_EQ("", hex(decoded)); - decoded = decode("Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb"); - EXPECT_EQ("11ff8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d291b", hex(decoded)); -} - -TEST(Base64, UrlFormat) { - const std::string const1 = "11003faa8556289975ec991ac9994dfb613abec4ea000d5094e6379080f594e559b330b8"; - - // Encoded string has both special characters - auto encoded = encode(parse_hex(const1)); - EXPECT_EQ("EQA/qoVWKJl17JkayZlN+2E6vsTqAA1QlOY3kID1lOVZszC4", encoded); - encoded = encodeBase64Url(parse_hex(const1)); - EXPECT_EQ("EQA_qoVWKJl17JkayZlN-2E6vsTqAA1QlOY3kID1lOVZszC4", encoded); - - auto decoded = decode("EQA/qoVWKJl17JkayZlN+2E6vsTqAA1QlOY3kID1lOVZszC4"); - EXPECT_EQ(const1, hex(decoded)); - decoded = decodeBase64Url("EQA_qoVWKJl17JkayZlN-2E6vsTqAA1QlOY3kID1lOVZszC4"); - EXPECT_EQ(const1, hex(decoded)); -} diff --git a/tests/Binance/SignerTests.cpp b/tests/Binance/SignerTests.cpp deleted file mode 100644 index 66e5d88fc91..00000000000 --- a/tests/Binance/SignerTests.cpp +++ /dev/null @@ -1,705 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Bech32Address.h" -#include "Binance/Address.h" -#include "Binance/Signer.h" -#include "Coin.h" -#include "Ethereum/Address.h" -#include "HDWallet.h" -#include "HexCoding.h" -#include "proto/Binance.pb.h" - -#include - -namespace TW::Binance { - -TEST(BinanceSigner, Sign) { - auto input = Proto::SigningInput(); - input.set_chain_id("chain-bnb"); - input.set_account_number(12); - input.set_sequence(35); - input.set_memo(""); - input.set_source(1); - - auto key = parse_hex("90335b9d2153ad1a9799a3ccc070bd64b4164e9642ee1dd48053c33f9a3a05e9"); - input.set_private_key(key.data(), key.size()); - - auto& order = *input.mutable_trade_order(); - Binance::Address address; - ASSERT_TRUE(Binance::Address::decode("bnb1hgm0p7khfk85zpz5v0j8wnej3a90w709vhkdfu", address)); - auto keyhash = address.getKeyHash(); - order.set_sender(keyhash.data(), keyhash.size()); - order.set_id("BA36F0FAD74D8F41045463E4774F328F4AF779E5-36"); - order.set_symbol("NNB-338_BNB"); - order.set_ordertype(2); - order.set_side(1); - order.set_price(136350000); - order.set_quantity(100000000); - order.set_timeinforce(1); - - auto signer = Binance::Signer(std::move(input)); - auto signature = signer.sign(); - - ASSERT_EQ(hex(signature.begin(), signature.end()), - "9123cb6906bb20aeb753f4a121d4d88ff0e9750ba75b0c4e10d76caee1e7d2481290fa3b9887a6225d69" - "97f5f939ef834ea61d596a314237c48e560da9e17b5a"); -} - -TEST(BinanceSigner, Build) { - auto input = Proto::SigningInput(); - input.set_chain_id("chain-bnb"); - input.set_account_number(1); - input.set_sequence(10); - - auto key = parse_hex("90335b9d2153ad1a9799a3ccc070bd64b4164e9642ee1dd48053c33f9a3a05e9"); - input.set_private_key(key.data(), key.size()); - - auto& order = *input.mutable_trade_order(); - auto address = Binance::Address(parse_hex("b6561dcc104130059a7c08f48c64610c1f6f9064")); - auto keyhash = address.getKeyHash(); - order.set_sender(keyhash.data(), keyhash.size()); - order.set_id("B6561DCC104130059A7C08F48C64610C1F6F9064-11"); - order.set_symbol("BTC-5C4_BNB"); - order.set_ordertype(2); - order.set_side(1); - order.set_price(100000000); - order.set_quantity(1200000000); - order.set_timeinforce(1); - - auto signer = Binance::Signer(std::move(input)); - auto result = signer.build(); - - ASSERT_EQ(hex(result.begin(), result.end()), "db01" - "f0625dee" - "0a65" - "ce6dc043" - "0a14""b6561dcc104130059a7c08f48c64610c1f6f9064" - "122b""423635363144434331303431333030353941374330384634384336343631304331463646393036342d3131" - "1a0b""4254432d3543345f424e42" - "2002" - "2801" - "3080c2d72f" - "3880989abc04" - "4001" - "126e" - "0a26" - "eb5ae987" - "21029729a52e4e3c2b4a4e52aa74033eedaf8ba1df5ab6d1f518fd69e67bbd309b0e" - "1240""2a78b6d9a108eb9440221802b626e24d80179395ac984f016db012ef1a0c16d71b4d7053e05366ae3ea2681fc8052398fda20551c965d74c5970bbc66b94b48e" - "1801" - "200a" - ); -} - -TEST(BinanceSigner, BuildSend) { - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("chain-bnb"); - signingInput.set_account_number(19); - signingInput.set_sequence(23); - signingInput.set_memo("test"); - signingInput.set_source(1); - - auto key = parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"); - signingInput.set_private_key(key.data(), key.size()); - - auto& order = *signingInput.mutable_send_order(); - - auto fromKeyhash = parse_hex("40c2979694bbc961023d1d27be6fc4d21a9febe6"); - auto fromAddress = Binance::Address(fromKeyhash); - - auto toKeyhash = parse_hex("88b37d5e05f3699e2a1406468e5d87cb9dcceb95"); - auto toAddress = Binance::Address(toKeyhash); - - auto input = order.add_inputs(); - input->set_address(fromKeyhash.data(), fromKeyhash.size()); - auto inputCoin = input->add_coins(); - inputCoin->set_denom("BNB"); - inputCoin->set_amount(1'001'000'000); - - auto output = order.add_outputs(); - output->set_address(toKeyhash.data(), toKeyhash.size()); - auto outputCoin = output->add_coins(); - outputCoin->set_denom("BNB"); - outputCoin->set_amount(1'001'000'000); - - auto signer = Binance::Signer(std::move(signingInput)); - auto signature = signer.sign(); - ASSERT_EQ(hex(signature.begin(), signature.end()), - "c65a13440f18a155bd971ee40b9e0dd58586f5bf344e12ec4c76c439aebca8c7789bab7bfbfb4ce89aad" - "c4a02df225b6b6efc861c13bbeb5f7a3eea2d7ffc80f"); - - auto result = signer.build(); - - ASSERT_EQ(hex(result.begin(), result.end()), "cc01" - "f0625dee" - "0a4e" - "2a2c87fa" - "0a23""0a1440c2979694bbc961023d1d27be6fc4d21a9febe6120b0a03424e4210c098a8dd03" - "1223""0a1488b37d5e05f3699e2a1406468e5d87cb9dcceb95120b0a03424e4210c098a8dd03" - "126e" - "0a26" - "eb5ae987" - "21026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502" - "1240""c65a13440f18a155bd971ee40b9e0dd58586f5bf344e12ec4c76c439aebca8c7789bab7bfbfb4ce89aadc4a02df225b6b6efc861c13bbeb5f7a3eea2d7ffc80f" - "1813" - "2017" - "1a04""74657374" - "2001" - ); -} - -TEST(BinanceSigner, BuildSend2) { - const auto derivationPath = TW::derivationPath(TWCoinTypeBinance); - - const auto fromWallet = HDWallet("swift slam quote sail high remain mandate sample now stamp title among fiscal captain joy puppy ghost arrow attract ozone situate install gain mean", ""); - const auto fromPrivateKey = fromWallet.getKey(TWCoinTypeBinance, derivationPath); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - - const auto toWallet = HDWallet( "bottom quick strong ranch section decide pepper broken oven demand coin run jacket curious business achieve mule bamboo remain vote kid rigid bench rubber", ""); - const auto toPrivateKey = toWallet.getKey(TWCoinTypeBinance, derivationPath); - const auto toPublicKey = PublicKey(toPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("bnbchain-1000"); - signingInput.set_account_number(0); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto token = Proto::SendOrder::Token(); - token.set_denom("BNB"); - token.set_amount(100000000000000); - - auto input = Proto::SendOrder::Input(); - auto fromKeyHash = Binance::Address(fromPublicKey).getKeyHash(); - input.set_address(fromKeyHash.data(), fromKeyHash.size()); - *input.add_coins() = token; - - auto output = Proto::SendOrder::Output(); - auto toKeyHash = Binance::Address(toPublicKey).getKeyHash(); - output.set_address(toKeyHash.data(), toKeyHash.size()); - *output.add_coins() = token; - - auto sendOrder = Proto::SendOrder(); - *sendOrder.add_inputs() = input; - *sendOrder.add_outputs() = output; - - *signingInput.mutable_send_order() = sendOrder; - - const auto data = Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data.begin(), data.end()), - "c601" - "f0625dee" - "0a52" - "2a2c87fa" - "0a25""0a141d0e3086e8e4e0a53c38a90d55bd58b34d57d2fa120d0a03424e42108080e983b1de16" - "1225""0a146b571fc0a9961a7ddf45e49a88a4d83941fcabbe120d0a03424e42108080e983b1de16" - "126c" - "0a26" - "eb5ae987" - "21027e69d96640300433654e016d218a8d7ffed751023d8efe81e55dedbd6754c971" - "1240""8b23eecfa8237a27676725173e58154e6c204bb291b31c3b7b507c8f04e2773909ba70e01b54f4bd0bc76669f5712a5a66b9508acdf3aa5e4fde75fbe57622a1" - "2001" - ); -} - -TEST(BinanceSigner, BuildHTLT) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - auto fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - const auto toPrivateKey = - PrivateKey(parse_hex("851fab89c14f4bbec0cc06f5e445ec065efc641068d78b308c67217d9bd5c88a")); - const auto toPublicKey = PublicKey(toPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - auto toAddr = Binance::Address(toPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(0); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto token = Proto::SendOrder::Token(); - token.set_denom("BNB"); - token.set_amount(100000000); - - auto randomNumberHash = - parse_hex("e8eae926261ab77d018202434791a335249b470246a7b02e28c3b2fb6ffad8f3"); - - auto& htltOrder = *signingInput.mutable_htlt_order(); - htltOrder.set_from(fromAddr.data(), fromAddr.size()); - htltOrder.set_to(toAddr.data(), toAddr.size()); - htltOrder.set_recipient_other_chain(""); - htltOrder.set_sender_other_chain(""); - *htltOrder.add_amount() = token; - htltOrder.set_height_span(400); - htltOrder.set_expected_income("100000000:BTC-1DC"); - htltOrder.set_timestamp(1567746273); - htltOrder.set_random_number_hash(randomNumberHash.data(), randomNumberHash.size()); - htltOrder.set_cross_chain(false); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data.begin(), data.end()), - "ee01f0625dee0a7ab33f9a240a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e112140153f11d6db7" - "e69c7d51e771c697378018fb6c242a20e8eae926261ab77d018202434791a335249b470246a7b02e28c3" - "b2fb6ffad8f330e1d1c7eb053a0a0a03424e421080c2d72f42113130303030303030303a4254432d3144" - "43489003126c0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" - "6b6a8fc7124051439de2da19fe9fd22137c903cfc5dc87553bf05dca0bb202c0e07c47f9b51269efa272" - "43eb7b55888f5384a84ac1eac6d325c830d1be0ed042838e2dc0f6a9180f"); -} - -TEST(BinanceSigner, BuildDepositHTLT) { - const auto fromPrivateKey = - PrivateKey(parse_hex("851fab89c14f4bbec0cc06f5e445ec065efc641068d78b308c67217d9bd5c88a")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - auto fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(16); - signingInput.set_sequence(0); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto token = Proto::SendOrder::Token(); - token.set_denom("BTC-1DC"); - token.set_amount(100000000); - - auto swapID = parse_hex("dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5"); - - auto& depositHTLTOrder = *signingInput.mutable_deposithtlt_order(); - depositHTLTOrder.set_from(fromAddr.data(), fromAddr.size()); - depositHTLTOrder.set_swap_id(swapID.data(), swapID.size()); - *depositHTLTOrder.add_amount() = token; - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data.begin(), data.end()), - "c001f0625dee0a4c639864960a140153f11d6db7e69c7d51e771c697378018fb6c24120e0a074254432d" - "3144431080c2d72f1a20dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5" - "126c0a26eb5ae98721038df6960084e20b2d07d50e1422f94105c6241d9f1482a4eb79ce8bfd460f19e4" - "12400ca4144c6818e2836d09b4faf3161781d85f9adfc00badb2eaa0953174610a233b81413dafcf8471" - "6abad48a4ed3aeb9884d90eb8416eec5d5c0c6930ab60bd01810"); -} - -TEST(BinanceSigner, BuildClaimHTLT) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - auto fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto randomNumber = - parse_hex("bda6933c7757d0ca428aa01fb9d0935a231f87bf2deeb9b409cea3f2d580a2cc"); - auto swapID = parse_hex("dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5"); - - auto& claimHTLTOrder = *signingInput.mutable_claimhtlt_order(); - claimHTLTOrder.set_from(fromAddr.data(), fromAddr.size()); - claimHTLTOrder.set_swap_id(swapID.data(), swapID.size()); - claimHTLTOrder.set_random_number(randomNumber.data(), randomNumber.size()); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ( - hex(data.begin(), data.end()), - "d401f0625dee0a5ec16653000a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11220dd8fd4719741844d35" - "eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e51a20bda6933c7757d0ca428aa01fb9d0935a231f87bf" - "2deeb9b409cea3f2d580a2cc126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561a" - "ac993dbe0f6b6a8fc71240fa30ba50111aa31d8329dacb6d044c1c7d54f1cb782bc9aa2a50c3fabce02a4579d7" - "5b76ca69a9fab11b676d9da66b5af7aa4c9ad3d18e24fffeb16433be39fb180f2001"); -} - -TEST(BinanceSigner, BuildRefundHTLT) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - auto fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto swapID = parse_hex("dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5"); - - auto& refundHTLTOrder = *signingInput.mutable_refundhtlt_order(); - refundHTLTOrder.set_from(fromAddr.data(), fromAddr.size()); - refundHTLTOrder.set_swap_id(swapID.data(), swapID.size()); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data.begin(), data.end()), - "b201f0625dee0a3c3454a27c0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11220dd8fd4719741" - "844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5126e0a26eb5ae9872103a9a55c040c8e" - "b8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240c9f36142534d16ec8ce656f8eb73" - "70b32206a2d15198b7165acf1e2a18952c9e4570b0f862e1ab7bb868c30781a42c9e3ec0ae08982e8d6c" - "91c55b83c71b7b1e180f2001"); -} - -TEST(BinanceSigner, BuildIssueOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& issueOrder = *signingInput.mutable_issue_order(); - issueOrder.set_from(fromAddr.data(), fromAddr.size()); - issueOrder.set_name("NewBinanceToken"); - issueOrder.set_symbol("NNB-338_BNB"); - issueOrder.set_total_supply(1000000000); - issueOrder.set_mintable(true); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data.begin(), data.end()), - "b601f0625dee0a40" - "17efab80" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120f" - "4e657742696e616e6365546f6b656e" - "1a0b" - "4e4e422d3333385f424e42" - "208094ebdc032801126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac" - "993dbe0f6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c9" - "5aa2213db595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001"); -} - -TEST(BinanceSigner, BuildMintOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& mintOrder = *signingInput.mutable_mint_order(); - mintOrder.set_from(fromAddr.data(), fromAddr.size()); - mintOrder.set_symbol("NNB-338_BNB"); - mintOrder.set_amount(1000000); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data.begin(), data.end()), - "a101f0625dee0a2b" - "467e0829" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" - "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" - "6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213d" - "b595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001"); -} - -TEST(BinanceSigner, BuildBurnOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& burnOrder = *signingInput.mutable_burn_order(); - burnOrder.set_from(fromAddr.data(), fromAddr.size()); - burnOrder.set_symbol("NNB-338_BNB"); - burnOrder.set_amount(1000000); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data.begin(), data.end()), - "a101f0625dee0a2b" - "7ed2d2a0" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" - "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" - "6b6a8fc71240b2612500b80f5ee8fab1574e9b1763fd898a7048910d02e00dea6337d3c848c95aa2213d" - "b595179db076dfdb10f6e2d9b2aa76c9cd3ee11396ac224991e3e0cd180f2001"); -} - -TEST(BinanceSigner, BuildFreezeOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& freezeOrder = *signingInput.mutable_freeze_order(); - freezeOrder.set_from(fromAddr.data(), fromAddr.size()); - freezeOrder.set_symbol("NNB-338_BNB"); - freezeOrder.set_amount(1000000); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data.begin(), data.end()), - "a101f0625dee0a2b" - "e774b32d" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" - "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" - "6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162" - "722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001"); -} - -TEST(BinanceSigner, BuildUnfreezeOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& unfreezeOrder = *signingInput.mutable_unfreeze_order(); - unfreezeOrder.set_from(fromAddr.data(), fromAddr.size()); - unfreezeOrder.set_symbol("NNB-338_BNB"); - unfreezeOrder.set_amount(1000000); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data.begin(), data.end()), - "a101f0625dee0a2b" - "6515ff0d" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" - "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" - "6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162" - "722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001"); -} - -TEST(BinanceSigner, BuildTransferOutOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - const auto toAddr = Ethereum::Address("0x35552c16704d214347f29Fa77f77DA6d75d7C752"); - const auto toBytes = Data(toAddr.bytes.begin(), toAddr.bytes.end()); - - auto input = Proto::SigningInput(); - input.set_chain_id("test-chain"); - input.set_account_number(15); - input.set_sequence(1); - input.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& order = *input.mutable_transfer_out_order(); - order.set_from(fromAddr.data(), fromAddr.size()); - order.set_to(toBytes.data(), toBytes.size()); - order.set_expire_time(12345678); - - auto& token = *order.mutable_amount(); - token.set_denom("BNB"); - token.set_amount(100000000); - - const auto data = Binance::Signer(std::move(input)).build(); - ASSERT_EQ(hex(data.begin(), data.end()), - "b701f0625dee0a41800819c00a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1121435552c16704d" - "214347f29fa77f77da6d75d7c7521a0a0a03424e421080c2d72f20cec2f105126e0a26eb5ae9872103a9" - "a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc712407eda148e1167b1be12" - "71a788ccf4e3eade1c7e1773e9d2093982d7f802f8f85f35ef550049011728206e4eda1a272f9e96fd95" - "ef3983cad85a29cd14262c22e0180f2001"); -} - -TEST(BinanceSigner, BuildSideChainDelegate) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - auto validator = Bech32Address(""); - Bech32Address::decode("bva10npy5809y303f227g4leqw7vs3s6ep5ul26sq2", validator, "bva"); - - auto input = Proto::SigningInput(); - input.set_chain_id("test-chain"); - input.set_account_number(15); - input.set_sequence(1); - input.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& order = *input.mutable_side_delegate_order(); - order.set_delegator_addr(fromAddr.data(), fromAddr.size()); - order.set_validator_addr(validator.getKeyHash().data(), validator.getKeyHash().size()); - order.set_chain_id("chapel"); - - auto& token = *order.mutable_delegation(); - token.set_denom("BNB"); - token.set_amount(200000000); - - const auto data = Binance::Signer(std::move(input)).build(); - ASSERT_EQ(hex(data.begin(), data.end()), - "ba01f0625dee0a44e3a07fd20a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e112147cc24a1de524" - "5f14a95e457f903bcc8461ac869c1a0a0a03424e42108084af5f220663686170656c126e0a26eb5ae987" - "2103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc7124039302c9975fb" - "2a09ac2b6b6fb1d3b9fb5b4c03630d3d7a7da42b1c6736d6127142a3fcdca0b70a3d065da8d4f4df8b5d" - "9d8f46aeb3627a7d7aa901fe186af34c180f2001"); -} - -TEST(BinanceSigner, BuildSideChainRedelegate) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - auto validator1 = Bech32Address(""); - auto validator2 = Bech32Address(""); - Bech32Address::decode("bva1echrty7p8r23cwx8g3ezwcza9azy4zq7ca0pzw", validator1, "bva"); - Bech32Address::decode("bva1p7s26ervsmv3w83k5696glautc9sm5rchz5f5e", validator2, "bva"); - - auto input = Proto::SigningInput(); - input.set_chain_id("test-chain"); - input.set_account_number(15); - input.set_sequence(1); - input.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& order = *input.mutable_side_redelegate_order(); - order.set_delegator_addr(fromAddr.data(), fromAddr.size()); - order.set_validator_src_addr(validator1.getKeyHash().data(), validator1.getKeyHash().size()); - order.set_validator_dst_addr(validator2.getKeyHash().data(), validator2.getKeyHash().size()); - order.set_chain_id("chapel"); - - auto& token = *order.mutable_amount(); - token.set_denom("BNB"); - token.set_amount(100000000); - - const auto data = Binance::Signer(std::move(input)).build(); - ASSERT_EQ(hex(data.begin(), data.end()), - "d001f0625dee0a5ae3ced3640a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11214ce2e3593c138" - "d51c38c7447227605d2f444a881e1a140fa0ad646c86d9171e36a68ba47fbc5e0b0dd078220a0a03424e" - "421080c2d72f2a0663686170656c126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08" - "af44ea561aac993dbe0f6b6a8fc71240114c6927423e95ecc831ec763b629b3a40db8feeb330528a941f" - "d74843c0d63b4271b23916770d4901988c1f56b20086e5768a12290ebec265e30a80f8f3d88e180f2001" - ); -} - -TEST(BinanceSigner, BuildSideChainUndelegate) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - auto validator = Bech32Address(""); - Bech32Address::decode("bva1echrty7p8r23cwx8g3ezwcza9azy4zq7ca0pzw", validator, "bva"); - - auto input = Proto::SigningInput(); - input.set_chain_id("test-chain"); - input.set_account_number(15); - input.set_sequence(1); - input.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& order = *input.mutable_side_undelegate_order(); - order.set_delegator_addr(fromAddr.data(), fromAddr.size()); - order.set_validator_addr(validator.getKeyHash().data(), validator.getKeyHash().size()); - order.set_chain_id("chapel"); - - auto& token = *order.mutable_amount(); - token.set_denom("BNB"); - token.set_amount(100000000); - - const auto data = Binance::Signer(std::move(input)).build(); - ASSERT_EQ(hex(data.begin(), data.end()), - "ba01f0625dee0a44514f7e0e0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11214ce2e3593c138" - "d51c38c7447227605d2f444a881e1a0a0a03424e421080c2d72f220663686170656c126e0a26eb5ae987" - "2103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240a622b7ca7a28" - "75e5eaa675a5ed976b2ec3b8ca055a2b05e7fb471d328bd04df854789437dd06407e41ebb1e5a345604c" - "93663dfb660e223800636c0b94c2e798180f2001" - ); -} - -TEST(BinanceSigner, BuildTimeLockOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& lockOrder = *signingInput.mutable_time_lock_order(); - lockOrder.set_from_address(fromAddr.data(), fromAddr.size()); - lockOrder.set_description("Description locked for offer"); - auto amount = lockOrder.add_amount(); - amount->set_denom("BNB"); - amount->set_amount(1000000); - lockOrder.set_lock_time(1600001371); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - EXPECT_EQ(hex(data.begin(), data.end()), - "bf01f0625dee0a49" - "07921531" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1121c4465736372697074696f6e206c6f636b656420666f72206f666665721a090a03424e4210c0843d20dbaaf8fa05126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240c270822b9515ba486c6a6b3472d388a5aea872ed960c0b53de0fafdc8682ef473a126f01e7dd2c00f04a0138a601b9540f54b14026846de362f7ab7f9fed948b180f2001" - ); -} - -TEST(BinanceSigner, BuildTimeRelockOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& relockOrder = *signingInput.mutable_time_relock_order(); - relockOrder.set_from_address(fromAddr.data(), fromAddr.size()); - relockOrder.set_id(333); - relockOrder.set_description("Description locked for offer"); - auto amount = relockOrder.add_amount(); - amount->set_denom("BNB"); - amount->set_amount(1000000); - relockOrder.set_lock_time(1600001371); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - EXPECT_EQ(hex(data.begin(), data.end()), - "c201f0625dee0a4c504711da0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e110cd021a1c446573" - "6372697074696f6e206c6f636b656420666f72206f6666657222090a03424e4210c0843d28dbaaf8fa05" - "126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc7" - "124086ddaa077c8ae551d402fa409cf7e91663982b0542200967c03c0b5876b181353250f689d342f221" - "7624a077b671ce7d09649187e29879f40abbbee9de7ab27c180f2001"); -} - -TEST(BinanceSigner, BuildTimeUnlockOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& unlockOrder = *signingInput.mutable_time_unlock_order(); - unlockOrder.set_from_address(fromAddr.data(), fromAddr.size()); - unlockOrder.set_id(333); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - EXPECT_EQ(hex(data.begin(), data.end()), - "9301f0625dee0a1dc4050c6c0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e110cd02126e0a26eb" - "5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240da777b" - "fd2032834f59ec9fe69fd6eaa4aca24242dfbc5ec4ef8c435cb9da7eb05ab78e1b8ca9f109657cb77996" - "898f1b59137b3d8f1e00f842e409e18033b347180f2001"); -} - -} // namespace TW::Binance diff --git a/tests/Binance/TWAnySignerTests.cpp b/tests/Binance/TWAnySignerTests.cpp deleted file mode 100644 index 514b0435992..00000000000 --- a/tests/Binance/TWAnySignerTests.cpp +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "Binance/Address.h" -#include "proto/Binance.pb.h" -#include -#include "Coin.h" - -#include "../interface/TWTestUtilities.h" -#include -#include - -using namespace TW; -using namespace TW::Binance; - -Proto::SigningOutput SignTest() { - auto input = Proto::SigningInput(); - input.set_chain_id("Binance-Chain-Tigris"); - input.set_account_number(0); - input.set_sequence(0); - input.set_source(0); - - auto key = parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"); - input.set_private_key(key.data(), key.size()); - - auto& order = *input.mutable_send_order(); - - Address fromAddress; - EXPECT_TRUE(Address::decode("bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", fromAddress)); - EXPECT_EQ(hex(fromAddress.getKeyHash()), "40c2979694bbc961023d1d27be6fc4d21a9febe6"); - auto fromKeyhash = fromAddress.getKeyHash(); - Address toAddress; - EXPECT_TRUE(Address::decode("bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5", toAddress)); - EXPECT_EQ(hex(toAddress.getKeyHash()), "bffe47abfaede50419c577f1074fee6dd1535cd1"); - auto toKeyhash = toAddress.getKeyHash(); - - { - auto input = order.add_inputs(); - input->set_address(fromKeyhash.data(), fromKeyhash.size()); - auto inputCoin = input->add_coins(); - inputCoin->set_denom("BNB"); - inputCoin->set_amount(1); - } - - { - auto output = order.add_outputs(); - output->set_address(toKeyhash.data(), toKeyhash.size()); - auto outputCoin = output->add_coins(); - outputCoin->set_denom("BNB"); - outputCoin->set_amount(1); - } - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeBinance); - return output; -} - -TEST(TWAnySignerBinance, Sign) { - Proto::SigningOutput output = SignTest(); - ASSERT_EQ(hex(output.encoded()), "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212409073e581e1ea4fdf11242fe30a732f96d20799c638354bcf7a242161ac015b9321fbbed93e85b0ef9b5de58fba74dff54ecb1e379ef26e1023be8996003f4899"); -} - -TEST(TWAnySignerBinance, SignJSON) { - auto json = STRING(R"({"chainId":"Binance-Chain-Tigris","accountNumber":"13186","source":"2","memo":"Testing","sendOrder":{"inputs":[{"address":"EuZU7e+eUIuDNzaph9Bp2lqJrts=","coins":[{"denom":"BNB","amount":"1345227"}]}],"outputs":[{"address":"M7vzB7mBRvE9IGk8+UbC13pMryg=","coins":[{"denom":"BNB","amount":"1345227"}]}]}})"); - auto key = DATA("f947b3554a1c2fa70e1caa9de53fbda353348d1e856c593848f3a29737d31f11"); - auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeBinance)); - - ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeBinance)); - assertStringsEqual(result, "ca01f0625dee0a4a2a2c87fa0a210a1412e654edef9e508b833736a987d069da5a89aedb12090a03424e4210cb8d5212210a1433bbf307b98146f13d20693cf946c2d77a4caf2812090a03424e4210cb8d52126d0a26eb5ae9872102e58176f271a9796b4288908be85094a2ac978e25535fd59a37b58626e3a84d9e1240015b4beb3d6ef366a7a92fd283f66b8f0d8cdb6b152a9189146b27f84f507f047e248517cf691a36ebc2b7f3b7f64e27585ce1c40f1928d119c31af428efcf3e1882671a0754657374696e672002"); -} - -TEST(TWAnySignerBinance, MultithreadedSigning) { - auto f = [](int n) { - for (int i = 0; i < n; i++) { - Proto::SigningOutput output = SignTest(); - ASSERT_EQ(hex(output.encoded()), "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212409073e581e1ea4fdf11242fe30a732f96d20799c638354bcf7a242161ac015b9321fbbed93e85b0ef9b5de58fba74dff54ecb1e379ef26e1023be8996003f4899"); - } - }; - - // Ensure multiple threads cause no asserts - std::thread th1(f, 1000); - std::thread th2(f, 1000); - std::thread th3(f, 1000); - - th1.join(); - th2.join(); - th3.join(); -} diff --git a/tests/Binance/TWCoinTypeTests.cpp b/tests/Binance/TWCoinTypeTests.cpp deleted file mode 100644 index 52ce9bc3bf9..00000000000 --- a/tests/Binance/TWCoinTypeTests.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWBinanceCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBinance)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("A93625C9F9ABEA1A8E31585B30BBB16C34FAE0D172EB5B6B2F834AF077BF06BB")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBinance, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("bnb1u7jm0cll5h3224y0tapwn6gf6pr49ytewx4gsz")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBinance, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBinance)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBinance)); - const auto chainId = WRAPS(TWCoinTypeChainId(TWCoinTypeBinance)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBinance), 8); - ASSERT_EQ(TWBlockchainBinance, TWCoinTypeBlockchain(TWCoinTypeBinance)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeBinance)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBinance)); - assertStringsEqual(symbol, "BNB"); - assertStringsEqual(txUrl, "https://explorer.binance.org/tx/A93625C9F9ABEA1A8E31585B30BBB16C34FAE0D172EB5B6B2F834AF077BF06BB"); - assertStringsEqual(accUrl, "https://explorer.binance.org/address/bnb1u7jm0cll5h3224y0tapwn6gf6pr49ytewx4gsz"); - assertStringsEqual(id, "binance"); - assertStringsEqual(name, "BNB Beacon Chain"); - assertStringsEqual(chainId, "Binance-Chain-Tigris"); -} diff --git a/tests/BinanceSmartChain/SignerTests.cpp b/tests/BinanceSmartChain/SignerTests.cpp deleted file mode 100644 index f4e5f4645b0..00000000000 --- a/tests/BinanceSmartChain/SignerTests.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include "Ethereum/Signer.h" -#include "Ethereum/Transaction.h" -#include "Ethereum/Address.h" -#include "Ethereum/RLP.h" -#include "Ethereum/ABI.h" -#include "proto/Ethereum.pb.h" -#include "HexCoding.h" -#include "uint256.h" -#include "../interface/TWTestUtilities.h" - -#include - -namespace TW::Binance { - -using namespace TW::Ethereum; -using namespace TW::Ethereum::ABI; - - -TEST(BinanceSmartChain, SignNativeTransfer) { - // https://explorer.binance.org/smart-testnet/tx/0x6da28164f7b3bc255d749c3ae562e2a742be54c12bf1858b014cc2fe5700684e - - auto toAddress = parse_hex("0x31BE00EB1fc8e14A696DBC72f746ec3e95f49683"); - auto transaction = TransactionNonTyped::buildNativeTransfer( - /* nonce: */ 0, - /* gasPrice: */ 20000000000, - /* gasLimit: */ 21000, - /* to: */ toAddress, - /* amount: */ 10000000000000000 // 0.01 - ); - - // addr: 0xB9F5771C27664bF2282D98E09D7F50cEc7cB01a7 mnemonic: isolate dismiss ... cruel note - auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); - uint256_t chainID = 97; - auto signature = Signer::sign(privateKey, chainID, transaction); - - auto encoded = transaction->encoded(signature, chainID); - ASSERT_EQ(hex(encoded), "f86c808504a817c8008252089431be00eb1fc8e14a696dbc72f746ec3e95f49683872386f26fc100008081e5a057806b486844c5d0b7b5ce34b289f4e8776aa1fe24a3311cef5053995c51050ca07697aa0695de27da817625df0e7e4c64b0ab22d9df30aec92299a7b380be8db7"); -} - -TEST(BinanceSmartChain, SignTokenTransfer) { - auto toAddress = parse_hex("0x31BE00EB1fc8e14A696DBC72f746ec3e95f49683"); - auto func = Function("transfer", std::vector>{ - std::make_shared(toAddress), - std::make_shared(uint256_t(10000000000000000)) - }); - Data payloadFunction; - func.encode(payloadFunction); - EXPECT_EQ(hex(payloadFunction), "a9059cbb00000000000000000000000031be00eb1fc8e14a696dbc72f746ec3e95f49683000000000000000000000000000000000000000000000000002386f26fc10000"); - - auto input = Proto::SigningInput(); - auto chainId = store(uint256_t(97)); - auto nonce = store(uint256_t(30)); - auto gasPrice = store(uint256_t(20000000000)); - auto gasLimit = store(uint256_t(1000000)); - auto tokenContractAddress = "0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee"; - auto dummyAmount = store(uint256_t(0)); - // addr: 0xB9F5771C27664bF2282D98E09D7F50cEc7cB01a7 mnemonic: isolate dismiss ... cruel note - auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); - - input.set_chain_id(chainId.data(), chainId.size()); - input.set_nonce(nonce.data(), nonce.size()); - input.set_gas_price(gasPrice.data(), gasPrice.size()); - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_to_address(tokenContractAddress); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); - transfer.set_data(payloadFunction.data(), payloadFunction.size()); - - const std::string expected = "f8ab1e8504a817c800830f424094ed24fc36d5ee211ea25a80239fb8c4cfd80f12ee80b844a9059cbb00000000000000000000000031be00eb1fc8e14a696dbc72f746ec3e95f49683000000000000000000000000000000000000000000000000002386f26fc1000081e6a0aa9d5e9a947e96f728fe5d3e6467000cd31a693c00270c33ec64b4abddc29516a00bf1d5646139b2bcca1ad64e6e79f45b7d1255de603b5a3765cbd9544ae148d0"; - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSmartChain); - - EXPECT_EQ(hex(output.encoded()), expected); -} - -} // namespace TW::Binance diff --git a/tests/BinanceSmartChain/TWAnyAddressTests.cpp b/tests/BinanceSmartChain/TWAnyAddressTests.cpp deleted file mode 100644 index e50537d7b8c..00000000000 --- a/tests/BinanceSmartChain/TWAnyAddressTests.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include "HexCoding.h" - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; - -TEST(TWBinanceSmartChain, Address) { - - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06").get())); - auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false)); - auto string = "0xf3d468DBb386aaD46E92FF222adDdf872C8CC064"; - - auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeSmartChain)); - auto expected = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING(string).get(), TWCoinTypeSmartChain)); - - auto addressString = WRAPS(TWAnyAddressDescription(address.get())); - auto expectedString = WRAPS(TWAnyAddressDescription(expected.get())); - - assertStringsEqual(addressString, string); - assertStringsEqual(expectedString, string); -} diff --git a/tests/BinanceSmartChain/TWCoinTypeTests.cpp b/tests/BinanceSmartChain/TWCoinTypeTests.cpp deleted file mode 100644 index 9f768216878..00000000000 --- a/tests/BinanceSmartChain/TWCoinTypeTests.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWBinanceSmartChainCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeSmartChain)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeSmartChain, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x35552c16704d214347f29Fa77f77DA6d75d7C752")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeSmartChain, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeSmartChain)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeSmartChain)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeSmartChain), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeSmartChain)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeSmartChain)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeSmartChain)); - ASSERT_EQ(20000714, TWCoinTypeSmartChain); - assertStringsEqual(symbol, "BNB"); - assertStringsEqual(txUrl, "https://bscscan.com/tx/0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e"); - assertStringsEqual(accUrl, "https://bscscan.com/address/0x35552c16704d214347f29Fa77f77DA6d75d7C752"); - assertStringsEqual(id, "smartchain"); - assertStringsEqual(name, "BNB Smart Chain"); -} - -TEST(TWBinanceSmartChainLegacyCoinType, TWCoinType) { - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeSmartChainLegacy)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeSmartChainLegacy)); - - ASSERT_EQ(10000714, TWCoinTypeSmartChainLegacy); - assertStringsEqual(id, "bsc"); - assertStringsEqual(name, "Smart Chain Legacy"); -} diff --git a/tests/BinaryCodingTests.cpp b/tests/BinaryCodingTests.cpp deleted file mode 100644 index 138cbb7bea8..00000000000 --- a/tests/BinaryCodingTests.cpp +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "BinaryCoding.h" -#include "HexCoding.h" - -#include - -#include - -using namespace std; -using namespace TW; - -TEST(BinaryCodingTests, varIntSize) { - vector> tests = { - {0, 1}, - {1, 1}, - {10, 1}, - {100, 1}, - {0xfb, 1}, - {0xfc, 1}, - {0xfd, 3}, - {0xfe, 3}, - {0xff, 3}, - {0x100, 3}, - {0x200, 3}, - {0x1000, 3}, - {0xffff, 3}, - {0x10000, 5}, - {0x20000, 5}, - {0xffffffff, 5}, - {0x100000000, 9}, - {0x200000000, 9}, - {0x1000000000, 9}, - {0x10000000000, 9}, - {0x100000000000, 9}, - {0x1000000000000, 9}, - {0x10000000000000, 9}, - {0x100000000000000, 9}, - {0xffffffffffffffff, 9}, - }; - for (auto& test : tests) { - EXPECT_EQ(varIntSize(get<0>(test)), get<1>(test)); - } -} - -void testEncodeVarInt(uint64_t input, const std::string& expectedEncoded) { - -} - -TEST(BinaryCodingTests, encodeAndDecodeVarInt) { - vector> tests = { - {0, "00"}, - {1, "01"}, - {10, "0a"}, - {100, "64"}, - {0xfb, "fb"}, - {0xfc, "fc"}, - {0xfd, "fdfd00"}, - {0xfe, "fdfe00"}, - {0xff, "fdff00"}, - {0x100, "fd0001"}, - {0x200, "fd0002"}, - {0x1000, "fd0010"}, - {0xffff, "fdffff"}, - {0x10000, "fe00000100"}, - {0x20000, "fe00000200"}, - {0xffffffff, "feffffffff"}, - {0x100000000, "ff0000000001000000"}, - {0x200000000, "ff0000000002000000"}, - {0x1000000000, "ff0000000010000000"}, - {0x10000000000, "ff0000000000010000"}, - {0x100000000000, "ff0000000000100000"}, - {0x1000000000000, "ff0000000000000100"}, - {0x10000000000000, "ff0000000000001000"}, - {0x100000000000000, "ff0000000000000001"}, - {0xffffffffffffffff, "ffffffffffffffffff"}, - }; - for (auto& test : tests) { - const auto input = get<0>(test); - Data encoded; - uint8_t resultEnc = encodeVarInt(input, encoded); - EXPECT_EQ(hex(encoded), get<1>(test)); - EXPECT_EQ(resultEnc, varIntSize(input)); - // decode back - size_t index = 0; - const auto resultDec = decodeVarInt(encoded, index); - EXPECT_EQ(get<0>(resultDec), true); - EXPECT_EQ(get<1>(resultDec), input); - } -} - -TEST(BinaryCodingTests, decodeVarIntTooShort) { - { - Data encoded = parse_hex("fe000000"); // one byte missing - size_t index = 0; - const auto result = decodeVarInt(encoded, index); - EXPECT_EQ(get<0>(result), false); - } - { - Data encoded = parse_hex("fe00000000"); - size_t index = 0; - const auto result = decodeVarInt(encoded, index); - EXPECT_EQ(get<0>(result), true); - } -} - -TEST(BinaryCodingTests, encodeAndDecodeString) { - vector> tests = { - {"", "00"}, - {"A", "0141"}, - {"AB", "024142"}, - {"abcdefghij", "0a6162636465666768696a"}, - {"abcdefghIj", "0a6162636465666768496a"}, - {"12345678901234567890123456789012345678901234567890", "323132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930"}, - { - "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" - "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" - "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" - , "fd2c01" - "3132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930" - "3132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930" - "3132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930" - "3132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930" - "3132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930" - "3132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930" - }, - }; - for (auto& test : tests) { - const auto input = get<0>(test); - Data encoded; - encodeString(input, encoded); - EXPECT_EQ(hex(encoded), get<1>(test)); - // decode back - size_t index = 0; - const auto resultDec = decodeString(encoded, index); - EXPECT_EQ(get<0>(resultDec), true); - EXPECT_EQ(get<1>(resultDec), get<0>(test)); - } -} - -TEST(BinaryCodingTests, decodeStringTooShort) { - { - Data encoded = parse_hex("0a616263646566676849"); // one byte missing - size_t index = 0; - const auto result = decodeString(encoded, index); - EXPECT_EQ(get<0>(result), false); - } - { - Data encoded = parse_hex("0a6162636465666768496a"); - size_t index = 0; - const auto result = decodeString(encoded, index); - EXPECT_EQ(get<0>(result), true); - } -} diff --git a/tests/Bitcoin/FeeCalculatorTests.cpp b/tests/Bitcoin/FeeCalculatorTests.cpp deleted file mode 100644 index fd0585d0482..00000000000 --- a/tests/Bitcoin/FeeCalculatorTests.cpp +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Bitcoin/FeeCalculator.h" - -#include - -using namespace TW; -using namespace TW::Bitcoin; - - -TEST(BitcoinFeeCalculator, ConstantFeeCalculator) { - const auto feeCalculator = ConstantFeeCalculator(33); - EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 33); - EXPECT_EQ(feeCalculator.calculate(0, 2, 1), 33); - EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 33); - EXPECT_EQ(feeCalculator.calculateSingleInput(10), 0); -} - -TEST(BitcoinFeeCalculator, LinearFeeCalculator) { - const auto feeCalculator = LinearFeeCalculator(10, 20, 50); - EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 100); - EXPECT_EQ(feeCalculator.calculate(1, 1, 1), 80); - EXPECT_EQ(feeCalculator.calculate(0, 2, 1), 90); - EXPECT_EQ(feeCalculator.calculate(1, 0, 1), 60); - EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 50); - EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 1000); - EXPECT_EQ(feeCalculator.calculateSingleInput(10), 100); -} - -TEST(BitcoinFeeCalculator, BitcoinCalculate) { - const FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 174); - EXPECT_EQ(feeCalculator.calculate(1, 1, 1), 143); - EXPECT_EQ(feeCalculator.calculate(0, 2, 1), 72); - EXPECT_EQ(feeCalculator.calculate(1, 0, 1), 112); - EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 10); - EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 1740); -} - -TEST(BitcoinFeeCalculator, SegwitCalculate) { - const FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 174); - EXPECT_EQ(feeCalculator.calculate(1, 1, 1), 143); - EXPECT_EQ(feeCalculator.calculate(0, 2, 1), 72); - EXPECT_EQ(feeCalculator.calculate(1, 0, 1), 112); - EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 10); - EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 1740); -} - -TEST(BitcoinFeeCalculator, DefaultCalculate) { - DefaultFeeCalculator defaultFeeCalculator; - EXPECT_EQ(defaultFeeCalculator.calculate(1, 2, 1), 226); - EXPECT_EQ(defaultFeeCalculator.calculate(1, 1, 1), 192); - EXPECT_EQ(defaultFeeCalculator.calculate(0, 2, 1), 78); - EXPECT_EQ(defaultFeeCalculator.calculate(1, 0, 1), 158); - EXPECT_EQ(defaultFeeCalculator.calculate(0, 0, 1), 10); - EXPECT_EQ(defaultFeeCalculator.calculate(1, 2, 10), 2260); -} - -TEST(BitcoinFeeCalculator, DefaultCalculateSingleInput) { - DefaultFeeCalculator defaultFeeCalculator; - EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(1), 148); - EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(2), 296); - EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(10), 1480); -} - -TEST(BitcoinFeeCalculator, DecredCalculate) { - const FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeDecred); - EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 254); - EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 12); - EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 2540); - EXPECT_EQ(feeCalculator.calculateSingleInput(1), 166); -} diff --git a/tests/Bitcoin/TWBitcoinSigningTests.cpp b/tests/Bitcoin/TWBitcoinSigningTests.cpp deleted file mode 100644 index 821272134de..00000000000 --- a/tests/Bitcoin/TWBitcoinSigningTests.cpp +++ /dev/null @@ -1,1760 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Bitcoin/Address.h" -#include "Bitcoin/SegwitAddress.h" -#include "Bitcoin/OutPoint.h" -#include "Bitcoin/Script.h" -#include "Bitcoin/Transaction.h" -#include "Bitcoin/TransactionBuilder.h" -#include "Bitcoin/TransactionSigner.h" -#include "Bitcoin/SigHashType.h" -#include "Bitcoin/SegwitAddress.h" -#include "Base58.h" -#include "Hash.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "proto/Bitcoin.pb.h" -#include "TxComparisonHelper.h" -#include "../interface/TWTestUtilities.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace TW; -using namespace TW::Bitcoin; - -SigningInput buildInputP2PKH(bool omitKey = false) { - auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); - auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); - - // Setup input - SigningInput input; - input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); - input.amount = 335'790'000; - input.byteFee = 1; - input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; - input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; - input.coinType = TWCoinTypeBitcoin; - - auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); - auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); - auto utxoPubkeyHash0 = Hash::ripemd(Hash::sha256(pubKey0.bytes)); - assert(hex(utxoPubkeyHash0) == "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); - if (!omitKey) { - input.privateKeys.push_back(utxoKey0); - } - - auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); - auto pubKey1 = utxoKey1.getPublicKey(TWPublicKeyTypeSECP256k1); - auto utxoPubkeyHash1 = Hash::ripemd(Hash::sha256(pubKey1.bytes)); - assert(hex(utxoPubkeyHash1) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - if (!omitKey) { - input.privateKeys.push_back(utxoKey1); - } - - auto utxo0Script = Script::buildPayToPublicKeyHash(utxoPubkeyHash0); - Data scriptHash; - utxo0Script.matchPayToPublicKeyHash(scriptHash); - assert(hex(scriptHash) == "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); - - UTXO utxo0; - utxo0.script = utxo0Script; - utxo0.amount = 625'000'000; - utxo0.outPoint = OutPoint(hash0, 0, UINT32_MAX); - input.utxos.push_back(utxo0); - - UTXO utxo1; - utxo1.script = Script(parse_hex("0014" "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); - utxo1.amount = 600'000'000; - utxo1.outPoint = OutPoint(hash1, 1, UINT32_MAX); - input.utxos.push_back(utxo1); - - return input; -} - -TEST(BitcoinSigning, SignP2PKH) { - auto input = buildInputP2PKH(); - - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 226)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{228, 225, 226})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); - ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction - "01000000" // version - "01" // inputs - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "6a" "47304402202819d70d4bec472113a1392cadc0860a7a1b34ea0869abb4bdce3290c3aba086022023eff75f410ad19cdbe6c6a017362bd554ce5fb906c13534ddc306be117ad30a012103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ffffffff" - "02" // outputs - "b0bf031400000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "aefd3c1100000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - "00000000" // nLockTime - ); -} - -TEST(BitcoinSigning, SignP2PKH_NegativeMissingKey) { - auto input = buildInputP2PKH(true); - - { - // test plan (but do not reuse plan result). Plan works even with missing keys. - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 226)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_missing_private_key); -} - -TEST(BitcoinSigning, EncodeP2WPKH) { - auto unsignedTx = Transaction(1, 0x11); - - auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); - auto outpoint0 = TW::Bitcoin::OutPoint(hash0, 0); - unsignedTx.inputs.emplace_back(outpoint0, Script(), 0xffffffee); - - auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); - auto outpoint1 = TW::Bitcoin::OutPoint(hash1, 1); - unsignedTx.inputs.emplace_back(outpoint1, Script(), UINT32_MAX); - - auto outScript0 = Script(parse_hex("76a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac")); - unsignedTx.outputs.emplace_back(112340000, outScript0); - - auto outScript1 = Script(parse_hex("76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac")); - unsignedTx.outputs.emplace_back(223450000, outScript1); - - Data unsignedData; - unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); - ASSERT_EQ(unsignedData.size(), 164); - ASSERT_EQ(hex(unsignedData), - "01000000" // version - "0001" // marker & flag - "02" // inputs - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "00" "" "eeffffff" - "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "" "ffffffff" - "02" // outputs - "202cb20600000000" "19" "76a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac" - "9093510d00000000" "19" "76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac" - // witness - "00" - "00" - "11000000" // nLockTime - ); -} - -TEST(BitcoinSigning, SignP2WPKH_Bip143) { - // https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#native-p2wpkh - - SigningInput input; - input.hashType = TWBitcoinSigHashTypeAll; - const auto amount = 112340000; // 0x06B22C20 - input.amount = amount; - input.byteFee = 20; // not relevant - input.toAddress = "1Cu32FVupVCgHkMMRJdYJugxwo2Aprgk7H"; - input.changeAddress = "16TZ8J6Q5iZKBWizWzFAYnrsaox5Z5aBRV"; - - const auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); - const auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); - const auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_EQ(hex(pubKey0.bytes), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); - - const auto utxo0Script = Script::buildPayToPublicKey(pubKey0.bytes); - Data key2; - utxo0Script.matchPayToPublicKey(key2); - EXPECT_EQ(hex(key2), hex(pubKey0.bytes)); - input.privateKeys.push_back(utxoKey0); - - const auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); - const auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); - const auto pubKey1 = utxoKey1.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_EQ(hex(pubKey1.bytes), "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357"); - const auto utxoPubkeyHash1 = Hash::ripemd(Hash::sha256(pubKey1.bytes)); - EXPECT_EQ(hex(utxoPubkeyHash1), "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - input.privateKeys.push_back(utxoKey1); - input.lockTime = 0x11; - - UTXO utxo0; - utxo0.script = utxo0Script; - utxo0.amount = 1000000; // note: this amount is not specified in the test - utxo0.outPoint = OutPoint(hash0, 0, 0xffffffee); - input.utxos.push_back(utxo0); - - UTXO utxo1; - auto utxo1Script = Script::buildPayToV0WitnessProgram(utxoPubkeyHash1); - utxo1.script = utxo1Script; - utxo1.amount = 600000000; // 0x23C34600 0046c323 - utxo1.outPoint = OutPoint(hash1, 1, UINT32_MAX); - input.utxos.push_back(utxo1); - - // Set plan to force both UTXOs and exact output amounts - TransactionPlan plan; - plan.amount = amount; - plan.availableAmount = 600000000 + 1000000; - plan.fee = 265210000; // very large, the amounts specified (in1, out0, out1) are not consistent/realistic - plan.change = 223450000; // 0x0d519390 - plan.branchId = {0}; - plan.utxos.push_back(utxo0); - plan.utxos.push_back(utxo1); - input.plan = plan; - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - const auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{343, 233, 261})); - // expected in one string for easy comparison/copy: - ASSERT_EQ(hex(serialized), "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000494830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac000247304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000"); - // expected in structured format: - ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "02" // inputs - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "4830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01" "eeffffff" - "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "" "ffffffff" - "02" // outputs - "202cb20600000000" "19" "76a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac" - "9093510d00000000" "19" "76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac" - // witness - "00" - "02" - "47" "304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee01" - "21" "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" - "11000000" // nLockTime - ); -} - -SigningInput buildInputP2WPKH(int64_t amount, TWBitcoinSigHashType hashType, int64_t utxo0Amount, int64_t utxo1Amount, bool useMaxAmount = false) { - auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); - auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); - - // Setup input - SigningInput input; - input.hashType = hashType; - input.amount = amount; - input.useMaxAmount = useMaxAmount; - input.byteFee = 1; - input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; - input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; - input.coinType = TWCoinTypeBitcoin; - - auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); - auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); - auto utxoPubkeyHash0 = Hash::ripemd(Hash::sha256(pubKey0.bytes)); - assert(hex(utxoPubkeyHash0) == "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); - input.privateKeys.push_back(utxoKey0); - - auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); - auto pubKey1 = utxoKey1.getPublicKey(TWPublicKeyTypeSECP256k1); - auto utxoPubkeyHash1 = Hash::ripemd(Hash::sha256(pubKey1.bytes)); - assert(hex(utxoPubkeyHash1) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - input.privateKeys.push_back(utxoKey1); - - auto scriptPub1 = Script(parse_hex("0014" "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); - Data scriptHash; - scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); - auto scriptHashHex = hex(scriptHash); - assert(scriptHashHex == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - - auto redeemScript = Script::buildPayToPublicKeyHash(parse_hex("1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); - input.scripts[scriptHashHex] = redeemScript; - - UTXO utxo0; - utxo0.script = Script(parse_hex("2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac")); - utxo0.amount = utxo0Amount; - utxo0.outPoint = OutPoint(hash0, 0, UINT32_MAX); - input.utxos.push_back(utxo0); - - UTXO utxo1; - utxo1.script = Script(parse_hex("0014" "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); - utxo1.amount = utxo1Amount; - utxo1.outPoint = OutPoint(hash1, 1, UINT32_MAX); - input.utxos.push_back(utxo1); - - return input; -} - -TEST(BitcoinSigning, SignP2WPKH) { - auto input = buildInputP2WPKH(335'790'000, TWBitcoinSigHashTypeAll, 625'000'000, 600'000'000); - - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 192)); - } - - // Signs - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{195, 192, 193})); - EXPECT_EQ(serialized.size(), 192); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); - ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction - "01000000" // version - "01" // inputs - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "483045022100c327babdd370f0fc5b24cf920736446bf7d9c5660e4a5f7df432386fd652fe280220269c4fc3690c1c248e50c8bf2435c20b4ef00f308b403575f4437f862a91c53a01" "ffffffff" - "02" // outputs - "b0bf031400000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "d0fd3c1100000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - "00000000" // nLockTime - ); - - { - // Non-segwit encoded, for comparison - Data serialized; - signedTx.encode(serialized, Transaction::SegwitFormatMode::NonSegwit); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{195, 192, 193})); - EXPECT_EQ(serialized.size(), 192); - ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction - "01000000" // version - "01" // inputs - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "483045022100c327babdd370f0fc5b24cf920736446bf7d9c5660e4a5f7df432386fd652fe280220269c4fc3690c1c248e50c8bf2435c20b4ef00f308b403575f4437f862a91c53a01" "ffffffff" - "02" // outputs - "b0bf031400000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "d0fd3c1100000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - "00000000" // nLockTime - ); - } -} - -TEST(BitcoinSigning, SignP2WPKH_HashSingle_TwoInput) { - auto input = buildInputP2WPKH(335'790'000, TWBitcoinSigHashTypeSingle, 210'000'000, 210'000'000); - - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {210'000'000, 210'000'000}, 335'790'000, 261)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{343, 233, 261})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); - ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "02" // inputs - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "483045022100fd8591c3611a07b55f509ec850534c7a9c49713c9b8fa0e844ea06c2e65e19d702205e3806676192e790bc93dd4c28e937c4bf97b15f189158ba1a30d7ecff5ee75503" "ffffffff" - "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "" "ffffffff" - "02" // outputs - "b0bf031400000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "4bf0040500000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - // witness - "00" - "02" "47" "30440220096d20c7e92f991c2bf38dc28118feb34019ae74ec1c17179b28cb041de7517402204594f46a911f24bdc7109ca192e6860ebf2f3a0087579b3c128d5ce0cd5ed46803" "21" "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" - "00000000" // nLockTime - ); -} - -TEST(BitcoinSigning, SignP2WPKH_HashAnyoneCanPay_TwoInput) { - auto input = buildInputP2WPKH(335'790'000, TWBitcoinSigHashTypeAnyoneCanPay, 210'000'000, 210'000'000); - - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {210'000'000, 210'000'000}, 335'790'000, 261)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{344, 233, 261})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); - ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "02" // inputs - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "483045022100e21fb2f1cfd59bdb3703fd45db38fd680d0c06e5d0be86fb7dc233c07ee7ab2f02207367220a73e43df4352a6831f6f31d8dc172c83c9f613a9caf679f0f15621c5e80" "ffffffff" - "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "" "ffffffff" - "02" // outputs - "b0bf031400000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "4bf0040500000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - // witness - "00" - "02" "48" "304502210095f9cc913d2f0892b953f2380112533e8930b67c53e00a7bbd7a01d547156adc022026efe3a684aa7432a00a919dbf81b63e635fb92d3149453e95b4a7ccea59f7c480" "21" "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" - "00000000" // nLockTime - ); -} - -TEST(BitcoinSigning, SignP2WPKH_MaxAmount) { - auto input = buildInputP2WPKH(1'000, TWBitcoinSigHashTypeAll, 625'000'000, 600'000'000, true); - - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {625'000'000, 600'000'000}, 1224999773, 227)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{310, 199, 227})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); - ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "02" // inputs - "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "483045022100a8b3c1619e985923994e80efdc0be0eac12f2419e11ce5e4286a0a5ac27c775d02205d6feee85ffe19ae0835cba1562beb3beb172107cd02ac4caf24a8be3749811f01" "ffffffff" - "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "" "ffffffff" - "01" // outputs - "5d03044900000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - // witness - "00" - "02" "48" "3045022100db1199de92f6fb638a0ba706d13ec686bb01138a254dec2c397616cd74bad30e02200d7286d6d2d4e00d145955bf3d3b848b03c0d1eef8899e4645687a3035d7def401" "21" "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" - "00000000" // nLockTime - ); -} - -TEST(BitcoinSigning, EncodeP2WSH) { - auto unsignedTx = Transaction(1); - - auto outpoint0 = OutPoint(parse_hex("0001000000000000000000000000000000000000000000000000000000000000"), 0); - unsignedTx.inputs.emplace_back(outpoint0, Script(), UINT32_MAX); - - auto outScript0 = Script(parse_hex("76a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac")); - unsignedTx.outputs.emplace_back(1000, outScript0); - - Data unsignedData; - unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::NonSegwit); - ASSERT_EQ(hex(unsignedData), - "01000000" // version - "01" // inputs - "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "" "ffffffff" - "01" // outputs - "e803000000000000" "19" "76a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac" - "00000000" // nLockTime - ); -} - -SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType, bool omitScript = false, bool omitKeys = false) { - SigningInput input; - input.hashType = hashType; - input.amount = 1000; - input.byteFee = 1; - input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; - input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; - - if (!omitKeys) { - auto utxoKey0 = PrivateKey(parse_hex("ed00a0841cd53aedf89b0c616742d1d2a930f8ae2b0fb514765a17bb62c7521a")); - input.privateKeys.push_back(utxoKey0); - - auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); - input.privateKeys.push_back(utxoKey1); - } - - if (!omitScript) { - auto redeemScript = Script(parse_hex("2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac")); - auto scriptHash = "593128f9f90e38b706c18623151e37d2da05c229"; - input.scripts[scriptHash] = redeemScript; - } - - UTXO utxo0; - auto p2wsh = Script::buildPayToWitnessScriptHash(parse_hex("ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db")); - utxo0.script = p2wsh; - utxo0.amount = 1226; - auto hash0 = parse_hex("0001000000000000000000000000000000000000000000000000000000000000"); - utxo0.outPoint = OutPoint(hash0, 0, UINT32_MAX); - input.utxos.push_back(utxo0); - - return input; -} - -TEST(BitcoinSigning, SignP2WSH) { - // Setup input - const auto input = buildInputP2WSH(hashTypeForCoin(TWCoinTypeBitcoin)); - - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 147)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{231, 119, 147})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); - ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "01" // inputs - "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "" "ffffffff" - "02" // outputs - "e803000000000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "4f00000000000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - // witness - "02" "48" "30450221009eefc1befe96158f82b74e6804f1f713768c6172636ca11fcc975c316ea86f75022057914c48bc24f717498b851a47a2926f96242e3943ebdf08d5a97a499efc8b9001" "23" "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" - "00000000" // nLockTime - ); -} - -TEST(BitcoinSigning, SignP2WSH_HashNone) { - // Setup input - const auto input = buildInputP2WSH(TWBitcoinSigHashTypeNone); - - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 147)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{231, 119, 147})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); - ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "01" // inputs - "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "" "ffffffff" - "02" // outputs - "e803000000000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "4f00000000000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - // witness - "02" "48" "3045022100caa585732cfc50226a90834a306d23d5d2ab1e94af2c66136a637e3d9bad3688022069028750908e53a663bb1f434fd655bcc0cf8d394c6fa1fd5a4983790135722e02" "23" "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" - "00000000" // nLockTime - ); -} - -TEST(BitcoinSigning, SignP2WSH_HashSingle) { - // Setup input - const auto input = buildInputP2WSH(TWBitcoinSigHashTypeSingle); - - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 147)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{230, 119, 147})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); - ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "01" // inputs - "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "" "ffffffff" - "02" // outputs - "e803000000000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "4f00000000000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - // witness - "02" "47" "304402201ba80b2c48fe82915297dc9782ae2141e40263001fafd21b02c04a092503f01e0220666d6c63475c6c52abd09371c200ac319bcf4a7c72eb3782e95790f5c847f0b903" "23" "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" - "00000000" // nLockTime - ); -} - -TEST(BitcoinSigning, SignP2WSH_HashAnyoneCanPay) { - // Setup input - const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAnyoneCanPay); - - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 147)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - EXPECT_EQ(serialized.size(), 231); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{231, 119, 147})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); - ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "01" // inputs - "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "" "ffffffff" - "02" // outputs - "e803000000000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "4f00000000000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - // witness - "02" "48" "3045022100d14699fc9b7337768bcd1430098d279cfaf05f6abfa75dd542da2dc038ae1700022063f0751c08796c086ac23b39c25f4320f432092e0c11bec46af0723cc4f55a3980" "23" "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" - "00000000" // nLockTime - ); -} - -TEST(BitcoinSigning, SignP2WSH_NegativeMissingScript) { - const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll, true); - - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 174)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_script_redeem); -} - -TEST(BitcoinSigning, SignP2WSH_NegativeMissingKeys) { - const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll, false, true); - - { - // test plan (but do not reuse plan result). Plan works even with missing keys. - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 147)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_missing_private_key); -} - -TEST(BitcoinSigning, SignP2WSH_NegativePlanWithError) { - // Setup input - auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll); - input.plan = TransactionBuilder::plan(input); - input.plan->error = Common::Proto::Error_missing_input_utxos; - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_missing_input_utxos); -} - -TEST(BitcoinSigning, SignP2WSH_NegativeNoUTXOs) { - // Setup input - auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll); - input.utxos.clear(); - ASSERT_FALSE(input.plan.has_value()); - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_missing_input_utxos); -} - -TEST(BitcoinSigning, SignP2WSH_NegativePlanWithNoUTXOs) { - // Setup input - auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll); - input.plan = TransactionBuilder::plan(input); - input.plan->utxos.clear(); - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_missing_input_utxos); -} - -TEST(BitcoinSigning, EncodeP2SH_P2WPKH) { - auto unsignedTx = Transaction(1, 0x492); - - auto outpoint0 = OutPoint(parse_hex("db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477"), 1); - unsignedTx.inputs.emplace_back(outpoint0, Script(), 0xfffffffe); - - auto outScript0 = Script(parse_hex("76a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac")); - unsignedTx.outputs.emplace_back(199'996'600, outScript0); - - auto outScript1 = Script(parse_hex("76a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac")); - unsignedTx.outputs.emplace_back(800'000'000, outScript1); - - Data unsignedData; - unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::NonSegwit); - ASSERT_EQ(hex(unsignedData), - "01000000" // version - "01" // inputs - "db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477" "01000000" "00" "" "feffffff" - "02" // outputs - "b8b4eb0b00000000" "19" "76a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac" - "0008af2f00000000" "19" "76a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac" - "92040000" // nLockTime - ); -} - -SigningInput buildInputP2SH_P2WPKH(bool omitScript = false, bool omitKeys = false, bool invalidOutputScript = false, bool invalidRedeemScript = false) { - // Setup input - SigningInput input; - input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); - input.amount = 200'000'000; - input.byteFee = 1; - input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; - input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; - input.coinType = TWCoinTypeBitcoin; - - auto utxoKey0 = PrivateKey(parse_hex("eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf")); - auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); - auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey0.bytes)); - assert(hex(utxoPubkeyHash) == "79091972186c449eb1ded22b78e40d009bdf0089"); - if (!omitKeys) { - input.privateKeys.push_back(utxoKey0); - } - - if (!omitScript && !invalidRedeemScript) { - auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); - auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); - assert(hex(scriptHash) == "4733f37cf4db86fbc2efed2500b4f4e49f312023"); - input.scripts[hex(scriptHash)] = redeemScript; - } else if (invalidRedeemScript) { - auto redeemScript = Script(parse_hex("FAFBFCFDFE")); - auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); - input.scripts[hex(scriptHash)] = redeemScript; - } - - UTXO utxo0; - auto utxo0Script = Script(parse_hex("a9144733f37cf4db86fbc2efed2500b4f4e49f31202387")); - if (invalidOutputScript) { - utxo0Script = Script(parse_hex("FFFEFDFCFB")); - } - utxo0.script = utxo0Script; - utxo0.amount = 1'000'000'000; - auto hash0 = parse_hex("db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477"); - utxo0.outPoint = OutPoint(hash0, 1, UINT32_MAX); - input.utxos.push_back(utxo0); - - return input; -} - -TEST(BitcoinSigning, SignP2SH_P2WPKH) { - auto input = buildInputP2SH_P2WPKH(); - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'000'000'000}, 200'000'000, 170)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{251, 142, 170})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); - ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "01" // inputs - "db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477" "01000000" "17" "16001479091972186c449eb1ded22b78e40d009bdf0089" "ffffffff" - "02" // outputs - "00c2eb0b00000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "5607af2f00000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - // witness - "02" "47" "3044022062b408cc7f92c8add622f3297b8992d68403849c6421ef58274ed6fc077102f30220250696eacc0aad022f55882d742dda7178bea780c03705bf9cdbee9f812f785301" "21" "03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873" - "00000000" // nLockTime - ); -} - -TEST(BitcoinSigning, SignP2SH_P2WPKH_NegativeOmitScript) { - auto input = buildInputP2SH_P2WPKH(true, false); - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'000'000'000}, 200'000'000, 174)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_script_redeem); -} - -TEST(BitcoinSigning, SignP2SH_P2WPKH_NegativeInvalidOutputScript) { - auto input = buildInputP2SH_P2WPKH(false, false, true); - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'000'000'000}, 200'000'000, 174)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_script_output); -} - -TEST(BitcoinSigning, SignP2SH_P2WPKH_NegativeInvalidRedeemScript) { - auto input = buildInputP2SH_P2WPKH(false, false, false, true); - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'000'000'000}, 200'000'000, 174)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_script_redeem); -} - -TEST(BitcoinSigning, SignP2SH_P2WPKH_NegativeOmitKeys) { - auto input = buildInputP2SH_P2WPKH(false, true); - { - // test plan (but do not reuse plan result). Plan works even with missing keys. - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'000'000'000}, 200'000'000, 170)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_missing_private_key); -} - -TEST(BitcoinSigning, EncodeP2SH_P2WSH) { - auto unsignedTx = Transaction(1); - - auto hash0 = parse_hex("36641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e"); - auto outpoint0 = OutPoint(hash0, 1); - unsignedTx.inputs.emplace_back(outpoint0, Script(), 0xffffffff); - - auto outScript0 = Script(parse_hex("76a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688ac")); - unsignedTx.outputs.emplace_back(0x0000000035a4e900, outScript0); - - auto outScript1 = Script(parse_hex("76a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac")); - unsignedTx.outputs.emplace_back(0x00000000052f83c0, outScript1); - - Data unsignedData; - unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::NonSegwit); - ASSERT_EQ(hex(unsignedData), - "01000000" // version - "01" // inputs - "36641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e" "01000000" "00" "" "ffffffff" - "02" // outputs - "00e9a43500000000" "19" "76a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688ac" - "c0832f0500000000" "19" "76a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac" - "00000000" // nLockTime - ); -} - -TEST(BitcoinSigning, SignP2SH_P2WSH) { - // Setup signing input - SigningInput input; - input.amount = 900000000; - input.hashType = (TWBitcoinSigHashType)0; - input.toAddress = "16AQVuBMt818u2HBcbxztAZTT2VTDKupPS"; - input.changeAddress = "1Bd1VA2bnLjoBk4ook3H19tZWETk8s6Ym5"; - - auto key0 = parse_hex("730fff80e1413068a05b57d6a58261f07551163369787f349438ea38ca80fac6"); - input.privateKeys.push_back(PrivateKey(key0)); - auto key1 = parse_hex("11fa3d25a17cbc22b29c44a484ba552b5a53149d106d3d853e22fdd05a2d8bb3"); - input.privateKeys.push_back(PrivateKey(key1)); - auto key2 = parse_hex("77bf4141a87d55bdd7f3cd0bdccf6e9e642935fec45f2f30047be7b799120661"); - input.privateKeys.push_back(PrivateKey(key2)); - auto key3 = parse_hex("14af36970f5025ea3e8b5542c0f8ebe7763e674838d08808896b63c3351ffe49"); - input.privateKeys.push_back(PrivateKey(key3)); - auto key4 = parse_hex("fe9a95c19eef81dde2b95c1284ef39be497d128e2aa46916fb02d552485e0323"); - input.privateKeys.push_back(PrivateKey(key4)); - auto key5 = parse_hex("428a7aee9f0c2af0cd19af3cf1c78149951ea528726989b2e83e4778d2c3f890"); - input.privateKeys.push_back(PrivateKey(key5)); - - auto redeemScript = Script::buildPayToWitnessScriptHash(parse_hex("a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54")); - auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); - input.scripts[hex(scriptHash)] = redeemScript; - - auto witnessScript = Script(parse_hex( - "56" - "210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3" - "2103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b" - "21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a" - "21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4" - "2103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16" - "2102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b" - "56ae" - )); - auto witnessScriptHash = Hash::ripemd(Hash::sha256(witnessScript.bytes)); - input.scripts[hex(witnessScriptHash)] = witnessScript; - - auto utxo0Script = Script(parse_hex("a9149993a429037b5d912407a71c252019287b8d27a587")); - UTXO utxo; - utxo.outPoint = OutPoint(parse_hex("36641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e"), 1, UINT32_MAX); - utxo.script = utxo0Script; - utxo.amount = 987654321; - input.utxos.push_back(utxo); - - TransactionPlan plan; - plan.amount = input.amount; - plan.availableAmount = input.utxos[0].amount; - plan.change = 87000000; - plan.fee = plan.availableAmount - plan.amount - plan.change; - plan.utxos = input.utxos; - input.plan = plan; - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - auto expected = - "01000000" // version - "0001" // marker & flag - "01" // inputs - "36641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e" "01000000" "23" "220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54" "ffffffff" - "02" // outputs - "00e9a43500000000" "19" "76a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688ac" - "c0832f0500000000" "19" "76a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac" - // witness - "08" - "00" "" - "47" "304402201992f5426ae0bab04cf206d7640b7e00410297bfe5487637f6c2427ee8496be002204ad4e64dc2d269f593cc4820db1fc1e8dc34774f602945115ce485940e05c64200" - "47" "304402201e412363fa554b994528fd44149f3985b18bb901289ef6b71105b27c7d0e336c0220595e4a1e67154337757562ed5869127533e3e5084c3c2e128518f5f0b85b721800" - "47" "3044022003b0a20ccf545b3f12c5ade10db8717e97b44da2e800387adfd82c95caf529d902206aee3a2395530d52f476d0ddd9d20ba062820ae6f4e1be4921c3630395743ad900" - "48" "3045022100ed7a0eeaf72b84351bceac474b0c0510f67065b1b334f77e6843ed102f968afe022004d97d0cfc4bf5651e46487d6f87bd4af6aef894459f9778f2293b0b2c5b7bc700" - "48" "3045022100934a0c364820588154aed2d519cbcc61969d837b91960f4abbf0e374f03aa39d022036b5c58b754bd44cb5c7d34806c89d9778ea1a1c900618a841e9fbfbe805ff9b00" - "47" "3044022044e3b59b06931d46f857c82fa1d53d89b116a40a581527eac35c5eb5b7f0785302207d0f8b5d063ffc6749fb4e133db7916162b540c70dee40ec0b21e142d8843b3a00" - "cf" "56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae" - "00000000" // nLockTime - ; - - Data serialized; - signedTx.encode(serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{800, 154, 316})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); - ASSERT_EQ(hex(serialized), expected); -} - -TEST(BitcoinSigning, Sign_NegativeNoUtxos) { - auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); - auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); - - // Setup input - SigningInput input; - input.hashType = TWBitcoinSigHashTypeAll; - input.amount = 335'790'000; - input.byteFee = 1; - input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; - input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; - - auto scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); - Data scriptHash; - scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); - auto scriptHashHex = hex(scriptHash); - ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - - auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); - input.scripts[scriptHashHex] = redeemScript; - - { - // plan returns empty, as there are 0 utxos - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {}, 0, 0, Common::Proto::Error_missing_input_utxos)); - } - - // Invoke Sign nonetheless - auto result = TransactionSigner::sign(input); - - // Fails as there are 0 utxos - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_missing_input_utxos); -} - -TEST(BitcoinSigning, Sign_NegativeInvalidAddress) { - auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); - auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); - - // Setup input - SigningInput input; - input.hashType = TWBitcoinSigHashTypeAll; - input.amount = 335'790'000; - input.byteFee = 1; - input.toAddress = "THIS-IS-NOT-A-BITCOIN-ADDRESS"; - input.changeAddress = "THIS-IS-NOT-A-BITCOIN-ADDRESS-EITHER"; - - auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); - input.privateKeys.push_back(utxoKey0); - - auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); - input.privateKeys.push_back(utxoKey1); - - auto scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); - Data scriptHash; - scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); - auto scriptHashHex = hex(scriptHash); - ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - - auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); - input.scripts[scriptHashHex] = redeemScript; - - UTXO utxo0; - auto utxo0Script = Script(parse_hex("2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac")); - utxo0.script = utxo0Script; - utxo0.amount = 625'000'000; - utxo0.outPoint = OutPoint(hash0, 0, UINT32_MAX); - input.utxos.push_back(utxo0); - - UTXO utxo1; - auto utxo1Script = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); - utxo1.script = utxo1Script; - utxo1.amount = 600'000'000; - utxo1.outPoint = OutPoint(hash1, 1, UINT32_MAX); - input.utxos.push_back(utxo1); - - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(std::move(input)); - EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 174)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), Common::Proto::Error_missing_input_utxos); -} - -TEST(BitcoinSigning, Plan_10input_MaxAmount) { - auto ownAddress = "bc1q0yy3juscd3zfavw76g4h3eqdqzda7qyf58rj4m"; - auto ownPrivateKey = "eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf"; - - SigningInput input; - - for (int i = 0; i < 10; ++i) { - auto utxoScript = Script::lockScriptForAddress(ownAddress, TWCoinTypeBitcoin); - Data keyHash; - EXPECT_TRUE(utxoScript.matchPayToWitnessPublicKeyHash(keyHash)); - EXPECT_EQ(hex(keyHash), "79091972186c449eb1ded22b78e40d009bdf0089"); - - auto redeemScript = Script::buildPayToPublicKeyHash(keyHash); - input.scripts[std::string(keyHash.begin(), keyHash.end())] = redeemScript; - - UTXO utxo; - utxo.script = utxoScript; - utxo.amount = 1'000'000 + i * 10'000; - auto hash = parse_hex("a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407"); - std::reverse(hash.begin(), hash.end()); - utxo.outPoint = OutPoint(hash, 0, UINT32_MAX); - input.utxos.push_back(utxo); - } - - input.coinType = TWCoinTypeBitcoin; - input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); - input.useMaxAmount = true; - input.amount = 2'000'000; - input.byteFee = 1; - input.toAddress = "bc1qauwlpmzamwlf9tah6z4w0t8sunh6pnyyjgk0ne"; - input.changeAddress = ownAddress; - - // Plan. - // Estimated size: witness size: 10 * (1 + 1 + 72 + 1 + 33) + 2 = 1082; base 451; raw 451 + 1082 = 1533; vsize 451 + 1082/4 --> 722 - // Actual size: witness size: 1078; base 451; raw 451 + 1078 = 1529; vsize 451 + 1078/4 --> 721 - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {1'000'000, 1'010'000, 1'020'000, 1'030'000, 1'040'000, 1'050'000, 1'060'000, 1'070'000, 1'080'000, 1'090'000}, 10'449'278, 722)); - - // Extend input with keys, reuse plan, Sign - auto privKey = PrivateKey(parse_hex(ownPrivateKey)); - input.privateKeys.push_back(privKey); - input.plan = plan; - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{1529, 451, 721})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); - - ASSERT_EQ(serialized.size(), 1529); -} - -TEST(BitcoinSigning, Sign_LitecoinReal_a85f) { - auto coin = TWCoinTypeLitecoin; - auto ownAddress = "ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00"; - auto ownPrivateKey = "b820f41f96c8b7442f3260acd23b3897e1450b8c7c6580136a3c2d3a14e34674"; - - // Setup input - SigningInput input; - input.coinType = coin; - input.hashType = hashTypeForCoin(coin); - input.amount = 3'899'774; - input.useMaxAmount = true; - input.byteFee = 1; - input.toAddress = "ltc1q0dvup9kzplv6yulzgzzxkge8d35axkq4n45hum"; - input.changeAddress = ownAddress; - - auto privKey = PrivateKey(parse_hex(ownPrivateKey)); - input.privateKeys.push_back(privKey); - - auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); - Data keyHash0; - EXPECT_TRUE(utxo0Script.matchPayToWitnessPublicKeyHash(keyHash0)); - EXPECT_EQ(hex(keyHash0), "5c74be45eb45a3459050667529022d9df8a1ecff"); - - auto redeemScript = Script::buildPayToPublicKeyHash(keyHash0); - input.scripts[std::string(keyHash0.begin(), keyHash0.end())] = redeemScript; - - UTXO utxo0; - utxo0.script = utxo0Script; - utxo0.amount = 3'900'000; - auto hash0 = parse_hex("7051cd18189401a844abf0f9c67e791315c4c154393870453f8ad98a818efdb5"); - std::reverse(hash0.begin(), hash0.end()); - utxo0.outPoint = OutPoint(hash0, 9, UINT32_MAX - 1); - input.utxos.push_back(utxo0); - - // set plan, to match real tx - TransactionPlan plan; - plan.availableAmount = 3'900'000; - plan.amount = 3'899'774; - plan.fee = 226; - plan.change = 0; - plan.utxos.push_back(input.utxos[0]); - input.plan = plan; - EXPECT_TRUE(verifyPlan(input.plan.value(), {3'900'000}, 3'899'774, 226)); - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - - // https://blockchair.com/litecoin/transaction/a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407 - ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "01" // inputs - "b5fd8e818ad98a3f4570383954c1c41513797ec6f9f0ab44a801941818cd5170" "09000000" "00" "" "feffffff" - "01" // outputs - "7e813b0000000000" "16" "00147b59c096c20fd9a273e240846b23276c69d35815" - // witness - "02" - "47" "3044022029153096af176f9cca0ba9b827e947689a8bb8d11dda570c880f9108bc590b3002202410c78b666722ade1ef4547ad85a128ddcbd4695c40f942457bea3d043b9bb301" - "21" "036739829f2cfec79cfe6aaf1c22ecb7d4867dfd8ab4deb7121b36a00ab646caed" - "00000000" // nLockTime - ); -} - -TEST(BitcoinSigning, PlanAndSign_LitecoinReal_8435) { - auto coin = TWCoinTypeLitecoin; - auto ownAddress = "ltc1q0dvup9kzplv6yulzgzzxkge8d35axkq4n45hum"; - auto ownPrivateKey = "690b34763f34e0226ad2a4d47098269322e0402f847c97166e8f39959fcaff5a"; - - // Setup input for Plan - SigningInput input; - input.coinType = coin; - input.hashType = hashTypeForCoin(coin); - input.amount = 1'200'000; - input.useMaxAmount = false; - input.byteFee = 1; - input.toAddress = "ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00"; - input.changeAddress = ownAddress; - - auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); - Data keyHash0; - EXPECT_TRUE(utxo0Script.matchPayToWitnessPublicKeyHash(keyHash0)); - EXPECT_EQ(hex(keyHash0), "7b59c096c20fd9a273e240846b23276c69d35815"); - - auto redeemScript = Script::buildPayToPublicKeyHash(keyHash0); - input.scripts[std::string(keyHash0.begin(), keyHash0.end())] = redeemScript; - - UTXO utxo0; - utxo0.script = utxo0Script; - utxo0.amount = 3'899'774; - auto hash0 = parse_hex("a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407"); - std::reverse(hash0.begin(), hash0.end()); - utxo0.outPoint = OutPoint(hash0, 0, UINT32_MAX); - input.utxos.push_back(utxo0); - - // Plan - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {3'899'774}, 1'200'000, 141)); - - // Extend input with keys and plan, for Sign - auto privKey = PrivateKey(parse_hex(ownPrivateKey)); - input.privateKeys.push_back(privKey); - input.plan = plan; - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{222, 113, 141})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); - - // https://blockchair.com/litecoin/transaction/8435d205614ee70066060734adf03af4194d0c3bc66dd01bb124ab7fd25e2ef8 - ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "01" // inputs - "07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8" "00000000" "00" "" "ffffffff" - "02" // outputs - "804f120000000000" "16" "00145c74be45eb45a3459050667529022d9df8a1ecff" - "7131290000000000" "16" "00147b59c096c20fd9a273e240846b23276c69d35815" - // witness - "02" - "47" "304402204139b82927dd80445f27a5d2c29fa4881dbd2911714452a4a706145bc43cc4bf022016fbdf4b09bc5a9c43e79edb1c1061759779a20c35535082bdc469a61ed0771f01" - "21" "02499e327a05cc8bb4b3c34c8347ecfcb152517c9927c092fa273be5379fde3226" - "00000000" // nLockTime - ); -} - -TEST(BitcoinSigning, Sign_ManyUtxos_400) { - auto ownAddress = "bc1q0yy3juscd3zfavw76g4h3eqdqzda7qyf58rj4m"; - auto ownPrivateKey = "eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf"; - - // Setup input - SigningInput input; - - const auto n = 400; - uint64_t utxoSum = 0; - for (int i = 0; i < n; ++i) { - auto utxoScript = Script::lockScriptForAddress(ownAddress, TWCoinTypeBitcoin); - Data keyHash; - EXPECT_TRUE(utxoScript.matchPayToWitnessPublicKeyHash(keyHash)); - EXPECT_EQ(hex(keyHash), "79091972186c449eb1ded22b78e40d009bdf0089"); - - auto redeemScript = Script::buildPayToPublicKeyHash(keyHash); - input.scripts[std::string(keyHash.begin(), keyHash.end())] = redeemScript; - - UTXO utxo; - utxo.script = utxoScript; - utxo.amount = 1000 + (i + 1) * 10; - auto hash = parse_hex("a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407"); - std::reverse(hash.begin(), hash.end()); - utxo.outPoint = OutPoint(hash, 0, UINT32_MAX); - input.utxos.push_back(utxo); - utxoSum += utxo.amount; - } - EXPECT_EQ(utxoSum, 1'202'000); - - input.coinType = TWCoinTypeBitcoin; - input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); - input.useMaxAmount = false; - input.amount = 300'000; - input.byteFee = 1; - input.toAddress = "bc1qauwlpmzamwlf9tah6z4w0t8sunh6pnyyjgk0ne"; - input.changeAddress = ownAddress; - - // Plan - auto plan = TransactionBuilder::plan(input); - - // expected result: 66 utxos, with the largest amounts - std::vector subset; - uint64_t subsetSum = 0; - for (int i = n - 66; i < n; ++i) { - const uint64_t val = 1000 + (i + 1) * 10; - subset.push_back(val); - subsetSum += val; - } - EXPECT_EQ(subset.size(), 66); - EXPECT_EQ(subsetSum, 308'550); - EXPECT_TRUE(verifyPlan(plan, subset, 300'000, 4'561)); - - // Extend input with keys, reuse plan, Sign - auto privKey = PrivateKey(parse_hex(ownPrivateKey)); - input.privateKeys.push_back(privKey); - input.plan = plan; - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - - EXPECT_EQ(serialized.size(), 9871); -} - -TEST(BitcoinSigning, Sign_ManyUtxos_2000) { - auto ownAddress = "bc1q0yy3juscd3zfavw76g4h3eqdqzda7qyf58rj4m"; - auto ownPrivateKey = "eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf"; - - // Setup input - SigningInput input; - - const auto n = 2000; - uint64_t utxoSum = 0; - for (int i = 0; i < n; ++i) { - auto utxoScript = Script::lockScriptForAddress(ownAddress, TWCoinTypeBitcoin); - Data keyHash; - EXPECT_TRUE(utxoScript.matchPayToWitnessPublicKeyHash(keyHash)); - EXPECT_EQ(hex(keyHash), "79091972186c449eb1ded22b78e40d009bdf0089"); - - auto redeemScript = Script::buildPayToPublicKeyHash(keyHash); - input.scripts[std::string(keyHash.begin(), keyHash.end())] = redeemScript; - - UTXO utxo; - utxo.script = utxoScript; - utxo.amount = 1000 + (i + 1) * 10; - auto hash = parse_hex("a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407"); - std::reverse(hash.begin(), hash.end()); - utxo.outPoint = OutPoint(hash, 0, UINT32_MAX); - input.utxos.push_back(utxo); - utxoSum += utxo.amount; - } - EXPECT_EQ(utxoSum, 22'010'000); - - input.coinType = TWCoinTypeBitcoin; - input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); - input.useMaxAmount = false; - input.amount = 2'000'000; - input.byteFee = 1; - input.toAddress = "bc1qauwlpmzamwlf9tah6z4w0t8sunh6pnyyjgk0ne"; - input.changeAddress = ownAddress; - - // Plan - auto plan = TransactionBuilder::plan(input); - - // expected result: 601 utxos (smaller ones) - std::vector subset; - uint64_t subsetSum = 0; - for (int i = 0; i < 601; ++i) { - const uint64_t val = 1000 + (i + 1) * 10; - subset.push_back(val); - subsetSum += val; - } - EXPECT_EQ(subset.size(), 601); - EXPECT_EQ(subsetSum, 2'410'010); - EXPECT_TRUE(verifyPlan(plan, subset, 2'000'000, 40'943)); - - // Extend input with keys, reuse plan, Sign - auto privKey = PrivateKey(parse_hex(ownPrivateKey)); - input.privateKeys.push_back(privKey); - input.plan = plan; - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - - EXPECT_EQ(serialized.size(), 89'339); -} - -TEST(BitcoinSigning, EncodeThreeOutput) { - auto coin = TWCoinTypeLitecoin; - auto ownAddress = "ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00"; - auto ownPrivateKey = "b820f41f96c8b7442f3260acd23b3897e1450b8c7c6580136a3c2d3a14e34674"; - auto toAddress0 = "ltc1qgknskahmm6svn42e33gum5wc4dz44wt9vc76q4"; - auto toAddress1 = "ltc1qulgtqdgxyd9nxnn5yxft6jykskz0ffl30nu32z"; - auto utxo0Amount = 3'851'829; - auto toAmount0 = 1'000'000; - auto toAmount1 = 2'000'000; - - auto unsignedTx = Transaction(1); - - auto hash0 = parse_hex("bbe736ada63c4678025dff0ff24d5f38970a3e4d7a2f77808689ed68004f55fe"); - std::reverse(hash0.begin(), hash0.end()); - auto outpoint0 = TW::Bitcoin::OutPoint(hash0, 0); - unsignedTx.inputs.emplace_back(outpoint0, Script(), UINT32_MAX); - - auto lockingScript0 = Script::lockScriptForAddress(toAddress0, coin); - unsignedTx.outputs.emplace_back(toAmount0, lockingScript0); - auto lockingScript1 = Script::lockScriptForAddress(toAddress1, coin); - unsignedTx.outputs.emplace_back(toAmount1, lockingScript1); - // change - auto lockingScript2 = Script::lockScriptForAddress(ownAddress, coin); - unsignedTx.outputs.emplace_back(utxo0Amount - toAmount0 - toAmount1 - 172, lockingScript2); - - Data unsignedData; - unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); - EXPECT_EQ(unsignedData.size(), 147); - EXPECT_EQ(hex(unsignedData), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "01" // inputs - "fe554f0068ed898680772f7a4d3e0a97385f4df20fff5d0278463ca6ad36e7bb" "00000000" "00" "" "ffffffff" - "03" // outputs - "40420f0000000000" "16" "001445a70b76fbdea0c9d5598c51cdd1d8ab455ab965" - "80841e0000000000" "16" "0014e7d0b03506234b334e742192bd48968584f4a7f1" - "c9fe0c0000000000" "16" "00145c74be45eb45a3459050667529022d9df8a1ecff" - // witness - "00" - "00000000" // nLockTime - ); - - // add signature - - auto privkey = PrivateKey(parse_hex(ownPrivateKey)); - auto pubkey = PrivateKey(privkey).getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_EQ(hex(pubkey.bytes), "036739829f2cfec79cfe6aaf1c22ecb7d4867dfd8ab4deb7121b36a00ab646caed"); - - auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); // buildPayToV0WitnessProgram() - Data keyHashIn0; - EXPECT_TRUE(utxo0Script.matchPayToWitnessPublicKeyHash(keyHashIn0)); - EXPECT_EQ(hex(keyHashIn0), "5c74be45eb45a3459050667529022d9df8a1ecff"); - - auto redeemScript0 = Script::buildPayToPublicKeyHash(keyHashIn0); - EXPECT_EQ(hex(redeemScript0.bytes), "76a9145c74be45eb45a3459050667529022d9df8a1ecff88ac"); - - auto hashType = TWBitcoinSigHashType::TWBitcoinSigHashTypeAll; - Data sighash = unsignedTx.getSignatureHash(redeemScript0, unsignedTx.inputs[0].previousOutput.index, - hashType, utxo0Amount, static_cast(unsignedTx.version)); - auto sig = privkey.signAsDER(sighash, TWCurveSECP256k1); - ASSERT_FALSE(sig.empty()); - sig.push_back(hashType); - EXPECT_EQ(hex(sig), "30450221008d88197a37ffcb51ecacc7e826aa588cb1068a107a82373c4b54ec42318a395c02204abbf5408504614d8f943d67e7873506c575e85a5e1bd92a02cd345e5192a82701"); - - // add witness stack - unsignedTx.inputs[0].scriptWitness.push_back(sig); - unsignedTx.inputs[0].scriptWitness.push_back(pubkey.bytes); - - unsignedData.clear(); - unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); - EXPECT_EQ(unsignedData.size(), 254); - // https://blockchair.com/litecoin/transaction/9e3fe98565a904d2da5ec1b3ba9d2b3376dfc074f43d113ce1caac01bf51b34c - EXPECT_EQ(hex(unsignedData), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "01" // inputs - "fe554f0068ed898680772f7a4d3e0a97385f4df20fff5d0278463ca6ad36e7bb" "00000000" "00" "" "ffffffff" - "03" // outputs - "40420f0000000000" "16" "001445a70b76fbdea0c9d5598c51cdd1d8ab455ab965" - "80841e0000000000" "16" "0014e7d0b03506234b334e742192bd48968584f4a7f1" - "c9fe0c0000000000" "16" "00145c74be45eb45a3459050667529022d9df8a1ecff" - // witness - "02" - "48" "30450221008d88197a37ffcb51ecacc7e826aa588cb1068a107a82373c4b54ec42318a395c02204abbf5408504614d8f943d67e7873506c575e85a5e1bd92a02cd345e5192a82701" - "21" "036739829f2cfec79cfe6aaf1c22ecb7d4867dfd8ab4deb7121b36a00ab646caed" - "00000000" // nLockTime - ); -} - -TEST(BitcoinSigning, RedeemExtendedPubkeyUTXO) { - auto wif = "L4BeKzm3AHDUMkxLRVKTSVxkp6Hz9FcMQPh18YCKU1uioXfovzwP"; - auto decoded = Base58::bitcoin.decodeCheck(wif); - auto key = PrivateKey(Data(decoded.begin() + 1, decoded.begin() + 33)); - auto pubkey = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended); - auto hash = Hash::sha256ripemd(pubkey.bytes.data(), pubkey.bytes.size()); - - Data data; - append(data, 0x00); - append(data, hash); - auto address = Bitcoin::Address(data); - auto addressString = address.string(); - - EXPECT_EQ(addressString, "1PAmpW5igXUJnuuzRa5yTcsWHwBamZG7Y2"); - - // Setup input for Plan - SigningInput input; - input.coinType = TWCoinTypeBitcoin; - input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); - input.amount = 26972; - input.useMaxAmount = true; - input.byteFee = 1; - input.toAddress = addressString; - - auto utxo0Script = Script::lockScriptForAddress(addressString, TWCoinTypeBitcoin); - - UTXO utxo0; - utxo0.script = utxo0Script; - utxo0.amount = 16874; - auto hash0 = parse_hex("6ae3f1d245521b0ea7627231d27d613d58c237d6bf97a1471341a3532e31906c"); - std::reverse(hash0.begin(), hash0.end()); - utxo0.outPoint = OutPoint(hash0, 0, UINT32_MAX); - input.utxos.push_back(utxo0); - - UTXO utxo1; - utxo1.script = utxo0Script; - utxo1.amount = 10098; - auto hash1 = parse_hex("fd1ea8178228e825d4106df0acb61a4fb14a8f04f30cd7c1f39c665c9427bf13"); - std::reverse(hash1.begin(), hash1.end()); - utxo1.outPoint = OutPoint(hash1, 0, UINT32_MAX); - input.utxos.push_back(utxo1); - - input.privateKeys.push_back(key); - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data encoded; - signedTx.encode(encoded); - EXPECT_EQ(encoded.size(), 402); -} - -TEST(BitcoinSigning, SignP2TR_5df51e) { - const auto privateKey = "13fcaabaf9e71ffaf915e242ec58a743d55f102cf836968e5bd4881135e0c52c"; - const auto ownAddress = "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"; - const auto toAddress = "bc1ptmsk7c2yut2xah4pgflpygh2s7fh0cpfkrza9cjj29awapv53mrslgd5cf"; // Taproot - const auto coin = TWCoinTypeBitcoin; - - // Setup input - SigningInput input; - input.hashType = hashTypeForCoin(coin); - input.amount = 1100; - input.useMaxAmount = false; - input.byteFee = 1; - input.toAddress = toAddress; - input.changeAddress = ownAddress; - input.coinType = coin; - - auto utxoKey0 = PrivateKey(parse_hex(privateKey)); - auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_EQ(hex(pubKey0.bytes), "021e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee"); - EXPECT_EQ(SegwitAddress(pubKey0, "bc").string(), ownAddress); - auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey0.bytes)); - EXPECT_EQ(hex(utxoPubkeyHash), "0cb9f5c6b62c03249367bc20a90dd2425e6926af"); - input.privateKeys.push_back(utxoKey0); - - auto redeemScript = Script::lockScriptForAddress(input.toAddress, coin); - EXPECT_EQ(hex(redeemScript.bytes), "51205ee16f6144e2d46edea1427e1222ea879377e029b0c5d2e252517aee85948ec7"); - auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); - EXPECT_EQ(hex(scriptHash), "e0a5001e7b394a1a6b2978cdcab272241280bf46"); - input.scripts[hex(scriptHash)] = redeemScript; - - UTXO utxo0; - auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); - EXPECT_EQ(hex(utxo0Script.bytes), "00140cb9f5c6b62c03249367bc20a90dd2425e6926af"); - utxo0.script = utxo0Script; - utxo0.amount = 49429; - auto hash0 = parse_hex("c24bd72e3eaea797bd5c879480a0db90980297bc7085efda97df2bf7d31413fb"); - std::reverse(hash0.begin(), hash0.end()); - utxo0.outPoint = OutPoint(hash0, 1, UINT32_MAX); - input.utxos.push_back(utxo0); - - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {49429}, 1100, 153)); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{234, 125, 153})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); - // https://mempool.space/tx/5df51e13bfeb79f386e1e17237f06d1b5c87c5bfcaa907c0c1cfe51cd7ca446d - EXPECT_EQ(hex(serialized), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "01" // inputs - "fb1314d3f72bdf97daef8570bc97029890dba08094875cbd97a7ae3e2ed74bc2" "01000000" "00" "" "ffffffff" - "02" // outputs - "4c04000000000000" "22" "51205ee16f6144e2d46edea1427e1222ea879377e029b0c5d2e252517aee85948ec7" - "30bc000000000000" "16" "00140cb9f5c6b62c03249367bc20a90dd2425e6926af" - // witness - "02" - "47" "3044022021cea91157fdab33226e38ee7c1a686538fc323f5e28feb35775cf82ba8c62210220723743b150cea8ead877d8b8d059499779a5df69f9bdc755c9f968c56cfb528f01" - "21" "021e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee" - "00000000" // nLockTime - ); -} - -TEST(BitcoinSigning, Build_OpReturn_THORChainSwap_eb4c) { - auto coin = TWCoinTypeBitcoin; - auto ownAddress = "bc1q7s0a2l4aguksehx8hf93hs9yggl6njxds6m02g"; - auto toAddress = "bc1qxu5a8gtnjxw3xwdlmr2gl9d76h9fysu3zl656e"; - auto utxoAmount = 342101; - auto toAmount = 300000; - int fee = 36888; - - auto unsignedTx = Transaction(2, 0); - - auto hash0 = parse_hex("30b82960291a39de3664ec4c844a815e3e680e29b4d3a919e450f0c119cf4e35"); - std::reverse(hash0.begin(), hash0.end()); - auto outpoint0 = TW::Bitcoin::OutPoint(hash0, 1); - unsignedTx.inputs.emplace_back(outpoint0, Script(), UINT32_MAX); - - auto lockingScriptTo = Script::lockScriptForAddress(toAddress, coin); - unsignedTx.outputs.push_back(TransactionOutput(toAmount, lockingScriptTo)); - // change - auto lockingScriptChange = Script::lockScriptForAddress(ownAddress, coin); - unsignedTx.outputs.push_back(TransactionOutput(utxoAmount - toAmount - fee, lockingScriptChange)); - // memo OP_RETURN - Data memo = data("SWAP:THOR.RUNE:thor1tpercamkkxec0q0jk6ltdnlqvsw29guap8wmcl:"); - auto lockingScriptOpReturn = Script::buildOpReturnScript(memo); - EXPECT_EQ(hex(lockingScriptOpReturn.bytes), "6a3b535741503a54484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a"); - unsignedTx.outputs.push_back(TransactionOutput(0, lockingScriptOpReturn)); - - Data unsignedData; - unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); - EXPECT_EQ(unsignedData.size(), 186); - EXPECT_EQ(hex(unsignedData), // printed using prettyPrintTransaction - "02000000" // version - "0001" // marker & flag - "01" // inputs - "354ecf19c1f050e419a9d3b4290e683e5e814a844cec6436de391a296029b830" "01000000" "00" "" "ffffffff" - "03" // outputs - "e093040000000000" "16" "00143729d3a173919d1339bfd8d48f95bed5ca924391" - "5d14000000000000" "16" "0014f41fd57ebd472d0cdcc7ba4b1bc0a4423fa9c8cd" - "0000000000000000" "3d" "6a3b535741503a54484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a" - // witness - "00" - "00000000" // nLockTime - ); - - // add signature - auto pubkey = parse_hex("0206121b83ebfddbb1997b50cb87b968190857269333e21e295142c8b88af9312a"); - auto sig = parse_hex("3045022100876eba8f9324d3fbb00b9dad9a34a8166dd75127d4facda63484c19703e9c178022052495a6229cc465d5f0fcf3cde3b22a0f861e762d0bb10acde26a57598bfe7e701"); - - // add witness stack - unsignedTx.inputs[0].scriptWitness.push_back(sig); - unsignedTx.inputs[0].scriptWitness.push_back(pubkey); - - unsignedData.clear(); - unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); - EXPECT_EQ(unsignedData.size(), 293); - // https://blockchair.com/bitcoin/transaction/eb4c1b064bfaf593d7cc6a5c73b75f932ffefe12a0478acf5a7e3145476683fc - EXPECT_EQ(hex(unsignedData), - "02000000000101354ecf19c1f050e419a9d3b4290e683e5e814a844cec6436de391a296029b8300100000000ffffffff03e0930400000000001600143729d3a1" - "73919d1339bfd8d48f95bed5ca9243915d14000000000000160014f41fd57ebd472d0cdcc7ba4b1bc0a4423fa9c8cd00000000000000003d6a3b535741503a54" - "484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a02483045022100876eba8f" - "9324d3fbb00b9dad9a34a8166dd75127d4facda63484c19703e9c178022052495a6229cc465d5f0fcf3cde3b22a0f861e762d0bb10acde26a57598bfe7e70121" - "0206121b83ebfddbb1997b50cb87b968190857269333e21e295142c8b88af9312a00000000" - ); -} - -TEST(BitcoinSigning, Sign_OpReturn_THORChainSwap) { - PrivateKey privateKey = PrivateKey(parse_hex("6bd4096fa6f08bd3af2b437244ba0ca2d35045c5233b8d6796df37e61e974de5")); - PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - auto ownAddress = SegwitAddress(publicKey, "bc"); - auto ownAddressString = ownAddress.string(); - EXPECT_EQ(ownAddressString, "bc1q2gzg42w98ytatvmsgxfc8vrg6l24c25pydup9u"); - auto toAddress = "bc1qxu5a8gtnjxw3xwdlmr2gl9d76h9fysu3zl656e"; - auto utxoAmount = 342101; - auto toAmount = 300000; - int byteFee = 126; - Data memo = data("SWAP:THOR.RUNE:thor1tpercamkkxec0q0jk6ltdnlqvsw29guap8wmcl:"); - - SigningInput input; - input.coinType = TWCoinTypeBitcoin; - input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); - input.amount = toAmount; - input.byteFee = byteFee; - input.toAddress = toAddress; - input.changeAddress = ownAddressString; - - input.privateKeys.push_back(privateKey); - input.outputOpReturn = memo; - - UTXO utxo; - auto utxoHash = parse_hex("30b82960291a39de3664ec4c844a815e3e680e29b4d3a919e450f0c119cf4e35"); - std::reverse(utxoHash.begin(), utxoHash.end()); - utxo.outPoint = OutPoint(utxoHash, 1, UINT32_MAX); - utxo.amount = utxoAmount; - - auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(publicKey.bytes)); - EXPECT_EQ(hex(utxoPubkeyHash), "52048aa9c53917d5b370419383b068d7d55c2a81"); - auto utxoScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); - EXPECT_EQ(hex(utxoScript.bytes), "001452048aa9c53917d5b370419383b068d7d55c2a81"); - utxo.script = utxoScript; - input.utxos.push_back(utxo); - - { - // test plan (but do not reuse plan result) - auto plan = TransactionBuilder::plan(input); - EXPECT_TRUE(verifyPlan(plan, {342101}, 300000, 26586)); - EXPECT_EQ(plan.outputOpReturn.size(), 59); - } - - // Sign - auto result = TransactionSigner::sign(input); - - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{293, 183, 211})); - EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); - ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "01" // inputs - "354ecf19c1f050e419a9d3b4290e683e5e814a844cec6436de391a296029b830" "01000000" "00" "" "ffffffff" - "03" // outputs - "e093040000000000" "16" "00143729d3a173919d1339bfd8d48f95bed5ca924391" - "9b3c000000000000" "16" "001452048aa9c53917d5b370419383b068d7d55c2a81" - "0000000000000000" "3d" "6a3b535741503a54484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a" - // witness - "02" - "48" "3045022100ff6c0aaef512aa52f3036161bfbcef39046ac89eb9617fa461a0c9c43fe45eb3022055d208d3f81736e72e3ad8ef761dc79ac5dd3dc00721174bc69db416a74960e301" - "21" "02c2e5c8b4927812fb37444a7862466ad23978a4ac626f8eaf93e1d1a60d6abb80" - "00000000" // nLockTime - ); -} diff --git a/tests/Bitcoin/TWBitcoinTransactionTests.cpp b/tests/Bitcoin/TWBitcoinTransactionTests.cpp deleted file mode 100644 index 4086289a438..00000000000 --- a/tests/Bitcoin/TWBitcoinTransactionTests.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Bitcoin/Transaction.h" -#include "HexCoding.h" -#include "../interface/TWTestUtilities.h" - -#include - -#include - -using namespace TW; -using namespace TW::Bitcoin; - -TEST(BitcoinTransaction, Encode) { - auto transaction = Transaction(2, 0); - - auto po0 = OutPoint(parse_hex("5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f"), 0); - transaction.inputs.emplace_back(po0, Script(), 4294967295); - - auto po1 = OutPoint(parse_hex("bf829c6bcf84579331337659d31f89dfd138f7f7785802d5501c92333145ca7c"), 18); - transaction.inputs.emplace_back(po1, Script(), 4294967295); - - auto po2 = OutPoint(parse_hex("22a6f904655d53ae2ff70e701a0bbd90aa3975c0f40bfc6cc996a9049e31cdfc"), 1); - transaction.inputs.emplace_back(po2, Script(), 4294967295); - - auto oscript0 = Script(parse_hex("76a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac")); - transaction.outputs.emplace_back(18000000, oscript0); - - auto oscript1 = Script(parse_hex("0x76a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac")); - transaction.outputs.emplace_back(400000000, oscript1); - - Data unsignedData; - transaction.encode(unsignedData, Transaction::SegwitFormatMode::NonSegwit); - ASSERT_EQ(unsignedData.size(), 201); - ASSERT_EQ(hex(unsignedData), - "02000000035897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f0000000000ffffffffbf829c6bcf84579331337659d31f89dfd138f7f7785802d5501c92333145ca7c1200000000ffffffff22a6f904655d53ae2ff70e701a0bbd90aa3975c0f40bfc6cc996a9049e31cdfc0100000000ffffffff0280a81201000000001976a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac0084d717000000001976a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac00000000"); -} diff --git a/tests/Bitcoin/TWCoinTypeTests.cpp b/tests/Bitcoin/TWCoinTypeTests.cpp deleted file mode 100644 index 0cba7d8cb7e..00000000000 --- a/tests/Bitcoin/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWBitcoinCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBitcoin)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0607f62530b68cfcc91c57a1702841dd399a899d0eecda8e31ecca3f52f01df2")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBitcoin, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("17A16QmavnUfCW11DAApiJxp7ARnxN5pGX")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBitcoin, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBitcoin)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBitcoin)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBitcoin), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeBitcoin)); - ASSERT_EQ(0x5, TWCoinTypeP2shPrefix(TWCoinTypeBitcoin)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBitcoin)); - assertStringsEqual(symbol, "BTC"); - assertStringsEqual(txUrl, "https://blockchair.com/bitcoin/transaction/0607f62530b68cfcc91c57a1702841dd399a899d0eecda8e31ecca3f52f01df2"); - assertStringsEqual(accUrl, "https://blockchair.com/bitcoin/address/17A16QmavnUfCW11DAApiJxp7ARnxN5pGX"); - assertStringsEqual(id, "bitcoin"); - assertStringsEqual(name, "Bitcoin"); -} diff --git a/tests/Bitcoin/TWSegwitAddressTests.cpp b/tests/Bitcoin/TWSegwitAddressTests.cpp deleted file mode 100644 index ccc41fc5737..00000000000 --- a/tests/Bitcoin/TWSegwitAddressTests.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" - -#include -#include - -#include - -const char* address1 = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"; -const char* address2 = "bc1qr583w2swedy2acd7rung055k8t3n7udp7vyzyg"; -const char* address3Taproot = "bc1ptmsk7c2yut2xah4pgflpygh2s7fh0cpfkrza9cjj29awapv53mrslgd5cf"; - -TEST(TWSegwitAddress, PublicKeyToAddress) { - auto pkData = DATA("0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"); - auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(pkData.get(), TWPublicKeyTypeSECP256k1)); - - auto address = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPBitcoin, publicKey.get())); - auto string = WRAPS(TWSegwitAddressDescription(address.get())); - - ASSERT_STREQ(address1, TWStringUTF8Bytes(string.get())); -} - -TEST(TWSegwitAddress, InitWithAddress) { - auto string = STRING(address1); - auto address = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithString(string.get())); - auto description = WRAPS(TWSegwitAddressDescription(address.get())); - - ASSERT_TRUE(address.get() != nullptr); - ASSERT_STREQ(address1, TWStringUTF8Bytes(description.get())); - - ASSERT_EQ(0, TWSegwitAddressWitnessVersion(address.get())); - - ASSERT_EQ(TWHRPBitcoin, TWSegwitAddressHRP(address.get())); -} - -TEST(TWSegwitAddress, TaprootString) { - const auto string = STRING(address3Taproot); - const auto address = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithString(string.get())); - ASSERT_TRUE(address.get() != nullptr); - - const auto description = WRAPS(TWSegwitAddressDescription(address.get())); - ASSERT_STREQ(address3Taproot, TWStringUTF8Bytes(description.get())); - - ASSERT_EQ(1, TWSegwitAddressWitnessVersion(address.get())); // taproot has segwit version 1 - - ASSERT_EQ(TWHRPBitcoin, TWSegwitAddressHRP(address.get())); -} - -TEST(TWSegwitAddress, InvalidAddress) { - std::vector> strings = { - STRING("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5"), - STRING("bc1rw5uspcuh"), - STRING("bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90"), - STRING("BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P"), - STRING("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7"), - STRING("bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du"), - STRING("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv"), - STRING("bc1gmk9yu"), - }; - for (auto& string : strings) { - ASSERT_TRUE(TWSegwitAddressCreateWithString(string.get()) == nullptr) << "Invalid address '" << TWStringUTF8Bytes(string.get()) << "' reported as valid."; - } -} diff --git a/tests/Bitcoin/TxComparisonHelper.h b/tests/Bitcoin/TxComparisonHelper.h deleted file mode 100644 index 05fea3251f1..00000000000 --- a/tests/Bitcoin/TxComparisonHelper.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Bitcoin/Amount.h" -#include "Bitcoin/SigningInput.h" -#include "Bitcoin/Transaction.h" -#include "Bitcoin/TransactionPlan.h" -#include - -#include -#include -#include - -using namespace TW; -using namespace TW::Bitcoin; - - -/// Build a dummy UTXO with the given amount -UTXO buildTestUTXO(int64_t amount); - -/// Build a set of dummy UTXO with the given amounts -UTXOs buildTestUTXOs(const std::vector& amounts); - -SigningInput buildSigningInput(Amount amount, int byteFee, const UTXOs& utxos, - bool useMaxAmount = false, enum TWCoinType coin = TWCoinTypeBitcoin, bool omitPrivateKey = false); - -/// Compare a set of selected UTXOs to the expected set of amounts. -/// Returns false on mismatch, and error is printed (stderr). -bool verifySelectedUTXOs(const UTXOs& selected, const std::vector& expectedAmounts); - -/// Compare a transaction plan against expected values (UTXO amounts, amount, fee, change is implicit). -/// Returns false on mismatch, and error is printed (stderr). -bool verifyPlan(const TransactionPlan& plan, const std::vector& utxoAmounts, int64_t outputAmount, int64_t fee, Common::Proto::SigningError error = Common::Proto::OK); - -int64_t sumUTXOs(const UTXOs& utxos); - -struct EncodedTxSize { - uint64_t segwit; - uint64_t nonSegwit; - uint64_t virtualBytes; -}; -bool operator==(const EncodedTxSize& s1, const EncodedTxSize& s2); - -/// Return the encoded size of the transaction, virtual and non-segwit, etc. -EncodedTxSize getEncodedTxSize(const Transaction& tx); - -/// Validate the previously estimated transaction size (if available) with the actual transaction size. -/// Uses segwit byte size (virtual size). Tolerance is estiamte-smaller and estimate-larger, like -1 and 20. -/// Returns false on mismatch, and error is printed (stderr). -bool validateEstimatedSize(const Transaction& tx, int smallerTolerance = -1, int biggerTolerance = 20); - -/// Print out a transaction in a nice format, as structured hex strings. -void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat = true); diff --git a/tests/BitcoinCash/TWCoinTypeTests.cpp b/tests/BitcoinCash/TWCoinTypeTests.cpp deleted file mode 100644 index 9b56e10a7af..00000000000 --- a/tests/BitcoinCash/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWBitcoinCashCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBitcoinCash)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBitcoinCash, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBitcoinCash, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBitcoinCash)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBitcoinCash)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBitcoinCash), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeBitcoinCash)); - ASSERT_EQ(0x5, TWCoinTypeP2shPrefix(TWCoinTypeBitcoinCash)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBitcoinCash)); - assertStringsEqual(symbol, "BCH"); - assertStringsEqual(txUrl, "https://blockchair.com/bitcoin-cash/transaction/t123"); - assertStringsEqual(accUrl, "https://blockchair.com/bitcoin-cash/address/a12"); - assertStringsEqual(id, "bitcoincash"); - assertStringsEqual(name, "Bitcoin Cash"); -} diff --git a/tests/BitcoinGold/TWCoinTypeTests.cpp b/tests/BitcoinGold/TWCoinTypeTests.cpp deleted file mode 100644 index 55b11cdfa52..00000000000 --- a/tests/BitcoinGold/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWBitcoinGoldCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBitcoinGold)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("2f807d7734de35d2236a1b3d8704eb12954f5f82ea66987949b10e94d9999b23")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBitcoinGold, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("GJjz2Du9BoJQ3CPcoyVTHUJZSj62i1693U")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBitcoinGold, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBitcoinGold)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBitcoinGold)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBitcoinGold), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeBitcoinGold)); - ASSERT_EQ(23, TWCoinTypeP2shPrefix(TWCoinTypeBitcoinGold)); - ASSERT_EQ(0, TWCoinTypeStaticPrefix(TWCoinTypeBitcoinGold)); - assertStringsEqual(symbol, "BTG"); - assertStringsEqual(txUrl, "https://explorer.bitcoingold.org/insight/tx/2f807d7734de35d2236a1b3d8704eb12954f5f82ea66987949b10e94d9999b23"); - assertStringsEqual(accUrl, "https://explorer.bitcoingold.org/insight/address/GJjz2Du9BoJQ3CPcoyVTHUJZSj62i1693U"); - assertStringsEqual(id, "bitcoingold"); - assertStringsEqual(name, "Bitcoin Gold"); -} diff --git a/tests/BitcoinGold/TWSegwitAddressTests.cpp b/tests/BitcoinGold/TWSegwitAddressTests.cpp deleted file mode 100644 index 43d8826984c..00000000000 --- a/tests/BitcoinGold/TWSegwitAddressTests.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// - -#include "../interface/TWTestUtilities.h" -#include "Bitcoin/SegwitAddress.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include "HexCoding.h" -#include -#include - -using namespace TW; - -TEST(TWBitcoinGoldSegwitAddress, Valid) { - ASSERT_TRUE(Bitcoin::SegwitAddress::isValid("btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk")); - ASSERT_FALSE(Bitcoin::SegwitAddress::isValid("btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sl")); -} - -/// Initializes a Bech32 address with a human-readable part, a witness -/// version, and a witness program. -TEST(TWBitcoinGoldSegwitAddress, WitnessProgramToAddress) { - auto address = Bitcoin::SegwitAddress("btg", 0, parse_hex("5e6132a9ad21f7423081441ab4ae229501f6c8a8")); - - ASSERT_TRUE(Bitcoin::SegwitAddress::isValid(address.string())); - ASSERT_EQ(address.string(), "btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); -} - -/// Initializes a Bech32 address with a public key and a HRP prefix. -TEST(TWBitcoinGoldSegwitAddress, PubkeyToAddress) { - const auto publicKey = PublicKey(parse_hex("02f74712b5d765a73b52a14c1e113f2ef3f9502d09d5987ee40f53828cfe68b9a6"), TWPublicKeyTypeSECP256k1); - - /// construct with public key - auto address = Bitcoin::SegwitAddress(publicKey, "btg"); - - ASSERT_TRUE(Bitcoin::SegwitAddress::isValid(address.string())); - ASSERT_EQ(address.string(), "btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); -} - -/// Decodes a SegWit address. -TEST(TWBitcoinGoldSegwitAddress, Decode) { - auto result = Bitcoin::SegwitAddress::decode("btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); - - ASSERT_TRUE(std::get<2>(result)); - ASSERT_EQ(std::get<0>(result).string(), "btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); - ASSERT_EQ(std::get<1>(result), "btg"); -} diff --git a/tests/Bluzelle/TWCoinTypeTests.cpp b/tests/Bluzelle/TWCoinTypeTests.cpp deleted file mode 100644 index 33acfa33f7b..00000000000 --- a/tests/Bluzelle/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWCoinTypeBluzelle, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBluzelle)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("AC026E0EC6E33A77D5EA6B9CEF9810699BC2AD8C5582E007E7857457C6D3B819")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBluzelle, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("bluzelle1q9cryfal7u3jvnq6er5ufety20xtzw6ycx2te9")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBluzelle, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBluzelle)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBluzelle)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBluzelle), 6); - ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeBluzelle)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeBluzelle)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBluzelle)); - assertStringsEqual(symbol, "BLZ"); - assertStringsEqual(txUrl, "https://bigdipper.net.bluzelle.com/transactions/AC026E0EC6E33A77D5EA6B9CEF9810699BC2AD8C5582E007E7857457C6D3B819"); - assertStringsEqual(accUrl, "https://bigdipper.net.bluzelle.com/account/bluzelle1q9cryfal7u3jvnq6er5ufety20xtzw6ycx2te9"); - assertStringsEqual(id, "bluzelle"); - assertStringsEqual(name, "Bluzelle"); -} diff --git a/tests/Boba/TWCoinTypeTests.cpp b/tests/Boba/TWCoinTypeTests.cpp deleted file mode 100644 index d538cea0cf3..00000000000 --- a/tests/Boba/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWBobaCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBoba)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x31533707c3feb3b10f7deeea387ff8893f229253e65ca6b14d2400bf95b5d103")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBoba, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x4F96F50eDB37a19216d87693E5dB241e31bD3735")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBoba, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBoba)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBoba)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBoba), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeBoba)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeBoba)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBoba)); - assertStringsEqual(symbol, "BOBAETH"); - assertStringsEqual(txUrl, "https://blockexplorer.boba.network/tx/0x31533707c3feb3b10f7deeea387ff8893f229253e65ca6b14d2400bf95b5d103"); - assertStringsEqual(accUrl, "https://blockexplorer.boba.network/address/0x4F96F50eDB37a19216d87693E5dB241e31bD3735"); - assertStringsEqual(id, "boba"); - assertStringsEqual(name, "Boba"); -} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ae58472a213..288f2842ba8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,8 +1,6 @@ -# Copyright © 2017-2022 Trust Wallet. +# SPDX-License-Identifier: Apache-2.0 # -# This file is part of Trust. The full Trust copyright notice, including -# terms governing use, modification, and redistribution, is contained in the -# file LICENSE at the root of the source code distribution tree. +# Copyright © 2017 Trust Wallet. enable_testing() @@ -25,11 +23,15 @@ file(GLOB_RECURSE test_sources *.cpp **/*.cpp **/*.cc) add_executable(tests ${test_sources}) target_link_libraries(tests gtest_main TrezorCrypto TrustWalletCore walletconsolelib protobuf Boost::boost) target_include_directories(tests PRIVATE ${CMAKE_SOURCE_DIR}/src) +target_include_directories(tests PRIVATE ${CMAKE_SOURCE_DIR}/tests/common) target_compile_options(tests PRIVATE "-Wall") +if (NOT ANDROID AND TW_UNITY_BUILD) + set_target_properties(tests PROPERTIES UNITY_BUILD ON) +endif() set_target_properties(tests PROPERTIES - CXX_STANDARD 17 + CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON ) diff --git a/tests/Callisto/TWCoinTypeTests.cpp b/tests/Callisto/TWCoinTypeTests.cpp deleted file mode 100644 index 2c83326e867..00000000000 --- a/tests/Callisto/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWCallistoCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeCallisto)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeCallisto, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeCallisto, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeCallisto)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeCallisto)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeCallisto), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeCallisto)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeCallisto)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeCallisto)); - assertStringsEqual(symbol, "CLO"); - assertStringsEqual(txUrl, "https://explorer.callisto.network/tx/t123"); - assertStringsEqual(accUrl, "https://explorer.callisto.network/addr/a12"); - assertStringsEqual(id, "callisto"); - assertStringsEqual(name, "Callisto"); -} diff --git a/tests/Cardano/AddressTests.cpp b/tests/Cardano/AddressTests.cpp deleted file mode 100644 index 7e55bb15f28..00000000000 --- a/tests/Cardano/AddressTests.cpp +++ /dev/null @@ -1,402 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Cardano/AddressV3.h" - -#include "HDWallet.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "Coin.h" - -#include - - -using namespace TW::Cardano; -using namespace TW; -using namespace std; - - -const auto dummyKey = parse_hex("1111111111111111111111111111111111111111111111111111111111111111"); - -TEST(CardanoAddress, V3NetworkIdKind) { - EXPECT_EQ(AddressV3::firstByte(AddressV3::Network_Test, AddressV3::Kind_Base), 0); - EXPECT_EQ(AddressV3::firstByte(AddressV3::Network_Production, AddressV3::Kind_Base), 1); - EXPECT_EQ(AddressV3::firstByte(AddressV3::NetworkId(2), AddressV3::Kind(3)), 50); - - EXPECT_EQ(AddressV3::networkIdFromFirstByte(0), AddressV3::Network_Test); - EXPECT_EQ(AddressV3::networkIdFromFirstByte(1), AddressV3::Network_Production); - EXPECT_EQ(AddressV3::networkIdFromFirstByte(50), AddressV3::NetworkId(2)); - - EXPECT_EQ(AddressV3::kindFromFirstByte(0), AddressV3::Kind_Base); - EXPECT_EQ(AddressV3::kindFromFirstByte(1), AddressV3::Kind_Base); - EXPECT_EQ(AddressV3::kindFromFirstByte(50), AddressV3::Kind(3)); -} - -TEST(CardanoAddress, Validation) { - // valid V3 address - ASSERT_TRUE(AddressV3::isValidLegacy("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23")); - ASSERT_TRUE(AddressV3::isValidLegacy("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5")); - - // valid V2 address - ASSERT_TRUE(AddressV3::isValidLegacy("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx")); - ASSERT_TRUE(AddressV3::isValidLegacy("Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W")); - - // valid V1 address - ASSERT_TRUE(AddressV3::isValidLegacy("DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ")); - ASSERT_TRUE(AddressV3::isValidLegacy("DdzFFzCqrht7HGoJ87gznLktJGywK1LbAJT2sbd4txmgS7FcYLMQFhawb18ojS9Hx55mrbsHPr7PTraKh14TSQbGBPJHbDZ9QVh6Z6Di")); - - // invalid V3, length (cardano-crypto.js) - ASSERT_FALSE(AddressV3::isValidLegacy("addr1sna05l45z33zpkm8z44q8f0h57wxvm0c86e34wlmua7gtcrdgrdrzy8ny3walyfjanhe33nsyuh088qr5gepqaen6jsa9r94xvvd7fh6jc3e6x")); - // invalid checksum V3 - ASSERT_FALSE(AddressV3::isValidLegacy("PREFIX1qvqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqxuzx4s")); - // invalid checksum V2 - ASSERT_FALSE(AddressV3::isValidLegacy("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvm")); - // random - ASSERT_FALSE(AddressV3::isValidLegacy("hasoiusaodiuhsaijnnsajnsaiussai")); - // empty - ASSERT_FALSE(AddressV3::isValidLegacy("")); - ASSERT_FALSE(AddressV3::isValidLegacy("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk2")); -} - -TEST(CardanoAddress, FromStringV2) { - { - auto address = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); - } - { - auto address = AddressV3("DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ"); - ASSERT_EQ(address.string(), "DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ"); - } -} - -TEST(CardanoAddress, FromStringV3) { - { - // single addr - auto address = AddressV3("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); - EXPECT_EQ(address.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); - EXPECT_EQ(address.string("addr"), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); - EXPECT_EQ(AddressV3::Network_Production, address.networkId); - EXPECT_EQ(AddressV3::Kind_Base, address.kind); - EXPECT_EQ("8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468", hex(address.bytes)); - } -} - -TEST(CardanoAddress, MnemonicToAddressV3) { - { - // Test from cardano-crypto.js; Test wallet - const auto mnemonic = "cost dash dress stove morning robust group affair stomach vacant route volume yellow salute laugh"; - const auto coin = TWCoinTypeCardano; - const auto derivPath = derivationPath(coin); - - const auto wallet = HDWallet(mnemonic, ""); - - // check entropy - EXPECT_EQ("30a6f50aeb58ff7699b822d63e0ef27aeff17d9f", hex(wallet.getEntropy())); - - { - PrivateKey masterPrivKey = wallet.getMasterKey(TWCurve::TWCurveED25519Extended); - PrivateKey masterPrivKeyExt = wallet.getMasterKeyExtension(TWCurve::TWCurveED25519Extended); - // the two together matches first half of keypair - ASSERT_EQ("a018cd746e128a0be0782b228c275473205445c33b9000a33dd5668b430b5744", hex(masterPrivKey.bytes)); - ASSERT_EQ("26877cfe435fddda02409b839b7386f3738f10a30b95a225f4b720ee71d2505b", hex(masterPrivKeyExt.bytes)); - - PublicKey masterPublicKey = masterPrivKey.getPublicKey(TWPublicKeyTypeED25519); - ASSERT_EQ("3aecb95953edd0b16db20366097ddedcb3512fe36193473c5fca2af774d44739", hex(masterPublicKey.bytes)); - } - { - string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); - EXPECT_EQ("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq", addr); - } - { - const auto privateKey = wallet.getKey(coin, derivPath); - EXPECT_EQ(hex(privateKey.bytes), "e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276"); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - - const auto address = AddressV3(publicKey); - EXPECT_EQ(address.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); - EXPECT_EQ(address.networkId, AddressV3::Network_Production); - EXPECT_EQ(address.kind, AddressV3::Kind_Base); - EXPECT_EQ(hex(address.bytes), "8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034e" "cc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); - } - { - PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/0")); - EXPECT_EQ("e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b5744", hex(privateKey.key())); - EXPECT_EQ("37aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f31", hex(privateKey.extension())); - EXPECT_EQ("10f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fa", hex(privateKey.chainCode())); - EXPECT_EQ("e0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744", hex(privateKey.secondKey())); - EXPECT_EQ("424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5", hex(privateKey.secondExtension())); - EXPECT_EQ("bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276", hex(privateKey.secondChainCode())); - PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - EXPECT_EQ("fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26faf4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276", hex(publicKey.bytes)); - string addr = AddressV3(publicKey).string(); - EXPECT_EQ("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq", addr); - } - { - PrivateKey privKey0 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/44'/1815'/0'/0/0")); - PublicKey pubKey0 = privKey0.getPublicKey(TWPublicKeyTypeED25519Extended); - auto addr0 = AddressV2(pubKey0); - EXPECT_EQ("Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W", addr0.string()); - } - { - PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/44'/1815'/0'/0/1")); - PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Extended); - auto addr1 = AddressV2(pubKey1); - EXPECT_EQ("Ae2tdPwUPEZ7dnds6ZyhQdmgkrDFFPSDh8jG9RAhswcXt1bRauNw5jczjpV", addr1.string()); - } - { - PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/44'/1815'/0'/0/2")); - PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Extended); - auto addr1 = AddressV2(pubKey1); - EXPECT_EQ("Ae2tdPwUPEZ8LAVy21zj4BF97iWxKCmPv12W6a18zLX3V7rZDFFVgqUBkKw", addr1.string()); - } - { - PrivateKey privKey0 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/0")); - PublicKey pubKey0 = privKey0.getPublicKey(TWPublicKeyTypeED25519Extended); - auto addr0 = AddressV3(pubKey0); - EXPECT_EQ("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq", addr0.string()); - } - { - PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/1")); - PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Extended); - auto addr1 = AddressV3(pubKey1); - EXPECT_EQ("addr1q9068st87h22h3l6w6t5evnlm067rag94llqya2hkjrsd3wvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qpmxzjt", addr1.string()); - } - { - PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/2")); - PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Extended); - auto addr1 = AddressV3(pubKey1); - EXPECT_EQ("addr1qxteqxsgxrs4he9d28lh70qu7qfz7saj6dmxwsqyle2yp3xvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35quehtx3", addr1.string()); - } - } - { - auto mnemonicPlay1 = "youth away raise north opinion slice dash bus soldier dizzy bitter increase saddle live champion"; - auto wallet = HDWallet(mnemonicPlay1, ""); - string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); - EXPECT_EQ("addr1q83nm9ntq3eaz8dya49txxtle6nn8geq4gmyylrzhzs7v0qjdwm6zuahwwds6c7mj8t6a09rup6m2cnh6zvzddnafp2slmcu95", addr); - } - { - auto mnemonicPlay2 = "return custom two home gain guilt kangaroo supply market current curtain tomorrow heavy blue robot"; - auto wallet = HDWallet(mnemonicPlay2, ""); - string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); - EXPECT_EQ("addr1qywxuqm7dx0yvqnn2yllye9urz5f2e4fgwanluzh008r22e53hart525dxgjcl0xzm0kes4n5tan8f5pz7ej0tkzgyrqtfmlal", addr); - } - { - auto mnemonicALDemo = "civil void tool perfect avocado sweet immense fluid arrow aerobic boil flash"; - auto wallet = HDWallet(mnemonicALDemo, ""); - string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); - EXPECT_EQ("addr1q94zzrtl32tjp8j96auatnhxd2y35fnk6wuxqvqm9364vp9spdkjdsmyfhvfagjzh4uzp9zs6p5djw89jac2g0ujs2eqsuy7pu", addr); - } - { - // V2 Tested against AdaLite - auto mnemonicPlay1 = "youth away raise north opinion slice dash bus soldier dizzy bitter increase saddle live champion"; - auto wallet = HDWallet(mnemonicPlay1, ""); - PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); - PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - string addr = AddressV2(publicKey).string(); - EXPECT_EQ("Ae2tdPwUPEZJYT9g1JgQWtLveUHavyRxQGi6hVzoQjct7yyCLGgk3pCyx7h", addr); - } - { - // V2 Tested against AdaLite - auto mnemonicPlay2 = "return custom two home gain guilt kangaroo supply market current curtain tomorrow heavy blue robot"; - auto wallet = HDWallet(mnemonicPlay2, ""); - PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); - PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - string addr = AddressV2(publicKey).string(); - EXPECT_EQ("Ae2tdPwUPEZLtJx7LA2XZ3zzwonH9x9ieX3dMzaTBD3TfXuKczjMSjTecr1", addr); - } - { - // V2 AdaLite Demo phrase, 12-word. AdaLite uses V1 for it, in V2 it produces different addresses. - // In AdaLite V1 addr0 is DdzFFzCqrht7HGoJ87gznLktJGywK1LbAJT2sbd4txmgS7FcYLMQFhawb18ojS9Hx55mrbsHPr7PTraKh14TSQbGBPJHbDZ9QVh6Z6Di - auto mnemonicALDemo = "civil void tool perfect avocado sweet immense fluid arrow aerobic boil flash"; - auto wallet = HDWallet(mnemonicALDemo, ""); - PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); - PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - string addr = AddressV2(publicKey).string(); - EXPECT_EQ("Ae2tdPwUPEZJbLcD8iLgN7hVGvq66WdR4zocntRekSP97Ds3MvCfmEDjJYu", addr); - } -} - -TEST(CardanoAddress, KeyHashV2) { - auto xpub = parse_hex("e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf69869272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"); - auto hash = AddressV2::keyHash(xpub); - ASSERT_EQ("a1eda96a9952a56c983d9f49117f935af325e8a6c9d38496e945faa8", hex(hash)); -} - -TEST(CardanoAddress, FromDataV3) { - auto address0 = AddressV3("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); - EXPECT_EQ(address0.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); - EXPECT_EQ(hex(address0.data()), "018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); - { - auto address = AddressV3(parse_hex("018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468")); - EXPECT_EQ(address.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); - EXPECT_EQ(address.kind, AddressV3::Kind_Base); - EXPECT_EQ(address.networkId, AddressV3::Network_Production); - EXPECT_EQ(hex(address.bytes), "8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); - } - { - auto address = AddressV3::createBase(AddressV3::Network_Production, - PublicKey(parse_hex("fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da31"), TWPublicKeyTypeED25519), - PublicKey(parse_hex("f4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4"), TWPublicKeyTypeED25519)); - EXPECT_EQ(address.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); - } -} - -TEST(CardanoAddress, FromPublicKeyV2) { - { - // caradano-crypto.js test - auto publicKey = PublicKey(parse_hex( - "e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf69869272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" - "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" // dummy second - ), TWPublicKeyTypeED25519Extended); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZCxt4UV1Uj2AMMRvg5pYPypqZowVptz3GYpK4pkcvn3EjkuNH"); - } - { - // Adalite test account addr0 - auto publicKey = PublicKey(parse_hex( - "57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" - "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" // dummy second - ), TWPublicKeyTypeED25519Extended); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W"); - } - { - // Adalite test account addr1 - auto publicKey = PublicKey(parse_hex( - "25af99056d600f7956312406bdd1cd791975bb1ae91c9d034fc65f326195fcdb247ee97ec351c0820dd12de4ca500232f73a35fe6f86778745bcd57f34d1048d" - "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" // dummy second - ), TWPublicKeyTypeED25519Extended); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZ7dnds6ZyhQdmgkrDFFPSDh8jG9RAhswcXt1bRauNw5jczjpV"); - } - { - // Play1 addr0 - auto publicKey = PublicKey(parse_hex( - "7cee0f30b9d536a786547dd77b35679b6830e945ffde768eb4f2a061b9dba016e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2" - "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" // dummy second - ), TWPublicKeyTypeED25519Extended); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZJYT9g1JgQWtLveUHavyRxQGi6hVzoQjct7yyCLGgk3pCyx7h"); - } -} - -TEST(CardanoAddress, FromPrivateKeyV2) { - { - // mnemonic Test, addr0 - auto privateKey = PrivateKey( - parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), - parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), - parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), - dummyKey, dummyKey, dummyKey - ); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - ASSERT_EQ(hex(publicKey.bytes), - "57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03" - "bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" - "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" - "1111111111111111111111111111111111111111111111111111111111111111"); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W"); - } - { - // mnemonic Play1, addr0 - auto privateKey = PrivateKey( - parse_hex("a089c9423100960440ccd5b7adbd202d1ab1993a7bb30fc88b287d94016df247"), - parse_hex("da86a87f08fb15de1431a6c0ccd5ebf51c3bee81f7eaf714801bbbe4d903154a"), - parse_hex("e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2"), - dummyKey, dummyKey, dummyKey - ); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - ASSERT_EQ(hex(publicKey.bytes), - "7cee0f30b9d536a786547dd77b35679b6830e945ffde768eb4f2a061b9dba016" - "e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2" - "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" - "1111111111111111111111111111111111111111111111111111111111111111"); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZJYT9g1JgQWtLveUHavyRxQGi6hVzoQjct7yyCLGgk3pCyx7h"); - } - { - // from cardano-crypto.js test - auto privateKey = PrivateKey( - parse_hex("d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48"), - parse_hex("d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c"), - parse_hex("69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"), - dummyKey, dummyKey, dummyKey - ); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - ASSERT_EQ(hex(publicKey.bytes), - "e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf698" - "69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" - "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" - "1111111111111111111111111111111111111111111111111111111111111111"); - auto address = AddressV2(publicKey); - ASSERT_EQ(address.string(), "Ae2tdPwUPEZCxt4UV1Uj2AMMRvg5pYPypqZowVptz3GYpK4pkcvn3EjkuNH"); - } -} - -TEST(CardanoAddress, PrivateKeyExtended) { - // check extended key lengths, private key 2x3x32 bytes, public key 2x64 bytes - auto privateKeyExt = PrivateKey( - parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), - parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), - parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), - dummyKey, dummyKey, dummyKey - ); - auto publicKeyExt = privateKeyExt.getPublicKey(TWPublicKeyTypeED25519Extended); - ASSERT_EQ(128, publicKeyExt.bytes.size()); - - // Non-extended: both are 32 bytes. - auto privateKeyNonext = PrivateKey( - parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744") - ); - auto publicKeyNonext = privateKeyNonext.getPublicKey(TWPublicKeyTypeED25519); - ASSERT_EQ(32, publicKeyNonext.bytes.size()); -} - -TEST(CardanoAddress, FromStringNegativeInvalidString) { - try { - auto address = AddressV3("__INVALID_ADDRESS__"); - } catch (...) { - return; - } - FAIL() << "Expected exception!"; -} - -TEST(CardanoAddress, FromStringNegativeBadChecksumV2) { - try { - auto address = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvm"); - } catch (...) { - return; - } - FAIL() << "Expected exception!"; -} - -TEST(CardanoAddress, CopyConstructorLegacy) { - AddressV3 address1 = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); - EXPECT_TRUE(address1.legacyAddressV2.has_value()); - AddressV3 address2 = AddressV3(address1); - EXPECT_TRUE(address2.legacyAddressV2.has_value()); - EXPECT_TRUE(*(address2.legacyAddressV2) == *(address1.legacyAddressV2)); - // if it was not a deep copy, double freeing would occur -} - -TEST(CardanoAddress, AssignmentOperatorLegacy) { - AddressV3 addr1leg = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); - EXPECT_TRUE(addr1leg.legacyAddressV2.has_value()); - AddressV3 addr2nonleg = AddressV3("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); - EXPECT_FALSE(addr2nonleg.legacyAddressV2.has_value()); - AddressV3 addr3leg = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); - EXPECT_TRUE(addr3leg.legacyAddressV2.has_value()); - - AddressV3 address = addr1leg; - EXPECT_TRUE(address.legacyAddressV2.has_value()); - EXPECT_TRUE(*address.legacyAddressV2 == *addr1leg.legacyAddressV2); - address = addr2nonleg; - EXPECT_FALSE(address.legacyAddressV2.has_value()); - address = addr3leg; - EXPECT_TRUE(address.legacyAddressV2.has_value()); - EXPECT_TRUE(*address.legacyAddressV2 == *addr3leg.legacyAddressV2); -} diff --git a/tests/Cardano/SigningTests.cpp b/tests/Cardano/SigningTests.cpp deleted file mode 100644 index 77fa4ad8d51..00000000000 --- a/tests/Cardano/SigningTests.cpp +++ /dev/null @@ -1,817 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Cardano/Signer.h" -#include "Cardano/AddressV3.h" -#include "proto/Cardano.pb.h" -#include - -#include "PrivateKey.h" -#include "HexCoding.h" -#include "Cbor.h" -#include "uint256.h" -#include -#include "../interface/TWTestUtilities.h" - -#include -#include - - -using namespace TW::Cardano; -using namespace TW; -using namespace std; - - -const auto privateKeyTest1 = "089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e"; -const auto ownAddress1 = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23"; -const auto sundaeTokenPolicy = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77"; - -TEST(CardanoSigning, SelectInputs) { - const auto inputs = std::vector({ - TxInput{{parse_hex("0001"), 0}, "ad01", 700}, - TxInput{{parse_hex("0002"), 1}, "ad02", 900}, - TxInput{{parse_hex("0003"), 2}, "ad03", 300}, - TxInput{{parse_hex("0004"), 3}, "ad04", 600}, - }); - - { // 2 - const auto s1 = Signer::selectInputsWithTokens(inputs, 1500, {}); - ASSERT_EQ(s1.size(), 2); - EXPECT_EQ(s1[0].amount, 900); - EXPECT_EQ(s1[1].amount, 700); - } - { // all - const auto s1 = Signer::selectInputsWithTokens(inputs, 10000, {}); - ASSERT_EQ(s1.size(), 4); - EXPECT_EQ(s1[0].amount, 900); - EXPECT_EQ(s1[1].amount, 700); - EXPECT_EQ(s1[2].amount, 600); - EXPECT_EQ(s1[3].amount, 300); - } - { // 3 - const auto s1 = Signer::selectInputsWithTokens(inputs, 2000, {}); - ASSERT_EQ(s1.size(), 3); - } - { // 1 - const auto s1 = Signer::selectInputsWithTokens(inputs, 500, {}); - ASSERT_EQ(s1.size(), 1); - } - { // at least 0 is returned - const auto s1 = Signer::selectInputsWithTokens(inputs, 0, {}); - ASSERT_EQ(s1.size(), 1); - } -} - -Proto::SigningInput createSampleInput(uint64_t amount, int utxoCount = 10, - const std::string& alternateToAddress = "", bool omitPrivateKey = false -) { - const std::string toAddress = (alternateToAddress.length() > 0) ? alternateToAddress : - "addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"; - - Proto::SigningInput input; - if (utxoCount >= 1) { - auto* utxo1 = input.add_utxos(); - const auto txHash1 = parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"); - utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); - utxo1->mutable_out_point()->set_output_index(1); - utxo1->set_address(ownAddress1); - utxo1->set_amount(1500000); - } - if (utxoCount >= 2) { - auto* utxo2 = input.add_utxos(); - const auto txHash2 = parse_hex("554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0"); - utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); - utxo2->mutable_out_point()->set_output_index(0); - utxo2->set_address(ownAddress1); - utxo2->set_amount(6500000); - } - - if (!omitPrivateKey) { - const auto privateKeyData = parse_hex(privateKeyTest1); - input.add_private_key(privateKeyData.data(), privateKeyData.size()); - } - input.mutable_transfer_message()->set_to_address(toAddress); - input.mutable_transfer_message()->set_change_address(ownAddress1); - input.mutable_transfer_message()->set_amount(amount); - input.mutable_transfer_message()->set_use_max_amount(false); - input.set_ttl(53333333); - return input; -} - -TEST(CardanoSigning, Plan) { - auto input = createSampleInput(7000000); - - { - auto signer = Signer(input); - const auto plan = signer.doPlan(); - EXPECT_EQ(plan.utxos.size(), 2); - EXPECT_EQ(plan.availableAmount, 8000000); - EXPECT_EQ(plan.amount, 7000000); - EXPECT_EQ(plan.fee, 170196); - EXPECT_EQ(plan.change, 829804); - EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); - EXPECT_EQ(plan.error, Common::Proto::OK); - } - { // very small target amount - input.mutable_transfer_message()->set_amount(1); - auto signer = Signer(input); - const auto plan = signer.doPlan(); - EXPECT_EQ(plan.utxos.size(), 1); - EXPECT_EQ(plan.availableAmount, 6500000); - EXPECT_EQ(plan.amount, 1); - EXPECT_EQ(plan.fee, 168435); - EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); - } - { // small target amount - input.mutable_transfer_message()->set_amount(2000000); - auto signer = Signer(input); - const auto plan = signer.doPlan(); - EXPECT_EQ(plan.utxos.size(), 1); - EXPECT_EQ(plan.availableAmount, 6500000); - EXPECT_EQ(plan.amount, 2000000); - EXPECT_EQ(plan.fee, 168611); - EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); - } - { // small target amount requested, but max amount - input.mutable_transfer_message()->set_amount(2000000); - input.mutable_transfer_message()->set_use_max_amount(true); - auto signer = Signer(input); - const auto plan = signer.doPlan(); - EXPECT_EQ(plan.utxos.size(), 2); - EXPECT_EQ(plan.availableAmount, 8000000); - EXPECT_EQ(plan.amount, 7832667); - EXPECT_EQ(plan.fee, 167333); - EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); - } -} - -TEST(CardanoSigning, PlanForceFee) { - auto requestedAmount = 6500000; - auto availableAmount = 8000000; - auto input = createSampleInput(requestedAmount); - - { - auto fee = 170147; - input.mutable_transfer_message()->set_force_fee(fee); - auto signer = Signer(input); - const auto plan = signer.doPlan(); - EXPECT_EQ(plan.availableAmount, availableAmount); - EXPECT_EQ(plan.amount, requestedAmount); - EXPECT_EQ(plan.fee, fee); - EXPECT_EQ(plan.change, availableAmount - requestedAmount - fee); - EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); - EXPECT_EQ(plan.error, Common::Proto::OK); - } - { // tiny fee - auto fee = 100; - input.mutable_transfer_message()->set_force_fee(fee); - auto signer = Signer(input); - const auto plan = signer.doPlan(); - EXPECT_EQ(plan.availableAmount, availableAmount); - EXPECT_EQ(plan.amount, requestedAmount); - EXPECT_EQ(plan.fee, fee); - EXPECT_EQ(plan.change, availableAmount - requestedAmount - fee); - EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); - } - { // large fee - auto fee = 1200000; - input.mutable_transfer_message()->set_force_fee(fee); - auto signer = Signer(input); - const auto plan = signer.doPlan(); - EXPECT_EQ(plan.availableAmount, availableAmount); - EXPECT_EQ(plan.amount, requestedAmount); - EXPECT_EQ(plan.fee, fee); - EXPECT_EQ(plan.change, availableAmount - requestedAmount - fee); - EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); - } - { // very large fee, larger than possible, truncated - auto fee = 3000000; - input.mutable_transfer_message()->set_force_fee(fee); - auto signer = Signer(input); - const auto plan = signer.doPlan(); - EXPECT_EQ(plan.availableAmount, availableAmount); - EXPECT_EQ(plan.amount, requestedAmount); - EXPECT_EQ(plan.fee, 1500000); - EXPECT_EQ(plan.change, 0); - EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); - } - { // force fee and max amount: fee is used, amount is max, change 0 - auto fee = 160000; - input.mutable_transfer_message()->set_force_fee(fee); - input.mutable_transfer_message()->set_use_max_amount(true); - auto signer = Signer(input); - const auto plan = signer.doPlan(); - EXPECT_EQ(plan.availableAmount, availableAmount); - EXPECT_EQ(plan.amount, 7840000); - EXPECT_EQ(plan.fee, fee); - EXPECT_EQ(plan.change, 0); - EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); - } -} - -TEST(CardanoSigning, PlanMissingPrivateKey) { - auto input = createSampleInput(7000000, 10, "", true); - - auto signer = Signer(input); - const auto plan = signer.doPlan(); - - EXPECT_EQ(plan.utxos.size(), 2); - EXPECT_EQ(plan.availableAmount, 8000000); - EXPECT_EQ(plan.amount, 7000000); - EXPECT_EQ(plan.fee, 170196); - EXPECT_EQ(plan.change, 829804); - EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); - EXPECT_EQ(plan.error, Common::Proto::OK); -} - -TEST(CardanoSigning, SignTransfer1) { - const auto input = createSampleInput(7000000); - - auto signer = Signer(input); - const auto output = signer.sign(); - - EXPECT_EQ(output.error(), Common::Proto::OK); - - const auto encoded = data(output.encoded()); - EXPECT_EQ(hex(encoded), "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a006acfc082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a000ca96c021a000298d4031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058407cf591599852b5f5e007fdc241062405c47e519266c0d884b0767c1d4f5eacce00db035998e53ed10ca4ba5ce4aac8693798089717ce6cf4415f345cc764200ef6"); - const auto txid = data(output.tx_id()); - EXPECT_EQ(hex(txid), "9b5b15e133cd73ccaa85307d2986aebc846505118a2eb4e6111e6b4b67d1f389"); - - { - const auto decode = Cbor::Decode(encoded); - ASSERT_TRUE(decode.isValid()); - EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0\", 0], [h\"f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767\", 1]], 1: [[h\"01558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5\", 7000000], [h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 829804]], 2: 170196, 3: 53333333}, {0: [[h\"6d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290\", h\"7cf591599852b5f5e007fdc241062405c47e519266c0d884b0767c1d4f5eacce00db035998e53ed10ca4ba5ce4aac8693798089717ce6cf4415f345cc764200e\"]]}, null]"); - EXPECT_EQ(decode.getArrayElements().size(), 3); - } -} - -TEST(CardanoSigning, PlanAndSignTransfer1) { - uint amount = 6000000; - auto input = createSampleInput(amount); - - { - // run plan and check result - auto signer = Signer(input); - const auto plan = signer.doPlan(); - - EXPECT_EQ(plan.availableAmount, 8000000); - EXPECT_EQ(plan.amount, amount); - EXPECT_EQ(plan.fee, 170196); - EXPECT_EQ(plan.change, 8000000 - amount - 170196); - ASSERT_EQ(plan.utxos.size(), 2); - EXPECT_EQ(plan.utxos[0].amount, 6500000); - EXPECT_EQ(plan.utxos[1].amount, 1500000); - - // perform sign with default plan - const auto output = signer.sign(); - - EXPECT_EQ(output.error(), Common::Proto::OK); - - const auto encoded = data(output.encoded()); - EXPECT_EQ(hex(encoded), "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a005b8d8082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a001bebac021a000298d4031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058404abc749ffaffcf2f87970e4f1983c5e44b352ee1515b60017fc65e581d42b3a6ed146d5eb35d04a770460b0541a25afd5aedfd027fdaded82686f43454196a0cf6"); - const auto txid = data(output.tx_id()); - EXPECT_EQ(hex(txid), "3852f809245d7000ad0c5ccb1357e5d333b0dd25158924581e4c7049ec68c564"); - } - - // set different plan, with one input only - input.mutable_plan()->set_amount(amount); - input.mutable_plan()->set_available_amount(6500000); - input.mutable_plan()->set_fee(165489); - input.mutable_plan()->set_change(17191988); - *(input.mutable_plan()->add_utxos()) = input.utxos(0); - input.mutable_plan()->set_error(Common::Proto::OK); - - auto signer = Signer(input); - const auto output = signer.sign(); - - EXPECT_EQ(output.error(), Common::Proto::OK); - - const auto encoded = data(output.encoded()); - EXPECT_EQ(hex(encoded), "83a40081825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a005b8d8082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a01065434021a00028671031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058408311a058035d75545a47b844fea401aa9c23e99fe7bc8136b554396eef135d4cd93062c5df38e613185c21bb1c98b881d1e0fd1024d3539b163c8e14d1a6e40df6"); - const auto txid = data(output.tx_id()); - EXPECT_EQ(hex(txid), "e319c0bfc99cdb79d64f00b7e8fb8bfbf29fa70554c84f101e92b7dfed172448"); -} - - -TEST(CardanoSigning, PlanAndSignMaxAmount) { - auto input = createSampleInput(7000000); - input.mutable_transfer_message()->set_use_max_amount(true); - - { - // run plan and check result - auto signer = Signer(input); - const auto plan = signer.doPlan(); - - EXPECT_EQ(plan.availableAmount, 8000000); - EXPECT_EQ(plan.amount, 8000000 - 167333); - EXPECT_EQ(plan.fee, 167333); - EXPECT_EQ(plan.change, 0); - ASSERT_EQ(plan.utxos.size(), 2); - EXPECT_EQ(plan.utxos[0].amount, 1500000); - EXPECT_EQ(plan.utxos[1].amount, 6500000); - } - - auto signer = Signer(input); - const auto output = signer.sign(); - - EXPECT_EQ(output.error(), Common::Proto::OK); - - const auto encoded = data(output.encoded()); - EXPECT_EQ(hex(encoded), "83a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000018182583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a0077845b021a00028da5031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058403e64473e08adc863953c0e9f820b658dda0b8a423d6172fdccff73fcd5559956c9df8ed93ff67405331d368a0c11fd18c69781046384946582e1555e9e8ec70bf6"); - const auto txid = data(output.tx_id()); - EXPECT_EQ(hex(txid), "ca0f1e12f20c95011da7d686d206a1eb98df94accd74c4df4ef403c5ce836057"); -} - -TEST(CardanoSigning, SignNegative) { - { // plan with error - auto input = createSampleInput(7000000); - const auto error = Common::Proto::Error_invalid_memo; - input.mutable_plan()->set_error(error); - auto signer = Signer(input); - const auto output = signer.sign(); - EXPECT_EQ(output.error(), error); - } - { // zero requested amount - auto input = createSampleInput(0); - auto signer = Signer(input); - const auto output = signer.sign(); - EXPECT_EQ(output.error(), Common::Proto::Error_zero_amount_requested); - } - { // no utxo - auto input = createSampleInput(7000000, 0); - auto signer = Signer(input); - const auto output = signer.sign(); - EXPECT_EQ(output.error(), Common::Proto::Error_missing_input_utxos); - } - { // low balance - auto input = createSampleInput(7000000000); - auto signer = Signer(input); - const auto output = signer.sign(); - EXPECT_EQ(output.error(), Common::Proto::Error_low_balance); - } - { // missing private key - auto input = createSampleInput(7000000, 10, "", true); - auto signer = Signer(input); - const auto output = signer.sign(); - EXPECT_EQ(output.error(), Common::Proto::Error_missing_private_key); - } -} - -TEST(CardanoSigning, SignTransfer_0db1ea) { - const auto amount = 1100000; - - Proto::SigningInput input; - auto* utxo1 = input.add_utxos(); - const auto txHash1 = parse_hex("81b935447bb994567f041d181b628a0afbcd747d0199c9ff4cd895686bbee8c6"); - utxo1->mutable_out_point()->set_tx_hash(std::string(txHash1.begin(), txHash1.end())); - utxo1->mutable_out_point()->set_output_index(0); - utxo1->set_address(ownAddress1); - utxo1->set_amount(1000000); - auto* utxo2 = input.add_utxos(); - const auto txHash2 = parse_hex("3a9068a273cc2af59b45593b78973841d972d01802abe992c55dbeecdffc561b"); - utxo2->mutable_out_point()->set_tx_hash(std::string(txHash2.begin(), txHash2.end())); - utxo2->mutable_out_point()->set_output_index(0); - utxo2->set_address(ownAddress1); - utxo2->set_amount(1800000); - - const auto privateKeyData1 = parse_hex(privateKeyTest1); - input.add_private_key(privateKeyData1.data(), privateKeyData1.size()); - input.mutable_transfer_message()->set_to_address("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); - input.mutable_transfer_message()->set_change_address(ownAddress1); - input.mutable_transfer_message()->set_amount(amount); - auto fee = 170147; - input.mutable_transfer_message()->set_use_max_amount(false); - input.mutable_transfer_message()->set_force_fee(fee); // use force fee feature here - input.set_ttl(54675589); - - { - // run plan and check result - auto signer = Signer(input); - const auto plan = signer.doPlan(); - - EXPECT_EQ(plan.availableAmount, 2800000); - EXPECT_EQ(plan.amount, amount); - EXPECT_EQ(plan.fee, fee); - EXPECT_EQ(plan.change, 2800000 - amount - fee); - EXPECT_EQ(plan.utxos.size(), 2); - } - - // set plan with specific fee, to match the real transaction - input.mutable_plan()->set_amount(amount); - input.mutable_plan()->set_available_amount(2800000); - input.mutable_plan()->set_fee(fee); - input.mutable_plan()->set_change(2800000 - amount - fee); - *(input.mutable_plan()->add_utxos()) = input.utxos(0); - *(input.mutable_plan()->add_utxos()) = input.utxos(1); - input.mutable_plan()->set_error(Common::Proto::OK); - - auto signer = Signer(input); - const auto output = signer.sign(); - - // https://cardanoscan.io/transaction/0db1ea8c5c5828bbd027fcef3da02a63b86899db670ad7bb0630cefbe35944fa - // curl -d '{"txHash":"0db1ea..44fa","txBody":"83a400..06f6"}' -H "Content-Type: application/json" https:///api/txs/submit - EXPECT_EQ(output.error(), Common::Proto::OK); - const auto encoded = data(output.encoded()); - EXPECT_EQ(hex(encoded), "83a4008282582081b935447bb994567f041d181b628a0afbcd747d0199c9ff4cd895686bbee8c6008258203a9068a273cc2af59b45593b78973841d972d01802abe992c55dbeecdffc561b000182825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a34681a0010c8e082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a001757fd021a000298a3031a03424885a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058406300b52aaff1e26067a3e0a48ae26f4f068765f46f934fabeab872c1d25535fc94893ec72feacd787f0174fbabd8933727d9a2b319b406e7a855843b0c051806f6"); - const auto txid = data(output.tx_id()); - EXPECT_EQ(hex(txid), "0db1ea8c5c5828bbd027fcef3da02a63b86899db670ad7bb0630cefbe35944fa"); -} - -TEST(CardanoSigning, SignTransferFromLegacy) { - Proto::SigningInput input; - auto* utxo1 = input.add_utxos(); - const auto txHash1 = parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"); - utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); - utxo1->mutable_out_point()->set_output_index(1); - utxo1->set_address("Ae2tdPwUPEZMRgecV9jV2e9RdbrmnWu7YgRie4de16xLdkWhy6q7ypmRhgn"); - utxo1->set_amount(1500000); - auto* utxo2 = input.add_utxos(); - const auto txHash2 = parse_hex("554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0"); - utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); - utxo2->mutable_out_point()->set_output_index(0); - utxo2->set_address("Ae2tdPwUPEZMRgecV9jV2e9RdbrmnWu7YgRie4de16xLdkWhy6q7ypmRhgn"); - utxo2->set_amount(6500000); - - const auto privateKeyData = parse_hex("c031e942f6bf2b2864700e7da20964ee6bb6d716345ce2e24d8c00e6500b574411111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"); - { - const auto privKey = PrivateKey(privateKeyData); - const auto pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519Extended); - const auto addr = AddressV2(pubKey); - EXPECT_EQ(addr.string(), "Ae2tdPwUPEZMRgecV9jV2e9RdbrmnWu7YgRie4de16xLdkWhy6q7ypmRhgn"); - } - input.add_private_key(privateKeyData.data(), privateKeyData.size()); - input.mutable_transfer_message()->set_to_address("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); - input.mutable_transfer_message()->set_change_address(ownAddress1); - input.mutable_transfer_message()->set_amount(7000000); - input.mutable_transfer_message()->set_use_max_amount(false); - input.set_ttl(53333333); - - auto signer = Signer(input); - const auto output = signer.sign(); - - EXPECT_EQ(output.error(), Common::Proto::Error_invalid_address); - EXPECT_EQ(hex(output.encoded()), ""); -} - -TEST(CardanoSigning, SignTransferToLegacy) { - const auto toAddressLegacy = "DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ"; - EXPECT_FALSE(AddressV3::isValid(toAddressLegacy)); // not V3 - EXPECT_TRUE(AddressV3::isValidLegacy(toAddressLegacy)); - - const auto input = createSampleInput(7000000, 10, toAddressLegacy); - - auto signer = Signer(input); - const auto output = signer.sign(); - - EXPECT_EQ(output.error(), Common::Proto::OK); - EXPECT_EQ(hex(output.encoded()), "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282584c82d818584283581c6aebd89cf88271c3ee76339930d8956b03f018b2f4871522f88eb8f9a101581e581c692a37dae3bc63dfc3e1463f12011f26655ab1d1e0f4ed4b8fc63708001ad8a9555b1a006acfc082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a000ca627021a00029c19031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840db9becdc733f4c08c0e7abc29b5cc6469f9339d32f565df8bf77455439ae1f949facc9b831754e74d3fbb42e99647eedd6c28de1461d18c315485f5d24b5b90af6"); - EXPECT_EQ(hex(data(output.tx_id())), "f9b713e9987ec1377ac223f50d63c7a5e155915302de43f40d7b2627accabf69"); -} - -TEST(CardanoSigning, SignTransferToInvalid) { - const auto input = createSampleInput(7000000, 10, "__INVALID_ADDRESS__"); - - auto signer = Signer(input); - const auto output = signer.sign(); - - EXPECT_EQ(output.error(), Common::Proto::Error_invalid_address); - EXPECT_EQ(hex(output.encoded()), ""); -} - -TEST(CardanoSigning, SignTransferToken) { - Proto::SigningInput input; - auto* utxo1 = input.add_utxos(); - const auto txHash1 = parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"); - utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); - utxo1->mutable_out_point()->set_output_index(1); - utxo1->set_address(ownAddress1); - utxo1->set_amount(8051373); - // some token, to be preserved - auto* token3 = utxo1->add_token_amount(); - token3->set_policy_id(sundaeTokenPolicy); - token3->set_asset_name("CUBY"); - const auto tokenAmount3 = store(uint256_t(3000000)); - token3->set_amount(tokenAmount3.data(), tokenAmount3.size()); - - auto* utxo2 = input.add_utxos(); - const auto txHash2 = parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"); - utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); - utxo2->mutable_out_point()->set_output_index(2); - utxo2->set_address(ownAddress1); - utxo2->set_amount(2000000); - // some SUNDAE token, to be transferred - auto* token1 = utxo2->add_token_amount(); - token1->set_policy_id(sundaeTokenPolicy); - token1->set_asset_name("SUNDAE"); - const auto tokenAmount1 = store(uint256_t(80996569)); - token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); - // some other token, to be preserved - auto* token2 = utxo2->add_token_amount(); - token2->set_policy_id(sundaeTokenPolicy); - token2->set_asset_name("CUBY"); - const auto tokenAmount2 = store(uint256_t(2000000)); - token2->set_amount(tokenAmount2.data(), tokenAmount2.size()); - - const auto privateKeyData = parse_hex(privateKeyTest1); - input.add_private_key(privateKeyData.data(), privateKeyData.size()); - input.mutable_transfer_message()->set_to_address("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); - input.mutable_transfer_message()->set_change_address("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); - input.mutable_transfer_message()->set_amount(1500000); - auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); - toToken->set_policy_id(sundaeTokenPolicy); - toToken->set_asset_name("SUNDAE"); - const auto toTokenAmount = store(uint256_t(20000000)); - toToken->set_amount(toTokenAmount.data(), toTokenAmount.size()); - input.mutable_transfer_message()->set_use_max_amount(false); - input.set_ttl(53333333); - - { // check min ADA amount, set it - const auto bundleProtoData = data(input.transfer_message().token_amount().SerializeAsString()); - const auto minAdaAmount = TWCardanoMinAdaAmount(&bundleProtoData); - EXPECT_EQ(minAdaAmount, 1444443); - input.mutable_transfer_message()->set_amount(minAdaAmount); - } - - { - // run plan and check result - auto signer = Signer(input); - const auto plan = signer.doPlan(); - - EXPECT_EQ(plan.availableAmount, 10051373); - EXPECT_EQ(plan.amount, 1444443); - EXPECT_EQ(plan.fee, 175966); - EXPECT_EQ(plan.change, 8430964); - EXPECT_EQ(plan.utxos.size(), 2); - EXPECT_EQ(plan.availableTokens.size(), 2); - EXPECT_EQ(plan.availableTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_CUBY"), 5000000); - EXPECT_EQ(plan.availableTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 80996569); - EXPECT_EQ(plan.outputTokens.size(), 1); - EXPECT_EQ(plan.outputTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_CUBY"), 0); - EXPECT_EQ(plan.outputTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 20000000); - EXPECT_EQ(plan.changeTokens.size(), 2); - EXPECT_EQ(plan.changeTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_CUBY"), 5000000); - EXPECT_EQ(plan.changeTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 60996569); - } - - auto signer = Signer(input); - const auto output = signer.sign(); - - EXPECT_EQ(output.error(), Common::Proto::OK); - const auto encoded = data(output.encoded()); - EXPECT_EQ(hex(encoded), "83a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76702018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5821a00160a5ba1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a01312d00825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468821a0080a574a2581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a144435542591a004c4b40581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a03a2bbd9021a0002af5e031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290584000feb412442f8851faa59742eb2c37f3994b0d143a424367143490cf828246991e504fa8eac61c403bfa7634bd1f0adc44f3f54f6a474856701e2cbb15fb5b04f6"); - const auto txid = data(output.tx_id()); - EXPECT_EQ(hex(txid), "dacb3a0c5b3b7fa36b49f25a0a59b941ab8a21f0db5770e9e6982ff120122649"); - - { - // also test proto toProto / fromProto - const Proto::TransactionPlan planProto = Signer::plan(input); - const auto plan2 = TransactionPlan::fromProto(planProto); - EXPECT_EQ(plan2.amount, 1444443); - EXPECT_EQ(plan2.change, 8430964); - } -} - -TEST(CardanoSigning, SignTransferToken_1dd248) { - Proto::SigningInput input; - auto* utxo1 = input.add_utxos(); - const auto txHash1 = parse_hex("f2d2b11c8c07c5c646f5b5af20fddf2f0a174743c6a1b13cca27e28a6ca34710"); - utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); - utxo1->mutable_out_point()->set_output_index(0); - utxo1->set_address(ownAddress1); - utxo1->set_amount(1500000); - // some token - auto* token3 = utxo1->add_token_amount(); - token3->set_policy_id(sundaeTokenPolicy); - token3->set_asset_name("SUNDAE"); - const auto tokenAmount3 = store(uint256_t(20000000)); - token3->set_amount(tokenAmount3.data(), tokenAmount3.size()); - - auto* utxo2 = input.add_utxos(); - const auto txHash2 = parse_hex("6975fcf7bbca745c85f50777f956219868fd9cad14ba496fed1371252e8df60f"); - utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); - utxo2->mutable_out_point()->set_output_index(0); - utxo2->set_address(ownAddress1); - utxo2->set_amount(10258890); - - const auto privateKeyData = parse_hex(privateKeyTest1); - input.add_private_key(privateKeyData.data(), privateKeyData.size()); - input.mutable_transfer_message()->set_to_address("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); // Test - input.mutable_transfer_message()->set_change_address(ownAddress1); - input.mutable_transfer_message()->set_amount(1600000); - auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); - toToken->set_policy_id(sundaeTokenPolicy); - toToken->set_asset_name("SUNDAE"); - const auto toTokenAmount = store(uint256_t(11000000)); - toToken->set_amount(toTokenAmount.data(), toTokenAmount.size()); - input.mutable_transfer_message()->set_use_max_amount(false); - input.set_ttl(61232158); - - { // check min ADA amount - const auto bundleProtoData = data(input.transfer_message().token_amount().SerializeAsString()); - EXPECT_EQ(TWCardanoMinAdaAmount(&bundleProtoData), 1444443); - EXPECT_GT(input.transfer_message().amount(), TWCardanoMinAdaAmount(&bundleProtoData)); - } - - { - // run plan and check result - auto signer = Signer(input); - const auto plan = signer.doPlan(); - - EXPECT_EQ(plan.availableAmount, 11758890); - EXPECT_EQ(plan.amount, 11758890 - 9984729 - 174161); - EXPECT_EQ(plan.fee, 174161); - EXPECT_EQ(plan.change, 9984729); - EXPECT_EQ(plan.utxos.size(), 2); - EXPECT_EQ(plan.availableTokens.size(), 1); - EXPECT_EQ(plan.availableTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 20000000); - EXPECT_EQ(plan.outputTokens.size(), 1); - EXPECT_EQ(plan.outputTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 11000000); - EXPECT_EQ(plan.changeTokens.size(), 1); - EXPECT_EQ(plan.changeTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 9000000); - } - - // set plan with specific fee, to match the real transaction - input.mutable_plan()->set_available_amount(11758890); - input.mutable_plan()->set_amount(1600000); - input.mutable_plan()->set_fee(174102); - input.mutable_plan()->set_change(9984788); - *(input.mutable_plan()->add_available_tokens()) = input.utxos(0).token_amount(0); - *(input.mutable_plan()->add_output_tokens()) = input.utxos(0).token_amount(0); - input.mutable_plan()->mutable_output_tokens(0)->set_amount(toTokenAmount.data(), toTokenAmount.size()); - *(input.mutable_plan()->add_change_tokens()) = input.utxos(0).token_amount(0); - const auto changeTokenAmount = store(uint256_t(9000000)); - input.mutable_plan()->mutable_change_tokens(0)->set_amount(changeTokenAmount.data(), changeTokenAmount.size()); - *(input.mutable_plan()->add_utxos()) = input.utxos(1); - *(input.mutable_plan()->add_utxos()) = input.utxos(0); - input.mutable_plan()->set_error(Common::Proto::OK); - - auto signer = Signer(input); - const auto output = signer.sign(); - - // https://cardanoscan.io/transaction/1dd24872d93d3b5091b98e19b9f920cd0c4369e4c5ca178e898152c52f00c162 - // curl -d '{"txHash":"1dd248..c162","txBody":"83a400..08f6"}' -H "Content-Type: application/json" https:///api/txs/submit - EXPECT_EQ(output.error(), Common::Proto::OK); - const auto encoded = data(output.encoded()); - EXPECT_EQ(hex(encoded), "83a400828258206975fcf7bbca745c85f50777f956219868fd9cad14ba496fed1371252e8df60f00825820f2d2b11c8c07c5c646f5b5af20fddf2f0a174743c6a1b13cca27e28a6ca34710000182825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468821a00186a00a1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a00a7d8c082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b821a00985b14a1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a00895440021a0002a816031a03a6541ea100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840c8cdee32bfd584f55cf334b4ec6f734635144736d48f882e647a7a6283f230bc5a67d4dd66a9e523e0c29c812ed1e3589febbcf96547a1fc6d061a7ccfb81308f6"); - const auto txid = data(output.tx_id()); - EXPECT_EQ(hex(txid), "1dd24872d93d3b5091b98e19b9f920cd0c4369e4c5ca178e898152c52f00c162"); -} - -TEST(CardanoSigning, SignTransferTokenMaxAmount_620b71) { - Proto::SigningInput input; - auto* utxo1 = input.add_utxos(); - const auto txHash1 = parse_hex("46964521ad00d9b3f3d41f77c07e1b3093848048dbdf2d95cf900e15cdac0d7f"); - utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); - utxo1->mutable_out_point()->set_output_index(0); - utxo1->set_address(ownAddress1); - utxo1->set_amount(2170871); - // some token - auto* token1 = utxo1->add_token_amount(); - token1->set_policy_id(sundaeTokenPolicy); - token1->set_asset_name("SUNDAE"); - const auto tokenAmount1 = store(uint256_t(20000000)); - token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); - - const auto privateKeyData = parse_hex(privateKeyTest1); - input.add_private_key(privateKeyData.data(), privateKeyData.size()); - input.mutable_transfer_message()->set_to_address("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); - input.mutable_transfer_message()->set_change_address(ownAddress1); - input.mutable_transfer_message()->set_amount(666); // doesn't matter, max is used - auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); - toToken->set_policy_id(sundaeTokenPolicy); - toToken->set_asset_name("SUNDAE"); - const auto toTokenAmount = store(uint256_t(666)); // doesn't matter, max is used - input.mutable_transfer_message()->set_use_max_amount(true); - input.set_ttl(61085916); - - { - // run plan and check result - auto signer = Signer(input); - const auto plan = signer.doPlan(); - - EXPECT_EQ(plan.availableAmount, 2170871); - EXPECT_EQ(plan.amount, 2170871 - 167730); - EXPECT_EQ(plan.fee, 167730); - EXPECT_EQ(plan.change, 0); - EXPECT_EQ(plan.utxos.size(), 1); - EXPECT_EQ(plan.availableTokens.size(), 1); - EXPECT_EQ(plan.availableTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 20000000); - EXPECT_EQ(plan.outputTokens.size(), 1); - EXPECT_EQ(plan.outputTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 20000000); - EXPECT_EQ(plan.changeTokens.size(), 0); - } - - // set plan with specific fee, to match the real transaction - input.mutable_plan()->set_available_amount(2170871); - input.mutable_plan()->set_amount(1998526); - input.mutable_plan()->set_fee(172345); - input.mutable_plan()->set_change(0); - *(input.mutable_plan()->add_available_tokens()) = input.utxos(0).token_amount(0); - *(input.mutable_plan()->add_output_tokens()) = input.utxos(0).token_amount(0); - *(input.mutable_plan()->add_utxos()) = input.utxos(0); - input.mutable_plan()->set_error(Common::Proto::OK); - - auto signer = Signer(input); - const auto output = signer.sign(); - - // https://cardanoscan.io/transaction/620b719338efb419b0e1417bfbe01fc94a62d5669a4b8cbbf4e32ecc1ca3b872 - // curl -d '{"txHash":"620b71..b872","txBody":"83a400..08f6"}' -H "Content-Type: application/json" https:///api/txs/submit - EXPECT_EQ(output.error(), Common::Proto::OK); - const auto encoded = data(output.encoded()); - EXPECT_EQ(hex(encoded), "83a4008182582046964521ad00d9b3f3d41f77c07e1b3093848048dbdf2d95cf900e15cdac0d7f00018182583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5821a001e7ebea1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a01312d00021a0002a139031a03a418dca100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840e1d1565cd747b20b0f10a92f068f3d5faebdee92b4b4a4b96ce14736d975e17d1446f7f51e64997a0bb38e0151dc738468161d574d6cfcd8040e4455ff46bc08f6"); - const auto txid = data(output.tx_id()); - EXPECT_EQ(hex(txid), "620b719338efb419b0e1417bfbe01fc94a62d5669a4b8cbbf4e32ecc1ca3b872"); -} - -TEST(CardanoSigning, SignTransferTwoTokens) { - auto input = createSampleInput(7000000); - input.mutable_transfer_message()->set_amount(1500000); - auto* token1 = input.mutable_transfer_message()->mutable_token_amount()->add_token(); - token1->set_policy_id(sundaeTokenPolicy); - token1->set_asset_name("SUNDAE"); - const auto tokenAmount1 = store(uint256_t(40000000)); - token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); - auto* token2 = input.mutable_transfer_message()->mutable_token_amount()->add_token(); - token2->set_policy_id(sundaeTokenPolicy); - token2->set_asset_name("CUBY"); - const auto tokenAmount2 = store(uint256_t(2000000)); - token2->set_amount(tokenAmount2.data(), tokenAmount2.size()); - - auto signer = Signer(input); - const auto output = signer.sign(); - - EXPECT_EQ(output.error(), Common::Proto::Error_invalid_requested_token_amount); - const auto encoded = data(output.encoded()); - EXPECT_EQ(hex(output.encoded()), ""); -} - -TEST(CardanoSigning, SignMessageWithKey) { - // test case from cardano-crypto.js - - const auto privateKey = PrivateKey(parse_hex( - "d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48" - "d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c" - "69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" - "1111111111111111111111111111111111111111111111111111111111111111" - "1111111111111111111111111111111111111111111111111111111111111111" - "1111111111111111111111111111111111111111111111111111111111111111" - )); - - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Extended); - EXPECT_EQ(hex(publicKey.bytes), - "e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf698" - "69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" - "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" - "1111111111111111111111111111111111111111111111111111111111111111" - ); - - const auto sampleMessageStr = "Hello world"; - const auto sampleMessage = data(sampleMessageStr); - - const auto signature = privateKey.sign(sampleMessage, TWCurveED25519Extended); - - const auto sampleRightSignature = "1096ddcfb2ad21a4c0d861ef3fabe18841e8de88105b0d8e36430d7992c588634ead4100c32b2800b31b65e014d54a8238bdda63118d829bf0bcf1b631e86f0e"; - EXPECT_EQ(hex(signature), sampleRightSignature); -} - -TEST(CardanoSigning, AnySignTransfer1) { - const auto input = createSampleInput(7000000); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeCardano); - - EXPECT_EQ(output.error(), Common::Proto::OK); - - const auto encoded = data(output.encoded()); - EXPECT_EQ(hex(encoded), "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a006acfc082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a000ca96c021a000298d4031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058407cf591599852b5f5e007fdc241062405c47e519266c0d884b0767c1d4f5eacce00db035998e53ed10ca4ba5ce4aac8693798089717ce6cf4415f345cc764200ef6"); - const auto txid = data(output.tx_id()); - EXPECT_EQ(hex(txid), "9b5b15e133cd73ccaa85307d2986aebc846505118a2eb4e6111e6b4b67d1f389"); -} - -TEST(CardanoSigning, AnyPlan1) { - const auto input = createSampleInput(7000000); - - Proto::TransactionPlan plan; - ANY_PLAN(input, plan, TWCoinTypeCardano); - - EXPECT_EQ(plan.error(), Common::Proto::OK); - EXPECT_EQ(plan.amount(), 7000000); - EXPECT_EQ(plan.available_amount(), 8000000); - EXPECT_EQ(plan.fee(), 170196); - EXPECT_EQ(plan.change(), 829804); - ASSERT_EQ(plan.utxos_size(), 2); - EXPECT_EQ(plan.utxos(0).amount(), 6500000); - EXPECT_EQ(plan.utxos(1).amount(), 1500000); - - EXPECT_EQ(hex(plan.SerializeAsString()), "0880a4e80310c09fab0318d4b10a20ecd2324292010a220a20554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af01267616464723171383034336d356865656179646e76746d6d6b7975686536717635686176766873663064323671336a7967737370786c796670796b3679716b77307968747976747230666c656b6a3834753634617a38326375666d716e36357a6473796c7a6b323318a0dd8c034293010a240a20f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76710011267616464723171383034336d356865656179646e76746d6d6b7975686536717635686176766873663064323671336a7967737370786c796670796b3679716b77307968747976747230666c656b6a3834753634617a38326375666d716e36357a6473796c7a6b323318e0c65b"); - - { - // also test fromProto - const auto plan2 = TransactionPlan::fromProto(plan); - EXPECT_EQ(plan2.amount, plan.amount()); - EXPECT_EQ(plan2.change, plan.change()); - } -} diff --git a/tests/Cardano/TWCardanoAddressTests.cpp b/tests/Cardano/TWCardanoAddressTests.cpp deleted file mode 100644 index 68a0c9a991d..00000000000 --- a/tests/Cardano/TWCardanoAddressTests.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include -#include -#include -#include -#include -#include "../interface/TWTestUtilities.h" -#include "PrivateKey.h" - -#include - -TEST(TWCardano, AddressFromPublicKey) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA( - "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" - "639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a" - ).get())); - ASSERT_NE(nullptr, privateKey.get()); - auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519Extended(privateKey.get())); - ASSERT_NE(nullptr, publicKey.get()); - ASSERT_EQ(128, publicKey.get()->impl.bytes.size()); - auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeCardano)); - auto addressString = WRAPS(TWAnyAddressDescription(address.get())); - assertStringsEqual(addressString, "addr1qx4z6twzknkkux0hhp0kq6hvdfutczp56g56y5em8r8mgvxalp7nkkk25vuspleke2zltaetmlwrfxv7t049cq9jmwjswmfw6t"); - - auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING("addr1qx4z6twzknkkux0hhp0kq6hvdfutczp56g56y5em8r8mgvxalp7nkkk25vuspleke2zltaetmlwrfxv7t049cq9jmwjswmfw6t").get(), TWCoinTypeCardano)); - ASSERT_NE(nullptr, address2.get()); - auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); - assertStringsEqual(address2String, "addr1qx4z6twzknkkux0hhp0kq6hvdfutczp56g56y5em8r8mgvxalp7nkkk25vuspleke2zltaetmlwrfxv7t049cq9jmwjswmfw6t"); - - ASSERT_TRUE(TWAnyAddressEqual(address.get(), address2.get())); -} - -TEST(TWCardano, AddressFromWallet) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( - STRING("cost dash dress stove morning robust group affair stomach vacant route volume yellow salute laugh").get(), - STRING("").get() - )); - auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeCardano)); - auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); - EXPECT_EQ(TWDataSize(privateKeyData.get()), 192); - - auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519Extended(privateKey.get())); - auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); - EXPECT_EQ(TWDataSize(publicKeyData.get()), 128); - assertHexEqual(publicKeyData, "fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26faf4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276"); - - auto address = WRAPS(TWCoinTypeDeriveAddress(TWCoinTypeCardano, privateKey.get())); - assertStringsEqual(address, "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); -} diff --git a/tests/Cardano/TWCoinTypeTests.cpp b/tests/Cardano/TWCoinTypeTests.cpp deleted file mode 100644 index 987f2fc7856..00000000000 --- a/tests/Cardano/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWCardanoCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeCardano)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("b7a6c5cadab0f64bdc89c77ee4a351463aba5c33f2cef6bbd6542a74a90a3af3")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeCardano, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("DdzFFzCqrhstpwKc8WMvPwwBb5oabcTW9zc5ykA37wJR4tYQucvsR9dXb2kEGNXkFJz2PtrpzfRiZkx8R1iNo8NYqdsukVmv7EAybFwC")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeCardano, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeCardano)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeCardano)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeCardano), 6); - ASSERT_EQ(TWBlockchainCardano, TWCoinTypeBlockchain(TWCoinTypeCardano)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeCardano)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeCardano)); - assertStringsEqual(symbol, "ADA"); - assertStringsEqual(txUrl, "https://cardanoscan.io/transaction/b7a6c5cadab0f64bdc89c77ee4a351463aba5c33f2cef6bbd6542a74a90a3af3"); - assertStringsEqual(accUrl, "https://cardanoscan.io/address/DdzFFzCqrhstpwKc8WMvPwwBb5oabcTW9zc5ykA37wJR4tYQucvsR9dXb2kEGNXkFJz2PtrpzfRiZkx8R1iNo8NYqdsukVmv7EAybFwC"); - assertStringsEqual(id, "cardano"); - assertStringsEqual(name, "Cardano"); -} diff --git a/tests/Cardano/TransactionTests.cpp b/tests/Cardano/TransactionTests.cpp deleted file mode 100644 index cce2c5be2bc..00000000000 --- a/tests/Cardano/TransactionTests.cpp +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Cardano/Transaction.h" -#include "Cardano/AddressV3.h" -#include - -#include "HexCoding.h" -#include "Cbor.h" - -#include - - -using namespace TW::Cardano; -using namespace TW; -using namespace std; - - -Transaction createTx() { - Transaction tx; - tx.inputs.emplace_back(parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"), 1); - tx.inputs.emplace_back(parse_hex("554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0"), 0); - tx.outputs.emplace_back( - AddressV3("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23").data(), - 2000000 - ); - tx.outputs.emplace_back( - AddressV3("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5").data(), - 16749189 - ); - tx.fee = 165555; - tx.ttl = 53333345; - return tx; -} - -TEST(CardanoTransaction, Encode) { - const Transaction tx = createTx(); - - const auto encoded = tx.encode(); - EXPECT_EQ(hex(encoded), "a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000018282583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a001e848082583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a00ff9285021a000286b3031a032dcd61"); - - { - const auto decode = Cbor::Decode(encoded); - ASSERT_TRUE(decode.isValid()); - EXPECT_EQ(decode.getMapElements().size(), 4); - EXPECT_EQ(decode.dumpToString(), "{0: [[h\"f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767\", 1], [h\"554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0\", 0]], 1: [[h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 2000000], [h\"01558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5\", 16749189]], 2: 165555, 3: 53333345}"); - } -} - -TEST(CardanoTransaction, GetId) { - const Transaction tx = createTx(); - - const auto txid = tx.getId(); - EXPECT_EQ(hex(txid), "cc262713a3e15a0fa245b062f33ffc6c2aa5a64c3ae7bfa793414069914e1bbf"); -} - -TEST(CardanoTransaction, minAdaAmount) { - const auto policyId = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77"; - - { // ADA only - const auto tb = TokenBundle(); - EXPECT_EQ(tb.minAdaAmount(), 1000000); - } - { // 1 policyId, 1 6-char asset name - const auto tb = TokenBundle({TokenAmount(policyId, "TOKEN1", 0)}); - EXPECT_EQ(tb.minAdaAmount(), 1444443); - } - { // 2 policyId, 2 4-char asset names - auto tb = TokenBundle(); - tb.add(TokenAmount("012345678901234567890POLICY1", "TOK1", 20)); - tb.add(TokenAmount("012345678901234567890POLICY2", "TOK2", 20)); - EXPECT_EQ(tb.minAdaAmount(), 1629628); - } - { // 10 policyId, 10 6-char asset names - auto tb = TokenBundle(); - for (auto i = 0; i < 10; ++i) { - string policyId1 = + "012345678901234567890123456" + std::to_string(i); - string name = "ASSET" + std::to_string(i); - tb.add(TokenAmount(policyId1, name, 0)); - } - EXPECT_EQ(tb.minAdaAmount(), 3370367); - } - - EXPECT_EQ(TokenBundle::minAdaAmountHelper(0, 0, 0), 1000000); // ADA only - EXPECT_EQ(TokenBundle::minAdaAmountHelper(1, 0, 0), 1370369); // 1 policyId, no asset name - EXPECT_EQ(TokenBundle::minAdaAmountHelper(1, 1, 1), 1444443); // 1 policyId, 1 1-char asset name - EXPECT_EQ(TokenBundle::minAdaAmountHelper(1, 1, 6), 1444443); // 1 policyId, 1 6-char asset name - EXPECT_EQ(TokenBundle::minAdaAmountHelper(1, 1, 32), 1555554); // 1 policyId, 1 32-char asset name - EXPECT_EQ(TokenBundle::minAdaAmountHelper(1, 110, 110*32), 23777754); // 1 policyId, 110 32-char asset name - EXPECT_EQ(TokenBundle::minAdaAmountHelper(2, 2, 8), 1629628); // 2 policyId, 2 4-char asset names - EXPECT_EQ(TokenBundle::minAdaAmountHelper(3, 5, 20), 1999998); // 3 policyId, 5 4-char asset names - EXPECT_EQ(TokenBundle::minAdaAmountHelper(10, 10, 10*6), 3370367); // 10 policyId, 10 6-char asset names - EXPECT_EQ(TokenBundle::minAdaAmountHelper(60, 60, 60*32), 21222201); // 60 policyId, 60 32-char asset names -} - -TEST(TWCardanoTransaction, minAdaAmount) { - { // ADA-only - const auto bundleProto = TokenBundle().toProto(); - const auto bundleProtoData = data(bundleProto.SerializeAsString()); - EXPECT_EQ(TWCardanoMinAdaAmount(&bundleProtoData), 1000000); - } - { // 2 policyId, 2 4-char asset names - auto bundle = TokenBundle(); - bundle.add(TokenAmount("012345678901234567890POLICY1", "TOK1", 20)); - bundle.add(TokenAmount("012345678901234567890POLICY2", "TOK2", 20)); - const auto bundleProto = bundle.toProto(); - const auto bundleProtoData = data(bundleProto.SerializeAsString()); - EXPECT_EQ(TWCardanoMinAdaAmount(&bundleProtoData), 1629628); - } -} diff --git a/tests/CborTests.cpp b/tests/CborTests.cpp deleted file mode 100644 index d8554de02cf..00000000000 --- a/tests/CborTests.cpp +++ /dev/null @@ -1,522 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Cbor.h" - -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Cbor; -using namespace std; - - -TEST(Cbor, EncSample1) { - EXPECT_EQ( - "8205a26178186461793831", - hex(Encode::array({ - Encode::uint(5), - Encode::map({ - make_pair(Encode::string("x"), Encode::uint(100)), - make_pair(Encode::string("y"), Encode::negInt(50)), - }), - }).encoded()) - ); -} - -TEST(Cbor, EncUInt) { - EXPECT_EQ("00", hex(Encode::uint(0).encoded())); - EXPECT_EQ("01", hex(Encode::uint(1).encoded())); - EXPECT_EQ("0a", hex(Encode::uint(10).encoded())); - EXPECT_EQ("17", hex(Encode::uint(23).encoded())); - EXPECT_EQ("1818", hex(Encode::uint(24).encoded())); - EXPECT_EQ("1819", hex(Encode::uint(25).encoded())); - EXPECT_EQ("181a", hex(Encode::uint(26).encoded())); - EXPECT_EQ("181b", hex(Encode::uint(27).encoded())); - EXPECT_EQ("181c", hex(Encode::uint(28).encoded())); - EXPECT_EQ("181d", hex(Encode::uint(29).encoded())); - EXPECT_EQ("181e", hex(Encode::uint(30).encoded())); - EXPECT_EQ("181f", hex(Encode::uint(31).encoded())); - EXPECT_EQ("1820", hex(Encode::uint(32).encoded())); - EXPECT_EQ("183f", hex(Encode::uint(0x3f).encoded())); - EXPECT_EQ("1840", hex(Encode::uint(0x40).encoded())); - EXPECT_EQ("1864", hex(Encode::uint(100).encoded())); - EXPECT_EQ("187f", hex(Encode::uint(0x7f).encoded())); - EXPECT_EQ("1880", hex(Encode::uint(0x80).encoded())); - EXPECT_EQ("18ff", hex(Encode::uint(0xff).encoded())); - EXPECT_EQ("190100", hex(Encode::uint(0x0100).encoded())); - EXPECT_EQ("1903e8", hex(Encode::uint(1000).encoded())); - EXPECT_EQ("198765", hex(Encode::uint(0x8765).encoded())); - EXPECT_EQ("19ffff", hex(Encode::uint(0xffff).encoded())); - EXPECT_EQ("1a00010000", hex(Encode::uint(0x00010000).encoded())); - EXPECT_EQ("1a000f4240", hex(Encode::uint(1000000).encoded())); - EXPECT_EQ("1a00800000", hex(Encode::uint(0x00800000).encoded())); - EXPECT_EQ("1a87654321", hex(Encode::uint(0x87654321).encoded())); - EXPECT_EQ("1affffffff", hex(Encode::uint(0xffffffff).encoded())); - EXPECT_EQ("1b0000000100000000", hex(Encode::uint(0x0000000100000000).encoded())); - EXPECT_EQ("1b000000e8d4a51000", hex(Encode::uint(1000000000000).encoded())); - EXPECT_EQ("1b876543210fedcba9", hex(Encode::uint(0x876543210fedcba9).encoded())); - EXPECT_EQ("1bffffffffffffffff", hex(Encode::uint(0xffffffffffffffff).encoded())); -} - -TEST(Cbor, EncNegInt) { - EXPECT_EQ("20", hex(Encode::negInt(1).encoded())); // -1 - EXPECT_EQ("00", hex(Encode::negInt(0).encoded())); // 0 - EXPECT_EQ("21", hex(Encode::negInt(2).encoded())); - EXPECT_EQ("28", hex(Encode::negInt(9).encoded())); - EXPECT_EQ("37", hex(Encode::negInt(24).encoded())); - EXPECT_EQ("3818", hex(Encode::negInt(25).encoded())); - EXPECT_EQ("38ff", hex(Encode::negInt(0x0100).encoded())); - EXPECT_EQ("390100", hex(Encode::negInt(0x0101).encoded())); - EXPECT_EQ("39ffff", hex(Encode::negInt(0x10000).encoded())); - EXPECT_EQ("3a00010000", hex(Encode::negInt(0x00010001).encoded())); - EXPECT_EQ("3a00800000", hex(Encode::negInt(0x00800001).encoded())); - EXPECT_EQ("3a87654321", hex(Encode::negInt(0x87654322).encoded())); - EXPECT_EQ("3affffffff", hex(Encode::negInt(0x100000000).encoded())); - EXPECT_EQ("3b0000000100000000", hex(Encode::negInt(0x0000000100000001).encoded())); - EXPECT_EQ("3b876543210fedcba9", hex(Encode::negInt(0x876543210fedcbaa).encoded())); - EXPECT_EQ("3bfffffffffffffffe", hex(Encode::negInt(0xffffffffffffffff).encoded())); - - EXPECT_EQ("-9", Decode(Encode::negInt(9).encoded()).dumpToString()); -} - - -TEST(Cbor, EncString) { - EXPECT_EQ("60", hex(Encode::string("").encoded())); - EXPECT_EQ("6141", hex(Encode::string("A").encoded())); - EXPECT_EQ("656162636465", hex(Encode::string("abcde").encoded())); - Data long258(258); - EXPECT_EQ( - "590102000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - hex(Encode::bytes(long258).encoded()) - ); - - EXPECT_EQ("\"abcde\"", Decode(Encode::string("abcde").encoded()).dumpToString()); - EXPECT_EQ("h\"6162636465\"", Decode(Encode::bytes(parse_hex("6162636465")).encoded()).dumpToString()); -} - -TEST(Cbor, EncTag) { - { - Data cbor = Encode::tag(5, Encode::uint(6)).encoded(); - EXPECT_EQ("c506", hex(cbor)); - EXPECT_TRUE(Decode(cbor).isValid()); - EXPECT_EQ("tag 5 6", Decode(cbor).dumpToString()); - } - EXPECT_EQ("d94321191234", hex(Encode::tag(0x4321, Encode::uint(0x1234)).encoded())); -} - -TEST(Cbor, EncNull) { - { - Data cbor = Encode::null().encoded(); - EXPECT_EQ("f6", hex(cbor)); - EXPECT_TRUE(Decode(cbor).isValid()); - EXPECT_EQ("null", Decode(cbor).dumpToString()); - } -} - -TEST(Cbor, EncInvalid) { - Data invalid = parse_hex("5b99999999999999991234"); // invalid very looong string - EXPECT_FALSE(Decode(invalid).isValid()); - - try { - Encode::fromRaw(invalid); - } catch (exception& ex) { - return; - } - FAIL() << "Expected exception"; -} - -TEST(Cbor, DecInt) { - EXPECT_EQ(0, Decode(parse_hex("00")).getValue()); - EXPECT_EQ(1, Decode(parse_hex("01")).getValue()); - EXPECT_EQ(10, Decode(parse_hex("0a")).getValue()); - EXPECT_EQ(23, Decode(parse_hex("17")).getValue()); - EXPECT_EQ(24, Decode(parse_hex("1818")).getValue()); - EXPECT_EQ(25, Decode(parse_hex("1819")).getValue()); - EXPECT_EQ(26, Decode(parse_hex("181a")).getValue()); - EXPECT_EQ(27, Decode(parse_hex("181b")).getValue()); - EXPECT_EQ(28, Decode(parse_hex("181c")).getValue()); - EXPECT_EQ(29, Decode(parse_hex("181d")).getValue()); - EXPECT_EQ(30, Decode(parse_hex("181e")).getValue()); - EXPECT_EQ(31, Decode(parse_hex("181f")).getValue()); - EXPECT_EQ(32, Decode(parse_hex("1820")).getValue()); - EXPECT_EQ(0x3f, Decode(parse_hex("183f")).getValue()); - EXPECT_EQ(0x40, Decode(parse_hex("1840")).getValue()); - EXPECT_EQ(100, Decode(parse_hex("1864")).getValue()); - EXPECT_EQ(0x7f, Decode(parse_hex("187f")).getValue()); - EXPECT_EQ(0x80, Decode(parse_hex("1880")).getValue()); - EXPECT_EQ(0xff, Decode(parse_hex("18ff")).getValue()); - EXPECT_EQ(0x100, Decode(parse_hex("190100")).getValue()); - EXPECT_EQ(1000, Decode(parse_hex("1903e8")).getValue()); - EXPECT_EQ(0x8765, Decode(parse_hex("198765")).getValue()); - EXPECT_EQ(0xffff, Decode(parse_hex("19ffff")).getValue()); - EXPECT_EQ(0x00010000, Decode(parse_hex("1a00010000")).getValue()); - EXPECT_EQ(1000000, Decode(parse_hex("1a000f4240")).getValue()); - EXPECT_EQ(0x00800000, Decode(parse_hex("1a00800000")).getValue()); - EXPECT_EQ(0x87654321, Decode(parse_hex("1a87654321")).getValue()); - EXPECT_EQ(0xffffffff, Decode(parse_hex("1affffffff")).getValue()); - EXPECT_EQ(0x0000000100000000, Decode(parse_hex("1b0000000100000000")).getValue()); - EXPECT_EQ(1000000000000, Decode(parse_hex("1b000000e8d4a51000")).getValue()); - EXPECT_EQ(0x876543210fedcba9, Decode(parse_hex("1b876543210fedcba9")).getValue()); - EXPECT_EQ(0xffffffffffffffff, Decode(parse_hex("1bffffffffffffffff")).getValue()); -} - -TEST(Cbor, DecMinortypeInvalid) { - EXPECT_FALSE(Decode(parse_hex("1c")).isValid()); // 28 unused - EXPECT_FALSE(Decode(parse_hex("1d")).isValid()); // 29 unused - EXPECT_FALSE(Decode(parse_hex("1e")).isValid()); // 30 unused - EXPECT_TRUE(Decode(parse_hex("1b0000000000000000")).isValid()); -} - -TEST(Cbor, DecArray3) { - Decode cbor = Decode(parse_hex("83010203")); - EXPECT_EQ(3, cbor.getArrayElements().size()); -} - -TEST(Cbor, DecArrayNested) { - Data d1 = parse_hex("8301820203820405"); - Decode cbor = Decode(d1); - EXPECT_EQ(3, cbor.getArrayElements().size()); - - EXPECT_EQ(1, cbor.getArrayElements()[0].getValue()); - EXPECT_EQ(2, cbor.getArrayElements()[1].getArrayElements().size()); - EXPECT_EQ(2, cbor.getArrayElements()[1].getArrayElements()[0].getValue()); - EXPECT_EQ(3, cbor.getArrayElements()[1].getArrayElements()[1].getValue()); - EXPECT_EQ(2, cbor.getArrayElements()[2].getArrayElements().size()); - EXPECT_EQ(4, cbor.getArrayElements()[2].getArrayElements()[0].getValue()); - EXPECT_EQ(5, cbor.getArrayElements()[2].getArrayElements()[1].getValue()); -} - -TEST(Cbor, DecEncoded) { - // sometimes getting the encoded version is useful during decoding too - Decode cbor = Decode(parse_hex("8301820203820405")); - EXPECT_EQ(3, cbor.getArrayElements().size()); - EXPECT_EQ("820203", hex(cbor.getArrayElements()[1].encoded())); - EXPECT_EQ("820405", hex(cbor.getArrayElements()[2].encoded())); -} - -TEST(Cbor, DecMemoryref) { - // make sure reference to data is valid even if parent object has been destroyed - Decode* cbor = new Decode(parse_hex("828301020383010203")); - auto elems = cbor->getArrayElements(); - // delete parent - delete cbor; - // also do some new allocation - Decode* dummy = new Decode(parse_hex("5555555555555555")); - // work with the child references - EXPECT_EQ(2, elems.size()); - EXPECT_EQ(3, elems[0].getArrayElements().size()); - EXPECT_EQ(3, elems[1].getArrayElements().size()); - delete dummy; -} - -TEST(Cbor, GetValue) { - EXPECT_EQ(5, Decode(parse_hex("05")).getValue()); -} - -TEST(Cbor, GetValueInvalid) { - try { - Decode(parse_hex("83010203")).getValue(); // array - } catch (exception& ex) { - return; - } - FAIL() << "Expected exception"; -} - -TEST(Cbor, GetString) { - // bytes/string and getString/getBytes work in all combinations - EXPECT_EQ("abcde", Decode(parse_hex("656162636465")).getString()); - EXPECT_EQ("abcde", Decode(parse_hex("456162636465")).getString()); - EXPECT_EQ("6162636465", hex(Decode(parse_hex("656162636465")).getBytes())); - EXPECT_EQ("6162636465", hex(Decode(parse_hex("456162636465")).getBytes())); -} - -TEST(Cbor, GetStringInvalidType) { - try { - Decode cbor = Decode(Encode::uint(5).encoded()); - cbor.getBytes(); - } catch (exception& ex) { - return; - } - FAIL() << "Expected exception"; -} - -TEST(Cbor, GetStringInvalidTooShort) { - try { - Decode cbor = Decode(parse_hex("65616263")); // too short - cbor.getBytes(); - } catch (exception& ex) { - return; - } - FAIL() << "Expected exception"; -} - -TEST(Cbor, ArrayEmpty) { - Data cbor = Encode::array({}).encoded(); - - EXPECT_EQ("80", hex(cbor)); - EXPECT_TRUE(Decode(cbor).isValid()); - EXPECT_EQ("[]", Decode(cbor).dumpToString()); - - Decode decode(cbor); - EXPECT_EQ(0, decode.getArrayElements().size()); -} - -TEST(Cbor, Array3) { - Data cbor = Encode::array({ - Encode::uint(1), - Encode::uint(2), - Encode::uint(3), - }).encoded(); - - EXPECT_EQ("83010203", hex(cbor)); - EXPECT_TRUE(Decode(cbor).isValid()); - EXPECT_EQ("[1, 2, 3]", Decode(cbor).dumpToString()); - - Decode decode(cbor); - EXPECT_EQ(3, decode.getArrayElements().size()); - EXPECT_EQ(1, decode.getArrayElements()[0].getValue()); - EXPECT_EQ(2, decode.getArrayElements()[1].getValue()); - EXPECT_EQ(3, decode.getArrayElements()[2].getValue()); -} - -TEST(Cbor, ArrayNested) { - Data cbor = Encode::array({ - Encode::uint(1), - Encode::array({ - Encode::uint(2), - Encode::uint(3), - }), - Encode::array({ - Encode::uint(4), - Encode::uint(5), - }), - }).encoded(); - - EXPECT_EQ("8301820203820405", hex(cbor)); - EXPECT_TRUE(Decode(cbor).isValid()); - EXPECT_EQ("[1, [2, 3], [4, 5]]", - Decode(cbor).dumpToString()); - - Decode decode(cbor); - EXPECT_EQ(3, decode.getArrayElements().size()); - EXPECT_EQ(1, decode.getArrayElements()[0].getValue()); - EXPECT_EQ(2, decode.getArrayElements()[1].getArrayElements().size()); - EXPECT_EQ(2, decode.getArrayElements()[1].getArrayElements()[0].getValue()); - EXPECT_EQ(3, decode.getArrayElements()[1].getArrayElements()[1].getValue()); - EXPECT_EQ(2, decode.getArrayElements()[2].getArrayElements().size()); - EXPECT_EQ(4, decode.getArrayElements()[2].getArrayElements()[0].getValue()); - EXPECT_EQ(5, decode.getArrayElements()[2].getArrayElements()[1].getValue()); -} - -TEST(Cbor, Array25) { - auto elem = vector(); - for (int i = 1; i <= 25; ++i) { - elem.push_back(Encode::uint(i)); - } - Data cbor = Encode::array(elem).encoded(); - - EXPECT_EQ("98190102030405060708090a0b0c0d0e0f101112131415161718181819", hex(cbor)); - EXPECT_TRUE(Decode(cbor).isValid()); - EXPECT_EQ("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]", - Decode(cbor).dumpToString()); - - Decode decode(cbor); - EXPECT_EQ(25, decode.getArrayElements().size()); - for (int i = 1; i <= 25; ++i) { - EXPECT_EQ(i, decode.getArrayElements()[i - 1].getValue()); - } -} - -TEST(Cbor, MapEmpty) { - Data cbor = Encode::map({}).encoded(); - - EXPECT_EQ("a0", hex(cbor)); - EXPECT_TRUE(Decode(cbor).isValid()); - EXPECT_EQ("{}", Decode(cbor).dumpToString()); - - Decode decode(cbor); - EXPECT_EQ(0, decode.getMapElements().size()); -} - -TEST(Cbor, Map2Num) { - Data cbor = Encode::map({ - make_pair(Encode::uint(1), Encode::uint(2)), - make_pair(Encode::uint(3), Encode::uint(4)), - }).encoded(); - - EXPECT_EQ("a201020304", hex(cbor)); - EXPECT_TRUE(Decode(cbor).isValid()); - EXPECT_EQ("{1: 2, 3: 4}", Decode(cbor).dumpToString()); - - Decode decode(cbor); - EXPECT_EQ(2, decode.getMapElements().size()); - EXPECT_EQ(1, decode.getMapElements()[0].first.getValue()); - EXPECT_EQ(2, decode.getMapElements()[0].second.getValue()); -} - -TEST(Cbor, Map2WithArr) { - Data cbor = Encode::map({ - make_pair(Encode::string("a"), Encode::uint(1)), - make_pair(Encode::string("b"), Encode::array({ - Encode::uint(2), - Encode::uint(3), - })), - }).encoded(); - - EXPECT_EQ("a26161016162820203", hex(cbor)); - EXPECT_TRUE(Decode(cbor).isValid()); - EXPECT_EQ("{\"a\": 1, \"b\": [2, 3]}", - Decode(cbor).dumpToString()); - - Decode decode(cbor); - EXPECT_EQ(2, decode.getMapElements().size()); - EXPECT_EQ("a", decode.getMapElements()[0].first.getString()); - EXPECT_EQ(1, decode.getMapElements()[0].second.getValue()); - EXPECT_EQ("b", decode.getMapElements()[1].first.getString()); - EXPECT_EQ(2, decode.getMapElements()[1].second.getArrayElements().size()); - EXPECT_EQ(2, decode.getMapElements()[1].second.getArrayElements()[0].getValue()); - EXPECT_EQ(3, decode.getMapElements()[1].second.getArrayElements()[1].getValue()); -} - -TEST(Cbor, MapNested) { - Data cbor = Encode::map({ - make_pair(Encode::string("a"), Encode::map({ - make_pair(Encode::string("b"), Encode::string("c")), - })), - }).encoded(); - - EXPECT_EQ("a16161a161626163", hex(cbor)); - EXPECT_TRUE(Decode(cbor).isValid()); - EXPECT_EQ("{\"a\": {\"b\": \"c\"}}", - Decode(cbor).dumpToString()); - - Decode decode(cbor); - EXPECT_EQ(1, decode.getMapElements().size()); - EXPECT_EQ("a", decode.getMapElements()[0].first.getString()); - EXPECT_EQ(1, decode.getMapElements()[0].second.getMapElements().size()); - EXPECT_EQ("b", decode.getMapElements()[0].second.getMapElements()[0].first.getString()); - EXPECT_EQ("c", decode.getMapElements()[0].second.getMapElements()[0].second.getString()); -} - -TEST(Cbor, MapIndef) { - Decode cbor = Decode(parse_hex("bf01020304ff")); - EXPECT_EQ("{_ 1: 2, 3: 4}", cbor.dumpToString()); - EXPECT_EQ(2, cbor.getMapElements().size()); - EXPECT_EQ(1, cbor.getMapElements()[0].first.getValue()); - EXPECT_EQ(2, cbor.getMapElements()[0].second.getValue()); -} - -TEST(Cbor, MapIsValidInvalidTooShort) { - { - Decode cbor = Decode(parse_hex("a301020304")); // too short - EXPECT_FALSE(cbor.isValid()); - } - { - Decode cbor = Decode(parse_hex("a3010203")); // too short, partial element - EXPECT_FALSE(cbor.isValid()); - } -} - -TEST(Cbor, MapGetInvalidTooShort1) { - try { - Decode cbor = Decode(parse_hex("a301020304")); // too short - auto elems = cbor.getMapElements(); - } catch (exception& ex) { - return; - } - FAIL() << "Expected exception"; -} - -TEST(Cbor, MapGetInvalidTooShort2) { - try { - Decode cbor = Decode(parse_hex("a3010203")); // too short, partial element - auto elems = cbor.getMapElements(); - } catch (exception& ex) { - return; - } - FAIL() << "Expected exception"; -} - -TEST(Cbor, ArrayIndef) { - Data cbor = Encode::indefArray() - .addIndefArrayElem(Encode::uint(1)) - .addIndefArrayElem(Encode::uint(2)) - .closeIndefArray() - .encoded(); - - EXPECT_EQ("9f0102ff", hex(cbor)); - EXPECT_TRUE(Decode(cbor).isValid()); - EXPECT_EQ("[_ 1, 2]", - Decode(cbor).dumpToString()); - - Decode decode(cbor); - EXPECT_EQ(2, decode.getArrayElements().size()); - EXPECT_EQ(1, decode.getArrayElements()[0].getValue()); - EXPECT_EQ(2, decode.getArrayElements()[1].getValue()); - - EXPECT_EQ("[_ 1, 2]", Decode(parse_hex("9f0102ff")).dumpToString()); - EXPECT_EQ("", Decode(parse_hex("ff")).dumpToString()); - EXPECT_EQ("spec 1", Decode(parse_hex("e1")).dumpToString()); -} - -TEST(Cbor, ArrayInfefErrorAddNostart) { - try { - Data cbor = Encode::uint(0).addIndefArrayElem(Encode::uint(1)).encoded(); - } catch (exception& ex) { - return; - } - FAIL() << "Expected exception"; -} - -TEST(Cbor, ArrayInfefErrorCloseNostart) { - try { - Data cbor = Encode::uint(0).closeIndefArray().encoded(); - } catch (exception& ex) { - return; - } - FAIL() << "Expected exception"; -} - -TEST(Cbor, ArrayInfefErrorResultNoclose) { - try { - Data cbor = Encode::indefArray() - .addIndefArrayElem(Encode::uint(1)) - .addIndefArrayElem(Encode::uint(2)) - // close is missing, break command not written - .encoded(); - } catch (exception& ex) { - return; - } - FAIL() << "Expected exception"; -} - -TEST(Cbor, ArrayInfefErrorNoBreak) { - EXPECT_TRUE(Decode(parse_hex("9f0102ff")).isValid()); - // without break it's invalid - EXPECT_FALSE(Decode(parse_hex("9f0102")).isValid()); -} - -TEST(Cbor, GetTagValueNotTag) { - try { - Decode cbor = Decode(Encode::string("abc").encoded()); - cbor.getTagValue(); - } catch (exception& ex) { - return; - } - FAIL() << "Expected exception"; -} - -TEST(Cbor, GetTagElementNotTag) { - try { - Decode cbor = Decode(Encode::string("abc").encoded()); - Decode tagElement = cbor.getTagElement(); - } catch (exception& ex) { - return; - } - FAIL() << "Expected exception"; -} diff --git a/tests/Celo/TWCoinTypeTests.cpp b/tests/Celo/TWCoinTypeTests.cpp deleted file mode 100644 index 7a66fc66eda..00000000000 --- a/tests/Celo/TWCoinTypeTests.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWCeloCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeCelo)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xaf41ee58afe633dc7b179c15693cca40fe0372c1d7c70351620105d59d326693")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeCelo, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xFBFf95e2Fa7e4Ff3aeA34eFB05fB60F9968a6aaD")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeCelo, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeCelo)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeCelo)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeCelo), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeCelo)); - - assertStringsEqual(symbol, "CELO"); - assertStringsEqual(txUrl, "https://explorer.celo.org/tx/0xaf41ee58afe633dc7b179c15693cca40fe0372c1d7c70351620105d59d326693"); - assertStringsEqual(accUrl, "https://explorer.celo.org/address/0xFBFf95e2Fa7e4Ff3aeA34eFB05fB60F9968a6aaD"); - assertStringsEqual(id, "celo"); - assertStringsEqual(name, "Celo"); -} diff --git a/tests/CoinAddressDerivationTests.cpp b/tests/CoinAddressDerivationTests.cpp deleted file mode 100644 index 319f0926431..00000000000 --- a/tests/CoinAddressDerivationTests.cpp +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Coin.h" -#include "HexCoding.h" - -#include - -#include -#include - -namespace TW { - -TEST(Coin, DeriveAddress) { - auto dummyKeyData = parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); - const auto privateKey = PrivateKey(dummyKeyData); - const auto privateKeyExt = PrivateKey(dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData); - - EXPECT_EQ(TW::deriveAddress(TWCoinTypeAion, privateKey), "0xa0010b0ea04ba4d76ca6e5e9900bacf19bc4402eaec7e36ea7ddd8eed48f60f3"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeBinance, privateKey), "bnb1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0mlq0d0"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeBitcoin, privateKey), "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeBitcoinCash, privateKey), "bitcoincash:qz7eyzytkl5z6cg6nw20hd62pyyp22mcfuardfd2vn"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeCallisto, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeCosmos, privateKey), "cosmos1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0emlrvp"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeDash, privateKey), "XsyCV5yojxF4y3bYeEiVYqarvRgsWFELZL"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeDecred, privateKey), "Dsp4u8xxTHSZU2ELWTQLQP77xJhgeWrTsGK"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeDigiByte, privateKey), "dgb1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0c69ssz"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeECash, privateKey), "ecash:qz7eyzytkl5z6cg6nw20hd62pyyp22mcfuywezks2y"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeEthereum, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeEthereumClassic, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeGoChain, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeGroestlcoin, privateKey), "grs1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0jsaf3d"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeICON, privateKey), "hx4728fc65c31728f0d3538b8783b5394b31a136b9"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeIoTeX, privateKey), "io1nk9x9ajk4rgkzhqjjn7hr6w0k0jg2kj0zgdt6h"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeLitecoin, privateKey), "ltc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0tamvsu"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeViacoin, privateKey), "via1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z09y9mn2"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeNimiq, privateKey), "NQ74 D40G N3M0 9EJD ET56 UPLR 02VC X6DU 8G1E"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeOntology, privateKey), "AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypePOANetwork, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeXRP, privateKey), "rJHMeqKu8Ep7Fazx8MQG6JunaafBXz93YQ"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeStellar, privateKey), "GDXJHJHWN6GRNOAZXON6XH74ZX6NYFAS5B7642RSJQVJTIPA4ZYUQLEB"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeTezos, privateKey), "tz1gcEWswVU6dxfNQWbhTgaZrUrNUFwrsT4z"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeThunderToken, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeTomoChain, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeTron, privateKey), "TQLCsShbQNXMTVCjprY64qZmEA4rBarpQp"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeVeChain, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeWanchain, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeZcash, privateKey), "t1b9xfAk3kZp5Qk3rinDPq7zzLkJGHTChDS"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeFiro, privateKey), "aHzpPjmY132KseS4nkiQTbDahTEXqesY89"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeNano, privateKey), "nano_1qepdf4k95dhb5gsmhmq3iddqsxiafwkihunm7irn48jdiwdtnn6pe93k3f6"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeKin, privateKey), "GDXJHJHWN6GRNOAZXON6XH74ZX6NYFAS5B7642RSJQVJTIPA4ZYUQLEB"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeTheta, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeQtum, privateKey), "QdtLm8ccxhuJFF5zCgikpaghbM3thdaGsW"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeNULS, privateKey), "NULSd6HgfXT3m5JBGxeCZXHRQbb82FKgZGT8o"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeEOS, privateKey), "EOS5TrYnZP1RkDSUMzBY4GanCy6AP68kCMdkAb5EACkAwkdgRLShz"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeIoTeX, privateKey), "io1nk9x9ajk4rgkzhqjjn7hr6w0k0jg2kj0zgdt6h"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeDogecoin, privateKey), "DNRTC6GZ5evmM7BZWwPqF54fyDqUqULMyu"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeZilliqa, privateKey), "zil1j2cvtd7j9n7fnxfv2r3neucjw8tp4xz9sp07v4"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeRavencoin, privateKey), "RSZYjMDCP4q3t7NAFXPPnqEGrMZn971pdB"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeAeternity, privateKey), "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeTerra, privateKey), "terra1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0ll9rwp"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeNebulas, privateKey), "n1XTciu9ZRYt3ni7SxNBmivk9Y6XpP6VrhT"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeMonacoin, privateKey), "MRBWtGEKHGCHhmyJ1L4CwaWQZJzM5DnVcs"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeFIO, privateKey), "FIO5TrYnZP1RkDSUMzBY4GanCy6AP68kCMdkAb5EACkAwkdgRLShz"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeAlgorand, privateKey), "52J2J5TPRULLQGN3TPVZ77GN7TOBIEXIP7XGUMSMFKM2DYHGOFEOGBP2T4"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeKusama, privateKey), "Hy8mqcexg5FMwMYnQvzrUvD723qMxDjMRU9HdNCnTsMAypY"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypePolkadot, privateKey), "16PpFrXrC6Ko3pYcyMAx6gPMp3mFFaxgyYMt4G5brkgNcSz8"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeKava, privateKey), "kava1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z09wt76x"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeCardano, privateKeyExt), "addr1qxzk4wqhh5qmzas4e26aghcvkz8feju6sa43nghfj5xxsly9d2up00gpk9mptj44630sevywnn9e4pmtrx3wn9gvdp7qjhvjl4"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeNEO, privateKeyExt), "AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeFilecoin, privateKey), "f1qsx7qwiojh5duxbxhbqgnlyx5hmpcf7mcz5oxsy"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeNEAR, privateKey), "ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeSolana, privateKey), "H4JcMPicKkHcxxDjkyyrLoQj7Kcibd9t815ak4UvTr9M"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeElrond, privateKey), "erd1a6f6fan035ttsxdmn04ellxdlnwpgyhg0lhx5vjv92v6rc8xw9yq83344f"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeOasis, privateKey), "oasis1qzw4h3wmyjtrttduqqrs8udggyy2emwdzqmuzwg4"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeTHORChain, privateKey), "thor1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0luxce7"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeAvalancheCChain, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeXDai, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeCelo, privateKey), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - EXPECT_EQ(TW::deriveAddress(TWCoinTypeRonin, privateKey), "ronin:9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); -} - -int countThreadReady = 0; -std::mutex countThreadReadyMutex; - -void useCoinFromThread() { - const int tryCount = 20; - for (int i = 0; i < tryCount; ++i) { - // perform some operations - TW::validateAddress(TWCoinTypeZilliqa, "zil1j8xae6lggm8y63m3y2r7aefu797ze7mhzulnqg"); - TW::validateAddress(TWCoinTypeEthereum, "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); - const auto coinTypes = TW::getCoinTypes(); - } - countThreadReadyMutex.lock(); - ++countThreadReady; - countThreadReadyMutex.unlock(); -} - -TEST(Coin, InitMultithread) { - const int numThread = 20; - countThreadReady = 0; - std::thread thread[numThread]; - // execute in threads - for (int i = 0; i < numThread; ++i) { - thread[i] = std::thread(useCoinFromThread); - } - // wait for completion - for (int i = 0; i < numThread; ++i) { - thread[i].join(); - } - // check that all completed OK - ASSERT_EQ(countThreadReady, numThread); -} - -} // namespace TW diff --git a/tests/Cosmos/AddressTests.cpp b/tests/Cosmos/AddressTests.cpp deleted file mode 100644 index bcb2012b58e..00000000000 --- a/tests/Cosmos/AddressTests.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include "Cosmos/Address.h" - -#include -#include - -namespace TW::Cosmos { - -TEST(CosmosAddress, Valid) { - ASSERT_TRUE(Address::isValid(TWCoinTypeBinance, "bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2")); -} - -TEST(CosmosAddress, Invalid) { - ASSERT_FALSE(Address::isValid(TWCoinTypeBinance, "bnb1grpf0955h0ykzq3ar6nmum7y6gdfl6lxfn46h2")); -} - -TEST(CosmosAddress, Cosmos_FromPublicKey) { - auto privateKey = PrivateKey(parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005")); - auto publicKeyData = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - ASSERT_EQ(hex(publicKeyData.bytes.begin(), publicKeyData.bytes.end()), "0257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc5"); - - auto publicKey = PublicKey(publicKeyData); - auto address = Address("cosmos", publicKey); - ASSERT_EQ(address.string(), "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); - ASSERT_EQ(hex(address.getKeyHash()), "bc2da90c84049370d1b7c528bc164bc588833f21"); -} - -TEST(CosmosAddress, Cosmos_FromString) { - Address addr; - ASSERT_TRUE(Address::decode("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", addr)); - ASSERT_EQ(addr.string(), "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); -} - -TEST(CosmosAddress, Cosmos_Valid) { - ASSERT_TRUE(Address::isValid(TWCoinTypeCosmos, "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")); - ASSERT_TRUE(Address::isValid(TWCoinTypeCosmos, "cosmospub1addwnpepqftjsmkr7d7nx4tmhw4qqze8w39vjq364xt8etn45xqarlu3l2wu2n7pgrq")); - ASSERT_TRUE(Address::isValid(TWCoinTypeCosmos, "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08")); - ASSERT_TRUE(Address::isValid(TWCoinTypeCosmos, "cosmosvalconspub1zcjduepqjnnwe2jsywv0kfc97pz04zkm7tc9k2437cde2my3y5js9t7cw9mstfg3sa")); -} - -TEST(CosmosAddress, Cosmos_Invalid) { - ASSERT_FALSE(Address::isValid(TWCoinTypeCosmos, "cosmos1xsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")); - ASSERT_FALSE(Address::isValid(TWCoinTypeCosmos, "cosmospub1xddwnpepqftjsmkr7d7nx4tmhw4qqze8w39vjq364xt8etn45xqarlu3l2wu2n7pgrq")); - ASSERT_FALSE(Address::isValid(TWCoinTypeCosmos, "cosmosvaloper1xxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08")); - ASSERT_FALSE(Address::isValid(TWCoinTypeCosmos, "cosmosvalconspub1xcjduepqjnnwe2jsywv0kfc97pz04zkm7tc9k2437cde2my3y5js9t7cw9mstfg3sa")); -} - -TEST(CosmosAddress, ThorFromPublicKey) { - auto privateKey = PrivateKey(parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e")); - auto publicKeyData = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - ASSERT_EQ(hex(publicKeyData.bytes.begin(), publicKeyData.bytes.end()), "03ed997e396cf4292f5fce5a42bba41599ccd5d96e313154a7c9ea7049de317c77"); - - auto publicKey = PublicKey(publicKeyData); - auto address = Address("thor", publicKey); - ASSERT_EQ(address.string(), "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r"); - ASSERT_EQ(hex(address.getKeyHash()), "1522e767db6eb19708b0038029bfbd607bc9bd0e"); -} - -TEST(CosmosAddress, ThorValid) { - ASSERT_TRUE(Address::isValid(TWCoinTypeTHORChain, "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r")); - ASSERT_FALSE(Address::isValid(TWCoinTypeTHORChain, "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2s")); -} - -} // namespace TW::Cosmos diff --git a/tests/Cosmos/ProtobufTests.cpp b/tests/Cosmos/ProtobufTests.cpp deleted file mode 100644 index 9066f6c1394..00000000000 --- a/tests/Cosmos/ProtobufTests.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Cosmos/Address.h" -#include "Cosmos/Protobuf/bank_tx.pb.h" -#include "Cosmos/Protobuf/tx.pb.h" -#include "Data.h" -#include "HexCoding.h" -#include "Base64.h" - -#include "Protobuf/Article.pb.h" -#include "../interface/TWTestUtilities.h" - -#include -#include - -#include - -using namespace TW::Cosmos; -using namespace TW; -using json = nlohmann::json; - - -TEST(CosmosProtobuf, SendMsg) { - auto msgSend = cosmos::bank::v1beta1::MsgSend(); - msgSend.set_from_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); - msgSend.set_to_address("cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"); - auto coin = msgSend.add_amount(); - coin->set_denom("muon"); - coin->set_amount("1"); - - auto txBody = cosmos::TxBody(); - txBody.add_messages()->PackFrom(msgSend); - txBody.set_memo(""); - txBody.set_timeout_height(0); - - const auto serialized = data(txBody.SerializeAsString()); - EXPECT_EQ(hex(serialized), "0a9c010a2f747970652e676f6f676c65617069732e636f6d2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a090a046d756f6e120131"); - EXPECT_EQ(Base64::encode(serialized), "CpwBCi90eXBlLmdvb2dsZWFwaXMuY29tL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZBJpCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISLWNvc21vczF6dDUwYXp1cGFucWxmYW01YWZodjNoZXh3eXV0bnVrZWg0YzU3MxoJCgRtdW9uEgEx"); - - std::string json; - google::protobuf::util::MessageToJsonString(txBody, &json); - assertJSONEqual(json, R"({"messages":[{"@type":"type.googleapis.com/cosmos.bank.v1beta1.MsgSend","amount":[{"amount":"1","denom":"muon"}],"fromAddress":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","toAddress":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}]})"); -} - -TEST(CosmosProtobuf, DeterministicSerialization_Article) { - // https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-027-deterministic-protobuf-serialization.md - auto article = blog::Article(); - article.set_title("The world needs change 🌳"); - article.set_description(""); - article.set_created(1596806111080); - article.set_updated(0); - article.set_public_(true); - article.set_promoted(false); - article.set_type(blog::NEWS); - article.set_review(blog::REVIEW_UNSPECIFIED); - *article.add_comments() = "Nice one"; - *article.add_comments() = "Thank you"; - - const auto serialized = data(article.SerializeAsString()); - EXPECT_EQ(hex(serialized), "0a1b54686520776f726c64206e65656473206368616e676520f09f8cb318e8bebec8bc2e280138024a084e696365206f6e654a095468616e6b20796f75"); -} diff --git a/tests/Cosmos/SignerTests.cpp b/tests/Cosmos/SignerTests.cpp deleted file mode 100644 index 06c4e5bc0e1..00000000000 --- a/tests/Cosmos/SignerTests.cpp +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Coin.h" -#include "HexCoding.h" -#include "Base64.h" -#include "proto/Cosmos.pb.h" -#include "Cosmos/Address.h" -#include "Cosmos/Signer.h" -#include "../interface/TWTestUtilities.h" - -#include -#include - -using namespace TW; -using namespace TW::Cosmos; - - -TEST(CosmosSigner, SignTxProtobuf) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::Protobuf); - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(8); - - auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); - auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_send_coins_message(); - message.set_from_address(fromAddress.string()); - message.set_to_address(toAddress.string()); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("muon"); - amountOfTx->set_amount("1"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount("200"); - - std::string json; - google::protobuf::util::MessageToJsonString(input, &json); - - EXPECT_EQ(R"({"signingMode":"Protobuf","accountNumber":"1037","chainId":"gaia-13003","fee":{"amounts":[{"denom":"muon","amount":"200"}],"gas":"200000"},"sequence":"8","messages":[{"sendCoinsMessage":{"fromAddress":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","toAddress":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573","amounts":[{"denom":"muon","amount":"1"}]}}]})", json); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeCosmos); - - assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); - EXPECT_EQ(hex(output.signature()), "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47"); - EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); -} - -TEST(CosmosSigner, SignProtobuf_ErrorMissingMessage) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::Protobuf); - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(8); - - auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); - auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount("200"); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeCosmos); - - EXPECT_EQ(output.error(), "Error: No message found"); - EXPECT_EQ(output.serialized(), ""); - EXPECT_EQ(output.json(), ""); - EXPECT_EQ(hex(output.signature()), ""); -} - -TEST(CosmosSigner, SignTxJson) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::JSON); // obsolete - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(8); - - auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); - auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_send_coins_message(); - message.set_from_address(fromAddress.string()); - message.set_to_address(toAddress.string()); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("muon"); - amountOfTx->set_amount("1"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount("200"); - - std::string json; - google::protobuf::util::MessageToJsonString(input, &json); - - EXPECT_EQ(R"({"accountNumber":"1037","chainId":"gaia-13003","fee":{"amounts":[{"denom":"muon","amount":"200"}],"gas":"200000"},"sequence":"8","messages":[{"sendCoinsMessage":{"fromAddress":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","toAddress":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573","amounts":[{"denom":"muon","amount":"1"}]}}]})", json); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeCosmos); - - // the sample tx on testnet - // https://hubble.figment.network/chains/gaia-13003/blocks/142933/transactions/3A9206598C3D2E75A5EC074FD33EA53EB18EC729357F0965971C1C51F812AEA3?format=json - EXPECT_EQ(R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); - - EXPECT_EQ(hex(output.signature()), "fc3ef899d206c88077fec42f21ba0b4df4bd3fd115fdf606ae01d9136fef363f57e9e33a7b9ec6ddab658cd07e3c0067470de94e4e75b979a1085a29f0efd926"); - EXPECT_EQ(output.serialized(), ""); - EXPECT_EQ(output.error(), ""); -} - -TEST(CosmosSigner, SignTxJson_WithMode) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::JSON); // obsolete - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(8); - input.set_mode(Proto::BroadcastMode::ASYNC); - - auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); - auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_send_coins_message(); - message.set_from_address(fromAddress.string()); - message.set_to_address(toAddress.string()); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("muon"); - amountOfTx->set_amount("1"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount("200"); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - { - auto output = Signer::sign(input, TWCoinTypeCosmos); - EXPECT_EQ(R"({"mode":"async","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); - EXPECT_EQ(output.error(), ""); - } - input.set_mode(Proto::BroadcastMode::SYNC); - { - auto output = Signer::sign(input, TWCoinTypeCosmos); - EXPECT_EQ(R"({"mode":"sync","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); - EXPECT_EQ(output.error(), ""); - } -} - -TEST(CosmosSigner, SignIbcTransferProtobuf_817101) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::Protobuf); - input.set_account_number(546179); - input.set_chain_id("cosmoshub-4"); - input.set_sequence(2); - - Address fromAddress; - EXPECT_TRUE(Address::decode("cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx", fromAddress)); - Address toAddress; - EXPECT_TRUE(Address::decode("osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn", toAddress)); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_transfer_tokens_message(); - message.set_source_port("transfer"); - message.set_source_channel("channel-141"); - message.set_sender(fromAddress.string()); - message.set_receiver(toAddress.string()); - message.mutable_token()->set_denom("uatom"); - message.mutable_token()->set_amount("100000"); // 0.1 ATOM - message.mutable_timeout_height()->set_revision_number(1); - message.mutable_timeout_height()->set_revision_height(8800000); - - auto& fee = *input.mutable_fee(); - fee.set_gas(500000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("uatom"); - amountOfFee->set_amount("12500"); - - auto privateKey = parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"); - EXPECT_EQ(Cosmos::Address(TWCoinTypeCosmos, PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeCosmos); - - // real-world tx: https://www.mintscan.io/cosmos/txs/817101F3D96314AD028733248B28BAFAD535024D7D2C8875D3FE31DC159F096B - // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Cr4BCr...1yKOU=", "mode": "BROADCAST_MODE_BLOCK"}' https://api.cosmos.network/cosmos/tx/v1beta1/txs - // also similar TX: BCDAC36B605576C8182C2829C808B30A69CAD4959D5ED1E6FF9984ABF280D603 - assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"Cr4BCrsBCikvaWJjLmFwcGxpY2F0aW9ucy50cmFuc2Zlci52MS5Nc2dUcmFuc2ZlchKNAQoIdHJhbnNmZXISC2NoYW5uZWwtMTQxGg8KBXVhdG9tEgYxMDAwMDAiLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseCorb3NtbzE4czBoZG5zbGxnY2Nsd2V1OWF5bXc0bmdrdHIyazBya3ZuN2ptbjIHCAEQgI6ZBBJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEC7O9c5DejAsZ/lUaN5LMfNukR9GfX5qUrQcHhPh1WNkkSBAoCCAEYAhIUCg4KBXVhdG9tEgUxMjUwMBCgwh4aQK0HIWdFMk+C6Gi1KG/vELe1ffcc1aEWUIqz2t/ZhwqNNHxUUSp27wteiugHEMVTEIOBhs84t2gIcT/nD/1yKOU=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); - EXPECT_EQ(hex(output.signature()), "ad07216745324f82e868b5286fef10b7b57df71cd5a116508ab3dadfd9870a8d347c54512a76ef0b5e8ae80710c55310838186cf38b76808713fe70ffd7228e5"); - EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); -} diff --git a/tests/Cosmos/StakingTests.cpp b/tests/Cosmos/StakingTests.cpp deleted file mode 100644 index e80358816b9..00000000000 --- a/tests/Cosmos/StakingTests.cpp +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Coin.h" -#include "HexCoding.h" -#include "Base64.h" -#include "proto/Cosmos.pb.h" -#include "Cosmos/Address.h" -#include "Cosmos/Signer.h" -#include "../interface/TWTestUtilities.h" - -#include - -using namespace TW; -using namespace TW::Cosmos; - -TEST(CosmosStaking, Staking) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::Protobuf); - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(7); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_stake_message(); - message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); - message.set_validator_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); - auto& amountOfTx = *message.mutable_amount(); - amountOfTx.set_denom("muon"); - amountOfTx.set_amount("10"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(101721); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount("1018"); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeCosmos); - - assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CpsBCpgBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJxCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3AaCgoEbXVvbhICMTASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpA8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA==\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); - EXPECT_EQ(hex(output.signature()), "f0ef499bf90be996b6237a680ece6fa4ca3060980dbd808905153fbf1023b3494d658b2ae34aa94dbc0e4db3918c903952343a6ae738d2feae0854f8ab8cfeb8"); - EXPECT_EQ(output.error(), ""); - - { // Json-serialization, for coverage (to be removed later) - input.set_signing_mode(Proto::JSON); - auto output = Signer::sign(input, TWCoinTypeCosmos); - ASSERT_EQ(hex(output.signature()), "c08bdf6c2b0b4428f37975e85d329f1cb19745b000994a743b5df81d57d573aa5f755349befcc848c1d1507818723b1288594bc91df685e89aff22e0303b4861"); - EXPECT_EQ(output.error(), ""); - EXPECT_EQ(hex(output.serialized()), ""); - } -} - -TEST(CosmosStaking, Unstaking) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::Protobuf); - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(7); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_unstake_message(); - message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); - message.set_validator_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); - auto& amountOfTx = *message.mutable_amount(); - amountOfTx.set_denom("muon"); - amountOfTx.set_amount("10"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(101721); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount("1018"); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeCosmos); - - assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"Cp0BCpoBCiUvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dVbmRlbGVnYXRlEnEKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBoKCgRtdW9uEgIxMBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYBxISCgwKBG11b24SBDEwMTgQ2ZoGGkBhlxHFnjBERxLtjLbMCKXcrDctaSZ9djtWCa3ely1bpV6m+6aAFjpr8aEZH+q2AtjJSEdgpQRJxP+9/gQsRTnZ\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); - EXPECT_EQ(hex(output.signature()), "619711c59e30444712ed8cb6cc08a5dcac372d69267d763b5609adde972d5ba55ea6fba680163a6bf1a1191feab602d8c9484760a50449c4ffbdfe042c4539d9"); - EXPECT_EQ(output.error(), ""); - - { // Json-serialization, for coverage (to be removed later) - input.set_signing_mode(Proto::JSON); - auto output = Signer::sign(input, TWCoinTypeCosmos); - ASSERT_EQ(hex(output.signature()), "8f85a9515a211881daebfb346c2beeca3ab5c2d406a9b3ad402cfddaa3d08e2b13378e13cfef8ecf1d6500fe85d0ce3e793034dd77aba90f216427807cbff79f"); - EXPECT_EQ(output.error(), ""); - EXPECT_EQ(hex(output.serialized()), ""); - } -} - -TEST(CosmosStaking, Restaking) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::Protobuf); - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(7); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_restake_message(); - message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); - message.set_validator_dst_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); - message.set_validator_src_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); - - auto& amountOfTx = *message.mutable_amount(); - amountOfTx.set_denom("muon"); - amountOfTx.set_amount("10"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(101721); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount("1018"); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeCosmos); - - assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CtIBCs8BCiovY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dCZWdpblJlZGVsZWdhdGUSoAEKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBotY29zbW9zMWhzazZqcnl5cWpmaHA1ZGhjNTV0YzlqdGNreWd4MGVwaDZkZDAyIgoKBG11b24SAjEwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQJXKG7D830zVXu7qgALJ3RKyQI6qZZ8rnWhgdH/kfqdxRIECgIIARgHEhIKDAoEbXVvbhIEMTAxOBDZmgYaQJ52qO5xdtBkNUeFeWrnqUXkngyHFKCXnOPPClyVI0HrULdp5jbwGra2RujEOn4BrbFCb3JFnpc2o1iuLXbKQxg=\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); - EXPECT_EQ(hex(output.signature()), "9e76a8ee7176d064354785796ae7a945e49e0c8714a0979ce3cf0a5c952341eb50b769e636f01ab6b646e8c43a7e01adb1426f72459e9736a358ae2d76ca4318"); - EXPECT_EQ(output.error(), ""); - - { // Json-serialization, for coverage (to be removed later) - input.set_signing_mode(Proto::JSON); - auto output = Signer::sign(input, TWCoinTypeCosmos); - ASSERT_EQ(hex(output.signature()), "e64d3761bd25a28befcda80c0a0e208d024fdb0a2b89955170e65a5c5d454aba2ce81d57e01f0c126de5a59c2b58124c109560c9803d65a17a14b548dd6c50db"); - EXPECT_EQ(output.error(), ""); - EXPECT_EQ(hex(output.serialized()), ""); - } -} - -TEST(CosmosStaking, Withdraw) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::Protobuf); - input.set_account_number(1037); - input.set_chain_id("gaia-13003"); - input.set_memo(""); - input.set_sequence(7); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_withdraw_stake_reward_message(); - message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); - message.set_validator_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(101721); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("muon"); - amountOfFee->set_amount("1018"); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeCosmos); - - assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CqMBCqABCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmUKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYBxISCgwKBG11b24SBDEwMTgQ2ZoGGkBW1Cd+0pNfMPEVXQtqG1VIijDjZP2UOiDlvUF478axnxlF8PaOAsY0S5OdUE3Wz7+nu8YVmrLZQS/8mlqLaK05\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); - EXPECT_EQ(hex(output.signature()), "56d4277ed2935f30f1155d0b6a1b55488a30e364fd943a20e5bd4178efc6b19f1945f0f68e02c6344b939d504dd6cfbfa7bbc6159ab2d9412ffc9a5a8b68ad39"); - EXPECT_EQ(output.error(), ""); - - { // Json-serialization, for coverage (to be removed later) - input.set_signing_mode(Proto::JSON); - auto output = Signer::sign(input, TWCoinTypeCosmos); - ASSERT_EQ(hex(output.signature()), "546f0d67356f6af94cfb5ab22b974e499c33123f2c2c292f4f0e64878e0e728f4643105fd771550beb3f2371f08880aaa38fa8f2334c103a779f1d82d2db98d6"); - EXPECT_EQ(output.error(), ""); - EXPECT_EQ(hex(output.serialized()), ""); - } -} diff --git a/tests/Cosmos/TWAnyAddressTests.cpp b/tests/Cosmos/TWAnyAddressTests.cpp deleted file mode 100644 index 5605e54aef0..00000000000 --- a/tests/Cosmos/TWAnyAddressTests.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" - -#include -#include - -#include - -TEST(CosmosAnyAddress, Cosmos) { - auto string = STRING("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); - auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeCosmos)); - auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); - EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); - auto keyHash = WRAPD(TWAnyAddressData(addr.get())); - assertHexEqual(keyHash, "bc2da90c84049370d1b7c528bc164bc588833f21"); -} diff --git a/tests/Cosmos/TWAnySignerTests.cpp b/tests/Cosmos/TWAnySignerTests.cpp deleted file mode 100644 index eef12c6e02c..00000000000 --- a/tests/Cosmos/TWAnySignerTests.cpp +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Cosmos/Address.h" -#include "HexCoding.h" -#include "proto/Cosmos.pb.h" -#include "../interface/TWTestUtilities.h" -#include -#include - -using namespace TW; -using namespace TW::Cosmos; - -TEST(TWAnySignerCosmos, SignTx) { - auto privateKey = parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"); - Proto::SigningInput input; - input.set_signing_mode(Proto::Protobuf); - input.set_account_number(546179); - input.set_chain_id("cosmoshub-4"); - input.set_memo(""); - input.set_sequence(0); - input.set_private_key(privateKey.data(), privateKey.size()); - - Address fromAddress; - EXPECT_TRUE(Address::decode("cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx", fromAddress)); - Address toAddress; - EXPECT_TRUE(Address::decode("cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp", toAddress)); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_send_coins_message(); - message.set_from_address(fromAddress.string()); - message.set_to_address(toAddress.string()); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("uatom"); - amountOfTx->set_amount("400000"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("uatom"); - amountOfFee->set_amount("1000"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeCosmos); - - // https://www.mintscan.io/cosmos/txs/85392373F54577562067030BF0D61596C91188AA5E6CA8FFE731BD0349296411 - // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "CpIBC...JXoCX", "mode": "BROADCAST_MODE_BLOCK"}' https://api.cosmos.network/cosmos/tx/v1beta1/txs - assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); - EXPECT_EQ(hex(output.signature()), "afbd513a776f4fdf470ef7f9675f21ae9d630fc4d635d8dbaa0dc0a716434cd07e02510765d4673dfa880825bae8e67cb367396ff6b976fc6b19a31fc95e8097"); - EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); -} - -TEST(TWAnySignerCosmos, SignJSON) { - auto json = STRING(R"({"accountNumber":"8733","chainId":"cosmoshub-2","fee":{"amounts":[{"denom":"uatom","amount":"5000"}],"gas":"200000"}, "memo":"Testing", "messages":[{"sendCoinsMessage":{"fromAddress":"cosmos1ufwv9ymhqaal6xz47n0jhzm2wf4empfqvjy575","toAddress":"cosmos135qla4294zxarqhhgxsx0sw56yssa3z0f78pm0","amounts":[{"denom":"uatom","amount":"995000"}]}}]})"); - auto key = DATA("c9b0a273831931aa4a5f8d1a570d5021dda91d3319bd3819becdaabfb7b44e3b"); - auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeCosmos)); - - ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeCosmos)); - assertStringsEqual(result, R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"5000","denom":"uatom"}],"gas":"200000"},"memo":"Testing","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"995000","denom":"uatom"}],"from_address":"cosmos1ufwv9ymhqaal6xz47n0jhzm2wf4empfqvjy575","to_address":"cosmos135qla4294zxarqhhgxsx0sw56yssa3z0f78pm0"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A6EsukEXB53GhohQVeDpxtkeH8KQIayd/Co/ApYRYkTm"},"signature":"ULEpUqNzoAnYEx2x22F3ANAiPXquAU9+mqLWoAA/ZOUGTMsdb6vryzsW6AKX2Kqj1pGNdrTcQ58Z09JPyjpgEA=="}]}})"); -} diff --git a/tests/Cosmos/TWCoinTypeTests.cpp b/tests/Cosmos/TWCoinTypeTests.cpp deleted file mode 100644 index e6e94c4a999..00000000000 --- a/tests/Cosmos/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWCosmosCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeCosmos)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("541FA06FB37AC1BF61922143783DD76FECA361830F9876D0342536EE8A87A790")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeCosmos, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("cosmos1gu6y2a0ffteesyeyeesk23082c6998xyzmt9mz")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeCosmos, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeCosmos)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeCosmos)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeCosmos), 6); - ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeCosmos)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeCosmos)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeCosmos)); - assertStringsEqual(symbol, "ATOM"); - assertStringsEqual(txUrl, "https://mintscan.io/cosmos/txs/541FA06FB37AC1BF61922143783DD76FECA361830F9876D0342536EE8A87A790"); - assertStringsEqual(accUrl, "https://mintscan.io/cosmos/account/cosmos1gu6y2a0ffteesyeyeesk23082c6998xyzmt9mz"); - assertStringsEqual(id, "cosmos"); - assertStringsEqual(name, "Cosmos Hub"); -} diff --git a/tests/Cronos/TWAnyAddressTests.cpp b/tests/Cronos/TWAnyAddressTests.cpp deleted file mode 100644 index bee3a617641..00000000000 --- a/tests/Cronos/TWAnyAddressTests.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" - -#include -#include - -#include - -TEST(CronosAnyAddress, Validate) { - auto string = STRING("0xEC49280228b0D05Aa8e8b756503254e1eE7835ab"); - - auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeCronosChain)); - - auto keyHash = WRAPD(TWAnyAddressData(addr.get())); - assertHexEqual(keyHash, "ec49280228b0d05aa8e8b756503254e1ee7835ab"); -} diff --git a/tests/Cronos/TWCoinTypeTests.cpp b/tests/Cronos/TWCoinTypeTests.cpp deleted file mode 100644 index 728c15a121d..00000000000 --- a/tests/Cronos/TWCoinTypeTests.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" -#include -#include - -TEST(TWCronosCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeCronosChain)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x850131282053328ad569fa91200aa970cbed7d97b52951fe8b449cca3708789e")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeCronosChain, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x44eed2bb80b688a8778173c19fe11cd6876af15a")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeCronosChain, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeCronosChain)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeCronosChain)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeCronosChain), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeCronosChain)); - - assertStringsEqual(symbol, "CRO"); - assertStringsEqual(txUrl, "https://cronoscan.com/tx/0x850131282053328ad569fa91200aa970cbed7d97b52951fe8b449cca3708789e"); - assertStringsEqual(accUrl, "https://cronoscan.com/address/0x44eed2bb80b688a8778173c19fe11cd6876af15a"); - assertStringsEqual(id, "cronos"); - assertStringsEqual(name, "Cronos Chain"); -} diff --git a/tests/CryptoOrg/AddressTests.cpp b/tests/CryptoOrg/AddressTests.cpp deleted file mode 100644 index 833a79016fa..00000000000 --- a/tests/CryptoOrg/AddressTests.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "Cosmos/Address.h" -#include "PublicKey.h" -#include "PrivateKey.h" -#include - -#include -#include - -using namespace TW; -using namespace TW::Cosmos; - -TEST(CryptoorgAddress, Valid) { - ASSERT_TRUE(Address::isValid(TWCoinTypeCryptoOrg, "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0")); - ASSERT_TRUE(Address::isValid(TWCoinTypeCryptoOrg, "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus")); - ASSERT_TRUE(Address::isValid(TWCoinTypeCryptoOrg, "cro1z53wwe7md6cewz9sqwqzn0aavpaun0gw39h3rd")); - ASSERT_TRUE(Address::isValid(TWCoinTypeCryptoOrg, "cro1y8ua5laceufhqtwzyhahq0qk7rm87hhugtsfey")); -} - -TEST(CryptoorgAddress, Invalid) { - EXPECT_FALSE(Address::isValid(TWCoinTypeCryptoOrg, "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf1")); - EXPECT_FALSE(Address::isValid(TWCoinTypeCryptoOrg, "cro1z53wwe7md6cewz9sqwqzn0aavpaun0gw39h3re")); - EXPECT_FALSE(Address::isValid(TWCoinTypeCryptoOrg, "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")); // valid cosmos -} - -TEST(CryptoorgAddress, FromPrivateKey) { - auto privateKey = PrivateKey(parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e")); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - auto address = Address(TWCoinTypeCryptoOrg, publicKey); - EXPECT_EQ(address.string(), "cro1z53wwe7md6cewz9sqwqzn0aavpaun0gw39h3rd"); - EXPECT_EQ(hex(address.getKeyHash()), "1522e767db6eb19708b0038029bfbd607bc9bd0e"); -} - -TEST(CryptoorgAddress, FromPublicKey) { - auto publicKey = PublicKey(parse_hex("03ed997e396cf4292f5fce5a42bba41599ccd5d96e313154a7c9ea7049de317c77"), TWPublicKeyTypeSECP256k1); - auto address = Address(TWCoinTypeCryptoOrg, publicKey); - EXPECT_EQ(address.string(), "cro1z53wwe7md6cewz9sqwqzn0aavpaun0gw39h3rd"); - EXPECT_EQ(hex(address.getKeyHash()), "1522e767db6eb19708b0038029bfbd607bc9bd0e"); -} - -TEST(CryptoorgAddress, FromString) { - Address address; - EXPECT_TRUE(Address::decode("cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0", address)); - EXPECT_EQ(address.string(), "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0"); - EXPECT_EQ(hex(address.getKeyHash()), "c2dcbc3828b42c429cedbaefa943e66679b471ed"); -} diff --git a/tests/CryptoOrg/SignerTests.cpp b/tests/CryptoOrg/SignerTests.cpp deleted file mode 100644 index f74b7af074e..00000000000 --- a/tests/CryptoOrg/SignerTests.cpp +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "proto/Cosmos.pb.h" -#include "Cosmos/Signer.h" -#include "Cosmos/Address.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include "../interface/TWTestUtilities.h" - -#include -#include - -using namespace TW; -using namespace TW::Cosmos; - -TEST(CryptoorgSigner, SignTx_DDCCE4) { - auto input = Cosmos::Proto::SigningInput(); - input.set_account_number(125798); - input.set_sequence(0); - input.set_chain_id("crypto-org-chain-mainnet-1"); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_send_coins_message(); - message.set_from_address("cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0"); - message.set_to_address("cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus"); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("basecro"); - amountOfTx->set_amount("100000000"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("basecro"); - amountOfFee->set_amount("5000"); - - std::string json; - google::protobuf::util::MessageToJsonString(input, &json); - - assertJSONEqual(json, R"( - { - "accountNumber": "125798", - "chainId": "crypto-org-chain-mainnet-1", - "fee": { - "amounts": [ - { - "denom": "basecro", - "amount": "5000" - } - ], - "gas": "200000" - }, - "messages": [ - { - "sendCoinsMessage": { - "fromAddress": "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0", - "toAddress": "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus", - "amounts": [ - { - "denom": "basecro", - "amount": "100000000" - } - ] - } - } - ] - } - )"); - - auto privateKey = parse_hex("200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Cosmos::Signer::sign(input, TWCoinTypeCryptoOrg); - - assertJSONEqual(output.json(), R"( - { - "mode": "block", - "tx": { - "fee": { - "amount": [ - { - "amount": "5000", - "denom": "basecro" - } - ], - "gas": "200000" - }, - "memo": "", - "msg": [ - { - "type": "cosmos-sdk/MsgSend", - "value": { - "amount": [ - { - "amount": "100000000", - "denom": "basecro" - } - ], - "from_address": "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0", - "to_address": "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus" - } - } - ], - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A4gxsGFiPn6L5Z2IjHEISkXI0IkwfL9exV3GLB171Wvj" - }, - "signature": "5+5rSFFg0FE9cTklQWQHNktBDJsz7UCnMSgF0t0+gYcrIhEWUyTtibXaHZQbKAAaciJ1BkHXYREjU55VswByVg==" - } - ] - } - } - )"); - EXPECT_EQ(hex(output.signature()), "e7ee6b485160d0513d713925416407364b410c9b33ed40a7312805d2dd3e81872b2211165324ed89b5da1d941b28001a7222750641d7611123539e55b3007256"); - - /// https://crypto.org/explorer/tx/DDCCE4052040B05914CADEFE78C0A75BE363AE39504E7EF6B2EDB8A9072AD44B - /// curl -H 'Content-Type: application/json' --data-binary '{"mode":"block","tx":{"fee": ... }}' https://mainnet.crypto.org:1317/txs -} - -TEST(CryptoorgSigner, SignJson) { - auto inputJson = R"({"accountNumber":"125798","chainId":"crypto-org-chain-mainnet-1","fee":{"amounts":[{"denom":"basecro","amount":"5000"}],"gas":"200000"},"messages":[{"sendCoinsMessage":{"fromAddress":"cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0","toAddress":"cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus","amounts":[{"denom":"basecro","amount":"100000000"}]}}]})"; - auto privateKey = parse_hex("200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d"); - - auto outputJson = Cosmos::Signer::signJSON(inputJson, privateKey, TWCoinTypeCryptoOrg); - - assertJSONEqual(outputJson, R"( - { - "mode": "block", - "tx": { - "fee": { - "amount": [ - { - "amount": "5000", - "denom": "basecro" - } - ], - "gas": "200000" - }, - "memo": "", - "msg": [ - { - "type": "cosmos-sdk/MsgSend", - "value": { - "amount": [ - { - "amount": "100000000", - "denom": "basecro" - } - ], - "from_address": "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0", - "to_address": "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus" - } - } - ], - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A4gxsGFiPn6L5Z2IjHEISkXI0IkwfL9exV3GLB171Wvj" - }, - "signature": "5+5rSFFg0FE9cTklQWQHNktBDJsz7UCnMSgF0t0+gYcrIhEWUyTtibXaHZQbKAAaciJ1BkHXYREjU55VswByVg==" - } - ] - } - } - )"); -} diff --git a/tests/CryptoOrg/TWAnyAddressTests.cpp b/tests/CryptoOrg/TWAnyAddressTests.cpp deleted file mode 100644 index e9771fed810..00000000000 --- a/tests/CryptoOrg/TWAnyAddressTests.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include "HexCoding.h" - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; - -TEST(CryptoorgAnyAddress, IsValid) { - EXPECT_TRUE(TWAnyAddressIsValid(STRING("cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0").get(), TWCoinTypeCryptoOrg)); - EXPECT_TRUE(TWAnyAddressIsValid(STRING("cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus").get(), TWCoinTypeCryptoOrg)); - EXPECT_FALSE(TWAnyAddressIsValid(STRING("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02").get(), TWCoinTypeCryptoOrg)); -} - -TEST(CryptoorgAnyAddress, Create) { - auto string = STRING("cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0"); - auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeCryptoOrg)); - auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); - EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); - auto keyHash = WRAPD(TWAnyAddressData(addr.get())); - assertHexEqual(keyHash, "c2dcbc3828b42c429cedbaefa943e66679b471ed"); -} diff --git a/tests/CryptoOrg/TWAnySignerTests.cpp b/tests/CryptoOrg/TWAnySignerTests.cpp deleted file mode 100644 index 5f87a527d21..00000000000 --- a/tests/CryptoOrg/TWAnySignerTests.cpp +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include "proto/Cosmos.pb.h" -#include "HexCoding.h" -#include "Base64.h" -#include "Data.h" - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; - - -const auto Address1 = "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0"; -const auto Address2 = "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus"; -const auto PrivateKey1 = "200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d"; - -TEST(TWAnySignerCryptoorg, SignTx_Proto_BCB213) { - auto input = Cosmos::Proto::SigningInput(); - input.set_signing_mode(Cosmos::Proto::Protobuf); - input.set_account_number(125798); - input.set_sequence(2); - input.set_chain_id("crypto-org-chain-mainnet-1"); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_send_coins_message(); - message.set_from_address(Address1); - message.set_to_address(Address2); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("basecro"); - amountOfTx->set_amount("50000000"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("basecro"); - amountOfFee->set_amount("5000"); - - auto privateKey = parse_hex(PrivateKey1); - input.set_private_key(privateKey.data(), privateKey.size()); - - Cosmos::Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeCryptoOrg); - - // https://crypto.org/explorer/tx/BCB213B0A121F0CF11BECCF52475F1C8328D6070F3CFDA9E14C42E6DB30E847E - // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "CpABC...F0SI=", "mode": "BROADCAST_MODE_BLOCK"}' https://mainnet.crypto.org:1317/cosmos/tx/v1beta1/txs - assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CpABCo0BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm0KKmNybzFjdHd0Y3dwZ2tza3k5ODhkaHRoNmpzbHh2ZXVtZ3UwZDQ1emdmMBIqY3JvMXhwYWh5NmM3d2xkeGFjdjZsZDk5aDQzNW1odmZuc3VwMjR2Y3VzGhMKB2Jhc2Vjcm8SCDUwMDAwMDAwEmkKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQOIMbBhYj5+i+WdiIxxCEpFyNCJMHy/XsVdxiwde9Vr4xIECgIIARgCEhUKDwoHYmFzZWNybxIENTAwMBDAmgwaQAcxK9xk6r69gmz+1UWaCnYxNuXPXZdp59YcqKPJE5d6fp+IICTBOwd2rs8MiApcf8kNSrbZ6oECxcGQAdxF0SI=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); - EXPECT_EQ(hex(output.signature()), "07312bdc64eabebd826cfed5459a0a763136e5cf5d9769e7d61ca8a3c913977a7e9f882024c13b0776aecf0c880a5c7fc90d4ab6d9ea8102c5c19001dc45d122"); - EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); -} - -TEST(TWAnySignerCryptoorg, SignTx_Json_DDCCE4) { - auto input = Cosmos::Proto::SigningInput(); - input.set_signing_mode(Cosmos::Proto::JSON); // obsolete - input.set_account_number(125798); - input.set_sequence(0); - input.set_chain_id("crypto-org-chain-mainnet-1"); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_send_coins_message(); - message.set_from_address(Address1); - message.set_to_address(Address2); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("basecro"); - amountOfTx->set_amount("100000000"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("basecro"); - amountOfFee->set_amount("5000"); - - auto privateKey = parse_hex(PrivateKey1); - input.set_private_key(privateKey.data(), privateKey.size()); - - Cosmos::Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeCryptoOrg); - - assertJSONEqual(output.json(), R"( - { - "mode": "block", - "tx": { - "fee": { - "amount": [ - { - "amount": "5000", - "denom": "basecro" - } - ], - "gas": "200000" - }, - "memo": "", - "msg": [ - { - "type": "cosmos-sdk/MsgSend", - "value": { - "amount": [ - { - "amount": "100000000", - "denom": "basecro" - } - ], - "from_address": "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0", - "to_address": "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus" - } - } - ], - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A4gxsGFiPn6L5Z2IjHEISkXI0IkwfL9exV3GLB171Wvj" - }, - "signature": "5+5rSFFg0FE9cTklQWQHNktBDJsz7UCnMSgF0t0+gYcrIhEWUyTtibXaHZQbKAAaciJ1BkHXYREjU55VswByVg==" - } - ] - } - } - )"); - EXPECT_EQ(hex(output.signature()), "e7ee6b485160d0513d713925416407364b410c9b33ed40a7312805d2dd3e81872b2211165324ed89b5da1d941b28001a7222750641d7611123539e55b3007256"); - EXPECT_EQ(output.serialized(), ""); - EXPECT_EQ(output.error(), ""); - - /// https://crypto.org/explorer/tx/DDCCE4052040B05914CADEFE78C0A75BE363AE39504E7EF6B2EDB8A9072AD44B - /// curl -H 'Content-Type: application/json' --data-binary '{"mode":"block","tx":{"fee": ... }}' https://mainnet.crypto.org:1317/txs -} diff --git a/tests/CryptoOrg/TWCoinTypeTests.cpp b/tests/CryptoOrg/TWCoinTypeTests.cpp deleted file mode 100644 index a6e06751ec4..00000000000 --- a/tests/CryptoOrg/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWCryptoorgCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeCryptoOrg)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("D87D2EB46B21466886EE149C1DEA3AE6C2E019C7D8C24FA1533A95439DDCE1E2")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeCryptoOrg, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("cro10wrflcdc4pys9vvgqm98yg7yv5ltj7d3xehent")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeCryptoOrg, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeCryptoOrg)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeCryptoOrg)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeCryptoOrg), 8); - ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeCryptoOrg)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeCryptoOrg)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeCryptoOrg)); - assertStringsEqual(symbol, "CRO"); - assertStringsEqual(txUrl, "https://crypto.org/explorer/tx/D87D2EB46B21466886EE149C1DEA3AE6C2E019C7D8C24FA1533A95439DDCE1E2"); - assertStringsEqual(accUrl, "https://crypto.org/explorer/account/cro10wrflcdc4pys9vvgqm98yg7yv5ltj7d3xehent"); - assertStringsEqual(id, "cryptoorg"); - assertStringsEqual(name, "Crypto.org"); -} diff --git a/tests/Dash/TWCoinTypeTests.cpp b/tests/Dash/TWCoinTypeTests.cpp deleted file mode 100644 index 20fa3257b39..00000000000 --- a/tests/Dash/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWDashCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeDash)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeDash, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeDash, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeDash)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeDash)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeDash), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeDash)); - ASSERT_EQ(0x10, TWCoinTypeP2shPrefix(TWCoinTypeDash)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeDash)); - assertStringsEqual(symbol, "DASH"); - assertStringsEqual(txUrl, "https://blockchair.com/dash/transaction/t123"); - assertStringsEqual(accUrl, "https://blockchair.com/dash/address/a12"); - assertStringsEqual(id, "dash"); - assertStringsEqual(name, "Dash"); -} diff --git a/tests/Dash/TWDashTests.cpp b/tests/Dash/TWDashTests.cpp deleted file mode 100644 index c1ad9c58117..00000000000 --- a/tests/Dash/TWDashTests.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" - -#include - -#include - -TEST(Dash, LockScripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("XgkpWEFe59pX3aMhx6PrDawjNnoZKHeZbp").get(), TWCoinTypeDash)); - auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); - assertHexEqual(scriptData, "76a91442914f5b70c61619eca5359df57d0b9bdcf8ccff88ac"); - - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("7eprxeVjKfVgS8p2RNsZ89K72YV61xg4gq").get(), TWCoinTypeDash)); - auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); - assertHexEqual(scriptData2, "a9148835ae54f297ad069552a1401e535dfe5f396f6187"); -} diff --git a/tests/Decred/AddressTests.cpp b/tests/Decred/AddressTests.cpp deleted file mode 100644 index 49f627dabe9..00000000000 --- a/tests/Decred/AddressTests.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Decred/Address.h" - -#include "Coin.h" -#include "HDWallet.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Decred; - -TEST(DecredAddress, FromPublicKey) { - const auto publicKey = PublicKey(parse_hex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), TWPublicKeyTypeSECP256k1); - const auto address = Address(publicKey); - ASSERT_EQ(address.string(), "DsmcYVbP1Nmag2H4AS17UTvmWXmGeA7nLDx"); -} - -TEST(DecredAddress, Valid) { - ASSERT_TRUE(Address::isValid("DsmcYVbP1Nmag2H4AS17UTvmWXmGeA7nLDx")); - ASSERT_TRUE(Address::isValid("Dcur2mcGjmENx4DhNqDctW5wJCVyT3Qeqkx")); -} - -TEST(DecredAddress, Invalid) { - ASSERT_FALSE(Address::isValid("rnBFvgZphmN39GWzUJeUitaP22Fr9be75H")); - ASSERT_FALSE(Address::isValid("t3gQDEavk5VzAAHK8TrQu2BWDLxEiF1unBm")); -} - -TEST(DecredAddress, FromString) { - const auto string = "DsmcYVbP1Nmag2H4AS17UTvmWXmGeA7nLDx"; - const auto address = Address(string); - - ASSERT_EQ(address.string(), string); -} - -TEST(DecredAddress, Derive) { - const auto mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; - const auto wallet = HDWallet(mnemonic, ""); - const auto path = TW::derivationPath(TWCoinTypeDecred); - const auto address = TW::deriveAddress(TWCoinTypeDecred, wallet.getKey(TWCoinTypeDecred, path)); - ASSERT_EQ(address, "DsVMHD5D86dpRnt2GPZvv4bYUJZg6B9Pzqa"); -} diff --git a/tests/Decred/SignerTests.cpp b/tests/Decred/SignerTests.cpp deleted file mode 100644 index 8ad2081c970..00000000000 --- a/tests/Decred/SignerTests.cpp +++ /dev/null @@ -1,429 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Decred/Address.h" -#include "Decred/Signer.h" -#include "proto/Decred.pb.h" - -#include "Hash.h" -#include "HexCoding.h" -#include "PrivateKey.h" - -#include -#include - -using namespace TW; -using namespace TW::Decred; - -TEST(DecredSigner, SignP2PKH) { - const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); - - const auto address = Address(publicKey); - ASSERT_EQ(address.string(), "DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); - - - // For this example, create a fake transaction that represents what would - // ordinarily be the real transaction that is being spent. It contains a - // single output that pays to address in the amount of 1 DCR. - auto originTx = Transaction(); - - auto txInOrigin = TransactionInput(); - txInOrigin.previousOutput = OutPoint(std::array{}, UINT32_MAX, 0); - txInOrigin.valueIn = 100'000'000; - txInOrigin.script = Bitcoin::Script(Data{OP_0, OP_0}); - originTx.inputs.push_back(txInOrigin); - - auto txOutOrigin = TransactionOutput(); - txOutOrigin.value = 100'000'000; - txOutOrigin.script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); - originTx.outputs.push_back(txOutOrigin); - - ASSERT_EQ(hex(originTx.hash()), "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a"); - - - // Setup input - Bitcoin::Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAll); - input.set_amount(100'000'000); - input.set_byte_fee(1); - input.set_to_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); - input.set_change_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); - - auto utxoKey0 = parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"); - input.add_private_key(utxoKey0.data(), utxoKey0.size()); - - auto utxo0 = input.add_utxo(); - auto utxo0Script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); - utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); - utxo0->set_amount(100'000'000); - utxo0->mutable_out_point()->set_hash(originTx.hash().data(), originTx.hash().size()); - utxo0->mutable_out_point()->set_index(0); - - - // Create the transaction to redeem the fake transaction. - auto redeemTx = Transaction(); - - auto txIn = TransactionInput(); - txIn.previousOutput = OutPoint(originTx.hash(), 0, 0); - txIn.valueIn = 100'000'000; - redeemTx.inputs.push_back(txIn); - - auto txOut = TransactionOutput(); - redeemTx.outputs.push_back(txOut); - - auto plan = input.mutable_plan(); - plan->set_amount(100'000'000); - plan->set_available_amount(100'000'000); - plan->set_fee(0); - plan->set_change(0); - auto utxop0 = plan->add_utxos(); - *utxop0 = *utxo0; - - // Sign - auto signer = Signer(std::move(input)); - signer.transaction = redeemTx; - signer.txPlan.amount = 100'000'000; - const auto result = signer.sign(); - - ASSERT_TRUE(result); - - const auto expectedSignature = "47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f0121" - "02a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf5"; - EXPECT_EQ(hex(result.payload().inputs[0].script.bytes), expectedSignature); - - const auto expectedEncoded = - "0100" // Serialize type - "0000" // Version - "01" // Inputs - "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a" // Hash - "00000000" // Index - "00" // Tree - "ffffffff" // Sequence - "01" // Outputs - "0000000000000000" // Value - "0000" // Version - "00" // Script - "00000000" // Lock time - "00000000" // Expiry - "01" - "00e1f50500000000" // Value - "00000000" // Block height - "ffffffff" // Block index - "6a47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f012102a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf5"; - auto encoded = Data(); - result.payload().encode(encoded); - EXPECT_EQ(hex(encoded), expectedEncoded); -} - -TEST(DecredSigner, SignP2SH) { - const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); - - const auto address = Address(publicKey); - ASSERT_EQ(address.string(), "DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); - - - // For this example, create a fake transaction that represents what would - // ordinarily be the real transaction that is being spent. It contains a - // single output that pays to address in the amount of 1 DCR. - auto originTx = Transaction(); - - auto txInOrigin = TransactionInput(); - txInOrigin.previousOutput = OutPoint(std::array{}, UINT32_MAX, 0); - txInOrigin.valueIn = 100'000'000; - txInOrigin.script = Bitcoin::Script(Data{OP_0, OP_0}); - originTx.inputs.push_back(txInOrigin); - - auto txOutOrigin = TransactionOutput(); - txOutOrigin.value = 100'000'000; - txOutOrigin.script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); - originTx.outputs.push_back(txOutOrigin); - - ASSERT_EQ(hex(originTx.hash()), "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a"); - - - // Setup input - Bitcoin::Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAll); - input.set_amount(100'000'000); - input.set_byte_fee(1); - input.set_to_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); - input.set_change_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); - - auto utxoKey0 = parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"); - input.add_private_key(utxoKey0.data(), utxoKey0.size()); - - auto redeemScript = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); - auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); - auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[hex(scriptHash.begin(), scriptHash.end())] = scriptString; - - auto utxo0 = input.add_utxo(); - auto utxo0Script = Bitcoin::Script::buildPayToScriptHash(scriptHash); - utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); - utxo0->set_amount(100'000'000); - utxo0->mutable_out_point()->set_hash(originTx.hash().data(), originTx.hash().size()); - utxo0->mutable_out_point()->set_index(0); - - auto plan = input.mutable_plan(); - plan->set_amount(100'000'000); - plan->set_available_amount(100'000'000); - plan->set_fee(0); - plan->set_change(0); - auto utxop0 = plan->add_utxos(); - *utxop0 = *utxo0; - - // Create the transaction to redeem the fake transaction. - auto redeemTx = Transaction(); - - auto txIn = TransactionInput(); - txIn.previousOutput = OutPoint(originTx.hash(), 0, 0); - txIn.valueIn = 100'000'000; - redeemTx.inputs.push_back(txIn); - - auto txOut = TransactionOutput(); - redeemTx.outputs.push_back(txOut); - - - // Sign - auto signer = Signer(std::move(input)); - signer.transaction = redeemTx; - signer.txPlan.amount = 100'000'000; - const auto result = signer.sign(); - - ASSERT_TRUE(result); - - const auto expectedSignature = "47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f012102a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf51976a914f5eba6730a4052ddeef0a93d93d24004f49db51e88ac1976a914f5eba6730a4052ddeef0a93d93d24004f49db51e88ac"; - EXPECT_EQ(hex(result.payload().inputs[0].script.bytes), expectedSignature); - - const auto expectedEncoded = - "0100" // Serialize type - "0000" // Version - "01" // Inputs - "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a" // Hash - "00000000" // Index - "00" // Tree - "ffffffff" // Sequence - "01" // Outputs - "0000000000000000" // Value - "0000" // Version - "00" // Script - "00000000" // Lock time - "00000000" // Expiry - "01" - "00e1f50500000000" // Value - "00000000" // Block height - "ffffffff" // Block index - "9e47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f012102a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf51976a914f5eba6730a4052ddeef0a93d93d24004f49db51e88ac1976a914f5eba6730a4052ddeef0a93d93d24004f49db51e88ac"; - auto encoded = Data(); - result.payload().encode(encoded); - EXPECT_EQ(hex(encoded), expectedEncoded); -} - -TEST(DecredSigner, SignNegativeNoUtxo) { - const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); - - const auto address = Address(publicKey); - ASSERT_EQ(address.string(), "DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); - - auto originTx = Transaction(); - - auto txInOrigin = TransactionInput(); - txInOrigin.previousOutput = OutPoint(std::array{}, UINT32_MAX, 0); - txInOrigin.valueIn = 100'000'000; - txInOrigin.script = Bitcoin::Script(Data{OP_0, OP_0}); - originTx.inputs.push_back(txInOrigin); - - auto txOutOrigin = TransactionOutput(); - txOutOrigin.value = 100'000'000; - txOutOrigin.script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); - originTx.outputs.push_back(txOutOrigin); - - ASSERT_EQ(hex(originTx.hash()), "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a"); - - // Setup input - Bitcoin::Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAll); - input.set_amount(100'000'000); - input.set_byte_fee(1); - input.set_to_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); - input.set_change_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); - - auto utxoKey0 = parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"); - input.add_private_key(utxoKey0.data(), utxoKey0.size()); - - // Create the transaction to redeem the fake transaction. - auto redeemTx = Transaction(); - - auto txIn = TransactionInput(); - txIn.previousOutput = OutPoint(originTx.hash(), 0, 0); - txIn.valueIn = 100'000'000; - redeemTx.inputs.push_back(txIn); - - auto txOut = TransactionOutput(); - redeemTx.outputs.push_back(txOut); - - // Sign - auto signer = Signer(std::move(input)); - signer.transaction = redeemTx; - signer.txPlan.amount = 100'000'000; - const auto result = signer.sign(); - - // Fails as there are 0 utxos - ASSERT_FALSE(result); -} - -TEST(DecredSigner, SignP2PKH_NoPlan) { - const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); - - const auto address = Address(publicKey); - ASSERT_EQ(address.string(), "DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); - - - // For this example, create a fake transaction that represents what would - // ordinarily be the real transaction that is being spent. It contains a - // single output that pays to address in the amount of 1 DCR. - auto originTx = Transaction(); - - auto txInOrigin = TransactionInput(); - txInOrigin.previousOutput = OutPoint(std::array{}, UINT32_MAX, 0); - txInOrigin.valueIn = 150'000'000; - txInOrigin.script = Bitcoin::Script(Data{OP_0, OP_0}); - originTx.inputs.push_back(txInOrigin); - - auto txOutOrigin = TransactionOutput(); - txOutOrigin.value = 100'000'000; - txOutOrigin.script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); - originTx.outputs.push_back(txOutOrigin); - - ASSERT_EQ(hex(originTx.hash()), "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a"); - - - // Setup input - Bitcoin::Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAll); - input.set_amount(100'000'000); - input.set_byte_fee(1); - input.set_to_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); - input.set_change_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); - - auto utxoKey0 = parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"); - input.add_private_key(utxoKey0.data(), utxoKey0.size()); - - auto utxo0 = input.add_utxo(); - auto utxo0Script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); - utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); - utxo0->set_amount(150'000'000); - utxo0->mutable_out_point()->set_hash(originTx.hash().data(), originTx.hash().size()); - utxo0->mutable_out_point()->set_index(0); - - - // Create the transaction to redeem the fake transaction. - auto redeemTx = Transaction(); - - auto txIn = TransactionInput(); - txIn.previousOutput = OutPoint(originTx.hash(), 0, 0); - txIn.valueIn = 100'000'000; - redeemTx.inputs.push_back(txIn); - - auto txOut = TransactionOutput(); - redeemTx.outputs.push_back(txOut); - - - // Sign - auto signer = Signer(std::move(input)); - signer.transaction = redeemTx; - //signer.txPlan.utxos.push_back(*utxo0); - //signer.txPlan.amount = 100'000'000; - const auto result = signer.sign(); - - ASSERT_TRUE(result); - ASSERT_TRUE(result.payload().inputs.size() >= 1); - - const auto expectedSignature = "47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f0121" - "02a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf5"; - EXPECT_EQ(hex(result.payload().inputs[0].script.bytes), expectedSignature); - - const auto expectedEncoded = - "0100" // Serialize type - "0000" // Version - "01" // Inputs - "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a" // Hash - "00000000" // Index - "00" // Tree - "ffffffff" // Sequence - "01" // Outputs - "0000000000000000" // Value - "0000" // Version - "00" // Script - "00000000" // Lock time - "00000000" // Expiry - "01" - "00e1f50500000000" // Value - "00000000" // Block height - "ffffffff" // Block index - "6a47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f012102a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf5"; - auto encoded = Data(); - result.payload().encode(encoded); - EXPECT_EQ(hex(encoded), expectedEncoded); -} - -TEST(DecredSigning, SignP2WPKH_NegativeAddressWrongType) { - auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); - auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); - - // Setup input - Bitcoin::Proto::SigningInput input; - input.set_hash_type(TWBitcoinSigHashTypeAll); - input.set_amount(335'790'000); - input.set_byte_fee(1); - input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); - input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); - - auto utxoKey0 = parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"); - input.add_private_key(utxoKey0.data(), utxoKey0.size()); - - auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); - input.add_private_key(utxoKey1.data(), utxoKey1.size()); - - auto scriptPub1 = Bitcoin::Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); - auto scriptHash = std::vector(); - scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); - auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); - ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - - auto redeemScript = Bitcoin::Script::buildPayToPublicKeyHash(scriptHash); - auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[scriptHashHex] = scriptString; - - auto utxo0 = input.add_utxo(); - auto utxo0Script = parse_hex("2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac"); - utxo0->set_script(utxo0Script.data(), utxo0Script.size()); - utxo0->set_amount(625'000'000); - utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); - utxo0->mutable_out_point()->set_index(0); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX); - - auto utxo1 = input.add_utxo(); - auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); - utxo1->set_script(utxo1Script.data(), utxo1Script.size()); - utxo1->set_amount(600'000'000); - utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); - utxo1->mutable_out_point()->set_index(1); - utxo1->mutable_out_point()->set_sequence(UINT32_MAX); - - // Sign - auto result = Signer(std::move(input)).sign(); - - ASSERT_FALSE(result) << std::to_string(result.error()); -} diff --git a/tests/Decred/TWAnySignerTests.cpp b/tests/Decred/TWAnySignerTests.cpp deleted file mode 100644 index e1d08bbb221..00000000000 --- a/tests/Decred/TWAnySignerTests.cpp +++ /dev/null @@ -1,114 +0,0 @@ - -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" - -#include "HexCoding.h" -#include "proto/Bitcoin.pb.h" -#include "proto/Decred.pb.h" -#include -#include -#include -#include - - -namespace TW::Decred { - -Bitcoin::Proto::SigningInput createInput() { - const int64_t utxoValue = 39900000; - const int64_t amount = 10000000; - - auto input = Bitcoin::Proto::SigningInput(); - input.set_hash_type(TWBitcoinSigHashTypeAll); - input.set_amount(amount); - input.set_byte_fee(1); - input.set_to_address("Dsesp1V6DZDEtcq2behmBVKdYqKMdkh96hL"); - input.set_change_address("DsUoWCAxprdGNtKQqambFbTcSBgH1SHn9Gp"); - input.set_coin_type(TWCoinTypeDecred); - - auto& utxo = *input.add_utxo(); - - auto hash = parse_hex("fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd11550"); - auto script = parse_hex("76a914b75fdec70b2e731795dd123ab40f918bf099fee088ac"); - auto utxoKey = parse_hex("ba005cd605d8a02e3d5dfd04234cef3a3ee4f76bfbad2722d1fb5af8e12e6764"); - - utxo.set_amount(utxoValue); - utxo.set_script(script.data(), script.size()); - - auto& outpoint = *utxo.mutable_out_point(); - outpoint.set_hash(hash.data(), hash.size()); - outpoint.set_index(0); - - input.add_private_key(utxoKey.data(), utxoKey.size()); - return input; -} - -TEST(TWAnySignerDecred, Signing) { - auto input = createInput(); - - const int64_t utxoValue = 39900000; - const int64_t amount = 10000000; - const int64_t fee = 100000; - - auto& plan = *input.mutable_plan(); - plan.set_amount(amount); - plan.set_available_amount(utxoValue); - plan.set_fee(fee); - plan.set_change(utxoValue - amount - fee); - auto& planUtxo = *plan.add_utxos(); - planUtxo = input.utxo(0); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeDecred); - - ASSERT_EQ(output.error(), Common::Proto::OK); - ASSERT_EQ(hex(output.encoded()), "0100000001fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd1155000000000000000000002809698000000000000001976a914989b1aecabf1c24e213cc0f2d8a22ffee25dd4e188ac40b6c6010000000000001976a9142a194fc92e27fef9cc2b057bc9060c580cbb484888ac000000000000000001000000000000000000000000ffffffff6a47304402206ee887c9239e5fff0048674bdfff2a8cfbeec6cd4a3ccebcc12fac44b24cc5ac0220718f7c760818fde18bc5ba8457d43d5a145cc4cf13d2a5557cba9107e9f4558d0121026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6"); -} - -TEST(TWAnySignerDecred, Plan) { - auto input = createInput(); - - Bitcoin::Proto::TransactionPlan plan; - ANY_PLAN(input, plan, TWCoinTypeDecred); - - EXPECT_EQ(plan.amount(), 10000000); - EXPECT_EQ(plan.available_amount(), 39900000); - EXPECT_EQ(plan.fee(), 254); - EXPECT_EQ(plan.change(), 29899746); - EXPECT_EQ(plan.utxos_size(), 1); - EXPECT_EQ(plan.branch_id(), ""); -} - -TEST(TWAnySignerDecred, PlanAndSign) { - auto input = createInput(); - - Bitcoin::Proto::TransactionPlan plan; - ANY_PLAN(input, plan, TWCoinTypeDecred); - - EXPECT_EQ(plan.amount(), 10000000); - EXPECT_EQ(plan.available_amount(), 39900000); - EXPECT_EQ(plan.fee(), 254); - EXPECT_EQ(plan.change(), 29899746); - EXPECT_EQ(plan.utxos_size(), 1); - EXPECT_EQ(plan.branch_id(), ""); - - // copy over plan fields - *input.mutable_plan() = plan; - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeDecred); - - ASSERT_EQ(output.error(), Common::Proto::OK); - ASSERT_EQ(output.encoded().size(), 251); - ASSERT_EQ(hex(output.encoded()), "0100000001fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd1155000000000000000000002809698000000000000001976a914989b1aecabf1c24e213cc0f2d8a22ffee25dd4e188ace23bc8010000000000001976a9142a194fc92e27fef9cc2b057bc9060c580cbb484888ac000000000000000001000000000000000000000000ffffffff6a47304402203e6ee9e16d6bc36bb4242f7a4cac333a1c2a150ea16143412b88b721f6ae16bf02201019affdf815a5c22e4b0fb7e4685c4707094922d6a41354f9055d3bb0f26e630121026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6"); -} - -TEST(TWAnySignerDecred, SupportsJSON) { - ASSERT_FALSE(TWAnySignerSupportsJSON(TWCoinTypeDecred)); -} - -} // namespace diff --git a/tests/Decred/TWCoinTypeTests.cpp b/tests/Decred/TWCoinTypeTests.cpp deleted file mode 100644 index 8e8200bae80..00000000000 --- a/tests/Decred/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWDecredCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeDecred)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeDecred, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeDecred, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeDecred)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeDecred)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeDecred), 8); - ASSERT_EQ(TWBlockchainDecred, TWCoinTypeBlockchain(TWCoinTypeDecred)); - ASSERT_EQ(0x1a, TWCoinTypeP2shPrefix(TWCoinTypeDecred)); - ASSERT_EQ(0x7, TWCoinTypeStaticPrefix(TWCoinTypeDecred)); - assertStringsEqual(symbol, "DCR"); - assertStringsEqual(txUrl, "https://dcrdata.decred.org/tx/t123"); - assertStringsEqual(accUrl, "https://dcrdata.decred.org/address/a12"); - assertStringsEqual(id, "decred"); - assertStringsEqual(name, "Decred"); -} diff --git a/tests/DigiByte/TWCoinTypeTests.cpp b/tests/DigiByte/TWCoinTypeTests.cpp deleted file mode 100644 index 0140cef4893..00000000000 --- a/tests/DigiByte/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWDigiByteCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeDigiByte)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeDigiByte, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeDigiByte, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeDigiByte)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeDigiByte)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeDigiByte), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeDigiByte)); - ASSERT_EQ(0x3f, TWCoinTypeP2shPrefix(TWCoinTypeDigiByte)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeDigiByte)); - assertStringsEqual(symbol, "DGB"); - assertStringsEqual(txUrl, "https://digiexplorer.info/tx/t123"); - assertStringsEqual(accUrl, "https://digiexplorer.info/address/a12"); - assertStringsEqual(id, "digibyte"); - assertStringsEqual(name, "DigiByte"); -} diff --git a/tests/Dogecoin/TWCoinTypeTests.cpp b/tests/Dogecoin/TWCoinTypeTests.cpp deleted file mode 100644 index decba7e836e..00000000000 --- a/tests/Dogecoin/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWDogecoinCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeDogecoin)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeDogecoin, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeDogecoin, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeDogecoin)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeDogecoin)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeDogecoin), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeDogecoin)); - ASSERT_EQ(0x16, TWCoinTypeP2shPrefix(TWCoinTypeDogecoin)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeDogecoin)); - assertStringsEqual(symbol, "DOGE"); - assertStringsEqual(txUrl, "https://blockchair.com/dogecoin/transaction/t123"); - assertStringsEqual(accUrl, "https://blockchair.com/dogecoin/address/a12"); - assertStringsEqual(id, "doge"); - assertStringsEqual(name, "Dogecoin"); -} diff --git a/tests/Dogecoin/TWDogeTests.cpp b/tests/Dogecoin/TWDogeTests.cpp deleted file mode 100644 index 76f83bdb96a..00000000000 --- a/tests/Dogecoin/TWDogeTests.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" - -#include - -#include - -TEST(Doge, LockScripts) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("DLSSSUS3ex7YNDACJDxMER1ZMW579Vy8Zy").get(), TWCoinTypeDogecoin)); - auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); - assertHexEqual(scriptData, "76a914a7d191ec42aa113e28cd858cceaa7c733ba2f77788ac"); - - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("AETZJzedcmLM2rxCM6VqCGF3YEMUjA3jMw").get(), TWCoinTypeDogecoin)); - auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); - assertHexEqual(scriptData2, "a914f191149f72f235548746654f5b473c58258f7fb687"); -} diff --git a/tests/ECash/TWCoinTypeTests.cpp b/tests/ECash/TWCoinTypeTests.cpp deleted file mode 100644 index 59d2ea5954d..00000000000 --- a/tests/ECash/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWECashCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeECash)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("6bc767e69cfacffd954c9e5acd178d3140bf00d094b92c6f6052b517500c553b")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeECash, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("ecash:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdg2jj94l5j")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeECash, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeECash)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeECash)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeECash), 2); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeECash)); - ASSERT_EQ(0x5, TWCoinTypeP2shPrefix(TWCoinTypeECash)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeECash)); - assertStringsEqual(symbol, "XEC"); - assertStringsEqual(txUrl, "https://explorer.bitcoinabc.org/tx/6bc767e69cfacffd954c9e5acd178d3140bf00d094b92c6f6052b517500c553b"); - assertStringsEqual(accUrl, "https://explorer.bitcoinabc.org/address/ecash:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdg2jj94l5j"); - assertStringsEqual(id, "ecash"); - assertStringsEqual(name, "eCash"); -} diff --git a/tests/EOS/AddressTests.cpp b/tests/EOS/AddressTests.cpp deleted file mode 100644 index 25ff60f2dd9..00000000000 --- a/tests/EOS/AddressTests.cpp +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "EOS/Address.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include - -#include - -using namespace TW; -using namespace TW::EOS; - -TEST(EOSAddress, Invalid) { - ASSERT_FALSE(Address::isValid("abc")); - ASSERT_FALSE(Address::isValid("65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjTF")); - ASSERT_FALSE(Address::isValid("EOS65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjT")); - ASSERT_FALSE(Address::isValid("PUB_5hieQEFWh68h6bjaYAY25Ptd2bmqLCaFsunaneh9gZsmSgUBUe")); - ASSERT_FALSE(Address::isValid("PUB_K1_5hieQEFWh68h6bjaYAY25Ptd2bmqLCaFsunaneh9gZsmSgUBUe")); - - ASSERT_THROW(Address("PUB_K1_65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjTF"), std::invalid_argument); - ASSERT_THROW(EOS::Address(Data(0)), std::invalid_argument); -} - -TEST(EOSAddress, Base58) { - ASSERT_EQ( - Address("EOS65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjTF").string(), - "EOS65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjTF" - ); - ASSERT_EQ( - Address("EOS55hdeEZHoArE8LLTv6drj2yR1K1AH8wAPT4kjTVSnkmQc3nzwQ").string(), - "EOS55hdeEZHoArE8LLTv6drj2yR1K1AH8wAPT4kjTVSnkmQc3nzwQ" - ); - ASSERT_EQ( - Address("PUB_R1_5hieQEFWh68h6bjaYAY25Ptd2bmqLCaFsunaneh9gZsmSgUBUe").string(), - "PUB_R1_5hieQEFWh68h6bjaYAY25Ptd2bmqLCaFsunaneh9gZsmSgUBUe" - ); - ASSERT_EQ( - Address("PUB_R1_7M9ckjr6p5CmS3N3yLPg9vcTB5NHmLcMHwZ3iGccEVfbjJRHv3").string(), - "PUB_R1_7M9ckjr6p5CmS3N3yLPg9vcTB5NHmLcMHwZ3iGccEVfbjJRHv3" - ); -} - -TEST(EOSAddress, FromPrivateKey) { - std::string privArray[] { "8e14ef506fee5e0aaa32f03a45242d32d0eb993ffe25ce77542ef07219db667c", - "e2bfd815c5923f404388a3257aa5527f0f52e92ce364e1e26a04d270c901edda", - "e6b783120a21cb234d8e15077ce186c47261d1043781ab8b16b84f2acd377668", - "bb96c0a4a6ec9c93ccc0b2cbad6b0e8110b9ca4731aef9c6937b99552a319b03" }; - - Type privTypes[] { Type::Legacy, Type::Legacy, Type::ModernR1, Type::ModernR1 }; - - std::string pubArray[] { "EOS6TFKUKVvtvjRq9T4fV9pdxNUuJke92nyb4rzSFtZfdR5ssmVuY", - "EOS5YtaCcbPJ3BknNBTDezE9eJoGNnAVuUwT8bnxhSRS5dqRvyfxr", - "PUB_R1_67itCyDj42CRgtpyP4fLbAccBYnVHGeZQujQAeK3fyNbvfvZM6", - "PUB_R1_5DpVkbrMBDnY4JRhiEdHLmdLDKGQLNfL7X7it2pqT7Uk83ccDL" }; - - for (int i = 0; i < 4; i++) { - const auto privateKey = PrivateKey(parse_hex(privArray[i])); - const auto publicKey = PublicKey(privateKey.getPublicKey(privTypes[i] == Type::Legacy ? TWPublicKeyTypeSECP256k1 : TWPublicKeyTypeNIST256p1)); - const auto address = Address(publicKey, privTypes[i]); - - ASSERT_EQ(address.string(), pubArray[i]); - } -} - -TEST(EOSAddress, IsValid) { - ASSERT_TRUE(Address::isValid("EOS6Vm7RWMS1KKAM9kDXgggpu4sJkFMEpARhmsWA84tk4P22m29AV")); - ASSERT_TRUE(Address::isValid("PUB_R1_6pQRUVU5vdneRnmjSiZPsvu3zBqcptvg6iK2Vz4vKo4ugnzow3")); - ASSERT_TRUE(Address::isValid("EOS5mGcPvsqFDe8YRrA3yMMjQgjrCa6yiCho79KViDhvxh4ajQjgS")); - ASSERT_TRUE(Address::isValid("PUB_R1_82dMu3zSSfyHYc4cvWJ6SPsHZWB5mBNAyhL53xiM5xpqmfqetN")); - - ASSERT_NO_THROW(Address(parse_hex("039d91164ea04f4e751762643ef4ae520690af361b8e677cf341fd213419956b356cb721b7"), Type::ModernR1)); - ASSERT_NO_THROW(Address(parse_hex("02d3c8e736a9a50889766caf3c37bd16e2fecc7340b3130e25d4c01b153f996a10a78afc0e"), Type::Legacy)); -} \ No newline at end of file diff --git a/tests/EOS/AssetTests.cpp b/tests/EOS/AssetTests.cpp deleted file mode 100644 index 979d54e2550..00000000000 --- a/tests/EOS/AssetTests.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "EOS/Asset.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::EOS; - -TEST(EOSAsset, Serialization) { - Data buf; - Asset(5000, 3, "BRAVO").serialize(buf); - ASSERT_EQ(hex(buf), "881300000000000003425241564f0000"); - - buf.clear(); - Asset(90000, 3, "BRAVO").serialize(buf); - ASSERT_EQ(hex(buf), "905f01000000000003425241564f0000"); - - buf.clear(); - Asset(1000, 3, "BRAVO").serialize(buf); - ASSERT_EQ(hex(buf), "e80300000000000003425241564f0000"); - - std::string assetStr = "3.141 PI"; - ASSERT_EQ(Asset::fromString(assetStr).string(), assetStr); - - // add tests for negative amounts, fractional amounts -} diff --git a/tests/EOS/NameTests.cpp b/tests/EOS/NameTests.cpp deleted file mode 100644 index 76cd5cb5e52..00000000000 --- a/tests/EOS/NameTests.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "EOS/Name.h" -#include "HexCoding.h" -#include "PrivateKey.h" - -#include - -using namespace TW; -using namespace TW::EOS; - -TEST(EOSName, Invalid) { - ASSERT_THROW(Name(std::string(14, 'a')), std::invalid_argument); - - std::string invalidNames[] = {"Alice", "alice16", "12345satoshis"}; - for(auto name: invalidNames) { - ASSERT_FALSE(Name(name).string() == name); - } -} - -TEST(EOSName, Valid) { - ASSERT_NO_THROW(Name(std::string(13, 'a'))); - - std::string validName = "satoshis12345"; - ASSERT_EQ(Name(validName).string(), validName); - Data buf; - Name(validName).serialize(buf); - ASSERT_EQ(hex(buf), "458608d8354cb3c1"); -} \ No newline at end of file diff --git a/tests/EOS/SignatureTests.cpp b/tests/EOS/SignatureTests.cpp deleted file mode 100644 index 28bb3ee51a2..00000000000 --- a/tests/EOS/SignatureTests.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "EOS/Transaction.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::EOS; - -TEST(EOSSignature, Serialization) { - Data buf; - Signature *sig = new Signature(parse_hex("1f14262320d5b145220fb94d8fe204117edd25e52bbe9557b6e0909dd00307af266f5be1deef001446979523ac9de32c7eae5e5be4180b5a60c0e6bf14b2dd3e05"), Type::ModernK1); - sig->serialize(buf); - - ASSERT_EQ( - hex(buf), - "001f14262320d5b145220fb94d8fe204117edd25e52bbe9557b6e0909dd00307af266f5be1deef001446979523ac9de32c7eae5e5be4180b5a60c0e6bf14b2dd3e05" - ); - - ASSERT_EQ( - sig->string(), - "SIG_K1_JwtfgsdSx5RuF5aejedQ7FJTexaKMrQyYosPUWUrU1mzdLx6JUgLTZJd7zWA8q8VdnXht3YmVt7jafmD2eEK7hTRpT9rY5" - ); - - delete sig; - sig = new Signature(parse_hex("1f5c419d16f573ddbf07d2eb959621f690f9cb856ea2d113e3af02b3b40005488410e82ffa37a079e119844d213f4eb066a640507db68851752bea6e61eb864d84"), Type::ModernR1); - buf.clear(); - sig->serialize(buf); - - ASSERT_EQ( - hex(buf), - "011f5c419d16f573ddbf07d2eb959621f690f9cb856ea2d113e3af02b3b40005488410e82ffa37a079e119844d213f4eb066a640507db68851752bea6e61eb864d84" - ); - - ASSERT_EQ( - sig->string(), - "SIG_R1_K7KpdLYqa6ebCP22TuiYAY9YoJh1dTWTZEVkdPzdoadFL6f8PkMYk5N8wtsF11cneEJ91XnEZP6wDJHhRyqr1fr68ouYcz" - ); - - delete sig; - ASSERT_THROW(sig = new Signature(parse_hex("1f5c419d16f573ddbf07d2eb959621f690f9cb856ea2d113e3af02b3b40005488410e82ffa37a079e119844d213f4eb066a640507db68851752bea6e61eb864d84"), Type::Legacy), std::invalid_argument); - ASSERT_THROW(sig = new Signature(parse_hex("011f5c419d16f573ddbf07d2eb959621f690f9cb856ea2d113e3af02b3b40005488410e82ffa37a079e119844d213f4eb066a640507db68851752bea6e61eb864d84"), Type::ModernR1), std::invalid_argument); -} diff --git a/tests/EOS/TWAnySignerTests.cpp b/tests/EOS/TWAnySignerTests.cpp deleted file mode 100644 index 537ae52b042..00000000000 --- a/tests/EOS/TWAnySignerTests.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" -#include -#include "HexCoding.h" -#include "proto/EOS.pb.h" - -#include - -using namespace TW; -using namespace TW::EOS; - -TEST(TWAnySignerEOS, Sign) { - Proto::SigningInput input; - auto chainId = parse_hex("cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f"); - auto refBlock = parse_hex("000067d6f6a7e7799a1f3d487439a679f8cf95f1c986f35c0d2fa320f51a7144"); - auto key = parse_hex("559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd"); - - auto& asset = *input.mutable_asset(); - asset.set_amount(300000); - asset.set_decimals(4); - asset.set_symbol("TKN"); - - input.set_chain_id(chainId.data(), chainId.size()); - input.set_reference_block_id(refBlock.data(), refBlock.size()); - input.set_reference_block_time(1554209118); - input.set_currency("token"); - input.set_sender("token"); - input.set_recipient("eosio"); - input.set_memo("my second transfer"); - input.set_private_key(key.data(), key.size()); - input.set_private_key_type(Proto::KeyType::MODERNK1); - - { - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeEOS); - - EXPECT_EQ(output.error(), Common::Proto::OK); - EXPECT_EQ(output.json_encoded(), R"({"compression":"none","packed_context_free_data":"","packed_trx":"7c59a35cd6679a1f3d4800000000010000000080a920cd000000572d3ccdcd010000000080a920cd00000000a8ed3232330000000080a920cd0000000000ea3055e09304000000000004544b4e00000000126d79207365636f6e64207472616e7366657200","signatures":["SIG_K1_KfCdjsrTnx5cBpbA5cUdHZAsRYsnC9uKzuS1shFeqfMCfdZwX4PBm9pfHwGRT6ffz3eavhtkyNci5GoFozQAx8P8PBnDmj"]})"); - } - - input.set_private_key_type(Proto::KeyType::LEGACY); - { - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeEOS); - EXPECT_EQ(output.error(), Common::Proto::Error_internal); - EXPECT_TRUE(output.json_encoded().empty()); - } -} diff --git a/tests/EOS/TWCoinTypeTests.cpp b/tests/EOS/TWCoinTypeTests.cpp deleted file mode 100644 index 65412f55772..00000000000 --- a/tests/EOS/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWEOSCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeEOS)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeEOS, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeEOS, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeEOS)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeEOS)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeEOS), 4); - ASSERT_EQ(TWBlockchainEOS, TWCoinTypeBlockchain(TWCoinTypeEOS)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeEOS)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeEOS)); - assertStringsEqual(symbol, "EOS"); - assertStringsEqual(txUrl, "https://bloks.io/transaction/t123"); - assertStringsEqual(accUrl, "https://bloks.io/account/a12"); - assertStringsEqual(id, "eos"); - assertStringsEqual(name, "EOS"); -} diff --git a/tests/EOS/TransactionTests.cpp b/tests/EOS/TransactionTests.cpp deleted file mode 100644 index 50e8860658d..00000000000 --- a/tests/EOS/TransactionTests.cpp +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "EOS/Transaction.h" -#include "EOS/Signer.h" -#include "EOS/Action.h" -#include "EOS/Address.h" -#include "PrivateKey.h" -#include "HexCoding.h" -#include "Hash.h" - -#include - -using namespace TW; -using namespace TW::EOS; - -static std::string k1Sigs[5] { - "SIG_K1_KfCdjsrTnx5cBpbA5cUdHZAsRYsnC9uKzuS1shFeqfMCfdZwX4PBm9pfHwGRT6ffz3eavhtkyNci5GoFozQAx8P8PBnDmj", - "SIG_K1_K6wW678ngyWT7fgR4nNqm5XoKZBp9NDN4tKsctyzzADjXn15iAH9tcnQ393t6uvsqYxHKjdnxxduT1nyKMLiZbRqL7dHYr", - "SIG_K1_K6cUbZX6xfWcV5iotVprnf12Lc5AmV8SKmN5hVdv39gcM8wfEcwcNScvTuGLWpWzDT463dyhNmUfMB4nqt7tJVFnzx8mSi", - "SIG_K1_Khj7xhMd8HxrT6dUzuwiFM1MfMHtog5jCygJj7ADvdmUGkzZkmjymZXucEAud3whJ2qsMcxHcKtLWs8Ndm6be14qjTAH2a", - "SIG_K1_K93MjjE39CSH7kwJBgoRsSF2VaH6a8psQKU29nSg4xxxrVhz2iQuubyyB5t2ACZFFYSkNHSdYia5efhnW6Z9SPtbQTquMY" -}; - -static std::string r1Sigs[5] { - "SIG_R1_Kbywy4Mjun4Ymrh23Xk5yRtKJxcDWaDjQjLKERAny6Vs6oT1DYoEdoAj9AJK9iukHdEd9HBYnf3GmLtA55JFY5VaNszJMa", - "SIG_R1_KAhJJg4QGYBWY7hG6BKGAbW57fg6g8xTh3LG3Sss3bGv4BwiwHmRV1jsgh6hrnVRUoCaKMbJQzzWy9HXy6PnDmfJ6fbZMJ", - "SIG_R1_KxAwVKfpLr2MeK4aSAp5LSi2Vohsp94Uhk5UvZZDUJqd7ccBkhc2kYY1L6z5rjRNNo7BeP1Qr6H2xPFqo54YQ6DjczAqLW", - "SIG_R1_K1isJT8pJhkrHi3mcvrfY12nY6jirMCWaAHWuBXvu2ondcm3QHkgdaTwERskftZ9cqB5k2r8ajoYS4VWsiivjbd56D6pxX", - "SIG_R1_KWtgvnj2LaaYdtBTjM7bTR23LPBytDHFE7gPEfGTZ7PWc4yc6piPuPUHsVJVkvKmpW2gEUhq3toCfjkt34itSxMgekovdG" -}; - -TEST(EOSTransaction, Serialization) { - ASSERT_THROW(TransferAction("token", "eosio", "token", Asset::fromString("-20.1234 TKN"), "my first transfer"), std::invalid_argument); - - Data referenceBlockId = parse_hex("000046dc08ad384ca452d92c59348aba888fcbb6ef1ebffc3181617706664d4c"); - int32_t referenceBlockTime = 1554121728; - auto chainId = parse_hex("cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f"); - - Transaction tx {referenceBlockId, referenceBlockTime}; - tx.actions.push_back(TransferAction("token", "eosio", "token", Asset::fromString("20.1234 TKN"), "my first transfer")); - ASSERT_EQ(tx.actions.back().serialize().dump(), "{\"account\":\"token\",\"authorizations\":[{\"actor\":\"eosio\",\"permission\":\"active\"}],\"data\":\"0000000000ea30550000000080a920cd121203000000000004544b4e00000000116d79206669727374207472616e73666572\",\"name\":\"transfer\"}"); - - Data buf; - tx.serialize(buf); - - Signer signer {chainId}; - - ASSERT_EQ( - hex(buf), - "1e04a25cdc46a452d92c00000000010000000080a920cd000000572d3ccdcd010000000000ea305500000000a8ed3232320000000000ea30550000000080a920cd121203000000000004544b4e00000000116d79206669727374207472616e7366657200" - ); - - ASSERT_EQ( - hex(signer.hash(tx)), - "5de974bb90b940b462688609735a1dd522fa853aba765c30d14bedd27d719dd1" - ); - - // make transaction invalid and see if signing succeeds - tx.maxNetUsageWords = UINT32_MAX; - ASSERT_THROW(signer.sign(PrivateKey(Hash::sha256(std::string("A"))), Type::ModernK1, tx), std::invalid_argument); - - referenceBlockId = parse_hex("000067d6f6a7e7799a1f3d487439a679f8cf95f1c986f35c0d2fa320f51a7144"); - referenceBlockTime = 1554209118; - - Transaction tx2 {referenceBlockId, referenceBlockTime}; - tx2.actions.push_back(TransferAction("token", "token", "eosio", Asset::fromString("30.0000 TKN"), "my second transfer")); - - buf.clear(); - tx2.serialize(buf); - ASSERT_EQ( - hex(buf), - "7c59a35cd6679a1f3d4800000000010000000080a920cd000000572d3ccdcd010000000080a920cd00000000a8ed3232330000000080a920cd0000000000ea3055e09304000000000004544b4e00000000126d79207365636f6e64207472616e7366657200" - ); - - ASSERT_EQ( - hex(signer.hash(tx2)), - "4dac38a8ad7f095a09ec0eb0cbd060c9d8ea0a842535d369c9ce526cdf1b5d85" - ); - - ASSERT_NO_THROW(tx2.serialize()); - - // verify k1 sigs - for (int i = 0; i < 5; i++) { - PrivateKey pk(Hash::sha256(std::string(i + 1, 'A'))); - ASSERT_NO_THROW(signer.sign(pk, Type::ModernK1, tx2)); - - ASSERT_EQ( - tx2.signatures.back().string(), - k1Sigs[i] - ); - } - - // verify r1 sigs - for (int i = 0; i < 5; i++) { - PrivateKey pk(Hash::sha256(std::string(i + 1, 'A'))); - ASSERT_NO_THROW(signer.sign(pk, Type::ModernR1, tx2)); - - ASSERT_EQ( - tx2.signatures.back().string(), - r1Sigs[i] - ); - } -} - -TEST(EOSTransaction, formatDate) { - EXPECT_EQ(Transaction::formatDate(1554209148), "2019-04-02T12:45:48"); - EXPECT_EQ(Transaction::formatDate(1654160000), "2022-06-02T08:53:20"); - EXPECT_EQ(Transaction::formatDate(0), "1970-01-01T00:00:00"); - EXPECT_EQ(Transaction::formatDate(std::numeric_limits::max()), "2038-01-19T03:14:07"); -} diff --git a/tests/Elrond/AddressTests.cpp b/tests/Elrond/AddressTests.cpp deleted file mode 100644 index ef387f5ad79..00000000000 --- a/tests/Elrond/AddressTests.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include - -#include "HexCoding.h" -#include "PublicKey.h" -#include "PrivateKey.h" -#include "Elrond/Address.h" -#include "TestAccounts.h" - -using namespace TW; -using namespace TW::Elrond; - - -TEST(ElrondAddress, Valid) { - ASSERT_TRUE(Address::isValid(ALICE_BECH32)); - ASSERT_TRUE(Address::isValid(BOB_BECH32)); -} - -TEST(ElrondAddress, Invalid) { - ASSERT_FALSE(Address::isValid("")); - ASSERT_FALSE(Address::isValid("foo")); - ASSERT_FALSE(Address::isValid("10z9xdugayn528ksaesdwlhf006fw5sg2qmmm0h52fvxczwgesyvq5pwemr")); - ASSERT_FALSE(Address::isValid("xerd10z9xdugayn528ksaesdwlhf006fw5sg2qmmm0h52fvxczwgesyvq5pwemr")); - ASSERT_FALSE(Address::isValid("foo10z9xdugayn528ksaesdwlhf006fw5sg2qmmm0h52fvxczwgesyvq5pwemr")); - ASSERT_FALSE(Address::isValid(ALICE_PUBKEY_HEX)); -} - -TEST(ElrondAddress, FromString) { - Address alice, bob, carol; - ASSERT_TRUE(Address::decode(ALICE_BECH32, alice)); - ASSERT_TRUE(Address::decode(BOB_BECH32, bob)); - - ASSERT_EQ(ALICE_PUBKEY_HEX, hex(alice.getKeyHash())); - ASSERT_EQ(BOB_PUBKEY_HEX, hex(bob.getKeyHash())); -} - -TEST(ElrondAddress, FromData) { - const auto alice = Address(parse_hex(ALICE_PUBKEY_HEX)); - const auto bob = Address(parse_hex(BOB_PUBKEY_HEX)); - - ASSERT_EQ(ALICE_BECH32, alice.string()); - ASSERT_EQ(BOB_BECH32, bob.string()); -} - -TEST(ElrondAddress, FromPrivateKey) { - auto aliceKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - auto alice = Address(aliceKey.getPublicKey(TWPublicKeyTypeED25519)); - ASSERT_EQ(ALICE_BECH32, alice.string()); - - auto bobKey = PrivateKey(parse_hex(BOB_SEED_HEX)); - auto bob = Address(bobKey.getPublicKey(TWPublicKeyTypeED25519)); - ASSERT_EQ(BOB_BECH32, bob.string()); -} - -TEST(ElrondAddress, FromPublicKey) { - auto alice = PublicKey(parse_hex(ALICE_PUBKEY_HEX), TWPublicKeyTypeED25519); - ASSERT_EQ(ALICE_BECH32, Address(alice).string()); - - auto bob = PublicKey(parse_hex(BOB_PUBKEY_HEX), TWPublicKeyTypeED25519); - ASSERT_EQ(BOB_BECH32, Address(bob).string()); -} diff --git a/tests/Elrond/SerializationTests.cpp b/tests/Elrond/SerializationTests.cpp deleted file mode 100644 index 3da9803f7e5..00000000000 --- a/tests/Elrond/SerializationTests.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include -#include "boost/format.hpp" - -#include "HexCoding.h" -#include "Elrond/Serialization.h" -#include "TestAccounts.h" - -using namespace TW; -using namespace TW::Elrond; - -TEST(ElrondSerialization, SignableString) { - Transaction transaction; - transaction.nonce = 42; - transaction.value = "43"; - transaction.sender = "alice"; - transaction.receiver = "bob"; - transaction.data = "foo"; - transaction.chainID = "1"; - transaction.version = 1; - - string jsonString = serializeTransaction(transaction); - ASSERT_EQ(R"({"nonce":42,"value":"43","receiver":"bob","sender":"alice","gasPrice":0,"gasLimit":0,"data":"Zm9v","chainID":"1","version":1})", jsonString); -} - -TEST(ElrondSerialization, SignableStringWithRealData) { - Transaction transaction; - transaction.nonce = 15; - transaction.value = "100"; - transaction.sender = ALICE_BECH32; - transaction.receiver = BOB_BECH32; - transaction.gasPrice = 1000000000; - transaction.gasLimit = 50000; - transaction.data = "foo"; - transaction.chainID = "1"; - transaction.version = 1; - - string expected = (boost::format(R"({"nonce":15,"value":"100","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1})") % BOB_BECH32 % ALICE_BECH32).str(); - string actual = serializeTransaction(transaction); - ASSERT_EQ(expected, actual); -} - -TEST(ElrondSerialization, SignableStringWithoutData) { - Transaction transaction; - transaction.nonce = 42; - transaction.value = "43"; - transaction.sender = "feed"; - transaction.receiver = "abba"; - transaction.chainID = "1"; - transaction.version = 1; - - string jsonString = serializeTransaction(transaction); - ASSERT_EQ(R"({"nonce":42,"value":"43","receiver":"abba","sender":"feed","gasPrice":0,"gasLimit":0,"chainID":"1","version":1})", jsonString); -} diff --git a/tests/Elrond/SignerTests.cpp b/tests/Elrond/SignerTests.cpp deleted file mode 100644 index 88540c19392..00000000000 --- a/tests/Elrond/SignerTests.cpp +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include "boost/format.hpp" - -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include "Elrond/Signer.h" -#include "Elrond/Address.h" -#include "TestAccounts.h" - -using namespace TW; -using namespace TW::Elrond; - - -TEST(ElrondSigner, SignGenericAction) { - auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); - input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); - input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); - input.mutable_generic_action()->set_value("0"); - input.mutable_generic_action()->set_data("foo"); - input.mutable_generic_action()->set_version(1); - input.set_gas_price(1000000000); - input.set_gas_limit(50000); - input.set_chain_id("1"); - - auto output = Signer::sign(input); - auto signature = output.signature(); - auto encoded = output.encoded(); - auto expectedSignature = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700"; - auto expectedEncoded = (boost::format(R"({"nonce":7,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); - - ASSERT_EQ(expectedEncoded, encoded); - ASSERT_EQ(expectedSignature, signature); -} - -TEST(ElrondSigner, SignGenericActionJSON) { - // Shuffle some fields, assume arbitrary order in the input - auto input = (boost::format(R"({"genericAction" : {"accounts": {"senderNonce": 7, "receiver": "%1%", "sender": "%2%"}, "data": "foo", "value": "0", "version": 1}, "gasPrice": 1000000000, "gasLimit": 50000, "chainId": "1"})") % BOB_BECH32 % ALICE_BECH32).str(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - - auto encoded = Signer::signJSON(input, privateKey.bytes); - auto expectedSignature = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700"; - auto expectedEncoded = (boost::format(R"({"nonce":7,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); - - ASSERT_EQ(expectedEncoded, encoded); -} - -TEST(ElrondSigner, SignWithoutData) { - auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(0); - input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); - input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); - input.mutable_generic_action()->set_value("0"); - input.mutable_generic_action()->set_data(""); - input.mutable_generic_action()->set_version(1); - input.set_gas_price(1000000000); - input.set_gas_limit(50000); - input.set_chain_id("1"); - - auto output = Signer::sign(input); - auto signature = output.signature(); - auto encoded = output.encoded(); - auto expectedSignature = "c7253b821c68011584ebd3a5bb050ade19235c2d10260e411e523105826c40a79849b3eeb96fcc2a7a6b1fa140b6756f50b249e005be056ce0cf53125e0b1b00"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); - - ASSERT_EQ(expectedSignature, signature); - ASSERT_EQ(expectedEncoded, encoded); -} - -TEST(ElrondSigner, SignJSONWithoutData) { - // Shuffle some fields, assume arbitrary order in the input - auto input = (boost::format(R"({"genericAction" : {"accounts": {"senderNonce": 0, "receiver": "%1%", "sender": "%2%"}, "value": "0", "version": 1}, "gasPrice": 1000000000, "gasLimit": 50000, "chainId": "1"})") % BOB_BECH32 % ALICE_BECH32).str(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - - auto encoded = Signer::signJSON(input, privateKey.bytes); - auto expectedSignature = "c7253b821c68011584ebd3a5bb050ade19235c2d10260e411e523105826c40a79849b3eeb96fcc2a7a6b1fa140b6756f50b249e005be056ce0cf53125e0b1b00"; - auto expectedEncoded = (boost::format(R"({"nonce":0,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); - - ASSERT_EQ(expectedEncoded, encoded); -} - -TEST(ElrondSigner, SignWithUsernames) { - // https://github.com/ElrondNetwork/elrond-go/blob/master/examples/construction_test.go, scenario "TestConstructTransaction_Usernames". - - auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(89); - input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); - input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); - input.mutable_generic_action()->mutable_accounts()->set_sender_username("alice"); - input.mutable_generic_action()->mutable_accounts()->set_receiver_username("bob"); - input.mutable_generic_action()->set_value("0"); - input.mutable_generic_action()->set_data(""); - input.mutable_generic_action()->set_version(1); - input.set_gas_price(1000000000); - input.set_gas_limit(50000); - input.set_chain_id("local-testnet"); - - auto output = Signer::sign(input); - auto signature = output.signature(); - auto encoded = output.encoded(); - auto expectedSignature = "1bed82c3f91c9d24f3a163e7b93a47453d70e8743201fe7d3656c0214569566a76503ef0968279ac942ca43b9c930bd26638dfb075a220ce80b058ab7bca140a"; - auto expectedEncoded = - ( - boost::format(R"({"nonce":89,"value":"0","receiver":"%1%","sender":"%2%","senderUsername":"%3%","receiverUsername":"%4%","gasPrice":1000000000,"gasLimit":50000,"chainID":"local-testnet","version":1,"signature":"%5%"})") - % BOB_BECH32 - % ALICE_BECH32 - // "alice" - % "YWxpY2U=" - // "bob" - % "Ym9i" - % expectedSignature - ).str(); - - ASSERT_EQ(expectedSignature, signature); - ASSERT_EQ(expectedEncoded, encoded); -} - -TEST(ElrondSigner, SignWithOptions) { - auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(89); - input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); - input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); - input.mutable_generic_action()->set_value("0"); - input.mutable_generic_action()->set_data(""); - input.mutable_generic_action()->set_version(1); - // We'll set a dummy value on the "options" field (merely an example). - // Currently, the "options" field should be ignored (not set) by applications using TW Core. - // In the future, TW Core will handle specific transaction options - // (such as the "SignedWithHash" flag, as seen in https://github.com/ElrondNetwork/elrond-go-core/blob/main/core/versioning/txVersionChecker.go) - // when building and signing transactions. - input.mutable_generic_action()->set_options(42); - input.set_gas_price(1000000000); - input.set_gas_limit(50000); - input.set_chain_id("local-testnet"); - - auto output = Signer::sign(input); - auto signature = output.signature(); - auto encoded = output.encoded(); - auto expectedSignature = "d9a624f13960ae1cc471de48bdb43b101b9d469bb8b159f68bb629bb32d0109e1acfebb62d6d2fc5786c0b85f9e7ce2caff74988864a8285f34797c5a5fa5801"; - auto expectedEncoded = - ( - boost::format(R"({"nonce":89,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainID":"local-testnet","version":1,"options":42,"signature":"%3%"})") - % BOB_BECH32 - % ALICE_BECH32 - % expectedSignature - ).str(); - - ASSERT_EQ(expectedEncoded, encoded); - ASSERT_EQ(expectedSignature, signature); -} - -TEST(ElrondSigner, SignEGLDTransfer) { - auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - input.mutable_egld_transfer()->mutable_accounts()->set_sender_nonce(7); - input.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); - input.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); - input.mutable_egld_transfer()->set_amount("1000000000000000000"); - - auto output = Signer::sign(input); - auto signature = output.signature(); - auto encoded = output.encoded(); - auto expectedSignature = "7e1c4c63b88ea72dcf7855a54463b1a424eb357ac3feb4345221e512ce07c7a50afb6d7aec6f480b554e32cf2037082f3bc17263d1394af1f3ef240be53c930b"; - auto expectedEncoded = - ( - boost::format(R"({"nonce":7,"value":"1000000000000000000","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1,"signature":"%3%"})") - % BOB_BECH32 - % ALICE_BECH32 - % expectedSignature - ).str(); - - ASSERT_EQ(expectedSignature, signature); - ASSERT_EQ(expectedEncoded, encoded); -} - -TEST(ElrondSigner, SignESDTTransfer) { - auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - input.mutable_esdt_transfer()->mutable_accounts()->set_sender_nonce(7); - input.mutable_esdt_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); - input.mutable_esdt_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); - input.mutable_esdt_transfer()->set_token_identifier("MYTOKEN-1234"); - input.mutable_esdt_transfer()->set_amount("10000000000000"); - - auto output = Signer::sign(input); - auto signature = output.signature(); - auto encoded = output.encoded(); - auto expectedSignature = "9add6d9ac3f1a1fddb07b934e8a73cad3b8c232bdf29d723c1b38ad619905f03e864299d06eb3fe3bbb48a9f1d9b7f14e21dc5eaffe0c87f5718ad0c4198bb0c"; - auto expectedEncoded = - ( - boost::format(R"({"nonce":7,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":425000,"data":"%3%","chainID":"1","version":1,"signature":"%4%"})") - % BOB_BECH32 - % ALICE_BECH32 - // "ESDTTransfer@4d59544f4b454e2d31323334@09184e72a000" - % "RVNEVFRyYW5zZmVyQDRkNTk1NDRmNGI0NTRlMmQzMTMyMzMzNEAwOTE4NGU3MmEwMDA=" - % expectedSignature - ).str(); - - ASSERT_EQ(expectedSignature, signature); - ASSERT_EQ(expectedEncoded, encoded); -} - -TEST(ElrondSigner, SignESDTNFTTransfer) { - auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - input.mutable_esdtnft_transfer()->mutable_accounts()->set_sender_nonce(7); - input.mutable_esdtnft_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); - input.mutable_esdtnft_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); - input.mutable_esdtnft_transfer()->set_token_collection("LKMEX-aab910"); - input.mutable_esdtnft_transfer()->set_token_nonce(4); - input.mutable_esdtnft_transfer()->set_amount("184300000000000000"); - - auto output = Signer::sign(input); - auto signature = output.signature(); - auto encoded = output.encoded(); - auto expectedSignature = "cc935685d5b31525e059a16a832cba98dee751983a5a93de4198f6553a2c55f5f1e0b4300fe9077376fa754546da0b0f6697e66462101a209aafd0fc775ab60a"; - auto expectedEncoded = - ( - boost::format(R"({"nonce":7,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":937500,"data":"%3%","chainID":"1","version":1,"signature":"%4%"})") - % ALICE_BECH32 - % ALICE_BECH32 - // "ESDTNFTTransfer@4c4b4d45582d616162393130@04@028ec3dfa01ac000@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8" - % "RVNEVE5GVFRyYW5zZmVyQDRjNGI0ZDQ1NTgyZDYxNjE2MjM5MzEzMEAwNEAwMjhlYzNkZmEwMWFjMDAwQDgwNDlkNjM5ZTVhNjk4MGQxY2QyMzkyYWJjY2U0MTAyOWNkYTc0YTE1NjM1MjNhMjAyZjA5NjQxY2MyNjE4Zjg=" - % expectedSignature - ).str(); - - ASSERT_EQ(expectedSignature, signature); - ASSERT_EQ(expectedEncoded, encoded); -} diff --git a/tests/Elrond/TWAnySignerTests.cpp b/tests/Elrond/TWAnySignerTests.cpp deleted file mode 100644 index 380c2c7de7b..00000000000 --- a/tests/Elrond/TWAnySignerTests.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include "boost/format.hpp" - -#include -#include "HexCoding.h" -#include "../interface/TWTestUtilities.h" -#include "Elrond/Signer.h" -#include "TestAccounts.h" - -using namespace TW; -using namespace TW::Elrond; - - -TEST(TWAnySignerElrond, Sign) { - auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); - input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); - input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); - input.mutable_generic_action()->set_value("0"); - input.mutable_generic_action()->set_data("foo"); - input.mutable_generic_action()->set_version(1); - input.set_gas_price(1000000000); - input.set_gas_limit(50000); - input.set_chain_id("1"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeElrond); - - auto signature = output.signature(); - auto encoded = output.encoded(); - auto expectedSignature = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700"; - auto expectedEncoded = (boost::format(R"({"nonce":7,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); - - ASSERT_EQ(expectedSignature, signature); - ASSERT_EQ(expectedEncoded, encoded); -} - -TEST(TWAnySignerElrond, SignJSON) { - // Shuffle some fields, assume arbitrary order in the input - auto input = STRING((boost::format(R"({"genericAction" : {"accounts": {"senderNonce": 7, "receiver": "%1%", "sender": "%2%"}, "data": "foo", "value": "0", "version": 1}, "gasPrice": 1000000000, "gasLimit": 50000, "chainId": "1"})") % BOB_BECH32 % ALICE_BECH32).str().c_str()); - auto privateKey = DATA(ALICE_SEED_HEX); - auto encoded = WRAPS(TWAnySignerSignJSON(input.get(), privateKey.get(), TWCoinTypeElrond)); - auto expectedSignature = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700"; - auto expectedEncoded = (boost::format(R"({"nonce":7,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignature).str(); - - ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeElrond)); - assertStringsEqual(encoded, expectedEncoded.c_str()); -} diff --git a/tests/Elrond/TWCoinTypeTests.cpp b/tests/Elrond/TWCoinTypeTests.cpp deleted file mode 100644 index 900d36abd6d..00000000000 --- a/tests/Elrond/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWElrondCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeElrond)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("1fc9785cb8bea0129a16cf7bddc97630c176a556ea566f0e72923c882b5cb3c8")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeElrond, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("erd12yne790km8ezwetkz7m3hmqy9utdc6vdkgsunfzpwguec6v04p2qtk9uqj")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeElrond, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeElrond)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeElrond)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeElrond), 18); - ASSERT_EQ(TWBlockchainElrondNetwork, TWCoinTypeBlockchain(TWCoinTypeElrond)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeElrond)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeElrond)); - assertStringsEqual(symbol, "eGLD"); - assertStringsEqual(txUrl, "https://explorer.elrond.com/transactions/1fc9785cb8bea0129a16cf7bddc97630c176a556ea566f0e72923c882b5cb3c8"); - assertStringsEqual(accUrl, "https://explorer.elrond.com/address/erd12yne790km8ezwetkz7m3hmqy9utdc6vdkgsunfzpwguec6v04p2qtk9uqj"); - assertStringsEqual(id, "elrond"); - assertStringsEqual(name, "Elrond"); -} diff --git a/tests/Elrond/TestAccounts.h b/tests/Elrond/TestAccounts.h deleted file mode 100644 index 80e45e638c3..00000000000 --- a/tests/Elrond/TestAccounts.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -// Well-known accounts on Testnet & Devnet, -// https://github.com/ElrondNetwork/elrond-sdk-erdpy/tree/main/erdpy/testnet/wallets/users: -const auto ALICE_BECH32 = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; -const auto ALICE_PUBKEY_HEX = "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1"; -const auto ALICE_SEED_HEX = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"; -const auto BOB_BECH32 = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"; -const auto BOB_PUBKEY_HEX = "8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8"; -const auto BOB_SEED_HEX = "b8ca6f8203fb4b545a8e83c5384da033c415db155b53fb5b8eba7ff5a039d639"; diff --git a/tests/Elrond/TransactionFactoryTests.cpp b/tests/Elrond/TransactionFactoryTests.cpp deleted file mode 100644 index 6ae9d13d176..00000000000 --- a/tests/Elrond/TransactionFactoryTests.cpp +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include -#include "boost/format.hpp" - -#include "Elrond/TransactionFactory.h" -#include "TestAccounts.h" - -using namespace TW; -using namespace TW::Elrond; - -TEST(ElrondTransactionFactory, fromEGLDTransfer) { - auto input = Proto::SigningInput(); - input.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); - input.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); - input.mutable_egld_transfer()->set_amount("1000000000000000000"); - - TransactionFactory factory; - Transaction transaction = factory.fromEGLDTransfer(input); - - ASSERT_EQ(ALICE_BECH32, transaction.sender); - ASSERT_EQ(BOB_BECH32, transaction.receiver); - ASSERT_EQ("", transaction.data); - ASSERT_EQ("1000000000000000000", transaction.value); - ASSERT_EQ(50000, transaction.gasLimit); - ASSERT_EQ(1000000000, transaction.gasPrice); - ASSERT_EQ("1", transaction.chainID); - ASSERT_EQ(1, transaction.version); -} - -TEST(ElrondTransactionFactory, fromESDTTransfer) { - auto input = Proto::SigningInput(); - input.mutable_esdt_transfer()->mutable_accounts()->set_sender_nonce(7); - input.mutable_esdt_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); - input.mutable_esdt_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); - input.mutable_esdt_transfer()->set_token_identifier("MYTOKEN-1234"); - input.mutable_esdt_transfer()->set_amount("10000000000000"); - - TransactionFactory factory; - Transaction transaction = factory.fromESDTTransfer(input); - - ASSERT_EQ(ALICE_BECH32, transaction.sender); - ASSERT_EQ(BOB_BECH32, transaction.receiver); - ASSERT_EQ("ESDTTransfer@4d59544f4b454e2d31323334@09184e72a000", transaction.data); - ASSERT_EQ("0", transaction.value); - ASSERT_EQ(425000, transaction.gasLimit); - ASSERT_EQ(1000000000, transaction.gasPrice); - ASSERT_EQ("1", transaction.chainID); - ASSERT_EQ(1, transaction.version); -} - -TEST(ElrondTransactionFactory, fromESDTNFTTransfer) { - auto input = Proto::SigningInput(); - input.mutable_esdtnft_transfer()->mutable_accounts()->set_sender_nonce(7); - input.mutable_esdtnft_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); - input.mutable_esdtnft_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); - input.mutable_esdtnft_transfer()->set_token_collection("LKMEX-aab910"); - input.mutable_esdtnft_transfer()->set_token_nonce(4); - input.mutable_esdtnft_transfer()->set_amount("184300000000000000"); - - TransactionFactory factory; - Transaction transaction = factory.fromESDTNFTTransfer(input); - - ASSERT_EQ(ALICE_BECH32, transaction.sender); - ASSERT_EQ(ALICE_BECH32, transaction.receiver); - ASSERT_EQ("ESDTNFTTransfer@4c4b4d45582d616162393130@04@028ec3dfa01ac000@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", transaction.data); - ASSERT_EQ("0", transaction.value); - ASSERT_EQ(937500, transaction.gasLimit); - ASSERT_EQ(1000000000, transaction.gasPrice); - ASSERT_EQ("1", transaction.chainID); - ASSERT_EQ(1, transaction.version); -} - -TEST(ElrondTransactionFactory, createTransfersWithProvidedNetworkConfig) { - NetworkConfig networkConfig; - - // Set dummy values: - networkConfig.setChainId("T"); - networkConfig.setMinGasPrice(1500000000); - networkConfig.setMinGasLimit(60000); - networkConfig.setGasPerDataByte(2000); - networkConfig.setGasCostESDTTransfer(300000); - networkConfig.setGasCostESDTNFTTransfer(300000); - - Proto::SigningInput signingInputWithEGLDTransfer; - signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); - signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); - signingInputWithEGLDTransfer.mutable_egld_transfer()->set_amount("0"); - - Proto::SigningInput signingInputWithESDTTransfer; - signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); - signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); - signingInputWithESDTTransfer.mutable_esdt_transfer()->set_token_identifier("MYTOKEN-1234"); - signingInputWithESDTTransfer.mutable_esdt_transfer()->set_amount("10000000000000"); - - Proto::SigningInput signingInputWithESDTNFTTransfer; - signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); - signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); - signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_token_collection("LKMEX-aab910"); - signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_token_nonce(4); - signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_amount("184300000000000000"); - - TransactionFactory factory(networkConfig); - Transaction tx1 = factory.fromEGLDTransfer(signingInputWithEGLDTransfer); - Transaction tx2 = factory.fromESDTTransfer(signingInputWithESDTTransfer); - Transaction tx3 = factory.fromESDTNFTTransfer(signingInputWithESDTNFTTransfer); - - ASSERT_EQ(60000, tx1.gasLimit); - ASSERT_EQ(1500000000, tx1.gasPrice); - ASSERT_EQ("T", tx1.chainID); - - ASSERT_EQ(560000, tx2.gasLimit); - ASSERT_EQ(1500000000, tx2.gasPrice); - ASSERT_EQ("T", tx2.chainID); - - ASSERT_EQ(1110000, tx3.gasLimit); - ASSERT_EQ(1500000000, tx3.gasPrice); - ASSERT_EQ("T", tx3.chainID); -} - -TEST(ElrondTransactionFactory, createTransfersWithOverriddenNetworkParameters) { - Proto::SigningInput signingInputWithEGLDTransfer; - signingInputWithEGLDTransfer.set_gas_limit(50500); - signingInputWithEGLDTransfer.set_gas_price(1000000001); - signingInputWithEGLDTransfer.set_chain_id("A"); - - Proto::SigningInput signingInputWithESDTTransfer; - signingInputWithESDTTransfer.set_gas_limit(5000000); - signingInputWithESDTTransfer.set_gas_price(1000000002); - signingInputWithESDTTransfer.set_chain_id("B"); - - Proto::SigningInput signingInputWithESDTNFTTransfer; - signingInputWithESDTNFTTransfer.set_gas_limit(10000000); - signingInputWithESDTNFTTransfer.set_gas_price(1000000003); - signingInputWithESDTNFTTransfer.set_chain_id("C"); - - TransactionFactory factory; - Transaction tx1 = factory.fromEGLDTransfer(signingInputWithEGLDTransfer); - Transaction tx2 = factory.fromESDTTransfer(signingInputWithESDTTransfer); - Transaction tx3 = factory.fromESDTNFTTransfer(signingInputWithESDTNFTTransfer); - - ASSERT_EQ(50500, tx1.gasLimit); - ASSERT_EQ(1000000001, tx1.gasPrice); - ASSERT_EQ("A", tx1.chainID); - - ASSERT_EQ(5000000, tx2.gasLimit); - ASSERT_EQ(1000000002, tx2.gasPrice); - ASSERT_EQ("B", tx2.chainID); - - ASSERT_EQ(10000000, tx3.gasLimit); - ASSERT_EQ(1000000003, tx3.gasPrice); - ASSERT_EQ("C", tx3.chainID); -} - -TEST(ElrondTransactionFactory, create) { - Proto::SigningInput signingInputWithGenericAction; - signingInputWithGenericAction.mutable_generic_action()->set_data("hello"); - - Proto::SigningInput signingInputWithEGLDTransfer; - signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); - signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); - signingInputWithEGLDTransfer.mutable_egld_transfer()->set_amount("1"); - - Proto::SigningInput signingInputWithESDTTransfer; - signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); - signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); - signingInputWithESDTTransfer.mutable_esdt_transfer()->set_token_identifier("MYTOKEN-1234"); - signingInputWithESDTTransfer.mutable_esdt_transfer()->set_amount("10000000000000"); - - Proto::SigningInput signingInputWithESDTNFTTransfer; - signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); - signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); - signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_token_collection("LKMEX-aab910"); - signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_token_nonce(4); - signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_amount("184300000000000000"); - - TransactionFactory factory; - Transaction tx1 = factory.create(signingInputWithGenericAction); - Transaction tx2 = factory.create(signingInputWithEGLDTransfer); - Transaction tx3 = factory.create(signingInputWithESDTTransfer); - Transaction tx4 = factory.create(signingInputWithESDTNFTTransfer); - - ASSERT_EQ("hello", tx1.data); - ASSERT_EQ("1", tx2.value); - ASSERT_EQ("ESDTTransfer@4d59544f4b454e2d31323334@09184e72a000", tx3.data); - ASSERT_EQ("ESDTNFTTransfer@4c4b4d45582d616162393130@04@028ec3dfa01ac000@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", tx4.data); -} diff --git a/tests/Ethereum/AbiStructTests.cpp b/tests/Ethereum/AbiStructTests.cpp deleted file mode 100644 index 5888f1d1c37..00000000000 --- a/tests/Ethereum/AbiStructTests.cpp +++ /dev/null @@ -1,908 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Ethereum/ABI.h" -#include "Ethereum/Address.h" -#include "Ethereum/Signer.h" -#include -#include -#include "../interface/TWTestUtilities.h" - -#include -#include - -using namespace TW::Ethereum::ABI; -using namespace TW::Ethereum; -using namespace TW; - -extern std::string TESTS_ROOT; - -std::string load_file(const std::string path) { - std::ifstream stream(path); - std::string content((std::istreambuf_iterator(stream)), (std::istreambuf_iterator())); - return content; -} - -// https://github.com/MetaMask/eth-sig-util/blob/main/test/index.ts - -ParamStruct msgPersonCow2("Person", std::vector>{ - std::make_shared("name", std::make_shared("Cow")), - std::make_shared("wallets", std::make_shared(std::vector>{ - std::make_shared(parse_hex("CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826")), - std::make_shared(parse_hex("DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF")) - })) -}); -ParamStruct msgPersonBob3("Person", std::vector>{ - std::make_shared("name", std::make_shared("Bob")), - std::make_shared("wallets", std::make_shared(std::vector>{ - std::make_shared(parse_hex("bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB")), - std::make_shared(parse_hex("B0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57")), - std::make_shared(parse_hex("B0B0b0b0b0b0B000000000000000000000000000")) - })) -}); -ParamStruct msgGroup("Group", std::vector>{ - std::make_shared("name", std::make_shared("")), - std::make_shared("members", std::make_shared(std::vector>{ - std::make_shared(msgPersonCow2) - })) -}); -ParamStruct msgMailCow1Bob1("Mail", std::vector>{ - std::make_shared("from", std::make_shared("Person", std::vector>{ - std::make_shared("name", std::make_shared("Cow")), - std::make_shared("wallet", std::make_shared(parse_hex("CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"))) - })), - std::make_shared("to", std::make_shared("Person", std::vector>{ - std::make_shared("name", std::make_shared("Bob")), - std::make_shared("wallet", std::make_shared(parse_hex("bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"))) - })), - std::make_shared("contents", std::make_shared("Hello, Bob!")) -}); -ParamStruct msgMailCow2Bob3("Mail", std::vector>{ - std::make_shared("from", std::make_shared(msgPersonCow2)), - std::make_shared("to", std::make_shared(std::make_shared(msgPersonBob3))), - std::make_shared("contents", std::make_shared("Hello, Bob!")) -}); -ParamStruct msgEIP712Domain("EIP712Domain", std::vector>{ - std::make_shared("name", std::make_shared("Ether Mail")), - std::make_shared("version", std::make_shared("1")), - std::make_shared("chainId", std::make_shared(1)), - std::make_shared("verifyingContract", std::make_shared(parse_hex("CcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"))) -}); - -PrivateKey privateKeyCow = PrivateKey(Hash::keccak256(TW::data("cow"))); -PrivateKey privateKeyDragon = PrivateKey(Hash::keccak256(TW::data("dragon"))); -PrivateKey privateKeyOilTimes12 = PrivateKey(parse_hex("b0f20d59451a2fac1be6d458e036adfa5d83ebd4c21f9a76de3c4a3a65671eba")); // 0x60c2A43Cc69658eC4b02a65A07623D7192166F4e - -// See 'signedTypeData' in https://github.com/MetaMask/eth-sig-util/blob/main/test/index.ts -TEST(EthereumAbiStruct, encodeTypes) { - EXPECT_EQ(msgMailCow1Bob1.encodeType(), "Mail(Person from,Person to,string contents)Person(string name,address wallet)"); - - EXPECT_EQ(hex(msgMailCow1Bob1.hashType()), "a0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2"); - - EXPECT_EQ(hex(msgMailCow1Bob1.encodeHashes()), "a0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8"); - - EXPECT_EQ(hex(msgMailCow1Bob1.hashStruct()), "c52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e"); - - EXPECT_EQ(hex(msgEIP712Domain.hashStruct()), "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"); - - Address address = Address(privateKeyCow.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); - EXPECT_EQ(address.string(), "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"); -} - -TEST(EthereumAbiStruct, encodeTypes_Json) { - auto hash = ParamStruct::hashStructJson( - R"({ - "types": { - "EIP712Domain": [ - {"name": "name", "type": "string"}, - {"name": "version", "type": "string"}, - {"name": "chainId", "type": "uint256"}, - {"name": "verifyingContract", "type": "address"} - ], - "Person": [ - {"name": "name", "type": "string"}, - {"name": "wallet", "type": "address"} - ], - "Mail": [ - {"name": "from", "type": "Person"}, - {"name": "to", "type": "Person"}, - {"name": "contents", "type": "string"} - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": "0x01", - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" - } - })"); - ASSERT_EQ(hex(hash), "be609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyCow, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d"); - EXPECT_EQ(hex(store(rsv.s)), "07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b91562"); - EXPECT_EQ(hex(store(rsv.v)), "1c"); -} - -// See 'signedTypeData with V3 string' in https://github.com/MetaMask/eth-sig-util/blob/main/test/index.ts -TEST(EthereumAbiStruct, encodeTypes_v3) { - EXPECT_EQ(msgMailCow1Bob1.encodeType(), "Mail(Person from,Person to,string contents)Person(string name,address wallet)"); - - EXPECT_EQ(hex(msgMailCow1Bob1.hashType()), "a0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2"); - - EXPECT_EQ(hex(msgMailCow1Bob1.encodeHashes()), "a0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8"); - - EXPECT_EQ(hex(msgMailCow1Bob1.hashStruct()), "c52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e"); - - EXPECT_EQ(hex(msgEIP712Domain.hashStruct()), "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"); - - Address address = Address(privateKeyCow.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); - EXPECT_EQ(address.string(), "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"); -} - -TEST(EthereumAbiStruct, encodeTypes_v3_Json) { - auto hash = ParamStruct::hashStructJson( - R"({ - "types": { - "EIP712Domain": [ - {"name": "name", "type": "string"}, - {"name": "version", "type": "string"}, - {"name": "chainId", "type": "uint256"}, - {"name": "verifyingContract", "type": "address"} - ], - "Person": [ - {"name": "name", "type": "string"}, - {"name": "wallet", "type": "address"} - ], - "Mail": [ - {"name": "from", "type": "Person"}, - {"name": "to", "type": "Person"}, - {"name": "contents", "type": "string"} - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" - } - })"); - ASSERT_EQ(hex(hash), "be609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyCow, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d"); - EXPECT_EQ(hex(store(rsv.s)), "07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b91562"); - EXPECT_EQ(hex(store(rsv.v)), "1c"); -} - -// See 'signedTypeData_v4' in https://github.com/MetaMask/eth-sig-util/blob/main/test/index.ts -TEST(EthereumAbiStruct, encodeTypes_v4) { - EXPECT_EQ(msgGroup.encodeType(), "Group(string name,Person[] members)Person(string name,address[] wallets)"); - - EXPECT_EQ(msgPersonCow2.encodeType(), "Person(string name,address[] wallets)"); - - EXPECT_EQ(hex(msgPersonCow2.hashType()), "fabfe1ed996349fc6027709802be19d047da1aa5d6894ff5f6486d92db2e6860"); - - EXPECT_EQ(hex(msgPersonCow2.encodeHashes()), - "fabfe1ed996349fc6027709802be19d047da1aa5d6894ff5f6486d92db2e6860" - "8c1d2bd5348394761719da11ec67eedae9502d137e8940fee8ecd6f641ee1648" - "8a8bfe642b9fc19c25ada5dadfd37487461dc81dd4b0778f262c163ed81b5e2a"); - - EXPECT_EQ(hex(msgPersonCow2.hashStruct()), "9b4846dd48b866f0ac54d61b9b21a9e746f921cefa4ee94c4c0a1c49c774f67f"); - - EXPECT_EQ(hex(msgPersonBob3.encodeHashes()), - "fabfe1ed996349fc6027709802be19d047da1aa5d6894ff5f6486d92db2e6860" - "28cac318a86c8a0a6a9156c2dba2c8c2363677ba0514ef616592d81557e679b6" - "d2734f4c86cc3bd9cabf04c3097589d3165d95e4648fc72d943ed161f651ec6d"); - - EXPECT_EQ(hex(msgPersonBob3.hashStruct()), "efa62530c7ae3a290f8a13a5fc20450bdb3a6af19d9d9d2542b5a94e631a9168"); - - EXPECT_EQ(msgMailCow2Bob3.encodeType(), "Mail(Person from,Person[] to,string contents)Person(string name,address[] wallets)"); - - EXPECT_EQ(hex(msgMailCow2Bob3.hashType()), "4bd8a9a2b93427bb184aca81e24beb30ffa3c747e2a33d4225ec08bf12e2e753"); - - EXPECT_EQ(hex(msgMailCow2Bob3.encodeHashes()), - "4bd8a9a2b93427bb184aca81e24beb30ffa3c747e2a33d4225ec08bf12e2e753" - "9b4846dd48b866f0ac54d61b9b21a9e746f921cefa4ee94c4c0a1c49c774f67f" - "ca322beec85be24e374d18d582a6f2997f75c54e7993ab5bc07404ce176ca7cd" - "b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8"); - - EXPECT_EQ(hex(msgMailCow2Bob3.hashStruct()), "eb4221181ff3f1a83ea7313993ca9218496e424604ba9492bb4052c03d5c3df8"); - - EXPECT_EQ(hex(msgEIP712Domain.hashStruct()), "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"); - - Address address = Address(privateKeyCow.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); - EXPECT_EQ(address.string(), "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"); -} - -TEST(EthereumAbiStruct, encodeTypes_v4_Json) { - auto hash = ParamStruct::hashStructJson( - R"({ - "types": { - "EIP712Domain": [ - {"name": "name", "type": "string"}, - {"name": "version", "type": "string"}, - {"name": "chainId", "type": "uint256"}, - {"name": "verifyingContract", "type": "address"} - ], - "Person": [ - {"name": "name", "type": "string"}, - {"name": "wallets", "type": "address[]"} - ], - "Mail": [ - {"name": "from", "type": "Person"}, - {"name": "to", "type": "Person[]"}, - {"name": "contents", "type": "string"} - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallets": [ - "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", - "DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" - ] - }, - "to": [ - { - "name": "Bob", - "wallets": [ - "bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", - "B0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", - "B0B0b0b0b0b0B000000000000000000000000000" - ] - } - ], - "contents": "Hello, Bob!" - } - })"); - ASSERT_EQ(hex(hash), "a85c2e2b118698e88db68a8105b794a8cc7cec074e89ef991cb4f5f533819cc2"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyCow, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "65cbd956f2fae28a601bebc9b906cea0191744bd4c4247bcd27cd08f8eb6b71c"); - EXPECT_EQ(hex(store(rsv.s)), "78efdf7a31dc9abee78f492292721f362d296cf86b4538e07b51303b67f74906"); - EXPECT_EQ(hex(store(rsv.v)), "1b"); -} - -// See 'signedTypeData_v4 with recursive types' in https://github.com/MetaMask/eth-sig-util/blob/main/test/index.ts -TEST(EthereumAbiStruct, encodeTypes_v4Rec) { - ParamStruct msgPersonRecursiveMother("Person", std::vector>{ - std::make_shared("name", std::make_shared("Lyanna")), - std::make_shared("mother", std::make_shared("Person", std::vector>{})), - std::make_shared("father", std::make_shared("Person", std::vector>{ - std::make_shared("name", std::make_shared("Rickard")), - std::make_shared("mother", std::make_shared("Person", std::vector>{})), - std::make_shared("father", std::make_shared("Person", std::vector>{})) - })) - }); - ParamStruct msgPersonRecursiveFather("Person", std::vector>{ - std::make_shared("name", std::make_shared("Rhaegar")), - std::make_shared("mother", std::make_shared("Person", std::vector>{})), - std::make_shared("father", std::make_shared("Person", std::vector>{ - std::make_shared("name", std::make_shared("Aeris II")), - std::make_shared("mother", std::make_shared("Person", std::vector>{})), - std::make_shared("father", std::make_shared("Person", std::vector>{})) - })) - }); - ParamStruct msgPersonRecursive("Person", std::vector>{ - std::make_shared("name", std::make_shared("Jon")), - std::make_shared("mother", std::make_shared(msgPersonRecursiveMother)), - std::make_shared("father", std::make_shared(msgPersonRecursiveFather)) - }); - - EXPECT_EQ(msgPersonRecursive.encodeType(), "Person(string name,Person mother,Person father)"); - - EXPECT_EQ(hex(msgPersonRecursive.hashType()), "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116"); - - EXPECT_EQ(hex(msgPersonRecursiveMother.encodeHashes()), - "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116" - "afe4142a2b3e7b0503b44951e6030e0e2c5000ef83c61857e2e6003e7aef8570" - "0000000000000000000000000000000000000000000000000000000000000000" - "88f14be0dd46a8ec608ccbff6d3923a8b4e95cdfc9648f0db6d92a99a264cb36"); - - EXPECT_EQ(hex(msgPersonRecursiveMother.hashStruct()), "9ebcfbf94f349de50bcb1e3aa4f1eb38824457c99914fefda27dcf9f99f6178b"); - - EXPECT_EQ(hex(msgPersonRecursiveFather.encodeHashes()), - "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116" - "b2a7c7faba769181e578a391a6a6811a3e84080c6a3770a0bf8a856dfa79d333" - "0000000000000000000000000000000000000000000000000000000000000000" - "02cc7460f2c9ff107904cff671ec6fee57ba3dd7decf999fe9fe056f3fd4d56e"); - - EXPECT_EQ(hex(msgPersonRecursiveFather.hashStruct()), "b852e5abfeff916a30cb940c4e24c43cfb5aeb0fa8318bdb10dd2ed15c8c70d8"); - - EXPECT_EQ(hex(msgPersonRecursive.encodeHashes()), - "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116" - "e8d55aa98b6b411f04dbcf9b23f29247bb0e335a6bc5368220032fdcb9e5927f" - "9ebcfbf94f349de50bcb1e3aa4f1eb38824457c99914fefda27dcf9f99f6178b" - "b852e5abfeff916a30cb940c4e24c43cfb5aeb0fa8318bdb10dd2ed15c8c70d8"); - - EXPECT_EQ(hex(msgPersonRecursive.hashStruct()), "fdc7b6d35bbd81f7fa78708604f57569a10edff2ca329c8011373f0667821a45"); - - ParamStruct msgEIP712Domain("EIP712Domain", std::vector>{ - std::make_shared("name", std::make_shared("Family Tree")), - std::make_shared("version", std::make_shared("1")), - std::make_shared("chainId", std::make_shared(1)), - std::make_shared("verifyingContract", std::make_shared(parse_hex("CcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"))) - }); - - EXPECT_EQ(hex(msgEIP712Domain.hashStruct()), "facb2c1888f63a780c84c216bd9a81b516fc501a19bae1fc81d82df590bbdc60"); - - Address address = Address(privateKeyDragon.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); - EXPECT_EQ(address.string(), "0x065a687103C9F6467380beE800ecD70B17f6b72F"); -} - -TEST(EthereumAbiStruct, encodeTypes_v4Rec_Json) { - auto hash = ParamStruct::hashStructJson( - R"({ - "types": { - "EIP712Domain": [ - {"name": "name", "type": "string"}, - {"name": "version", "type": "string"}, - {"name": "chainId", "type": "uint256"}, - {"name": "verifyingContract", "type": "address"} - ], - "Person": [ - {"name": "name", "type": "string"}, - {"name": "mother", "type": "Person"}, - {"name": "father", "type": "Person"} - ] - }, - "primaryType": "Person", - "domain": { - "name": "Family Tree", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "name": "Jon", - "mother": { - "name": "Lyanna", - "father": { - "name": "Rickard" - } - }, - "father": { - "name": "Rhaegar", - "father": { - "name": "Aeris II" - } - } - } - })"); - ASSERT_EQ(hex(hash), "807773b9faa9879d4971b43856c4d60c2da15c6f8c062bd9d33afefb756de19c"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyDragon, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "f2ec61e636ff7bb3ac8bc2a4cc2c8b8f635dd1b2ec8094c963128b358e79c85c"); - EXPECT_EQ(hex(store(rsv.s)), "5ca6dd637ed7e80f0436fe8fce39c0e5f2082c9517fe677cc2917dcd6c84ba88"); - EXPECT_EQ(hex(store(rsv.v)), "1c"); -} - -// See 'signedTypeData' in https://github.com/MetaMask/eth-sig-util/blob/main/test/index.ts -TEST(EthereumAbiStruct, encodeTypeCow1) { - ParamStruct msgPersonCow1("Person", std::vector>{ - std::make_shared("name", std::make_shared("Cow")), - std::make_shared("wallet", std::make_shared(parse_hex("CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"))) - }); - - EXPECT_EQ(msgPersonCow1.encodeType(), "Person(string name,address wallet)"); - - EXPECT_EQ(hex(msgPersonCow1.hashType()), "b9d8c78acf9b987311de6c7b45bb6a9c8e1bf361fa7fd3467a2163f994c79500"); - - EXPECT_EQ(hex(msgPersonCow1.encodeHashes()), "b9d8c78acf9b987311de6c7b45bb6a9c8e1bf361fa7fd3467a2163f994c795008c1d2bd5348394761719da11ec67eedae9502d137e8940fee8ecd6f641ee1648000000000000000000000000cd2a3d9f938e13cd947ec05abc7fe734df8dd826"); - - EXPECT_EQ(hex(msgPersonCow1.hashStruct()), "fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8"); -} - -TEST(EthereumAbiStruct, hashStructJson) { - { - auto hash = ParamStruct::hashStructJson( - R"({ - "types": { - "EIP712Domain": [ - {"name": "name", "type": "string"}, - {"name": "version", "type": "string"}, - {"name": "chainId", "type": "uint256"}, - {"name": "verifyingContract", "type": "address"} - ], - "Person": [ - {"name": "name", "type": "string"}, - {"name": "wallet", "type": "address"} - ] - }, - "primaryType": "Person", - "domain": { - "name": "Ether Person", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "name": "Cow", - "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - } - })"); - ASSERT_EQ(hex(hash), "0b4bb85394b9ebb1c2425e283c9e734a9a7a832622e97c998f77e1c7a3f01a20"); - } - { // edge cases - EXPECT_EXCEPTION(ParamStruct::hashStructJson("NOT_A_JSON"), "Could not parse Json"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson("+/{\\"), "Could not parse Json"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(""), "Could not parse Json"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson("0"), "Expecting Json object"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson("[]"), "Expecting Json object"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({})"), "Top-level string field 'primaryType' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"domain": {}, "message": {}, "types": {}})"), "Top-level string field 'primaryType' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": [], "domain": {}, "message": {}, "types": {}})"), "Top-level string field 'primaryType' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "message": {}, "types": {}})"), "Top-level object field 'domain' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": "vDomain", "message": {}, "types": {}})"), "Top-level object field 'domain' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": {}, "types": {}})"), "Top-level object field 'message' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": {}, "message": "v2", "types": {}})"), "Top-level object field 'message' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": {}, "message": {}})"), "Top-level object field 'types' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": {}, "message": {}, "types": "vTypes"})"), "Top-level object field 'types' missing"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": {}, "message": {}, "types": {}})"), "Type not found, EIP712Domain"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": {}, "message": {}, "types": {"EIP712Domain": []}})"), "No valid params found"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": {}, "message": {}, "types": {"EIP712Domain": [{"name": "param", "type": "type"}]}})"), "Unknown type type"); - EXPECT_EXCEPTION(ParamStruct::hashStructJson(R"({"primaryType": "v1", "domain": {"param": "val"}, "message": {}, "types": {"EIP712Domain": [{"name": "param", "type": "string"}]}})"), "Type not found, v1"); - } -} - -TEST(EthereumAbiStruct, hashStruct_emptyString) { - auto path = TESTS_ROOT + "/Ethereum/Data/eip712_emptyString.json"; - auto typeData = load_file(path); - auto hash = ParamStruct::hashStructJson(typeData); - EXPECT_EQ(hex(hash), "bc9d33285c5e42b00571f5deaf9636d2e498a6fa50e0d1be81095bded070117a"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyOilTimes12, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "5df6cb46d874bc0acc519695f393008a837ca9d2e316836b669b8f0de7673638"); - EXPECT_EQ(hex(store(rsv.s)), "54cc0bcc0ad657f9222f7e7be3fbe0ec4a8edb9385c39d578dfac8d38727af12"); - EXPECT_EQ(hex(store(rsv.v)), "1c"); -} - -TEST(EthereumAbiStruct, hashStruct_emptyArray) { - auto path = TESTS_ROOT + "/Ethereum/Data/eip712_emptyArray.json"; - auto typeData = load_file(path); - auto hash = ParamStruct::hashStructJson(typeData); - EXPECT_EQ(hex(hash), "9f1a1bc718e966d683c544aef6fd0b73c85a1d6244af9b64bb8f4a6fa6716086"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyOilTimes12, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "de47efd592493f7189d44f071424ecb24b50d80750d3bd2bb6fc80451c13a52f"); - EXPECT_EQ(hex(store(rsv.s)), "202b8a2be1ef3c466853e8cd5275a6af15b11e7e1cc0ae4a7e249bc9bad591eb"); - EXPECT_EQ(hex(store(rsv.v)), "1c"); -} - -TEST(EthereumAbiStruct, hashStruct_walletConnect) { - // https://github.com/WalletConnect/walletconnect-example-dapp/blob/master/src/helpers/eip712.ts - auto path = TESTS_ROOT + "/Ethereum/Data/eip712_walletconnect.json"; - auto typeData = load_file(path); - auto hash = ParamStruct::hashStructJson(typeData); - EXPECT_EQ(hex(hash), "abc79f527273b9e7bca1b3f1ac6ad1a8431fa6dc34ece900deabcd6969856b5e"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyOilTimes12, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "e9c1ce1307593c378c7e38e8aa00dfb42b5a1ce543b59a138a12f29bd7fea75c"); - EXPECT_EQ(hex(store(rsv.s)), "3fe71ef91c37abea29fe14b5f0de805f924af19d71bcef09e74aef2f0ccdf52a"); - EXPECT_EQ(hex(store(rsv.v)), "1c"); -} - -TEST(EthereumAbiStruct, hashStruct_cryptofights) { - auto path = TESTS_ROOT + "/Ethereum/Data/eip712_cryptofights.json"; - auto typeData = load_file(path); - auto hash = ParamStruct::hashStructJson(typeData); - EXPECT_EQ(hex(hash), "db12328a6d193965801548e1174936c3aa7adbe1b54b3535a3c905bd4966467c"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyOilTimes12, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "9e26bdf0d113a72805acb1c2c8b0734d264290fd1cfbdf5e6502ae65a2f2bd83"); - EXPECT_EQ(hex(store(rsv.s)), "11512c15ad0833fd457ae5dd59c3bcb3d03f35b3d33c1c5a575852163db42369"); - EXPECT_EQ(hex(store(rsv.v)), "1b"); -} - -TEST(EthereumAbiStruct, hashStruct_rarible) { - auto path = TESTS_ROOT + "/Ethereum/Data/eip712_rarible.json"; - auto typeData = load_file(path); - auto hash = ParamStruct::hashStructJson(typeData); - EXPECT_EQ(hex(hash), "df0200de55c05eb55af2597012767ea3af653d68000be49580f8e05acd91d366"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyCow, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "9e6155c62a55d3dc6034973d93821dace5a0c66bfbd8413ad29205c2fb079e84"); - EXPECT_EQ(hex(store(rsv.s)), "3ca5906f24b82672304302a0e42e5dc090acc800060bad51fb81cc4469f69930"); - EXPECT_EQ(hex(store(rsv.v)), "1b"); -} - -TEST(EthereumAbiStruct, hashStruct_snapshot) { - auto path = TESTS_ROOT + "/Ethereum/Data/eip712_snapshot_v4.json"; - auto typeData = load_file(path); - auto hash = ParamStruct::hashStructJson(typeData); - EXPECT_EQ(hex(hash), "f558d08ad4a7651dbc9ec028cfcb4a8e6878a249073ef4fa694f85ee95f61c0f"); - - // sign the hash - const auto rsv = Signer::sign(privateKeyOilTimes12, hash, true, 0); - EXPECT_EQ(hex(store(rsv.r)), "9da563ffcafe9fa8809540ebcc4bcf8bbc26874e192f430432e06547593e8681"); - EXPECT_EQ(hex(store(rsv.s)), "164808603aca259775bdf511124b58651f1b3ce9ccbcd5a8d63df02e2359bb8b"); - EXPECT_EQ(hex(store(rsv.v)), "1b"); -} - -TEST(EthereumAbiStruct, ParamFactoryMakeNamed) { - std::shared_ptr p = ParamFactory::makeNamed("firstparam", "uint256"); - EXPECT_EQ(p->getName(), "firstparam"); - ASSERT_NE(p->getParam().get(), nullptr); - EXPECT_EQ(p->getParam()->getType(), "uint256"); -} - -TEST(EthereumAbiStruct, ParamStructMakeStruct) { - { - std::shared_ptr s = ParamStruct::makeStruct("Person", - R"( - {"name": "Cow", "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"} - )", - R"({ - "Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}] - })"); - ASSERT_NE(s.get(), nullptr); - EXPECT_EQ(s->getType(), "Person"); - ASSERT_EQ(s->getCount(), 2); - EXPECT_EQ(s->encodeType(), "Person(string name,address wallet)"); - EXPECT_EQ(hex(s->hashStruct()), "fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8"); - } - { - std::shared_ptr s = ParamStruct::makeStruct("Person", - R"( - {"name": "Cow", "wallets": ["CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", "DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"]} - )", - R"({ - "Person": [{"name": "name", "type": "string"}, {"name": "wallets", "type": "address[]"}] - })"); - ASSERT_NE(s.get(), nullptr); - EXPECT_EQ(s->getType(), "Person"); - ASSERT_EQ(s->getCount(), 2); - EXPECT_EQ(s->encodeType(), "Person(string name,address[] wallets)"); - EXPECT_EQ(hex(s->hashStruct()), "9b4846dd48b866f0ac54d61b9b21a9e746f921cefa4ee94c4c0a1c49c774f67f"); - } - { - std::shared_ptr s = ParamStruct::makeStruct("Mail", - R"({"from": {"name": "Cow", "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"}, "to": {"name": "Bob", "wallet": "bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"}, "contents": "Hello, Bob!"})", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}],"Mail": [{"name": "from", "type": "Person"},{"name": "to", "type": "Person"},{"name": "contents", "type": "string"}]})"); - ASSERT_NE(s.get(), nullptr); - EXPECT_EQ(s->getType(), "Mail"); - ASSERT_EQ(s->getCount(), 3); - EXPECT_EQ(s->encodeType(), "Mail(Person from,Person to,string contents)Person(string name,address wallet)"); - EXPECT_EQ(hex(s->hashStruct()), "c52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e"); - } - - { // extra param - std::shared_ptr s = ParamStruct::makeStruct("Person", - R"({"extra_param": "extra_value", "name": "Cow", "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"})", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})"); - ASSERT_NE(s.get(), nullptr); - EXPECT_EQ(s->encodeType(), "Person(string name,address wallet)"); - EXPECT_EQ(hex(s->hashStruct()), "fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8"); - } - { // empty array - std::shared_ptr s = ParamStruct::makeStruct("Person", - R"({"extra_param": "extra_value", "name": "Cow", "wallets": []})", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallets", "type": "address[]"}]})"); - ASSERT_NE(s.get(), nullptr); - EXPECT_EQ(s->encodeType(), "Person(string name,address[] wallets)"); - } - { // missing param - std::shared_ptr s = ParamStruct::makeStruct("Person", - R"({"wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"})", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})"); - ASSERT_NE(s.get(), nullptr); - EXPECT_EQ(s->encodeType(), "Person(string name,address wallet)"); - } - - { - EXPECT_EXCEPTION(ParamStruct::makeStruct("Person", - "NOT_A_JSON+/{\\", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})"), - "Could not parse value Json"); - } - { - EXPECT_EXCEPTION(ParamStruct::makeStruct("Person", - "0", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})"), - "Expecting object"); - } - { - EXPECT_EXCEPTION(ParamStruct::makeStruct("Person", - // params mixed up - R"({"wallets": "Cow", "name": ["CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", "DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"]})", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallets", "type": "address[]"}]})"), - "Could not set type for param wallets"); - } - { - EXPECT_EXCEPTION(ParamStruct::makeStruct("Person", - R"({"name": "Cow", "wallets": ["CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", "DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"]})", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallets", "type": "missingtype[]"}]})"), - "Unknown struct array type missingtype"); - } - { - EXPECT_EXCEPTION(ParamStruct::makeStruct("Person", - R"({"name": "Cow", "wallets": "NOT_AN_ARRAY"})", - R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallets", "type": "address[]"}]})"), - "Could not set type for param wallets"); - } - { - EXPECT_EXCEPTION(ParamStruct::makeStruct("Mail", - R"({"from": {"name": "Cow", "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"}, "to": {"name": "Bob", "wallet": "bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"}, "contents": "Hello, Bob!"})", - R"({"Mail": [{"name": "from", "type": "Person"},{"name": "to", "type": "Person"},{"name": "contents", "type": "string"}]})"), - "Unknown type Person"); - } -} - -TEST(EthereumAbiStruct, ParamFactoryMakeTypes) { - { - std::vector> tt = ParamStruct::makeTypes(R"({"Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})"); - ASSERT_EQ(tt.size(), 1); - EXPECT_EQ(tt[0]->encodeType(), "Person(string name,address wallet)"); - } - { - std::vector> tt = ParamStruct::makeTypes( - R"({ - "Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}], - "Mail": [{"name": "from", "type": "Person"}, {"name": "to", "type": "Person"}, {"name": "contents", "type": "string"}] - })"); - ASSERT_EQ(tt.size(), 2); - EXPECT_EQ(tt[0]->encodeType(), "Mail(Person from,Person to,string contents)Person(string name,address wallet)"); - EXPECT_EQ(tt[1]->encodeType(), "Person(string name,address wallet)"); - } - { // edge cases - EXPECT_EXCEPTION(ParamStruct::makeTypes("NOT_A_JSON"), "Could not parse types Json"); - EXPECT_EXCEPTION(ParamStruct::makeTypes("+/{\\"), "Could not parse types Json"); - EXPECT_EXCEPTION(ParamStruct::makeTypes(""), "Could not parse types Json"); - EXPECT_EXCEPTION(ParamStruct::makeTypes("0"), "Expecting object"); - EXPECT_EXCEPTION(ParamStruct::makeTypes("[]"), "Expecting object"); - EXPECT_EXCEPTION(ParamStruct::makeTypes("[{}]"), "Expecting object"); - EXPECT_EQ(ParamStruct::makeTypes("{}").size(), 0); - EXPECT_EXCEPTION(ParamStruct::makeTypes("{\"a\": 0}"), "Expecting array"); - EXPECT_EXCEPTION(ParamStruct::makeTypes(R"({"name": 0})"), "Expecting array"); - // order does not matter - EXPECT_EQ(ParamStruct::makeTypes(R"({"Mail": [{"name": "from", "type": "Person"}, {"name": "to", "type": "Person"}, {"name": "contents", "type": "string"}], "Person": [{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}]})").size(), 2); - } -} - -TEST(EthereumAbiStruct, ParamFactoryMakeType) { - { - std::shared_ptr t = ParamStruct::makeType("Person", R"([{"name": "name", "type": "string"}, {"name": "wallet", "type": "address"}])"); - EXPECT_NE(t.get(), nullptr); - EXPECT_EQ(t->getType(), "Person"); - ASSERT_EQ(t->getCount(), 2); - ASSERT_EQ(t->encodeType(), "Person(string name,address wallet)"); - } - { // edge cases - EXPECT_EXCEPTION(ParamStruct::makeType("", ""), "Missing type name"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", "NOT_A_JSON"), "Could not parse type Json"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", "+/{\\"), "Could not parse type Json"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", ""), "Could not parse type Json"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", "0"), "Expecting array"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", "{}"), "Expecting array"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", "[]"), "No valid params found"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", R"([{"dummy": 0}])"), "Could not process Json: "); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", R"([{"name": "val"}])"), "Could not process Json: "); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", R"([{"type": "val"}])"), "Could not process Json: "); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", R"([{"name": "", "type": "type"}])"), "Expecting 'name' and 'type', in Person"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", R"([{"name": "name", "type": ""}])"), "Expecting 'name' and 'type', in Person"); - EXPECT_EXCEPTION(ParamStruct::makeType("Person", R"([{"name": "name", "type": "UNKNOWN_TYPE"}, {"name": "wallet", "type": "address"}])"), "Unknown type UNKNOWN_TYPE"); - EXPECT_EQ(ParamStruct::makeType("Person", R"([{"name": "name", "type": "UNKNOWN_TYPE"}, {"name": "wallet", "type": "address"}])", {}, true)->encodeType(), "Person(UNKNOWN_TYPE name,address wallet)UNKNOWN_TYPE()"); - } -} - -TEST(EthereumAbiStruct, ParamNamedMethods) { - const auto ps = std::make_shared("Hello"); - auto pn = std::make_shared("name", ps); - - EXPECT_EQ(pn->getSize(), ps->getSize()); - EXPECT_EQ(pn->isDynamic(), ps->isDynamic()); - Data encoded; - pn->encode(encoded); - EXPECT_EQ(hex(encoded), "000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000"); - size_t offset = 0; - EXPECT_EQ(pn->decode(encoded, offset), true); - EXPECT_EQ(offset, 64); - pn->setValueJson("World"); - EXPECT_EQ(ps->getVal(), "World"); -} - -TEST(EthereumAbiStruct, ParamSetNamed) { - const auto pn1 = std::make_shared("param1", std::make_shared("Hello")); - const auto pn2 = std::make_shared("param2", std::make_shared("World")); - auto ps = std::make_shared(std::vector>{pn1, pn2}); - EXPECT_EQ(ps->getCount(), 2); - EXPECT_EQ(ps->addParam(std::shared_ptr(nullptr)), -1); - EXPECT_EQ(ps->findParamByName("NO_SUCH_PARAM"), nullptr); - auto pf1 = ps->findParamByName("param2"); - ASSERT_NE(pf1.get(), nullptr); - EXPECT_EQ(pf1->getName(), "param2"); -} - -TEST(EthereumAbiStruct, ParamStructMethods) { - const auto pn1 = std::make_shared("param1", std::make_shared("Hello")); - const auto pn2 = std::make_shared("param2", std::make_shared("World")); - auto ps = std::make_shared("struct", std::vector>{pn1, pn2}); - - EXPECT_EQ(ps->getSize(), 2); - EXPECT_EQ(ps->isDynamic(), true); - Data encoded; - ps->encode(encoded); - EXPECT_EQ(hex(encoded), ""); - size_t offset = 0; - EXPECT_EQ(ps->decode(encoded, offset), true); - EXPECT_EQ(offset, 0); - EXPECT_FALSE(ps->setValueJson("dummy")); - EXPECT_EQ(ps->findParamByName("param2")->getName(), "param2"); -} - -TEST(EthereumAbiStruct, ParamHashStruct) { - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("13")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000000000000000000d"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("1234")); - EXPECT_EQ(hex(p->hashStruct()), "00000000000000000000000000000000000000000000000000000000000004d2"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("1234567")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000000000000012d687"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("1234567")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000000000000012d687"); - } - { - auto p = std::make_shared(128); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000112210f47de98115"); - } - { - auto p = std::make_shared(168); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000112210f47de98115"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000112210f47de98115"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("13")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000000000000000000d"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("1234")); - EXPECT_EQ(hex(p->hashStruct()), "00000000000000000000000000000000000000000000000000000000000004d2"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("1234567")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000000000000012d687"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("1234567")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000000000000012d687"); - } - { - auto p = std::make_shared(128); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000112210f47de98115"); - } - { - auto p = std::make_shared(168); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000112210f47de98115"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ(hex(p->hashStruct()), "000000000000000000000000000000000000000000000000112210f47de98115"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("true")); - EXPECT_EQ(hex(p->hashStruct()), "0000000000000000000000000000000000000000000000000000000000000001"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("ABCdefGHI")); - EXPECT_EQ(hex(p->hashStruct()), "3a2aa9c027187dbf5a2f0c980281da43e810ecbe4d32e0b5c22211882c691889"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("0123456789")); - EXPECT_EQ(hex(p->hashStruct()), "79fad56e6cf52d0c8c2c033d568fc36856ba2b556774960968d79274b0e6b944"); - EXPECT_TRUE(p->setValueJson("0xa9059cbb0000000000000000000000002e0d94754b348d208d64d52d78bcd443afa9fa520000000000000000000000000000000000000000000000000000000000000007")); - EXPECT_EQ(hex(p->hashStruct()), "a9485354dd9d340e02789cfc540c6c4a2ff5511beb414b64634a5e11c6a7168c"); - EXPECT_TRUE(p->setValueJson("0x0000000000000000000000000000000000000000000000000000000123456789")); - EXPECT_EQ(hex(p->hashStruct()), "c8243991757dc8723e4976248127e573da4a2cbfad54b776d5a7c8d92b6e2a6b"); - EXPECT_TRUE(p->setValueJson("0x00")); - EXPECT_EQ(hex(p->hashStruct()), "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a"); - EXPECT_TRUE(p->setValueJson("0x")); - EXPECT_EQ(hex(p->hashStruct()), "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); - EXPECT_TRUE(p->setValueJson("")); - EXPECT_EQ(hex(p->hashStruct()), "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); - } - { - auto p = std::make_shared(36); - EXPECT_TRUE(p->setValueJson("0x000000000000000000000000000000000000000000000000000000000000000123456789")); - EXPECT_EQ(hex(p->hashStruct()), "3deb4663f580c622d668f2121c29c3f4dacf06e40a3a76d1dea25e90bcd63b5d"); - } - { - auto p = std::make_shared(20); - EXPECT_TRUE(p->setValueJson("0x0000000000000000000000000000000123456789")); - EXPECT_EQ(hex(p->hashStruct()), "0000000000000000000000000000000123456789000000000000000000000000"); - } - { - auto p = std::make_shared(32); - EXPECT_TRUE(p->setValueJson("0x0000000000000000000000000000000000000000000000000000000123456789")); - EXPECT_EQ(hex(p->hashStruct()), "0000000000000000000000000000000000000000000000000000000123456789"); - } - { - auto p = std::make_shared(); - EXPECT_TRUE(p->setValueJson("0x0000000000000000000000000000000123456789")); - EXPECT_EQ(hex(p->hashStruct()), "0000000000000000000000000000000000000000000000000000000123456789"); - } - { - auto p = std::make_shared(std::make_shared()); - EXPECT_TRUE(p->setValueJson("[13,14,15]")); - EXPECT_EQ(hex(p->hashStruct()), "71494e9b6acbff3356f1292cc149101310110b6b13f835ae4665e4b00892fa83"); - } - { - auto p = std::make_shared(std::make_shared()); - EXPECT_TRUE(p->setValueJson("[\"0x0000000000000000000000000000000123456789\"]")); - EXPECT_EQ(hex(p->hashStruct()), "c8243991757dc8723e4976248127e573da4a2cbfad54b776d5a7c8d92b6e2a6b"); - } - { - auto p = std::make_shared(std::make_shared()); - EXPECT_TRUE(p->setValueJson("[true,false,true]")); - EXPECT_EQ(hex(p->hashStruct()), "5c6090c0461491a2941743bda5c3658bf1ea53bbd3edcde54e16205e18b45792"); - } -} diff --git a/tests/Ethereum/AbiTests.cpp b/tests/Ethereum/AbiTests.cpp deleted file mode 100644 index 81e62b5fee1..00000000000 --- a/tests/Ethereum/AbiTests.cpp +++ /dev/null @@ -1,1580 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Ethereum/ABI.h" -#include "HexCoding.h" -#include "../interface/TWTestUtilities.h" - -#include - -using namespace TW; -using namespace TW::Ethereum::ABI; - - -///// Parameter types - -TEST(EthereumAbi, ParamTypeNames) { - EXPECT_EQ("uint8", ParamUInt8().getType()); - EXPECT_EQ("uint16", ParamUInt16().getType()); - EXPECT_EQ("uint32", ParamUInt32().getType()); - EXPECT_EQ("uint64", ParamUInt64().getType()); - EXPECT_EQ("uint256", ParamUInt256().getType()); - EXPECT_EQ("uint168", ParamUIntN(168).getType()); - EXPECT_EQ("int8", ParamInt8().getType()); - EXPECT_EQ("int16", ParamInt16().getType()); - EXPECT_EQ("int32", ParamInt32().getType()); - EXPECT_EQ("int64", ParamInt64().getType()); - EXPECT_EQ("int256", ParamInt256().getType()); - EXPECT_EQ("int168", ParamIntN(168).getType()); - EXPECT_EQ("bool", ParamBool().getType()); - EXPECT_EQ("string", ParamString().getType()); - EXPECT_EQ("address", ParamAddress().getType()); - EXPECT_EQ("bytes", ParamByteArray().getType()); - EXPECT_EQ("bytes168", ParamByteArrayFix(168).getType()); - { - // ParamArray, non-empty - auto paramArray = ParamArray(); - paramArray.addParam(std::make_shared()); - EXPECT_EQ("bool[]", paramArray.getType()); - } - { - // ParamArray, empty with prototype - auto paramArray = ParamArray(); - paramArray.setProto(std::make_shared()); - EXPECT_EQ("bool[]", paramArray.getType()); - } - { - // ParamArray, empty, no prototype - auto paramArray = ParamArray(); - EXPECT_EQ("__empty__[]", paramArray.getType()); - } - EXPECT_EQ("()", ParamTuple().getType()); -} - -TEST(EthereumAbi, ParamBool) { - { - auto param = ParamBool(false); - EXPECT_FALSE(param.getVal()); - param.setVal(true); - EXPECT_TRUE(param.getVal()); - - EXPECT_EQ("bool", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); - } - { - auto param = ParamBool(false); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000000", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_FALSE(param.getVal()); - } - { - auto param = ParamBool(true); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000001", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_TRUE(param.getVal()); - } -} - -TEST(EthereumAbi, ParamUInt8) { - { - auto param = ParamUInt8(101); - EXPECT_EQ(101, param.getVal()); - param.setVal(1); - EXPECT_EQ(1, param.getVal()); - - EXPECT_EQ("uint8", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); - } - { - auto param = ParamUInt8(101); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000065", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(101, param.getVal()); - } - { - auto param = ParamUInt8(1); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000001", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(1, param.getVal()); - } -} - -TEST(EthereumAbi, ParamUInt16) { - { - auto param = ParamUInt16(101); - EXPECT_EQ(101, param.getVal()); - param.setVal(1234); - EXPECT_EQ(1234, param.getVal()); - - EXPECT_EQ("uint16", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); - } - { - auto param = ParamUInt16(101); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000065", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(101, param.getVal()); - } - { - auto param = ParamUInt16(1234); - Data encoded; - param.encode(encoded); - EXPECT_EQ("00000000000000000000000000000000000000000000000000000000000004d2", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(1234, param.getVal()); - } -} - -TEST(EthereumAbi, ParamUInt32) { - { - auto param = ParamUInt32(101); - EXPECT_EQ(101, param.getVal()); - param.setVal(1234); - EXPECT_EQ(1234, param.getVal()); - - EXPECT_EQ("uint32", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); - } - { - auto param = ParamUInt32(101); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000065", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(101, param.getVal()); - } - { - auto param = ParamUInt32(1234); - Data encoded; - param.encode(encoded); - EXPECT_EQ("00000000000000000000000000000000000000000000000000000000000004d2", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(1234, param.getVal()); - } -} - -TEST(EthereumAbi, ParamUInt64) { - { - auto param = ParamUInt64(101); - EXPECT_EQ(101, param.getVal()); - param.setVal(1234); - EXPECT_EQ(1234, param.getVal()); - - EXPECT_EQ("uint64", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); - } - { - auto param = ParamUInt64(101); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000065", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(101, param.getVal()); - } - { - auto param = ParamUInt64(1234); - Data encoded; - param.encode(encoded); - EXPECT_EQ("00000000000000000000000000000000000000000000000000000000000004d2", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(1234, param.getVal()); - } -} - -TEST(EthereumAbi, ParamUInt256) { - const uint256_t bigInt = uint256_t("0x1234567890123456789012345678901234567890"); - { - auto param = ParamUInt256(uint256_t(101)); - EXPECT_EQ(uint256_t(101), param.getVal()); - param.setVal(uint256_t(1234)); - EXPECT_EQ(uint256_t(1234), param.getVal()); - - EXPECT_EQ("uint256", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); - } - { - auto param = ParamUInt256(uint256_t(101)); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000065", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(uint256_t(101), param.getVal()); - } - { - auto param = ParamUInt256(uint256_t(1234)); - Data encoded; - param.encode(encoded); - EXPECT_EQ("00000000000000000000000000000000000000000000000000000000000004d2", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(uint256_t(1234), param.getVal()); - } - { - auto param = ParamUInt256(bigInt); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000001234567890123456789012345678901234567890", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(bigInt, param.getVal()); - } - { - auto param = ParamUInt256(uint256_t(-1)); - Data encoded; - param.encode(encoded); - EXPECT_EQ("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(uint256_t(-1), param.getVal()); - } -} - -TEST(EthereumAbi, ParamUInt80) { - { - auto param = ParamUIntN(80, 0); - EXPECT_EQ(0, param.getVal()); - param.setVal(100); - EXPECT_EQ(100, param.getVal()); - - EXPECT_EQ("uint80", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); - - // above number of bits, masked - param.setVal(load(Data(parse_hex("1010101010101010101010101010101010101010101010101010101010101010")))); - EXPECT_EQ(load(Data(parse_hex("00000010101010101010101010"))), param.getVal()); - } - { - auto param = ParamUIntN(80, 1); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000001", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(1, param.getVal()); - } - { - auto param = ParamUIntN(80, 0x123); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000123", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(0x123, param.getVal()); - } -} - -TEST(EthereumAbi, ParamInt80) { - // large negative, above number of bits, and its counterpart truncated to 80 bits - int256_t largeNeg2 = ValueEncoder::int256FromUint256(load(Data(parse_hex("ffff101010101010101010101010101010101010101010101010101010101010")))); - int256_t largeNeg1 = ValueEncoder::int256FromUint256(load(Data(parse_hex("ffffffffffffffffffffffffffffffffffffffffffff10101010101010101010")))); - { - auto param = ParamIntN(80, 0); - EXPECT_EQ(0, param.getVal()); - param.setVal(int256_t(101)); - EXPECT_EQ(int256_t(101), param.getVal()); - - EXPECT_EQ("int80", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); - - param.setVal(int256_t(-101)); - EXPECT_EQ(int256_t(-101), param.getVal()); - param.setVal(largeNeg1); - EXPECT_EQ(largeNeg1, param.getVal()); - // large negative, above number of bits, masked - param.setVal(largeNeg2); - EXPECT_EQ(largeNeg1, param.getVal()); - } - { - auto param = ParamIntN(80, 1); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000001", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(1, param.getVal()); - } - { - auto param = ParamIntN(80, int256_t(-1234)); - Data encoded; - param.encode(encoded); - EXPECT_EQ("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb2e", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(int256_t(-1234), param.getVal()); - } - { - auto param = ParamIntN(80, largeNeg1); - Data encoded; - param.encode(encoded); - EXPECT_EQ("ffffffffffffffffffffffffffffffffffffffffffff10101010101010101010", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(int256_t(largeNeg1), param.getVal()); - } - { - auto param = ParamIntN(80, largeNeg2); - Data encoded; - param.encode(encoded); - EXPECT_EQ("ffffffffffffffffffffffffffffffffffffffffffff10101010101010101010", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(int256_t(largeNeg1), param.getVal()); - } -} - -TEST(EthereumAbi, ParamString) { - const std::string helloStr = "Hello World! Hello World! Hello World!"; - { - auto param = ParamString(helloStr); - EXPECT_EQ(helloStr, param.getVal()); - - EXPECT_EQ("string", param.getType()); - EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ(3 * 32, param.getSize()); - } - { - auto param = ParamString(helloStr); - Data encoded; - param.encode(encoded); - EXPECT_EQ( - "000000000000000000000000000000000000000000000000000000000000002c" - "48656c6c6f20576f726c64212020202048656c6c6f20576f726c642120202020" - "48656c6c6f20576f726c64210000000000000000000000000000000000000000", - hex(encoded)); - EXPECT_EQ(3 * 32, encoded.size()); - EXPECT_EQ(3 * 32, param.getSize()); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(helloStr, param.getVal()); - } -} - -TEST(EthereumAbi, ParamAddress) { - std::string val1Str("f784682c82526e245f50975190ef0fff4e4fc077"); - Data val1(parse_hex(val1Str)); - { - auto param = ParamAddress(val1); - EXPECT_EQ(val1, param.getData()); - - EXPECT_EQ("address", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); - } - { - auto param = ParamAddress(val1); - Data encoded; - param.encode(encoded); - EXPECT_EQ("000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077", hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(val1, param.getData()); - } - { - auto param = ParamAddress(parse_hex("0000000000000000000000000000000000000012")); - EXPECT_EQ("0000000000000000000000000000000000000012", hex(param.getData())); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000012", hex(encoded)); - } - { - auto param = ParamAddress(parse_hex("4300000000000000000000000000000000000000")); - EXPECT_EQ("4300000000000000000000000000000000000000", hex(param.getData())); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000004300000000000000000000000000000000000000", hex(encoded)); - } -} - -TEST(EthereumAbi, ParamByteArray) { - Data data10 = parse_hex("31323334353637383930"); - { - auto param = ParamByteArray(data10); - EXPECT_EQ(data10, param.getVal()); - - EXPECT_EQ("bytes", param.getType()); - EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ(2 * 32, param.getSize()); - } - { - auto param = ParamByteArray(data10); - Data encoded; - param.encode(encoded); - EXPECT_EQ(2 * 32, encoded.size()); - EXPECT_EQ(2 * 32, param.getSize()); - EXPECT_EQ( - "000000000000000000000000000000000000000000000000000000000000000a" - "3132333435363738393000000000000000000000000000000000000000000000", - hex(encoded)); - EXPECT_EQ(2 * 32, encoded.size()); - EXPECT_EQ(2 * 32, param.getSize()); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(data10, param.getVal()); - } -} - -TEST(EthereumAbi, ParamByteArrayFix) { - Data data10 = parse_hex("31323334353637383930"); - { - auto param = ParamByteArrayFix(10, data10); - EXPECT_EQ(data10, param.getVal()); - - EXPECT_EQ("bytes10", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(32, param.getSize()); - } - { - auto param = ParamByteArrayFix(10, data10); - Data encoded; - param.encode(encoded); - EXPECT_EQ(32, encoded.size()); - EXPECT_EQ(32, param.getSize()); - EXPECT_EQ( - "3132333435363738393000000000000000000000000000000000000000000000", - hex(encoded)); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(data10, param.getVal()); - } -} - -TEST(EthereumAbi, ParamArrayByte) { - { - auto param = ParamArray(); - param.addParam(std::make_shared(49)); - param.addParam(std::make_shared(50)); - param.addParam(std::make_shared(51)); - EXPECT_EQ(3, param.getVal().size()); - EXPECT_EQ(49, (std::dynamic_pointer_cast(param.getVal()[0]))->getVal()); - EXPECT_EQ(51, (std::dynamic_pointer_cast(param.getVal()[2]))->getVal()); - - EXPECT_EQ("uint8[]", param.getType()); - EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ((3 + 1) * 32, param.getSize()); - EXPECT_EQ(3, param.getCount()); - } - { - auto param = ParamArray(); - param.addParam(std::make_shared(49)); - param.addParam(std::make_shared(50)); - param.addParam(std::make_shared(51)); - Data encoded; - param.encode(encoded); - EXPECT_EQ(4 * 32, encoded.size()); - EXPECT_EQ(4 * 32, param.getSize()); - EXPECT_EQ( - "0000000000000000000000000000000000000000000000000000000000000003" - "0000000000000000000000000000000000000000000000000000000000000031" - "0000000000000000000000000000000000000000000000000000000000000032" - "0000000000000000000000000000000000000000000000000000000000000033", - hex(encoded)); - EXPECT_EQ(4 * 32, encoded.size()); - EXPECT_EQ(4 * 32, param.getSize()); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(3, param.getVal().size()); - EXPECT_EQ(49, (std::dynamic_pointer_cast(param.getVal()[0]))->getVal()); - EXPECT_EQ(51, (std::dynamic_pointer_cast(param.getVal()[2]))->getVal()); - } -} - -TEST(EthereumAbi, ParamArrayEmpty) { - auto param = ParamArray(); - param.setProto(std::make_shared(0)); - - EXPECT_EQ(0, param.getCount()); - Data encoded; - param.encode(encoded); - EXPECT_EQ(32, encoded.size()); - EXPECT_EQ(32, param.getSize()); - EXPECT_EQ( - "0000000000000000000000000000000000000000000000000000000000000000", - hex(encoded)); - EXPECT_EQ("uint8[]", param.getType()); - EXPECT_EQ("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", hex(param.hashStruct())); -} - -TEST(EthereumAbi, ParamArrayAddress) { - { - auto param = ParamArray(); - param.addParam(std::make_shared(Data(parse_hex("f784682c82526e245f50975190ef0fff4e4fc077")))); - param.addParam(std::make_shared(Data(parse_hex("2e00cd222cb42b616d86d037cc494e8ab7f5c9a3")))); - EXPECT_EQ(2, param.getVal().size()); - - EXPECT_EQ("address[]", param.getType()); - EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ((2 + 1) * 32, param.getSize()); - EXPECT_EQ(2, param.getCount()); - } - { - auto param = ParamArray(); - param.addParam(std::make_shared(Data(parse_hex("f784682c82526e245f50975190ef0fff4e4fc077")))); - param.addParam(std::make_shared(Data(parse_hex("2e00cd222cb42b616d86d037cc494e8ab7f5c9a3")))); - Data encoded; - param.encode(encoded); - EXPECT_EQ(3 * 32, encoded.size()); - EXPECT_EQ(3 * 32, param.getSize()); - EXPECT_EQ( - "0000000000000000000000000000000000000000000000000000000000000002" - "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077" - "0000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3", - hex(encoded)); - EXPECT_EQ(3 * 32, encoded.size()); - EXPECT_EQ(3 * 32, param.getSize()); - size_t offset = 0; - EXPECT_TRUE(param.decode(encoded, offset)); - EXPECT_EQ(2, param.getCount()); - EXPECT_EQ(2, param.getVal().size()); - EXPECT_EQ( - "2e00cd222cb42b616d86d037cc494e8ab7f5c9a3", - hex((std::dynamic_pointer_cast(param.getVal()[1]))->getData())); - } -} - -TEST(EthereumAbi, ParamArrayOfByteArray) { - auto param = ParamArray(); - param.addParam(std::make_shared(parse_hex("1011"))); - param.addParam(std::make_shared(parse_hex("102222"))); - param.addParam(std::make_shared(parse_hex("10333333"))); - EXPECT_EQ(3, param.getVal().size()); - - EXPECT_EQ("bytes[]", param.getType()); - EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ((1 + 3 + 3 * 2) * 32, param.getSize()); - EXPECT_EQ(3, param.getCount()); -} - -TEST(EthereumAbi, ParamArrayBytesContract) { - auto param = ParamArray(); - param.addParam(std::make_shared(parse_hex("0xd5fa2b00e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000047331175b23c2f067204b506ca1501c26731c990"))); - param.addParam(std::make_shared(parse_hex("0x304e6adee71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000"))); - param.addParam(std::make_shared(parse_hex("0x8b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001447331175b23c2f067204b506ca1501c26731c990000000000000000000000000"))); - param.addParam(std::make_shared(parse_hex("0x8b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000002ca00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000014d30f834b53d8f7e851e87b90ffa65757a35b8505000000000000000000000000"))); - EXPECT_EQ(4, param.getCount()); - EXPECT_EQ(4, param.getVal().size()); - - EXPECT_EQ("bytes[]", param.getType()); - EXPECT_TRUE(param.isDynamic()); - - Data encoded; - param.encode(encoded); - EXPECT_EQ(896, encoded.size()); - - EXPECT_EQ(896, param.getSize()); -} - -TEST(EthereumAbi, ParamTupleStatic) { - { - auto param = ParamTuple(); - param.addParam(std::make_shared(true)); - param.addParam(std::make_shared(123)); - EXPECT_EQ("(bool,uint64)", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(2, param.getCount()); - EXPECT_EQ(64, param.getSize()); - Data encoded; - param.encode(encoded); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000007b", hex(encoded)); - { // decode - size_t offset = 0; - auto param2 = ParamTuple(); - param2.addParam(std::make_shared()); - param2.addParam(std::make_shared()); - EXPECT_TRUE(param2.decode(encoded, offset)); - EXPECT_EQ(2, param2.getCount()); - Data encoded2; - param2.encode(encoded2); - EXPECT_EQ("0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000007b", hex(encoded)); - } - } - { - auto param = ParamTuple(); - param.addParam(std::make_shared(456)); - param.addParam(std::make_shared(parse_hex("000102030405060708090a0b0c0d0e0f10111213"))); - EXPECT_EQ("(uint64,address)", param.getType()); - EXPECT_FALSE(param.isDynamic()); - EXPECT_EQ(2, param.getCount()); - EXPECT_EQ(64, param.getSize()); - Data encoded; - param.encode(encoded); - EXPECT_EQ("00000000000000000000000000000000000000000000000000000000000001c8000000000000000000000000000102030405060708090a0b0c0d0e0f10111213", hex(encoded)); - } -} - -TEST(EthereumAbi, ParamTupleDynamic) { - { - auto param = ParamTuple(); - param.addParam(std::make_shared("Don't trust, verify!")); - param.addParam(std::make_shared(13)); - param.addParam(std::make_shared(parse_hex("00010203040506070809"))); - EXPECT_EQ("(string,uint64,bytes)", param.getType()); - EXPECT_TRUE(param.isDynamic()); - EXPECT_EQ(3, param.getCount()); - EXPECT_EQ(7 * 32, param.getSize()); - Data encoded; - param.encode(encoded); - EXPECT_EQ( - "0000000000000000000000000000000000000000000000000000000000000060" // offet 3*32 - "000000000000000000000000000000000000000000000000000000000000000d" - "00000000000000000000000000000000000000000000000000000000000000a0" // offet 5*32 - "0000000000000000000000000000000000000000000000000000000000000014" // len - "446f6e27742074727573742c2076657269667921000000000000000000000000" - "000000000000000000000000000000000000000000000000000000000000000a" // len - "0001020304050607080900000000000000000000000000000000000000000000", hex(encoded)); - } -} - -///// Direct encode & decode - -TEST(EthereumAbi, EncodeVectorByte10) { - auto p = ParamByteArrayFix(10, parse_hex("31323334353637383930")); - EXPECT_EQ("bytes10", p.getType()); - Data encoded; - p.encode(encoded); - EXPECT_EQ("3132333435363738393000000000000000000000000000000000000000000000", hex(encoded)); -} - -TEST(EthereumAbi, EncodeVectorByte) { - auto p = ParamByteArray(parse_hex("31323334353637383930")); - EXPECT_EQ("bytes", p.getType()); - Data encoded; - p.encode(encoded); - EXPECT_EQ( - "000000000000000000000000000000000000000000000000000000000000000a" - "3132333435363738393000000000000000000000000000000000000000000000", hex(encoded)); -} - -TEST(EthereumAbi, EncodeArrayByte) { - auto p = ParamArray(std::vector>{ - std::make_shared(parse_hex("1011")), - std::make_shared(parse_hex("102222")) - }); - EXPECT_EQ("bytes[]", p.getType()); - Data encoded; - p.encode(encoded); - EXPECT_EQ( - "0000000000000000000000000000000000000000000000000000000000000002" - "0000000000000000000000000000000000000000000000000000000000000040" - "0000000000000000000000000000000000000000000000000000000000000080" - "0000000000000000000000000000000000000000000000000000000000000002" - "1011000000000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000000000000000000000000000000003" - "1022220000000000000000000000000000000000000000000000000000000000", - hex(encoded) - ); - EXPECT_EQ((1 + 2 + 2 * 2) * 32, encoded.size()); - EXPECT_EQ((1 + 2 + 2 * 2) * 32, p.getSize()); -} - -TEST(EthereumAbi, DecodeUInt) { - Data encoded = parse_hex("000000000000000000000000000000000000000000000000000000000000002a"); - size_t offset = 0; - uint256_t decoded; - bool res = ParamNumberType::decodeNumber(encoded, decoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(uint256_t(42), decoded); - EXPECT_EQ(32, offset); -} - -TEST(EthereumAbi, DecodeUInt8) { - Data encoded = parse_hex("0000000000000000000000000000000000000000000000000000000000000018"); - size_t offset = 0; - uint8_t decoded; - bool res = ParamNumberType::decodeNumber(encoded, decoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(24, decoded); - EXPECT_EQ(32, offset); -} - -TEST(EthereumAbi, DecodeUInt8WithOffset) { - Data encoded = parse_hex("abcdef0000000000000000000000000000000000000000000000000000000000000018"); - size_t offset = 3; - uint8_t decoded; - bool res = ParamNumberType::decodeNumber(encoded, decoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(24, decoded); - EXPECT_EQ(3 + 32, offset); -} - -TEST(EthereumAbi, DecodeUIntWithOffset) { - Data encoded = parse_hex("abcdef000000000000000000000000000000000000000000000000000000000000002a"); - size_t offset = 3; - uint256_t decoded; - bool res = decode(encoded, decoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(uint256_t(42), decoded); - EXPECT_EQ(3 + 32, offset); -} - -TEST(EthereumAbi, DecodeUIntErrorTooShort) { - Data encoded = parse_hex("000000000000000000000000000000000000000000000000002a"); - size_t offset = 0; - uint256_t decoded; - bool res = decode(encoded, decoded, offset); - EXPECT_FALSE(res); - EXPECT_EQ(uint256_t(0), decoded); - EXPECT_EQ(0, offset); -} - -TEST(EthereumAbi, DecodeArrayUint) { - Data encoded; - append(encoded, parse_hex("000000000000000000000000000000000000000000000000000000000000000a")); - append(encoded, parse_hex("3132333435363738393000000000000000000000000000000000000000000000")); - size_t offset = 0; - std::vector decoded; - bool res = ParamByteArray::decodeBytes(encoded, decoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(10, decoded.size()); - if (decoded.size() >= 2) { - EXPECT_EQ(49u, decoded[0]); - EXPECT_EQ(50u, decoded[1]); - } - EXPECT_EQ(32 + 32, offset); -} - -TEST(EthereumAbi, DecodeArrayTooShort) { - Data encoded; - append(encoded, parse_hex("000000000000000000000000000000000000000000000000000000000000000a")); - append(encoded, parse_hex("313233343536373839")); - size_t offset = 0; - std::vector decoded; - bool res = ParamByteArray::decodeBytes(encoded, decoded, offset); - EXPECT_FALSE(res); -} - -TEST(EthereumAbi, DecodeArrayInvalidLen) { - Data encoded = parse_hex("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); - size_t offset = 0; - std::vector decoded; - bool res = ParamByteArray::decodeBytes(encoded, decoded, offset); - EXPECT_FALSE(res); -} - -TEST(EthereumAbi, DecodeByteArray) { - Data encoded; - append(encoded, parse_hex("000000000000000000000000000000000000000000000000000000000000000a")); - append(encoded, parse_hex("3132333435363738393000000000000000000000000000000000000000000000")); - size_t offset = 0; - Data decoded; - bool res = ParamByteArray::decodeBytes(encoded, decoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ("31323334353637383930", hex(decoded)); - EXPECT_EQ(32 + 32, offset); -} - -TEST(EthereumAbi, DecodeByteArray10) { - Data encoded = parse_hex("3132333435363738393000000000000000000000000000000000000000000000"); - size_t offset = 0; - Data decoded; - bool res = ParamByteArrayFix::decodeBytesFix(encoded, 10, decoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(10, decoded.size()); - EXPECT_EQ(49u, decoded[0]); - EXPECT_EQ(50u, decoded[1]); - EXPECT_EQ(32, offset); -} - -TEST(EthereumAbi, DecodeArrayOfByteArray) { - Data encoded = parse_hex( - "0000000000000000000000000000000000000000000000000000000000000002" - "0000000000000000000000000000000000000000000000000000000000000040" - "0000000000000000000000000000000000000000000000000000000000000080" - "0000000000000000000000000000000000000000000000000000000000000002" - "1011000000000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000000000000000000000000000000003" - "1022200000000000000000000000000000000000000000000000000000000000" - ); - size_t offset = 0; - Data decoded; - auto param = ParamArray(); - param.addParam(std::make_shared(Data())); - param.addParam(std::make_shared(Data())); - bool res = param.decode(encoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(2, param.getCount()); - EXPECT_EQ(7 * 32, offset); - EXPECT_EQ(2, param.getVal().size()); -} - -///// Parameters encode & decode - -TEST(EthereumAbi, EncodeParamsSimple) { - auto p = Parameters(std::vector>{ - std::make_shared(16u), - std::make_shared(17u), - std::make_shared(true) }); - EXPECT_EQ("(uint256,uint256,bool)", p.getType()); - Data encoded; - p.encode(encoded); - - EXPECT_EQ(3 * 32, encoded.size()); - EXPECT_EQ(3 * 32, p.getSize()); - EXPECT_EQ( - "0000000000000000000000000000000000000000000000000000000000000010" - "0000000000000000000000000000000000000000000000000000000000000011" - "0000000000000000000000000000000000000000000000000000000000000001", - hex(encoded)); -} - -TEST(EthereumAbi, EncodeParamsMixed) { - auto p = Parameters(std::vector>{ - std::make_shared(69u), - std::make_shared(std::vector>{ - std::make_shared(1), - std::make_shared(2), - std::make_shared(3) - }), - std::make_shared(true), - std::make_shared("Hello"), - std::make_shared(Data{0x64, 0x61, 0x76, 0x65}) - }); - EXPECT_EQ("(uint256,uint256[],bool,string,bytes)", p.getType()); - Data encoded; - p.encode(encoded); - - EXPECT_EQ(13 * 32, encoded.size()); - EXPECT_EQ(13 * 32, p.getSize()); - EXPECT_EQ( - "0000000000000000000000000000000000000000000000000000000000000045" - "00000000000000000000000000000000000000000000000000000000000000a0" - "0000000000000000000000000000000000000000000000000000000000000001" - "0000000000000000000000000000000000000000000000000000000000000120" - "0000000000000000000000000000000000000000000000000000000000000160" - "0000000000000000000000000000000000000000000000000000000000000003" - "0000000000000000000000000000000000000000000000000000000000000001" - "0000000000000000000000000000000000000000000000000000000000000002" - "0000000000000000000000000000000000000000000000000000000000000003" - "0000000000000000000000000000000000000000000000000000000000000005" - "48656c6c6f000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000000000000000000000000000000004" - "6461766500000000000000000000000000000000000000000000000000000000", - hex(encoded)); - /* - * explained: - * uint256 69u - * idx of dynamic vector, 5*32 - * true - * index of dynamic string, 9*32 - * index of dynamic data, 11*32 - * vector size 3 - * vector val1 - * vector val2 - * vector val3 - * string size 5 - * string - * data size 4 - * data - */ -} - -TEST(EthereumAbi, DecodeParamsSimple) { - Data encoded; - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000010")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000011")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000001")); - auto p = Parameters(std::vector>{ - std::make_shared(0), - std::make_shared(0), - std::make_shared(false) - }); - EXPECT_EQ("(uint256,uint256,bool)", p.getType()); - size_t offset = 0; - bool res = p.decode(encoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(uint256_t(16u), (std::dynamic_pointer_cast(p.getParam(0)))->getVal()); - EXPECT_EQ(uint256_t(17u), (std::dynamic_pointer_cast(p.getParam(1)))->getVal()); - EXPECT_EQ(true, (std::dynamic_pointer_cast(p.getParam(2)))->getVal()); - EXPECT_EQ(3 * 32, offset); -} - -TEST(EthereumAbi, DecodeParamsMixed) { - Data encoded; - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000045")); - append(encoded, parse_hex("00000000000000000000000000000000000000000000000000000000000000a0")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000001")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000120")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000160")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000003")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000001")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000002")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000003")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000005")); - append(encoded, parse_hex("48656c6c6f000000000000000000000000000000000000000000000000000000")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000004")); - append(encoded, parse_hex("6461766500000000000000000000000000000000000000000000000000000000")); - auto p = Parameters(std::vector>{ - std::make_shared(), - std::make_shared(std::vector>{ - std::make_shared(), - std::make_shared(), - std::make_shared() - }), - std::make_shared(), - std::make_shared(), - std::make_shared() - }); - EXPECT_EQ("(uint256,uint256[],bool,string,bytes)", p.getType()); - size_t offset = 0; - bool res = p.decode(encoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(uint256_t(69u), (std::dynamic_pointer_cast(p.getParam(0)))->getVal()); - EXPECT_EQ(3, (std::dynamic_pointer_cast(p.getParam(1)))->getCount()); - EXPECT_EQ(1, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(p.getParam(1)))->getParam(0)))->getVal()); - EXPECT_EQ(3, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(p.getParam(1)))->getParam(2)))->getVal()); - EXPECT_EQ(true, (std::dynamic_pointer_cast(p.getParam(2)))->getVal()); - EXPECT_EQ("Hello", (std::dynamic_pointer_cast(p.getParam(3)))->getVal()); - EXPECT_EQ(13 * 32, offset); -} - -///// Function encode & decode - -TEST(EthereumAbi, EncodeSignature) { - auto func = Function("baz", std::vector>{ - std::make_shared(69u), - std::make_shared(true) - }); - EXPECT_EQ("baz(uint256,bool)", func.getType()); - Data encoded; - func.encode(encoded); - - EXPECT_EQ(encoded.size(), 32 * 2 + 4); - EXPECT_EQ(hex(encoded.begin(), encoded.begin() + 4), "72ed38b6"); - EXPECT_EQ(hex(encoded.begin() + 4, encoded.begin() + 36 ), "0000000000000000000000000000000000000000000000000000000000000045"); - EXPECT_EQ(hex(encoded.begin() + 36, encoded.begin() + 68 ), "0000000000000000000000000000000000000000000000000000000000000001"); -} - -TEST(EthereumAbi, EncodeFunctionWithDynamicArgumentsCase1) { - auto func = Function("sam", std::vector>{ - std::make_shared(Data{0x64, 0x61, 0x76, 0x65}), - std::make_shared(true), - std::make_shared(std::vector>{ - std::make_shared(1), - std::make_shared(2), - std::make_shared(3) - }) - }); - EXPECT_EQ("sam(bytes,bool,uint256[])", func.getType()); - Data encoded; - func.encode(encoded); - - EXPECT_EQ(encoded.size(), 32 * 9 + 4); - EXPECT_EQ(hex(encoded.begin() + 0, encoded.begin() + 4 ), "a5643bf2"); - EXPECT_EQ(hex(encoded.begin() + 4, encoded.begin() + 36 ), "0000000000000000000000000000000000000000000000000000000000000060"); - EXPECT_EQ(hex(encoded.begin() + 36, encoded.begin() + 68 ), "0000000000000000000000000000000000000000000000000000000000000001"); - EXPECT_EQ(hex(encoded.begin() + 68, encoded.begin() + 100), "00000000000000000000000000000000000000000000000000000000000000a0"); - EXPECT_EQ(hex(encoded.begin() + 100, encoded.begin() + 132), "0000000000000000000000000000000000000000000000000000000000000004"); - EXPECT_EQ(hex(encoded.begin() + 132, encoded.begin() + 164), "6461766500000000000000000000000000000000000000000000000000000000"); - EXPECT_EQ(hex(encoded.begin() + 164, encoded.begin() + 196), "0000000000000000000000000000000000000000000000000000000000000003"); - EXPECT_EQ(hex(encoded.begin() + 196, encoded.begin() + 228), "0000000000000000000000000000000000000000000000000000000000000001"); - EXPECT_EQ(hex(encoded.begin() + 228, encoded.begin() + 260), "0000000000000000000000000000000000000000000000000000000000000002"); - EXPECT_EQ(hex(encoded.begin() + 260, encoded.begin() + 292), "0000000000000000000000000000000000000000000000000000000000000003"); -} - -TEST(EthereumAbi, EncodeFunctionWithDynamicArgumentsCase2) { - auto func = Function("f", std::vector>{ - std::make_shared(0x123), - std::make_shared(std::vector>{ - std::make_shared(0x456), - std::make_shared(0x789) - }), - std::make_shared(10, std::vector{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30}), - std::make_shared("Hello, world!") - }); - EXPECT_EQ("f(uint256,uint32[],bytes10,string)", func.getType()); - Data encoded; - func.encode(encoded); - - EXPECT_EQ(encoded.size(), 32 * 9 + 4); - EXPECT_EQ(hex(encoded.begin() + 0, encoded.begin() + 4 ), "47b941bf"); - EXPECT_EQ(hex(encoded.begin() + 4, encoded.begin() + 36 ), "0000000000000000000000000000000000000000000000000000000000000123"); - EXPECT_EQ(hex(encoded.begin() + 36, encoded.begin() + 68 ), "0000000000000000000000000000000000000000000000000000000000000080"); - EXPECT_EQ(hex(encoded.begin() + 68, encoded.begin() + 100), "3132333435363738393000000000000000000000000000000000000000000000"); - EXPECT_EQ(hex(encoded.begin() + 100, encoded.begin() + 132), "00000000000000000000000000000000000000000000000000000000000000e0"); - EXPECT_EQ(hex(encoded.begin() + 132, encoded.begin() + 164), "0000000000000000000000000000000000000000000000000000000000000002"); - EXPECT_EQ(hex(encoded.begin() + 164, encoded.begin() + 196), "0000000000000000000000000000000000000000000000000000000000000456"); - EXPECT_EQ(hex(encoded.begin() + 196, encoded.begin() + 228), "0000000000000000000000000000000000000000000000000000000000000789"); - EXPECT_EQ(hex(encoded.begin() + 228, encoded.begin() + 260), "000000000000000000000000000000000000000000000000000000000000000d"); - EXPECT_EQ(hex(encoded.begin() + 260, encoded.begin() + 292), "48656c6c6f2c20776f726c642100000000000000000000000000000000000000"); -} - -TEST(EthereumAbi, DecodeFunctionOutputCase1) { - Data encoded; - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000045")); - - auto func = Function("readout", std::vector>{ - std::make_shared(parse_hex("f784682c82526e245f50975190ef0fff4e4fc077")), - std::make_shared(1000) - }); - func.addOutParam(std::make_shared()); - EXPECT_EQ("readout(address,uint64)", func.getType()); - - // original output value - std::shared_ptr param; - EXPECT_TRUE(func.getOutParam(0, param)); - EXPECT_EQ(0, (std::dynamic_pointer_cast(param))->getVal()); - - size_t offset = 0; - bool res = func.decodeOutput(encoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(32, offset); - - // new output value - EXPECT_EQ(0x45, (std::dynamic_pointer_cast(param))->getVal()); -} - -TEST(EthereumAbi, DecodeFunctionOutputCase2) { - auto func = Function("getAmountsOut", std::vector>{ - std::make_shared(100), - std::make_shared(std::make_shared(parse_hex("000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"))) - }); - func.addOutParam(std::make_shared(std::vector>{ - std::make_shared(66), - std::make_shared(67) - })); - EXPECT_EQ("getAmountsOut(uint256,address[])", func.getType()); - - Data encoded; - append(encoded, parse_hex( - "0000000000000000000000000000000000000000000000000000000000000020" - "0000000000000000000000000000000000000000000000000000000000000002" - "0000000000000000000000000000000000000000000000000000000000000004" - "0000000000000000000000000000000000000000000000000000000000000005" - )); - size_t offset = 0; - bool res = func.decodeOutput(encoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(128, offset); - - // new output values - std::shared_ptr param; - EXPECT_TRUE(func.getOutParam(0, param)); - EXPECT_EQ(2, (std::dynamic_pointer_cast(param))->getCount()); - EXPECT_EQ(4, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getParam(0)))->getVal()); - EXPECT_EQ(5, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getParam(1)))->getVal()); -} - -TEST(EthereumAbi, DecodeInputSignature) { - Data encoded; - append(encoded, parse_hex("72ed38b6")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000045")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000001")); - auto func = Function("baz", std::vector>{ - std::make_shared(), std::make_shared() - }); - EXPECT_EQ("baz(uint256,bool)", func.getType()); - size_t offset = 0; - bool res = func.decodeInput(encoded, offset); - EXPECT_TRUE(res); - std::shared_ptr param; - EXPECT_TRUE(func.getInParam(0, param)); - EXPECT_EQ(69u, (std::dynamic_pointer_cast(param))->getVal()); - EXPECT_TRUE(func.getInParam(1, param)); - EXPECT_EQ(true, (std::dynamic_pointer_cast(param))->getVal()); - EXPECT_EQ(4 + 2 * 32, offset); -} - -TEST(EthereumAbi, DecodeFunctionInputWithDynamicArgumentsCase1) { - Data encoded; - append(encoded, parse_hex("a5643bf2")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000060")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000001")); - append(encoded, parse_hex("00000000000000000000000000000000000000000000000000000000000000a0")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000004")); - append(encoded, parse_hex("6461766500000000000000000000000000000000000000000000000000000000")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000003")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000001")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000002")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000003")); - - auto func = Function("sam", std::vector>{ - std::make_shared(Data{0x64, 0x61, 0x76, 0x65}), - std::make_shared(true), - std::make_shared(std::vector>{ - std::make_shared(1), - std::make_shared(2), - std::make_shared(3) - }) - }); - EXPECT_EQ("sam(bytes,bool,uint256[])", func.getType()); - - size_t offset = 0; - bool res = func.decodeInput(encoded, offset); - EXPECT_TRUE(res); - std::shared_ptr param; - EXPECT_TRUE(func.getInParam(0, param)); - EXPECT_EQ(4, (std::dynamic_pointer_cast(param))->getCount()); - EXPECT_EQ(0x64, (std::dynamic_pointer_cast(param))->getVal()[0]); - EXPECT_EQ(0x65, (std::dynamic_pointer_cast(param))->getVal()[3]); - EXPECT_TRUE(func.getInParam(1, param)); - EXPECT_EQ(true, (std::dynamic_pointer_cast(param))->getVal()); - EXPECT_TRUE(func.getInParam(2, param)); - EXPECT_EQ(3, (std::dynamic_pointer_cast(param))->getCount()); - EXPECT_EQ(uint256_t(1), (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getVal()[0]))->getVal()); - EXPECT_EQ(uint256_t(3), (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getVal()[2]))->getVal()); - EXPECT_EQ(4 + 9 * 32, offset); -} - -TEST(EthereumAbi, DecodeFunctionInputWithDynamicArgumentsCase2) { - Data encoded; - append(encoded, parse_hex("47b941bf")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000123")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000080")); - append(encoded, parse_hex("3132333435363738393000000000000000000000000000000000000000000000")); - append(encoded, parse_hex("00000000000000000000000000000000000000000000000000000000000000e0")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000002")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000456")); - append(encoded, parse_hex("0000000000000000000000000000000000000000000000000000000000000789")); - append(encoded, parse_hex("000000000000000000000000000000000000000000000000000000000000000d")); - append(encoded, parse_hex("48656c6c6f2c20776f726c642100000000000000000000000000000000000000")); - - auto func = Function("f", std::vector>{ - std::make_shared(0x123), - std::make_shared(std::vector>{ - std::make_shared(0x456), - std::make_shared(0x789) - }), - std::make_shared(10, std::vector{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30}), - std::make_shared("Hello, world!") - }); - EXPECT_EQ("f(uint256,uint32[],bytes10,string)", func.getType()); - - size_t offset = 0; - bool res = func.decodeInput(encoded, offset); - EXPECT_TRUE(res); - std::shared_ptr param; - EXPECT_TRUE(func.getInParam(0, param)); - EXPECT_EQ(uint256_t(0x123), (std::dynamic_pointer_cast(param))->getVal()); - EXPECT_TRUE(func.getInParam(1, param)); - EXPECT_EQ(2, (std::dynamic_pointer_cast(param))->getCount()); - EXPECT_EQ(0x456, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getVal()[0]))->getVal()); - EXPECT_EQ(0x789, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getVal()[1]))->getVal()); - EXPECT_TRUE(func.getInParam(2, param)); - EXPECT_EQ(10, (std::dynamic_pointer_cast(param))->getCount()); - EXPECT_EQ("31323334353637383930", hex((std::dynamic_pointer_cast(param))->getVal())); - EXPECT_TRUE(func.getInParam(3, param)); - EXPECT_EQ(std::string("Hello, world!"), (std::dynamic_pointer_cast(param))->getVal()); - EXPECT_EQ(4 + 9 * 32, offset); -} - -TEST(EthereumAbi, DecodeFunctionContractMulticall) { - Data encoded = parse_hex( - "0xac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000" - "000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000" - "000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000" - "0000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000" - "000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000" - "00000044d5fa2b00e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f0000000000" - "0000000000000047331175b23c2f067204b506ca1501c26731c990000000000000000000000000000000000000" - "000000000000000000000000000000000000000000000000000000000000000000000000000000000064304e6a" - "dee71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f000000000000000000000000" - "000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000" - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - "000000000000000000000000000000000000000000a48b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e" - "574c32a17a67156fa9ed3c4c6f000000000000000000000000000000000000000000000000000000000000003c" - "000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000" - "0000000000000000000000000000000000001447331175b23c2f067204b506ca1501c26731c990000000000000" - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000000000a48b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e57" - "4c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000002ca00" - "000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000" - "000000000000000000000000000000000014d30f834b53d8f7e851e87b90ffa65757a35b850500000000000000" - "000000000000000000000000000000000000000000000000000000000000000000"); - ASSERT_EQ(4 + 928, encoded.size()); - - auto func = Function("multicall", std::vector>{ - std::make_shared(std::vector>{ - std::make_shared(Data()), - std::make_shared(Data()), - std::make_shared(Data()), - std::make_shared(Data()) - }), - }); - EXPECT_EQ("multicall(bytes[])", func.getType()); - - size_t offset = 0; - bool res = func.decodeInput(encoded, offset); - EXPECT_TRUE(res); - EXPECT_EQ(4 + 29 * 32, offset); -} - -TEST(EthereumAbi, ParamFactoryMake) { - { - // test for UInt256: ParamUInt256 and ParamUIntN(256), both have type "uint256", factory produces the more specific ParamUInt256 - // there was confusion about this - std::shared_ptr p = ParamFactory::make("uint256"); - EXPECT_EQ("uint256", p->getType()); - EXPECT_TRUE(nullptr != std::dynamic_pointer_cast(p).get()); - EXPECT_EQ(nullptr, std::dynamic_pointer_cast(p).get()); - } - { - // int32 is ParamInt32, not ParamIntN - std::shared_ptr p = ParamFactory::make("int32"); - EXPECT_EQ("int32", p->getType()); - EXPECT_TRUE(nullptr != std::dynamic_pointer_cast(p).get()); - EXPECT_EQ(nullptr, std::dynamic_pointer_cast(p).get()); - } - { - // int168 is ParamIntN - std::shared_ptr p = ParamFactory::make("int168"); - EXPECT_EQ("int168", p->getType()); - EXPECT_TRUE(nullptr != std::dynamic_pointer_cast(p).get()); - } - { - // uint is uint256 - std::shared_ptr p = ParamFactory::make("uint"); - EXPECT_EQ("uint256", p->getType()); - EXPECT_TRUE(nullptr != std::dynamic_pointer_cast(p).get()); - } - { - // int is int256 - std::shared_ptr p = ParamFactory::make("int"); - EXPECT_EQ("int256", p->getType()); - EXPECT_TRUE(nullptr != std::dynamic_pointer_cast(p).get()); - } - { - std::shared_ptr p = ParamFactory::make("uint8[]"); - EXPECT_EQ("uint8[]", p->getType()); - EXPECT_TRUE(nullptr != std::dynamic_pointer_cast(p).get()); - auto elemParam = std::dynamic_pointer_cast(p)->getParam(0); - EXPECT_TRUE(nullptr != elemParam.get()); - } - { - std::shared_ptr p = ParamFactory::make("address[]"); - EXPECT_EQ("address[]", p->getType()); - EXPECT_TRUE(nullptr != std::dynamic_pointer_cast(p).get()); - auto elemParam = std::dynamic_pointer_cast(p)->getParam(0); - EXPECT_TRUE(nullptr != elemParam.get()); - } - { - std::shared_ptr p = ParamFactory::make("bytes[]"); - EXPECT_EQ("bytes[]", p->getType()); - EXPECT_TRUE(nullptr != std::dynamic_pointer_cast(p).get()); - auto elemParam = std::dynamic_pointer_cast(p)->getParam(0); - EXPECT_TRUE(nullptr != elemParam.get()); - } -} - -TEST(EthereumAbi, ParamFactoryMakeException) { - EXPECT_EXCEPTION(ParamFactory::make("uint93"), "invalid bit size"); -} - -TEST(EthereumAbi, ParamFactoryGetArrayValue) { - { - auto pArray = std::make_shared(std::make_shared()); - const auto vals = ParamFactory::getArrayValue(pArray, pArray->getType()); - ASSERT_EQ(vals.size(), 1); - EXPECT_EQ(vals[0], "0"); - } - { // wrong type, not array - auto pArray = std::make_shared(std::make_shared()); - const auto vals = ParamFactory::getArrayValue(pArray, "bool"); - EXPECT_EQ(vals.size(), 0); - } - { // wrong param, not array - auto pArray = std::make_shared(); - const auto vals = ParamFactory::getArrayValue(pArray, "uint8[]"); - EXPECT_EQ(vals.size(), 0); - } -} - -TEST(EthereumAbi, ParamFactorySetGetValue) { - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("13")); - EXPECT_EQ("13", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234")); - EXPECT_EQ("1234", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567")); - EXPECT_EQ("1234567", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567")); - EXPECT_EQ("1234567", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(128); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ("1234567890123456789", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0xa5")); - EXPECT_EQ("165", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - } - { - auto p = std::make_shared(168); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ("1234567890123456789", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0xa5")); - EXPECT_EQ("165", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ("1234567890123456789", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0xa5")); - EXPECT_EQ("165", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0x00")); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0x")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("13")); - EXPECT_EQ("13", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234")); - EXPECT_EQ("1234", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567")); - EXPECT_EQ("1234567", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567")); - EXPECT_EQ("1234567", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(128); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ("1234567890123456789", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0xa5")); - EXPECT_EQ("165", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - } - { - auto p = std::make_shared(168); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ("1234567890123456789", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0xa5")); - EXPECT_EQ("165", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("a5")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("1234567890123456789")); - EXPECT_EQ("1234567890123456789", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0xa5")); - EXPECT_EQ("165", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0x00")); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0x")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("false", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("true")); - EXPECT_EQ("true", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("false")); - EXPECT_TRUE(p->setValueJson("1")); - EXPECT_TRUE(p->setValueJson("0")); - EXPECT_FALSE(p->setValueJson("a5")); - EXPECT_FALSE(p->setValueJson("0xa5")); - EXPECT_FALSE(p->setValueJson("0x00")); - } - { - auto p = std::make_shared(); - EXPECT_EQ("", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("ABCdefGHI")); - EXPECT_EQ("ABCdefGHI", ParamFactory::getValue(p, p->getType())); - EXPECT_EQ(9, p->getCount()); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0x", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0123456789")); - EXPECT_EQ("0x0123456789", ParamFactory::getValue(p, p->getType())); - } - { - auto p = std::make_shared(36); - EXPECT_EQ("0x000000000000000000000000000000000000000000000000000000000000000000000000", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0x000000000000000000000000000000000000000000000000000000000000000123456789")); - EXPECT_EQ("0x000000000000000000000000000000000000000000000000000000000000000123456789", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0x0123456789")); // will be padded - EXPECT_EQ("0x012345678900000000000000000000000000000000000000000000000000000000000000", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0xabcdef0000000000000000000000000000000000000000000000000000000000000123456789")); // will be cropped - EXPECT_EQ("0xabcdef000000000000000000000000000000000000000000000000000000000000012345", ParamFactory::getValue(p, p->getType())); - } - { - auto p = std::make_shared(); - EXPECT_EQ("0x0000000000000000000000000000000000000000", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("0x0000000000000000000000000000000123456789")); - EXPECT_EQ("0x0000000000000000000000000000000123456789", ParamFactory::getValue(p, p->getType())); - } - { - auto p = std::make_shared(std::make_shared()); - EXPECT_EQ("[0]", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("[13,14,15]")); - EXPECT_EQ("[13,14,15]", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("13")); - } - { - auto p = std::make_shared(std::make_shared()); - EXPECT_EQ("[\"0x0000000000000000000000000000000000000000\"]", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("[\"0x0000000000000000000000000000000123456789\"]")); - EXPECT_EQ("[\"0x0000000000000000000000000000000123456789\"]", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("0x0000000000000000000000000000000123456789")); - } - { - auto p = std::make_shared(std::make_shared()); - EXPECT_EQ("[false]", ParamFactory::getValue(p, p->getType())); - EXPECT_TRUE(p->setValueJson("[true,false,true]")); - EXPECT_EQ("[true,false,true]", ParamFactory::getValue(p, p->getType())); - EXPECT_FALSE(p->setValueJson("true")); - } -} - -TEST(EthereumAbi, ParamFactoryGetValue) { - const std::vector types = { - "uint8", "uint16", "uint32", "uint64", "uint128", "uint168", "uint256", - "int8", "int16", "int32", "int64", "int128", "int168", "int256", - "bool", "string", "bytes", "bytes168", "address", - "uint8[]", "address[]", "bool[]", "bytes[]", - }; - for (auto t: types) { - std::shared_ptr p = ParamFactory::make(t); - EXPECT_EQ(t, p->getType()); - - std::string expected = ""; - // for numerical values, value is "0" - if (t == "uint8[]" || t == "int8[]") { - expected = "[0]"; - } else if (t == "bool") { - expected = "false"; - } else if (t == "address[]") { - expected = "[\"0x0000000000000000000000000000000000000000\"]"; - } else if (t == "address") { - expected = "0x0000000000000000000000000000000000000000"; - } else if (t == "bool[]") { - expected = "[false]"; - } else if (t == "bytes[]") { - expected = "[\"0x\"]"; - } else if (t == "bytes") { - expected = "0x"; - } else if (t == "bytes168") { - expected = "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; - } else if (t.substr(0, 3) == "int" || t.substr(0, 4) == "uint") { - expected = "0"; - } - EXPECT_EQ(expected, ParamFactory::getValue(p, t)); - } -} - -TEST(EthereumAbi, MaskForBits) { - EXPECT_EQ(0x000000ffffff, ParamUIntN::maskForBits(24)); - EXPECT_EQ(0x00ffffffffff, ParamUIntN::maskForBits(40)); -} - -TEST(EthereumAbi, ParamSetMethods) { - { - auto p = ParamSet(std::vector>{ - std::make_shared(16u), - std::make_shared(true) }); - EXPECT_EQ(p.getCount(), 2); - EXPECT_EQ(p.addParam(std::shared_ptr(nullptr)), -1); - - std::shared_ptr getparam; - EXPECT_TRUE(p.getParam(1, getparam)); - EXPECT_EQ(getparam->getType(), "bool"); - EXPECT_FALSE(p.getParam(2, getparam)); - - EXPECT_EQ(p.getParamUnsafe(0)->getType(), "uint256"); - EXPECT_EQ(p.getParamUnsafe(1)->getType(), "bool"); - EXPECT_EQ(p.getParamUnsafe(2)->getType(), "uint256"); - EXPECT_EQ(p.getParamUnsafe(99)->getType(), "uint256"); - } - { - auto pEmpty = ParamSet(std::vector>{}); - EXPECT_EQ(pEmpty.getParamUnsafe(0).get(), nullptr); - } -} - -TEST(EthereumAbi, ParametersMethods) { - auto p = Parameters(std::vector>{ - std::make_shared(16u), - std::make_shared(true) }); - EXPECT_TRUE(p.isDynamic()); - EXPECT_EQ(p.getCount(), 2); - EXPECT_FALSE(p.setValueJson("value")); - EXPECT_EQ(hex(p.hashStruct()), "755311b9e2cee471a91b161ccc5deed933d844b5af2b885543cc3c04eb640983"); -} diff --git a/tests/Ethereum/AddressTests.cpp b/tests/Ethereum/AddressTests.cpp deleted file mode 100644 index f2fab5a7c5a..00000000000 --- a/tests/Ethereum/AddressTests.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Ethereum/Address.h" -#include "HexCoding.h" -#include "PrivateKey.h" - -#include - -using namespace TW; -using namespace TW::Ethereum; - -TEST(EthereumAddress, Invalid) { - ASSERT_FALSE(Address::isValid("abc")); - ASSERT_FALSE(Address::isValid("aaeb60f3e94c9b9a09f33669435e7ef1beaed")); - ASSERT_FALSE(Address::isValid("fB6916095ca1df60bB79Ce92cE3Ea74c37c5d359")); -} - -TEST(EthereumAddress, EIP55) { - ASSERT_EQ( - Address(parse_hex("5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")).string(), - "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed" - ); - ASSERT_EQ( - Address(parse_hex("0x5AAEB6053F3E94C9b9A09f33669435E7Ef1BEAED")).string(), - "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed" - ); - ASSERT_EQ( - Address(parse_hex("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359")).string(), - "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359" - ); - ASSERT_EQ( - Address(parse_hex("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB")).string(), - "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB" - ); - ASSERT_EQ( - Address(parse_hex("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb")).string(), - "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb" - ); -} - -TEST(EthereumAddress, String) { - const auto address = Address("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"); - ASSERT_EQ(address.string(), "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"); -} - -TEST(EthereumAddress, FromPrivateKey) { - const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); - const auto address = Address(publicKey); - - ASSERT_EQ(address.string(), "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309"); -} - -TEST(EthereumAddress, IsValid) { - ASSERT_FALSE(Address::isValid("abc")); - ASSERT_TRUE(Address::isValid("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")); -} diff --git a/tests/Ethereum/ContractCallTests.cpp b/tests/Ethereum/ContractCallTests.cpp deleted file mode 100644 index f7e27e1e634..00000000000 --- a/tests/Ethereum/ContractCallTests.cpp +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Ethereum/ContractCall.h" -#include "HexCoding.h" - -#include -#include - -using namespace TW; -using namespace TW::Ethereum::ABI; - -extern std::string TESTS_ROOT; - -static nlohmann::json load_json(std::string path) { - std::ifstream stream(path); - nlohmann::json json; - stream >> json; - return json; -} - -TEST(ContractCall, Approval) { - auto path = TESTS_ROOT + "/Ethereum/Data/erc20.json"; - auto abi = load_json(path); - auto call = parse_hex("095ea7b30000000000000000000000005aaeb6053f3e94c9b9a09f33669435e7ef1beaed" - "0000000000000000000000000000000000000000000000000000000000000001"); - auto decoded = decodeCall(call, abi); - - auto expected = - R"|({"function":"approve(address,uint256)","inputs":[{"name":"_spender","type":"address","value":"0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"},{"name":"_value","type":"uint256","value":"1"}]})|"; - - EXPECT_EQ(decoded.value(), expected); -} - -TEST(ContractCall, UniswapSwapTokens) { - auto path = TESTS_ROOT + "/Ethereum/Data/uniswap_router_v2.json"; - auto abi = load_json(path); - // https://etherscan.io/tx/0x57a2414f3cd9ca373b7e663ae67ecf933e40cb77a6e4ed28e4e28b5aa0d8ec63 - auto call = parse_hex( - "0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000" - "00000000000000000000000000000000229f7e501ad62bdb000000000000000000000000000000000000000000" - "00000000000000000000a00000000000000000000000007d8bf18c7ce84b3e175b339c4ca93aed1dd166f10000" - "00000000000000000000000000000000000000000000000000005f0ed070000000000000000000000000000000" - "00000000000000000000000000000000040000000000000000000000006b175474e89094c44da98b954eedeac4" - "95271d0f0000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a2000000000000000000" - "000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000e41d2489571d32218924" - "6dafa5ebde1f4699f498"); - auto decoded = decodeCall(call, abi); - auto expected = - R"|({"function":"swapExactTokensForTokens(uint256,uint256,address[],address,uint256)","inputs":[{"name":"amountIn","type":"uint256","value":"1000000000000000000"},{"name":"amountOutMin","type":"uint256","value":"2494851601099271131"},{"name":"path","type":"address[]","value":["0x6b175474e89094c44da98b954eedeac495271d0f","0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2","0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","0xe41d2489571d322189246dafa5ebde1f4699f498"]},{"name":"to","type":"address","value":"0x7d8bf18c7ce84b3e175b339c4ca93aed1dd166f1"},{"name":"deadline","type":"uint256","value":"1594806384"}]})|"; - - EXPECT_EQ(decoded.value(), expected); -} - -TEST(ContractCall, KyberTrade) { - auto path = TESTS_ROOT + "/Ethereum/Data/kyber_proxy.json"; - auto abi = load_json(path); - - // https://etherscan.io/tx/0x51ffab782b9a27d754389505d5a50db525c04c68142ce20512d579f10f9e13e4 - auto call = parse_hex( - "ae591d54000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000" - "000000000000000000000000000004a97d605a3b980000000000000000000000000000dac17f958d2ee523a220" - "6206994597c13d831ec70000000000000000000000007755297c6a26d495739206181fe81646dbd0bf39ffffff" - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000" - "000000000000000ce32ff7d63c35d189000000000000000000000000440bbd6a888a36de6e2f6a25f65bc4e168" - "74faa9000000000000000000000000000000000000000000000000000000000000000800000000000000000000" - "000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000" - "000000000000000000"); - auto decoded = decodeCall(call, abi); - - auto expected = - R"|({"function":"tradeWithHintAndFee(address,uint256,address,address,uint256,uint256,address,uint256,bytes)","inputs":[{"name":"src","type":"address","value":"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"},{"name":"srcAmount","type":"uint256","value":"86000000000000000000"},{"name":"dest","type":"address","value":"0xdac17f958d2ee523a2206206994597c13d831ec7"},{"name":"destAddress","type":"address","value":"0x7755297c6a26d495739206181fe81646dbd0bf39"},{"name":"maxDestAmount","type":"uint256","value":"115792089237316195423570985008687907853269984665640564039457584007913129639935"},{"name":"minConversionRate","type":"uint256","value":"237731504554534883721"},{"name":"platformWallet","type":"address","value":"0x440bbd6a888a36de6e2f6a25f65bc4e16874faa9"},{"name":"platformFeeBps","type":"uint256","value":"8"},{"name":"hint","type":"bytes","value":"0x"}]})|"; - - EXPECT_EQ(decoded.value(), expected); -} - -TEST(ContractCall, ApprovalForAll) { - auto path = TESTS_ROOT + "/Ethereum/Data/erc721.json"; - auto abi = load_json(path); - - // https://etherscan.io/tx/0xc2744000a107aee4761cf8a638657f91c3003a54e2f1818c37d781be7e48187a - auto call = parse_hex("0xa22cb46500000000000000000000000088341d1a8f672d2780c8dc725902aae72f143b" - "0c0000000000000000000000000000000000000000000000000000000000000001"); - auto decoded = decodeCall(call, abi); - - auto expected = - R"|({"function":"setApprovalForAll(address,bool)","inputs":[{"name":"to","type":"address","value":"0x88341d1a8f672d2780c8dc725902aae72f143b0c"},{"name":"approved","type":"bool","value":true}]})|"; - - EXPECT_EQ(decoded.value(), expected); -} - -TEST(ContractCall, CustomCall) { - auto path = TESTS_ROOT + "/Ethereum/Data/custom.json"; - auto abi = load_json(path); - - auto call = parse_hex("ec37a4a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000067472757374790000000000000000000000000000000000000000000000000000"); - auto decoded = decodeCall(call, abi); - auto expected = - R"|({"function":"setName(string,uint256,int32)","inputs":[{"name":"name","type":"string","value":"trusty"},{"name":"age","type":"uint","value":"3"},{"name":"height","type":"int32","value":"100"}]})|"; - - EXPECT_EQ(decoded.value(), expected); -} - -TEST(ContractCall, SetResolver) { - auto call = parse_hex("0x1896f70ae71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c" - "6f0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41"); - auto path = TESTS_ROOT + "/Ethereum/Data/ens.json"; - auto abi = load_json(path); - auto decoded = decodeCall(call, abi); - auto expected = - R"|({"function":"setResolver(bytes32,address)","inputs":[{"name":"node","type":"bytes32","value":"0xe71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f"},{"name":"resolver","type":"address","value":"0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41"}]})|"; - - EXPECT_EQ(decoded.value(), expected); -} - -TEST(ContractCall, RenewENS) { - auto call = parse_hex( - "0xacf1a84100000000000000000000000000000000000000000000000000000000000000400000000000000000" - "000000000000000000000000000000000000000001e18558000000000000000000000000000000000000000000" - "000000000000000000000a68657769676f76656e7300000000000000000000000000000000000000000000"); - auto path = TESTS_ROOT + "/Ethereum/Data/ens.json"; - auto abi = load_json(path); - auto decoded = decodeCall(call, abi); - auto expected = - R"|({"function":"renew(string,uint256)","inputs":[{"name":"name","type":"string","value":"hewigovens"},{"name":"duration","type":"uint256","value":"31556952"}]})|"; - - EXPECT_EQ(decoded.value(), expected); -} - -TEST(ContractCall, Multicall) { - auto call = parse_hex( - "0xac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000" - "000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000" - "000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000" - "0000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000" - "000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000" - "00000044d5fa2b00e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f0000000000" - "0000000000000047331175b23c2f067204b506ca1501c26731c990000000000000000000000000000000000000" - "000000000000000000000000000000000000000000000000000000000000000000000000000000000064304e6a" - "dee71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f000000000000000000000000" - "000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000" - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - "000000000000000000000000000000000000000000a48b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e" - "574c32a17a67156fa9ed3c4c6f000000000000000000000000000000000000000000000000000000000000003c" - "000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000" - "0000000000000000000000000000000000001447331175b23c2f067204b506ca1501c26731c990000000000000" - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000000000a48b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e57" - "4c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000002ca00" - "000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000" - "000000000000000000000000000000000014d30f834b53d8f7e851e87b90ffa65757a35b850500000000000000" - "000000000000000000000000000000000000000000000000000000000000000000"); - ASSERT_EQ(4 + 928, call.size()); - auto path = TESTS_ROOT + "/Ethereum/Data/ens.json"; - auto abi = load_json(path); - auto decoded = decodeCall(call, abi); - auto expected = - R"|({"function":"multicall(bytes[])","inputs":[{"name":"data","type":"bytes[]","value":["0xd5fa2b00e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000047331175b23c2f067204b506ca1501c26731c990","0x304e6adee71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000","0x8b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001447331175b23c2f067204b506ca1501c26731c990000000000000000000000000","0x8b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000002ca00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000014d30f834b53d8f7e851e87b90ffa65757a35b8505000000000000000000000000"]}]})|"; - - EXPECT_EQ(decoded.value(), expected); -} - -TEST(ContractCall, Invalid) { - EXPECT_FALSE(decodeCall(Data(), "{}").has_value()); - EXPECT_FALSE(decodeCall(parse_hex("0xa22cb46500"), "{}").has_value()); -} - -TEST(ContractCall, GetAmountsOut) { - auto call = parse_hex( - "d06ca61f" - "0000000000000000000000000000000000000000000000000000000000000064" - "0000000000000000000000000000000000000000000000000000000000000040" - "0000000000000000000000000000000000000000000000000000000000000001" - "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077" - ); - auto path = TESTS_ROOT + "/Ethereum/Data/getAmountsOut.json"; - auto abi = load_json(path); - - auto decoded = decodeCall(call, abi); - ASSERT_TRUE(decoded.has_value()); - auto expected = - R"|({"function":"getAmountsOut(uint256,address[])","inputs":[{"name":"amountIn","type":"uint256","value":"100"},{"name":"path","type":"address[]","value":["0xf784682c82526e245f50975190ef0fff4e4fc077"]}]})|"; - EXPECT_EQ(decoded.value(), expected); -} - -TEST(ContractCall, 1inch) { - auto path = TESTS_ROOT + "/Ethereum/Data/1inch.json"; - auto abi = load_json(path); - - // https://etherscan.io/tx/0xc2d113151124579c21332d4cc6ab2b7f61e81d62392ed8596174513cb47e35ba - auto call = parse_hex( - "7c02520000000000000000000000000027239549dd40e1d60f5b80b0c4196923745b1fd2000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001800000000000000000000000002b591e99afe9f32eaa6214f7b7629768c40eeb39000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000027239549dd40e1d60f5b80b0c4196923745b1fd20000000000000000000000001611c227725c5e420ef058275ae772b41775e261000000000000000000000000000000000000000000000000000005d0fadb1c0000000000000000000000000000000000000000000000000000000005c31df1da000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002080000000000000000000000069d91b94f0aaf8e8a2586909fa77a5c2c89818d50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000104128acb080000000000000000000000001611c227725c5e420ef058275ae772b41775e2610000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000005d0fadb1c0000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000002b591e99afe9f32eaa6214f7b7629768c40eeb39000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000" - ); - auto decoded = decodeCall(call, abi); - ASSERT_TRUE(decoded.has_value()); - auto expected = - R"|({"function":"swap(address,(address,address,address,address,uint256,uint256,uint256,bytes),bytes)","inputs":[{"name":"caller","type":"address","value":"0x27239549dd40e1d60f5b80b0c4196923745b1fd2"},{"components":[{"name":"srcToken","type":"address","value":"0x2b591e99afe9f32eaa6214f7b7629768c40eeb39"},{"name":"dstToken","type":"address","value":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"},{"name":"srcReceiver","type":"address","value":"0x27239549dd40e1d60f5b80b0c4196923745b1fd2"},{"name":"dstReceiver","type":"address","value":"0x1611c227725c5e420ef058275ae772b41775e261"},{"name":"amount","type":"uint256","value":"6395120000000"},{"name":"minReturnAmount","type":"uint256","value":"24748356058"},{"name":"flags","type":"uint256","value":"4"},{"name":"permit","type":"bytes","value":"0x"}],"name":"desc","type":"tuple"},{"name":"data","type":"bytes","value":"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002080000000000000000000000069d91b94f0aaf8e8a2586909fa77a5c2c89818d50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000104128acb080000000000000000000000001611c227725c5e420ef058275ae772b41775e2610000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000005d0fadb1c0000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000002b591e99afe9f32eaa6214f7b7629768c40eeb39000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000"}]})|"; - EXPECT_EQ(decoded.value(), expected); -} - -TEST(ContractCall, TupleNested) { - auto path = TESTS_ROOT + "/Ethereum/Data/tuple_nested.json"; - auto abi = load_json(path); - - auto call = parse_hex( - "74b6ef0b" - "0000000000000000000000000000000000000000000000000000000000000001" - "0000000000000000000000000000000000000000000000000000000000000002" - "0000000000000000000000000000000000000000000000000000000000000003" - "0000000000000000000000000000000000000000000000000000000000000004" - "0000000000000000000000000000000000000000000000000000000000000005" - "0000000000000000000000000000000000000000000000000000000000000001" - ); - auto decoded = decodeCall(call, abi); - ASSERT_TRUE(decoded.has_value()); - auto expected = - R"|({"function":"nested_tuple(uint16,(uint16,(uint16,uint64),uint32),bool)","inputs":[{"name":"param1","type":"uint16","value":"1"},{"components":[{"name":"param21","type":"uint16","value":"2"},{"components":[{"name":"param221","type":"uint16","value":"3"},{"name":"param222","type":"uint64","value":"4"}],"name":"param22","type":"tuple"},{"name":"param23","type":"uint32","value":"5"}],"name":"param2","type":"tuple"},{"name":"param3","type":"bool","value":true}]})|"; - EXPECT_EQ(decoded.value(), expected); -} diff --git a/tests/Ethereum/RLPTests.cpp b/tests/Ethereum/RLPTests.cpp deleted file mode 100644 index ed8dd1181c4..00000000000 --- a/tests/Ethereum/RLPTests.cpp +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Ethereum/RLP.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Ethereum; -using boost::multiprecision::uint256_t; - -std::string stringifyItems(const RLP::DecodedItem& di); - -std::string stringifyData(const Data& data) { - if (data.size() == 0) return "0"; - // try if only letters - bool isLettersOnly = true; - for(auto i: data) { - if (!((i >= 'A' && i <= 'Z') || (i >= 'a' && i <= 'z') || i == ' ' || i == ',')) { - isLettersOnly = false; - break; - } - } - if (isLettersOnly) return std::string("'") + std::string(data.begin(), data.end()) + "'"; - // try if it can be parsed (recursive) - if (data.size() >= 2) { - try { - const auto di = RLP::decode(data); - if (di.decoded.size() > 0 && di.remainder.size() == 0) { - return stringifyItems(di); - } - } catch (...) {} - } - // any other: as hex string - return hex(data); -} - -std::string stringifyItems(const RLP::DecodedItem& di) { - const auto n = di.decoded.size(); - if (n == 0) { - return "-"; - } - if (n == 1) { - return stringifyData(di.decoded[0]); - } - std::string res = "(" + std::to_string(n) + ": "; - int count = 0; - for(auto i: di.decoded) { - if (count++) res += " "; - res += stringifyData(i); - } - res += ")"; - return res; -} - -std::string decodeHelper(const std::string& hexData) { - const auto data = parse_hex(hexData); - const auto di = RLP::decode(data); - return stringifyItems(di); -} - -TEST(RLP, EncodeString) { - EXPECT_EQ(hex(RLP::encode("")), "80"); - EXPECT_EQ(hex(RLP::encode("d")), "64"); - EXPECT_EQ(hex(RLP::encode("dog")), "83646f67"); -} - -TEST(RLP, EncodeInteger) { - EXPECT_EQ(hex(RLP::encode(0)), "80"); - EXPECT_EQ(hex(RLP::encode(127)), "7f"); - EXPECT_EQ(hex(RLP::encode(128)), "8180"); - EXPECT_EQ(hex(RLP::encode(255)), "81ff"); - EXPECT_EQ(hex(RLP::encode(256)), "820100"); - EXPECT_EQ(hex(RLP::encode(1024)), "820400"); - EXPECT_EQ(hex(RLP::encode(0xffff)), "82ffff"); - EXPECT_EQ(hex(RLP::encode(0x010000)), "83010000"); - EXPECT_EQ(hex(RLP::encode(0xffffff)), "83ffffff"); - EXPECT_EQ(hex(RLP::encode(static_cast(0xffffffffULL))), "84ffffffff"); - EXPECT_EQ(hex(RLP::encode(static_cast(0xffffffffffffffULL))), "87ffffffffffffff"); -} - -TEST(RLP, EncodeUInt256) { - EXPECT_EQ(hex(RLP::encode(uint256_t(0))), "80"); - EXPECT_EQ(hex(RLP::encode(uint256_t(1))), "01"); - EXPECT_EQ(hex(RLP::encode(uint256_t(127))), "7f"); - EXPECT_EQ(hex(RLP::encode(uint256_t(128))), "8180"); - EXPECT_EQ(hex(RLP::encode(uint256_t(256))), "820100"); - EXPECT_EQ(hex(RLP::encode(uint256_t(1024))), "820400"); - EXPECT_EQ(hex(RLP::encode(uint256_t(0xffffff))), "83ffffff"); - EXPECT_EQ(hex(RLP::encode(uint256_t(0xffffffffULL))), "84ffffffff"); - EXPECT_EQ(hex(RLP::encode(uint256_t(0xffffffffffffffULL))), "87ffffffffffffff"); - EXPECT_EQ( - hex(RLP::encode(uint256_t("0x102030405060708090a0b0c0d0e0f2"))), - "8f102030405060708090a0b0c0d0e0f2" - ); - EXPECT_EQ( - hex(RLP::encode(uint256_t("0x0100020003000400050006000700080009000a000b000c000d000e01"))), - "9c0100020003000400050006000700080009000a000b000c000d000e01" - ); - EXPECT_EQ( - hex(RLP::encode(uint256_t("0x0100000000000000000000000000000000000000000000000000000000000000"))), - "a00100000000000000000000000000000000000000000000000000000000000000" - ); -} - -TEST(RLP, EncodeList) { - EXPECT_EQ(hex(RLP::encodeList(std::vector())), "c0"); - EXPECT_EQ(hex(RLP::encodeList(std::vector{1, 2, 3})), "c3010203"); - EXPECT_EQ(hex(RLP::encodeList(std::vector{"a", "b"})), "c26162"); - EXPECT_EQ(hex(RLP::encodeList(std::vector{"cat", "dog"})), "c88363617483646f67"); - { - const auto encoded = RLP::encodeList(std::vector(1024)); - EXPECT_EQ(hex(subData(encoded, 0, 20)), "f904008080808080808080808080808080808080"); - } -} - -TEST(RLP, EncodeListNested) { - const auto l11 = RLP::encodeList(std::vector{1, 2, 3}); - const auto l12 = RLP::encodeList(std::vector{"apple", "banana", "cherry"}); - const auto l21 = RLP::encodeList(std::vector{parse_hex("abcdef"), parse_hex("00010203040506070809")}); - const auto l22 = RLP::encodeList(std::vector{"bitcoin", "beeenbee", "eth"}); - const auto l1 = RLP::encodeList(std::vector{l11, l12}); - const auto l2 = RLP::encodeList(std::vector{l21, l22}); - const auto encoded = RLP::encodeList(std::vector{l1, l2}); - EXPECT_EQ(hex(encoded), "f8479cdb84c301020395d4856170706c658662616e616e6186636865727279a9e890cf83abcdef8a0001020304050607080996d587626974636f696e88626565656e62656583657468"); -} - -TEST(RLP, EncodeInvalid) { - ASSERT_TRUE(RLP::encode(-1).empty()); - ASSERT_TRUE(RLP::encodeList(std::vector{0, -1}).empty()); -} - -TEST(RLP, DecodeInteger) { - EXPECT_EQ(decodeHelper("00"), "00"); // not the primary encoding for 0 - EXPECT_EQ(decodeHelper("01"), "01"); - EXPECT_EQ(decodeHelper("09"), "09"); - EXPECT_EQ(decodeHelper("7f"), "7f"); - EXPECT_EQ(decodeHelper("80"), "0"); - EXPECT_EQ(decodeHelper("8180"), "80"); - EXPECT_EQ(decodeHelper("81ff"), "ff"); - EXPECT_EQ(decodeHelper("820100"), "0100"); - EXPECT_EQ(decodeHelper("820400"), "0400"); - EXPECT_EQ(decodeHelper("82ffff"), "ffff"); - EXPECT_EQ(decodeHelper("83010000"), "010000"); - EXPECT_EQ(decodeHelper("83ffffff"), "ffffff"); - EXPECT_EQ(decodeHelper("84ffffffff"), "ffffffff"); - EXPECT_EQ(decodeHelper("87ffffffffffffff"), "ffffffffffffff"); -} - -TEST(RLP, DecodeString) { - EXPECT_EQ(decodeHelper("80"), "0"); - EXPECT_EQ(decodeHelper("64"), "'d'"); - EXPECT_EQ(decodeHelper("83646f67"), "'dog'"); - EXPECT_EQ(decodeHelper("83636174"), "'cat'"); - EXPECT_EQ(decodeHelper("8f102030405060708090a0b0c0d0e0f2"), "102030405060708090a0b0c0d0e0f2"); - EXPECT_EQ(decodeHelper("9c0100020003000400050006000700080009000a000b000c000d000e01"), "0100020003000400050006000700080009000a000b000c000d000e01"); - EXPECT_EQ(decodeHelper("a00100000000000000000000000000000000000000000000000000000000000000"), "0100000000000000000000000000000000000000000000000000000000000000"); - // long string - EXPECT_EQ(decodeHelper("b87674686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e67"), - "'this is a a very long string, this is a a very long string, this is a a very long string, this is a a very long string'"); -} - -TEST(RLP, DecodeList) { - // empty list - EXPECT_EQ(decodeHelper("c0"), "-"); - // short list - EXPECT_EQ(decodeHelper("c3010203"), "(3: 01 02 03)"); - EXPECT_EQ(decodeHelper("c26162"), "(2: 'a' 'b')"); - EXPECT_EQ(decodeHelper("c88363617483646f67"), "(2: 'cat' 'dog')"); - - // long list, raw ether transfer tx - EXPECT_EQ(decodeHelper("f86b81a985051f4d5ce982520894515778891c99e3d2e7ae489980cb7c77b37b5e76861b48eb57e0008025a0ad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475a00dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65"), - "(9: " - "a9 " // nonce - "051f4d5ce9 " // gas price - "5208 " // gas limit - "515778891c99e3d2e7ae489980cb7c77b37b5e76 " // to - "1b48eb57e000 " // amount - "0 " // data - "25 " // v - "ad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475 " // r - "0dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65" // s - ")" - ); - - // long list, raw token transfer tx - EXPECT_EQ(decodeHelper("f8aa81d485077359400082db9194dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000c6b6b55c8c4971145a842cc4e5db92d879d0b3e00000000000000000000000000000000000000000000000000000000002faf0801ca02843d8ed66b9623392dc336dd36d5dd5a630b2019962869b6e50fdb4ecb5b6aca05d9ea377bc65e2921f7fc257de8135530cc74e3188b6ba57a4b9cb284393050a"), - "(9: " - "d4 " - "0773594000 " - "db91 " - "dac17f958d2ee523a2206206994597c13d831ec7 " - "0 " - "a9059cbb000000000000000000000000c6b6b55c8c4971145a842cc4e5db92d879d0b3e00000000000000000000000000000000000000000000000000000000002faf080 " - "1c " - "2843d8ed66b9623392dc336dd36d5dd5a630b2019962869b6e50fdb4ecb5b6ac " - "5d9ea377bc65e2921f7fc257de8135530cc74e3188b6ba57a4b9cb284393050a" - ")" - ); - - { - // long list, with 2-byte size - const std::string elem = "0123"; - const int n = 500; - std::vector longarr; - for (auto i = 0; i < n; ++i) longarr.push_back(elem); - - const Data encoded = RLP::encodeList(longarr); - ASSERT_EQ(hex(subData(encoded, 0, 20)), "f909c48430313233843031323384303132338430"); - - auto decoded = RLP::decode(encoded); - ASSERT_EQ(decoded.decoded.size(), n); - for (int i = 0; i < 20; i++) { - EXPECT_EQ(hex(decoded.decoded[i]), "30313233"); - } - } - { - // long list, with 3-byte size - const std::string elem = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; - const int n = 650; - std::vector longarr; - for (auto i = 0; i < n; ++i) longarr.push_back(elem); - - const Data encoded = RLP::encodeList(longarr); - ASSERT_EQ(encoded.size(), 66304); - ASSERT_EQ(hex(subData(encoded, 0, 30)), "fa0102fcb864303132333435363738393031323334353637383930313233"); - - auto decoded = RLP::decode(encoded); - ASSERT_EQ(decoded.decoded.size(), n); - } - - // nested list - EXPECT_EQ(decodeHelper("f8479cdb84c301020395d4856170706c658662616e616e6186636865727279a9e890cf83abcdef8a0001020304050607080996d587626974636f696e88626565656e62656583657468"), - "(2: (2: (3: 01 02 03) (3: 'apple' 'banana' 'cherry')) (2: (2: abcdef 00010203040506070809) (3: 'bitcoin' 'beeenbee' 'eth')))"); -} - -TEST(RLP, DecodeInvalid) { - // decode empty data - EXPECT_THROW(RLP::decode(Data()), std::invalid_argument); - - // incorrect length - EXPECT_THROW(RLP::decode(parse_hex("0x81636174")), std::invalid_argument); - EXPECT_THROW(RLP::decode(parse_hex("0xb9ffff")), std::invalid_argument); - EXPECT_THROW(RLP::decode(parse_hex("0xc883636174")), std::invalid_argument); - - // some tests are from https://github.com/ethereum/tests/blob/develop/RLPTests/invalidRLPTest.json - // int32 overflow - EXPECT_THROW(RLP::decode(parse_hex("0xbf0f000000000000021111")), std::invalid_argument); - - // wrong size list - EXPECT_THROW(RLP::decode(parse_hex("0xf80180")), std::invalid_argument); - - // bytes should be single byte - EXPECT_THROW(RLP::decode(parse_hex("0x8100")), std::invalid_argument); - - // leading zeros in long length list - EXPECT_THROW(RLP::decode(parse_hex("fb00000040000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f")), std::invalid_argument); - EXPECT_THROW(RLP::decode(parse_hex("f800")), std::invalid_argument); -} - -TEST(RLP, putVarInt) { - EXPECT_EQ(hex(RLP::putVarInt(0)), "00"); - EXPECT_EQ(hex(RLP::putVarInt(1)), "01"); - EXPECT_EQ(hex(RLP::putVarInt(0x21)), "21"); - EXPECT_EQ(hex(RLP::putVarInt(0xff)), "ff"); - EXPECT_EQ(hex(RLP::putVarInt(0x100)), "0100"); - EXPECT_EQ(hex(RLP::putVarInt(0x4321)), "4321"); - EXPECT_EQ(hex(RLP::putVarInt(0x654321)), "654321"); - EXPECT_EQ(hex(RLP::putVarInt(0x87654321)), "87654321"); - EXPECT_EQ(hex(RLP::putVarInt(0xa987654321)), "a987654321"); - EXPECT_EQ(hex(RLP::putVarInt(0xcba987654321)), "cba987654321"); - EXPECT_EQ(hex(RLP::putVarInt(0xedcba987654321)), "edcba987654321"); - EXPECT_EQ(hex(RLP::putVarInt(0x21edcba987654321)), "21edcba987654321"); - EXPECT_EQ(hex(RLP::putVarInt(0xffffffffffffffff)), "ffffffffffffffff"); -} - -TEST(RLP, parseVarInt) { - EXPECT_EQ(hex(store(RLP::parseVarInt(1, parse_hex("00"), 0))), "00"); - EXPECT_EQ(hex(store(RLP::parseVarInt(1, parse_hex("01"), 0))), "01"); - EXPECT_EQ(hex(store(RLP::parseVarInt(1, parse_hex("fc"), 0))), "fc"); - EXPECT_EQ(hex(store(RLP::parseVarInt(1, parse_hex("ff"), 0))), "ff"); - EXPECT_EQ(hex(store(RLP::parseVarInt(1, parse_hex("abcd"), 1))), "cd"); - EXPECT_EQ(hex(store(RLP::parseVarInt(2, parse_hex("0102"), 0))), "0102"); - EXPECT_EQ(hex(store(RLP::parseVarInt(2, parse_hex("0100"), 0))), "0100"); - EXPECT_EQ(hex(store(RLP::parseVarInt(2, parse_hex("fedc"), 0))), "fedc"); - EXPECT_EQ(hex(store(RLP::parseVarInt(2, parse_hex("ffff"), 0))), "ffff"); - EXPECT_EQ(hex(store(RLP::parseVarInt(3, parse_hex("010203"), 0))), "010203"); - EXPECT_EQ(hex(store(RLP::parseVarInt(4, parse_hex("01020304"), 0))), "01020304"); - EXPECT_EQ(hex(store(RLP::parseVarInt(5, parse_hex("0102030405"), 0))), "0102030405"); - EXPECT_EQ(hex(store(RLP::parseVarInt(6, parse_hex("010203040506"), 0))), "010203040506"); - EXPECT_EQ(hex(store(RLP::parseVarInt(7, parse_hex("01020304050607"), 0))), "01020304050607"); - EXPECT_EQ(hex(store(RLP::parseVarInt(8, parse_hex("0102030405060708"), 0))), "0102030405060708"); - EXPECT_EQ(hex(store(RLP::parseVarInt(8, parse_hex("abcd0102030405060708"), 2))), "0102030405060708"); - EXPECT_THROW(RLP::parseVarInt(0, parse_hex("01"), 0), std::invalid_argument); // wrong size - EXPECT_THROW(RLP::parseVarInt(9, parse_hex("010203040506070809"), 0), std::invalid_argument); // wrong size - EXPECT_THROW(RLP::parseVarInt(4, parse_hex("0102"), 0), std::invalid_argument); // too short - EXPECT_THROW(RLP::parseVarInt(4, parse_hex("01020304"), 2), std::invalid_argument); // too short - EXPECT_THROW(RLP::parseVarInt(2, parse_hex("0002"), 0), std::invalid_argument); // starts with 0 -} diff --git a/tests/Ethereum/SignerTests.cpp b/tests/Ethereum/SignerTests.cpp deleted file mode 100644 index 0899ff812c0..00000000000 --- a/tests/Ethereum/SignerTests.cpp +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Ethereum/Address.h" -#include "Ethereum/RLP.h" -#include "Ethereum/Signer.h" -#include "Ethereum/Transaction.h" -#include "HexCoding.h" - -#include - -namespace TW::Ethereum { - -using boost::multiprecision::uint256_t; - - -TEST(EthereumTransaction, encodeTransactionNonTyped) { - const auto transaction = TransactionNonTyped::buildERC20Transfer( - /* nonce: */ 0, - /* gasPrice: */ 42000000000, // 0x09c7652400 - /* gasLimit: */ 78009, // 130B9 - /* tokenContract: */ parse_hex("0x6b175474e89094c44da98b954eedeac495271d0f"), // DAI - /* toAddress: */ parse_hex("0x5322b34c88ed0691971bf52a7047448f0f4efc84"), - /* amount: */ 2000000000000000000 - ); - const uint256_t dummyChain = 0x34; - const auto dummySignature = Signature{0, 0, 0}; - - const auto preHash = transaction->preHash(dummyChain); - EXPECT_EQ(hex(preHash), "b3525019dc367d3ecac48905f9a95ff3550c25a24823db765f92cae2dec7ebfd"); - - const auto encoded = transaction->encoded(dummySignature, dummyChain); - EXPECT_EQ(hex(encoded), "f86a808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000808080"); -} - -TEST(EthereumSigner, PreHash) { - const auto address = parse_hex("0x3535353535353535353535353535353535353535"); - const auto transaction = TransactionNonTyped::buildNativeTransfer( - /* nonce: */ 9, - /* gasPrice: */ 20000000000, - /* gasLimit: */ 21000, - /* to: */ address, - /* amount: */ 1000000000000000000 - ); - const auto preHash = transaction->preHash(1); - - ASSERT_EQ(hex(preHash), "daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53"); -} - -TEST(EthereumSigner, Sign) { - const auto address = parse_hex("0x3535353535353535353535353535353535353535"); - const auto transaction = TransactionNonTyped::buildNativeTransfer( - /* nonce: */ 9, - /* gasPrice: */ 20000000000, - /* gasLimit: */ 21000, - /* to: */ address, - /* amount: */ 1000000000000000000 - ); - - const auto key = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); - const auto signature = Signer::sign(key, 1, transaction); - - ASSERT_EQ(signature.v, 37); - ASSERT_EQ(signature.r, uint256_t("18515461264373351373200002665853028612451056578545711640558177340181847433846")); - ASSERT_EQ(signature.s, uint256_t("46948507304638947509940763649030358759909902576025900602547168820602576006531")); -} - -TEST(EthereumSigner, SignERC20Transfer) { - const auto transaction = TransactionNonTyped::buildERC20Transfer( - /* nonce: */ 0, - /* gasPrice: */ 42000000000, // 0x09c7652400 - /* gasLimit: */ 78009, // 130B9 - /* tokenContract: */ parse_hex("0x6b175474e89094c44da98b954eedeac495271d0f"), // DAI - /* toAddress: */ parse_hex("0x5322b34c88ed0691971bf52a7047448f0f4efc84"), - /* amount: */ 2000000000000000000 - ); - - const auto key = PrivateKey(parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151")); - const auto signature = Signer::sign(key, 1, transaction); - - ASSERT_EQ(signature.v, 37); - ASSERT_EQ(hex(store(signature.r)), "724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98ce"); - ASSERT_EQ(hex(store(signature.s)), "032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931"); -} - -TEST(EthereumSigner, EIP1559_1442) { - const auto transaction = TransactionEip1559::buildNativeTransfer( - 6, // nonce - 2000000000, // maxInclusionFeePerGas - 3000000000, // maxFeePerGas - 21100, // gasLimit - parse_hex("B9F5771C27664bF2282D98E09D7F50cEc7cB01a7"), - 543210987654321, // amount - Data() // data - ); - - const uint256_t chainID = 3; - const auto key = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); - const auto signature = Signer::sign(key, chainID, transaction); - EXPECT_EQ(signature.v, 0); - EXPECT_EQ(hex(store(signature.r)), "92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c64"); - EXPECT_EQ(hex(store(signature.s)), "6487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e58"); - - const auto encoded = transaction->encoded(signature, chainID); - // https://ropsten.etherscan.io/tx/0x14429509307efebfdaa05227d84c147450d168c68539351fbc01ed87c916ab2e - EXPECT_EQ(hex(encoded), "02f8710306847735940084b2d05e0082526c94b9f5771c27664bf2282d98e09d7f50cec7cb01a78701ee0c29f50cb180c080a092c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c64a06487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e58"); -} - -} // namespace TW::Ethereum diff --git a/tests/Ethereum/TWAnySignerTests.cpp b/tests/Ethereum/TWAnySignerTests.cpp deleted file mode 100644 index b57e9b26232..00000000000 --- a/tests/Ethereum/TWAnySignerTests.cpp +++ /dev/null @@ -1,490 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" -#include -#include "HexCoding.h" -#include "uint256.h" -#include "proto/Ethereum.pb.h" -#include "Ethereum/ABI/Function.h" -#include "Ethereum/ABI/ParamBase.h" -#include "Ethereum/ABI/ParamAddress.h" - -#include - -using namespace TW; -using namespace TW::Ethereum; -using namespace TW::Ethereum::ABI; - -TEST(TWEthereumSigner, EmptyValue) { - auto str = std::string(""); - uint256_t zero = load(str); - - ASSERT_EQ(zero, uint256_t(0)); -} - -TEST(TWEthereumSigner, BigInt) { - // Check uint256_t loading - Data expectedData = {0x52, 0x08}; - auto value = uint256_t(21000); - auto loaded = load(expectedData); - ASSERT_EQ(loaded, value); - - // Check proto storing - Proto::SigningInput input; - auto storedData = store(value); - input.set_gas_limit(storedData.data(), storedData.size()); - ASSERT_EQ(hex(input.gas_limit()), hex(expectedData)); - - // Check proto loading - auto protoLoaded = load(input.gas_limit()); - ASSERT_EQ(protoLoaded, value); -} - -TEST(TWAnySignerEthereum, Sign) { - // from http://thetokenfactory.com/#/factory - // https://etherscan.io/tx/0x63879f20909a315bcffe629bc03b20e5bc65ba2a377bd7152e3b69c4bd4cd6cc - Proto::SigningInput input; - auto chainId = store(uint256_t(1)); - auto nonce = store(uint256_t(11)); - auto gasPrice = store(uint256_t(20000000000)); - auto gasLimit = store(uint256_t(1000000)); - auto data = parse_hex("0x60a060405260046060527f48302e31000000000000000000000000000000000000000000000000000000006080526006805460008290527f48302e310000000000000000000000000000000000000000000000000000000882556100b5907ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f602060026001841615610100026000190190931692909204601f01919091048101905b8082111561017957600081556001016100a1565b505060405161094b38038061094b83398101604052808051906020019091908051820191906020018051906020019091908051820191906020015050836000600050600033600160a060020a0316815260200190815260200160002060005081905550836002600050819055508260036000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061017d57805160ff19168380011785555b506101ad9291506100a1565b5090565b8280016001018555821561016d579182015b8281111561016d57825182600050559160200191906001019061018f565b50506004805460ff19168317905560058054825160008390527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0602060026001851615610100026000190190941693909304601f90810184900482019386019083901061022d57805160ff19168380011785555b5061025d9291506100a1565b82800160010185558215610221579182015b8281111561022157825182600050559160200191906001019061023f565b5050505050506106da806102716000396000f36060604052361561008d5760e060020a600035046306fdde038114610095578063095ea7b3146100f357806318160ddd1461016857806323b872dd14610171578063313ce5671461025c57806354fd4d501461026857806370a08231146102c657806395d89b41146102f4578063a9059cbb14610352578063cae9ca51146103f7578063dd62ed3e146105be575b6105f2610002565b6040805160038054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03908116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b6102e260025481565b610662600435602435604435600160a060020a0383166000908152602081905260408120548290108015906101c4575060016020908152604080832033600160a060020a03168452909152812054829010155b80156101d05750600082115b156106bf57600160a060020a0383811660008181526020818152604080832080548801905588851680845281842080548990039055600183528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016106c3565b61067660045460ff1681565b6040805160068054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b600160a060020a03600435166000908152602081905260409020545b60408051918252519081900360200190f35b6105f46005805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03166000908152602081905260408120548290108015906103845750600082115b156106ca5733600160a060020a0390811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a3506001610162565b604080516020604435600481810135601f810184900484028501840190955284845261066294813594602480359593946064949293910191819084018382808284375094965050505050505033600160a060020a03908116600081815260016020908152604080832094881680845294825280832087905580518781529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a383600160a060020a031660405180807f72656365697665417070726f76616c28616464726573732c75696e743235362c81526020017f616464726573732c627974657329000000000000000000000000000000000000815260200150602e019050604051809103902060e060020a9004338530866040518560e060020a0281526004018085600160a060020a0316815260200184815260200183600160a060020a031681526020018280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156105965780820380516001836020036101000a031916815260200191505b509450505050506000604051808303816000876161da5a03f19250505015156106d257610002565b6102e2600435602435600160a060020a03828116600090815260016020908152604080832093851683529290522054610162565b005b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156106545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080519115158252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b81548152906001019060200180831161069a57829003601f168201915b505050505081565b5060005b9392505050565b506000610162565b5060016106c35600000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000754204275636b73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003544f540000000000000000000000000000000000000000000000000000000000"); - auto key = parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); - - input.set_chain_id(chainId.data(), chainId.size()); - input.set_nonce(nonce.data(), nonce.size()); - // tx_mode not set, Legacy is the default - input.set_gas_price(gasPrice.data(), gasPrice.size()); - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_private_key(key.data(), key.size()); - auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); - transfer.set_data(data.data(), data.size()); - - std::string expected = "f90a9e0b8504a817c800830f42408080b90a4b60a060405260046060527f48302e31000000000000000000000000000000000000000000000000000000006080526006805460008290527f48302e310000000000000000000000000000000000000000000000000000000882556100b5907ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f602060026001841615610100026000190190931692909204601f01919091048101905b8082111561017957600081556001016100a1565b505060405161094b38038061094b83398101604052808051906020019091908051820191906020018051906020019091908051820191906020015050836000600050600033600160a060020a0316815260200190815260200160002060005081905550836002600050819055508260036000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061017d57805160ff19168380011785555b506101ad9291506100a1565b5090565b8280016001018555821561016d579182015b8281111561016d57825182600050559160200191906001019061018f565b50506004805460ff19168317905560058054825160008390527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0602060026001851615610100026000190190941693909304601f90810184900482019386019083901061022d57805160ff19168380011785555b5061025d9291506100a1565b82800160010185558215610221579182015b8281111561022157825182600050559160200191906001019061023f565b5050505050506106da806102716000396000f36060604052361561008d5760e060020a600035046306fdde038114610095578063095ea7b3146100f357806318160ddd1461016857806323b872dd14610171578063313ce5671461025c57806354fd4d501461026857806370a08231146102c657806395d89b41146102f4578063a9059cbb14610352578063cae9ca51146103f7578063dd62ed3e146105be575b6105f2610002565b6040805160038054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03908116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b6102e260025481565b610662600435602435604435600160a060020a0383166000908152602081905260408120548290108015906101c4575060016020908152604080832033600160a060020a03168452909152812054829010155b80156101d05750600082115b156106bf57600160a060020a0383811660008181526020818152604080832080548801905588851680845281842080548990039055600183528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016106c3565b61067660045460ff1681565b6040805160068054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b600160a060020a03600435166000908152602081905260409020545b60408051918252519081900360200190f35b6105f46005805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03166000908152602081905260408120548290108015906103845750600082115b156106ca5733600160a060020a0390811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a3506001610162565b604080516020604435600481810135601f810184900484028501840190955284845261066294813594602480359593946064949293910191819084018382808284375094965050505050505033600160a060020a03908116600081815260016020908152604080832094881680845294825280832087905580518781529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a383600160a060020a031660405180807f72656365697665417070726f76616c28616464726573732c75696e743235362c81526020017f616464726573732c627974657329000000000000000000000000000000000000815260200150602e019050604051809103902060e060020a9004338530866040518560e060020a0281526004018085600160a060020a0316815260200184815260200183600160a060020a031681526020018280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156105965780820380516001836020036101000a031916815260200191505b509450505050506000604051808303816000876161da5a03f19250505015156106d257610002565b6102e2600435602435600160a060020a03828116600090815260016020908152604080832093851683529290522054610162565b005b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156106545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080519115158252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b81548152906001019060200180831161069a57829003601f168201915b505050505081565b5060005b9392505050565b506000610162565b5060016106c35600000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000754204275636b73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003544f54000000000000000000000000000000000000000000000000000000000026a042556c4f2a3f4e4e639cca524d1da70e60881417d4643e5382ed110a52719eafa0172f591a2a763d0bd6b13d042d8c5eb66e87f129c9dc77ada66b6041012db2b3"; - - { - // sign test - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeEthereum); - - ASSERT_EQ(hex(output.encoded()), expected); - ASSERT_EQ(hex(output.data()), hex(data)); - } -} - -TEST(TWAnySignerEthereum, SignERC20TransferAsERC20) { - auto chainId = store(uint256_t(1)); - auto nonce = store(uint256_t(0)); - auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 - auto gasLimit = store(uint256_t(78009)); // 130B9 - auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; - auto token = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI - auto amount = uint256_t(2000000000000000000); - auto amountData = store(amount); - auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); - - Proto::SigningInput input; - input.set_chain_id(chainId.data(), chainId.size()); - input.set_nonce(nonce.data(), nonce.size()); - // tx_mode not set, Legacy is the default - input.set_gas_price(gasPrice.data(), gasPrice.size()); - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_to_address(token); - input.set_private_key(key.data(), key.size()); - auto& erc20 = *input.mutable_transaction()->mutable_erc20_transfer(); - erc20.set_to(toAddress); - erc20.set_amount(amountData.data(), amountData.size()); - - // https://etherscan.io/tx/0x199a7829fc5149e49b452c2cab76d8fa5a9682fee6e4891b8acb697ac142513e - std::string expected = "f8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec8000025a0724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98cea0032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931"; - - // sign test - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeEthereum); - - ASSERT_EQ(hex(output.encoded()), expected); - - // expected payload - Data payload; - { - auto func = Function("transfer", std::vector>{ - std::make_shared(parse_hex(toAddress)), - std::make_shared(amount) - }); - func.encode(payload); - } - ASSERT_EQ(hex(output.data()), hex(payload)); - ASSERT_EQ(hex(output.data()), "a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000"); -} - -TEST(TWAnySignerEthereum, SignERC20TransferAsGenericContract) { - auto chainId = store(uint256_t(1)); - auto nonce = store(uint256_t(0)); - auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 - auto gasLimit = store(uint256_t(78009)); // 130B9 - auto toAddress = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI - // payload: transfer(0x5322b34c88ed0691971bf52a7047448f0f4efc84, 2000000000000000000) - auto data = parse_hex("0xa9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000"); - auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); - - Proto::SigningInput input; - input.set_chain_id(chainId.data(), chainId.size()); - input.set_nonce(nonce.data(), nonce.size()); - // tx_mode not set, Legacy is the default - input.set_gas_price(gasPrice.data(), gasPrice.size()); - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_to_address(toAddress); - input.set_private_key(key.data(), key.size()); - auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); - transfer.set_data(data.data(), data.size()); - - // https://etherscan.io/tx/0x199a7829fc5149e49b452c2cab76d8fa5a9682fee6e4891b8acb697ac142513e - std::string expected = "f8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec8000025a0724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98cea0032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931"; - - // sign test - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeEthereum); - - ASSERT_EQ(hex(output.encoded()), expected); - ASSERT_EQ(hex(output.data()), hex(data)); -} - -TEST(TWAnySignerEthereum, SignERC20TransferInvalidAddress) { - auto chainId = store(uint256_t(1)); - auto nonce = store(uint256_t(0)); - auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 - auto gasLimit = store(uint256_t(78009)); // 130B9 - auto invalidAddress = "0xdeadbeef"; - auto amount = store(uint256_t(2000000000000000000)); - auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); - - Proto::SigningInput input; - input.set_chain_id(chainId.data(), chainId.size()); - input.set_nonce(nonce.data(), nonce.size()); - // tx_mode not set, Legacy is the default - input.set_gas_price(gasPrice.data(), gasPrice.size()); - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_to_address(invalidAddress); - input.set_private_key(key.data(), key.size()); - auto& erc20 = *input.mutable_transaction()->mutable_erc20_transfer(); - erc20.set_to(invalidAddress); - erc20.set_amount(amount.data(), amount.size()); - - // sign test - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeEthereum); - - ASSERT_EQ(hex(output.encoded()), ""); -} - -TEST(TWAnySignerEthereum, SignERC20Approve) { - auto chainId = store(uint256_t(1)); - auto nonce = store(uint256_t(0)); - auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 - auto gasLimit = store(uint256_t(78009)); // 130B9 - auto spenderAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; - auto token = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI - auto amount = store(uint256_t(2000000000000000000)); - auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); - - Proto::SigningInput input; - input.set_chain_id(chainId.data(), chainId.size()); - input.set_nonce(nonce.data(), nonce.size()); - // tx_mode not set, Legacy is the default - input.set_gas_price(gasPrice.data(), gasPrice.size()); - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_to_address(token); - input.set_private_key(key.data(), key.size()); - auto& erc20 = *input.mutable_transaction()->mutable_erc20_approve(); - erc20.set_spender(spenderAddress); - erc20.set_amount(amount.data(), amount.size()); - - std::string expected = "f8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844095ea7b30000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec8000025a0d8136d66da1e0ba8c7208d5c4f143167f54b89a0fe2e23440653bcca28b34dc1a049222a79339f1a9e4641cb4ad805c49c225ae704299ffc10627bf41c035c464a"; - - // sign test - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeEthereum); - - ASSERT_EQ(hex(output.encoded()), expected); -} - -TEST(TWAnySignerEthereum, SignERC721Transfer) { - auto chainId = store(uint256_t(1)); - auto nonce = store(uint256_t(0)); - auto gasPrice = store(uint256_t(42000000000)); - auto gasLimit = store(uint256_t(78009)); - auto tokenContract = "0x4e45e92ed38f885d39a733c14f1817217a89d425"; - auto fromAddress = "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc"; - auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; - auto tokenId = parse_hex("23c47ee5"); - auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); - - Proto::SigningInput input; - input.set_chain_id(chainId.data(), chainId.size()); - input.set_nonce(nonce.data(), nonce.size()); - // tx_mode not set, Legacy is the default - input.set_gas_price(gasPrice.data(), gasPrice.size()); - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_to_address(tokenContract); - input.set_private_key(key.data(), key.size()); - auto& erc721 = *input.mutable_transaction()->mutable_erc721_transfer(); - erc721.set_from(fromAddress); - erc721.set_to(toAddress); - erc721.set_token_id(tokenId.data(), tokenId.size()); - - std::string expected = "f8ca808509c7652400830130b9944e45e92ed38f885d39a733c14f1817217a89d42580b86423b872dd000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee526a0d38440a4dc140a4100d301eb49fcc35b64439e27d1d8dd9b55823dca04e6e659a03b5f56a57feabc3406f123d6f8198cd7d7e2ced7e2d58d375f076952ecd9ce88"; - - // sign test - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeEthereum); - - ASSERT_EQ(hex(output.encoded()), expected); - ASSERT_EQ(hex(output.data()), "23b872dd000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee5"); -} - -TEST(TWAnySignerEthereum, SignERC1155Transfer) { - auto chainId = store(uint256_t(1)); - auto nonce = store(uint256_t(0)); - auto gasPrice = store(uint256_t(42000000000)); - auto gasLimit = store(uint256_t(78009)); - auto tokenContract = "0x4e45e92ed38f885d39a733c14f1817217a89d425"; - auto fromAddress = "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc"; - auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; - auto tokenId = parse_hex("23c47ee5"); - auto value = uint256_t(2000000000000000000); - auto valueData = store(value); - auto data = parse_hex("01020304"); - auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); - - Proto::SigningInput input; - input.set_chain_id(chainId.data(), chainId.size()); - input.set_nonce(nonce.data(), nonce.size()); - // tx_mode not set, Legacy is the default - input.set_gas_price(gasPrice.data(), gasPrice.size()); - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_to_address(tokenContract); - input.set_private_key(key.data(), key.size()); - auto& erc1155 = *input.mutable_transaction()->mutable_erc1155_transfer(); - erc1155.set_from(fromAddress); - erc1155.set_to(toAddress); - erc1155.set_token_id(tokenId.data(), tokenId.size()); - erc1155.set_value(valueData.data(), valueData.size()); - erc1155.set_data(data.data(), data.size()); - - std::string expected = "f9014a808509c7652400830130b9944e45e92ed38f885d39a733c14f1817217a89d42580b8e4f242432a000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee50000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000004010203040000000000000000000000000000000000000000000000000000000026a010315488201ac801ce346bffd1570de147615462d7e7db3cf08cf558465c6b79a06643943b24593bc3904a9fda63bb169881730994c973ab80f07d66a698064573"; - - // sign test - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeEthereum); - - ASSERT_EQ(hex(output.encoded()), expected); - - // expected payload - Data payload; - { - auto func = Function("safeTransferFrom", std::vector>{ - std::make_shared(parse_hex(fromAddress)), - std::make_shared(parse_hex(toAddress)), - std::make_shared(uint256_t(0x23c47ee5)), - std::make_shared(value), - std::make_shared(data) - }); - func.encode(payload); - } - ASSERT_EQ(hex(output.data()), hex(payload)); - ASSERT_EQ(hex(output.data()), "f242432a000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee50000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000"); -} - -TEST(TWAnySignerEthereum, SignJSON) { - auto json = STRING(R"({"chainId":"AQ==","gasPrice":"1pOkAA==","gasLimit":"Ugg=","toAddress":"0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1","transaction":{"transfer":{"amount":"A0i8paFgAA=="}}})"); - auto key = DATA("17209af590a86462395d5881e60d11c7fa7d482cfb02b5a01b93c2eeef243543"); - auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeEthereum)); - - ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeEthereum)); - assertStringsEqual(result, "f86a8084d693a400825208947d8bf18c7ce84b3e175b339c4ca93aed1dd166f1870348bca5a160008025a0fe5802b49e04c6b1705088310e133605ed8b549811a18968ad409ea02ad79f21a05bf845646fb1e1b9365f63a7fd5eb5e984094e3ed35c3bed7361aebbcbf41f10"); -} - -TEST(TWAnySignerEthereum, PlanNotSupported) { - // Ethereum does not use plan(), call it nonetheless - Proto::SigningInput input; - auto inputData = input.SerializeAsString(); - auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size())); - auto outputTWData = WRAPD(TWAnySignerPlan(inputTWData.get(), TWCoinTypeEthereum)); - EXPECT_EQ(TWDataSize(outputTWData.get()), 0); -} - -TEST(TWAnySignerEthereum, SignERC1559Transfer_1442) { - auto chainId = store(uint256_t(3)); - auto nonce = store(uint256_t(6)); - auto gasLimit = store(uint256_t(21100)); - auto maxInclusionFeePerGas = store(uint256_t(2000000000)); - auto maxFeePerGas = store(uint256_t(3000000000)); - auto toAddress = "0xB9F5771C27664bF2282D98E09D7F50cEc7cB01a7"; - auto value = uint256_t(543210987654321); - auto valueData = store(value); - auto key = parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904"); - - Proto::SigningInput input; - input.set_chain_id(chainId.data(), chainId.size()); - input.set_nonce(nonce.data(), nonce.size()); - input.set_tx_mode(Proto::TransactionMode::Enveloped); // EIP1559 - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); - input.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); - input.set_to_address(toAddress); - input.set_private_key(key.data(), key.size()); - auto& transfer = *input.mutable_transaction()->mutable_transfer(); - transfer.set_amount(valueData.data(), valueData.size()); - transfer.set_data(Data().data(), 0); - - // https://ropsten.etherscan.io/tx/0x14429509307efebfdaa05227d84c147450d168c68539351fbc01ed87c916ab2e - std::string expected = "02f8710306847735940084b2d05e0082526c94b9f5771c27664bf2282d98e09d7f50cec7cb01a78701ee0c29f50cb180c080a092c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c64a06487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e58"; - - // sign test - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeEthereum); - - EXPECT_EQ(hex(output.encoded()), expected); - EXPECT_EQ(hex(output.v()), "00"); - EXPECT_EQ(hex(output.r()), "92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c64"); - EXPECT_EQ(hex(output.s()), "6487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e58"); - EXPECT_EQ(hex(output.data()), ""); -} - -TEST(TWAnySignerEthereum, SignERC20Transfer_1559) { - auto chainId = store(uint256_t(1)); - auto nonce = store(uint256_t(0)); - auto gasLimit = store(uint256_t(78009)); // 130B9 - auto maxInclusionFeePerGas = store(uint256_t(2000000000)); // 77359400 - auto maxFeePerGas = store(uint256_t(3000000000)); // B2D05E00 - auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; - auto token = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI - auto amount = uint256_t(2000000000000000000); - auto amountData = store(amount); - auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); - - Proto::SigningInput input; - input.set_chain_id(chainId.data(), chainId.size()); - input.set_nonce(nonce.data(), nonce.size()); - input.set_tx_mode(Proto::TransactionMode::Enveloped); - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); - input.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); - input.set_to_address(token); - input.set_private_key(key.data(), key.size()); - auto& erc20 = *input.mutable_transaction()->mutable_erc20_transfer(); - erc20.set_to(toAddress); - erc20.set_amount(amountData.data(), amountData.size()); - - // sign test - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeEthereum); - - ASSERT_EQ(hex(output.encoded()), "02f8b00180847735940084b2d05e00830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000c080a0adfcfdf98d4ed35a8967a0c1d78b42adb7c5d831cf5a3272654ec8f8bcd7be2ea011641e065684f6aa476f4fd250aa46cd0b44eccdb0a6e1650d658d1998684cdf"); -} - -TEST(TWAnySignerEthereum, SignERC20Approve_1559) { - auto chainId = store(uint256_t(1)); - auto nonce = store(uint256_t(0)); - auto gasLimit = store(uint256_t(78009)); // 130B9 - auto maxInclusionFeePerGas = store(uint256_t(2000000000)); - auto maxFeePerGas = store(uint256_t(3000000000)); - auto spenderAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; - auto token = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI - auto amount = store(uint256_t(2000000000000000000)); - auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); - - Proto::SigningInput input; - input.set_chain_id(chainId.data(), chainId.size()); - input.set_nonce(nonce.data(), nonce.size()); - input.set_tx_mode(Proto::TransactionMode::Enveloped); - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); - input.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); - input.set_to_address(token); - input.set_private_key(key.data(), key.size()); - auto& erc20 = *input.mutable_transaction()->mutable_erc20_approve(); - erc20.set_spender(spenderAddress); - erc20.set_amount(amount.data(), amount.size()); - - // sign test - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeEthereum); - - ASSERT_EQ(hex(output.encoded()), "02f8b00180847735940084b2d05e00830130b9946b175474e89094c44da98b954eedeac495271d0f80b844095ea7b30000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000c080a05a43dda3dc193480ee532a5ed67ba8fbd2e3afb9eee218f4fb955b415d592925a01300e5b5f51c8cd5bf80f018cea3fb347fae589e65355068ac44ffc996313c60"); -} - -TEST(TWAnySignerEthereum, SignERC721Transfer_1559) { - auto chainId = store(uint256_t(1)); - auto nonce = store(uint256_t(0)); - auto gasLimit = store(uint256_t(78009)); - auto maxInclusionFeePerGas = store(uint256_t(2000000000)); - auto maxFeePerGas = store(uint256_t(3000000000)); - auto tokenContract = "0x4e45e92ed38f885d39a733c14f1817217a89d425"; - auto fromAddress = "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc"; - auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; - auto tokenId = parse_hex("23c47ee5"); - auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); - - Proto::SigningInput input; - input.set_chain_id(chainId.data(), chainId.size()); - input.set_nonce(nonce.data(), nonce.size()); - input.set_tx_mode(Proto::TransactionMode::Enveloped); - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); - input.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); - input.set_to_address(tokenContract); - input.set_private_key(key.data(), key.size()); - auto& erc721 = *input.mutable_transaction()->mutable_erc721_transfer(); - erc721.set_from(fromAddress); - erc721.set_to(toAddress); - erc721.set_token_id(tokenId.data(), tokenId.size()); - - // sign test - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeEthereum); - - ASSERT_EQ(hex(output.encoded()), "02f8d00180847735940084b2d05e00830130b9944e45e92ed38f885d39a733c14f1817217a89d42580b86423b872dd000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee5c080a0dbd591d1eac39bad62d7c158d5e1d55e7014d2218998f8980462e2f283f42d4aa05acadb904484a0fb5526a4c64b8addb8aac4f6548f90199e40eb787b79faed4a"); -} - -TEST(TWAnySignerEthereum, SignERC1155Transfer_1559) { - auto chainId = store(uint256_t(1)); - auto nonce = store(uint256_t(0)); - auto gasLimit = store(uint256_t(78009)); - auto maxInclusionFeePerGas = store(uint256_t(2000000000)); - auto maxFeePerGas = store(uint256_t(3000000000)); - auto tokenContract = "0x4e45e92ed38f885d39a733c14f1817217a89d425"; - auto fromAddress = "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc"; - auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; - auto tokenId = parse_hex("23c47ee5"); - auto value = uint256_t(2000000000000000000); - auto valueData = store(value); - auto data = parse_hex("01020304"); - auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); - - Proto::SigningInput input; - input.set_chain_id(chainId.data(), chainId.size()); - input.set_nonce(nonce.data(), nonce.size()); - input.set_tx_mode(Proto::TransactionMode::Enveloped); - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); - input.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); - input.set_to_address(tokenContract); - input.set_private_key(key.data(), key.size()); - auto& erc1155 = *input.mutable_transaction()->mutable_erc1155_transfer(); - erc1155.set_from(fromAddress); - erc1155.set_to(toAddress); - erc1155.set_token_id(tokenId.data(), tokenId.size()); - erc1155.set_value(valueData.data(), valueData.size()); - erc1155.set_data(data.data(), data.size()); - - // sign test - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeEthereum); - - ASSERT_EQ(hex(output.encoded()), "02f901500180847735940084b2d05e00830130b9944e45e92ed38f885d39a733c14f1817217a89d42580b8e4f242432a000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee50000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000c080a0533df41dda5540c57257b7fe89c29cefff0155c333e063220df2bf9680fcc15aa036a844fd20de5a51de96ceaaf078558e87d86426a4a5d4b215ee1fd0fa397f8a"); -} diff --git a/tests/Ethereum/TWCoinTypeTests.cpp b/tests/Ethereum/TWCoinTypeTests.cpp deleted file mode 100644 index 128fa78357e..00000000000 --- a/tests/Ethereum/TWCoinTypeTests.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWEthereumCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeEthereum)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x9edaf0f7d9c6629c31bbf0471fc07d696c73b566b93783f7e25d8d5d2b62fa4f")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeEthereum, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x5bb497e8d9fe26e92dd1be01e32076c8e024d167")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeEthereum, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeEthereum)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeEthereum)); - const auto chainId = WRAPS(TWCoinTypeChainId(TWCoinTypeEthereum)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeEthereum), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeEthereum)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeEthereum)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeEthereum)); - assertStringsEqual(symbol, "ETH"); - assertStringsEqual(txUrl, "https://etherscan.io/tx/0x9edaf0f7d9c6629c31bbf0471fc07d696c73b566b93783f7e25d8d5d2b62fa4f"); - assertStringsEqual(accUrl, "https://etherscan.io/address/0x5bb497e8d9fe26e92dd1be01e32076c8e024d167"); - assertStringsEqual(id, "ethereum"); - assertStringsEqual(name, "Ethereum"); - assertStringsEqual(chainId, "1"); -} diff --git a/tests/Ethereum/TWEthereumAbiTests.cpp b/tests/Ethereum/TWEthereumAbiTests.cpp deleted file mode 100644 index f64d3886540..00000000000 --- a/tests/Ethereum/TWEthereumAbiTests.cpp +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include -#include - -#include "Ethereum/ABI.h" -#include "Data.h" -#include "HexCoding.h" -#include "uint256.h" - -#include "../interface/TWTestUtilities.h" -#include -#include - -namespace TW::Ethereum { - -TEST(TWEthereumAbi, FuncCreateEmpty) { - TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("baz")).get()); - EXPECT_TRUE(func != nullptr); - - auto type = WRAPS(TWEthereumAbiFunctionGetType(func)); - std::string type2 = TWStringUTF8Bytes(type.get()); - EXPECT_EQ("baz()", type2); - - // delete - TWEthereumAbiFunctionDelete(func); -} - -TEST(TWEthereumAbi, FuncCreate1) { - TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("baz")).get()); - EXPECT_TRUE(func != nullptr); - - auto p1index = TWEthereumAbiFunctionAddParamUInt64(func, 69, false); - EXPECT_EQ(0, p1index); - auto p2index = TWEthereumAbiFunctionAddParamUInt64(func, 9, true); - EXPECT_EQ(0, p2index); - // check back get value - auto p2val2 = TWEthereumAbiFunctionGetParamUInt64(func, p2index, true); - EXPECT_EQ(9, p2val2); - - auto type = WRAPS(TWEthereumAbiFunctionGetType(func)); - std::string type2 = TWStringUTF8Bytes(type.get()); - EXPECT_EQ("baz(uint64)", type2); - - // delete - TWEthereumAbiFunctionDelete(func); -} - -TEST(TWEthereumAbi, FuncCreate2) { - TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("baz")).get()); - EXPECT_TRUE(func != nullptr); - - auto p1valStr = WRAPS(TWStringCreateWithUTF8Bytes("0045")); - auto p1val = WRAPD(TWDataCreateWithHexString(p1valStr.get())); - auto p1index = TWEthereumAbiFunctionAddParamUInt256(func, p1val.get(), false); - EXPECT_EQ(0, p1index); - - Data dummy(0); - auto p2index = TWEthereumAbiFunctionAddParamUInt256(func, &dummy, true); - EXPECT_EQ(0, p2index); - - // check back get value - auto p2val2 = WRAPD(TWEthereumAbiFunctionGetParamUInt256(func, p2index, true)); - Data p2val3 = data(TWDataBytes(p2val2.get()), TWDataSize(p2val2.get())); - EXPECT_EQ("00", hex(p2val3)); - - auto type = WRAPS(TWEthereumAbiFunctionGetType(func)); - EXPECT_EQ("baz(uint256)", std::string(TWStringUTF8Bytes(type.get()))); - - // delete - TWEthereumAbiFunctionDelete(func); -} - -TEST(TWEthereumAbi, EncodeFuncCase1) { - TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("sam")).get()); - EXPECT_TRUE(func != nullptr); - - EXPECT_EQ(0, TWEthereumAbiFunctionAddParamBytes(func, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("64617665")).get())).get(), false)); - EXPECT_EQ(1, TWEthereumAbiFunctionAddParamBool(func, true, false)); - auto paramArrIdx = TWEthereumAbiFunctionAddParamArray(func, false); - EXPECT_EQ(2, paramArrIdx); - EXPECT_EQ(0, TWEthereumAbiFunctionAddInArrayParamUInt256(func, paramArrIdx, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("01")).get())).get())); - EXPECT_EQ(1, TWEthereumAbiFunctionAddInArrayParamUInt256(func, paramArrIdx, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("02")).get())).get())); - EXPECT_EQ(2, TWEthereumAbiFunctionAddInArrayParamUInt256(func, paramArrIdx, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("03")).get())).get())); - - auto type = WRAPS(TWEthereumAbiFunctionGetType(func)); - EXPECT_EQ("sam(bytes,bool,uint256[])", std::string(TWStringUTF8Bytes(type.get()))); - - auto encoded = WRAPD(TWEthereumAbiEncode(func)); - Data enc2 = data(TWDataBytes(encoded.get()), TWDataSize(encoded.get())); - EXPECT_EQ("a5643bf2" - "0000000000000000000000000000000000000000000000000000000000000060" - "0000000000000000000000000000000000000000000000000000000000000001" - "00000000000000000000000000000000000000000000000000000000000000a0" - "0000000000000000000000000000000000000000000000000000000000000004" - "6461766500000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000000000000000000000000000000003" - "0000000000000000000000000000000000000000000000000000000000000001" - "0000000000000000000000000000000000000000000000000000000000000002" - "0000000000000000000000000000000000000000000000000000000000000003", - hex(enc2)); - - // delete - TWEthereumAbiFunctionDelete(func); -} - -TEST(TWEthereumAbi, EncodeFuncCase2) { - TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("f")).get()); - EXPECT_TRUE(func != nullptr); - - EXPECT_EQ(0, TWEthereumAbiFunctionAddParamUInt256(func, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get(), false)); - auto paramArrIdx = TWEthereumAbiFunctionAddParamArray(func, false); - EXPECT_EQ(1, paramArrIdx); - EXPECT_EQ(0, TWEthereumAbiFunctionAddInArrayParamUInt32(func, paramArrIdx, 0x456)); - EXPECT_EQ(1, TWEthereumAbiFunctionAddInArrayParamUInt32(func, paramArrIdx, 0x789)); - EXPECT_EQ(2, TWEthereumAbiFunctionAddParamBytesFix(func, 10, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("31323334353637383930")).get())).get(), false)); - EXPECT_EQ(3, TWEthereumAbiFunctionAddParamString(func, WRAPS(TWStringCreateWithUTF8Bytes("Hello, world!")).get(), false)); - - auto type = WRAPS(TWEthereumAbiFunctionGetType(func)); - EXPECT_EQ("f(uint256,uint32[],bytes10,string)", std::string(TWStringUTF8Bytes(type.get()))); - - auto encoded = WRAPD(TWEthereumAbiEncode(func)); - Data enc2 = data(TWDataBytes(encoded.get()), TWDataSize(encoded.get())); - EXPECT_EQ("47b941bf" - "0000000000000000000000000000000000000000000000000000000000000123" - "0000000000000000000000000000000000000000000000000000000000000080" - "3132333435363738393000000000000000000000000000000000000000000000" - "00000000000000000000000000000000000000000000000000000000000000e0" - "0000000000000000000000000000000000000000000000000000000000000002" - "0000000000000000000000000000000000000000000000000000000000000456" - "0000000000000000000000000000000000000000000000000000000000000789" - "000000000000000000000000000000000000000000000000000000000000000d" - "48656c6c6f2c20776f726c642100000000000000000000000000000000000000", - hex(enc2)); - - // delete - TWEthereumAbiFunctionDelete(func); -} - -TEST(TWEthereumAbi, EncodeFuncMonster) { - // Monster function with all parameters types - TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("monster")).get()); - EXPECT_TRUE(func != nullptr); - - TWEthereumAbiFunctionAddParamUInt8(func, 1, false); - TWEthereumAbiFunctionAddParamUInt16(func, 2, false); - TWEthereumAbiFunctionAddParamUInt32(func, 3, false); - TWEthereumAbiFunctionAddParamUInt64(func, 4, false); - TWEthereumAbiFunctionAddParamUIntN(func, 168, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get(), false); - TWEthereumAbiFunctionAddParamUInt256(func, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get(), false); - TWEthereumAbiFunctionAddParamInt8(func, 1, false); - TWEthereumAbiFunctionAddParamInt16(func, 2, false); - TWEthereumAbiFunctionAddParamInt32(func, 3, false); - TWEthereumAbiFunctionAddParamInt64(func, 4, false); - TWEthereumAbiFunctionAddParamIntN(func, 168, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get(), false); - TWEthereumAbiFunctionAddParamInt256(func, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get(), false); - TWEthereumAbiFunctionAddParamBool(func, true, false); - TWEthereumAbiFunctionAddParamString(func, WRAPS(TWStringCreateWithUTF8Bytes("Hello, world!")).get(), false); - TWEthereumAbiFunctionAddParamAddress(func, - WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("f784682c82526e245f50975190ef0fff4e4fc077")).get())).get(), false); - TWEthereumAbiFunctionAddParamBytes(func, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("3132333435")).get())).get(), false); - TWEthereumAbiFunctionAddParamBytesFix(func, 5, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("3132333435")).get())).get(), false); - - TWEthereumAbiFunctionAddInArrayParamUInt8(func, TWEthereumAbiFunctionAddParamArray(func, false), 1); - TWEthereumAbiFunctionAddInArrayParamUInt16(func, TWEthereumAbiFunctionAddParamArray(func, false), 2); - TWEthereumAbiFunctionAddInArrayParamUInt32(func, TWEthereumAbiFunctionAddParamArray(func, false), 3); - TWEthereumAbiFunctionAddInArrayParamUInt64(func, TWEthereumAbiFunctionAddParamArray(func, false), 4); - TWEthereumAbiFunctionAddInArrayParamUIntN(func, TWEthereumAbiFunctionAddParamArray(func, false), 168, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get()); - TWEthereumAbiFunctionAddInArrayParamUInt256(func, TWEthereumAbiFunctionAddParamArray(func, false), WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get()); - TWEthereumAbiFunctionAddInArrayParamInt8(func, TWEthereumAbiFunctionAddParamArray(func, false), 1); - TWEthereumAbiFunctionAddInArrayParamInt16(func, TWEthereumAbiFunctionAddParamArray(func, false), 2); - TWEthereumAbiFunctionAddInArrayParamInt32(func, TWEthereumAbiFunctionAddParamArray(func, false), 3); - TWEthereumAbiFunctionAddInArrayParamInt64(func, TWEthereumAbiFunctionAddParamArray(func, false), 4); - TWEthereumAbiFunctionAddInArrayParamIntN(func, TWEthereumAbiFunctionAddParamArray(func, false), 168, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get()); - TWEthereumAbiFunctionAddInArrayParamInt256(func, TWEthereumAbiFunctionAddParamArray(func, false), WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get()); - TWEthereumAbiFunctionAddInArrayParamBool(func, TWEthereumAbiFunctionAddParamArray(func, false), true); - TWEthereumAbiFunctionAddInArrayParamString(func, TWEthereumAbiFunctionAddParamArray(func, false), WRAPS(TWStringCreateWithUTF8Bytes("Hello, world!")).get()); - TWEthereumAbiFunctionAddInArrayParamAddress(func, TWEthereumAbiFunctionAddParamArray(func, false), - WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("f784682c82526e245f50975190ef0fff4e4fc077")).get())).get()); - TWEthereumAbiFunctionAddInArrayParamBytes(func, TWEthereumAbiFunctionAddParamArray(func, false), WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("3132333435")).get())).get()); - TWEthereumAbiFunctionAddInArrayParamBytesFix(func, TWEthereumAbiFunctionAddParamArray(func, false), 5, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("3132333435")).get())).get()); - - // check back out params - EXPECT_EQ(1, TWEthereumAbiFunctionGetParamUInt8(func, 0, false)); - EXPECT_EQ(4, TWEthereumAbiFunctionGetParamUInt64(func, 3, false)); - EXPECT_EQ(true, TWEthereumAbiFunctionGetParamBool(func, 12, false)); - EXPECT_EQ(std::string("Hello, world!"), std::string(TWStringUTF8Bytes(WRAPS(TWEthereumAbiFunctionGetParamString(func, 13, false)).get()))); - WRAPD(TWEthereumAbiFunctionGetParamAddress(func, 14, false)); - - auto type = WRAPS(TWEthereumAbiFunctionGetType(func)); - EXPECT_EQ( - "monster(uint8,uint16,uint32,uint64,uint168,uint256,int8,int16,int32,int64,int168,int256,bool,string,address,bytes,bytes5," - "uint8[],uint16[],uint32[],uint64[],uint168[],uint256[],int8[],int16[],int32[],int64[],int168[],int256[],bool[],string[],address[],bytes[],bytes5[])", - std::string(TWStringUTF8Bytes(type.get()))); - - auto encoded = WRAPD(TWEthereumAbiEncode(func)); - Data enc2 = data(TWDataBytes(encoded.get()), TWDataSize(encoded.get())); - EXPECT_EQ(4 + 76 * 32, enc2.size()); - - // delete - TWEthereumAbiFunctionDelete(func); -} - -TEST(TWEthereumAbi, DecodeOutputFuncCase1) { - TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("readout")).get()); - EXPECT_TRUE(func != nullptr); - - TWEthereumAbiFunctionAddParamAddress(func, - WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("f784682c82526e245f50975190ef0fff4e4fc077")).get())).get(), false); - TWEthereumAbiFunctionAddParamUInt64(func, 1000, false); - EXPECT_EQ(0, TWEthereumAbiFunctionAddParamUInt64(func, 0, true)); - - // original output value - EXPECT_EQ(0, TWEthereumAbiFunctionGetParamUInt64(func, 0, true)); - - // decode - auto encoded = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0000000000000000000000000000000000000000000000000000000000000045")).get())); - EXPECT_EQ(true, TWEthereumAbiDecodeOutput(func, encoded.get())); - - // new output value - EXPECT_EQ(0x45, TWEthereumAbiFunctionGetParamUInt64(func, 0, true)); - - // delete - TWEthereumAbiFunctionDelete(func); -} - -TEST(TWEthereumAbi, GetParamWrongType) { - TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("func")).get()); - ASSERT_TRUE(func != nullptr); - // add parameters - EXPECT_EQ(0, TWEthereumAbiFunctionAddParamUInt8(func, 1, true)); - EXPECT_EQ(1, TWEthereumAbiFunctionAddParamUInt64(func, 2, true)); - - // GetParameter with correct types - EXPECT_EQ(1, TWEthereumAbiFunctionGetParamUInt8(func, 0, true)); - EXPECT_EQ(2, TWEthereumAbiFunctionGetParamUInt64(func, 1, true)); - - // GetParameter with wrong type, default value (0) is returned - EXPECT_EQ(0, TWEthereumAbiFunctionGetParamUInt8(func, 1, true)); - EXPECT_EQ(0, TWEthereumAbiFunctionGetParamUInt64(func, 0, true)); - EXPECT_EQ("00", hex(*(static_cast(WRAPD(TWEthereumAbiFunctionGetParamUInt256(func, 0, true)).get())))); - EXPECT_EQ(false, TWEthereumAbiFunctionGetParamBool(func, 0, true)); - EXPECT_EQ("", std::string(TWStringUTF8Bytes(WRAPS(TWEthereumAbiFunctionGetParamString(func, 0, true)).get()))); - EXPECT_EQ("", hex(*(static_cast(WRAPD(TWEthereumAbiFunctionGetParamAddress(func, 0, true)).get())))); - - // GetParameter with wrong index, default value (0) is returned - EXPECT_EQ(0, TWEthereumAbiFunctionGetParamUInt8(func, 99, true)); - EXPECT_EQ(0, TWEthereumAbiFunctionGetParamUInt64(func, 99, true)); - EXPECT_EQ("00", hex(*(static_cast(WRAPD(TWEthereumAbiFunctionGetParamUInt256(func, 99, true)).get())))); - EXPECT_EQ(false, TWEthereumAbiFunctionGetParamBool(func, 99, true)); - EXPECT_EQ("", std::string(TWStringUTF8Bytes(WRAPS(TWEthereumAbiFunctionGetParamString(func, 99, true)).get()))); - EXPECT_EQ("", hex(*(static_cast(WRAPD(TWEthereumAbiFunctionGetParamAddress(func, 99, true)).get())))); - - // delete - TWEthereumAbiFunctionDelete(func); -} - -TEST(TWEthereumAbi, DecodeCall) { - auto callHex = STRING("c47f0027000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000086465616462656566000000000000000000000000000000000000000000000000"); - auto call = WRAPD(TWDataCreateWithHexString(callHex.get())); - auto abi = STRING(R"|({"c47f0027":{"constant":false,"inputs":[{"name":"name","type":"string"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}})|"); - auto decoded = WRAPS(TWEthereumAbiDecodeCall(call.get(), abi.get())); - auto expected = R"|({"function":"setName(string)","inputs":[{"name":"name","type":"string","value":"deadbeef"}]})|"; - - assertStringsEqual(decoded, expected); -} - -TEST(TWEthereumAbi, DecodeInvalidCall) { - auto callHex = STRING("c47f002700"); - auto call = WRAPD(TWDataCreateWithHexString(callHex.get())); - - auto decoded1 = TWEthereumAbiDecodeCall(call.get(), STRING(",,").get()); - auto decoded2 = TWEthereumAbiDecodeCall(call.get(), STRING("{}").get()); - - EXPECT_TRUE(decoded1 == nullptr); - EXPECT_TRUE(decoded2 == nullptr); -} - -TEST(TWEthereumAbi, encodeTyped) { - auto message = WRAPS(TWStringCreateWithUTF8Bytes( - R"({ - "types": { - "EIP712Domain": [ - {"name": "name", "type": "string"}, - {"name": "version", "type": "string"}, - {"name": "chainId", "type": "uint256"}, - {"name": "verifyingContract", "type": "address"} - ], - "Person": [ - {"name": "name", "type": "string"}, - {"name": "wallets", "type": "address[]"} - ], - "Mail": [ - {"name": "from", "type": "Person"}, - {"name": "to", "type": "Person[]"}, - {"name": "contents", "type": "string"} - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallets": [ - "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", - "DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" - ] - }, - "to": [ - { - "name": "Bob", - "wallets": [ - "bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", - "B0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", - "B0B0b0b0b0b0B000000000000000000000000000" - ] - } - ], - "contents": "Hello, Bob!" - } - })")); - auto hash = WRAPD(TWEthereumAbiEncodeTyped(message.get())); - - EXPECT_EQ( - hex(TW::data(TWDataBytes(hash.get()), TWDataSize(hash.get()))), - "a85c2e2b118698e88db68a8105b794a8cc7cec074e89ef991cb4f5f533819cc2" - ); -} - -} // namespace TW::Ethereum diff --git a/tests/Ethereum/ValueDecoderTests.cpp b/tests/Ethereum/ValueDecoderTests.cpp deleted file mode 100644 index 78d52a80f4c..00000000000 --- a/tests/Ethereum/ValueDecoderTests.cpp +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Ethereum/ABI/ValueDecoder.h" -#include - -#include - -using namespace TW; -using namespace TW::Ethereum; - -uint256_t decodeFromHex(std::string s) { - auto data = parse_hex(s); - return ABI::ValueDecoder::decodeUInt256(data); -} - -TEST(EthereumAbiValueDecoder, decodeUInt256) { - EXPECT_EQ(uint256_t(0), decodeFromHex("")); - EXPECT_EQ(uint256_t(0), decodeFromHex("0000000000000000000000000000000000000000000000000000000000000000")); - EXPECT_EQ(uint256_t(1), decodeFromHex("0000000000000000000000000000000000000000000000000000000000000001")); - EXPECT_EQ(uint256_t(123456), decodeFromHex("01e240")); - EXPECT_EQ(uint256_t(10020405371567), decodeFromHex("0000000000000000000000000000000000000000000000000000091d0eb3e2af")); - EXPECT_EQ(uint256_t(10020405371567), decodeFromHex("0000000000000000000000000000000000000000000000000000091d0eb3e2af00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")); -} - -TEST(EthereumAbiValueDecoder, decodeValue) { - EXPECT_EQ("42", ABI::ValueDecoder::decodeValue(parse_hex("000000000000000000000000000000000000000000000000000000000000002a"), "uint")); - EXPECT_EQ("24", ABI::ValueDecoder::decodeValue(parse_hex("0000000000000000000000000000000000000000000000000000000000000018"), "uint8")); - EXPECT_EQ("123456", ABI::ValueDecoder::decodeValue(parse_hex("000000000000000000000000000000000000000000000000000000000001e240"), "uint256")); - EXPECT_EQ("0xf784682c82526e245f50975190ef0fff4e4fc077", ABI::ValueDecoder::decodeValue(parse_hex("000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"), "address")); - EXPECT_EQ("Hello World! Hello World! Hello World!", - ABI::ValueDecoder::decodeValue(parse_hex( - "000000000000000000000000000000000000000000000000000000000000002c" - "48656c6c6f20576f726c64212020202048656c6c6f20576f726c642120202020" - "48656c6c6f20576f726c64210000000000000000000000000000000000000000" - ), "string")); - EXPECT_EQ("0x31323334353637383930", ABI::ValueDecoder::decodeValue(parse_hex("3132333435363738393000000000000000000000000000000000000000000000"), "bytes10")); -} - -TEST(EthereumAbiValueDecoder, decodeArray) { - { - // Array of UInt8 - Data input = parse_hex( - "0000000000000000000000000000000000000000000000000000000000000003" - "0000000000000000000000000000000000000000000000000000000000000031" - "0000000000000000000000000000000000000000000000000000000000000032" - "0000000000000000000000000000000000000000000000000000000000000033" - ); - auto res = ABI::ValueDecoder::decodeArray(input, "uint8[]"); - EXPECT_EQ(3, res.size()); - EXPECT_EQ("49", res[0]); - EXPECT_EQ("50", res[1]); - EXPECT_EQ("51", res[2]); - } - { - // Array of Address - Data input = parse_hex( - "0000000000000000000000000000000000000000000000000000000000000002" - "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077" - "0000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3" - ); - auto res = ABI::ValueDecoder::decodeArray(input, "address[]"); - EXPECT_EQ(2, res.size()); - EXPECT_EQ("0xf784682c82526e245f50975190ef0fff4e4fc077", res[0]); - EXPECT_EQ("0x2e00cd222cb42b616d86d037cc494e8ab7f5c9a3", res[1]); - } - { - // Array of ByteArray - Data input = parse_hex( - "0000000000000000000000000000000000000000000000000000000000000002" - "0000000000000000000000000000000000000000000000000000000000000040" - "0000000000000000000000000000000000000000000000000000000000000080" - "0000000000000000000000000000000000000000000000000000000000000002" - "1011000000000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000000000000000000000000000000003" - "1022220000000000000000000000000000000000000000000000000000000000" - ); - auto res = ABI::ValueDecoder::decodeArray(input, "bytes[]"); - EXPECT_EQ(2, res.size()); - EXPECT_EQ("0x1011", res[0]); - EXPECT_EQ("0x102222", res[1]); - } -} diff --git a/tests/EthereumClassic/TWCoinTypeTests.cpp b/tests/EthereumClassic/TWCoinTypeTests.cpp deleted file mode 100644 index 4e0183374fe..00000000000 --- a/tests/EthereumClassic/TWCoinTypeTests.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWEthereumClassicCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeEthereumClassic)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x66004165d3901819dc22e568931591d2e4287bda54995f4ce2701a12016f5997")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeEthereumClassic, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x9eab4b0fc468a7f5d46228bf5a76cb52370d068d")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeEthereumClassic, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeEthereumClassic)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeEthereumClassic)); - const auto chainId = WRAPS(TWCoinTypeChainId(TWCoinTypeEthereumClassic)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeEthereumClassic), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeEthereumClassic)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeEthereumClassic)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeEthereumClassic)); - assertStringsEqual(symbol, "ETC"); - assertStringsEqual(txUrl, "https://blockscout.com/etc/mainnet/tx/0x66004165d3901819dc22e568931591d2e4287bda54995f4ce2701a12016f5997"); - assertStringsEqual(accUrl, "https://blockscout.com/etc/mainnet/address/0x9eab4b0fc468a7f5d46228bf5a76cb52370d068d"); - assertStringsEqual(id, "classic"); - assertStringsEqual(name, "Ethereum Classic"); - assertStringsEqual(chainId, "61"); -} diff --git a/tests/Evmos/TWAnyAddressTests.cpp b/tests/Evmos/TWAnyAddressTests.cpp deleted file mode 100644 index 95fdc299a28..00000000000 --- a/tests/Evmos/TWAnyAddressTests.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" - -#include -#include - -#include - -TEST(EvmosAnyAddress, EvmosValidate) { - auto string = STRING("0x30627903124Aa1e71384bc52e1cb96E4AB3252b6"); - - EXPECT_TRUE(TWAnyAddressIsValid(string.get(), TWCoinTypeEvmos)); - - auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeEvmos)); - - auto keyHash = WRAPD(TWAnyAddressData(addr.get())); - assertHexEqual(keyHash, "30627903124aa1e71384bc52e1cb96e4ab3252b6"); -} - -TEST(EvmosAnyAddress, EvmosCreate) { - auto publicKeyHex = "045a0c6b83b8bd9827e507270cadb499b7e3a9095246f6a2213281f783d877c98b256742741b0639f317768fe4f4c2762660c2112283a7685d815507dee3229173"; // shoot island position ... - const auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyHex).get(), TWPublicKeyTypeSECP256k1Extended)); - - auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeEvmos)); - - EXPECT_EQ(std::string(TWStringUTF8Bytes(WRAPS(TWAnyAddressDescription(addr.get())).get())), std::string("0x8f348F300873Fd5DA36950B2aC75a26584584feE")); - - auto keyHash = WRAPD(TWAnyAddressData(addr.get())); - assertHexEqual(keyHash, "8f348f300873fd5da36950b2ac75a26584584fee"); -} - -TEST(EvmosAnyAddress, NativeEvmosValidate) { - auto string = STRING("evmos1mwspdc5y6lj0glm90j9sg7vmr5ksmqcnzhg0h9"); - - EXPECT_TRUE(TWAnyAddressIsValid(string.get(), TWCoinTypeNativeEvmos)); - - auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeNativeEvmos)); - - auto keyHash = WRAPD(TWAnyAddressData(addr.get())); - assertHexEqual(keyHash, "dba016e284d7e4f47f657c8b04799b1d2d0d8313"); -} - -TEST(EvmosAnyAddress, NativeEvmosCreate) { - auto publicKeyHex = "035a0c6b83b8bd9827e507270cadb499b7e3a9095246f6a2213281f783d877c98b"; // shoot island position ... - const auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyHex).get(), TWPublicKeyTypeSECP256k1)); - - auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeNativeEvmos)); - - EXPECT_EQ(std::string(TWStringUTF8Bytes(WRAPS(TWAnyAddressDescription(addr.get())).get())), std::string("evmos1mwspdc5y6lj0glm90j9sg7vmr5ksmqcnzhg0h9")); - - auto keyHash = WRAPD(TWAnyAddressData(addr.get())); - assertHexEqual(keyHash, "dba016e284d7e4f47f657c8b04799b1d2d0d8313"); -} diff --git a/tests/Evmos/TWCoinTypeTests.cpp b/tests/Evmos/TWCoinTypeTests.cpp deleted file mode 100644 index d7e5ce7c27a..00000000000 --- a/tests/Evmos/TWCoinTypeTests.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" -#include -#include - -TEST(TWEvmosCoinType, TWCoinTypeEvmos) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeEvmos)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x24af42cf4977a96d62e3a82c3cd9b519c3e7c53dd83398b88f0cb435d867b422")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeEvmos, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x30627903124Aa1e71384bc52e1cb96E4AB3252b6")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeEvmos, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeEvmos)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeEvmos)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeEvmos), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeEvmos)); - - assertStringsEqual(symbol, "EVMOS"); - assertStringsEqual(txUrl, "https://evm.evmos.org/tx/0x24af42cf4977a96d62e3a82c3cd9b519c3e7c53dd83398b88f0cb435d867b422"); - assertStringsEqual(accUrl, "https://evm.evmos.org/address/0x30627903124Aa1e71384bc52e1cb96E4AB3252b6"); - assertStringsEqual(id, "evmos"); - assertStringsEqual(name, "Evmos"); -} - -TEST(TWEvmosCoinType, TWCoinTypeNativeEvmos) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNativeEvmos)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("A16C211C83AD1E684DE46F694FAAC17D8465C864BD7385A81EC062CDE0638811")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNativeEvmos, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNativeEvmos, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNativeEvmos)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNativeEvmos)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNativeEvmos), 18); - ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeNativeEvmos)); - - assertStringsEqual(symbol, "EVMOS"); - assertStringsEqual(txUrl, "https://mintscan.io/evmos/txs/A16C211C83AD1E684DE46F694FAAC17D8465C864BD7385A81EC062CDE0638811"); - assertStringsEqual(accUrl, "https://mintscan.io/evmos/account/evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34"); - assertStringsEqual(id, "nativeevmos"); - assertStringsEqual(name, "Native Evmos"); -} diff --git a/tests/FIO/AddressTests.cpp b/tests/FIO/AddressTests.cpp deleted file mode 100644 index f78e3ae25f2..00000000000 --- a/tests/FIO/AddressTests.cpp +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "FIO/Address.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include - -#include - -using namespace TW; -using namespace TW::FIO; - -TEST(FIOAddress, ValidateString) { - ASSERT_FALSE(Address::isValid("abc")); - ASSERT_FALSE(Address::isValid("65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjTF")); - ASSERT_FALSE(Address::isValid("EOS65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjT")); - - ASSERT_TRUE(Address::isValid("FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o")); -} - -TEST(FIOAddress, ValidateData) { - Address address("FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); - EXPECT_EQ(address.bytes.size(), 37); - Data addrData = TW::data(address.bytes.data(), address.bytes.size()); - - EXPECT_EQ(Address::isValid(addrData), true); - - // create invalid data, too short - Data addressDataShort = subData(addrData, 0, addrData.size() - 1); - EXPECT_EQ(Address::isValid(addressDataShort), false); -} - -TEST(FIOAddress, FromString) { - Address addr("FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); - ASSERT_EQ(addr.string(), "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); - ASSERT_EQ(hex(addr.bytes), "0271195c66ec2799e436757a70cd8431d4b17733a097b18a5f7f1b6b085978ff0f343fc54e"); -} - -TEST(FIOAddress, FromStringInvalid) { - try { - Address address("WRP5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); - } catch (std::invalid_argument&) { - return; // ok - } - ADD_FAILURE() << "Missed expected exeption"; -} - -TEST(FIOAddress, FromPublicKey) { - auto key = PrivateKey(parse_hex("ea8eb60b7e5868e218f248e032769020b4fea5dcfd02f2992861eaf4fb534854")); - auto publicKey = key.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_EQ(hex(publicKey.bytes), "0271195c66ec2799e436757a70cd8431d4b17733a097b18a5f7f1b6b085978ff0f"); - auto address = Address(publicKey); - - ASSERT_EQ(address.string(), "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); -} - -TEST(FIOAddress, GetPublicKey) { - const auto publicKeyHex = "0271195c66ec2799e436757a70cd8431d4b17733a097b18a5f7f1b6b085978ff0f"; - const PublicKey publicKey(parse_hex(publicKeyHex), TWPublicKeyTypeSECP256k1); - auto address = Address(publicKey); - EXPECT_EQ(hex(address.publicKey().bytes), publicKeyHex); -} diff --git a/tests/FIO/SignerTests.cpp b/tests/FIO/SignerTests.cpp deleted file mode 100644 index cdf1277a11b..00000000000 --- a/tests/FIO/SignerTests.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "FIO/Actor.h" -#include "FIO/Signer.h" - -#include "HexCoding.h" -#include "Hash.h" -#include "Base58.h" - -#include - -using namespace TW; -using namespace TW::FIO; -using namespace std; - - -TEST(FIOSigner, SignEncode) { - string sig1 = Signer::signatureToBsase58(parse_hex("1f4fccc30bcba876963aef6de584daf7258306c02f4528fe25b116b517de8b349968bdc080cd6bef36f5a46d31a7c01ed0806ad215bb66a94f61e27a895d610983"));; - EXPECT_EQ("SIG_K1_K5hJTPeiV4bDkNR13mf66N2DY5AtVL4NU1iWE4G4dsczY2q68oCcUVxhzFdxjgV2eAeb2jNV1biqtCJ3SaNL8kkNgoZ43H", sig1); -} - -TEST(FIOSigner, SignInternals) { - // 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf - PrivateKey pk = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); - { - Data pk2 = parse_hex("80"); - append(pk2, pk.bytes); - EXPECT_EQ("5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri", Base58::bitcoin.encodeCheck(pk2)); - } - Data rawData = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd774a26285e19fac10ac5390000000001003056372503a85b0000c6eaa6645232017016f2cc12266c6b00000000a8ed3232bd010f6164616d4066696f746573746e657403034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c7877703730643776034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b397300000000000000007016f2cc12266c6b0e726577617264734077616c6c6574000000000000000000000000000000000000000000000000000000000000000000"); - Data hash = Hash::sha256(rawData); - EXPECT_EQ("0f3cca0f50da4200b2858f65de1ea4530a9afd9e4bfc0b6b7196e36c25cc7a8b", hex(hash)); - Data sign2 = Signer::signData(pk, rawData); - EXPECT_EQ("1f4ae8d1b993f94d0de4b249d5185481770de0711863ad640b3aac21de598fcc02761c6e5395106bafb7b09aab1c7aa5ac0573dbd821c2d255725391a5105d30d1", hex(sign2)); - - string sigStr = Signer::signatureToBsase58(sign2); - EXPECT_EQ("SIG_K1_K54CA1jmhgWrSdvrNrkokPyvqh7dwsSoQHNU9xgD3Ezf6cJySzhKeUubVRqmpYdnjoP1DM6SorroVAgrCu3qqvJ9coAQ6u", sigStr); - EXPECT_TRUE(Signer::verify(pk.getPublicKey(TWPublicKeyTypeSECP256k1), hash, sign2)); -} - -TEST(FIOSigner, Actor) { - { - const auto addr1 = "FIO6cDpi7vPnvRwMEdXtLnAmFwygaQ8CzD7vqKLBJ2GfgtHBQ4PPy"; - Address addr = Address(addr1); - EXPECT_EQ(addr1, addr.string()); - - uint64_t shortenedKey = Actor::shortenKey(addr.bytes); - EXPECT_EQ(1518832697283783336U, shortenedKey); - string name = Actor::name(shortenedKey); - EXPECT_EQ("2odzomo2v4pec", name); - } - const int n = 4; - const string addrArr[n] = { - "FIO6cDpi7vPnvRwMEdXtLnAmFwygaQ8CzD7vqKLBJ2GfgtHBQ4PPy", - "FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE", - "FIO7bxrQUTbQ4mqcoefhWPz1aFieN4fA9RQAiozRz7FrUChHZ7Rb8", - "FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf", - }; - const string actorArr[n] = { - "2odzomo2v4pe", - "hhq2g4qgycfb", - "5kmx4qbqlpld", - "qdfejz2a5wpl", - }; - for (int i = 0; i < n; ++i) { - Address addr = Address(addrArr[i]); - EXPECT_EQ(addrArr[i], addr.string()); - - string actor = Actor::actor(addr); - EXPECT_EQ(actorArr[i], actor); - } -} diff --git a/tests/FIO/TWCoinTypeTests.cpp b/tests/FIO/TWCoinTypeTests.cpp deleted file mode 100644 index fcbb7a47bb6..00000000000 --- a/tests/FIO/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWFIOCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeFIO)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("930d1d3cf8988b39b5f64b64e9d61314a3e05a155d9e3505bdf863aab1adddf3")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeFIO, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("f5axfpgffiqz")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeFIO, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeFIO)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeFIO)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeFIO), 9); - ASSERT_EQ(TWBlockchainFIO, TWCoinTypeBlockchain(TWCoinTypeFIO)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeFIO)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeFIO)); - assertStringsEqual(symbol, "FIO"); - assertStringsEqual(txUrl, "https://explorer.fioprotocol.io/transaction/930d1d3cf8988b39b5f64b64e9d61314a3e05a155d9e3505bdf863aab1adddf3"); - assertStringsEqual(accUrl, "https://explorer.fioprotocol.io/account/f5axfpgffiqz"); - assertStringsEqual(id, "fio"); - assertStringsEqual(name, "FIO"); -} diff --git a/tests/FIO/TWFIOTests.cpp b/tests/FIO/TWFIOTests.cpp deleted file mode 100644 index 81ea60022a7..00000000000 --- a/tests/FIO/TWFIOTests.cpp +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include -#include -#include "proto/FIO.pb.h" -#include "FIO/Address.h" -#include "Data.h" -#include "../interface/TWTestUtilities.h" -#include "HexCoding.h" -#include "PrivateKey.h" - -#include - -using namespace TW; -using namespace TW::FIO; -using namespace std; - - -TEST(TWFIO, Address) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035").get())); - ASSERT_NE(nullptr, privateKey.get()); - auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false)); - ASSERT_NE(nullptr, publicKey.get()); - ASSERT_EQ(65, publicKey.get()->impl.bytes.size()); - auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeFIO)); - auto addressString = WRAPS(TWAnyAddressDescription(address.get())); - assertStringsEqual(addressString, "FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf"); - - auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING("FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf").get(), TWCoinTypeFIO)); - ASSERT_NE(nullptr, address2.get()); - auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); - assertStringsEqual(address2String, "FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf"); - - ASSERT_TRUE(TWAnyAddressEqual(address.get(), address2.get())); -} - -const Data chainId = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77"); -// 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf -const PrivateKey privKeyBA = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); -const PublicKey pubKey6M = privKeyBA.getPublicKey(TWPublicKeyTypeSECP256k1); -const Address addr6M(pubKey6M); - -TEST(TWFIO, RegisterFioAddress) { - Proto::SigningInput input; - input.set_expiry(1579784511); - input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); - input.mutable_chain_params()->set_head_block_number(39881); - input.mutable_chain_params()->set_ref_block_prefix(4279583376); - input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); - input.set_tpid("rewards@wallet"); - input.mutable_action()->mutable_register_fio_address_message()->set_fio_address("adam@fiotestnet"); - input.mutable_action()->mutable_register_fio_address_message()->set_owner_fio_public_key(addr6M.string()); - input.mutable_action()->mutable_register_fio_address_message()->set_fee(5000000000); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeFIO); - EXPECT_EQ(Common::Proto::OK, output.error()); - EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"3f99295ec99b904215ff0000000001003056372503a85b0000c6eaa66498ba01102b2f46fca756b200000000a8ed3232650f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K19ugLriG3ApYgjJCRDsy21p9xgsjbDtqBuZrmAEix9XYzndR1kNbJ6fXCngMJMAhxUHfwHAsPnh58otXiJZkazaM1EkS5"]})", output.json()); -} - -TEST(TWFIO, AddPubAddress) { - Proto::SigningInput input; - input.set_expiry(1579729429); - input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); - input.mutable_chain_params()->set_head_block_number(11565); - input.mutable_chain_params()->set_ref_block_prefix(4281229859); - input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); - input.set_tpid("rewards@wallet"); - auto action = input.mutable_action()->mutable_add_pub_address_message(); - action->set_fio_address("adam@fiotestnet"); - action->add_public_addresses(); - action->add_public_addresses(); - action->add_public_addresses(); - action->mutable_public_addresses(0)->set_coin_symbol("BTC"); - action->mutable_public_addresses(0)->set_address("bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"); - action->mutable_public_addresses(1)->set_coin_symbol("ETH"); - action->mutable_public_addresses(1)->set_address("0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a8F51"); - action->mutable_public_addresses(2)->set_coin_symbol("BNB"); - action->mutable_public_addresses(2)->set_address("bnb1ts3dg54apwlvr9hupv2n0j6e46q54znnusjk9s"); - action->set_fee(0); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeFIO); - EXPECT_EQ(Common::Proto::OK, output.error()); - EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"15c2285e2d2d23622eff0000000001003056372503a85b0000c6eaa664523201102b2f46fca756b200000000a8ed3232c9010f6164616d4066696f746573746e65740303425443034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c787770373064377603455448034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e4203424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b39730000000000000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K3zimaMKU8cBhVRPw46KM2u7uQWaAKXrnoeYZ7MEbp6sVJcDQmQR2RtdavpUPwkAnYUkd8NqLun8H48tcxZBcTUgkiPGVJ"]})", output.json()); -} - -TEST(TWFIO, Transfer) { - Proto::SigningInput input; - input.set_expiry(1579790000); - input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); - input.mutable_chain_params()->set_head_block_number(50000); - input.mutable_chain_params()->set_ref_block_prefix(4000123456); - input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); - input.set_tpid("rewards@wallet"); - input.mutable_action()->mutable_transfer_message()->set_payee_public_key("FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE"); - input.mutable_action()->mutable_transfer_message()->set_amount(1000000000); - input.mutable_action()->mutable_transfer_message()->set_fee(250000000); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeFIO); - EXPECT_EQ(Common::Proto::OK, output.error()); - EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K9VRCnvaTYN7vgcoVKVXgyJTdKUGV8hLXgFLoEbvqAcFxy7DXQ1rSnAfEuabi4ATkgmvnpaSBdVFN7TBtM1wrbZYqeJQw9"]})", output.json()); -} - -TEST(TWFIO, RenewFioAddress) { - Proto::SigningInput input; - input.set_expiry(1579785000); - input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); - input.mutable_chain_params()->set_head_block_number(39881); - input.mutable_chain_params()->set_ref_block_prefix(4279583376); - input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); - input.set_tpid("rewards@wallet"); - input.mutable_action()->mutable_renew_fio_address_message()->set_fio_address("nick@fiotestnet"); - input.mutable_action()->mutable_renew_fio_address_message()->set_owner_fio_public_key(addr6M.string()); - input.mutable_action()->mutable_renew_fio_address_message()->set_fee(3000000000); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeFIO); - EXPECT_EQ(Common::Proto::OK, output.error()); - EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"289b295ec99b904215ff0000000001003056372503a85b80b1ba2919aea6ba01102b2f46fca756b200000000a8ed32322f0f6e69636b4066696f746573746e6574005ed0b200000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_Jxz7oCJ7Z4ECsxqb2utqBcyP3zPQCeQCBws9wWQjyptUKoWVk2AyCVEqtdMHJwqtLniio5Z7npMnaZB8E4pa2G75P9uGkb"]})", output.json()); -} - -TEST(TWFIO, NewFundsRequest) { - Proto::SigningInput input; - input.set_expiry(1579785000); - input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); - input.mutable_chain_params()->set_head_block_number(39881); - input.mutable_chain_params()->set_ref_block_prefix(4279583376); - input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); - input.set_tpid("rewards@wallet"); - input.mutable_action()->mutable_new_funds_request_message()->set_payer_fio_name("mario@fiotestnet"); - input.mutable_action()->mutable_new_funds_request_message()->set_payer_fio_address("FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); - input.mutable_action()->mutable_new_funds_request_message()->set_payee_fio_name("alice@fiotestnet"); - input.mutable_action()->mutable_new_funds_request_message()->mutable_content()->set_payee_public_address("bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"); - input.mutable_action()->mutable_new_funds_request_message()->mutable_content()->set_amount("5"); - input.mutable_action()->mutable_new_funds_request_message()->mutable_content()->set_coin_symbol("BTC"); - input.mutable_action()->mutable_new_funds_request_message()->mutable_content()->set_memo("Memo"); - input.mutable_action()->mutable_new_funds_request_message()->mutable_content()->set_hash("Hash"); - input.mutable_action()->mutable_new_funds_request_message()->mutable_content()->set_offline_url("https://trustwallet.com"); - input.mutable_action()->mutable_new_funds_request_message()->set_fee(3000000000); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeFIO); - // Packed transacton varies, as there is no way to control encryption IV parameter from this level. - // Therefore full equality cannot be checked, tail is cut off. The first N chars are checked, works in this case. - EXPECT_EQ( - R"({"compression":"none","packed_context_free_data":"","packed_trx":"289b295ec99b904215ff000000000100403ed4aa0ba85b00acba384dbdb89a01102b2f46fca756b200000000a8ed32328802106d6172696f4066696f746573746)", - output.json().substr(0, 195) - ); -} diff --git a/tests/FIO/TransactionBuilderTests.cpp b/tests/FIO/TransactionBuilderTests.cpp deleted file mode 100644 index 04b36498cf2..00000000000 --- a/tests/FIO/TransactionBuilderTests.cpp +++ /dev/null @@ -1,358 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "FIO/Action.h" -#include "FIO/Transaction.h" -#include "FIO/TransactionBuilder.h" -#include "FIO/NewFundsRequest.h" - -#include "HexCoding.h" -#include "BinaryCoding.h" - -#include - -#include -#include - -using namespace TW; -using namespace TW::FIO; -using namespace std; - - -const Data chainId = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77"); -// 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf -const PrivateKey privKeyBA = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); -const PublicKey pubKey6M = privKeyBA.getPublicKey(TWPublicKeyTypeSECP256k1); -const Address addr6M(pubKey6M); - -TEST(FIOTransactionBuilder, RegisterFioAddressGeneric) { - Proto::SigningInput input; - input.set_expiry(1579784511); - input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); - input.mutable_chain_params()->set_head_block_number(39881); - input.mutable_chain_params()->set_ref_block_prefix(4279583376); - input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); - input.set_tpid("rewards@wallet"); - input.mutable_action()->mutable_register_fio_address_message()->set_fio_address("adam@fiotestnet"); - input.mutable_action()->mutable_register_fio_address_message()->set_owner_fio_public_key(addr6M.string()); - input.mutable_action()->mutable_register_fio_address_message()->set_fee(5000000000); - - auto json = TransactionBuilder::sign(input); - - EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"3f99295ec99b904215ff0000000001003056372503a85b0000c6eaa66498ba01102b2f46fca756b200000000a8ed3232650f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K19ugLriG3ApYgjJCRDsy21p9xgsjbDtqBuZrmAEix9XYzndR1kNbJ6fXCngMJMAhxUHfwHAsPnh58otXiJZkazaM1EkS5"]})", json); -} - -TEST(FIOTransactionBuilder, RegisterFioAddress) { - ChainParams chainParams{chainId, 39881, 4279583376}; - uint64_t fee = 5000000000; - - string t = TransactionBuilder::createRegisterFioAddress(addr6M, privKeyBA, "adam@fiotestnet", - chainParams, fee, "rewards@wallet", 1579784511); - - EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"3f99295ec99b904215ff0000000001003056372503a85b0000c6eaa66498ba01102b2f46fca756b200000000a8ed3232650f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K19ugLriG3ApYgjJCRDsy21p9xgsjbDtqBuZrmAEix9XYzndR1kNbJ6fXCngMJMAhxUHfwHAsPnh58otXiJZkazaM1EkS5"]})", t); -} - -TEST(FIOTransactionBuilder, AddPubAddress) { - ChainParams chainParams{chainId, 11565, 4281229859}; - - string t = TransactionBuilder::createAddPubAddress(addr6M, privKeyBA, "adam@fiotestnet", { - {"BTC", "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"}, - {"ETH", "0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a8F51"}, - {"BNB", "bnb1ts3dg54apwlvr9hupv2n0j6e46q54znnusjk9s"}}, - chainParams, 0, "rewards@wallet", 1579729429); - - EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"15c2285e2d2d23622eff0000000001003056372503a85b0000c6eaa664523201102b2f46fca756b200000000a8ed3232c9010f6164616d4066696f746573746e65740303425443034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c787770373064377603455448034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e4203424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b39730000000000000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K3zimaMKU8cBhVRPw46KM2u7uQWaAKXrnoeYZ7MEbp6sVJcDQmQR2RtdavpUPwkAnYUkd8NqLun8H48tcxZBcTUgkiPGVJ"]})", t); -} - -TEST(FIOTransactionBuilder, Transfer) { - ChainParams chainParams{chainId, 50000, 4000123456}; - string payee = "FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE"; - uint64_t amount = 1000000000; - uint64_t fee = 250000000; - - string t = TransactionBuilder::createTransfer(addr6M, privKeyBA, payee, amount, - chainParams, fee, "rewards@wallet", 1579790000); - - EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K9VRCnvaTYN7vgcoVKVXgyJTdKUGV8hLXgFLoEbvqAcFxy7DXQ1rSnAfEuabi4ATkgmvnpaSBdVFN7TBtM1wrbZYqeJQw9"]})", t); -} - -TEST(FIOTransactionBuilder, RenewFioAddress) { - ChainParams chainParams{chainId, 39881, 4279583376}; - uint64_t fee = 3000000000; - - string t = TransactionBuilder::createRenewFioAddress(addr6M, privKeyBA, "nick@fiotestnet", - chainParams, fee, "rewards@wallet", 1579785000); - - EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"289b295ec99b904215ff0000000001003056372503a85b80b1ba2919aea6ba01102b2f46fca756b200000000a8ed32322f0f6e69636b4066696f746573746e6574005ed0b200000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_Jxz7oCJ7Z4ECsxqb2utqBcyP3zPQCeQCBws9wWQjyptUKoWVk2AyCVEqtdMHJwqtLniio5Z7npMnaZB8E4pa2G75P9uGkb"]})", t); -} - -TEST(FIOTransactionBuilder, NewFundsRequest) { - { - ChainParams chainParams{chainId, 18484, 3712870657}; - const Data iv = parse_hex("000102030405060708090a0b0c0d0e0f"); // use fixed iv for testability - string t = TransactionBuilder::createNewFundsRequest( - Address("FIO5NMm9Vf3NjYFnhoc7yxTCrLW963KPUCzeMGv3SJ6zR3GMez4ub"), privKeyBA, - "tag@fiotestnet", "FIO7iYHtDhs45smFgSqLyJ6Zi4D3YG8K5bZGyxmshLCDXXBPbbmJN", "dapixbp@fiotestnet", "14R4wEsGT58chmqo7nB65Dy4je6TiijDWc", - "1", "BTC", "payment", "", "", - chainParams, 800000000, "", 1583528215, iv); - EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"17b9625e344801e94ddd000000000100403ed4aa0ba85b00acba384dbdb89a01702096fedf5bf9f900000000a8ed3232d2010e7461674066696f746573746e657412646170697862704066696f746573746e6574980141414543417751464267634943516f4c4441304f447a684533513779504a4738592f52486a69545576436163734a444a516243612f41643763354e36354f56366d3441566974596379654e7a4749306d4c366b5a71567348737837537845623471724d346435567258364939746a6842447067566b3078596575325861676759516b323168684972306c76412b7535546977545661673d3d0008af2f000000000c7a62777072727a796d736b620000","signatures":["SIG_K1_K95jnXSBCf1BnQXQPZzxKYPGxugwpbeVp2NSjN1kmYd9SQibvnSfh2ggmSVXii4Jvq3dtRHFA8s7n3kcQdLhY4KMrkgDgp"]})", t); - } - - ChainParams chainParams{chainId, 39881, 4279583376}; - uint64_t fee = 3000000000; - - const Data iv = parse_hex("000102030405060708090a0b0c0d0e0f"); // use fixed iv for testability - string t = TransactionBuilder::createNewFundsRequest(addr6M, privKeyBA, - "mario@fiotestnet", "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o", "alice@fiotestnet", "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v", - "5", "BTC", "Memo", "Hash", "https://trustwallet.com", - chainParams, fee, "rewards@wallet", 1579785000, iv); - - EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"289b295ec99b904215ff000000000100403ed4aa0ba85b00acba384dbdb89a01102b2f46fca756b200000000a8ed32328802106d6172696f4066696f746573746e657410616c6963654066696f746573746e6574c00141414543417751464267634943516f4c4441304f442f3575342f6b436b7042554c4a44682f546951334d31534f4e4938426668496c4e54766d39354249586d54396f616f7a55632f6c6c3942726e57426563464e767a76766f6d3751577a517250717241645035683433305732716b52355266416555446a704f514732364c347a6936767241553052764855474e382b685779736a6971506b2b7a455a444952534678426268796c69686d59334f4752342f5a46466358484967343241327834005ed0b2000000000c716466656a7a32613577706c0e726577617264734077616c6c657400","signatures":["SIG_K1_Kk79iVcQMpqpVgZwGTmC1rxgCTLy5BDFtHd8FvjRNm2FqNHR9dpeUmPTNqBKGMNG3BsPy4c5u26TuEDpS87SnyMpF43cZk"]})", t); -} - -TEST(FIOTransaction, ActionRegisterFioAddressInternal) { - RegisterFioAddressData radata("adam@fiotestnet", addr6M.string(), - 5000000000, "rewards@wallet", "qdfejz2a5wpl"); - Data ser1; - radata.serialize(ser1); - EXPECT_EQ( - hex(parse_hex("0F6164616D4066696F746573746E65743546494F366D31664D645470526B52426E6564765973685843784C4669433573755255384B44667838787874587032686E7478706E6600F2052A01000000102B2F46FCA756B20E726577617264734077616C6C6574")), - hex(ser1)); - - Action raAction; - raAction.account = "fio.address"; - raAction.name = "regaddress"; - raAction.actionDataSer = ser1; - raAction.auth.authArray.push_back(Authorization{"qdfejz2a5wpl", "active"}); - Data ser2; - raAction.serialize(ser2); - EXPECT_EQ( - "003056372503a85b0000c6eaa66498ba0" - "1102b2f46fca756b200000000a8ed3232" - "65" - "0f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400", - hex(ser2)); - - Transaction tx; - tx.expiration = 1579784511; - tx.refBlockNumber = 39881; - tx.refBlockPrefix = 4279583376; - tx.actions.push_back(raAction); - Data ser3; - tx.serialize(ser3); - EXPECT_EQ( - "3f99295ec99b904215ff0000000001" - "003056372503a85b0000c6eaa66498ba0" - "1102b2f46fca756b200000000a8ed3232" - "65" - "0f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400", - hex(ser3)); -} - -TEST(FIOTransaction, ActionAddPubAddressInternal) { - AddPubAddressData aadata("adam@fiotestnet", { - {"BTC", "BTC", "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"}, - {"ETH", "ETH", "0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a8F51"}, - {"BNB", "BNB", "bnb1ts3dg54apwlvr9hupv2n0j6e46q54znnusjk9s"}}, - 0, "rewards@wallet", "qdfejz2a5wpl"); - Data ser1; - aadata.serialize(ser1); - EXPECT_EQ( - "0f6164616d4066696f746573746e65740303425443034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c787770373064377603455448034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e4203424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b39730000000000000000102b2f46fca756b20e726577617264734077616c6c6574", - hex(ser1)); - - Action aaAction; - aaAction.account = "fio.address"; - aaAction.name = "addaddress"; - aaAction.actionDataSer = ser1; - aaAction.auth.authArray.push_back(Authorization{"qdfejz2a5wpl", "active"}); - Data ser2; - aaAction.serialize(ser2); - EXPECT_EQ( - "003056372503a85b0000c6eaa66452320" - "1102b2f46fca756b200000000a8ed3232" - "c901" - "0f6164616d4066696f746573746e65740303425443034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c787770373064377603455448034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e4203424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b39730000000000000000102b2f46fca756b20e726577617264734077616c6c657400", - hex(ser2)); - - Transaction tx; - tx.expiration = 1579729429; - tx.refBlockNumber = 11565; - tx.refBlockPrefix = 4281229859; - tx.actions.push_back(aaAction); - Data ser3; - tx.serialize(ser3); - EXPECT_EQ( - "15c2285e2d2d23622eff0000000001" - "003056372503a85b0000c6eaa66452320" - "1102b2f46fca756b200000000a8ed3232" - "c901" - "0f6164616d4066696f746573746e65740303425443034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c787770373064377603455448034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e4203424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b39730000000000000000102b2f46fca756b20e726577617264734077616c6c657400", - hex(ser3)); -} - -TEST(FIONewFundsContent, serialize) { - { - NewFundsContent newFunds {"bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v", "10", "BTC", "BTC", "Memo", "Hash", "https://trustwallet.com"}; - Data ser; - newFunds.serialize(ser); - EXPECT_EQ(hex(ser), "2a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c78777037306437760231300342544303425443044d656d6f04486173681768747470733a2f2f747275737477616c6c65742e636f6d0000000000"); - } - { - // empty struct - NewFundsContent newFunds {"", "", "", "", "", "", ""}; - Data ser; - newFunds.serialize(ser); - EXPECT_EQ(hex(ser), "000000000000000000000000"); - } - { - // test from https://github.com/fioprotocol/fiojs/blob/master/src/tests/encryption-fio.test.ts - NewFundsContent newFunds {"purse.alice", "1", "", "fio.reqobt", "", "", ""}; - Data ser; - newFunds.serialize(ser); - EXPECT_EQ(hex(ser), "0b70757273652e616c6963650131000a66696f2e7265716f62740000000000000000"); - } -} - -TEST(FIONewFundsContent, deserialize) { - { - const Data ser = parse_hex("2a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c78777037306437760231300342544303425443044d656d6f04486173681768747470733a2f2f747275737477616c6c65742e636f6d0000000000"); - size_t index = 0; - const auto newFunds = NewFundsContent::deserialize(ser, index); - EXPECT_EQ(newFunds.payeePublicAddress, "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"); - EXPECT_EQ(newFunds.amount, "10"); - EXPECT_EQ(newFunds.coinSymbol, "BTC"); - EXPECT_EQ(newFunds.offlineUrl, "https://trustwallet.com"); - } - { - // incomplete input - const Data ser = parse_hex("0b6d"); - size_t index = 0; - const auto newFunds = NewFundsContent::deserialize(ser, index); - EXPECT_EQ(newFunds.payeePublicAddress, ""); - EXPECT_EQ(newFunds.amount, ""); - EXPECT_EQ(newFunds.offlineUrl, ""); - } -} - -TEST(FIOTransactionBuilder, expirySetDefault) { - uint32_t expiry = 1579790000; - EXPECT_EQ(TransactionBuilder::expirySetDefaultIfNeeded(expiry), false); - EXPECT_EQ(expiry, 1579790000); - expiry = 0; - EXPECT_EQ(expiry, 0); - EXPECT_EQ(TransactionBuilder::expirySetDefaultIfNeeded(expiry), true); - EXPECT_TRUE(expiry > 1579790000); -} - -// May throw nlohmann::json::type_error -void createTxWithChainParam(const ChainParams& paramIn, ChainParams& paramOut) { - string tx = TransactionBuilder::createAddPubAddress(addr6M, privKeyBA, "adam@fiotestnet", { - {"BTC", "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"}}, - paramIn, 0, "rewards@wallet", 1579729429); - // retrieve chain params from encoded tx; parse out packed tx - try { - nlohmann::json txJson = nlohmann::json::parse(tx); - Data txData = parse_hex(txJson.at("packed_trx").get()); - // decode values - ASSERT_TRUE(txData.size() >= 10); - paramOut.headBlockNumber = decode16LE(txData.data() + 4); - paramOut.refBlockPrefix = decode32LE(txData.data() + 4 + 2); - } catch (nlohmann::json::type_error& e) { - FAIL() << "Json parse error " << e.what(); - } -} - -void checkBlockNum(uint64_t blockNumIn, uint64_t blockNumExpected) { - ChainParams paramOut; - createTxWithChainParam(ChainParams{chainId, blockNumIn, 4281229859}, paramOut); - EXPECT_EQ(paramOut.headBlockNumber, blockNumExpected); -} - -void checkRefBlockPrefix(uint64_t blockPrefixIn, uint64_t blockPrefixExpected) { - ChainParams paramOut; - createTxWithChainParam(ChainParams{chainId, 11565, blockPrefixIn}, paramOut); - EXPECT_EQ(paramOut.refBlockPrefix, blockPrefixExpected); -} - -TEST(FIOTransactionBuilder, chainParansRange) { - // headBlockNumber, 2 bytes - checkBlockNum(101, 101); - checkBlockNum(0xFFFF, 0xFFFF); - checkBlockNum(0x00011234, 0x1234); - // large values truncated - checkBlockNum(0xFFAB1234, 0x1234); - checkBlockNum(0x0000000112345678, 0x5678); - checkBlockNum(0xFFABCDEF12345678, 0x5678); - - // refBlockPrefix, 4 bytes; Large refBlockPrefix values used to cause problem - checkRefBlockPrefix(101, 101); - checkRefBlockPrefix(4281229859, 4281229859); - checkRefBlockPrefix(0xFFFFFFFF, 0xFFFFFFFF); - // large values truncated - checkRefBlockPrefix(0x0000000112345678, 0x12345678); - checkRefBlockPrefix(0xFFABCDEF12345678, 0x12345678); -} - -TEST(FIOTransactionBuilder, encodeVarInt) { - { - Data data; - EXPECT_EQ(TW::FIO::encodeVarInt(0x11, data), 1); - EXPECT_EQ(hex(data), "11"); - } - { - Data data; - EXPECT_EQ(TW::FIO::encodeVarInt(0x7F, data), 1); - EXPECT_EQ(hex(data), "7f"); - } - { - Data data; - EXPECT_EQ(TW::FIO::encodeVarInt(0x80, data), 2); - EXPECT_EQ(hex(data), "8001"); - } - { - Data data; - EXPECT_EQ(TW::FIO::encodeVarInt(0xFF, data), 2); - EXPECT_EQ(hex(data), "ff01"); - } - { - Data data; - EXPECT_EQ(TW::FIO::encodeVarInt(0x100, data), 2); - EXPECT_EQ(hex(data), "8002"); - } - { - Data data; - EXPECT_EQ(TW::FIO::encodeVarInt(0x3FFF, data), 2); - EXPECT_EQ(hex(data), "ff7f"); - } - { - Data data; - EXPECT_EQ(TW::FIO::encodeVarInt(0x4000, data), 3); - EXPECT_EQ(hex(data), "808001"); - } - { - Data data; - EXPECT_EQ(TW::FIO::encodeVarInt(0xFFFFFFFF, data), 5); - EXPECT_EQ(hex(data), "ffffffff0f"); - } -} - -TEST(FIOTransactionBuilder, encodeString) { - { - Data data; - const string text = "ABC"; - TW::FIO::encodeString(text, data); - EXPECT_EQ(hex(data), "03" + hex(text)); - } - { - Data data; - const string text = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; - EXPECT_EQ(text.length(), 130); - TW::FIO::encodeString(text, data); - // length on 2 bytes - EXPECT_EQ(hex(data), "8201" + hex(text)); - } -} diff --git a/tests/Fantom/TWCoinTypeTests.cpp b/tests/Fantom/TWCoinTypeTests.cpp deleted file mode 100644 index acd79755342..00000000000 --- a/tests/Fantom/TWCoinTypeTests.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWFantomCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeFantom)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xb0a741d882291951de1fac72e90b9baf886ddb0c9c87658a0c206490dfaa5202")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeFantom, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x9474feb9917b87da6f0d830ba66ee0035835c0d3")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeFantom, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeFantom)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeFantom)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeFantom), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeFantom)); - - assertStringsEqual(symbol, "FTM"); - assertStringsEqual(txUrl, "https://ftmscan.com/tx/0xb0a741d882291951de1fac72e90b9baf886ddb0c9c87658a0c206490dfaa5202"); - assertStringsEqual(accUrl, "https://ftmscan.com/address/0x9474feb9917b87da6f0d830ba66ee0035835c0d3"); - assertStringsEqual(id, "fantom"); - assertStringsEqual(name, "Fantom"); -} diff --git a/tests/Filecoin/AddressTests.cpp b/tests/Filecoin/AddressTests.cpp deleted file mode 100644 index db657eda357..00000000000 --- a/tests/Filecoin/AddressTests.cpp +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright © 2017-2020 Trust. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Filecoin/Address.h" -#include "HexCoding.h" - -#include -#include - -using namespace TW; -using namespace TW::Filecoin; - -// clang-format off - -struct address_test { - std::string encoded; - const char* hex; -}; - -static const address_test validAddresses[] = { - // ID addresses - {"f00", "0000"}, - {"f01", "0001"}, - {"f010", "000a"}, - {"f0150", "009601"}, - {"f0499", "00f303"}, - {"f01024", "008008"}, - {"f01729", "00c10d"}, - {"f0999999", "00bf843d"}, - {"f018446744073709551615", "00ffffffffffffffffff01"}, - // secp256k1 addresses - {"f15ihq5ibzwki2b4ep2f46avlkrqzhpqgtga7pdrq", "01ea0f0ea039b291a0f08fd179e0556a8c3277c0d3"}, - {"f12fiakbhe2gwd5cnmrenekasyn6v5tnaxaqizq6a", "01d1500504e4d1ac3e89ac891a4502586fabd9b417"}, - {"f1wbxhu3ypkuo6eyp6hjx6davuelxaxrvwb2kuwva", "01b06e7a6f0f551de261fe3a6fe182b422ee0bc6b6"}, - {"f1xtwapqc6nh4si2hcwpr3656iotzmlwumogqbuaa", "01bcec07c05e69f92468e2b3e3bf77c874f2c5da8c"}, - {"f1xcbgdhkgkwht3hrrnui3jdopeejsoatkzmoltqy", "01b882619d46558f3d9e316d11b48dcf211327026a"}, - {"f17uoq6tp427uzv7fztkbsnn64iwotfrristwpryy", "01fd1d0f4dfcd7e99afcb99a8326b7dc459d32c628"}, - // Actor addresses - {"f24vg6ut43yw2h2jqydgbg2xq7x6f4kub3bg6as6i", "02e54dea4f9bc5b47d261819826d5e1fbf8bc5503b"}, - {"f25nml2cfbljvn4goqtclhifepvfnicv6g7mfmmvq", "02eb58bd08a15a6ade19d0989674148fa95a8157c6"}, - {"f2nuqrg7vuysaue2pistjjnt3fadsdzvyuatqtfei", "026d21137eb4c4814269e894d296cf6500e43cd714"}, - {"f24dd4ox4c2vpf5vk5wkadgyyn6qtuvgcpxxon64a", "02e0c7c75f82d55e5ed55db28033630df4274a984f"}, - {"f2gfvuyh7v2sx3patm5k23wdzmhyhtmqctasbr23y", "02316b4c1ff5d4afb7826ceab5bb0f2c3e0f364053"}, - // BLS addresses - {"f3vvmn62lofvhjd2ugzca6sof2j2ubwok6cj4xxbfzz4yuxfkgobpihhd2thlanmsh3w2ptld2gqkn2jvlss4a","03ad58df696e2d4e91ea86c881e938ba4ea81b395e12797b84b9cf314b9546705e839c7a99d606b247ddb4f9ac7a3414dd"}, - {"f3wmuu6crofhqmm3v4enos73okk2l366ck6yc4owxwbdtkmpk42ohkqxfitcpa57pjdcftql4tojda2poeruwa","03b3294f0a2e29e0c66ebc235d2fedca5697bf784af605c75af608e6a63d5cd38ea85ca8989e0efde9188b382f9372460d"}, - {"f3s2q2hzhkpiknjgmf4zq3ejab2rh62qbndueslmsdzervrhapxr7dftie4kpnpdiv2n6tvkr743ndhrsw6d3a","0396a1a3e4ea7a14d49985e661b22401d44fed402d1d0925b243c923589c0fbc7e32cd04e29ed78d15d37d3aaa3fe6da33"}, - {"f3q22fijmmlckhl56rn5nkyamkph3mcfu5ed6dheq53c244hfmnq2i7efdma3cj5voxenwiummf2ajlsbxc65a","0386b454258c589475f7d16f5aac018a79f6c1169d20fc33921dd8b5ce1cac6c348f90a3603624f6aeb91b64518c2e8095"}, - {"f3u5zgwa4ael3vuocgc5mfgygo4yuqocrntuuhcklf4xzg5tcaqwbyfabxetwtj4tsam3pbhnwghyhijr5mixa","03a7726b038022f75a384617585360cee629070a2d9d28712965e5f26ecc40858382803724ed34f2720336f09db631f074"}, -}; - -static const std::string invalidAddresses[] = { - "", - "f0-1", // Negative :) - "f018446744073709551616", // Greater than max uint64_t - "f15ihq5ibzwki2b4ep2f46avlkr\0zhpqgtga7pdrq", // Embedded NUL - "t15ihq5ibzwki2b4ep2f46avlkrqzhpqgtga7pdrq", // Test net - "a15ihq5ibzwki2b4ep2f46avlkrqzhpqgtga7pdrq", // Unknown net - "f95ihq5ibzwki2b4ep2f46avlkrqzhpqgtga7pdrq", // Unknown address type - // Invalid checksum cases - "f15ihq5ibzwki2b4ep2f46avlkrqzhpqgtga7rdrr", - "f24vg6ut43yw2h2jqydgbg2xq7x6f4kub3bg6as66", - "f3vvmn62lofvhjd2ugzca6sof2j2ubwok6cj4xxbfzz4yuxfkgobpihhd2thlanmsh3w2ptld2gqkn2jvlss44", -}; - -TEST(FilecoinAddress, IsValid) { - for (const auto& test : validAddresses) { - ASSERT_TRUE(Address::isValid(test.encoded)) - << "is_valid() != true: " << test.encoded << std::endl; - ASSERT_TRUE(Address::isValid(parse_hex(test.hex))) - << "is_valid() != true: " << test.hex << std::endl; - } - for (const auto& address : invalidAddresses) - ASSERT_FALSE(Address::isValid(address)) << "is_valid() != false: " << address << std::endl; - - ASSERT_FALSE(Address::isValid(parse_hex("00"))) << "Empty varuint"; - ASSERT_FALSE(Address::isValid(parse_hex("00ff"))) << "Short varuint"; - ASSERT_FALSE(Address::isValid(parse_hex("00ff00ff"))) << "Varuint with hole"; - ASSERT_FALSE(Address::isValid(parse_hex("000101"))) << "Long varuint"; - ASSERT_FALSE(Address::isValid(parse_hex("000000"))) << "Long varuint"; - ASSERT_FALSE(Address::isValid(parse_hex("00ffffffffffffffffff02"))) << "Overflow"; -} - -TEST(FilecoinAddress, String) { - for (const auto& test : validAddresses) { - Address a(parse_hex(test.hex)); - ASSERT_EQ(a.string(), test.encoded) << "Address(" << test.hex << ")"; - } -} - -TEST(FilecoinAddress, FromString) { - for (const auto& test : validAddresses) { - Address a(test.encoded); - ASSERT_EQ(hex(a.bytes), test.hex) << "Address(" << test.encoded << ")"; - } -} diff --git a/tests/Filecoin/SignerTests.cpp b/tests/Filecoin/SignerTests.cpp deleted file mode 100644 index 2e52a650ee0..00000000000 --- a/tests/Filecoin/SignerTests.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright © 2017-2019 Trust. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Filecoin/Address.h" -#include "Filecoin/Signer.h" -#include "HexCoding.h" -#include "PrivateKey.h" - -#include - -namespace TW::Filecoin { - -TEST(FilecoinSigner, DerivePublicKey) { - const PrivateKey privateKey( - parse_hex("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe")); - const PublicKey publicKey((privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended))); - const Address address(publicKey); - ASSERT_EQ(address.string(), "f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq"); -} - -TEST(FilecoinSigner, Sign) { - const PrivateKey privateKey( - parse_hex("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe")); - const PublicKey publicKey((privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended))); - const Address fromAddress(publicKey); - const Address toAddress("f1rletqqhinhagw6nxjcr4kbfws25thgt7owzuruy"); - - Transaction tx(toAddress, fromAddress, - /*nonce*/ 1, - /*value*/ 6000, - /*gasLimit*/ 23423423423423, - /*gasFeeCap*/ 456456456456445645, - /*gasPremium*/ 5675674564734345); - - Data signature = Signer::sign(privateKey, tx); - - ASSERT_EQ(tx.serialize(signature), R"({"Message":{"From":"f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq","GasFeeCap":"456456456456445645","GasLimit":23423423423423,"GasPremium":"5675674564734345","Nonce":1,"To":"f1rletqqhinhagw6nxjcr4kbfws25thgt7owzuruy","Value":"6000"},"Signature":{"Data":"3GOUpn2Wiwe20QXLC8ixx23WiKDwrVkfxYi3CgzZ5jBVKZT4WUOZNuZhpUFky0PqGaM7vErEOi//yqBGSIQQUAA=","Type":1}})"); -} - -} // namespace TW::Filecoin diff --git a/tests/Filecoin/TWAnySignerTests.cpp b/tests/Filecoin/TWAnySignerTests.cpp deleted file mode 100644 index ee9c85f7f95..00000000000 --- a/tests/Filecoin/TWAnySignerTests.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" -#include - -#include "Data.h" -#include "HexCoding.h" -#include "proto/Filecoin.pb.h" -#include "uint256.h" - -#include - -using namespace TW; -using namespace Filecoin; - -TEST(TWAnySignerFilecoin, Sign) { - Proto::SigningInput input; - auto privateKey = parse_hex("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe"); - auto toAddress = - "f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq"; - uint64_t nonce = 2; - // 600 FIL - // auto value = parse_hex("2086ac351052600000"); - auto value = store(uint256_t(600) * uint256_t(1'000'000'000) * uint256_t(1'000'000'000)); - uint64_t gasLimit = 1000; - // auto gasFeeCap = parse_hex("25f273933db5700000"); - auto gasFeeCap = store(uint256_t(700) * uint256_t(1'000'000'000) * uint256_t(1'000'000'000)); - // auto gasPremium = parse_hex("2b5e3af16b18800000"); - auto gasPremium = store(uint256_t(800) * uint256_t(1'000'000'000) * uint256_t(1'000'000'000)); - - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_to(toAddress); - input.set_nonce(nonce); - input.set_value(value.data(), value.size()); - input.set_gas_limit(gasLimit); - input.set_gas_fee_cap(gasFeeCap.data(), gasFeeCap.size()); - input.set_gas_premium(gasPremium.data(), gasPremium.size()); - - auto inputString = input.SerializeAsString(); - auto inputData = WRAPD(TWDataCreateWithBytes((const byte*)inputString.data(), inputString.size())); - - auto outputData = WRAPD(TWAnySignerSign(inputData.get(), TWCoinTypeFilecoin)); - - Proto::SigningOutput output; - output.ParseFromArray(TWDataBytes(outputData.get()), static_cast(TWDataSize(outputData.get()))); - - ASSERT_EQ(output.json(), R"({"Message":{"From":"f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq","GasFeeCap":"700000000000000000000","GasLimit":1000,"GasPremium":"800000000000000000000","Nonce":2,"To":"f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq","Value":"600000000000000000000"},"Signature":{"Data":"jMRu+OZ/lfppgmqSfGsntFrRLWZnUg3ZYmJTTRLsVt4V1310vR3VKGJpaE6S4sNvDOE6sEgmN9YmfTkPVK2qMgE=","Type":1}})"); -} - -TEST(TWAnySignerFilecoin, SignJSON) { - auto json = STRING( - R"({ - "to": "f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq", - "nonce": "2", - "value": "IIasNRBSYAAA", - "gasLimit": 1000, - "gasFeeCap": "JfJzkz21cAAA", - "gasPremium": "K1468WsYgAAA" - })"); - auto key = DATA("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe"); - auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeFilecoin)); - - ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeFilecoin)); - assertStringsEqual(result, R"({"Message":{"From":"f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq","GasFeeCap":"700000000000000000000","GasLimit":1000,"GasPremium":"800000000000000000000","Nonce":2,"To":"f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq","Value":"600000000000000000000"},"Signature":{"Data":"jMRu+OZ/lfppgmqSfGsntFrRLWZnUg3ZYmJTTRLsVt4V1310vR3VKGJpaE6S4sNvDOE6sEgmN9YmfTkPVK2qMgE=","Type":1}})"); -} diff --git a/tests/Filecoin/TWCoinTypeTests.cpp b/tests/Filecoin/TWCoinTypeTests.cpp deleted file mode 100644 index 9e5ff95f72e..00000000000 --- a/tests/Filecoin/TWCoinTypeTests.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - -TEST(TWFilecoinCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeFilecoin)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("bafy2bzacedsgjcd6xfhrrymmfrqubb44otlyhvgqkgsh533d5j5hwniiqespm")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeFilecoin, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("f1abjxfbp274xpdqcpuaykwkfb43omjotacm2p3za")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeFilecoin, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeFilecoin)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeFilecoin)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeFilecoin), 18); - ASSERT_EQ(TWBlockchainFilecoin, TWCoinTypeBlockchain(TWCoinTypeFilecoin)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeFilecoin)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeFilecoin)); - assertStringsEqual(symbol, "FIL"); - assertStringsEqual(txUrl, "https://filfox.info/en/message/bafy2bzacedsgjcd6xfhrrymmfrqubb44otlyhvgqkgsh533d5j5hwniiqespm"); - assertStringsEqual(accUrl, "https://filfox.info/en/address/f1abjxfbp274xpdqcpuaykwkfb43omjotacm2p3za"); - assertStringsEqual(id, "filecoin"); - assertStringsEqual(name, "Filecoin"); -} diff --git a/tests/Filecoin/TransactionTests.cpp b/tests/Filecoin/TransactionTests.cpp deleted file mode 100644 index 6e35a17e0a0..00000000000 --- a/tests/Filecoin/TransactionTests.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright © 2017-2020 Trust. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Filecoin/Address.h" -#include "Filecoin/Transaction.h" -#include "HexCoding.h" -#include "PrivateKey.h" - -#include - -namespace TW::Filecoin { - -TEST(FilecoinTransaction, EncodeBigInt) { - ASSERT_EQ(hex(encodeBigInt(0)), ""); - ASSERT_EQ(hex(encodeBigInt(1)), "0001"); - ASSERT_EQ(hex(encodeBigInt(16)), "0010"); - ASSERT_EQ(hex(encodeBigInt(1111111111111)), "000102b36211c7"); - uint256_t reallyBig = 1; - reallyBig <<= 128; - ASSERT_EQ(hex(encodeBigInt(reallyBig)), "000100000000000000000000000000000000"); -} - -TEST(FilecoinTransaction, Serialize) { - const PrivateKey privateKey( - parse_hex("2f0f1d2c8de955c7c3fb4d9cae02539fadcb13fa998ccd9a1e871bed95f1941e")); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); - const Address fromAddress(publicKey); - const Address toAddress("f1hvadvq4rd2pyayrigjx2nbqz2nvemqouslw4wxi"); - - Transaction tx(toAddress, fromAddress, - /*nonce*/ 0x1234567890, - /*value*/ 1000, - /*gasLimit*/ 3333333333, - /*gasFeeCap*/ 11111111, - /*gasPremium*/ 333333); - - ASSERT_EQ(hex(tx.message().encoded()), - "8a0055013d403ac3911e9f806228326fa68619d36a4641d455013d413d4c3fe3d89f99495a48c6046224" - "a71f0cd71b0000001234567890430003e81ac6aea1554400a98ac744000516150040"); - ASSERT_EQ(hex(tx.cid()), - "0171a0e40220a3b06c2837a94e3a431a78b00536d0298455ceec3d304adf26a3868147c4e6e1"); -} - -} // namespace TW::Filecoin diff --git a/tests/Firo/TWCoinTypeTests.cpp b/tests/Firo/TWCoinTypeTests.cpp deleted file mode 100644 index b8e7a60f49b..00000000000 --- a/tests/Firo/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWFiroCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeFiro)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("09a60d58b3d17519a42a8eca60750c33b710ca8f3ca71994192e05c248a2a111")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeFiro, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a8ULhhDgfdSiXJhSZVdhb8EuDc6R3ogsaM")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeFiro, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeFiro)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeFiro)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeFiro), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeFiro)); - ASSERT_EQ(0x7, TWCoinTypeP2shPrefix(TWCoinTypeFiro)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeFiro)); - assertStringsEqual(symbol, "FIRO"); - assertStringsEqual(txUrl, "https://explorer.firo.org/tx/09a60d58b3d17519a42a8eca60750c33b710ca8f3ca71994192e05c248a2a111"); - assertStringsEqual(accUrl, "https://explorer.firo.org/address/a8ULhhDgfdSiXJhSZVdhb8EuDc6R3ogsaM"); - assertStringsEqual(id, "firo"); - assertStringsEqual(name, "Firo"); -} diff --git a/tests/Firo/TWZCoinAddressTests.cpp b/tests/Firo/TWZCoinAddressTests.cpp deleted file mode 100644 index 21c495364e5..00000000000 --- a/tests/Firo/TWZCoinAddressTests.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" - -#include -#include -#include -#include -#include -#include -#include - -#include - -TEST(TWZCoin, Address) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get())); - auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); - auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeFiro))); - auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); - assertStringsEqual(addressString, "aAbqxogrjdy2YHVcnQxFHMzqpt2fhjCTVT"); -} - -TEST(TWZCoin, ExtendedKeys) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( - STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), - STRING("TREZOR").get() - )); - - auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeFiro, TWHDVersionXPUB)); - auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeFiro, TWHDVersionXPRV)); - - assertStringsEqual(xpub, "xpub6Cb8Q6pDeS8PdKNbDv9Hvq4WpJXL3JvKvmHHwR1wD2H543hiCUE1f1tB5AXE6yg13k7xZ6PzEXMNUFHXk6kkx4RYte8VB1i4tCX9rwQVR4a"); - assertStringsEqual(xprv, "xprv9ybmzbHKp4a6QqJ87tcHZh7nGGgqdrCUZYMh92cKegk6BFNZevum7DZhDuVDqqMdcBT9B4wJSEmwJW9JNdkMcUUjEWKqppxNrJjKFSyKsCr"); -} - -TEST(TWZcoin, DeriveFromXpub) { - auto xpub = STRING("xpub6Cb8Q6pDeS8PdKNbDv9Hvq4WpJXL3JvKvmHHwR1wD2H543hiCUE1f1tB5AXE6yg13k7xZ6PzEXMNUFHXk6kkx4RYte8VB1i4tCX9rwQVR4a"); - auto pubKey3 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeFiro, STRING("m/44'/136'/0'/0/3").get())); - auto pubKey5 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeFiro, STRING("m/44'/136'/0'/0/5").get())); - - auto address3 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey3.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeFiro))); - auto address3String = WRAPS(TWBitcoinAddressDescription(address3.get())); - - auto address5 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey5.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeFiro))); - auto address5String = WRAPS(TWBitcoinAddressDescription(address5.get())); - - assertStringsEqual(address3String, "aLnztJEbyACnxF9H7SFC8YjUxedwyQsgVm"); - assertStringsEqual(address5String, "aJj2jdMzHyKFJLEFTxhpn379avEqRKFUyw"); -} - -TEST(TWZcoin, LockScripts) { - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("a4YtT82mWWxHZhLmdx7e5aroW92dqJoRs3").get(), TWCoinTypeFiro)); - auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); - assertHexEqual(scriptData2, "76a9142a10f88e30768d2712665c279922b9621ce58bc788ac"); - - auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("4CFa4fnAQvFz4VpikGNzQ9XfCDXMmdk6sh").get(), TWCoinTypeFiro)); - auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); - assertHexEqual(scriptData3, "a914f010b17a9189e0f2737d71ae9790359eb5bbc13787"); -} diff --git a/tests/GoChain/TWCoinTypeTests.cpp b/tests/GoChain/TWCoinTypeTests.cpp deleted file mode 100644 index 7e8b1b75a94..00000000000 --- a/tests/GoChain/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWGoChainCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeGoChain)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeGoChain, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeGoChain, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeGoChain)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeGoChain)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeGoChain), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeGoChain)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeGoChain)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeGoChain)); - assertStringsEqual(symbol, "GO"); - assertStringsEqual(txUrl, "https://explorer.gochain.io/tx/t123"); - assertStringsEqual(accUrl, "https://explorer.gochain.io/addr/a12"); - assertStringsEqual(id, "gochain"); - assertStringsEqual(name, "GoChain"); -} diff --git a/tests/Groestlcoin/AddressTests.cpp b/tests/Groestlcoin/AddressTests.cpp deleted file mode 100644 index d3910b6c382..00000000000 --- a/tests/Groestlcoin/AddressTests.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Groestlcoin/Address.h" - -#include "Coin.h" -#include "HDWallet.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Groestlcoin; - -TEST(GroestlcoinAddress, FromPublicKey) { - const auto publicKey = PublicKey(parse_hex("03b85cc59b67c35851eb5060cfc3a759a482254553c5857075c9e247d74d412c91"), TWPublicKeyTypeSECP256k1); - const auto address = Address(publicKey, 36); - ASSERT_EQ(address.string(), "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); -} - -TEST(GroestlcoinAddress, Valid) { - ASSERT_TRUE(Address::isValid(std::string("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"))); -} - -TEST(GroestlcoinAddress, Invalid) { - ASSERT_FALSE(Address::isValid(std::string("1JAd7XCBzGudGpJQSDSfpmJhiygtLQWaGL"))); // Valid bitcoin address -} - -TEST(GroestlcoinAddress, FromString) { - const auto string = "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"; - const auto address = Address(string); - - ASSERT_EQ(address.string(), string); -} - -TEST(GroestlcoinAddress, Derive) { - const auto mnemonic = "all all all all all all all all all all all all"; - const auto wallet = HDWallet(mnemonic, ""); - const auto path = TW::derivationPath(TWCoinTypeGroestlcoin); - const auto address = TW::deriveAddress(TWCoinTypeGroestlcoin, wallet.getKey(TWCoinTypeGroestlcoin, path)); - ASSERT_EQ(address, "grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne"); -} diff --git a/tests/Groestlcoin/TWCoinTypeTests.cpp b/tests/Groestlcoin/TWCoinTypeTests.cpp deleted file mode 100644 index 6187d5d4e85..00000000000 --- a/tests/Groestlcoin/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWGroestlcoinCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeGroestlcoin)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeGroestlcoin, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeGroestlcoin, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeGroestlcoin)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeGroestlcoin)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeGroestlcoin), 8); - ASSERT_EQ(TWBlockchainGroestlcoin, TWCoinTypeBlockchain(TWCoinTypeGroestlcoin)); - ASSERT_EQ(0x5, TWCoinTypeP2shPrefix(TWCoinTypeGroestlcoin)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeGroestlcoin)); - assertStringsEqual(symbol, "GRS"); - assertStringsEqual(txUrl, "https://blockchair.com/groestlcoin/transaction/t123"); - assertStringsEqual(accUrl, "https://blockchair.com/groestlcoin/address/a12"); - assertStringsEqual(id, "groestlcoin"); - assertStringsEqual(name, "Groestlcoin"); -} diff --git a/tests/HDWallet/HDWalletTests.cpp b/tests/HDWallet/HDWalletTests.cpp deleted file mode 100644 index fcf85eaa5e1..00000000000 --- a/tests/HDWallet/HDWalletTests.cpp +++ /dev/null @@ -1,399 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HDWallet.h" -#include "Mnemonic.h" -#include "Bitcoin/Address.h" -#include "Bitcoin/CashAddress.h" -#include "Bitcoin/SegwitAddress.h" -#include "Ethereum/Address.h" -#include "HexCoding.h" -#include "PublicKey.h" -#include "Hash.h" -#include "Base58.h" -#include "Coin.h" -#include "../interface/TWTestUtilities.h" - -#include - -extern std::string TESTS_ROOT; - -namespace TW { - -const auto mnemonic1 = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; -const auto passphrase = "passphrase"; - -TEST(HDWallet, generate) { - { - HDWallet wallet = HDWallet(128, passphrase); - EXPECT_TRUE(Mnemonic::isValid(wallet.getMnemonic())); - EXPECT_EQ(wallet.getPassphrase(), passphrase); - EXPECT_EQ(wallet.getEntropy().size(), 16); - } - { - HDWallet wallet = HDWallet(256, passphrase); - EXPECT_TRUE(Mnemonic::isValid(wallet.getMnemonic())); - EXPECT_EQ(wallet.getPassphrase(), passphrase); - EXPECT_EQ(wallet.getEntropy().size(), 32); - } -} - -TEST(HDWallet, generateInvalid) { - EXPECT_EXCEPTION(HDWallet(64, passphrase), "Invalid strength"); - EXPECT_EXCEPTION(HDWallet(129, passphrase), "Invalid strength"); - EXPECT_EXCEPTION(HDWallet(512, passphrase), "Invalid strength"); -} - -TEST(HDWallet, createFromMnemonic) { - { - HDWallet wallet = HDWallet(mnemonic1, passphrase); - EXPECT_EQ(wallet.getMnemonic(), mnemonic1); - EXPECT_EQ(wallet.getPassphrase(), passphrase); - EXPECT_EQ(hex(wallet.getEntropy()), "ba5821e8c356c05ba5f025d9532fe0f21f65d594"); - EXPECT_EQ(hex(wallet.getSeed()), "143cd5fc27ae46eb423efebc41610473f5e24a80f2ca2e2fa7bf167e537f58f4c68310ae487fce82e25bad29bab2530cf77fd724a5ebfc05a45872773d7ee2d6"); - } - { // empty passphrase - HDWallet wallet = HDWallet(mnemonic1, ""); - EXPECT_EQ(wallet.getMnemonic(), mnemonic1); - EXPECT_EQ(wallet.getPassphrase(), ""); - EXPECT_EQ(hex(wallet.getEntropy()), "ba5821e8c356c05ba5f025d9532fe0f21f65d594"); - EXPECT_EQ(hex(wallet.getSeed()), "354c22aedb9a37407adc61f657a6f00d10ed125efa360215f36c6919abd94d6dbc193a5f9c495e21ee74118661e327e84a5f5f11fa373ec33b80897d4697557d"); - } -} - -TEST(HDWallet, entropyLength_createFromMnemonic) { - { // 12 words - HDWallet wallet = HDWallet("oil oil oil oil oil oil oil oil oil oil oil oil", ""); - EXPECT_EQ(wallet.getEntropy().size(), 16); - EXPECT_EQ(hex(wallet.getEntropy()), "99d33a674ce99d33a674ce99d33a674c"); - } - { // 12 words, from https://github.com/trezor/python-mnemonic/blob/master/vectors.json - HDWallet wallet = HDWallet("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", ""); - EXPECT_EQ(wallet.getEntropy().size(), 16); - EXPECT_EQ(hex(wallet.getEntropy()), "00000000000000000000000000000000"); - } - { // 15 words - HDWallet wallet = HDWallet("history step cheap card humble screen raise seek robot slot coral roof spoil wreck caution", ""); - EXPECT_EQ(wallet.getEntropy().size(), 20); - EXPECT_EQ(hex(wallet.getEntropy()), "6c3aac9b9146ef832c4e18bb3980c0dddd25fc49"); - } - { // 18 words - HDWallet wallet = HDWallet("caught hockey split gun symbol code payment copy broccoli silly shed secret stove tell citizen staff photo high", ""); - EXPECT_EQ(wallet.getEntropy().size(), 24); - EXPECT_EQ(hex(wallet.getEntropy()), "246d8f48b3fdc65a2869801c791715614d6bbd8a56a0a3ad"); - } - { // 21 words - HDWallet wallet = HDWallet("diary shine country alpha bridge coast loan hungry hip media sell crucial swarm share gospel lake visa coin dizzy physical basket", ""); - EXPECT_EQ(wallet.getEntropy().size(), 28); - EXPECT_EQ(hex(wallet.getEntropy()), "3d58bcc40381bc59a0c37a6bf14f0d9a3db78a5933e5f4a5ad00d1f1"); - } - { // 24 words - HDWallet wallet = HDWallet("poet spider smile swift roof pilot subject save hand diet ice universe over brown inspire ugly wide economy symbol shove episode patient plug swamp", ""); - EXPECT_EQ(wallet.getEntropy().size(), 32); - EXPECT_EQ(hex(wallet.getEntropy()), "a73a3732edebbb49f5fdfe68c7b5c0f6e9de3a1d5760faa8c771e384bf4229b6"); - } - { // 24 words, from https://github.com/trezor/python-mnemonic/blob/master/vectors.json - HDWallet wallet = HDWallet("letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", ""); - EXPECT_EQ(wallet.getEntropy().size(), 32); - EXPECT_EQ(hex(wallet.getEntropy()), "8080808080808080808080808080808080808080808080808080808080808080"); - } -} - -TEST(HDWallet, createFromSpanishMnemonic) { - { - EXPECT_EXCEPTION(HDWallet("llanto radical atraer riesgo actuar masa fondo cielo dieta archivo sonrisa mamut", ""), "Invalid mnemonic"); - } - { - HDWallet wallet = HDWallet("llanto radical atraer riesgo actuar masa fondo cielo dieta archivo sonrisa mamut", "", false); - EXPECT_EQ(wallet.getMnemonic(), "llanto radical atraer riesgo actuar masa fondo cielo dieta archivo sonrisa mamut"); - EXPECT_EQ(wallet.getPassphrase(), ""); - EXPECT_EQ(hex(wallet.getEntropy()), ""); - EXPECT_EQ(hex(wallet.getSeed()), "ec8f8703432fc7d32e699ee056e9d84b1435e6a64a6a40ad63dbde11eab189a276ddcec20f3326d3c6ee39cbd018585b104fc3633b801c011063ae4c318fb9b6"); - } -} - -TEST(HDWallet, createFromMnemonicInvalid) { - EXPECT_EXCEPTION(HDWallet("THIS IS AN INVALID MNEMONIC", passphrase), "Invalid mnemonic"); - EXPECT_EXCEPTION(HDWallet("", passphrase), "Invalid mnemonic"); - - EXPECT_EXCEPTION(HDWallet("", passphrase, false), "Invalid mnemonic"); - HDWallet walletUnchecked = HDWallet("THIS IS AN INVALID MNEMONIC", passphrase, false); -} - -TEST(HDWallet, createFromEntropy) { - { - HDWallet wallet = HDWallet(parse_hex("ba5821e8c356c05ba5f025d9532fe0f21f65d594"), passphrase); - EXPECT_EQ(wallet.getMnemonic(), mnemonic1); - } -} - -TEST(HDWallet, createFromEntropyInvalid) { - EXPECT_EXCEPTION(HDWallet(parse_hex(""), passphrase), "Invalid mnemonic data"); - EXPECT_EXCEPTION(HDWallet(parse_hex("123456"), passphrase), "Invalid mnemonic data"); -} - -TEST(HDWallet, recreateFromEntropy) { - { - HDWallet wallet1 = HDWallet(mnemonic1, passphrase); - EXPECT_EQ(wallet1.getMnemonic(), mnemonic1); - EXPECT_EQ(hex(wallet1.getEntropy()), "ba5821e8c356c05ba5f025d9532fe0f21f65d594"); - HDWallet wallet2 = HDWallet(wallet1.getEntropy(), passphrase); - EXPECT_EQ(wallet2.getMnemonic(), wallet1.getMnemonic()); - EXPECT_EQ(wallet2.getEntropy(), wallet1.getEntropy()); - EXPECT_EQ(wallet2.getSeed(), wallet1.getSeed()); - } -} - -TEST(HDWallet, privateKeyFromXPRV) { - const std::string xprv = "xprv9yqEgpMG2KCjvotCxaiMkzmKJpDXz2xZi3yUe4XsURvo9DUbPySW1qRbdeDLiSxZt88hESHUhm2AAe2EqfWM9ucdQzH3xv1HoKoLDqHMK9n"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 3)); - ASSERT_TRUE(privateKey); - auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); - auto address = Bitcoin::BitcoinCashAddress(publicKey); - - EXPECT_EQ(hex(publicKey.bytes), "025108168f7e5aad52f7381c18d8f880744dbee21dc02c15abe512da0b1cca7e2f"); - EXPECT_EQ(address.string(), "bitcoincash:qp3y0dyg6ya8nt4n3algazn073egswkytqs00z7rz4"); -} - -TEST(HDWallet, privateKeyFromXPRV_Invalid) { - const std::string xprv = "xprv9y0000"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 3)); - ASSERT_FALSE(privateKey); -} - -TEST(HDWallet, privateKeyFromXPRV_InvalidVersion) { - { - // Version bytes (first 4) are invalid, 0x00000000 - const std::string xprv = "11117pE7xwz2GARukXY8Vj2ge4ozfX4HLgy5ztnJXjr5btzJE8EbtPhZwrcPWAodW2aFeYiXkXjSxJYm5QrnhSKFXDgACcFdMqGns9VLqESCq3"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 3)); - ASSERT_FALSE(privateKey); - } - { - // Version bytes (first 4) are invalid, 0xdeadbeef - const std::string xprv = "pGoh3VZXR4mTkT4bfqj4paog12KmHkAWkdLY8HNsZagD1ihVccygLr1ioLBhVQsny47uEh5swP3KScFc4JJrazx1Y7xvzmH2y5AseLgVMwomBTg2"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 3)); - ASSERT_FALSE(privateKey); - } -} - -TEST(HDWallet, privateKeyFromExtended_InvalidCurve) { - // invalid coin & curve, should fail - const std::string xprv = "xprv9yqEgpMG2KCjvotCxaiMkzmKJpDXz2xZi3yUe4XsURvo9DUbPySW1qRbdeDLiSxZt88hESHUhm2AAe2EqfWM9ucdQzH3xv1HoKoLDqHMK9n"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, TWCoinType(123456), DerivationPath(TWPurposeBIP44, 123456, 0, 0, 0)); - ASSERT_FALSE(privateKey); -} - -TEST(HDWallet, privateKeyFromXPRV_Invalid45) { - // 45th byte is not 0 - const std::string xprv = "xprv9yqEgpMG2KCjvotCxaiMkzmKJpDXz2xZi3yUe4XsURvo9DUbPySW1qRbhw2dJ8QexahgVSfkjxU4FgmN4GLGN3Ui8oLqC6433CeyPUNVHHh"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(xprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 3)); - ASSERT_FALSE(privateKey); -} - -TEST(HDWallet, privateKeyFromMptv) { - const std::string mptv = "Mtpv7SkyM349Svcf1WiRtB5hC91ZZkVsGuv3kz1V7tThGxBFBzBLFnw6LpaSvwpHHuy8dAfMBqpBvaSAHzbffvhj2TwfojQxM7Ppm3CzW67AFL5"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(mptv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 4)); - auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); - - auto witness = Data{0x00, 0x14}; - auto keyHash = Hash::sha256ripemd(publicKey.bytes.data(), 33); - witness.insert(witness.end(), keyHash.begin(), keyHash.end()); - - auto prefix = Data{TW::p2shPrefix(TWCoinTypeLitecoin)}; - auto redeemScript = Hash::sha256ripemd(witness.data(), witness.size()); - prefix.insert(prefix.end(), redeemScript.begin(), redeemScript.end()); - - auto address = Bitcoin::Address(prefix); - - EXPECT_EQ(hex(publicKey.bytes), "02c36f9c3051e9cfbb196ecc35311f3ad705ea6798ffbe6b039e70f6bd047e6f2c"); - EXPECT_EQ(address.string(), "MBzcCaoLk9626cLj2UVvcxs6nsVUi39zEy"); -} - -TEST(HDWallet, privateKeyFromZprv) { - const std::string zprv = "zprvAdzGEQ44z4WPLNCRpDaup2RumWxLGgR8PQ9UVsSmJigXsHVDaHK1b6qGM2u9PmxB2Gx264ctAz4yRoN3Xwf1HZmKcn6vmjqwsawF4WqQjfd"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(zprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoin), 0, 0, 5)); - auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); - auto address = Bitcoin::SegwitAddress(publicKey, "bc"); - - EXPECT_EQ(hex(publicKey.bytes), "022dc3f5a3fcfd2d1cc76d0cb386eaad0e30247ba729da0d8847a2713e444fdafa"); - EXPECT_EQ(address.string(), "bc1q5yyq60jepll68hds7exa7kpj20gsvdu0aztw5x"); -} - -TEST(HDWallet, privateKeyFromDGRV) { - const std::string dgpv = "dgpv595jAJYGBLanByCJXRzrWBZFVXdNisfuPmKRDquCQcwBbwKbeR21AtkETf4EpjBsfsK3kDZgMqhcuky1B9PrT5nxiEcjghxpUVYviHXuCmc"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(dgpv, TWCoinTypeDogecoin, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeDogecoin), 0, 0, 1)); - auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); - auto address = Bitcoin::Address(publicKey, TW::p2pkhPrefix(TWCoinTypeDogecoin)); - - EXPECT_EQ(hex(publicKey.bytes), "03eb6bf281990ee074a39c71ed8ce78c486066ac433bcf066dd5eb08f87d3a6c34"); - EXPECT_EQ(address.string(), "D5taDndQJ1fDF3AM1yWavmJY2BgSi17CUv"); -} - -TEST(HDWallet, privateKeyFromXPRVForDGB) { - const std::string xprvForDgb = "xprv9ynLofyuR3uCqCMJADwzBaPnXB53EVe5oLujvPfdvCxae3NzgEpYjZMgcUeS8EUeYfYVLG61ZgPXm9TZWiwBnLVCgd551vCwpXC19hX3mFJ"; - auto privateKey = HDWallet::getPrivateKeyFromExtended(xprvForDgb, TWCoinTypeDigiByte, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeDigiByte), 0, 0, 1)); - auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); - auto address = Bitcoin::Address(publicKey, TW::p2pkhPrefix(TWCoinTypeDigiByte)); - - EXPECT_EQ(hex(publicKey.bytes), "03238a5c541c2cbbf769dbe0fb2a373c22db4da029370767fbe746d59da4de07f1"); - EXPECT_EQ(address.string(), "D9Gv7jWSVsS9Y5q98C79WyfEj6P2iM5Nzs"); -} - -TEST(HDWallet, DeriveWithLeadingZerosEth) { - // Derivation test case with leading zeroes, see https://blog.polychainlabs.com/bitcoin,/bip32,/bip39,/kdf/2021/05/17/inconsistent-bip32-derivations.html - const auto mnemonic = "name dash bleak force moral disease shine response menu rescue more will"; - const auto derivationPath = "m/44'/60'"; - const auto coin = TWCoinTypeEthereum; - auto wallet = HDWallet(mnemonic, ""); - const auto addr = Ethereum::Address(wallet.getKey(coin, DerivationPath(derivationPath)).getPublicKey(TW::publicKeyType(coin))); - EXPECT_EQ(addr.string(), "0x0ba17e928471c64AaEaf3ABfB3900EF4c27b380D"); -} - -static nlohmann::json getVectors() { - const std::string vectorsJsonPath = std::string(TESTS_ROOT) + "/HDWallet/bip39_vectors.json"; - auto vectorsJson = loadJson(vectorsJsonPath)["english"]; - return vectorsJson; -} - -TEST(HDWallet, Bip39Vectors) { - // BIP39 test vectors, from https://github.com/trezor/python-mnemonic/blob/master/vectors.json - const auto passphrase = "TREZOR"; - const auto vectors = getVectors(); - for (const auto& v: vectors) { - const std::string entropy = v[0]; - const std::string mnemonic = v[1]; - const std::string seed = v[2]; - const std::string xprv = v[3]; - { // from mnemonic - HDWallet wallet = HDWallet(mnemonic, passphrase); - EXPECT_EQ(wallet.getMnemonic(), mnemonic); - EXPECT_EQ(wallet.getPassphrase(), passphrase); - EXPECT_EQ(hex(wallet.getEntropy()), entropy); - EXPECT_EQ(hex(wallet.getSeed()), seed); - EXPECT_EQ(wallet.getRootKey(TWCoinTypeBitcoin, TWHDVersionXPRV), xprv); - } - { // from entropy - HDWallet wallet = HDWallet(parse_hex(entropy), passphrase); - EXPECT_EQ(wallet.getMnemonic(), mnemonic); - EXPECT_EQ(wallet.getPassphrase(), passphrase); - EXPECT_EQ(hex(wallet.getEntropy()), entropy); - EXPECT_EQ(hex(wallet.getSeed()), seed); - EXPECT_EQ(wallet.getRootKey(TWCoinTypeBitcoin, TWHDVersionXPRV), xprv); - } - } -} - -TEST(HDWallet, getExtendedPrivateKey) { - const HDWallet wallet = HDWallet(mnemonic1, ""); - const auto purpose = TWPurposeBIP44; - const auto coin = TWCoinTypeBitcoin; - const auto hdVersion = TWHDVersionZPRV; - - // default - const auto extPubKey1 = wallet.getExtendedPrivateKey(purpose, coin, hdVersion); - EXPECT_EQ(extPubKey1, "zprvAcwsTZNaY1f7rfwsy5GseSDStYBrxwtsBZDkb3iyuQUs4NF6n58BuH7Xj54RuaSCWtU5CiQzuYQgFgqr1HokgKcVAeGeXokhJUAJeP3VmvY"); - - // explicitly specify default account=0 - const auto extPubKey2 = wallet.getExtendedPrivateKeyAccount(purpose, coin, hdVersion, 0); - EXPECT_EQ(extPubKey2, "zprvAcwsTZNaY1f7rfwsy5GseSDStYBrxwtsBZDkb3iyuQUs4NF6n58BuH7Xj54RuaSCWtU5CiQzuYQgFgqr1HokgKcVAeGeXokhJUAJeP3VmvY"); - - // custom account=1 - const auto extPubKey3 = wallet.getExtendedPrivateKeyAccount(purpose, coin, hdVersion, 1); - EXPECT_EQ(extPubKey3, "zprvAcwsTZNaY1f7sifgNNgdNa4P9mPtyg3zRVgwkx2qF9Sn7F255MzP6Zyumn6bgV5xuoS8ZrDvjzE7APcFSacXdzFYpGvyybb1bnAoh5nHxpn"); -} - -TEST(HDWallet, getExtendedPublicKey) { - const HDWallet wallet = HDWallet(mnemonic1, ""); - const auto purpose = TWPurposeBIP44; - const auto coin = TWCoinTypeBitcoin; - const auto hdVersion = TWHDVersionZPUB; - - // default - const auto extPubKey1 = wallet.getExtendedPublicKey(purpose, coin, hdVersion); - EXPECT_EQ(extPubKey1, "zpub6qwDs4uUNPDR5A2M56ot1aABSa2MNQciYn9MPS8bTk1qwAaFKcSST5S1aLidvPp9twqpaumG7vikR2vHhBXjp5oGgHyMvWK3AtUkfeEgyns"); - - // explicitly specify default account=0 - const auto extPubKey2 = wallet.getExtendedPublicKeyAccount(purpose, coin, hdVersion, 0); - EXPECT_EQ(extPubKey2, "zpub6qwDs4uUNPDR5A2M56ot1aABSa2MNQciYn9MPS8bTk1qwAaFKcSST5S1aLidvPp9twqpaumG7vikR2vHhBXjp5oGgHyMvWK3AtUkfeEgyns"); - - // custom account=1 - const auto extPubKey3 = wallet.getExtendedPublicKeyAccount(purpose, coin, hdVersion, 1); - EXPECT_EQ(extPubKey3, "zpub6qwDs4uUNPDR6Ck9UQDdji17hoEPP8mqnicYZLSSoUykz3MDcuJdeNJPd3BozqEafeLZkegWqzAvkgA4JZZ5tTN2rDpGKfk54essyfx1eZP"); -} - -TEST(HDWallet, Derive_XpubPub_vs_PrivPub) { - // Test different routes for deriving address from mnemonic, result should be the same: - // - Direct: mnemonic -> seed -> privateKey -> publicKey -> address - // - Extended Public: mnemonic -> seed -> zpub -> publicKey -> address - // - Extended Private: mnemonic -> seed -> zpriv -> privateKey -> publicKey -> address - - const HDWallet wallet = HDWallet(mnemonic1, ""); - const auto coin = TWCoinTypeBitcoin; - const auto derivPath1 = DerivationPath("m/84'/0'/0'/0/0"); - const auto derivPath2 = DerivationPath("m/84'/0'/0'/0/2"); - const auto expectedPublicKey1 = "02df9ef2a7a5552765178b181e1e1afdefc7849985c7dfe9647706dd4fa40df6ac"; - const auto expectedPublicKey2 = "031e1f64d2f6768dccb6814545b2e2d58e26ad5f91b7cbaffe881ed572c65060db"; - const auto expectedAddress1 = "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"; - const auto expectedAddress2 = "bc1q7zddsunzaftf4zlsg9exhzlkvc5374a6v32jf6"; - - // -> privateKey -> publicKey - { - const auto privateKey1 = wallet.getKey(coin, derivPath1); - const auto publicKey1 = privateKey1.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_EQ(hex(publicKey1.bytes), expectedPublicKey1); - const auto address1 = Bitcoin::SegwitAddress(publicKey1, "bc"); - EXPECT_EQ(address1.string(), expectedAddress1); - } - { - const auto privateKey2 = wallet.getKey(coin, derivPath2); - const auto publicKey2 = privateKey2.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_EQ(hex(publicKey2.bytes), expectedPublicKey2); - const auto address2 = Bitcoin::SegwitAddress(publicKey2, "bc"); - EXPECT_EQ(address2.string(), expectedAddress2); - } - - // zpub -> publicKey - const auto zpub = wallet.getExtendedPublicKey(TWPurposeBIP84, coin, TWHDVersionZPUB); - EXPECT_EQ(zpub, "zpub6rNUNtxSa9Gxvm4Bdxf1MPMwrvkzwDx6vP96Hkzw3jiQKdg3fhXBStxjn12YixQB8h88B3RMSRscRstf9AEVaYr3MAqVBEWBDuEJU4PGaT9"); - - { - const auto publicKey1 = wallet.getPublicKeyFromExtended(zpub, coin, derivPath1); - EXPECT_TRUE(publicKey1.has_value()); - EXPECT_EQ(hex(publicKey1->bytes), expectedPublicKey1); - const auto address1 = Bitcoin::SegwitAddress(publicKey1.value(), "bc"); - EXPECT_EQ(address1.string(), expectedAddress1); - } - { - const auto publicKey2 = wallet.getPublicKeyFromExtended(zpub, coin, derivPath2); - EXPECT_TRUE(publicKey2.has_value()); - EXPECT_EQ(hex(publicKey2->bytes), expectedPublicKey2); - const auto address2 = Bitcoin::SegwitAddress(publicKey2.value(), "bc"); - EXPECT_EQ(address2.string(), expectedAddress2); - } - - // zpriv -> privateKey -> publicKey - const auto zpriv = wallet.getExtendedPrivateKey(TWPurposeBIP84, coin, TWHDVersionZPRV); - EXPECT_EQ(zpriv, "zprvAdP7yPRYjmifiGyiXw7zzFRDJtvWXmEFZADVVNbKVQBRSqLu8ACvu6eFvhrnnw4QwdTD8PUVa48MguwiPTiyfn85zWx9iA5MYy4Eufu5bas"); - - { - const auto privateKey1 = wallet.getPrivateKeyFromExtended(zpriv, coin, derivPath1); - EXPECT_TRUE(privateKey1.has_value()); - const auto publicKey1 = privateKey1->getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_EQ(hex(publicKey1.bytes), expectedPublicKey1); - const auto address1 = Bitcoin::SegwitAddress(publicKey1, "bc"); - EXPECT_EQ(address1.string(), expectedAddress1); - } - { - const auto privateKey2 = wallet.getPrivateKeyFromExtended(zpriv, coin, derivPath2); - EXPECT_TRUE(privateKey2.has_value()); - const auto publicKey2 = privateKey2->getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_EQ(hex(publicKey2.bytes), expectedPublicKey2); - const auto address2 = Bitcoin::SegwitAddress(publicKey2, "bc"); - EXPECT_EQ(address2.string(), expectedAddress2); - } -} - -} // namespace diff --git a/tests/HECO/TWCoinTypeTests.cpp b/tests/HECO/TWCoinTypeTests.cpp deleted file mode 100644 index 3f2f2ae6b96..00000000000 --- a/tests/HECO/TWCoinTypeTests.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWHECOCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeECOChain)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x2e62832615f5b68b3bbcd72046a24260ce47052841c1679841b9c574d3959f13")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeECOChain, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xc5a5b3e49e5d06afe163553c942dc59b4e358cf1")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeECOChain, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeECOChain)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeECOChain)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeECOChain), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeECOChain)); - - assertStringsEqual(symbol, "HT"); - assertStringsEqual(txUrl, "https://hecoinfo.com/tx/0x2e62832615f5b68b3bbcd72046a24260ce47052841c1679841b9c574d3959f13"); - assertStringsEqual(accUrl, "https://hecoinfo.com/address/0xc5a5b3e49e5d06afe163553c942dc59b4e358cf1"); - assertStringsEqual(id, "heco"); - assertStringsEqual(name, "Huobi ECO Chain"); -} diff --git a/tests/Harmony/AddressTests.cpp b/tests/Harmony/AddressTests.cpp deleted file mode 100644 index e5ed260abda..00000000000 --- a/tests/Harmony/AddressTests.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Harmony/Address.h" -#include "HexCoding.h" -#include "PrivateKey.h" - -#include - -using namespace TW; -using namespace TW::Harmony; - -TEST(HarmonyAddress, FromString) { - Address sender; - ASSERT_TRUE(Address::decode("one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe", sender)); - Address receiver; - ASSERT_TRUE(Address::decode("one1tp7xdd9ewwnmyvws96au0e7e7mz6f8hjqr3g3p", receiver)); - - ASSERT_EQ("ed1ebe4fd1f73f86388f231997859ca42c07da5d", hex(sender.getKeyHash())); - ASSERT_EQ("587c66b4b973a7b231d02ebbc7e7d9f6c5a49ef2", hex(receiver.getKeyHash())); -} - -TEST(HarmonyAddress, FromData) { - const auto address = Address(parse_hex("0x587c66b4b973a7b231d02ebbc7e7d9f6c5a49ef2")); - const auto address_2 = Address(parse_hex("0xed1ebe4fd1f73f86388f231997859ca42c07da5d")); - ASSERT_EQ(address.string(), "one1tp7xdd9ewwnmyvws96au0e7e7mz6f8hjqr3g3p"); - ASSERT_EQ(address_2.string(), "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe"); -} - -TEST(HarmonyAddress, InvalidHarmonyAddress) { - ASSERT_FALSE(Address::isValid("one1a50tun737ulcvwy0yvve0pe")); - ASSERT_FALSE(Address::isValid("oe1tp7xdd9ewwnmyvws96au0ee7e7mz6f8hjqr3g3p")); -} - -TEST(HarmonyAddress, FromPrivateKey) { - const auto privateKey = - PrivateKey(parse_hex("e2f88b4974ae763ca1c2db49218802c2e441293a09eaa9ab681779e05d1b7b94")); - const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); - const auto address = Address(publicKey); - ASSERT_EQ(address.string(), "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe"); -} diff --git a/tests/Harmony/SignerTests.cpp b/tests/Harmony/SignerTests.cpp deleted file mode 100644 index 9cfe4104306..00000000000 --- a/tests/Harmony/SignerTests.cpp +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include - -#include "Ethereum/RLP.h" -#include "Harmony/Address.h" -#include "Harmony/Signer.h" -#include "HexCoding.h" -#include "proto/Harmony.pb.h" - -namespace TW::Harmony { - -using namespace boost::multiprecision; - -class SignerExposed : public Signer { - public: - SignerExposed(uint256_t chainID) : Signer(chainID) {} - using Signer::hash; -}; - -static uint256_t MAIN_NET = 0x1; - -static uint256_t LOCAL_NET = 0x2; - -static uint256_t TEST_AMOUNT = uint256_t("0x4c53ecdc18a60000"); - -static Address TEST_RECEIVER; -static bool testReceiverDecodeResult = - Address::decode("one1d2rngmem4x2c6zxsjjz29dlah0jzkr0k2n88wc", TEST_RECEIVER); - -static auto TEST_TRANSACTION = Transaction(/* nonce: */ 0x9, - /* gasPrice: */ 0x0, - /* gasLimit: */ 0x5208, - /* fromShardID */ 0x1, - /* toShardID */ 0x0, - /* to: */ TEST_RECEIVER, - /* amount: */ TEST_AMOUNT, - /* payload: */ {}); - -TEST(HarmonySigner, RLPEncodingAndHashAssumeLocalNet) { - auto rlpUnhashedShouldBe = "e909808252080180946a87346f3ba9958d08d09484a" - "2b7fdbbe42b0df6884c53ecdc18a6000080028080"; - auto rlpHashedShouldBe = "610238ad72e4492af494f49bf5d92" - "13626a0ee5adb8256bb2558e990ee4da8f0"; - auto signer = SignerExposed(LOCAL_NET); - auto rlpHex = signer.txnAsRLPHex(TEST_TRANSACTION); - auto hash = signer.hash(TEST_TRANSACTION); - - ASSERT_EQ(rlpHex, rlpUnhashedShouldBe); - ASSERT_EQ(hex(hash), rlpHashedShouldBe); -} - -TEST(HarmonySigner, SignAssumeLocalNet) { - auto key = - PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); - auto signer = SignerExposed(LOCAL_NET); - - uint256_t v("0x28"); - uint256_t r("0x325aed6caa01a5235b7a508c8ab67f0c43946b05a1ea6a3e0628de4033fe372d"); - uint256_t s("0x6c19085d3376c30f6dc47cec795991cd37d6d0ebddfa633b0a8f494bc19cd01b"); - - auto transaction = Transaction(TEST_TRANSACTION); - auto hash = signer.hash(transaction); - - signer.sign(key, hash, transaction); - - ASSERT_EQ(transaction.v, v); - ASSERT_EQ(transaction.r, r); - ASSERT_EQ(transaction.s, s); -} - -TEST(HarmonySigner, SignProtoBufAssumeLocalNet) { - auto input = Proto::SigningInput(); - auto trasactionMsg = input.mutable_transaction_message(); - - trasactionMsg->set_to_address(TEST_RECEIVER.string()); - const auto privateKey = - PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); - auto payload = parse_hex(""); - trasactionMsg->set_payload(payload.data(), payload.size()); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - auto value = store(LOCAL_NET); - input.set_chain_id(value.data(), value.size()); - - value = store(uint256_t("0x9")); - trasactionMsg->set_nonce(value.data(), value.size()); - - value = store(uint256_t("")); - trasactionMsg->set_gas_price(value.data(), value.size()); - - value = store(uint256_t("0x5208")); - trasactionMsg->set_gas_limit(value.data(), value.size()); - - value = store(uint256_t("0x1")); - trasactionMsg->set_from_shard_id(value.data(), value.size()); - - value = store(uint256_t("0x0")); - trasactionMsg->set_to_shard_id(value.data(), value.size()); - - value = store(uint256_t("0x4c53ecdc18a60000")); - trasactionMsg->set_amount(value.data(), value.size()); - - auto proto_output = Signer::sign(input); - - auto shouldBeV = "28"; - auto shouldBeR = "325aed6caa01a5235b7a508c8ab67f0c43946b05a1ea6a3e0628de4033fe372d"; - auto shouldBeS = "6c19085d3376c30f6dc47cec795991cd37d6d0ebddfa633b0a8f494bc19cd01b"; - - ASSERT_EQ(hex(proto_output.v()), shouldBeV); - ASSERT_EQ(hex(proto_output.r()), shouldBeR); - ASSERT_EQ(hex(proto_output.s()), shouldBeS); -} - -TEST(HarmonySigner, SignOverProtoBufAssumeMainNet) { - auto input = Proto::SigningInput(); - auto trasactionMsg = input.mutable_transaction_message(); - trasactionMsg->set_to_address(TEST_RECEIVER.string()); - const auto privateKey = - PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); - auto payload = parse_hex(""); - trasactionMsg->set_payload(payload.data(), payload.size()); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - auto value = store(MAIN_NET); - input.set_chain_id(value.data(), value.size()); - - value = store(uint256_t("0xa")); - trasactionMsg->set_nonce(value.data(), value.size()); - - value = store(uint256_t("")); - trasactionMsg->set_gas_price(value.data(), value.size()); - - value = store(uint256_t("0x5208")); - trasactionMsg->set_gas_limit(value.data(), value.size()); - - value = store(uint256_t("0x1")); - trasactionMsg->set_from_shard_id(value.data(), value.size()); - - value = store(uint256_t("0x0")); - trasactionMsg->set_to_shard_id(value.data(), value.size()); - - value = store(uint256_t("0x4c53ecdc18a60000")); - trasactionMsg->set_amount(value.data(), value.size()); - - auto proto_output = Signer::sign(input); - - auto expectEncoded = "f8690a808252080180946a87346f3ba9958d08d09484a2b7fdbbe42b0df6884c53ecdc18a" - "600008026a074acbc63a58e7861e54ca24babf1cb800c5b694da25c3ae2b1543045053667" - "08a0616ab8262ee6f6fb30ffcab3e9e8261479c7469ce97010a70b3d3f962842c61a"; - - auto v = "26"; - auto r = "74acbc63a58e7861e54ca24babf1cb800c5b694da25c3ae2b154304505366708"; - auto s = "616ab8262ee6f6fb30ffcab3e9e8261479c7469ce97010a70b3d3f962842c61a"; - - ASSERT_EQ(hex(proto_output.encoded()), expectEncoded); - ASSERT_EQ(hex(proto_output.v()), v); - ASSERT_EQ(hex(proto_output.r()), r); - ASSERT_EQ(hex(proto_output.s()), s); -} -} // namespace TW::Harmony diff --git a/tests/Harmony/StakingTests.cpp b/tests/Harmony/StakingTests.cpp deleted file mode 100644 index 4f96ec0fa87..00000000000 --- a/tests/Harmony/StakingTests.cpp +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Base64.h" -#include "Coin.h" -#include "HDWallet.h" -#include "Harmony/Address.h" -#include "Harmony/Signer.h" -#include "HexCoding.h" -#include "proto/Harmony.pb.h" - -#include -#include - -namespace TW::Harmony { - -static Address TEST_ACCOUNT; -static bool testAccountDecodeResult = - Address::decode("one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9", TEST_ACCOUNT); - -static auto PRIVATE_KEY = - PrivateKey(parse_hex("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48")); - -TEST(HarmonyStaking, SignCreateValidator) { - auto input = Proto::SigningInput(); - input.set_private_key(PRIVATE_KEY.bytes.data(), PRIVATE_KEY.bytes.size()); - - auto value = store(uint256_t("0x2")); - input.set_chain_id(value.data(), value.size()); - - auto stakingMessage = input.mutable_staking_message(); - auto createValidatorMsg = stakingMessage->mutable_create_validator_message(); - - createValidatorMsg->set_validator_address(TEST_ACCOUNT.string()); - - auto description = createValidatorMsg->mutable_description(); - description->set_name("Alice"); - description->set_identity("alice"); - description->set_website("alice.harmony.one"); - description->set_security_contact("Bob"); - description->set_details("Don't mess with me!!!"); - auto commission = createValidatorMsg->mutable_commission_rates(); - - // (value, precision): (1, 1) represents 0.1 - value = store(uint256_t("1")); - commission->mutable_rate()->set_value(value.data(), value.size()); - value = store(uint256_t("1")); - commission->mutable_rate()->set_precision(value.data(), value.size()); - - // (value, precision): (9, 1) represents 0.9 - value = store(uint256_t("9")); - commission->mutable_max_rate()->set_value(value.data(), value.size()); - value = store(uint256_t("1")); - commission->mutable_max_rate()->set_precision(value.data(), value.size()); - - // (value, precision): (5, 2) represents 0.05 - value = store(uint256_t("5")); - commission->mutable_max_change_rate()->set_value(value.data(), value.size()); - value = store(uint256_t("2")); - commission->mutable_max_change_rate()->set_precision(value.data(), value.size()); - - value = store(uint256_t("10")); - createValidatorMsg->set_min_self_delegation(value.data(), value.size()); - - value = store(uint256_t("3000")); - createValidatorMsg->set_max_total_delegation(value.data(), value.size()); - - value = parse_hex("b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df" - "2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611"); - createValidatorMsg->add_slot_pub_keys(value.data(), value.size()); - - value = parse_hex("4252b0f1210efb0d5061e8a706a7ea9d62292a7947a975472f" - "b77e1af7278a1c3c2e6eeba73c0581ece398613829940df129" - "f3071c9a24b4b448bb1e880dc5872a58cb07eed94294c4e01a" - "5c864771cafef7b96be541cb3c521a98f01838dd94"); - createValidatorMsg->add_slot_key_sigs(value.data(), value.size()); - - value = store(uint256_t("100")); - createValidatorMsg->set_amount(value.data(), value.size()); - - value = store(uint256_t("0x02")); - stakingMessage->set_nonce(value.data(), value.size()); - - value = store(uint256_t("0x0")); - stakingMessage->set_gas_price(value.data(), value.size()); - - value = store(uint256_t("0x64")); - stakingMessage->set_gas_limit(value.data(), value.size()); - - auto proto_output = Signer::sign(input); - - auto expectEncoded = - "f9015280f9010894ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c696365916" - "16c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121dd" - "c988016345785d8a0000c9880c7d713b49da0000c887b1a2bc2ec500000a820bb8f1b0b9486167ab9087ab8" - "18dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611f862b860" - "4252b0f1210efb0d5061e8a706a7ea9d62292a7947a975472fb77e1af7278a1c3c2e6eeba73c0581ece3986" - "13829940df129f3071c9a24b4b448bb1e880dc5872a58cb07eed94294c4e01a5c864771cafef7b96be541cb" - "3c521a98f01838dd946402806428a00d8437f81be3481b01542e9baef0445f3758cf084c5e1fba93d087ccc" - "e084cb1a0404c1a42442c2d39f84582353a1c67012451ff83ef6d3622f684041df9bf0072"; - - auto v = "28"; - auto r = "0d8437f81be3481b01542e9baef0445f3758cf084c5e1fba93d087ccce084cb1"; - auto s = "404c1a42442c2d39f84582353a1c67012451ff83ef6d3622f684041df9bf0072"; - - ASSERT_EQ(hex(proto_output.encoded()), expectEncoded); - ASSERT_EQ(hex(proto_output.v()), v); - ASSERT_EQ(hex(proto_output.r()), r); - ASSERT_EQ(hex(proto_output.s()), s); -} - -TEST(HarmonyStaking, SignEditValidator) { - auto input = Proto::SigningInput(); - input.set_private_key(PRIVATE_KEY.bytes.data(), PRIVATE_KEY.bytes.size()); - - auto value = store(uint256_t("0x2")); - input.set_chain_id(value.data(), value.size()); - - auto stakingMessage = input.mutable_staking_message(); - auto editValidatorMsg = stakingMessage->mutable_edit_validator_message(); - - editValidatorMsg->set_validator_address(TEST_ACCOUNT.string()); - - auto description = editValidatorMsg->mutable_description(); - description->set_name("Alice"); - description->set_identity("alice"); - description->set_website("alice.harmony.one"); - description->set_security_contact("Bob"); - description->set_details("Don't mess with me!!!"); - - auto commissionRate = editValidatorMsg->mutable_commission_rate(); - - // (value, precision): (1, 1) represents 0.1 - value = store(uint256_t("1")); - commissionRate->set_value(value.data(), value.size()); - value = store(uint256_t("1")); - commissionRate->set_precision(value.data(), value.size()); - - value = store(uint256_t("10")); - editValidatorMsg->set_min_self_delegation(value.data(), value.size()); - - value = store(uint256_t("3000")); - editValidatorMsg->set_max_total_delegation(value.data(), value.size()); - - value = parse_hex("b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df" - "2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611"); - editValidatorMsg->set_slot_key_to_remove(value.data(), value.size()); - editValidatorMsg->set_slot_key_to_add(value.data(), value.size()); - - value = parse_hex("4252b0f1210efb0d5061e8a706a7ea9d62292a7947a975472f" - "b77e1af7278a1c3c2e6eeba73c0581ece398613829940df129" - "f3071c9a24b4b448bb1e880dc5872a58cb07eed94294c4e01a" - "5c864771cafef7b96be541cb3c521a98f01838dd94"); - editValidatorMsg->set_slot_key_to_add_sig(value.data(), value.size()); - - value = store(uint256_t("1")); - editValidatorMsg->set_active(value.data(), value.size()); - - value = store(uint256_t("0x02")); - stakingMessage->set_nonce(value.data(), value.size()); - - value = store(uint256_t("0x0")); - stakingMessage->set_gas_price(value.data(), value.size()); - - value = store(uint256_t("0x64")); // 0x5208 - stakingMessage->set_gas_limit(value.data(), value.size()); - - auto proto_output = Signer::sign(input); - - auto expectEncoded = - "f9016c01f9012294ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121c988016345785d8a00000a820bb8b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611b8604252b0f1210efb0d5061e8a706a7ea9d62292a7947a975472fb77e1af7278a1c3c2e6eeba73c0581ece398613829940df129f3071c9a24b4b448bb1e880dc5872a58cb07eed94294c4e01a5c864771cafef7b96be541cb3c521a98f01838dd940102806427a089d6f87855619c31e933d5f00638ca58737dfffdfdf8b66a048a2e45f103e05da04aafc5c51a95412760c089371b411a5ab8f235b456291a9754d544b162df4eef"; - - auto v = "27"; - auto r = "89d6f87855619c31e933d5f00638ca58737dfffdfdf8b66a048a2e45f103e05d"; - auto s = "4aafc5c51a95412760c089371b411a5ab8f235b456291a9754d544b162df4eef"; - - ASSERT_EQ(hex(proto_output.encoded()), expectEncoded); - ASSERT_EQ(hex(proto_output.v()), v); - ASSERT_EQ(hex(proto_output.r()), r); - ASSERT_EQ(hex(proto_output.s()), s); -} - -TEST(HarmonyStaking, SignDelegate) { - auto input = Proto::SigningInput(); - input.set_private_key(PRIVATE_KEY.bytes.data(), PRIVATE_KEY.bytes.size()); - - auto value = store(uint256_t("0x2")); - input.set_chain_id(value.data(), value.size()); - - auto stakingMessage = input.mutable_staking_message(); - auto delegateMsg = stakingMessage->mutable_delegate_message(); - delegateMsg->set_delegator_address(TEST_ACCOUNT.string()); - delegateMsg->set_validator_address(TEST_ACCOUNT.string()); - - value = store(uint256_t("0xa")); - delegateMsg->set_amount(value.data(), value.size()); - - value = store(uint256_t("0x02")); - stakingMessage->set_nonce(value.data(), value.size()); - - value = store(uint256_t("0x0")); - stakingMessage->set_gas_price(value.data(), value.size()); - - value = store(uint256_t("0x64")); - stakingMessage->set_gas_limit(value.data(), value.size()); - - auto proto_output = Signer::sign(input); - - auto expectEncoded = - "f87302eb94ebcd16e8c1d8f493ba04e99a56474122d81a9c5894ebcd16e8c1d8f493ba04e99a56474122d81a" - "9c580a02806428a0ada9a8fb49eb3cd74f0f861e16bc1f1d56a0c6e3c25b0391f9e07a7963317e80a05c28dbc4" - "1763dc2391263e1aae30f842f90734d7ec68cee2352af0d4b0662b54"; - - auto v = "28"; - auto r = "ada9a8fb49eb3cd74f0f861e16bc1f1d56a0c6e3c25b0391f9e07a7963317e80"; - auto s = "5c28dbc41763dc2391263e1aae30f842f90734d7ec68cee2352af0d4b0662b54"; - - ASSERT_EQ(hex(proto_output.encoded()), expectEncoded); - ASSERT_EQ(hex(proto_output.v()), v); - ASSERT_EQ(hex(proto_output.r()), r); - ASSERT_EQ(hex(proto_output.s()), s); -} - -TEST(HarmonyStaking, SignUndelegate) { - auto input = Proto::SigningInput(); - input.set_private_key(PRIVATE_KEY.bytes.data(), PRIVATE_KEY.bytes.size()); - - auto value = store(uint256_t("0x2")); - input.set_chain_id(value.data(), value.size()); - - auto stakingMessage = input.mutable_staking_message(); - auto undelegateMsg = stakingMessage->mutable_undelegate_message(); - undelegateMsg->set_delegator_address(TEST_ACCOUNT.string()); - undelegateMsg->set_validator_address(TEST_ACCOUNT.string()); - - value = store(uint256_t("0xa")); - undelegateMsg->set_amount(value.data(), value.size()); - - value = store(uint256_t("0x02")); - stakingMessage->set_nonce(value.data(), value.size()); - - value = store(uint256_t("0x0")); - stakingMessage->set_gas_price(value.data(), value.size()); - - value = store(uint256_t("0x64")); - stakingMessage->set_gas_limit(value.data(), value.size()); - - auto proto_output = Signer::sign(input); - - auto expectEncoded = - "f87303eb94ebcd16e8c1d8f493ba04e99a56474122d81a9c5894ebcd16e8c1d8f493ba04e99a56474122d81a9c" - "580a02806428a05bf8c653567defe2c3728732bc9d67dd099a977df91c740a883fd89e03abb6e2a05202c4b516" - "52d5144c6a30d14d1a7a316b5a4a6b49be985b4bc6980e49f7acb7"; - - auto v = "28"; - auto r = "5bf8c653567defe2c3728732bc9d67dd099a977df91c740a883fd89e03abb6e2"; - auto s = "5202c4b51652d5144c6a30d14d1a7a316b5a4a6b49be985b4bc6980e49f7acb7"; - - ASSERT_EQ(hex(proto_output.encoded()), expectEncoded); - ASSERT_EQ(hex(proto_output.v()), v); - ASSERT_EQ(hex(proto_output.r()), r); - ASSERT_EQ(hex(proto_output.s()), s); -} - -TEST(HarmonyStaking, SignCollectRewards) { - auto input = Proto::SigningInput(); - input.set_private_key(PRIVATE_KEY.bytes.data(), PRIVATE_KEY.bytes.size()); - - auto value = store(uint256_t("0x2")); - input.set_chain_id(value.data(), value.size()); - - auto stakingMessage = input.mutable_staking_message(); - auto collectRewardsMsg = stakingMessage->mutable_collect_rewards(); - collectRewardsMsg->set_delegator_address(TEST_ACCOUNT.string()); - - value = store(uint256_t("0x02")); - stakingMessage->set_nonce(value.data(), value.size()); - - value = store(uint256_t("0x0")); - stakingMessage->set_gas_price(value.data(), value.size()); - - value = store(uint256_t("0x64")); - stakingMessage->set_gas_limit(value.data(), value.size()); - - auto proto_output = Signer::sign(input); - - auto expectEncoded = "f85d04d594ebcd16e8c1d8f493ba04e99a56474122d81a9c5802806428a04c15c72f425" - "77001083a9c7ff9d9724077aec704a524e53dc7c9afe97ca4e625a055c13ea17c3efd1cd9" - "1f2988c7e7673950bac5a08c174f2d0af27a82039f1e3d"; - - auto v = "28"; - auto r = "4c15c72f42577001083a9c7ff9d9724077aec704a524e53dc7c9afe97ca4e625"; - auto s = "55c13ea17c3efd1cd91f2988c7e7673950bac5a08c174f2d0af27a82039f1e3d"; - - ASSERT_EQ(hex(proto_output.encoded()), expectEncoded); - ASSERT_EQ(hex(proto_output.v()), v); - ASSERT_EQ(hex(proto_output.r()), r); - ASSERT_EQ(hex(proto_output.s()), s); -} - -} // namespace TW::Harmony diff --git a/tests/Harmony/TWAnyAddressTests.cpp b/tests/Harmony/TWAnyAddressTests.cpp deleted file mode 100644 index 759eef52c84..00000000000 --- a/tests/Harmony/TWAnyAddressTests.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" - -#include -#include - -#include - -TEST(HarmonyAnyAddress, Harmony) { - auto string = STRING("one1c8dpswxg2p50znzecnq0peuxlxtcm9je7q7yje"); - auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeHarmony)); - auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); - - EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); - - auto keyHash = WRAPD(TWAnyAddressData(addr.get())); - assertHexEqual(keyHash, "c1da1838c85068f14c59c4c0f0e786f9978d9659"); -} diff --git a/tests/Harmony/TWAnySignerTests.cpp b/tests/Harmony/TWAnySignerTests.cpp deleted file mode 100644 index 08415e5356a..00000000000 --- a/tests/Harmony/TWAnySignerTests.cpp +++ /dev/null @@ -1,74 +0,0 @@ - -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "../interface/TWTestUtilities.h" -#include "proto/Harmony.pb.h" -#include "uint256.h" -#include - -#include - -using namespace TW; -using namespace Harmony; - -static auto TEST_RECEIVER = "one129r9pj3sk0re76f7zs3qz92rggmdgjhtwge62k"; - -static uint256_t LOCAL_NET = 0x2; - -TEST(TWAnySignerHarmony, Sign) { - Proto::SigningInput input; - - auto transactionMessage = input.mutable_transaction_message(); - transactionMessage->set_to_address(TEST_RECEIVER); - const auto privateKey = parse_hex("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48"); - - input.set_private_key(privateKey.data(), privateKey.size()); - - auto value = store(LOCAL_NET); - input.set_chain_id(value.data(), value.size()); - - value = store(uint256_t("0x1")); - transactionMessage->set_nonce(value.data(), value.size()); - - value = store(uint256_t("")); - transactionMessage->set_gas_price(value.data(), value.size()); - - value = store(uint256_t("0x5208")); - transactionMessage->set_gas_limit(value.data(), value.size()); - - value = store(uint256_t("0x1")); - transactionMessage->set_from_shard_id(value.data(), value.size()); - - value = store(uint256_t("0x0")); - transactionMessage->set_to_shard_id(value.data(), value.size()); - - value = store(uint256_t("0x6bfc8da5ee8220000")); - transactionMessage->set_amount(value.data(), value.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeHarmony); - - auto shouldBeV = "28"; - auto shouldBeR = "84cc200aab11f5e1b2f7ece0d56ec67385ac50cefb6e3dc2a2f3e036ed575a5c"; - auto shouldBeS = "643f18005b790cac8d8e7dc90e6147df0b83874b52db198864694ea28a79e6fc"; - - ASSERT_EQ(hex(output.v()), shouldBeV); - ASSERT_EQ(hex(output.r()), shouldBeR); - ASSERT_EQ(hex(output.s()), shouldBeS); - - ASSERT_EQ(hex(output.encoded()), "f86a0180825208018094514650ca30b3c79f693e14220115434236d44aeb8906bfc8da5ee82200008028a084cc200aab11f5e1b2f7ece0d56ec67385ac50cefb6e3dc2a2f3e036ed575a5ca0643f18005b790cac8d8e7dc90e6147df0b83874b52db198864694ea28a79e6fc"); -} - -TEST(TWAnySignerHarmony, SignJSON) { - auto json = STRING(R"({"chainId":"Ag==","transactionMessage":{"nonce":"AQ==","gasPrice":"AA==","gasLimit":"Ugg=","toAddress":"one129r9pj3sk0re76f7zs3qz92rggmdgjhtwge62k","amount":"Br/I2l7oIgAA","fromShardId":"AQ==","toShardId":"AA=="}})"); - auto key = DATA("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48"); - auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeHarmony)); - - ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeHarmony)); - assertStringsEqual(result, "f86a0180825208018094514650ca30b3c79f693e14220115434236d44aeb8906bfc8da5ee82200008028a084cc200aab11f5e1b2f7ece0d56ec67385ac50cefb6e3dc2a2f3e036ed575a5ca0643f18005b790cac8d8e7dc90e6147df0b83874b52db198864694ea28a79e6fc"); -} diff --git a/tests/Harmony/TWCoinTypeTests.cpp b/tests/Harmony/TWCoinTypeTests.cpp deleted file mode 100644 index 5541e8ca94f..00000000000 --- a/tests/Harmony/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWHarmonyCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeHarmony)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeHarmony, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeHarmony, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeHarmony)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeHarmony)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeHarmony), 18); - ASSERT_EQ(TWBlockchainHarmony, TWCoinTypeBlockchain(TWCoinTypeHarmony)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeHarmony)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeHarmony)); - assertStringsEqual(symbol, "ONE"); - assertStringsEqual(txUrl, "https://explorer.harmony.one/#/tx/t123"); - assertStringsEqual(accUrl, "https://explorer.harmony.one/#/address/a12"); - assertStringsEqual(id, "harmony"); - assertStringsEqual(name, "Harmony"); -} diff --git a/tests/HexCodingTests.cpp b/tests/HexCodingTests.cpp deleted file mode 100644 index 57a40717fed..00000000000 --- a/tests/HexCodingTests.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "Data.h" -#include "uint256.h" -#include - -namespace TW { - -TEST(HexCoding, validation) { - const std::string valid = "0x7d8bf18c7ce84b3e175b339c4ca93aed1dd166f1"; - const std::string invalid = "0xMQqpqMQgCBuiPkoXfgZZsJvuzCeI1zc00z6vHJj4"; - const auto bytes = parse_hex(invalid); - const auto bytes2 = parse_hex(valid); - - ASSERT_TRUE(bytes.empty()); - ASSERT_EQ("0x" + hex(bytes2), valid); -} - -TEST(HexCoding, OddLength) { - const std::string oddHex = "0x28fa6ae00"; - const auto bytes = parse_hex(oddHex, true); - const auto number = load(bytes); - ASSERT_EQ(number, 11000000000); -} - -} diff --git a/tests/ICON/TWCoinTypeTests.cpp b/tests/ICON/TWCoinTypeTests.cpp deleted file mode 100644 index bffbd0459f0..00000000000 --- a/tests/ICON/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWICONCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeICON)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeICON, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeICON, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeICON)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeICON)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeICON), 18); - ASSERT_EQ(TWBlockchainIcon, TWCoinTypeBlockchain(TWCoinTypeICON)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeICON)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeICON)); - assertStringsEqual(symbol, "ICX"); - assertStringsEqual(txUrl, "https://tracker.icon.foundation/transaction/t123"); - assertStringsEqual(accUrl, "https://tracker.icon.foundation/address/a12"); - assertStringsEqual(id, "icon"); - assertStringsEqual(name, "ICON"); -} diff --git a/tests/Icon/AddressTests.cpp b/tests/Icon/AddressTests.cpp deleted file mode 100644 index f5fbc583a2c..00000000000 --- a/tests/Icon/AddressTests.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Icon/Address.h" -#include "HexCoding.h" -#include "PrivateKey.h" - -#include - -using namespace TW; -using namespace TW::Icon; - -TEST(IconAddress, Validation) { - ASSERT_TRUE(Address::isValid("cx116f042497e5f34268b1b91e742680f84cf4e9f3")); - ASSERT_TRUE(Address::isValid("hx116f042497e5f34268b1b91e742680f84cf4e9f3")); - - ASSERT_FALSE(Address::isValid("abc")); - ASSERT_FALSE(Address::isValid("dshadghasdghsadadsadjsad")); - ASSERT_FALSE(Address::isValid("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")); -} - -TEST(IconAddress, String) { - const auto address = Address("hx116f042497e5f34268b1b91e742680f84cf4e9f3"); - ASSERT_EQ(address.string(), "hx116f042497e5f34268b1b91e742680f84cf4e9f3"); - - const auto address2 = Address("cx116f042497e5f34268b1b91e742680f84cf4e9f3"); - ASSERT_EQ(address2.string(), "cx116f042497e5f34268b1b91e742680f84cf4e9f3"); -} - -TEST(IconAddress, FromPrivateKey) { - const auto privateKey = PrivateKey(parse_hex("94d1a980d5e528067d44bf8a60d646f556e40ca71e17cd4ead2d56f89e4bd20f")); - const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); - const auto address = Address(publicKey, TypeAddress); - - ASSERT_EQ(address.string(), "hx98c0832ca5bd8e8bf355ca9491888aa9725c2c48"); -} diff --git a/tests/Icon/TWAnySignerTests.cpp b/tests/Icon/TWAnySignerTests.cpp deleted file mode 100644 index a1b6e5ce289..00000000000 --- a/tests/Icon/TWAnySignerTests.cpp +++ /dev/null @@ -1,48 +0,0 @@ - -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" -#include -#include "Data.h" -#include "HexCoding.h" -#include "uint256.h" -#include "proto/Icon.pb.h" - -#include - -using namespace TW; -using namespace TW::Icon; - -TEST(TWAnySignerIcon, Sign) { - auto key = parse_hex("2d42994b2f7735bbc93a3e64381864d06747e574aa94655c516f9ad0a74eed79"); - auto input = Proto::SigningInput(); - - input.set_from_address("hxbe258ceb872e08851f1f59694dac2558708ece11"); - input.set_to_address("hx5bfdb090f43a808005ffc27c25b213145e80b7cd"); - - auto value = uint256_t(1000000000000000000); - auto valueData = store(value); - input.set_value(valueData.data(), valueData.size()); - - auto stepLimit = uint256_t("74565"); - auto stepLimitData = store(stepLimit); - input.set_step_limit(stepLimitData.data(), stepLimitData.size()); - - auto one = uint256_t("01"); - auto oneData = store(one); - input.set_network_id(oneData.data(), oneData.size()); - input.set_nonce(oneData.data(), oneData.size()); - - input.set_timestamp(1516942975500598); - input.set_private_key(key.data(), key.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeICON); - - auto expected = std::string("{\"from\":\"hxbe258ceb872e08851f1f59694dac2558708ece11\",\"nid\":\"0x1\",\"nonce\":\"0x1\",\"signature\":\"xR6wKs+IA+7E91bT8966jFKlK5mayutXCvayuSMCrx9KB7670CsWa0B7LQzgsxU0GLXaovlAT2MLs1XuDiSaZQE=\",\"stepLimit\":\"0x12345\",\"timestamp\":\"0x563a6cf330136\",\"to\":\"hx5bfdb090f43a808005ffc27c25b213145e80b7cd\",\"value\":\"0xde0b6b3a7640000\",\"version\":\"0x3\"}"); - ASSERT_EQ(output.encoded(), expected); -} diff --git a/tests/IoTeX/AddressTests.cpp b/tests/IoTeX/AddressTests.cpp deleted file mode 100644 index 60616bf1002..00000000000 --- a/tests/IoTeX/AddressTests.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include - -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" - -#include "IoTeX/Address.h" - -namespace TW::IoTeX { - -TEST(IoTeXAddress, Invalid) { - ASSERT_FALSE(Address::isValid("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8")); - ASSERT_FALSE(Address::isValid("io187wzp08vnhjpkydnr97qlh8kh0dpkkytfam8j")); - ASSERT_FALSE(Address::isValid("it187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j")); - ASSERT_FALSE(Address::isValid("ko187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j")); - ASSERT_FALSE(Address::isValid("io187wzp18vnhjjpkydnr97qlh8kh0dpkkytfam8j")); - ASSERT_FALSE(Address::isValid("io187wzp08vnhjbpkydnr97qlh8kh0dpkkytfam8j")); - ASSERT_FALSE(Address::isValid("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8k")); -} - -TEST(IoTeXAddress, Valid) { - ASSERT_TRUE(Address::isValid("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j")); -} - -TEST(IoTeXAddress, FromString) { - Address address; - ASSERT_TRUE(Address::decode("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j", address)); - ASSERT_EQ("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j", address.string()); - - ASSERT_FALSE(Address::decode("io187wzp08vnhjbpkydnr97qlh8kh0dpkkytfam8j", address)); -} - -TEST(IoTeXAddress, FromPrivateKey) { - const auto privateKey = PrivateKey(parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f")); - const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); - const auto address = Address(publicKey); - ASSERT_EQ(address.string(), "io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); - - EXPECT_THROW({ - try - { - const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const auto address = Address(publicKey); - } - catch( const std::invalid_argument& e ) - { - EXPECT_STREQ("address may only be an extended SECP256k1 public key", e.what()); - throw; - } - }, std::invalid_argument); -} - -TEST(IoTeXAddress, FromKeyHash) { - const auto keyHash = parse_hex("3f9c20bcec9de520d88d98cbe07ee7b5ded0dac4"); - const auto address = Address(keyHash); - ASSERT_EQ(address.string(), "io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); - - EXPECT_THROW({ - try - { - const auto keyHash = parse_hex("3f9c20bcec9de520d88d98cbe07ee7b5ded0da"); - const auto address = Address(keyHash); - } - catch( const std::invalid_argument& e ) - { - EXPECT_STREQ("invalid address data", e.what()); - throw; - } - }, std::invalid_argument); -} - -} // namespace TW::IoTeX diff --git a/tests/IoTeX/SignerTests.cpp b/tests/IoTeX/SignerTests.cpp deleted file mode 100644 index 43a5c5ee22f..00000000000 --- a/tests/IoTeX/SignerTests.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include - -#include "HexCoding.h" - -#include "IoTeX/Address.h" -#include "IoTeX/Signer.h" -#include "proto/IoTeX.pb.h" - -namespace TW::IoTeX { - -TEST(IoTeXSigner, Sign) { - auto input = Proto::SigningInput(); - input.set_version(1); - input.set_nonce(123); - input.set_gaslimit(888); - input.set_gasprice("999"); - auto key = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); - input.set_privatekey(key.data(), key.size()); - auto tsf = input.mutable_transfer(); - tsf->set_amount("456"); - tsf->set_recipient("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); - auto text = parse_hex("68656c6c6f20776f726c6421"); // "hello world!" - tsf->set_payload(text.data(), text.size()); - - auto signer = IoTeX::Signer(std::move(input)); - auto h = signer.hash(); - ASSERT_EQ(hex(h.begin(), h.end()), "0f17cd7f43bdbeff73dfe8f5cb0c0045f2990884e5050841de887cf22ca35b50"); - auto sig = signer.sign(); - ASSERT_EQ(hex(sig.begin(), sig.end()), "555cc8af4181bf85c044c3201462eeeb95374f78aa48c67b87510ee63d5e502372e53082f03e9a11c1e351de539cedf85d8dff87de9d003cb9f92243541541a000"); -} - -TEST(IoTeXSigner, Build) { - auto input = Proto::SigningInput(); - input.set_version(1); - input.set_nonce(123); - input.set_gaslimit(888); - input.set_gasprice("999"); - auto keyhex = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); - input.set_privatekey(keyhex.data(), keyhex.size()); - auto tsf = input.mutable_transfer(); - tsf->set_amount("456"); - tsf->set_recipient("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); - auto text = parse_hex("68656c6c6f20776f726c6421"); // "hello world!" - tsf->set_payload(text.data(), text.size()); - - auto signer = IoTeX::Signer(std::move(input)); - auto output = signer.build(); - auto encoded = output.encoded(); // signed action's serialized bytes - ASSERT_EQ(hex(encoded.begin(), encoded.end()), "0a4c0801107b18f8062203393939523e0a033435361229696f313837777a703038766e686a6a706b79646e723937716c68386b683064706b6b797466616d386a1a0c68656c6c6f20776f726c64211241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a41555cc8af4181bf85c044c3201462eeeb95374f78aa48c67b87510ee63d5e502372e53082f03e9a11c1e351de539cedf85d8dff87de9d003cb9f92243541541a000"); - auto h = output.hash(); // signed action's hash - ASSERT_EQ(hex(h.begin(), h.end()), "6c84ac119058e859a015221f87a4e187c393d0c6ee283959342eac95fad08c33"); -} - -} // namespace TW::IoTeX diff --git a/tests/IoTeX/StakingTests.cpp b/tests/IoTeX/StakingTests.cpp deleted file mode 100644 index fd0e074d312..00000000000 --- a/tests/IoTeX/StakingTests.cpp +++ /dev/null @@ -1,334 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Data.h" -#include "HexCoding.h" -#include "IoTeX/Signer.h" -#include "IoTeX/Staking.h" -#include "PrivateKey.h" -#include "proto/IoTeX.pb.h" -#include "../interface/TWTestUtilities.h" -#include -#include - -using namespace TW; -using namespace TW::IoTeX; - -TEST(TWIoTeXStaking, Create) { - std::string IOTEX_STAKING_CANDIDATE = "io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj"; - std::string IOTEX_STAKING_PAYLOAD = "payload"; - std::string IOTEX_STAKING_AMOUNT = "100"; - Data candidate(IOTEX_STAKING_CANDIDATE.begin(), IOTEX_STAKING_CANDIDATE.end()); - Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); - Data amount(IOTEX_STAKING_AMOUNT.begin(), IOTEX_STAKING_AMOUNT.end()); - - auto stake = stakingCreate(candidate, amount, 10000, true, payload); - ASSERT_EQ(hex(stake), "0a29696f313964307033616834673877773964376b63786671383779786537666e723872" - "7074683573686a120331303018904e20012a077061796c6f6164"); -} - -TEST(TWIoTeXStaking, AddDeposit) { - std::string IOTEX_STAKING_PAYLOAD = "payload"; - std::string IOTEX_STAKING_AMOUNT = "10"; - Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); - Data amount(IOTEX_STAKING_AMOUNT.begin(), IOTEX_STAKING_AMOUNT.end()); - - auto stake = stakingAddDeposit(10, amount, payload); - - ASSERT_EQ(hex(stake), "080a120231301a077061796c6f6164"); -} - -TEST(TWIoTeXStaking, Unstake) { - std::string IOTEX_STAKING_PAYLOAD = "payload"; - Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); - - auto stake = stakingUnstake(10, payload); - - ASSERT_EQ(hex(stake), "080a12077061796c6f6164"); -} - -TEST(TWIoTeXStaking, Withdraw) { - std::string IOTEX_STAKING_PAYLOAD = "payload"; - Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); - - auto stake = stakingWithdraw(10, payload); - - ASSERT_EQ(hex(stake), "080a12077061796c6f6164"); -} - -TEST(TWIoTeXStaking, Restake) { - std::string IOTEX_STAKING_PAYLOAD = "payload"; - Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); - - auto stake = stakingRestake(10, 1000, true, payload); - - ASSERT_EQ(hex(stake), "080a10e807180122077061796c6f6164"); -} - -TEST(TWIoTeXStaking, ChangeCandidate) { - std::string IOTEX_STAKING_CANDIDATE = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza"; - std::string IOTEX_STAKING_PAYLOAD = "payload"; - Data candidate(IOTEX_STAKING_CANDIDATE.begin(), IOTEX_STAKING_CANDIDATE.end()); - Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); - - auto stake = stakingChangeCandidate(10, candidate, payload); - - ASSERT_EQ(hex(stake), "080a1229696f3178707136326177383575717a72636367397935686e727976386c" - "64326e6b7079636333677a611a077061796c6f6164"); -} - -TEST(TWIoTeXStaking, Transfer) { - std::string IOTEX_STAKING_CANDIDATE = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza"; - std::string IOTEX_STAKING_PAYLOAD = "payload"; - Data candidate(IOTEX_STAKING_CANDIDATE.begin(), IOTEX_STAKING_CANDIDATE.end()); - Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); - - auto stake = stakingTransfer(10, candidate, payload); - - ASSERT_EQ(hex(stake), "080a1229696f3178707136326177383575717a72636367397935686e727976386c6432" - "6e6b7079636333677a611a077061796c6f6164"); -} - -TEST(TWIoTeXStaking, CandidateRegister) { - std::string IOTEX_STAKING_NAME = "test"; - std::string IOTEX_STAKING_OPERATOR = "io10a298zmzvrt4guq79a9f4x7qedj59y7ery84he"; - std::string IOTEX_STAKING_REWARD = "io13sj9mzpewn25ymheukte4v39hvjdtrfp00mlyv"; - std::string IOTEX_STAKING_OWNER = "io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj"; - std::string IOTEX_STAKING_AMOUNT = "100"; - std::string IOTEX_STAKING_PAYLOAD = "payload"; - Data name(IOTEX_STAKING_NAME.begin(), IOTEX_STAKING_NAME.end()); - Data operatorAddress(IOTEX_STAKING_OPERATOR.begin(), IOTEX_STAKING_OPERATOR.end()); - Data reward(IOTEX_STAKING_REWARD.begin(), IOTEX_STAKING_REWARD.end()); - Data amount(IOTEX_STAKING_AMOUNT.begin(), IOTEX_STAKING_AMOUNT.end()); - Data owner(IOTEX_STAKING_OWNER.begin(), IOTEX_STAKING_OWNER.end()); - Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); - - auto stake = - candidateRegister(name, operatorAddress, reward, amount, 10000, false, owner, payload); - - ASSERT_EQ(hex(stake), - "0a5c0a04746573741229696f3130613239387a6d7a7672743467757137396139663478377165646a3539" - "7937657279383468651a29696f3133736a396d7a7065776e3235796d6865756b74653476333968766a64" - "7472667030306d6c7976120331303018904e2a29696f313964307033616834673877773964376b637866" - "71383779786537666e7238727074683573686a32077061796c6f6164"); -} - -TEST(TWIoTeXStaking, CandidateUpdate) { - std::string IOTEX_STAKING_NAME = "test"; - std::string IOTEX_STAKING_OPERATOR = "io1cl6rl2ev5dfa988qmgzg2x4hfazmp9vn2g66ng"; - std::string IOTEX_STAKING_REWARD = "io1juvx5g063eu4ts832nukp4vgcwk2gnc5cu9ayd"; - Data name(IOTEX_STAKING_NAME.begin(), IOTEX_STAKING_NAME.end()); - Data operatorAddress(IOTEX_STAKING_OPERATOR.begin(), IOTEX_STAKING_OPERATOR.end()); - Data reward(IOTEX_STAKING_REWARD.begin(), IOTEX_STAKING_REWARD.end()); - - auto stake = candidateUpdate(name, operatorAddress, reward); - - ASSERT_EQ(hex(stake), "0a04746573741229696f31636c36726c32657635646661393838716d677a6732783468" - "66617a6d7039766e326736366e671a29696f316a757678356730363365753474733833" - "326e756b7034766763776b32676e6335637539617964"); -} - -Proto::SigningInput createSigningInput() -{ - auto keyhex = parse_hex("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"); - auto input = Proto::SigningInput(); - input.set_version(1); - input.set_nonce(0); - input.set_gaslimit(1000000); - input.set_gasprice("10"); - input.set_privatekey(keyhex.data(), keyhex.size()); - return input; -} - -TEST(TWIoTeXStaking, SignAll) { - { // sign stakecreate - auto input = createSigningInput(); - Proto::SigningOutput output; - auto& action = *input.mutable_stakecreate(); - action.set_candidatename("io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj"); - action.set_stakedamount("100"); - action.set_stakedduration(10000); - action.set_autostake(true); - action.set_payload("payload"); - ANY_SIGN(input, TWCoinTypeIoTeX); - ASSERT_EQ(hex(output.encoded()), - "0a4b080118c0843d22023130c2023e0a29696f313964307033616834673877773964376b63786671" - "3837797865" - "37666e7238727074683573686a120331303018904e20012a077061796c6f6164124104755ce6d890" - "3f6b3793bd" - "db4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c" - "0bc76ef30d" - "d6a1038ed9da8daf331a412e8bac421bab88dcd99c26ac8ffbf27f11ee57a41e7d2537891bfed5ae" - "d8e2e026d4" - "6e55d1b856787bc1cd7c1216a6e2534c5b5d1097c3afe8e657aa27cbbb0801"); - // signed action's hash - ASSERT_EQ(hex(output.hash()), - "f1785e47b4200c752bb6518bd18097a41e075438b8c18c9cb00e1ae2f38ce767"); - } - { // sign stakeadddeposit - auto input = createSigningInput(); - Proto::SigningOutput output; - auto& action = *input.mutable_stakeadddeposit(); - action.set_bucketindex(10); - action.set_amount("10"); - action.set_payload("payload"); - ANY_SIGN(input, TWCoinTypeIoTeX); - ASSERT_EQ( - hex(output.encoded()), - "0a1c080118c0843d22023130da020f080a120231301a077061796c6f6164124104755ce6d8903f6b3793" - "bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0b" - "c76ef30dd6a1038ed9da8daf331a41a48ab1feba8181d760de946aefed7d815a89fd9b1ab503d2392bb5" - "5e1bb75eec42dddc8bd642f89accc3a37b3cf15a103a95d66695fdf0647b202869fdd66bcb01"); - // signed action's hash - ASSERT_EQ(hex(output.hash()), - "ca8937d6f224a4e4bf93cb5605581de2d26fb0481e1dfc1eef384ee7ccf94b73"); - } - { // sign stakeunstake - auto input = createSigningInput(); - Proto::SigningOutput output; - auto& action = *input.mutable_stakeunstake(); - action.set_bucketindex(10); - action.set_payload("payload"); - ANY_SIGN(input, TWCoinTypeIoTeX); - ASSERT_EQ( - hex(output.encoded()), - "0a18080118c0843d22023130ca020b080a12077061796c6f6164124104755ce6d8903f6b3793bddb4ea5" - "d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30d" - "d6a1038ed9da8daf331a4100adee39b48e1d3dbbd65298a57c7889709fc4df39987130da306f6997374a" - "184b7e7c232a42f21e89b06e6e7ceab81303c6b7483152d08d19ac829b22eb81e601"); - // signed action's hash - ASSERT_EQ(hex(output.hash()), - "bed58b64a6c4e959eca60a86f0b2149ce0e1dd527ac5fd26aef725ebf7c22a7d"); - } - { // sign stakewithdraw - auto input = createSigningInput(); - Proto::SigningOutput output; - auto& action = *input.mutable_stakewithdraw(); - action.set_bucketindex(10); - action.set_payload("payload"); - ANY_SIGN(input, TWCoinTypeIoTeX); - ASSERT_EQ( - hex(output.encoded()), - "0a18080118c0843d22023130d2020b080a12077061796c6f6164124104755ce6d8903f6b3793bddb4ea5" - "d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30d" - "d6a1038ed9da8daf331a4152644d102186be6640d46b517331f3402e24424b0d85129595421d28503d75" - "340b2922f5a0d4f667bbd6f576d9816770286b2ce032ba22eaec3952e24da4756b00"); - // signed action's hash - ASSERT_EQ(hex(output.hash()), - "28049348cf34f1aa927caa250e7a1b08778c44efaf73b565b6fa9abe843871b4"); - } - { // sign stakerestake - auto input = createSigningInput(); - Proto::SigningOutput output; - auto& action = *input.mutable_stakerestake(); - action.set_bucketindex(10); - action.set_stakedduration(1000); - action.set_autostake(true); - action.set_payload("payload"); - ANY_SIGN(input, TWCoinTypeIoTeX); - ASSERT_EQ( - hex(output.encoded()), - "0a1d080118c0843d22023130e20210080a10e807180122077061796c6f6164124104755ce6d8903f6b37" - "93bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c" - "0bc76ef30dd6a1038ed9da8daf331a41e2e763aed5b1fd1a8601de0f0ae34eb05162e34b0389ae3418ee" - "dbf762f64959634a968313a6516dba3a97b34efba4753bbed3a33d409ecbd45ac75007cd8e9101"); - // signed action's hash - ASSERT_EQ(hex(output.hash()), - "8816e8f784a1fce40b54d1cd172bb6976fd9552f1570c73d1d9fcdc5635424a9"); - } - { // sign stakechangecandidate - auto input = createSigningInput(); - Proto::SigningOutput output; - auto& action = *input.mutable_stakechangecandidate(); - action.set_bucketindex(10); - action.set_candidatename("io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza"); - action.set_payload("payload"); - ANY_SIGN(input, TWCoinTypeIoTeX); - ASSERT_EQ( - hex(output.encoded()), - "0a43080118c0843d22023130ea0236080a1229696f3178707136326177383575717a7263636739793568" - "6e727976386c64326e6b7079636333677a611a077061796c6f6164124104755ce6d8903f6b3793bddb4e" - "a5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef3" - "0dd6a1038ed9da8daf331a41d519eb3747163b945b862989b7e82a7f8468001e9683757cb88d5ddd95f8" - "1895047429e858bd48f7d59a88bfec92de231d216293aeba1e4fbe11461d9c9fc99801"); - // signed action's hash - ASSERT_EQ(hex(output.hash()), - "186526b5b9fe74e25beb52c83c41780a69108160bef2ddaf3bffb9f1f1e5e73a"); - } - { // sign staketransfer - auto input = createSigningInput(); - Proto::SigningOutput output; - auto& action = *input.mutable_staketransferownership(); - action.set_bucketindex(10); - action.set_voteraddress("io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza"); - action.set_payload("payload"); - ANY_SIGN(input, TWCoinTypeIoTeX); - ASSERT_EQ( - hex(output.encoded()), - "0a43080118c0843d22023130f20236080a1229696f3178707136326177383575717a7263636739793568" - "6e727976386c64326e6b7079636333677a611a077061796c6f6164124104755ce6d8903f6b3793bddb4e" - "a5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef3" - "0dd6a1038ed9da8daf331a41fa26db427ab87a56a129196c1604f2e22c4dd2a1f99b2217bc916260757d" - "00093d9e6dccdf53e3b0b64e41a69d71c238fbf9281625164694a74dfbeba075d0ce01"); - // signed action's hash - ASSERT_EQ(hex(output.hash()), - "74b2e1d6a09ba5d1298fa422d5850991ae516865077282196295a38f93c78b85"); - } - { // sign candidateupdate - auto input = createSigningInput(); - Proto::SigningOutput output; - auto& action = *input.mutable_candidateupdate(); - action.set_name("test"); - action.set_operatoraddress("io1cl6rl2ev5dfa988qmgzg2x4hfazmp9vn2g66ng"); - action.set_rewardaddress("io1juvx5g063eu4ts832nukp4vgcwk2gnc5cu9ayd"); - ANY_SIGN(input, TWCoinTypeIoTeX); - ASSERT_EQ( - hex(output.encoded()), - "0a69080118c0843d2202313082035c0a04746573741229696f31636c36726c3265763564666139383871" - "6d677a673278346866617a6d7039766e326736366e671a29696f316a7576783567303633657534747338" - "33326e756b7034766763776b32676e6335637539617964124104755ce6d8903f6b3793bddb4ea5d3589d" - "637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a103" - "8ed9da8daf331a4101885c9c6684a4a8f2f5bf11f8326f27be48658f292e8f55ec8a11a604bb0c563a11" - "ebf12d995ca1c152e00f8e0f0edf288db711aa10dbdfd5b7d73b4a28e1f701"); - // signed action's hash - ASSERT_EQ(hex(output.hash()), - "ca1a28f0e9a58ffc67037cc75066dbdd8e024aa2b2e416e4d6ce16c3d86282e5"); - } - { // sign candidateregister - auto input = createSigningInput(); - Proto::SigningOutput output; - input.set_gasprice("1000"); - auto& cbi = *input.mutable_candidateregister()->mutable_candidate(); - cbi.set_name("test"); - cbi.set_operatoraddress("io10a298zmzvrt4guq79a9f4x7qedj59y7ery84he"); - cbi.set_rewardaddress("io13sj9mzpewn25ymheukte4v39hvjdtrfp00mlyv"); - - auto& action = *input.mutable_candidateregister(); - action.set_stakedamount("100"); - action.set_stakedduration(10000); - action.set_autostake(false); - action.set_owneraddress("io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj"); - action.set_payload("payload"); - ANY_SIGN(input, TWCoinTypeIoTeX); - ASSERT_EQ(hex(output.encoded()), - "0aaa01080118c0843d220431303030fa029a010a5c0a04746573741229696f3130613239387a6d7a" - "7672743467" - "757137396139663478377165646a35397937657279383468651a29696f3133736a396d7a7065776e" - "3235796d68" - "65756b74653476333968766a647472667030306d6c7976120331303018904e2a29696f3139643070" - "3361683467" - "3877773964376b63786671383779786537666e7238727074683573686a32077061796c6f61641241" - "04755ce6d8" - "903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99" - "a5c1335b58" - "3c0bc76ef30dd6a1038ed9da8daf331a417819b5bcb635e3577acc8ca757f2c3d6afa451c2b6ff8a" - "9179b141ac" - "68e2c50305679e5d09d288da6f0fb52876a86c74deab6a5247edc6d371de5c2f121e159400"); - // signed action's hash - ASSERT_EQ(hex(output.hash()), - "35f53a536e014b32b85df50483ef04849b80ad60635b3b1979c5ba1096b65237"); - } -} diff --git a/tests/IoTeX/TWAnySignerTests.cpp b/tests/IoTeX/TWAnySignerTests.cpp deleted file mode 100644 index 3bcb630ece4..00000000000 --- a/tests/IoTeX/TWAnySignerTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ - -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" -#include -#include "HexCoding.h" -#include "proto/IoTeX.pb.h" - -#include - -using namespace TW; -using namespace TW::IoTeX; - -TEST(TWAnySignerIoTeX, Sign) { - auto key = parse_hex("68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748"); - Proto::SigningInput input; - input.set_version(1); - input.set_nonce(1); - input.set_gaslimit(1); - input.set_gasprice("1"); - input.set_privatekey(key.data(), key.size()); - auto& transfer = *input.mutable_transfer(); - transfer.set_amount("1"); - transfer.set_recipient("io1e2nqsyt7fkpzs5x7zf2uk0jj72teu5n6aku3tr"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeIoTeX); - - ASSERT_EQ(hex(output.encoded()), "0a39080110011801220131522e0a01311229696f3165326e7173797437666b707a733578377a6632756b306a6a3732746575356e36616b75337472124104fb30b196ce3e976593ecc2da220dca9cdea8c84d2373770042a930b892ac0f5cf762f20459c9100eb9d4d7597f5817bf21e10b53a0120b9ec1ba5cddfdcb669b1a41ec9757ae6c9009315830faaab250b6db0e9535b00843277f596ae0b2b9efc0bd4e14138c056fc4cdfa285d13dd618052b3d1cb7a3f554722005a2941bfede96601"); -} diff --git a/tests/IoTeX/TWCoinTypeTests.cpp b/tests/IoTeX/TWCoinTypeTests.cpp deleted file mode 100644 index ce42df73442..00000000000 --- a/tests/IoTeX/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWIoTeXCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeIoTeX)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeIoTeX, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeIoTeX, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeIoTeX)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeIoTeX)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeIoTeX), 18); - ASSERT_EQ(TWBlockchainIoTeX, TWCoinTypeBlockchain(TWCoinTypeIoTeX)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeIoTeX)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeIoTeX)); - assertStringsEqual(symbol, "IOTX"); - assertStringsEqual(txUrl, "https://iotexscan.io/action/t123"); - assertStringsEqual(accUrl, "https://iotexscan.io/address/a12"); - assertStringsEqual(id, "iotex"); - assertStringsEqual(name, "IoTeX"); -} diff --git a/tests/Kava/TWCoinTypeTests.cpp b/tests/Kava/TWCoinTypeTests.cpp deleted file mode 100644 index b8c061f97fc..00000000000 --- a/tests/Kava/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWKavaCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeKava)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("2988DF83FCBFAA38179D583A96734CBD071541D6768221BB23111BC8136D5E6A")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeKava, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("kava1xd39avn2f008jmvua0eupg39zsp2xn3wf802vn")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeKava, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeKava)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeKava)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeKava), 6); - ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeKava)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeKava)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeKava)); - assertStringsEqual(symbol, "KAVA"); - assertStringsEqual(txUrl, "https://mintscan.io/kava/txs/2988DF83FCBFAA38179D583A96734CBD071541D6768221BB23111BC8136D5E6A"); - assertStringsEqual(accUrl, "https://mintscan.io/kava/account/kava1xd39avn2f008jmvua0eupg39zsp2xn3wf802vn"); - assertStringsEqual(id, "kava"); - assertStringsEqual(name, "Kava"); -} diff --git a/tests/Keystore/StoredKeyTests.cpp b/tests/Keystore/StoredKeyTests.cpp deleted file mode 100644 index 135fb995d2b..00000000000 --- a/tests/Keystore/StoredKeyTests.cpp +++ /dev/null @@ -1,626 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Keystore/StoredKey.h" - -#include "Coin.h" -#include "HexCoding.h" -#include "Data.h" -#include "PrivateKey.h" -#include "Mnemonic.h" -#include "Bitcoin/Address.h" - -#include -#include - -extern std::string TESTS_ROOT; - -namespace TW::Keystore { - -using namespace std; - -const auto passwordString = "password"; -const auto password = TW::data(string(passwordString)); -const auto mnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; -const TWCoinType coinTypeBc = TWCoinTypeBitcoin; -const TWCoinType coinTypeBnb = TWCoinTypeBinance; -const TWCoinType coinTypeBsc = TWCoinTypeSmartChain; -const TWCoinType coinTypeEth = TWCoinTypeEthereum; -const TWCoinType coinTypeBscLegacy = TWCoinTypeSmartChainLegacy; - -TEST(StoredKey, CreateWithMnemonic) { - auto key = StoredKey::createWithMnemonic("name", password, mnemonic, TWStoredKeyEncryptionLevelDefault); - EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); - const Data& mnemo2Data = key.payload.decrypt(password); - EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(mnemonic)); - EXPECT_EQ(key.accounts.size(), 0); - EXPECT_EQ(key.wallet(password).getMnemonic(), string(mnemonic)); - - const auto json = key.json(); - EXPECT_EQ(json["name"], "name"); - EXPECT_EQ(json["type"], "mnemonic"); - EXPECT_EQ(json["version"], 3); -} - -TEST(StoredKey, CreateWithMnemonicInvalid) { - try { - auto key = StoredKey::createWithMnemonic("name", password, "_THIS_IS_NOT_A_VALID_MNEMONIC_", TWStoredKeyEncryptionLevelDefault); - } catch (std::invalid_argument&) { - // expedcted exception OK - return; - } - FAIL() << "Missing excpected excpetion"; -} - -TEST(StoredKey, CreateWithMnemonicRandom) { - const auto key = StoredKey::createWithMnemonicRandom("name", password, TWStoredKeyEncryptionLevelDefault); - EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); - // random mnemonic: check only length and validity - const Data& mnemo2Data = key.payload.decrypt(password); - EXPECT_TRUE(mnemo2Data.size() >= 36); - EXPECT_TRUE(Mnemonic::isValid(string(mnemo2Data.begin(), mnemo2Data.end()))); - EXPECT_EQ(key.accounts.size(), 0); -} - -TEST(StoredKey, CreateWithMnemonicAddDefaultAddress) { - auto key = StoredKey::createWithMnemonicAddDefaultAddress("name", password, mnemonic, coinTypeBc); - EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); - - const Data& mnemo2Data = key.payload.decrypt(password); - - EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(mnemonic)); - EXPECT_EQ(key.accounts.size(), 1); - EXPECT_EQ(key.accounts[0].coin, coinTypeBc); - EXPECT_EQ(key.accounts[0].address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); - EXPECT_EQ(key.accounts[0].publicKey, "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"); - EXPECT_EQ(key.accounts[0].extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); - EXPECT_EQ(hex(key.privateKey(coinTypeBc, password).bytes), "d2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f"); -} - -TEST(StoredKey, CreateWithPrivateKeyAddDefaultAddress) { - const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); - auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", password, coinTypeBc, privateKey); - EXPECT_EQ(key.type, StoredKeyType::privateKey); - EXPECT_EQ(key.accounts.size(), 1); - EXPECT_EQ(key.accounts[0].coin, coinTypeBc); - EXPECT_EQ(key.accounts[0].address, "bc1q375sq4kl2nv0mlmup3vm8znn4eqwu7mt6hkwhr"); - EXPECT_EQ(hex(key.privateKey(coinTypeBc, password).bytes), hex(privateKey)); - - const auto json = key.json(); - EXPECT_EQ(json["name"], "name"); - EXPECT_EQ(json["type"], "private-key"); - EXPECT_EQ(json["version"], 3); -} - -TEST(StoredKey, CreateWithPrivateKeyAddDefaultAddressInvalid) { - try { - const auto privateKeyInvalid = parse_hex("0001020304"); - auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", password, coinTypeBc, privateKeyInvalid); - } catch (std::invalid_argument&) { - // expected exception ok - return; - } - FAIL() << "Missing expected exception"; -} - -TEST(StoredKey, AccountGetCreate) { - auto key = StoredKey::createWithMnemonic("name", password, mnemonic, TWStoredKeyEncryptionLevelDefault); - EXPECT_EQ(key.accounts.size(), 0); - - // not exists - EXPECT_FALSE(key.account(coinTypeBc).has_value()); - EXPECT_EQ(key.accounts.size(), 0); - - auto wallet = key.wallet(password); - // not exists, wallet null, not create - EXPECT_FALSE(key.account(coinTypeBc, nullptr).has_value()); - EXPECT_EQ(key.accounts.size(), 0); - - // not exists, wallet nonnull, create - std::optional acc3 = key.account(coinTypeBc, &wallet); - EXPECT_TRUE(acc3.has_value()); - EXPECT_EQ(acc3->coin, coinTypeBc); - EXPECT_EQ(key.accounts.size(), 1); - - // exists - std::optional acc4 = key.account(coinTypeBc); - EXPECT_TRUE(acc4.has_value()); - EXPECT_EQ(acc4->coin, coinTypeBc); - EXPECT_EQ(key.accounts.size(), 1); - - // exists, wallet nonnull, not create - std::optional acc5 = key.account(coinTypeBc, &wallet); - EXPECT_TRUE(acc5.has_value()); - EXPECT_EQ(acc5->coin, coinTypeBc); - EXPECT_EQ(key.accounts.size(), 1); - - // exists, wallet null, not create - std::optional acc6 = key.account(coinTypeBc, nullptr); - EXPECT_TRUE(acc6.has_value()); - EXPECT_EQ(acc6->coin, coinTypeBc); - EXPECT_EQ(key.accounts.size(), 1); -} - -TEST(StoredKey, AccountGetDoesntChange) { - auto key = StoredKey::createWithMnemonic("name", password, mnemonic, TWStoredKeyEncryptionLevelDefault); - auto wallet = key.wallet(password); - EXPECT_EQ(key.accounts.size(), 0); - - vector coins = {coinTypeBc, coinTypeEth, coinTypeBnb}; - // retrieve multiple accounts, which will be created - vector accounts; - for (auto coin: coins) { - std::optional account = key.account(coin, &wallet); - accounts.push_back(*account); - - // check - ASSERT_TRUE(account.has_value()); - EXPECT_EQ(account->coin, coin); - } - - // Check again; make sure returned references don't change - for (auto i = 0; i < accounts.size(); ++i) { - // check - EXPECT_EQ(accounts[i].coin, coins[i]); - } -} - -TEST(StoredKey, AddRemoveAccount) { - auto key = StoredKey::createWithMnemonic("name", password, mnemonic, TWStoredKeyEncryptionLevelDefault); - EXPECT_EQ(key.accounts.size(), 0); - - { - const auto derivationPath = DerivationPath("m/84'/0'/0'/0/0"); - key.addAccount("bc1qaucw06s3agez8tyyk4zj9kt0q2934e3mcewdpf", coinTypeBc, TWDerivationDefault, derivationPath, "", "zpub6rxtad3SPT1C5GUDjPiKQ5oJN5DBeMbdUR7LrdYt12VbU7TBSpGUkdLvfVYGuj1N5edkDoZ3bu1fdN1HprQYfCBdsSH5CaAAygHGsanwtTe"); - EXPECT_EQ(key.accounts.size(), 1); - } - { - const auto derivationPath = DerivationPath("m/714'/0'/0'/0/0"); - key.addAccount("bnb1utrnnjym7ustgw7pgyvtmnxay4qmt3ahh276nu", coinTypeBnb, TWDerivationDefault, derivationPath, "", ""); - key.addAccount("0x23b02dC8f67eD6cF8DCa47935791954286ffe7c9", coinTypeBsc, TWDerivationDefault, derivationPath, "", ""); - EXPECT_EQ(key.accounts.size(), 3); - } - { - const auto derivationPath = DerivationPath("m/60'/0'/0'/0/0"); - key.addAccount("0xC0d97f61A84A0708225F15d54978D628Fe2C5E62", coinTypeEth, TWDerivationDefault, derivationPath, "", ""); - key.addAccount("0xC0d97f61A84A0708225F15d54978D628Fe2C5E62", coinTypeBscLegacy, TWDerivationDefault, derivationPath, "", ""); - EXPECT_EQ(key.accounts.size(), 5); - } - - key.removeAccount(coinTypeBc); - key.removeAccount(coinTypeBnb); - key.removeAccount(coinTypeBsc); - key.removeAccount(coinTypeEth); - key.removeAccount(coinTypeBscLegacy); - EXPECT_EQ(key.accounts.size(), 0); -} - -TEST(StoredKey, AddRemoveAccountDerivation) { - auto key = StoredKey::createWithMnemonic("name", Data(), mnemonic, TWStoredKeyEncryptionLevelDefault); - EXPECT_EQ(key.accounts.size(), 0); - - const auto derivationPath = DerivationPath("m/84'/0'/0'/0/0"); - { - key.addAccount("bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny", coinTypeBc, TWDerivationDefault, derivationPath, "", "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); - EXPECT_EQ(key.accounts.size(), 1); - } - { - key.addAccount("1NyRyFewhZcWMa9XCj3bBxSXPXyoSg8dKz", coinTypeBc, TWDerivationBitcoinLegacy, derivationPath, "", "xpub6CR52eaUuVb4kXAVyHC2i5ZuqJ37oWNPZFtjXaazFPXZD45DwWBYEBLdrF7fmCR9pgBuCA9Q57zZfyJjDUBDNtWkhWuGHNYKLgDHpqrHsxV"); - EXPECT_EQ(key.accounts.size(), 2); - } - - key.removeAccount(coinTypeBc, TWDerivationDefault); - EXPECT_EQ(key.accounts.size(), 1); - key.removeAccount(coinTypeBc, TWDerivationDefault); // try 2nd time - EXPECT_EQ(key.accounts.size(), 1); - key.removeAccount(coinTypeBc, TWDerivationBitcoinLegacy); - EXPECT_EQ(key.accounts.size(), 0); - key.removeAccount(coinTypeBc, TWDerivationBitcoinLegacy); // try 2nd time - EXPECT_EQ(key.accounts.size(), 0); -} - -TEST(StoredKey, AddRemoveAccountDerivationPath) { - auto key = StoredKey::createWithMnemonic("name", Data(), mnemonic, TWStoredKeyEncryptionLevelDefault); - EXPECT_EQ(key.accounts.size(), 0); - - const auto derivationPath0 = DerivationPath("m/84'/0'/0'/0/0"); - { - key.addAccount("bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny", coinTypeBc, TWDerivationDefault, derivationPath0, "", "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); - EXPECT_EQ(key.accounts.size(), 1); - } - const auto derivationPath1 = DerivationPath("m/84'/0'/0'/1/0"); - { - key.addAccount("bc1qumuzptwdr6jlsqum8jnzz80rdg8nx6x29m2qpu", coinTypeBc, TWDerivationDefault, derivationPath1, "", "zpub6rxtad3SPT1C5GUDjPiKQ5oJN5DBeMbdUR7LrdYt12VbU7TBSpGUkdLvfVYGuj1N5edkDoZ3bu1fdN1HprQYfCBdsSH5CaAAygHGsanwtTe"); - EXPECT_EQ(key.accounts.size(), 2); - } - - key.removeAccount(coinTypeBc, derivationPath0); - EXPECT_EQ(key.accounts.size(), 1); - key.removeAccount(coinTypeBc, derivationPath0); // try 2nd time - EXPECT_EQ(key.accounts.size(), 1); - key.removeAccount(coinTypeBc, derivationPath1); - EXPECT_EQ(key.accounts.size(), 0); - key.removeAccount(coinTypeBc, derivationPath1); // try 2nd time - EXPECT_EQ(key.accounts.size(), 0); -} - -TEST(StoredKey, FixAddress) { - { - auto key = StoredKey::createWithMnemonic("name", password, mnemonic, TWStoredKeyEncryptionLevelDefault); - key.fixAddresses(password); - } - { - const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); - auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", password, coinTypeBc, privateKey); - key.fixAddresses(password); - } -} - -TEST(StoredKey, WalletInvalid) { - const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); - auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", password, coinTypeBc, privateKey); - try { - auto wallet = key.wallet(password); - } catch (std::invalid_argument&) { - // expected exception ok - return; - } - FAIL() << "Missing expected exception"; -} - -TEST(StoredKey, LoadNonexistent) { - ASSERT_THROW(StoredKey::load(TESTS_ROOT + "/Keystore/Data/nonexistent.json"), invalid_argument); -} - -TEST(StoredKey, LoadLegacyPrivateKey) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/legacy-private-key.json"); - EXPECT_EQ(key.type, StoredKeyType::privateKey); - EXPECT_EQ(key.id, "3051ca7d-3d36-4a4a-acc2-09e9083732b0"); - EXPECT_EQ(key.accounts[0].coin, TWCoinTypeEthereum); - EXPECT_EQ(hex(key.payload.decrypt(TW::data("testpassword"))), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); -} - -TEST(StoredKey, LoadLivepeerKey) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/livepeer.json"); - EXPECT_EQ(key.type, StoredKeyType::privateKey); - EXPECT_EQ(key.id, "70ea3601-ee21-4e94-a7e4-66255a987d22"); - EXPECT_EQ(key.accounts[0].coin, TWCoinTypeEthereum); - EXPECT_EQ(hex(key.payload.decrypt(TW::data("Radchenko"))), "09b4379d9a41a71d94ee36357bccb4d77b45e7fd9307e2c0f673dd54c0558c73"); -} - -TEST(StoredKey, LoadPBKDF2Key) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/pbkdf2.json"); - EXPECT_EQ(key.type, StoredKeyType::privateKey); - EXPECT_EQ(key.id, "3198bc9c-6672-5ab3-d995-4942343ae5b6"); - - const auto& payload = key.payload; - ASSERT_TRUE(payload.params.kdfParams.which() == 1); - EXPECT_EQ(boost::get(payload.params.kdfParams).desiredKeyLength, 32); - EXPECT_EQ(boost::get(payload.params.kdfParams).iterations, 262144); - EXPECT_EQ(hex(boost::get(payload.params.kdfParams).salt), "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"); - - EXPECT_EQ(hex(payload.decrypt(TW::data("testpassword"))), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); -} - -TEST(StoredKey, LoadLegacyMnemonic) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/legacy-mnemonic.json"); - EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); - EXPECT_EQ(key.id, "629aad29-0b22-488e-a0e7-b4219d4f311c"); - - const auto data = key.payload.decrypt(password); - const auto mnemonic = string(reinterpret_cast(data.data())); - EXPECT_EQ(mnemonic, "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn back"); - - EXPECT_EQ(key.accounts[0].coin, TWCoinTypeEthereum); - EXPECT_EQ(key.accounts[0].derivationPath.string(), "m/44'/60'/0'/0/0"); - EXPECT_EQ(key.accounts[0].address, ""); - EXPECT_EQ(key.accounts[1].coin, coinTypeBc); - EXPECT_EQ(key.accounts[1].derivationPath.string(), "m/84'/0'/0'/0/0"); - EXPECT_EQ(key.accounts[1].address, ""); - EXPECT_EQ(key.accounts[1].extendedPublicKey, "zpub6r97AegwVxVbJeuDAWP5KQgX5y4Q6KyFUrsFQRn8yzSXrnmpwg1ZKHSWwECR1Kiqgr4h93WN5kdS48KC6hVFniuZHqVFXjULZZkCwurqyPn"); -} - -TEST(StoredKey, LoadFromWeb3j) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/web3j.json"); - EXPECT_EQ(key.type, StoredKeyType::privateKey); - EXPECT_EQ(key.id, "86066d8c-8dba-4d81-afd4-934e2a2b72a2"); - const auto password = parse_hex("2d6eefbfbd4622efbfbdefbfbd516718efbfbdefbfbdefbfbdefbfbd59efbfbd30efbfbdefbfbd3a4348efbfbd2aefbfbdefbfbd49efbfbd27efbfbd0638efbfbdefbfbdefbfbd4cefbfbd6befbfbdefbfbd6defbfbdefbfbd63efbfbd5aefbfbd61262b70efbfbdefbfbdefbfbdefbfbdefbfbdc7aa373163417cefbfbdefbfbdefbfbd44efbfbdefbfbd1d10efbfbdefbfbdefbfbd61dc9e5b124befbfbd11efbfbdefbfbd2fefbfbdefbfbd3d7c574868efbfbdefbfbdefbfbd37043b7b5c1a436471592f02efbfbd18efbfbdefbfbd2befbfbdefbfbd7218efbfbd6a68efbfbdcb8e5f3328773ec48174efbfbd67efbfbdefbfbdefbfbdefbfbdefbfbd2a31efbfbd7f60efbfbdd884efbfbd57efbfbd25efbfbd590459efbfbd37efbfbd2bdca20fefbfbdefbfbdefbfbdefbfbd39450113efbfbdefbfbdefbfbd454671efbfbdefbfbdd49fefbfbd47efbfbdefbfbdefbfbdefbfbd00efbfbdefbfbdefbfbdefbfbd05203f4c17712defbfbd7bd1bbdc967902efbfbdc98a77efbfbd707a36efbfbd12efbfbdefbfbd57c78cefbfbdefbfbdefbfbd10efbfbdefbfbdefbfbde1a1bb08efbfbdefbfbd26efbfbdefbfbd58efbfbdefbfbdc4b1efbfbd295fefbfbd0eefbfbdefbfbdefbfbd0e6eefbfbd"); - const auto data = key.payload.decrypt(password); - EXPECT_EQ(hex(data), "043c5429c7872502531708ec0d821c711691402caf37ef7ba78a8c506f10653b"); -} - -TEST(StoredKey, ReadWallet) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/key.json"); - - EXPECT_EQ(key.type, StoredKeyType::privateKey); - EXPECT_EQ(key.id, "e13b209c-3b2f-4327-bab0-3bef2e51630d"); - EXPECT_EQ(key.name, "Test Account"); - - const auto header = key.payload; - - EXPECT_EQ(header.params.cipher, "aes-128-ctr"); - EXPECT_EQ(hex(header.encrypted), "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c"); - EXPECT_EQ(hex(header.mac), "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"); - EXPECT_EQ(hex(header.params.cipherParams.iv), "83dbcc02d8ccb40e466191a123791e0e"); - - ASSERT_TRUE(header.params.kdfParams.which() == 0); - EXPECT_EQ(boost::get(header.params.kdfParams).desiredKeyLength, 32); - EXPECT_EQ(boost::get(header.params.kdfParams).n, 262144); - EXPECT_EQ(boost::get(header.params.kdfParams).p, 8); - EXPECT_EQ(boost::get(header.params.kdfParams).r, 1); - EXPECT_EQ(hex(boost::get(header.params.kdfParams).salt), "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"); -} - -TEST(StoredKey, ReadMyEtherWallet) { - ASSERT_NO_THROW(StoredKey::load(TESTS_ROOT + "/Keystore/Data/myetherwallet.uu")); -} - -TEST(StoredKey, InvalidPassword) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/key.json"); - - ASSERT_THROW(key.payload.decrypt(password), DecryptionError); -} - -TEST(StoredKey, EmptyAccounts) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/empty-accounts.json"); - - ASSERT_NO_THROW(key.payload.decrypt(TW::data("testpassword"))); -} - -TEST(StoredKey, Decrypt) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/key.json"); - const auto privateKey = key.payload.decrypt(TW::data("testpassword")); - - EXPECT_EQ(hex(privateKey), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); -} - -TEST(StoredKey, CreateWallet) { - const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); - const auto key = StoredKey::createWithPrivateKey("name", password, privateKey); - const auto decrypted = key.payload.decrypt(password); - - EXPECT_EQ(hex(decrypted), hex(privateKey)); -} - -TEST(StoredKey, CreateAccounts) { - string mnemonicPhrase = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; - auto key = StoredKey::createWithMnemonic("name", password, mnemonicPhrase, TWStoredKeyEncryptionLevelDefault); - const auto wallet = key.wallet(password); - - EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->address, "0x494f60cb6Ac2c8F5E1393aD9FdBdF4Ad589507F7"); - EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->publicKey, "04cc32a479080d83fdcf69966713f0aad1bc1dc3ecf873b034894e84259841bc1c9b122717803e68905220ff54952d3f5ea2ab2698ca31f843addf94ae73fae9fd"); - EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->extendedPublicKey, ""); - - EXPECT_EQ(key.account(coinTypeBc, &wallet)->address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); - EXPECT_EQ(key.account(coinTypeBc, &wallet)->publicKey, "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"); - EXPECT_EQ(key.account(coinTypeBc, &wallet)->extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); -} - -TEST(StoredKey, DecodingEthereumAddress) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/key.json"); - - EXPECT_EQ(key.accounts[0].address, "0x008AeEda4D805471dF9b2A5B0f38A0C3bCBA786b"); -} - -TEST(StoredKey, DecodingBitcoinAddress) { - const auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/key_bitcoin.json"); - - EXPECT_EQ(key.accounts[0].address, "3PWazDi9n1Hfyq9gXFxDxzADNL8RNYyK2y"); -} - -TEST(StoredKey, RemoveAccount) { - auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/legacy-mnemonic.json"); - EXPECT_EQ(key.accounts.size(), 2); - key.removeAccount(TWCoinTypeEthereum); - EXPECT_EQ(key.accounts.size(), 1); - EXPECT_EQ(key.accounts[0].coin, coinTypeBc); -} - -TEST(StoredKey, MissingAddressFix) { - auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/missing-address.json"); - EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); - - const auto wallet = key.wallet(password); - EXPECT_EQ(wallet.getMnemonic(), "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"); - EXPECT_TRUE(Mnemonic::isValid(wallet.getMnemonic())); - - EXPECT_EQ(key.account(TWCoinTypeBitcoin)->address, ""); - EXPECT_EQ(key.account(TWCoinTypeEthereum)->address, ""); - - key.fixAddresses(password); - - EXPECT_EQ(key.account(TWCoinTypeEthereum, nullptr)->address, "0xA3Dcd899C0f3832DFDFed9479a9d828c6A4EB2A7"); - EXPECT_EQ(key.account(TWCoinTypeEthereum, nullptr)->publicKey, "0448a9ffac8022f1c7eb5253746e24d11d9b6b2737c0aecd48335feabb95a179916b1f3a97bed6740a85a2d11c663d38566acfb08af48a47ce0c835c65c9b23d0d"); - EXPECT_EQ(key.account(coinTypeBc, nullptr)->address, "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"); - EXPECT_EQ(key.account(coinTypeBc, nullptr)->publicKey, "02df9ef2a7a5552765178b181e1e1afdefc7849985c7dfe9647706dd4fa40df6ac"); -} - -TEST(StoredKey, MissingAddressReadd) { - auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/missing-address.json"); - EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); - - const auto wallet = key.wallet(password); - EXPECT_EQ(wallet.getMnemonic(), "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"); - EXPECT_TRUE(Mnemonic::isValid(wallet.getMnemonic())); - - EXPECT_EQ(key.account(TWCoinTypeBitcoin)->address, ""); - EXPECT_EQ(key.account(TWCoinTypeEthereum)->address, ""); - - // get accounts, this will also fill addresses as they are empty - const auto btcAccount = key.account(TWCoinTypeBitcoin, &wallet); - const auto ethAccount = key.account(TWCoinTypeEthereum, &wallet); - - EXPECT_TRUE(btcAccount.has_value()); - EXPECT_EQ(btcAccount->address, "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"); - EXPECT_TRUE(ethAccount.has_value()); - EXPECT_EQ(ethAccount->address, "0xA3Dcd899C0f3832DFDFed9479a9d828c6A4EB2A7"); -} - -TEST(StoredKey, EtherWalletAddressNo0x) { - auto key = StoredKey::load(TESTS_ROOT + "/Keystore/Data/ethereum-wallet-address-no-0x.json"); - key.fixAddresses(TW::data("15748c4e3dca6ae2110535576ab0c398cb79d985707c68ee6c9f9df9d421dd53")); - const auto account = key.account(TWCoinTypeEthereum, nullptr); - EXPECT_EQ(account->address, "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309"); - EXPECT_EQ(account->publicKey, "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91"); -} - -TEST(StoredKey, CreateMinimalEncryptionParameters) { - const auto key = StoredKey::createWithMnemonic("name", password, mnemonic, TWStoredKeyEncryptionLevelMinimal); - EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); - const Data& mnemo2Data = key.payload.decrypt(password); - EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(mnemonic)); - EXPECT_EQ(key.accounts.size(), 0); - EXPECT_EQ(key.wallet(password).getMnemonic(), string(mnemonic)); - - const auto json = key.json(); - - EXPECT_EQ(json["crypto"]["kdf"], "scrypt"); - EXPECT_EQ(json["crypto"]["kdfparams"]["n"], 4096); - - // load it back - const auto key2 = StoredKey::createWithJson(json); - EXPECT_EQ(key2.wallet(password).getMnemonic(), string(mnemonic)); -} - -TEST(StoredKey, CreateWeakEncryptionParameters) { - const auto key = StoredKey::createWithMnemonic("name", password, mnemonic, TWStoredKeyEncryptionLevelWeak); - EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); - const Data& mnemo2Data = key.payload.decrypt(password); - EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(mnemonic)); - EXPECT_EQ(key.accounts.size(), 0); - EXPECT_EQ(key.wallet(password).getMnemonic(), string(mnemonic)); - - const auto json = key.json(); - - EXPECT_EQ(json["crypto"]["kdf"], "scrypt"); - EXPECT_EQ(json["crypto"]["kdfparams"]["n"], 16384); - - // load it back - const auto key2 = StoredKey::createWithJson(json); - EXPECT_EQ(key2.wallet(password).getMnemonic(), string(mnemonic)); -} - -TEST(StoredKey, CreateStandardEncryptionParameters) { - const auto key = StoredKey::createWithMnemonic("name", password, mnemonic, TWStoredKeyEncryptionLevelStandard); - EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); - const Data& mnemo2Data = key.payload.decrypt(password); - EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(mnemonic)); - EXPECT_EQ(key.accounts.size(), 0); - EXPECT_EQ(key.wallet(password).getMnemonic(), string(mnemonic)); - - const auto json = key.json(); - - EXPECT_EQ(json["crypto"]["kdf"], "scrypt"); - EXPECT_EQ(json["crypto"]["kdfparams"]["n"], 262144); - - // load it back - const auto key2 = StoredKey::createWithJson(json); - EXPECT_EQ(key2.wallet(password).getMnemonic(), string(mnemonic)); -} - -TEST(StoredKey, CreateMultiAccounts) { // Multiple accounts for the same coin - auto key = StoredKey::createWithMnemonic("name", password, mnemonic, TWStoredKeyEncryptionLevelDefault); - EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); - const Data& mnemo2Data = key.payload.decrypt(password); - EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(mnemonic)); - EXPECT_EQ(key.wallet(password).getMnemonic(), string(mnemonic)); - EXPECT_EQ(key.accounts.size(), 0); - - const auto expectedBtc1 = "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"; - const auto expectedBtc2 = "1NyRyFewhZcWMa9XCj3bBxSXPXyoSg8dKz"; - const auto expectedSol1 = "HiipoCKL8hX2RVmJTz3vaLy34hS2zLhWWMkUWtw85TmZ"; - const auto wallet = key.wallet(password); - int expectedAccounts = 0; - - { // Create default Bitcoin account - const auto coin = TWCoinTypeBitcoin; - - const auto btc1 = key.account(coin, &wallet); - - EXPECT_TRUE(btc1.has_value()); - EXPECT_EQ(btc1->address, expectedBtc1); - EXPECT_EQ(btc1->derivationPath.string(), "m/84'/0'/0'/0/0"); - EXPECT_EQ(btc1->extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); - EXPECT_EQ(key.accounts.size(), ++expectedAccounts); - EXPECT_EQ(key.accounts[expectedAccounts - 1].address, expectedBtc1); - EXPECT_EQ(key.account(coin)->address, expectedBtc1); - EXPECT_EQ(key.getAccounts(coin).size(), 1); - EXPECT_EQ(key.getAccounts(coin)[0].address, expectedBtc1); - } - { // Create default Solana account - const auto coin = TWCoinTypeSolana; - - const auto sol1 = key.account(coin, &wallet); - - EXPECT_TRUE(sol1.has_value()); - EXPECT_EQ(sol1->address, expectedSol1); - EXPECT_EQ(sol1->derivationPath.string(), "m/44'/501'/0'"); - EXPECT_EQ(key.accounts.size(), ++expectedAccounts); - EXPECT_EQ(key.accounts[expectedAccounts - 1].address, expectedSol1); - EXPECT_EQ(key.account(coin)->address, expectedSol1); - EXPECT_EQ(key.getAccounts(coin).size(), 1); - EXPECT_EQ(key.getAccounts(coin)[0].address, expectedSol1); - } - { // Create alternative P2PK Bitcoin account (different address format) - const auto coin = TWCoinTypeBitcoin; - - const auto btc2 = key.account(coin, TWDerivationBitcoinLegacy, wallet); - - EXPECT_EQ(btc2.address, expectedBtc2); - EXPECT_EQ(btc2.derivationPath.string(), "m/44'/0'/0'/0/0"); - EXPECT_EQ(btc2.extendedPublicKey, "xpub6CR52eaUuVb4kXAVyHC2i5ZuqJ37oWNPZFtjXaazFPXZD45DwWBYEBLdrF7fmCR9pgBuCA9Q57zZfyJjDUBDNtWkhWuGHNYKLgDHpqrHsxV"); - EXPECT_EQ(key.accounts.size(), ++expectedAccounts); - EXPECT_EQ(key.accounts[expectedAccounts - 1].address, expectedBtc2); - EXPECT_EQ(key.account(coin)->address, expectedBtc1); - EXPECT_EQ(key.account(coin, TWDerivationBitcoinLegacy, wallet).address, expectedBtc2); - EXPECT_EQ(key.getAccounts(coin).size(), 2); - EXPECT_EQ(key.getAccounts(coin)[0].address, expectedBtc1); - EXPECT_EQ(key.getAccounts(coin)[1].address, expectedBtc2); - } - { // Create alternative Solana account with non-default derivation path (different derivation path and address) - const auto coin = TWCoinTypeSolana; - - const auto sol2 = key.account(coin, TWDerivationSolanaSolana, wallet); - - const auto expectedSol2 = "CgWJeEWkiYqosy1ba7a3wn9HAQuHyK48xs3LM4SSDc1C"; - EXPECT_EQ(sol2.address, expectedSol2); - EXPECT_EQ(sol2.derivationPath.string(), "m/44'/501'/0'/0'"); - EXPECT_EQ(key.accounts.size(), ++expectedAccounts); - EXPECT_EQ(key.accounts[expectedAccounts - 1].address, expectedSol2); - // Now we have 2 Solana addresses, 1st is returned here - EXPECT_EQ(key.account(coin)->address, expectedSol1); - EXPECT_EQ(key.account(coin, TWDerivationSolanaSolana, wallet).address, expectedSol2); - EXPECT_EQ(key.getAccounts(coin).size(), 2); - EXPECT_EQ(key.getAccounts(coin)[0].address, expectedSol1); - EXPECT_EQ(key.getAccounts(coin)[1].address, expectedSol2); - } - { // Create CUSTOM account with alternative Bitcoin address. Note: this is not recommended. - const auto coin = TWCoinTypeBitcoin; - const auto customPath = DerivationPath("m/44'/2'/0'/0/0"); - const auto btcPrivateKey = wallet.getKey(coin, customPath); - EXPECT_NE(TW::deriveAddress(coin, btcPrivateKey), expectedBtc1); - const auto btcPublicKey = btcPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - const auto p2pkhBtcAddress = Bitcoin::Address(btcPublicKey, TWCoinTypeP2pkhPrefix(coin)).string(); - const auto expectedBtc3 = "1C43YUWSYTgaoBEsRffAkzF6HruJegEqP5"; - EXPECT_EQ(p2pkhBtcAddress, expectedBtc3); - const auto extendedPublicKey = wallet.getExtendedPublicKey(TW::purpose(coin), coin, TWHDVersionZPUB); - EXPECT_EQ(extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); - - key.addAccount(p2pkhBtcAddress, coin, TWDerivationCustom, customPath, hex(btcPublicKey.bytes), extendedPublicKey); - - EXPECT_EQ(key.accounts.size(), ++expectedAccounts); - EXPECT_EQ(key.accounts[expectedAccounts - 1].address, expectedBtc3); - // Now we have 2 Bitcoin addresses, 1st is returned here - EXPECT_EQ(key.account(coin)->address, expectedBtc1); - EXPECT_EQ(key.getAccounts(coin).size(), 3); - EXPECT_EQ(key.getAccounts(coin)[0].address, expectedBtc1); - EXPECT_EQ(key.getAccounts(coin)[1].address, expectedBtc2); - EXPECT_EQ(key.getAccounts(coin)[2].address, expectedBtc3); - EXPECT_EQ(key.getAccounts(coin)[2].derivationPath.string(), "m/44'/2'/0'/0/0"); - } -} - -} // namespace TW::Keystore diff --git a/tests/Kin/TWCoinTypeTests.cpp b/tests/Kin/TWCoinTypeTests.cpp deleted file mode 100644 index 9120ef0e2f9..00000000000 --- a/tests/Kin/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWKinCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeKin)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeKin, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeKin, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeKin)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeKin)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeKin), 5); - ASSERT_EQ(TWBlockchainStellar, TWCoinTypeBlockchain(TWCoinTypeKin)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeKin)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeKin)); - assertStringsEqual(symbol, "KIN"); - assertStringsEqual(txUrl, "https://www.kin.org/blockchainInfoPage/?&dataType=public&header=Transaction&id=t123"); - assertStringsEqual(accUrl, "https://www.kin.org/blockchainAccount/?&dataType=public&header=accountID&id=a12"); - assertStringsEqual(id, "kin"); - assertStringsEqual(name, "Kin"); -} diff --git a/tests/Klaytn/TWCoinTypeTests.cpp b/tests/Klaytn/TWCoinTypeTests.cpp deleted file mode 100644 index 9f293370262..00000000000 --- a/tests/Klaytn/TWCoinTypeTests.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWKlaytnCoinType, TWCoinType) { - const auto coin = TWCoinTypeKlaytn; - const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); - const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); - const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); - const auto chainId = WRAPS(TWCoinTypeChainId(coin)); - const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x93ea92687845fe7bb6cacd69c76a16a2a3c2bbb85a8a93ff0e032d0098d583d7")); - const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); - const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x2ad9656bf5b82caf10847b431012e28e301e83ba")); - const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); - - assertStringsEqual(id, "klaytn"); - assertStringsEqual(name, "Klaytn"); - assertStringsEqual(symbol, "KLAY"); - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); - ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); - ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); - ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); - assertStringsEqual(chainId, "8217"); - assertStringsEqual(txUrl, "https://scope.klaytn.com/tx/0x93ea92687845fe7bb6cacd69c76a16a2a3c2bbb85a8a93ff0e032d0098d583d7"); - assertStringsEqual(accUrl, "https://scope.klaytn.com/account/0x2ad9656bf5b82caf10847b431012e28e301e83ba"); -} diff --git a/tests/KuCoinCommunityChain/TWCoinTypeTests.cpp b/tests/KuCoinCommunityChain/TWCoinTypeTests.cpp deleted file mode 100644 index c8069645912..00000000000 --- a/tests/KuCoinCommunityChain/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWKuCoinCommunityChainCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeKuCoinCommunityChain)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x2f0d79cd289a02f3181b68b9583a64c3809fe7387810b274275985c29d02c80d")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeKuCoinCommunityChain, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x4446fc4eb47f2f6586f9faab68b3498f86c07521")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeKuCoinCommunityChain, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeKuCoinCommunityChain)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeKuCoinCommunityChain)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeKuCoinCommunityChain), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeKuCoinCommunityChain)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeKuCoinCommunityChain)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeKuCoinCommunityChain)); - assertStringsEqual(symbol, "KCS"); - assertStringsEqual(txUrl, "https://explorer.kcc.io/en/tx/0x2f0d79cd289a02f3181b68b9583a64c3809fe7387810b274275985c29d02c80d"); - assertStringsEqual(accUrl, "https://explorer.kcc.io/en/address/0x4446fc4eb47f2f6586f9faab68b3498f86c07521"); - assertStringsEqual(id, "kcc"); - assertStringsEqual(name, "KuCoin Community Chain"); -} diff --git a/tests/Kusama/AddressTests.cpp b/tests/Kusama/AddressTests.cpp deleted file mode 100644 index 7a7229a1167..00000000000 --- a/tests/Kusama/AddressTests.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "Kusama/Address.h" -#include "PublicKey.h" -#include "PrivateKey.h" -#include -#include - -using namespace TW; -using namespace TW::Kusama; - -TEST(KusamaAddress, Validation) { - // Substrate ed25519 - ASSERT_FALSE(Address::isValid("5FqqU2rytGPhcwQosKRtW1E3ha6BJKAjHgtcodh71dSyXhoZ")); - // Polkadot ed25519 - ASSERT_FALSE(Address::isValid("15AeCjMpcSt3Fwa47jJBd7JzQ395Kr2cuyF5Zp4UBf1g9ony")); - // Polkadot sr25519 - ASSERT_FALSE(Address::isValid("15AeCjMpcSt3Fwa47jJBd7JzQ395Kr2cuyF5Zp4UBf1g9ony")); - // Bitcoin - ASSERT_FALSE(Address::isValid("1ES14c7qLb5CYhLMUekctxLgc1FV2Ti9DA")); - - // Kusama ed25519 - ASSERT_TRUE(Address::isValid("FHKAe66mnbk8ke8zVWE9hFVFrJN1mprFPVmD5rrevotkcDZ")); - // Kusama secp256k1 - ASSERT_TRUE(Address::isValid("FxQFyTorsjVsjjMyjdgq8w5vGx8LiA1qhWbRYcFijxKKchx")); - // Kusama sr25519 - ASSERT_TRUE(Address::isValid("EJ5UJ12GShfh7EWrcNZFLiYU79oogdtXFUuDDZzk7Wb2vCe")); -} - -TEST(KusamaAddress, FromPrivateKey) { - // from subkey: tiny escape drive pupil flavor endless love walk gadget match filter luxury - auto privateKey = PrivateKey(parse_hex("0xa21981f3bb990c40837df44df639541ff57c5e600f9eb4ac00ed8d1f718364e5")); - auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519)); - ASSERT_EQ(address.string(), "CeVXtoU4py9e7F6upfM2ZarVave299TjcdaTSxhDDZrYgnM"); -} - -TEST(KusamaAddress, FromPublicKey) { - auto publicKey = PublicKey(parse_hex("0x032eb287017c5cde2940b5dd062d413f9d09f8aa44723fc80bf46b96c81ac23d"), TWPublicKeyTypeED25519); - auto address = Address(publicKey); - ASSERT_EQ(address.string(), "CeVXtoU4py9e7F6upfM2ZarVave299TjcdaTSxhDDZrYgnM"); -} - -TEST(KusamaAddress, FromString) { - auto address = Address("CeVXtoU4py9e7F6upfM2ZarVave299TjcdaTSxhDDZrYgnM"); - ASSERT_EQ(address.string(), "CeVXtoU4py9e7F6upfM2ZarVave299TjcdaTSxhDDZrYgnM"); -} diff --git a/tests/Kusama/SignerTests.cpp b/tests/Kusama/SignerTests.cpp deleted file mode 100644 index 99acb6efa5b..00000000000 --- a/tests/Kusama/SignerTests.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Polkadot/Signer.h" -#include "Polkadot/Extrinsic.h" -#include "Polkadot/SS58Address.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include "proto/Polkadot.pb.h" -#include "uint256.h" - -#include -#include - - -namespace TW::Polkadot { - extern PrivateKey privateKey; - extern PublicKey toPublicKey; - auto genesisHashKSM = parse_hex("b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); - -TEST(PolkadotSigner, SignTransferKSM) { - auto blockHash = parse_hex("4955dd4813f3e91ef3fd5a825b928af2fc50a71380085f753ccef00bb1582891"); - auto toAddress = SS58Address(toPublicKey, TWSS58AddressTypeKusama); - - auto input = Proto::SigningInput(); - input.set_block_hash(blockHash.data(), blockHash.size()); - input.set_genesis_hash(genesisHashKSM.data(), genesisHashKSM.size()); - input.set_nonce(0); - input.set_spec_version(2019); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::KUSAMA); - input.set_transaction_version(2); - - auto balanceCall = input.mutable_balance_call(); - auto& transfer = *balanceCall->mutable_transfer(); - auto value = store(uint256_t(12345)); - transfer.set_to_address(toAddress.string()); - transfer.set_value(value.data(), value.size()); - - auto extrinsic = Extrinsic(input); - auto preimage = extrinsic.encodePayload(); - auto output = Signer::sign(input); - - ASSERT_EQ(hex(preimage), "04008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0000000e307000002000000b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe4955dd4813f3e91ef3fd5a825b928af2fc50a71380085f753ccef00bb1582891"); - ASSERT_EQ(hex(output.encoded()), "25028488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee000765cfa76cfe19499f4f19ef7dc4527652ec5b2e6b5ecfaf68725dafd48ae2694ad52e61f44152a544784e847de10ddb2c56bee4406574dcbcfdb5e5d35b6d0300000004008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); -} - -} // namespace diff --git a/tests/Kusama/TWAnySignerTests.cpp b/tests/Kusama/TWAnySignerTests.cpp deleted file mode 100644 index d3bf8fd5adb..00000000000 --- a/tests/Kusama/TWAnySignerTests.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "proto/Polkadot.pb.h" -#include "uint256.h" -#include "../interface/TWTestUtilities.h" -#include - -#include - -using namespace TW; -using namespace TW::Polkadot; - -TEST(TWAnySignerKusama, Sign) { - auto key = parse_hex("0x8cdc538e96f460da9d639afc5c226f477ce98684d77fb31e88db74c1f1dd86b2"); - auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); - - Proto::SigningInput input; - input.set_block_hash(genesisHash.data(), genesisHash.size()); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(1); - input.set_spec_version(2019); - input.set_private_key(key.data(), key.size()); - input.set_network(Proto::Network::KUSAMA); - input.set_transaction_version(2); - - auto balanceCall = input.mutable_balance_call(); - auto& transfer = *balanceCall->mutable_transfer(); - auto value = store(uint256_t(10000000000)); - transfer.set_to_address("CtwdfrhECFs3FpvCGoiE4hwRC4UsSiM8WL899HjRdQbfYZY"); - transfer.set_value(value.data(), value.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeKusama); - - ASSERT_EQ(hex(output.encoded()), "350284f41296779fd61a5bed6c2f506cc6c9ea93d6aeb357b9c69717193f434ba24ae700cd78b46eff36c433e642d7e9830805aab4f43eef70067ef32c8b2a294c510673a841c5f8a6e8900c03be40cfa475ae53e6f8aa61961563cb7cc0fa169ef9630d00040004000e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc720700e40b5402"); -} diff --git a/tests/Kusama/TWCoinTypeTests.cpp b/tests/Kusama/TWCoinTypeTests.cpp deleted file mode 100644 index 3ea8f9284f5..00000000000 --- a/tests/Kusama/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWKusamaCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeKusama)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xcbe0c2e2851c1245bedaae4d52f06eaa6b4784b786bea2f0bff11af7715973dd")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeKusama, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("DbCNECPna3k6MXFWWNZa5jGsuWycqEE6zcUxZYkxhVofrFk")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeKusama, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeKusama)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeKusama)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeKusama), 12); - ASSERT_EQ(TWBlockchainKusama, TWCoinTypeBlockchain(TWCoinTypeKusama)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeKusama)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeKusama)); - assertStringsEqual(symbol, "KSM"); - assertStringsEqual(txUrl, "https://kusama.subscan.io/extrinsic/0xcbe0c2e2851c1245bedaae4d52f06eaa6b4784b786bea2f0bff11af7715973dd"); - assertStringsEqual(accUrl, "https://kusama.subscan.io/account/DbCNECPna3k6MXFWWNZa5jGsuWycqEE6zcUxZYkxhVofrFk"); - assertStringsEqual(id, "kusama"); - assertStringsEqual(name, "Kusama"); -} diff --git a/tests/Litecoin/TWCoinTypeTests.cpp b/tests/Litecoin/TWCoinTypeTests.cpp deleted file mode 100644 index 3d96044505d..00000000000 --- a/tests/Litecoin/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWLitecoinCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeLitecoin)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeLitecoin, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeLitecoin, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeLitecoin)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeLitecoin)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeLitecoin), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeLitecoin)); - ASSERT_EQ(0x32, TWCoinTypeP2shPrefix(TWCoinTypeLitecoin)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeLitecoin)); - assertStringsEqual(symbol, "LTC"); - assertStringsEqual(txUrl, "https://blockchair.com/litecoin/transaction/t123"); - assertStringsEqual(accUrl, "https://blockchair.com/litecoin/address/a12"); - assertStringsEqual(id, "litecoin"); - assertStringsEqual(name, "Litecoin"); -} diff --git a/tests/Metis/TWCoinTypeTests.cpp b/tests/Metis/TWCoinTypeTests.cpp deleted file mode 100644 index da3e515be54..00000000000 --- a/tests/Metis/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWMetisCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeMetis)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x422f2ebbede32d4434ad0cf0ae55d44a84e14d3d5725a760133255b42676d8ce")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeMetis, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xBe9E8Ec25866B21bA34e97b9393BCabBcB4A5C86")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeMetis, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeMetis)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeMetis)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeMetis), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeMetis)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeMetis)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeMetis)); - assertStringsEqual(symbol, "METIS"); - assertStringsEqual(txUrl, "https://andromeda-explorer.metis.io/tx/0x422f2ebbede32d4434ad0cf0ae55d44a84e14d3d5725a760133255b42676d8ce"); - assertStringsEqual(accUrl, "https://andromeda-explorer.metis.io/address/0xBe9E8Ec25866B21bA34e97b9393BCabBcB4A5C86"); - assertStringsEqual(id, "metis"); - assertStringsEqual(name, "Metis"); -} diff --git a/tests/Monacoin/TWCoinTypeTests.cpp b/tests/Monacoin/TWCoinTypeTests.cpp deleted file mode 100644 index e539e3924e2..00000000000 --- a/tests/Monacoin/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWMonacoinCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeMonacoin)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeMonacoin, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeMonacoin, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeMonacoin)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeMonacoin)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeMonacoin), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeMonacoin)); - ASSERT_EQ(0x37, TWCoinTypeP2shPrefix(TWCoinTypeMonacoin)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeMonacoin)); - assertStringsEqual(symbol, "MONA"); - assertStringsEqual(txUrl, "https://blockbook.electrum-mona.org/tx/t123"); - assertStringsEqual(accUrl, "https://blockbook.electrum-mona.org/address/a12"); - assertStringsEqual(id, "monacoin"); - assertStringsEqual(name, "Monacoin"); -} diff --git a/tests/Moonbeam/TWCoinTypeTests.cpp b/tests/Moonbeam/TWCoinTypeTests.cpp deleted file mode 100644 index 76083ca05e8..00000000000 --- a/tests/Moonbeam/TWCoinTypeTests.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWMoonbeamCoinType, TWCoinType) { - const auto coin = TWCoinTypeMoonbeam; - const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); - const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); - const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); - const auto chainId = WRAPS(TWCoinTypeChainId(coin)); - const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xb22a146c933e6e51affbfa5f712a266b5f5e92ae453cd2f252bcc3c36ff035a6")); - const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); - const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x201bb4f276C765dF7225e5A4153E17edD23a67eC")); - const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); - - assertStringsEqual(id, "moonbeam"); - assertStringsEqual(name, "Moonbeam"); - assertStringsEqual(symbol, "GLMR"); - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); - ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); - ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); - ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); - assertStringsEqual(chainId, "1284"); - assertStringsEqual(txUrl, "https://moonscan.io/tx/0xb22a146c933e6e51affbfa5f712a266b5f5e92ae453cd2f252bcc3c36ff035a6"); - assertStringsEqual(accUrl, "https://moonscan.io/address/0x201bb4f276C765dF7225e5A4153E17edD23a67eC"); -} diff --git a/tests/Moonriver/TWCoinTypeTests.cpp b/tests/Moonriver/TWCoinTypeTests.cpp deleted file mode 100644 index 8ef39efaf15..00000000000 --- a/tests/Moonriver/TWCoinTypeTests.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWMoonriverCoinType, TWCoinType) { - const auto coin = TWCoinTypeMoonriver; - const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); - const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); - const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); - const auto chainId = WRAPS(TWCoinTypeChainId(coin)); - const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x2e2daa3943ba65d9bbb910a4f6765aa6a466a0ef8935090547ca9d30e201e032")); - const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); - const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x899831D937937d011305E73EE782cce0455DF15a")); - const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); - - assertStringsEqual(id, "moonriver"); - assertStringsEqual(name, "Moonriver"); - assertStringsEqual(symbol, "MOVR"); - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); - ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); - ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); - ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); - assertStringsEqual(chainId, "1285"); - assertStringsEqual(txUrl, "https://moonriver.moonscan.io/tx/0x2e2daa3943ba65d9bbb910a4f6765aa6a466a0ef8935090547ca9d30e201e032"); - assertStringsEqual(accUrl, "https://moonriver.moonscan.io/address/0x899831D937937d011305E73EE782cce0455DF15a"); -} diff --git a/tests/NEAR/AccountTests.cpp b/tests/NEAR/AccountTests.cpp deleted file mode 100644 index 36d38993b78..00000000000 --- a/tests/NEAR/AccountTests.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "NEAR/Account.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::NEAR; - -TEST(NEARAccount, Validation) { - ASSERT_FALSE(Account::isValid("a")); - ASSERT_FALSE(Account::isValid("!?:")); - ASSERT_FALSE(Account::isValid("11111111111111111111111111111111222222222222222222222222222222223")); - - ASSERT_TRUE(Account::isValid("9902c136629fc630416e50d4f2fef6aff867ea7e.lockup.near")); - ASSERT_TRUE(Account::isValid("app_1.alice.near")); - ASSERT_TRUE(Account::isValid("test-trust.vlad.near")); - ASSERT_TRUE(Account::isValid("deadbeef")); -} diff --git a/tests/NEAR/AddressTests.cpp b/tests/NEAR/AddressTests.cpp deleted file mode 100644 index 1da6ca22089..00000000000 --- a/tests/NEAR/AddressTests.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "NEAR/Address.h" -#include "Base58.h" -#include "PrivateKey.h" -#include - -#include - -using namespace TW; -using namespace TW::NEAR; - -TEST(NEARAddress, Validation) { - ASSERT_FALSE(Address::isValid("abc")); - ASSERT_FALSE(Address::isValid("65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjTF")); - ASSERT_FALSE(Address::isValid("EOS65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjT")); - - ASSERT_TRUE(Address::isValid("NEAR2758Nk7CMUcxTwXdjVdSxNEidiZQWMZN3USJzj76q5ia3v2v2v")); - ASSERT_TRUE(Address::isValid("917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d")); -} - -TEST(NEARAddress, FromString) { - ASSERT_EQ( - Address("NEAR2758Nk7CMUcxTwXdjVdSxNEidiZQWMZN3USJzj76q5ia3v2v2v").string(), - "917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d" - ); - ASSERT_EQ( - Address("9685af3fe2dc231e5069ccff8ec6950eb961d42ebb9116a8ab9c0d38f9e45249").string(), - "9685af3fe2dc231e5069ccff8ec6950eb961d42ebb9116a8ab9c0d38f9e45249" - ); -} - -TEST(NEARAddress, FromPrivateKey) { - auto fullKey = Base58::bitcoin.decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); - auto key = PrivateKey(Data(fullKey.begin(), fullKey.begin() + 32)); - auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); - auto address = Address(publicKey); - - ASSERT_EQ(address.string(), "917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d"); -} diff --git a/tests/NEAR/SerializationTests.cpp b/tests/NEAR/SerializationTests.cpp deleted file mode 100644 index df4c1334c1b..00000000000 --- a/tests/NEAR/SerializationTests.cpp +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "Base58.h" -#include "proto/NEAR.pb.h" -#include "NEAR/Serialization.h" - -#include -#include - -namespace TW::NEAR { - -TEST(NEARSerialization, SerializeTransferTransaction) { - auto publicKey = Base58::bitcoin.decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); - - auto input = Proto::SigningInput(); - input.set_signer_id("test.near"); - input.set_nonce(1); - input.set_receiver_id("whatever.near"); - - input.add_actions(); - auto& transfer = *input.mutable_actions(0)->mutable_transfer(); - Data deposit(16, 0); - deposit[0] = 1; - transfer.set_deposit(deposit.data(), deposit.size()); - - auto blockHash = Base58::bitcoin.decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); - input.set_block_hash(blockHash.data(), blockHash.size()); - - auto privateKey = Base58::bitcoin.decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); - input.set_private_key(privateKey.data(), 32); - - auto serialized = transactionData(input); - auto serializedHex = hex(serialized); - - ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000301000000000000000000000000000000"); -} - -TEST(NEARSerialization, SerializeFunctionCallTransaction) { - auto publicKey = Base58::bitcoin.decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); - - auto input = Proto::SigningInput(); - input.set_signer_id("test.near"); - input.set_nonce(1); - input.set_receiver_id("whatever.near"); - - input.add_actions(); - auto& functionCall = *input.mutable_actions(0)->mutable_function_call(); - - functionCall.set_method_name("qqq"); - functionCall.set_gas(1000); - - Data deposit(16, 0); - deposit[0] = 1; - functionCall.set_deposit(deposit.data(), deposit.size()); - - Data args(3, 0); - args[0] = 1; - args[1] = 2; - args[2] = 3; - functionCall.set_args(args.data(), args.size()); - - auto blockHash = Base58::bitcoin.decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); - input.set_block_hash(blockHash.data(), blockHash.size()); - - auto privateKey = Base58::bitcoin.decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); - input.set_private_key(privateKey.data(), 32); - - auto serialized = transactionData(input); - auto serializedHex = hex(serialized); - - ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000020300000071717103000000010203e80300000000000001000000000000000000000000000000"); -} - -} diff --git a/tests/NEAR/SignerTests.cpp b/tests/NEAR/SignerTests.cpp deleted file mode 100644 index 2a41296484c..00000000000 --- a/tests/NEAR/SignerTests.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Base64.h" -#include "Base58.h" -#include "HexCoding.h" -#include "proto/NEAR.pb.h" -#include "NEAR/Signer.h" - -#include -#include - -namespace TW::NEAR { - -TEST(NEARSigner, SignTx) { - auto publicKey = Base58::bitcoin.decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); - - auto input = Proto::SigningInput(); - input.set_signer_id("test.near"); - input.set_nonce(1); - input.set_receiver_id("whatever.near"); - - input.add_actions(); - auto& transfer = *input.mutable_actions(0)->mutable_transfer(); - Data deposit(16, 0); - deposit[0] = 1; - // uint128_t / little endian byte order - transfer.set_deposit(deposit.data(), deposit.size()); - - auto blockHash = Base58::bitcoin.decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); - input.set_block_hash(blockHash.data(), blockHash.size()); - - auto privateKey = Base58::bitcoin.decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); - input.set_private_key(privateKey.data(), 32); - - auto output = Signer::sign(std::move(input)); - - auto signed_transaction = output.signed_transaction(); - auto outputInBase64 = Base64::encode(Data(signed_transaction.begin(), signed_transaction.end())); - - ASSERT_EQ(outputInBase64, "CQAAAHRlc3QubmVhcgCRez0mjUtY9/7BsVC9aNab4+5dTMOYVeNBU4Rlu3eGDQEAAAAAAAAADQAAAHdoYXRldmVyLm5lYXIPpHP9JpAd8pa+atxMxN800EDvokNSJLaYaRDmMML+9gEAAAADAQAAAAAAAAAAAAAAAAAAAACWmoMzIYbul1Xkg5MlUlgG4Ymj0tK7S0dg6URD6X4cTyLe7vAFmo6XExAO2m4ZFE2n6KDvflObIHCLodjQIb0B"); - ASSERT_EQ(hex(output.hash()), "eea6e680f3ea51a7f667e9a801d0bfadf66e03d41ed54975b3c6006351461b32"); -} - -} diff --git a/tests/NEAR/TWAnySignerTests.cpp b/tests/NEAR/TWAnySignerTests.cpp deleted file mode 100644 index c3dce258856..00000000000 --- a/tests/NEAR/TWAnySignerTests.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "proto/NEAR.pb.h" -#include "../interface/TWTestUtilities.h" -#include -#include - -namespace TW::NEAR { - -TEST(TWAnySignerNEAR, Sign) { - - auto privateKey = parse_hex("8737b99bf16fba78e1e753e23ba00c4b5423ac9c45d9b9caae9a519434786568"); - auto blockHash = parse_hex("0fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6"); - // uint128_t / little endian byte order - auto deposit = parse_hex("01000000000000000000000000000000"); - - Proto::SigningInput input; - input.set_signer_id("test.near"); - input.set_nonce(1); - input.set_receiver_id("whatever.near"); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_block_hash(blockHash.data(), blockHash.size()); - - auto& action = *input.add_actions(); - auto& transfer = *action.mutable_transfer(); - transfer.set_deposit(deposit.data(), deposit.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeNEAR); - - ASSERT_EQ(hex(output.signed_transaction()), "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000030100000000000000000000000000000000969a83332186ee9755e4839325525806e189a3d2d2bb4b4760e94443e97e1c4f22deeef0059a8e9713100eda6e19144da7e8a0ef7e539b20708ba1d8d021bd01"); - ASSERT_EQ(hex(output.hash()), "eea6e680f3ea51a7f667e9a801d0bfadf66e03d41ed54975b3c6006351461b32"); -} - -} // namespace TW::NEAR diff --git a/tests/NEAR/TWCoinTypeTests.cpp b/tests/NEAR/TWCoinTypeTests.cpp deleted file mode 100644 index e5b5951a368..00000000000 --- a/tests/NEAR/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWNEARCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNEAR)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("FPQAMaVnvFHNwNBJWnTttXfdJhp5FvMGGDJEesB8gvbL")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNEAR, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("test-trust.vlad.near")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNEAR, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNEAR)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNEAR)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNEAR), 24); - ASSERT_EQ(TWBlockchainNEAR, TWCoinTypeBlockchain(TWCoinTypeNEAR)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeNEAR)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeNEAR)); - assertStringsEqual(symbol, "NEAR"); - assertStringsEqual(txUrl, "https://explorer.near.org/transactions/FPQAMaVnvFHNwNBJWnTttXfdJhp5FvMGGDJEesB8gvbL"); - assertStringsEqual(accUrl, "https://explorer.near.org/accounts/test-trust.vlad.near"); - assertStringsEqual(id, "near"); - assertStringsEqual(name, "NEAR"); -} diff --git a/tests/NEO/AddressTests.cpp b/tests/NEO/AddressTests.cpp deleted file mode 100644 index fa6adb863c0..00000000000 --- a/tests/NEO/AddressTests.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "PublicKey.h" -#include "HexCoding.h" -#include "NEO/Address.h" -#include "NEO/Signer.h" - -#include - -using namespace std; -using namespace TW; -using namespace TW::NEO; - -TEST(NEOAddress, FromPublicKey) { - const auto publicKey = PublicKey(parse_hex("0222b2277d039d67f4197a638dd5a1d99c290b17aa8c4a16ccee5165fe612de66a"), TWPublicKeyTypeSECP256k1); - const auto address = Address(publicKey); - EXPECT_EQ(string("AKmrAHRD9ZDUnu4m3vWWonpsojo4vgSuqp"), address.string()); -} - -TEST(NEOAddress, FromString) { - string neoAddress = "AXkgwcMJTy9wTAXHsbyhauxh7t2Tt31MmC"; - const auto address = Address(neoAddress); - EXPECT_EQ(address.string(), neoAddress); -} - -TEST(NEOAddress, isValid) { - string neoAddress = "AQAsqiyHS4SSVWZ4CmMmnCxWg7vJ84GEj4"; - string bitcoinAddress = "1Ma2DrB78K7jmAwaomqZNRMCvgQrNjE2QC"; - - EXPECT_TRUE(Address::isValid(neoAddress)); - EXPECT_FALSE(Address::isValid(bitcoinAddress)); -} - -TEST(NEOAddress, validation) { - EXPECT_FALSE(Address::isValid("abc")); - EXPECT_FALSE(Address::isValid("abeb60f3e94c1b9a09f33669435e7ef12eacd")); - EXPECT_FALSE(Address::isValid("abcb60f3e94c9b9a09f33669435e7ef1beaedads")); - EXPECT_TRUE(Address::isValid("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD")); -} - -TEST(NEOAddress, fromPubKey) { - auto address = Address(PublicKey(parse_hex("031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"), TWPublicKeyTypeNIST256p1)); - EXPECT_EQ("AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5", address.string()); -} - -TEST(NEOAddress, fromString) { - auto b58Str = "AYTxeseHT5khTWhtWX1pFFP1mbQrd4q1zz"; - auto address = Address(b58Str); - EXPECT_EQ(b58Str, address.string()); - auto errB58Str = "AATxeseHT5khTWhtWX1pFFP1mbQrd4q1zz"; - EXPECT_THROW(new Address(errB58Str), std::invalid_argument); -} - - -TEST(NEOAddress, Valid) { - ASSERT_TRUE(Address::isValid("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD")); -} - -TEST(NEOAddress, Invalid) { - ASSERT_FALSE(Address::isValid("ANDfjwrUr54515515155WKRMyxFwvVwnZD")); -} - -TEST(NEOAddress, FromPrivateKey) { - auto key = PrivateKey(parse_hex("0x2A9EAB0FEC93CD94FA0A209AC5604602C1F0105FB02EAB398E17B4517C2FFBAB")); - auto publicKey = key.getPublicKey(TWPublicKeyTypeNIST256p1); - auto address = Address(publicKey); - ASSERT_EQ(address.string(), "AQCSMB3oSDA1dHPn6GXN6KB4NHmdo1fX41"); -} - diff --git a/tests/NEO/CoinReferenceTests.cpp b/tests/NEO/CoinReferenceTests.cpp deleted file mode 100644 index 9c707d5bdeb..00000000000 --- a/tests/NEO/CoinReferenceTests.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "uint256.h" -#include "HexCoding.h" -#include "NEO/CoinReference.h" - -#include - -using namespace std; -using namespace TW; -using namespace TW::NEO; - -TEST(NEOCoinReference, Serialize) { - auto coinReference = CoinReference(); - string prevHash = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"; - coinReference.prevHash = load(parse_hex(prevHash)); - coinReference.prevIndex = 1; - EXPECT_EQ(prevHash + "0100", hex(coinReference.serialize())); -} - -TEST(NEOCoinReference, Deserialize) { - auto coinReference = CoinReference(); - coinReference.deserialize(parse_hex("bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a0100")); - EXPECT_EQ("bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a", hex(store(coinReference.prevHash))); - EXPECT_EQ(1, coinReference.prevIndex); -} diff --git a/tests/NEO/SignerTests.cpp b/tests/NEO/SignerTests.cpp deleted file mode 100644 index 6f3b10fd913..00000000000 --- a/tests/NEO/SignerTests.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "PublicKey.h" -#include "HexCoding.h" -#include "NEO/Address.h" -#include "NEO/Signer.h" - -#include - -using namespace std; -using namespace TW; -using namespace TW::NEO; - -TEST(NEOSigner, FromPublicPrivateKey) { - auto hexPrvKey = "4646464646464646464646464646464646464646464646464646464646464646"; - auto hexPubKey = "031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"; - auto signer = Signer(PrivateKey(parse_hex(hexPrvKey))); - auto prvKey = signer.getPrivateKey(); - auto pubKey = signer.getPublicKey(); - - EXPECT_EQ(hexPrvKey, hex(prvKey.bytes)); - EXPECT_EQ(hexPubKey, hex(pubKey.bytes)); - - auto address = signer.getAddress(); - EXPECT_TRUE(Address::isValid(address.string())); - - EXPECT_EQ(Address(pubKey), address); -} - -TEST(NEOSigner, SigningData) { - auto signer = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); - auto verScript = "ba7908ddfe5a1177f2c9d3fa1d3dc71c9c289a3325b3bdd977e20c50136959ed02d1411efa5e8b897d970ef7e2325e6c0a3fdee4eb421223f0d86e455879a9ad"; - auto invocationScript = string("401642b3d538e138f34b32330e381a7fe3f5151fcf958f2030991e72e2e25043143e4a1ebd239634efba279c96fa0ab04a15aa15179d73a7ef5a886ac8a06af484401642b3d538e138f34b32330e381a7fe3f5151fcf958f2030991e72e2e25043143e4a1ebd239634efba279c96fa0ab04a15aa15179d73a7ef5a886ac8a06af484401642b3d538e138f34b32330e381a7fe3f5151fcf958f2030991e72e2e25043143e4a1ebd239634efba279c96fa0ab04a15aa15179d73a7ef5a886ac8a06af484"); - invocationScript = string(invocationScript.rbegin(), invocationScript.rend()); - - EXPECT_EQ(verScript, hex(signer.sign(parse_hex(invocationScript)))); -} - -TEST(NEOAccount, validity) { - auto hexPrvKey = "4646464646464646464646464646464646464646464646464646464646464646"; - auto hexPubKey = "031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"; - auto signer = Signer(PrivateKey(parse_hex(hexPrvKey))); - auto prvKey = signer.getPrivateKey(); - auto pubKey = signer.getPublicKey(); - EXPECT_EQ(hexPrvKey, hex(prvKey.bytes)); - EXPECT_EQ(hexPubKey, hex(pubKey.bytes)); -} - -TEST(NEOSigner, SigningTransaction) { - auto signer = Signer(PrivateKey(parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128"))); - auto transaction = Transaction(); - transaction.type = TransactionType::TT_ContractTransaction; - transaction.version = 0x00; - - CoinReference coin; - coin.prevHash = load(parse_hex("9c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de")); //reverse hash - coin.prevIndex = (uint16_t) 1; - transaction.inInputs.push_back(coin); - - { - TransactionOutput out; - out.assetId = load(parse_hex("9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5")); - out.value = (int64_t) 1 * 100000000; - auto scriptHash = TW::NEO::Address("Ad9A1xPbuA5YBFr1XPznDwBwQzdckAjCev").toScriptHash(); - out.scriptHash = load(scriptHash); - transaction.outputs.push_back(out); - } - - { - TransactionOutput out; - out.assetId = load(parse_hex("9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5")); - out.value = (int64_t) 892 * 100000000; - auto scriptHash = TW::NEO::Address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV").toScriptHash(); - out.scriptHash = load(scriptHash); - transaction.outputs.push_back(out); - } - signer.sign(transaction); - auto signedTx = transaction.serialize(); - EXPECT_EQ(hex(signedTx), "800000019c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de0100029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500fcbbc414000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac0141405046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb27e1fc01c1858576228f1fef8c0945a8ad69688e52a4ed19f5b85f5eff7e961d7232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); -} diff --git a/tests/NEO/TWAnySignerTests.cpp b/tests/NEO/TWAnySignerTests.cpp deleted file mode 100644 index 3bead1f67e6..00000000000 --- a/tests/NEO/TWAnySignerTests.cpp +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" -#include -#include "HexCoding.h" -#include "proto/NEO.pb.h" - -#include - -using namespace TW; -using namespace TW::NEO; - -Proto::SigningInput createInput() { - const std::string NEO_ASSET_ID = "9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5"; - const std::string GAS_ASSET_ID = "e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60"; - - Proto::SigningInput input; - auto privateKey = parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128"); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_fee(12345); //too low - input.set_gas_asset_id(GAS_ASSET_ID); - input.set_gas_change_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); - -#define ADD_UTXO_INPUT(hash, index , value, assetId) \ - { \ - auto utxo = input.add_inputs(); \ - utxo->set_prev_hash(parse_hex(hash).data(), parse_hex(hash).size()); \ - utxo->set_prev_index(index); \ - utxo->set_asset_id(assetId); \ - utxo->set_value(value); \ - } - - ADD_UTXO_INPUT("c61508268c5d0343af1875c60e569493100824dbdba108b31789e0e33bcb50fb", 1, 98899890000, GAS_ASSET_ID); - ADD_UTXO_INPUT("4eb2f96937a0d4dc96b77ba69a29e1de9574cbd62b16d881f1ee2061a291d70b", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("3fee0109d155dcfab272176117306b45b176914c88e8c379933c246a9e29ea0b", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("6ea9ce8c578bfeeecdf281f498e2a764689df3b93d6855a3cc45bd6b5213c426", 0, 400000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("f75ad3cbd277d83ee240e08f99a97ffd7e42a82a868e0f7043414f6d6147262b", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("054734e98f442b3e73a940ca8f594859ece1c7ddac14130b0e2f5e2799b85931", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("8b0c42d448912fc28c674fdcf8e21e4667d7d2133666168eaa0570488a9c5036", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 1, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 2, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 3, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 4, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 5, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 6, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 7, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 8, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 9, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("cf83bce600626b6077e136581c1aecc78a0bbb7d7649b1f580b6be881087ec40", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("9bd7572ba8df685e262369897d24f7217b42be496b9eed16e16a889dd83b394e", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("b4ee250397dde2f1001d782d3c803c38992447d3b351cdc9bf20cfaa2cbf995b", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("e1019ca259a1615f77263324156a70007b76cb4f26b01b2956b8f85e6842ac62", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("bd379df2aca526ac600919aaba0e59d4a1ad4e2f22d18966063cf45e431d016f", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("164c3f843b9b7bfa6a7376a1548f343acb5cdfa0193b8f31e8c9a647ea63ea7d", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("4acec74a76161eafe70e0791b1f504b5ba1d175fd4f340d5bf56804e25505e92", 0, 300000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("895c6629a71c84cbdc8956abea9ca2d9d215e909e6173b1a1a96289186a67796", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("54828143c4c3a0e1b09102e4ed29220b141089c2bc4200b1042eeb12e5e49296", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("5345e4abc86f7ace47112f5a91c129175833bafcaf9f1e1bcbbaf4d019c1c69d", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("c83e19d0d4210df97b3bc7768dc7184ae3acfc1b5b3ac9b05d2be0fe5a636b9f", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("3456b03f5cb688ce26ab1d09b7a15799136c8c886ca7c3c6bcb2363e61bb1bb1", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 10, 34000000000, NEO_ASSET_ID); - // all inputs below must be unused in this tx - ADD_UTXO_INPUT("e5a7887521b8b3aaf2d5426617ddabe8ef8ea3eab31c80a977c3b8f339df5be0", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("1455e9dd3cd6a04d81cd47acc07a7335212029ebbdcd0abc3e52c33f8b77f6eb", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("da711260085211b5573801d0dfe064235c69e61a55f9c15449ac55cc02b9adee", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("04486cfed371103dd51a89205b2c8bcc45ad887c49a768a62465f35810437bef", 0, 500000000, NEO_ASSET_ID); - ADD_UTXO_INPUT("a5f27055a442db0e65103561900456d37af4233267960daded870c1ab2219ef4", 0, 500000000, NEO_ASSET_ID); - - { - auto output = input.add_outputs(); - output->set_asset_id(NEO_ASSET_ID); - output->set_to_address("Ad9A1xPbuA5YBFr1XPznDwBwQzdckAjCev"); - output->set_change_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); - output->set_amount(25000000000); - } - - return input; -} - -TEST(TWAnySignerNEO, Sign) { - Proto::SigningInput input = createInput(); - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeNEO); - - // https://testnet-explorer.o3.network/transactions/0x7b138c753c24f474d0f70af30a9d79756e0ee9c1f38c12ed07fbdf6fc5132eaf - ASSERT_EQ(hex(output.encoded()), "8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf122956bc88746dc666759a2d67f120fe3ce1659f916d22a91e0b02421d3bddbd1232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); -} - -TEST(TWAnySignerNEO, Plan) { - Proto::SigningInput input = createInput(); - Proto::TransactionPlan plan; - ANY_PLAN(input, plan, TWCoinTypeNEO); - - EXPECT_EQ(plan.inputs_size(), 30); - EXPECT_EQ(plan.outputs_size(), 2); - EXPECT_EQ(plan.fee(), 1408000); - EXPECT_EQ(plan.error(), Common::Proto::OK); -} diff --git a/tests/NEO/TWCoinTypeTests.cpp b/tests/NEO/TWCoinTypeTests.cpp deleted file mode 100644 index 8b986b73a4d..00000000000 --- a/tests/NEO/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWNEOCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNEO)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("e0ddf7c81c732df26180aca0c36d5868ad009fdbbe6e7a56ebafc14bba41cd53")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNEO, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("AcxuqWhTureEQGeJgbmtSWNAtssjMLU7pb")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNEO, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNEO)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNEO)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNEO), 8); - ASSERT_EQ(TWBlockchainNEO, TWCoinTypeBlockchain(TWCoinTypeNEO)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeNEO)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeNEO)); - assertStringsEqual(symbol, "NEO"); - assertStringsEqual(txUrl, "https://neoscan.io/transaction/e0ddf7c81c732df26180aca0c36d5868ad009fdbbe6e7a56ebafc14bba41cd53"); - assertStringsEqual(accUrl, "https://neoscan.io/address/AcxuqWhTureEQGeJgbmtSWNAtssjMLU7pb"); - assertStringsEqual(id, "neo"); - assertStringsEqual(name, "NEO"); -} \ No newline at end of file diff --git a/tests/NEO/TWNEOAddressTests.cpp b/tests/NEO/TWNEOAddressTests.cpp deleted file mode 100644 index 029f3bdb95b..00000000000 --- a/tests/NEO/TWNEOAddressTests.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include -#include -#include "HexCoding.h" - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; - -#include - - -TEST(NEO, ExtendedKeys) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( - STRING("client recycle grass verb guitar battle abstract table they swamp accuse athlete recall ski light").get(), - STRING("NEO").get() - )); - - auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeNEO, TWHDVersionXPUB)); - auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeNEO, TWHDVersionXPRV)); - - assertStringsEqual(xpub, "xpub6CEwQUwgAnJmxNkb7CxuJGZN8FQNnqkKWeFjHqBEsD6PN267g3yNejdZyNEALzM7CxbQbtBzmndRjhvKyQDZoP8JrBLBQ8DJbhS1ge9Ln6F"); - assertStringsEqual(xprv, "xprv9yFazyQnLQkUjtg81BRtw8cdaDZtPP2U9RL8VSmdJsZQVDky8Wf86wK687witsCZhYZCaRALSbGRVbLBuzDzbp6dpJFqnjnvNbiNV4JgrNY"); -} \ No newline at end of file diff --git a/tests/NEO/TransactionAttributeTests.cpp b/tests/NEO/TransactionAttributeTests.cpp deleted file mode 100644 index b99ca38141b..00000000000 --- a/tests/NEO/TransactionAttributeTests.cpp +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "uint256.h" -#include "HexCoding.h" -#include "NEO/ReadData.h" -#include "NEO/TransactionAttribute.h" -#include "NEO/TransactionAttributeUsage.h" - -#include -#include - -using namespace std; -using namespace TW; -using namespace TW::NEO; - -TEST(NEOTransactionAttribute, Serialize) { - auto transactionAttribute = TransactionAttribute(); - string data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"; - transactionAttribute.usage = TransactionAttributeUsage::TAU_ContractHash; - transactionAttribute.data = parse_hex(data); - EXPECT_EQ("00" + data, hex(transactionAttribute.serialize())); - - data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4b"; - transactionAttribute.usage = TransactionAttributeUsage::TAU_Vote; - transactionAttribute.data = parse_hex(data); - EXPECT_EQ("30" + data, hex(transactionAttribute.serialize())); - - transactionAttribute.usage = TransactionAttributeUsage::TAU_ECDH02; - transactionAttribute.data = parse_hex(data); - EXPECT_EQ("02" + data, hex(transactionAttribute.serialize())); - - data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af"; - transactionAttribute.usage = TransactionAttributeUsage::TAU_Script; - transactionAttribute.data = parse_hex(data); - EXPECT_EQ("20" + data, hex(transactionAttribute.serialize())); - - data = "bd"; - transactionAttribute.usage = TransactionAttributeUsage::TAU_DescriptionUrl; - transactionAttribute.data = parse_hex(data); - EXPECT_EQ("81" + data, hex(transactionAttribute.serialize())); - - data = "bdecbb623eee6f9ade28d5a8ff5fb3ea"; - transactionAttribute.usage = TransactionAttributeUsage::TAU_Remark; - transactionAttribute.data = parse_hex(data); - EXPECT_EQ("f0" + data, hex(transactionAttribute.serialize())); -} - -TEST(NEOTransactionAttribute, Deserialize) { - auto transactionAttribute = TransactionAttribute(); - string data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"; - transactionAttribute.deserialize(parse_hex("00" + data)); - EXPECT_EQ(TransactionAttributeUsage::TAU_ContractHash, transactionAttribute.usage); - EXPECT_EQ(data, hex(transactionAttribute.data)); - - data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4b"; - transactionAttribute.deserialize(parse_hex("30" + data)); - EXPECT_EQ(TransactionAttributeUsage::TAU_Vote, transactionAttribute.usage); - EXPECT_EQ(data, hex(transactionAttribute.data)); - - transactionAttribute.deserialize(parse_hex("02" + data)); - EXPECT_EQ(TransactionAttributeUsage::TAU_ECDH02, transactionAttribute.usage); - EXPECT_EQ(data, hex(transactionAttribute.data)); - - data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af"; - transactionAttribute.deserialize(parse_hex("20" + data)); - EXPECT_EQ(TransactionAttributeUsage::TAU_Script, transactionAttribute.usage); - EXPECT_EQ(data, hex(transactionAttribute.data)); - - data = "bd"; - transactionAttribute.deserialize(parse_hex("81" + data)); - EXPECT_EQ(TransactionAttributeUsage::TAU_DescriptionUrl, transactionAttribute.usage); - EXPECT_EQ(data, hex(transactionAttribute.data)); - - data = "bdecbb623eee6f9ade28d5a8ff5fb3ea"; - transactionAttribute.deserialize(parse_hex("f0" + data)); - EXPECT_EQ(TransactionAttributeUsage::TAU_Remark, transactionAttribute.usage); - EXPECT_EQ(data, hex(transactionAttribute.data)); - - EXPECT_THROW(transactionAttribute.deserialize(parse_hex("b1" + data)), std::invalid_argument); -} - -TEST(NEOTransactionAttribute, DeserializeInitialPositionAfterData) { - auto transactionAttribute = TransactionAttribute(); - EXPECT_THROW(transactionAttribute.deserialize(Data(), 1), std::invalid_argument); - - EXPECT_THROW(transactionAttribute.deserialize(Data({1}), 2), std::invalid_argument); -} diff --git a/tests/NEO/TransactionOutputTests.cpp b/tests/NEO/TransactionOutputTests.cpp deleted file mode 100644 index 5c695cd9aab..00000000000 --- a/tests/NEO/TransactionOutputTests.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "uint256.h" -#include "HexCoding.h" -#include "NEO/TransactionOutput.h" - -#include -#include - -using namespace std; -using namespace TW; -using namespace TW::NEO; - -TEST(NEOTransactionOutput, Serialize) { - auto transactionOutput = TransactionOutput(); - string assetId = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"; - string scriptHash = "cbb23e6f9ade28d5a8ff3eac9d73af039e821b1b"; - transactionOutput.value = 1L; - transactionOutput.assetId = load(parse_hex(assetId)); - transactionOutput.scriptHash = load(parse_hex(scriptHash)); - EXPECT_EQ(assetId + "0100000000000000" + scriptHash, hex(transactionOutput.serialize())); - - transactionOutput.value = 0xff01; - EXPECT_EQ(assetId + "01ff000000000000" + scriptHash, hex(transactionOutput.serialize())); -} - -TEST(NEOTransactionOutput, Deserialize) { - string assetId = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"; - string scriptHash = "cbb23e6f9ade28d5a8ff3eac9d73af039e821b1b"; - auto transactionOutput = TransactionOutput(); - transactionOutput.deserialize(parse_hex(assetId + "0100000000000000" + scriptHash)); - EXPECT_EQ(1, transactionOutput.value); - EXPECT_EQ(assetId, hex(store(transactionOutput.assetId))); - EXPECT_EQ(scriptHash, hex(store(transactionOutput.scriptHash))); - - transactionOutput.deserialize(parse_hex(assetId + "01ff000000000000" + scriptHash)); - EXPECT_EQ(0xff01, transactionOutput.value); - EXPECT_EQ(assetId, hex(store(transactionOutput.assetId))); - EXPECT_EQ(scriptHash, hex(store(transactionOutput.scriptHash))); -} diff --git a/tests/NEO/TransactionTests.cpp b/tests/NEO/TransactionTests.cpp deleted file mode 100644 index 1c0aa39ecf5..00000000000 --- a/tests/NEO/TransactionTests.cpp +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "uint256.h" -#include "HexCoding.h" -#include "NEO/Transaction.h" -#include "NEO/TransactionType.h" -#include "NEO/TransactionAttributeUsage.h" -#include "NEO/TransactionAttribute.h" - -#include -#include - -using namespace std; -using namespace TW; -using namespace TW::NEO; - -TEST(NEOTransaction, SerializeDeserializeEmpty) { - auto transaction = Transaction(); - EXPECT_EQ(transaction, transaction); - - EXPECT_EQ(0, transaction.attributes.size()); - EXPECT_EQ(0, transaction.inInputs.size()); - EXPECT_EQ(0, transaction.outputs.size()); - auto serialized = transaction.serialize(); - - auto deserializedTransaction = Transaction(); - deserializedTransaction.deserialize(serialized); - EXPECT_EQ(transaction, deserializedTransaction); -} - -TEST(NEOTransaction, SerializeDeserializeEmptyCollections) { - auto transaction = Transaction(); - transaction.type = TransactionType::TT_EnrollmentTransaction; - transaction.version = 0x07; - const string zeroVarLong = "00"; - auto serialized = transaction.serialize(); - EXPECT_EQ("2007" + zeroVarLong + zeroVarLong + zeroVarLong, hex(serialized)); - - auto deserializedTransaction = Transaction(); - deserializedTransaction.deserialize(serialized); - EXPECT_EQ(transaction, deserializedTransaction); - EXPECT_EQ(transaction, transaction); -} - -TEST(NEOTransaction, SerializeDeserializeAttribute) { - auto transaction = Transaction(); - transaction.type = TransactionType::TT_ContractTransaction; - transaction.version = 0x07; - const string zeroVarLong = "00"; - const string oneVarLong = "01"; - transaction.attributes.push_back(TransactionAttribute()); - transaction.attributes[0].usage = TransactionAttributeUsage::TAU_ContractHash; - transaction.attributes[0].data = parse_hex("bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"); - auto serialized = transaction.serialize(); - EXPECT_EQ("8007" + oneVarLong + hex(transaction.attributes[0].serialize()) + zeroVarLong + zeroVarLong, hex(serialized)); - - auto deserializedTransaction = Transaction(); - deserializedTransaction.deserialize(serialized); - EXPECT_EQ(transaction, deserializedTransaction); - - transaction.attributes.push_back(TransactionAttribute()); - transaction.attributes[1].usage = TransactionAttributeUsage::TAU_ECDH02; - transaction.attributes[1].data = parse_hex("b7ecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"); - serialized = transaction.serialize(); - const string twoVarLong = "02"; - string expectedSerialized = "8007" + twoVarLong; - expectedSerialized += hex(transaction.attributes[0].serialize()); - expectedSerialized += hex(transaction.attributes[1].serialize()); - expectedSerialized += zeroVarLong + zeroVarLong; - EXPECT_EQ(expectedSerialized, hex(serialized)); - - deserializedTransaction.deserialize(serialized); - EXPECT_EQ(transaction, deserializedTransaction); - EXPECT_EQ(transaction, transaction); -} - -TEST(NEOTransaction, SerializeDeserializeInputs) { - auto transaction = Transaction(); - transaction.type = TransactionType::TT_ContractTransaction; - transaction.version = 0x07; - const string zeroVarLong = "00"; - const string oneVarLong = "01"; - transaction.inInputs.push_back(CoinReference()); - transaction.inInputs[0].prevHash = load(parse_hex("bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a")); - transaction.inInputs[0].prevIndex = 0xa; - auto serialized = transaction.serialize(); - EXPECT_EQ("8007" + zeroVarLong + oneVarLong + hex(transaction.inInputs[0].serialize()) + zeroVarLong, hex(serialized)); - - auto deserializedTransaction = Transaction(); - deserializedTransaction.deserialize(serialized); - EXPECT_EQ(transaction, deserializedTransaction); - - transaction.inInputs.push_back(CoinReference()); - transaction.inInputs[1].prevHash = load(parse_hex("bdecbb623eee4f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a")); - transaction.inInputs[1].prevIndex = 0xbc; - serialized = transaction.serialize(); - const string twoVarLong = "02"; - string expectedSerialized = "8007" + zeroVarLong + twoVarLong; - expectedSerialized += hex(transaction.inInputs[0].serialize()); - expectedSerialized += hex(transaction.inInputs[1].serialize()); - expectedSerialized += zeroVarLong; - EXPECT_EQ(expectedSerialized, hex(serialized)); - - deserializedTransaction.deserialize(serialized); - EXPECT_EQ(transaction, deserializedTransaction); - EXPECT_EQ(transaction, transaction); -} - -TEST(NEOTransaction, SerializeDeserializeOutputs) { - auto transaction = Transaction(); - transaction.type = TransactionType::TT_ContractTransaction; - transaction.version = 0x07; - const string zeroVarLong = "00"; - const string oneVarLong = "01"; - transaction.outputs.push_back(TransactionOutput()); - transaction.outputs[0].assetId = load(parse_hex("bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a")); - transaction.outputs[0].scriptHash = load(parse_hex("cbb23e6f9ade28d5a8ff3eac9d73af039e821b1b")); - transaction.outputs[0].value = 0x2; - auto serialized = transaction.serialize(); - EXPECT_EQ("8007" + zeroVarLong + zeroVarLong + oneVarLong + hex(transaction.outputs[0].serialize()), hex(serialized)); - - auto deserializedTransaction = Transaction(); - deserializedTransaction.deserialize(serialized); - EXPECT_EQ(transaction, deserializedTransaction); - - transaction.outputs.push_back(TransactionOutput()); - transaction.outputs[1].assetId = load(parse_hex("bdecbb623eee6a9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a")); - transaction.outputs[1].scriptHash = load(parse_hex("cbb23e6f9a3e28d5a8ff3eac9d73af039e821b1b")); - transaction.outputs[1].value = 0x2; - serialized = transaction.serialize(); - const string twoVarLong = "02"; - string expectedSerialized = "8007" + zeroVarLong + zeroVarLong + twoVarLong; - expectedSerialized += hex(transaction.outputs[0].serialize()); - expectedSerialized += hex(transaction.outputs[1].serialize()); - EXPECT_EQ(expectedSerialized, hex(serialized)); - - deserializedTransaction.deserialize(serialized); - EXPECT_EQ(transaction, deserializedTransaction); -} - -TEST(NEOTransaction, SerializeDeserialize) { - auto transaction = Transaction(); - transaction.type = TransactionType::TT_ContractTransaction; - transaction.version = 0x07; - const string oneVarLong = "01"; - - transaction.attributes.push_back(TransactionAttribute()); - transaction.attributes[0].usage = TransactionAttributeUsage::TAU_ContractHash; - transaction.attributes[0].data = parse_hex("bdecbb623eee6f9ade28d5a8ff5fbdea9c9d73af039e0286201b3b0291fb4d4a"); - - transaction.inInputs.push_back(CoinReference()); - transaction.inInputs[0].prevHash = load(parse_hex("bdecbb623eee679ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a")); - transaction.inInputs[0].prevIndex = 0xa; - - transaction.outputs.push_back(TransactionOutput()); - transaction.outputs[0].assetId = load(parse_hex("bdecbb623eee6f9ad328d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a")); - transaction.outputs[0].scriptHash = load(parse_hex("cbb23e6f9ade28a5a8ff3eac9d73af039e821b1b")); - transaction.outputs[0].value = 0x2; - - auto serialized = transaction.serialize(); - string expectedSerialized = "8007"; - expectedSerialized += oneVarLong + hex(transaction.attributes[0].serialize()); - expectedSerialized += oneVarLong + hex(transaction.inInputs[0].serialize()); - expectedSerialized += oneVarLong + hex(transaction.outputs[0].serialize()); - ASSERT_EQ(expectedSerialized, hex(serialized)); - - auto deserializedTransaction = Transaction(); - deserializedTransaction.deserialize(serialized); - EXPECT_EQ(transaction, deserializedTransaction); - - transaction.outputs.push_back(TransactionOutput()); - transaction.outputs[1].assetId = load(parse_hex("bdecbb623eee6a9a3e28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a")); - transaction.outputs[1].scriptHash = load(parse_hex("cbb23e6f9a3e28d5a8ff3eac9da3af039e821b1b")); - transaction.outputs[1].value = 0x2; - serialized = transaction.serialize(); - const string twoVarLong = "02"; - expectedSerialized = "8007"; - expectedSerialized += oneVarLong + hex(transaction.attributes[0].serialize()); - expectedSerialized += oneVarLong + hex(transaction.inInputs[0].serialize()); - expectedSerialized += twoVarLong + hex(transaction.outputs[0].serialize()); - expectedSerialized += hex(transaction.outputs[1].serialize()); - EXPECT_EQ(expectedSerialized, hex(serialized)); - - deserializedTransaction.deserialize(serialized); - EXPECT_EQ(transaction, deserializedTransaction); - - transaction.inInputs.push_back(CoinReference()); - transaction.inInputs[1].prevHash = load(parse_hex("bdecbb623e3e6f9ade28d5a8ff4fb3ea9c9d73af039e0286201b3b0291fb4d4a")); - transaction.inInputs[1].prevIndex = 0xbc; - transaction.inInputs.push_back(CoinReference()); - transaction.inInputs[2].prevHash = load(parse_hex("bdecbb624eee6f9ade28d5a8ff3fb3ea9c9d73af039e0286201b3b0291fb4d4a")); - transaction.inInputs[2].prevIndex = 0x1f; - - serialized = transaction.serialize(); - const string threeVarLong = "03"; - expectedSerialized = "8007"; - expectedSerialized += oneVarLong + hex(transaction.attributes[0].serialize()); - expectedSerialized += threeVarLong + hex(transaction.inInputs[0].serialize()); - expectedSerialized += hex(transaction.inInputs[1].serialize()); - expectedSerialized += hex(transaction.inInputs[2].serialize()); - expectedSerialized += twoVarLong + hex(transaction.outputs[0].serialize()); - expectedSerialized += hex(transaction.outputs[1].serialize()); - EXPECT_EQ(expectedSerialized, hex(serialized)); - - deserializedTransaction.deserialize(serialized); - EXPECT_EQ(transaction, deserializedTransaction); -} - -TEST(NEOTransaction, SerializeDeserializeMiner) { - string block2tn = "0000d11f7a2800000000"; - std::unique_ptr deserializedTransaction(Transaction::deserializeFrom(parse_hex(block2tn))); - auto serialized = deserializedTransaction->serialize(); - std::unique_ptr serializedTransaction(Transaction::deserializeFrom(serialized)); - - EXPECT_EQ(*deserializedTransaction, *serializedTransaction); - - string notMiner = "1000d11f7a2800000000"; - EXPECT_THROW( - std::unique_ptr deserializedTransaction(Transaction::deserializeFrom(parse_hex(notMiner))), - std::invalid_argument - ); -} - -TEST(NEOTransaction, GetHash) { - string block2tn = "0000d11f7a2800000000"; - std::unique_ptr deserializedTransaction(Transaction::deserializeFrom(parse_hex(block2tn))); - - Data hash = parse_hex("8e3a32ba3a7e8bdb0ad9a2ad064713e45bd20eb0dab0d2e77df5b5ce985276d0"); - // It is flipped on the https://github.com/NeoResearch/neopt/blob/master/tests/ledger_Tests/Transaction.Test.cpp - hash = Data(hash.rbegin(), hash.rend()); - - EXPECT_EQ(hex(hash), hex(deserializedTransaction->getHash())); -} - -TEST(NEOTransaction, SerializeSize) { - auto transaction = Transaction(); - transaction.type = TransactionType::TT_EnrollmentTransaction; - transaction.version = 0x07; - const string zeroVarLong = "00"; - auto serialized = transaction.serialize(); - auto verSerialized = parse_hex("2007" + zeroVarLong + zeroVarLong + zeroVarLong); - EXPECT_EQ(hex(verSerialized), hex(serialized)); - EXPECT_EQ(verSerialized, serialized); - - EXPECT_EQ(serialized.size(), transaction.size()); -} diff --git a/tests/NULS/AddressTests.cpp b/tests/NULS/AddressTests.cpp deleted file mode 100644 index 9adba2e43e9..00000000000 --- a/tests/NULS/AddressTests.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -#include "NULS/Address.h" - -#include "HexCoding.h" -#include "PrivateKey.h" -#include - -using namespace TW; -using namespace TW::NULS; - - -TEST(NULSAddress, StaticInvalid) { - ASSERT_FALSE(Address::isValid("abc")); - ASSERT_FALSE(Address::isValid("aaeb60f3e94c9b9a09f33669435e7ef1beaed")); - ASSERT_FALSE(Address::isValid("NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z")); - ASSERT_TRUE(Address::isValid("NULSd6HgUxmcJWc88iELEJ7RH9XHsazBQqnJc")); - ASSERT_TRUE(Address::isValid("NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z2")); -} - -TEST(NULSAddress, ChainID) { - const auto address = Address("NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z2"); - ASSERT_TRUE(address.chainID() == 1); -} - -TEST(NULSAddress, Type) { - const auto address = Address("NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z2"); - ASSERT_TRUE(address.type() == 1); -} - -TEST(NULSAddress, FromString) { - const auto address = Address("NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z2"); - ASSERT_EQ(address.string(), "NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z2"); -} - -TEST(NULSAddress, FromPrivateKey) { - const auto privateKey = - PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")); - const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const auto address = Address(publicKey); - - ASSERT_EQ(address.string(), "NULSd6HghWa4CN5qdxqMwYVikQxRZyj57Jn4L"); -} - -TEST(NULSAddress, FromCompressedPublicKey) { - const auto publicKey = - PublicKey(parse_hex("0244d50ff36c3136b4bf81f0c74b066695bc2af43e28d7f0ca1d48fcfd084bea66"), TWPublicKeyTypeSECP256k1); - const auto address = Address(publicKey); - - ASSERT_EQ(address.string(), "NULSd6HgUiMKPNi221bPfqvvho8QpuYBvn1x3"); -} - -TEST(NULSAddress, FromPrivateKey33) { - const auto privateKey = PrivateKey(parse_hex("d77580833f0b3c35b7114c23d6b66790d726c308baf237ec8c369152f2c08d27")); - const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const auto address = Address(publicKey); - - ASSERT_EQ(address.string(), "NULSd6HgXx8YkwEjePLWUmdRSZzPQzK6BXnsB"); -} diff --git a/tests/NULS/TWAnySignerTests.cpp b/tests/NULS/TWAnySignerTests.cpp deleted file mode 100644 index bfab5b9177b..00000000000 --- a/tests/NULS/TWAnySignerTests.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "proto/NULS.pb.h" -#include -#include "uint256.h" - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; -using namespace TW::NULS; - -TEST(TWAnySignerNULS, Sign) { - auto privateKey = parse_hex("0x9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b"); - auto amount = store(uint256_t(10000000)); - auto balance = store(uint256_t(100000000)); - std::string nonce = "0000000000000000"; - Proto::SigningInput input; - - input.set_from("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H"); - input.set_to("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); - input.set_amount(amount.data(), amount.size()); - input.set_chain_id(1); - input.set_idassets_id(1); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_balance(balance.data(), balance.size()); - input.set_timestamp(1569228280); - input.set_nonce(nonce.data(), nonce.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeNULS); - - EXPECT_EQ(hex(output.encoded()), "0200f885885d00008c0117010001f7ec6473df12e751d64cf20a8baa7edd50810f8101000100201d9a0000000000000000000000000000000000000000000000000000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120100010080969800000000000000000000000000000000000000000000000000000000000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae143c1d7e3463044022028019c0099e2233c7adb84bb03a9a5666ece4a5b65a026a090fa460f3679654702204df0fcb8762b5944b3aba033fa1a287ccb098150035dd8b66f52dc58d3d0843a"); -} diff --git a/tests/NULS/TWCoinTypeTests.cpp b/tests/NULS/TWCoinTypeTests.cpp deleted file mode 100644 index 3d048c09faf..00000000000 --- a/tests/NULS/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWNULSCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNULS)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNULS, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNULS, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNULS)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNULS)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNULS), 8); - ASSERT_EQ(TWBlockchainNULS, TWCoinTypeBlockchain(TWCoinTypeNULS)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeNULS)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeNULS)); - assertStringsEqual(symbol, "NULS"); - assertStringsEqual(txUrl, "https://nulscan.io/transaction/info?hash=t123"); - assertStringsEqual(accUrl, "https://nulscan.io/address/info?address=a12"); - assertStringsEqual(id, "nuls"); - assertStringsEqual(name, "NULS"); -} diff --git a/tests/Nano/AddressTests.cpp b/tests/Nano/AddressTests.cpp deleted file mode 100644 index 8812a0c152b..00000000000 --- a/tests/Nano/AddressTests.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2019 Mart Roosmaa. -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Nano/Address.h" -#include "HexCoding.h" - -#include - -using namespace std; -using namespace TW; -using namespace TW::Nano; - -TEST(NanoAddress, FromPublicKey) { - { - const auto publicKey = PublicKey(parse_hex("5114aad86a390897d2a91b33b931b3a59a7df9e63eb3694f9430122f5622ae50"), TWPublicKeyTypeED25519Blake2b); - const auto address = Address(publicKey); - ASSERT_EQ(string("nano_1nanode8ngaakzbck8smq6ru9bethqwyehomf79sae1k7xd47dkidjqzffeg"), address.string()); - } - - { - const auto publicKey = PublicKey(parse_hex("03e20ec6b4a39a629815ae02c0a1393b9225e3b890cae45b59f42fa29be9668d"), TWPublicKeyTypeED25519); - ASSERT_THROW(Address address(publicKey), std::invalid_argument); - } -} - -TEST(NanoAddress, FromString) { - { - string nanoAddress = "nano_1nanode8ngaakzbck8smq6ru9bethqwyehomf79sae1k7xd47dkidjqzffeg"; - const auto address = Address(nanoAddress); - ASSERT_EQ(address.string(), nanoAddress); - ASSERT_EQ(hex(address.bytes), "5114aad86a390897d2a91b33b931b3a59a7df9e63eb3694f9430122f5622ae50"); - } - - { - string xrbAddress = "xrb_1111111111111111111111111111111111111111111111111111hifc8npp"; - string nanoAddress = "nano_1111111111111111111111111111111111111111111111111111hifc8npp"; - const auto address = Address(xrbAddress); - ASSERT_EQ(address.string(), nanoAddress); - ASSERT_EQ(hex(address.bytes), "0000000000000000000000000000000000000000000000000000000000000000"); - } -} - -TEST(NanoAddress, isValid) { - string nanodeAddress = "nano_1nanode8ngaakzbck8smq6ru9bethqwyehomf79sae1k7xd47dkidjqzffeg"; - string faultyChecksumAddress = "xrb_1111111111111111111111111111111111111111111111111111hi111111"; - string bitcoinAddress = "1Ma2DrB78K7jmAwaomqZNRMCvgQrNjE2QC"; - - ASSERT_TRUE(Address::isValid(nanodeAddress)); - ASSERT_FALSE(Address::isValid(faultyChecksumAddress)); - ASSERT_FALSE(Address::isValid(bitcoinAddress)); -} diff --git a/tests/Nano/SignerTests.cpp b/tests/Nano/SignerTests.cpp deleted file mode 100644 index 30dd6845de7..00000000000 --- a/tests/Nano/SignerTests.cpp +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright © 2019 Mart Roosmaa. -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Nano/Signer.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Nano; - -const std::string kPrivateKey{"173c40e97fe2afcd24187e74f6b603cb949a5365e72fbdd065a6b165e2189e34"}; -const std::string kRepOfficial1{"xrb_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4"}; -const std::string kRepNanode{"xrb_1nanode8ngaakzbck8smq6ru9bethqwyehomf79sae1k7xd47dkidjqzffeg"}; - -TEST(NanoSigner, sign1) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); - const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); - - auto input = Proto::SigningInput(); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_link_block(linkBlock.data(), linkBlock.size()); - input.set_representative(kRepOfficial1); - input.set_balance("96242336390000000000000000000"); - - // https://www.nanode.co/block/f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696 - const auto signer = Signer(input); - ASSERT_EQ(hex(signer.blockHash), "f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"); - const auto signature = signer.sign(); - ASSERT_EQ(hex(signature), "d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09"); - const Proto::SigningOutput out = signer.build(); - EXPECT_EQ(hex(out.signature()), "d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09"); - EXPECT_EQ(hex(out.block_hash()), "f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"); - EXPECT_EQ( - "{\"account\":\"nano_1bhbsc9yuh15anq3owu1izw1nk7bhhqefrkhfo954fyt8dk1q911buk1kk4c\"," - "\"balance\":\"96242336390000000000000000000\"," - "\"link\":\"491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507\"," - "\"link_as_account\":\"nano_1kazsap8mc481zbqbcqjytpf9mmigj87qr5k5fhf97579t4k8fa94octjx6d\"," - "\"previous\":\"0000000000000000000000000000000000000000000000000000000000000000\"," - "\"representative\":\"nano_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4\"," - "\"signature\":\"d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09\"," - "\"type\":\"state\"}", - out.json()); -} - -TEST(NanoSigner, sign2) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); - const auto parentBlock = parse_hex("f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"); - - auto input = Proto::SigningInput(); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_parent_block(parentBlock.data(), parentBlock.size()); - input.set_representative(kRepNanode); - input.set_balance("96242336390000000000000000000"); - - // https://www.nanode.co/block/2568bf76336f7a415ca236dab97c1df9de951ca057a2e79df1322e647a259e7b - const auto signer = Signer(input); - ASSERT_EQ(hex(signer.blockHash), "2568bf76336f7a415ca236dab97c1df9de951ca057a2e79df1322e647a259e7b"); - const auto signature = signer.sign(); - ASSERT_EQ(hex(signature), "3a0687542405163d5623808052042b3482360a82cc003d178a0c0d8bfbca86450975d0faec60ae5ac37feba9a8e2205c8540317b26f2c589c2a6578b03870403"); -} - -TEST(NanoSigner, sign3) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); - const auto parentBlock = parse_hex("2568bf76336f7a415ca236dab97c1df9de951ca057a2e79df1322e647a259e7b"); - const auto linkBlock = parse_hex("d7384845d2ae530b45a5dd50ee50757f988329f652781767af3f1bc2322f52b9"); - - auto input = Proto::SigningInput(); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_parent_block(parentBlock.data(), parentBlock.size()); - input.set_link_block(linkBlock.data(), linkBlock.size()); - input.set_representative(kRepNanode); - input.set_balance("196242336390000000000000000000"); - input.set_work("123456789"); - - // https://www.nanode.co/block/1ca240212838d053ecaa9dceee598c52a6080067edecaeede3319eb0b7db6525 - const auto signer = Signer(input); - ASSERT_EQ(hex(signer.blockHash), "1ca240212838d053ecaa9dceee598c52a6080067edecaeede3319eb0b7db6525"); - const auto signature = signer.sign(); - ASSERT_EQ(hex(signature), "e980d45365ae2fb291950019f7c19a3d5fa5df2736ca7e7ca1984338b4686976cb7efdda2894ddcea480f82645b50f2340c9d0fc69a05621bdc355783a21820d"); - const Proto::SigningOutput out = signer.build(); - EXPECT_EQ( - "{\"account\":\"nano_1bhbsc9yuh15anq3owu1izw1nk7bhhqefrkhfo954fyt8dk1q911buk1kk4c\"," - "\"balance\":\"196242336390000000000000000000\"," - "\"link\":\"d7384845d2ae530b45a5dd50ee50757f988329f652781767af3f1bc2322f52b9\"," - "\"link_as_account\":\"nano_3osrb34x7dkm3f4tdqcixsa9czwrienzenmr4xmtyhruras4ynosarg1sdiq\"," - "\"previous\":\"2568bf76336f7a415ca236dab97c1df9de951ca057a2e79df1322e647a259e7b\"," - "\"representative\":\"nano_1nanode8ngaakzbck8smq6ru9bethqwyehomf79sae1k7xd47dkidjqzffeg\"," - "\"signature\":\"e980d45365ae2fb291950019f7c19a3d5fa5df2736ca7e7ca1984338b4686976cb7efdda2894ddcea480f82645b50f2340c9d0fc69a05621bdc355783a21820d\"," - "\"type\":\"state\",\"work\":\"123456789\"}", - out.json()); -} - -TEST(NanoSigner, sign4) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); - const auto parentBlock = parse_hex("1ca240212838d053ecaa9dceee598c52a6080067edecaeede3319eb0b7db6525"); - - auto input = Proto::SigningInput(); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_parent_block(parentBlock.data(), parentBlock.size()); - input.set_link_recipient("xrb_3wm37qz19zhei7nzscjcopbrbnnachs4p1gnwo5oroi3qonw6inwgoeuufdp"); - input.set_representative(kRepNanode); - input.set_balance("126242336390000000000000000000"); - - // https://www.nanode.co/block/32ac7d8f5a16a498abf203b8dfee623c9e111ff25e7339f8cd69ec7492b23edd - const auto signer = Signer(input); - ASSERT_EQ(hex(signer.blockHash), "32ac7d8f5a16a498abf203b8dfee623c9e111ff25e7339f8cd69ec7492b23edd"); - const auto signature = signer.sign(); - ASSERT_EQ(hex(signature), "bcb806e140c9e2bc71c51ebbd941b4d99cee3d97fd50e3006eabc5e325c712662e2dc163ee32660875d67815ce4721e122389d2e64f1c9ad4555a9d3d8c33802"); -} - -TEST(NanoSigner, signInvalid1) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); - - // Missing link_block - auto input = Proto::SigningInput(); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_representative(kRepOfficial1); - input.set_balance("96242336390000000000000000000"); - - ASSERT_THROW(Signer signer(input), std::invalid_argument); -} - -TEST(NanoSigner, signInvalid2) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); - const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); - - // Missing representative - auto input = Proto::SigningInput(); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_link_block(linkBlock.data(), linkBlock.size()); - input.set_balance("96242336390000000000000000000"); - - ASSERT_THROW(Signer signer(input), std::invalid_argument); -} - -TEST(NanoSigner, signInvalid3) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); - const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); - - // Missing balance - auto input = Proto::SigningInput(); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_link_block(linkBlock.data(), linkBlock.size()); - input.set_representative(kRepOfficial1); - - ASSERT_THROW(Signer signer(input), std::invalid_argument); -} - -TEST(NanoSigner, signInvalid4) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); - const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); - - // Account first block cannot be 0 balance - auto input = Proto::SigningInput(); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_link_block(linkBlock.data(), linkBlock.size()); - input.set_representative(kRepOfficial1); - input.set_balance("0"); - - ASSERT_THROW(Signer signer(input), std::invalid_argument); -} - -TEST(NanoSigner, signInvalid5) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); - - // First block must use link_block not link_recipient - auto input = Proto::SigningInput(); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_link_recipient("xrb_3wm37qz19zhei7nzscjcopbrbnnachs4p1gnwo5oroi3qonw6inwgoeuufdp"); - input.set_representative(kRepOfficial1); - input.set_balance("96242336390000000000000000000"); - - ASSERT_THROW(Signer signer(input), std::invalid_argument); -} - -TEST(NanoSigner, signInvalid6) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); - const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); - - // Invalid representative value - auto input = Proto::SigningInput(); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_link_block(linkBlock.data(), linkBlock.size()); - input.set_representative("xrb_4wm37qz19zhei7nzscjcopbrbnnachs4p1gnwo5oroi3qonw6inwgoeuufdp"); - input.set_balance("96242336390000000000000000000"); - - ASSERT_THROW(Signer signer(input), std::invalid_argument); -} - -TEST(NanoSigner, signInvalid7) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); - const auto parentBlock = parse_hex("f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"); - - auto input = Proto::SigningInput(); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_parent_block(parentBlock.data(), parentBlock.size()); - input.set_link_recipient("xrb_4wm37qz19zhei7nzscjcopbrbnnachs4p1gnwo5oroi3qonw6inwgoeuufdp"); - input.set_representative(kRepOfficial1); - input.set_balance("1.2.3"); - - ASSERT_THROW(Signer signer(input), std::invalid_argument); -} diff --git a/tests/Nano/TWAnySignerTests.cpp b/tests/Nano/TWAnySignerTests.cpp deleted file mode 100644 index 54638c40b5b..00000000000 --- a/tests/Nano/TWAnySignerTests.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright © 2019 Mart Roosmaa. -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "proto/Nano.pb.h" -#include "../interface/TWTestUtilities.h" -#include -#include - -using namespace TW; -using namespace TW::Nano; - -TEST(TWAnySignerNano, sign) { - const auto privateKey = parse_hex("173c40e97fe2afcd24187e74f6b603cb949a5365e72fbdd065a6b165e2189e34"); - const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); - - auto input = Proto::SigningInput(); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_link_block(linkBlock.data(), linkBlock.size()); - input.set_representative("xrb_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4"); - input.set_balance("96242336390000000000000000000"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeNano); - - EXPECT_EQ( - "{\"account\":\"nano_1bhbsc9yuh15anq3owu1izw1nk7bhhqefrkhfo954fyt8dk1q911buk1kk4c\"," - "\"balance\":\"96242336390000000000000000000\"," - "\"link\":\"491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507\"," - "\"link_as_account\":\"nano_1kazsap8mc481zbqbcqjytpf9mmigj87qr5k5fhf97579t4k8fa94octjx6d\"," - "\"previous\":\"0000000000000000000000000000000000000000000000000000000000000000\"," - "\"representative\":\"nano_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4\"," - "\"signature\":" - "\"d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5" - "ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09\"," - "\"type\":\"state\"}", - output.json()); -} - -TEST(TWAnySignerNano, SignJSON) { - auto json = STRING(R"({"link_block":"SR/KLGmoRgfTdKrx9qzTznB0TFvgchte05RlPoUjNQc=","representative":"nano_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4","balance":"96242336390000000000000000000"})"); - auto key = DATA("173c40e97fe2afcd24187e74f6b603cb949a5365e72fbdd065a6b165e2189e34"); - auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeNano)); - - ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeNano)); - assertStringsEqual(result, R"({"account":"nano_1bhbsc9yuh15anq3owu1izw1nk7bhhqefrkhfo954fyt8dk1q911buk1kk4c","balance":"96242336390000000000000000000","link":"491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507","link_as_account":"nano_1kazsap8mc481zbqbcqjytpf9mmigj87qr5k5fhf97579t4k8fa94octjx6d","previous":"0000000000000000000000000000000000000000000000000000000000000000","representative":"nano_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4","signature":"d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09","type":"state"})"); -} diff --git a/tests/Nano/TWCoinTypeTests.cpp b/tests/Nano/TWCoinTypeTests.cpp deleted file mode 100644 index 37be0b6aa62..00000000000 --- a/tests/Nano/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWNanoCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNano)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("C264DB7BF40738F0CEFF19B606746CB925B713E4B8699A055699E0DC8ABBC70F")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNano, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("nano_1wpj616kwhe1y38y1mspd8aub8i334cwybqco511iyuxm55zx8d67ptf1tsf")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNano, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNano)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNano)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNano), 30); - ASSERT_EQ(TWBlockchainNano, TWCoinTypeBlockchain(TWCoinTypeNano)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeNano)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeNano)); - assertStringsEqual(symbol, "XNO"); - assertStringsEqual(txUrl, "https://nanocrawler.cc/explorer/block/C264DB7BF40738F0CEFF19B606746CB925B713E4B8699A055699E0DC8ABBC70F"); - assertStringsEqual(accUrl, "https://nanocrawler.cc/explorer/account/nano_1wpj616kwhe1y38y1mspd8aub8i334cwybqco511iyuxm55zx8d67ptf1tsf"); - assertStringsEqual(id, "nano"); - assertStringsEqual(name, "Nano"); -} diff --git a/tests/Nebulas/AddressTests.cpp b/tests/Nebulas/AddressTests.cpp deleted file mode 100644 index 3cca8382bb1..00000000000 --- a/tests/Nebulas/AddressTests.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Nebulas/Address.h" -#include "../src/Base58.h" -#include "HexCoding.h" -#include "PrivateKey.h" - -#include - -using namespace std; -using namespace TW; -using namespace TW::Nebulas; - -TEST(NebulasAddress, Invalid) { - ASSERT_FALSE(Address::isValid("abc")); - ASSERT_FALSE(Address::isValid("a1TgpFZWCMmFd2sphb6RKsCvsEyMCNa2Yyv")); - ASSERT_FALSE(Address::isValid("n2TgpFZWCMmFd2sphb6RKsCvsEyMCNa2Yyv")); - // normal address test - ASSERT_TRUE(Address::isValid("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY")); - // contract address test - ASSERT_TRUE(Address::isValid("n1zUNqeBPvsyrw5zxp9mKcDdLTjuaEL7s39")); -} - -TEST(NebulasAddress, String) { - ASSERT_THROW(Address("abc"), std::invalid_argument); - ASSERT_EQ(Address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY").string(), - "n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); - ASSERT_EQ(Address(Base58::bitcoin.decode("n1TgpFZWCMmFd2sphb6RKsCvsEyMCNa2Yyv")).string(), - "n1TgpFZWCMmFd2sphb6RKsCvsEyMCNa2Yyv" - ); - - const auto address = Address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); - ASSERT_EQ(address.string(), "n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); -} - -TEST(NebulasAddress, Data) { - Data data; - EXPECT_THROW(Address(data).string(), std::invalid_argument); - ASSERT_EQ(Address(Base58::bitcoin.decode("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY")).string(), - "n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); -} - -TEST(NebulasAddress, FromPrivateKey) { - const auto privateKey = PrivateKey(parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b")); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); - const auto address = Address(publicKey); - ASSERT_EQ(address.string(), "n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); - - EXPECT_THROW(Address(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)), std::invalid_argument); -} diff --git a/tests/Nebulas/SignerTests.cpp b/tests/Nebulas/SignerTests.cpp deleted file mode 100644 index f0f130f6860..00000000000 --- a/tests/Nebulas/SignerTests.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "Nebulas/Address.h" -#include "Nebulas/Signer.h" -#include - -#include - -namespace TW::Nebulas { - -class SignerExposed : public Signer { - public: - SignerExposed(boost::multiprecision::uint256_t chainID) : Signer(chainID) {} - using Signer::hash; -}; - -TEST(NebulasSigner, Hash) { - auto from = Address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); - auto to = Address("n1SAeQRVn33bamxN4ehWUT7JGdxipwn8b17"); - auto transaction = Transaction( - /* to: */ from, - /* nonce: */ 7, - /* gasPrice: */ 1000000, - /* gasLimit: */ 200000, - /* to: */ to, - /* amount: */ 11000000000000000000ULL, - /* timestamp: */ 1560052938, - /* payload: */ std::string()); - auto signer = SignerExposed(1); - auto hash = signer.hash(transaction); - - ASSERT_EQ(hex(hash), "505dd4769de32a9c4bb6d6afd4f8e1ea6474815fd43484d8917cbd9e0993b885"); -} - -} // namespace TW::Nebulas diff --git a/tests/Nebulas/TWAnySignerTests.cpp b/tests/Nebulas/TWAnySignerTests.cpp deleted file mode 100644 index 69008c4d8a6..00000000000 --- a/tests/Nebulas/TWAnySignerTests.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" -#include -#include "HexCoding.h" -#include "uint256.h" -#include "proto/Nebulas.pb.h" - -#include - -using namespace TW; -using namespace TW::Nebulas; - -TEST(TWAnySignerNebulas, Sign) { - Proto::SigningInput input; - input.set_from_address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); - input.set_to_address("n1SAeQRVn33bamxN4ehWUT7JGdxipwn8b17"); - auto value = store(uint256_t(7)); - input.set_nonce(value.data(),value.size()); - value = store(uint256_t(1000000)); - input.set_gas_price(value.data(),value.size()); - value = store(uint256_t(200000)); - input.set_gas_limit(value.data(),value.size()); - value = store(uint256_t(11000000000000000000ULL)); - input.set_amount(value.data(),value.size()); - input.set_payload(""); - value = store(uint256_t(1560052938)); - input.set_timestamp(value.data(),value.size()); - - const auto privateKey = parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b"); - input.set_private_key(privateKey.data(), privateKey.size()); - auto chainid = store(uint256_t(1)); - input.set_chain_id(chainid.data(), chainid.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeNebulas); - - EXPECT_EQ(hex(output.signature()), "f53f4a9141ff8e462b094138eccd8c3a5d7865f9e9ab509626c78460a9e0b0fc35f7ed5ba1795ceb81a5e46b7580a6f7fb431d44fdba92515399cf6a8e47e71500"); - EXPECT_EQ(output.raw(), "CiBQXdR2neMqnEu21q/U+OHqZHSBX9Q0hNiRfL2eCZO4hRIaGVefwtw23wEobqA40/7aIwQHghETxH4r+50aGhlXf89CeLWgHFjKu9/6tn4KNbelsMDAIIi2IhAAAAAAAAAAAJin2bgxTAAAKAcwyony5wU6CAoGYmluYXJ5QAFKEAAAAAAAAAAAAAAAAAAPQkBSEAAAAAAAAAAAAAAAAAADDUBYAWJB9T9KkUH/jkYrCUE47M2MOl14Zfnpq1CWJseEYKngsPw19+1boXlc64Gl5Gt1gKb3+0MdRP26klFTmc9qjkfnFQA="); -} diff --git a/tests/Nebulas/TWCoinTypeTests.cpp b/tests/Nebulas/TWCoinTypeTests.cpp deleted file mode 100644 index c3eac69cffa..00000000000 --- a/tests/Nebulas/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWNebulasCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNebulas)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNebulas, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNebulas, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNebulas)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNebulas)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNebulas), 18); - ASSERT_EQ(TWBlockchainNebulas, TWCoinTypeBlockchain(TWCoinTypeNebulas)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeNebulas)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeNebulas)); - assertStringsEqual(symbol, "NAS"); - assertStringsEqual(txUrl, "https://explorer.nebulas.io/#/tx/t123"); - assertStringsEqual(accUrl, "https://explorer.nebulas.io/#/address/a12"); - assertStringsEqual(id, "nebulas"); - assertStringsEqual(name, "Nebulas"); -} diff --git a/tests/Nebulas/TransactionTests.cpp b/tests/Nebulas/TransactionTests.cpp deleted file mode 100644 index 2774e02d623..00000000000 --- a/tests/Nebulas/TransactionTests.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Nebulas/Signer.h" -#include "HexCoding.h" -#include "Base64.h" -#include "PrivateKey.h" - -#include - -using namespace std; -using namespace TW; -using namespace TW::Nebulas; - -extern std::string htmlescape(const std::string& str); - -TEST(NebulasTransaction, serialize) { - auto from = Address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); - auto to = Address("n1SAeQRVn33bamxN4ehWUT7JGdxipwn8b17"); - auto transaction = Transaction( - /* to: */ from, - /* nonce: */ 7, - /* gasPrice: */ 1000000, - /* gasLimit: */ 200000, - /* to: */ to, - /* amount: */ 11000000000000000000ULL, - /* timestamp: */ 1560052938, - /* payload: */ std::string()); - - const auto privateKey = PrivateKey(parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b")); - auto signer = Signer(1); - signer.sign(privateKey, transaction); - transaction.serializeToRaw(); - - ASSERT_EQ(TW::Base64::encode(transaction.raw), "CiBQXdR2neMqnEu21q/U+OHqZHSBX9Q0hNiRfL2eCZO4hRIaGVefwtw23wEobqA40/7aIwQHghETxH4r+50aGhlXf89CeLWgHFjKu9/6tn4KNbelsMDAIIi2IhAAAAAAAAAAAJin2bgxTAAAKAcwyony5wU6CAoGYmluYXJ5QAFKEAAAAAAAAAAAAAAAAAAPQkBSEAAAAAAAAAAAAAAAAAADDUBYAWJB9T9KkUH/jkYrCUE47M2MOl14Zfnpq1CWJseEYKngsPw19+1boXlc64Gl5Gt1gKb3+0MdRP26klFTmc9qjkfnFQA="); -} - -TEST(NebulasTransaction, binaryPayload) { - auto from = Address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); - auto to = Address("n1SAeQRVn33bamxN4ehWUT7JGdxipwn8b17"); - auto transaction = Transaction( - /* to: */ from, - /* nonce: */ 7, - /* gasPrice: */ 1000000, - /* gasLimit: */ 200000, - /* to: */ to, - /* amount: */ 11000000000000000000ULL, - /* timestamp: */ 1560052938, - /* payload: */ std::string("{\"binary\":\"test\"}")); - - const auto privateKey = PrivateKey(parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b")); - auto signer = Signer(1); - signer.sign(privateKey, transaction); - ASSERT_EQ(TW::Base64::encode(transaction.raw), "CiB1Oqj7bxLQMHEoNyg/vFHmsTrGdkpTf/5qFDkYPB3bkxIaGVefwtw23wEobqA40/7aIwQHghETxH4r+50aGhlXf89CeLWgHFjKu9/6tn4KNbelsMDAIIi2IhAAAAAAAAAAAJin2bgxTAAAKAcwyony5wU6PQoGYmluYXJ5EjN7IkRhdGEiOnsiZGF0YSI6WzExNiwxMDEsMTE1LDExNl0sInR5cGUiOiJCdWZmZXIifX1AAUoQAAAAAAAAAAAAAAAAAA9CQFIQAAAAAAAAAAAAAAAAAAMNQFgBYkGHXq+JWPaEyeB19bqL3QB5jyM961WLq7PMTpnGM4iLtBjCkngjS81kgPM2TE4qKDcpzqjum/NccrZtUPQLGk0MAQ=="); -} - -TEST(NebulasTransaction, htmlescape) { - // test for escaped label - auto test = ("test&<>\x20\x28\x20\x29"); - auto result = htmlescape(test); - ASSERT_EQ(result, "test\\u0026\\u003c\\u003e\\u2028\\u2029"); -} - -TEST(NebulasTransaction, serializeUnsigned) { - auto from = Address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); - auto to = Address("n1SAeQRVn33bamxN4ehWUT7JGdxipwn8b17"); - auto transaction = Transaction( - /* to: */ from, - /* nonce: */ 7, - /* gasPrice: */ 1000000, - /* gasLimit: */ 200000, - /* to: */ to, - /* amount: */ 11000000000000000000ULL, - /* timestamp: */ 1560052938, - /* payload: */ std::string()); - - ASSERT_THROW(transaction.serializeToRaw(),std::logic_error); -} \ No newline at end of file diff --git a/tests/Nimiq/AddressTests.cpp b/tests/Nimiq/AddressTests.cpp deleted file mode 100644 index 2bc4cfa2403..00000000000 --- a/tests/Nimiq/AddressTests.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "Nimiq/Address.h" -#include "Nimiq/Signer.h" - -#include -#include - -using namespace TW; -using namespace TW::Nimiq; - -TEST(NimiqAddress, IsValid) { - // No address - ASSERT_FALSE(Address::isValid("")); - // Invalid country code - ASSERT_FALSE(Address::isValid("DE86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA")); - // Invalid checksum - ASSERT_FALSE(Address::isValid("NQ42 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA")); - // Too short - ASSERT_FALSE(Address::isValid("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0ML")); - // Too long - ASSERT_FALSE(Address::isValid("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA 0MLA")); - // Valid, without spaces - ASSERT_TRUE(Address::isValid("NQ862H8FYGU5RM77QSN9LYLHC56ACYYR0MLA")); - // Valid, normal format - ASSERT_TRUE(Address::isValid("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA")); -} - -TEST(NimiqAddress, String) { - // Address to string - ASSERT_EQ( - Address(parse_hex("5b3e9e5f32b89abafc3708765dc8f00216cefbb1")).string(), - "NQ61 BCY9 UPRJ P2DB MY1P 11T5 TJ7G 08BC VXVH" - ); - // Without spaces - ASSERT_EQ( - Address("NQ862H8FYGU5RM77QSN9LYLHC56ACYYR0MLA").string(), - "NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA" - ); - // With spaces - ASSERT_EQ( - Address("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA").string(), - "NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA" - ); -} - -TEST(NimiqAddress, FromPublicKey) { - const auto publicKey = Signer::publicKeyFromBytes( - parse_hex("70c7492aaa9c9ac7a05bc0d9c5db2dae9372029654f71f0c7f95deed5099b702")); - const auto address = Address(publicKey); - ASSERT_EQ(address.string(), "NQ27 GBAY EVHP HK5X 6JHV JGFJ 5M3H BF4Y G7GD"); -} diff --git a/tests/Nimiq/SignerTests.cpp b/tests/Nimiq/SignerTests.cpp deleted file mode 100644 index 998b53c3642..00000000000 --- a/tests/Nimiq/SignerTests.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "PrivateKey.h" -#include "Nimiq/Address.h" -#include "Nimiq/Signer.h" -#include "Nimiq/Transaction.h" - -#include - -namespace TW::Nimiq { - -TEST(NimiqSigner, DerivePublicKey) { - const PrivateKey privateKey(parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756")); - const PublicKey publicKey((privateKey.getPublicKey(TWPublicKeyTypeED25519))); - const auto address = Address(publicKey); - ASSERT_EQ(address.string(), "NQ27 GBAY EVHP HK5X 6JHV JGFJ 5M3H BF4Y G7GD"); -} - -TEST(NimiqSigner, Sign) { - const PrivateKey privateKey(parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756")); - const auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - std::array pubkeyBytes; - std::copy(pubkey.bytes.begin(), pubkey.bytes.end(), pubkeyBytes.data()); - - Transaction tx( - pubkeyBytes, - Address("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"), - 42042042, - 1000, - 314159 - ); - - Signer signer; - signer.sign(privateKey, tx); - - ASSERT_EQ(hex(tx.signature), - "74dc7f6e0ab58a0bf52cc6e8801b0cca132dd4229d9a3e3a3d2f90e4d8f045d981b771bf5fc3851a98f3c617b1a943228f963e910e061808a721cfa0e3cad50b"); -} - -} // namespace TW::Nimiq diff --git a/tests/Nimiq/TWAnySignerTests.cpp b/tests/Nimiq/TWAnySignerTests.cpp deleted file mode 100644 index 5a827a87009..00000000000 --- a/tests/Nimiq/TWAnySignerTests.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "proto/Nimiq.pb.h" -#include - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; -using namespace TW::Nimiq; - -TEST(TWAnySignerNimiq, Sign) { - auto privateKey = parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756"); - - Proto::SigningInput input; - - input.set_destination("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"); - input.set_fee(1000); - input.set_value(42042042); - input.set_validity_start_height(314159); - input.set_private_key(privateKey.data(), privateKey.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeNimiq); - - EXPECT_EQ(hex(output.encoded()), "0070c7492aaa9c9ac7a05bc0d9c5db2dae9372029654f71f0c7f95deed5099b7021450ffc385cd4e7c6ac9a7e91614ca67ff90568a00000000028182ba00000000000003e80004cb2f2a74dc7f6e0ab58a0bf52cc6e8801b0cca132dd4229d9a3e3a3d2f90e4d8f045d981b771bf5fc3851a98f3c617b1a943228f963e910e061808a721cfa0e3cad50b"); -} diff --git a/tests/Nimiq/TWCoinTypeTests.cpp b/tests/Nimiq/TWCoinTypeTests.cpp deleted file mode 100644 index 40f585625e3..00000000000 --- a/tests/Nimiq/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWNimiqCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNimiq)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNimiq, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNimiq, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNimiq)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNimiq)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNimiq), 5); - ASSERT_EQ(TWBlockchainNimiq, TWCoinTypeBlockchain(TWCoinTypeNimiq)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeNimiq)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeNimiq)); - assertStringsEqual(symbol, "NIM"); - assertStringsEqual(txUrl, "https://nimiq.watch/#t123"); - assertStringsEqual(accUrl, "https://nimiq.watch/#a12"); - assertStringsEqual(id, "nimiq"); - assertStringsEqual(name, "Nimiq"); -} diff --git a/tests/Nimiq/TransactionTests.cpp b/tests/Nimiq/TransactionTests.cpp deleted file mode 100644 index 089198f2ec6..00000000000 --- a/tests/Nimiq/TransactionTests.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "PrivateKey.h" -#include "Nimiq/Address.h" -#include "Nimiq/Transaction.h" - -#include - -namespace TW::Nimiq { - -TEST(NimiqTransaction, PreImage) { - const PrivateKey privateKey(parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756")); - const auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - std::array pubkeyBytes; - std::copy(pubkey.bytes.begin(), pubkey.bytes.end(), pubkeyBytes.data()); - - Transaction tx( - pubkeyBytes, - Address("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"), - 42042042, - 1000, - 314159 - ); - ASSERT_EQ(hex(tx.getPreImage()), - "000082d5f776378ccbe34a3d941f22d4715bc9f81e0d001450ffc385cd4e7c6ac9a7e91614ca67ff90568a0000000000028182ba00000000000003e80004cb2f2a00"); -} - -TEST(NimiqTransaction, Serialize) { - const PrivateKey privateKey(parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756")); - const auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - std::array pubkeyBytes; - std::copy(pubkey.bytes.begin(), pubkey.bytes.end(), pubkeyBytes.data()); - - Transaction tx( - pubkeyBytes, - Address("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"), - 42042042, - 1000, - 314159 - ); - - const auto signature = parse_hex("74dc7f6e0ab58a0bf52cc6e8801b0cca132dd4229d9a3e3a3d2f90e4d8f045d981b771bf5fc3851a98f3c617b1a943228f963e910e061808a721cfa0e3cad50b"); - std::copy(signature.begin(), signature.end(), tx.signature.begin()); - - ASSERT_EQ(hex(tx.serialize()), - "0070c7492aaa9c9ac7a05bc0d9c5db2dae9372029654f71f0c7f95deed5099b7021450ffc385cd4e7c6ac9a7e91614ca67ff90568a00000000028182ba00000000000003e80004cb2f2a74dc7f6e0ab58a0bf52cc6e8801b0cca132dd4229d9a3e3a3d2f90e4d8f045d981b771bf5fc3851a98f3c617b1a943228f963e910e061808a721cfa0e3cad50b"); -} - -} // namespace TW::Nimiq diff --git a/tests/NumericLiteralTests.cpp b/tests/NumericLiteralTests.cpp deleted file mode 100644 index 07813e2f48e..00000000000 --- a/tests/NumericLiteralTests.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "NumericLiteral.h" - -#include - -TEST(UzLitteralOperator, SizetEquality) { - [[maybe_unused]] auto size = 42_uz; - static_assert(std::is_same_v); -} \ No newline at end of file diff --git a/tests/Oasis/AddressTests.cpp b/tests/Oasis/AddressTests.cpp deleted file mode 100644 index 543fb51fdcf..00000000000 --- a/tests/Oasis/AddressTests.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "Oasis/Address.h" -#include "PublicKey.h" -#include "PrivateKey.h" -#include -#include - -using namespace TW; -using namespace TW::Oasis; - -TEST(OasisAddress, Valid) { - ASSERT_TRUE(Address::isValid("oasis1qp0cnmkjl22gky6p6qeghjytt4v7dkxsrsmueweh")); -} - -TEST(OasisAddress, Invalid) { - ASSERT_FALSE(Address::isValid("oasis1qp0cnmkjl22gky6p6qeghjytt4v7dkxsrsmuewehj")); - ASSERT_FALSE(Address::isValid("oasi1qp0cnmkjl22gky6p6qeghjytt4v7dkxsrsmueweh")); -} - -TEST(OasisAddress, ForceInvalid) { - try { - auto addressString = "oasis1qp0cnmkjl22gky6p6qeghjytt4v7dkxsrsmuewehj"; - auto address = Address( addressString ); - } catch( std::invalid_argument& e1 ) { - return; - } - FAIL() << "This test should generate an exception as it an invalid address"; -} - -TEST(OasisAddress, FromWrongData) { - try { - auto dataString = "asdadfasdfsdfwrwrsadasdasdsad"; - auto address = Address( data( dataString ) ); - } catch( std::invalid_argument& e1 ) { - return; - } - FAIL() << "This test should generate an exception as it an invalid data"; -} - -TEST(OasisAddress, FromPrivateKey) { - auto privateKey = PrivateKey(parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0")); - auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519)); - ASSERT_EQ(address.string(), "oasis1qzawzy5kaa2xgphenf3r0f5enpr3mx5dps559yxm"); -} - -TEST(OasisAddress, FromPublicKey) { - auto publicKey = PublicKey(parse_hex("aba52c0dcb80c2fe96ed4c3741af40c573a0500c0d73acda22795c37cb0f1739"), TWPublicKeyTypeED25519); - auto address = Address(publicKey); - ASSERT_EQ(address.string(), "oasis1qphdkldpttpsj2j3l9sde9h26cwpfwqwwuhvruyu"); -} - -TEST(OasisAddress, WrongPublicKeyType) { - try { - auto publicKey = PublicKey(parse_hex("aba52c0dcb80c2fe96ed4c3741af40c573a0500c0d73acda22795c37cb0f1739"), TWPublicKeyTypeED25519Extended); - auto address = Address(publicKey); - } catch( std::invalid_argument& e1 ) { - return; - } - FAIL() << "TWPublicKeyTypeED25519Extended should generate an exception as it an invalid publicKey type"; -} - -TEST(OasisAddress, FromString) { - Address address; - ASSERT_TRUE(Address::decode("oasis1hts399h023jqd7v6vgm6dxvcguwe4rgvqqgvq38n", address)); - ASSERT_EQ(address.string(), "oasis1hts399h023jqd7v6vgm6dxvcguwe4rgvqqgvq38n"); - - ASSERT_FALSE(Address::decode("oasis1hts399h023jqd7v6vgm6dxvcguwe4rgvqqgvq38ng", address)); -} diff --git a/tests/Oasis/SignerTests.cpp b/tests/Oasis/SignerTests.cpp deleted file mode 100644 index 9e8b454f8d7..00000000000 --- a/tests/Oasis/SignerTests.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Oasis/Signer.h" -#include "Oasis/Address.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" - -#include - -using namespace TW; -using namespace TW::Oasis; - -TEST(OasisSigner, Sign) { - auto input = Proto::SigningInput(); - auto& transfer = *input.mutable_transfer(); - - transfer.set_gas_price(0); - transfer.set_gas_amount("0"); - transfer.set_nonce(0); - transfer.set_to("oasis1qrrnesqpgc6rfy2m50eew5d7klqfqk69avhv4ak5"); - transfer.set_amount("10000000"); - - // The use of this context thing is explained here --> https://docs.oasis.dev/oasis-core/common-functionality/crypto#domain-separation - transfer.set_context("oasis-core/consensus: tx for chain a245619497e580dd3bc1aa3256c07f68b8dcc13f92da115eadc3b231b083d3c4"); - - auto key = parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0"); - input.set_private_key(key.data(), key.size()); - - Proto::SigningOutput output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()),"a273756e747275737465645f7261775f76616c7565585ea4656e6f6e636500666d6574686f64707374616b696e672e5472616e7366657263666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680697369676e6174757265a26a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b697369676e61747572655840e331ce731ed819106586152b13cd98ecf3248a880bdc71174ee3d83f6d5f3f8ee8fc34c19b22032f2f1e3e06d382720125d7a517fba9295c813228cc2b63170b"); -} diff --git a/tests/Oasis/TWAnySignerTests.cpp b/tests/Oasis/TWAnySignerTests.cpp deleted file mode 100644 index 0cae4b34bcd..00000000000 --- a/tests/Oasis/TWAnySignerTests.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include "HexCoding.h" -#include "proto/Oasis.pb.h" - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; -using namespace TW::Oasis; - -TEST(TWAnySignerOasis, Sign) { - auto input = Proto::SigningInput(); - auto output = Proto::SigningOutput(); - auto& transfer = *input.mutable_transfer(); - - transfer.set_gas_price(0); - transfer.set_gas_amount("0"); - transfer.set_nonce(0); - transfer.set_to("oasis1qrrnesqpgc6rfy2m50eew5d7klqfqk69avhv4ak5"); - transfer.set_amount("10000000"); - transfer.set_context("oasis-core/consensus: tx for chain a245619497e580dd3bc1aa3256c07f68b8dcc13f92da115eadc3b231b083d3c4"); - - - auto key = parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0"); - input.set_private_key(key.data(), key.size()); - - ANY_SIGN(input, TWCoinTypeOasis); - - EXPECT_EQ("a273756e747275737465645f7261775f76616c7565585ea4656e6f6e636500666d6574686f64707374616b696e672e5472616e7366657263666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680697369676e6174757265a26a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b697369676e61747572655840e331ce731ed819106586152b13cd98ecf3248a880bdc71174ee3d83f6d5f3f8ee8fc34c19b22032f2f1e3e06d382720125d7a517fba9295c813228cc2b63170b", - hex(output.encoded())); -} diff --git a/tests/Oasis/TWCoinTypeTests.cpp b/tests/Oasis/TWCoinTypeTests.cpp deleted file mode 100644 index a75e79d0796..00000000000 --- a/tests/Oasis/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWOasisCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeOasis)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0b9bd4983f1c88a1c71bf33562b6ba02b3064e01697d15a0de4bfe1922ec74b8")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeOasis, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("oasis1qrx376dmwuckmruzn9vq64n49clw72lywctvxdf4")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeOasis, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeOasis)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeOasis)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeOasis), 9); - ASSERT_EQ(TWBlockchainOasisNetwork, TWCoinTypeBlockchain(TWCoinTypeOasis)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeOasis)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeOasis)); - assertStringsEqual(symbol, "ROSE"); - assertStringsEqual(txUrl, "https://oasisscan.com/transactions/0b9bd4983f1c88a1c71bf33562b6ba02b3064e01697d15a0de4bfe1922ec74b8"); - assertStringsEqual(accUrl, "https://oasisscan.com/accounts/detail/oasis1qrx376dmwuckmruzn9vq64n49clw72lywctvxdf4"); - assertStringsEqual(id, "oasis"); - assertStringsEqual(name, "Oasis"); -} diff --git a/tests/Ontology/AccountTests.cpp b/tests/Ontology/AccountTests.cpp deleted file mode 100644 index e6dcbfa56e9..00000000000 --- a/tests/Ontology/AccountTests.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Hash.h" -#include "HexCoding.h" -#include "PrivateKey.h" - -#include "Ontology/Signer.h" - -#include - -using namespace TW; -using namespace TW::Ontology; - -TEST(OntologyAccount, validity) { - auto hexPrvKey = "4646464646464646464646464646464646464646464646464646464646464646"; - auto hexPubKey = "031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"; - auto signer = Signer(PrivateKey(parse_hex(hexPrvKey))); - auto prvKey = signer.getPrivateKey(); - auto pubKey = signer.getPublicKey(); - EXPECT_EQ(hexPrvKey, hex(prvKey.bytes)); - EXPECT_EQ(hexPubKey, hex(pubKey.bytes)); -} \ No newline at end of file diff --git a/tests/Ontology/AddressTests.cpp b/tests/Ontology/AddressTests.cpp deleted file mode 100644 index f27c2cdd691..00000000000 --- a/tests/Ontology/AddressTests.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "PublicKey.h" - -#include "Ontology/Address.h" -#include "Ontology/Signer.h" - -#include - -using namespace TW; -using namespace TW::Ontology; - -TEST(OntologyAddress, validation) { - ASSERT_FALSE(Address::isValid("abc")); - ASSERT_FALSE(Address::isValid("abeb60f3e94c1b9a09f33669435e7ef12eacd")); - ASSERT_FALSE(Address::isValid("abcb60f3e94c9b9a09f33669435e7ef1beaedads")); - ASSERT_TRUE(Address::isValid("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD")); -} - -TEST(OntologyAddress, fromPubKey) { - auto address = Address( - PublicKey(parse_hex("031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"), TWPublicKeyTypeSECP256k1)); - EXPECT_EQ("AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5", address.string()); -} - -TEST(OntologyAddress, fromString) { - auto b58Str = "AYTxeseHT5khTWhtWX1pFFP1mbQrd4q1zz"; - auto address = Address(b58Str); - EXPECT_EQ(b58Str, address.string()); - auto errB58Str = "AATxeseHT5khTWhtWX1pFFP1mbQrd4q1zz"; - ASSERT_THROW(new Address(errB58Str), std::runtime_error); -} - -TEST(OntologyAddress, fromMultiPubKeys) { - auto signer1 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); - auto signer2 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); - auto signer3 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464658"))); - std::vector pubKeys{signer1.getPublicKey().bytes, signer2.getPublicKey().bytes, signer3.getPublicKey().bytes}; - uint8_t m = 2; - auto multiAddress = Address(m, pubKeys); - EXPECT_EQ("AYGWgijVZnrUa2tRoCcydsHUXR1111DgdW", multiAddress.string()); -} \ No newline at end of file diff --git a/tests/Ontology/OngTests.cpp b/tests/Ontology/OngTests.cpp deleted file mode 100644 index afb5ba4fd67..00000000000 --- a/tests/Ontology/OngTests.cpp +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" - -#include "Ontology/Ong.h" - -#include -#include - -using namespace TW; -using namespace TW::Ontology; - -TEST(OntologyOng, decimals) { - uint32_t nonce = 0; - auto tx = Ong().decimals(nonce); - auto rawTx = hex(tx.serialize()); - EXPECT_EQ("00d100000000000000000000000000000000000000000000000000000000000000000000000000000000" - "380008646563696d616c731400000000000000000000000000000000000000020068164f6e746f6c6f67" - "792e4e61746976652e496e766f6b650000", - rawTx); -} - -TEST(OntologyOng, balanceOf) { - uint32_t nonce = 0; - auto address = Address("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD"); - auto tx = Ong().balanceOf(address, nonce); - auto rawTx = hex(tx.serialize()); - EXPECT_EQ("00d100000000000000000000000000000000000000000000000000000000000000000000000000000000" - "4d1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b70962616c616e63654f6614000000000000000000" - "00000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b650000", - rawTx); -} - -TEST(OntologyOng, transfer) { - auto signer1 = Signer( - PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); - auto signer2 = Signer( - PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); - auto toAddress = Address("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn"); - uint32_t nonce = 0; - uint64_t amount = 1, gasPrice = 500, gasLimit = 20000; - auto tx = Ong().transfer(signer1, toAddress, amount, signer2, gasPrice, gasLimit, nonce); - auto rawTx = hex(tx.serialize()); - EXPECT_EQ("00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" - "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" - "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" - "00000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140ac3edf2d00540f9c" - "2f3b24878936b409c995c425ab5edf247c5b0d812a50df293ff63e173bac71a6cd0772ff78415c46ac64" - "f625cbc06fe90ccdecf9a94319c42321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" - "47125f927b7486ac41406fea9f12b125d7f65a94774e765a796428b3c6c4c46b0470624b9a1cef4ff420" - "488828f308c263b35287363e51add8cd49136eb57a397c6ade95df80d9a16282232103d9fd62df332403" - "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", - rawTx); -} - -TEST(OntologyOng, withdraw) { - auto signer1 = Signer( - PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); - auto signer2 = Signer( - PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); - uint32_t nonce = 0; - uint64_t amount = 1, gasPrice = 500, gasLimit = 20000; - auto tx = - Ong().withdraw(signer1, signer1.getAddress(), amount, signer2, gasPrice, gasLimit, nonce); - auto rawTx = hex(tx.serialize()); - EXPECT_EQ( - "00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df68b00c6" - "6b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc81400000000000000000000000000000000000000" - "016a7cc814fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc8516a7cc86c0c7472616e7366657246726f" - "6d1400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f" - "6b6500024140b8b859055c744a89ef4d4f6ae7a58e0a99fef2eb0f6cf09d740b56cf4c7c14ab64e00c28de9b1f" - "28921cbd62e6bcd6d452ab9871f8f5d2288812ff322ee2f4af2321031bec1250aa8f78275f99a6663688f31085" - "848d0ed92f1203e447125f927b7486ac41406413b060329e133cd13709c361ccd90b3944477cf3937f1459313f" - "0ea6435f6f2b1335192a5d1b346fd431e8af912bfa4e1a23ad7d0ab7fc5b808655af5c9043232103d9fd62df33" - "2403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", - rawTx); -} \ No newline at end of file diff --git a/tests/Ontology/OntTests.cpp b/tests/Ontology/OntTests.cpp deleted file mode 100644 index e2d26a06895..00000000000 --- a/tests/Ontology/OntTests.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" - -#include "Ontology/Ont.h" - -#include - -#include -#include - -using namespace TW; -using namespace TW::Ontology; - -TEST(OntologyOnt, decimals) { - uint32_t nonce = 0; - auto tx = Ont().decimals(nonce); - auto rawTx = hex(tx.serialize()); - EXPECT_EQ("00d100000000000000000000000000000000000000000000000000000000000000000000000000000000" - "380008646563696d616c731400000000000000000000000000000000000000010068164f6e746f6c6f67" - "792e4e61746976652e496e766f6b650000", - rawTx); -} - -TEST(OntologyOnt, queryBalance) { - uint32_t nonce = 0; - auto address = Address("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD"); - auto tx = Ont().balanceOf(address, nonce); - auto rawTx = hex(tx.serialize()); - EXPECT_EQ("00d100000000000000000000000000000000000000000000000000000000000000000000000000000000" - "4d1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b70962616c616e63654f6614000000000000000000" - "00000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b650000", - rawTx); -} - -TEST(OntologyOnt, transfer) { - auto signer1 = Signer( - PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); - auto signer2 = Signer( - PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); - auto toAddress = Address("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn"); - uint32_t nonce = 0; - uint64_t amount = 1, gasPrice = 500, gasLimit = 20000; - auto tx = Ont().transfer(signer1, toAddress, amount, signer2, gasPrice, gasLimit, nonce); - auto rawTx = hex(tx.serialize()); - EXPECT_EQ("00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" - "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" - "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" - "00000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b65000241407531e7d5bb9ae138" - "862585a65c26d624f1a7a61011298809d9ed9cf60d10a4504067dee9d549a836b480c4e48904e28f9b42" - "dd5fa14376cbb1ef27d931eaea552321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" - "47125f927b7486ac4140bcc6df81d7f2f3143f152c446643ac5bf7910ef90046be8c89818264a11d360d" - "0576d7b092fabafd0913a67ccf8b2f8e3d2bd708f768c2bb67e2d2f759805608232103d9fd62df332403" - "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", - rawTx); -} \ No newline at end of file diff --git a/tests/Ontology/ParamsBuilderTests.cpp b/tests/Ontology/ParamsBuilderTests.cpp deleted file mode 100644 index bb8a3540d1e..00000000000 --- a/tests/Ontology/ParamsBuilderTests.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "PublicKey.h" - -#include "Ontology/Address.h" -#include "Ontology/Ont.h" -#include "Ontology/ParamsBuilder.h" - -#include - -using namespace TW; -using namespace TW::Ontology; - -TEST(ParamsBuilder, pushInt) { - std::vector numVector{0, - 1, - 2, - 127, - 128, - 129, - 65534, - 65535, - 65536, - 65537, - 4294967294, - 4294967295, - 4294967296, - 68719476735, - 68719476736, - 72057594037927935, - 1152921504606846975}; - std::vector codeVector{"00", - "51", - "52", - "017f", - "028000", - "028100", - "03feff00", - "03ffff00", - "03000001", - "03010001", - "05feffffff00", - "05ffffffff00", - "050000000001", - "05ffffffff0f", - "050000000010", - "08ffffffffffffff00", - "08ffffffffffffff0f"}; - for (auto index = 0; index < numVector.size(); index++) { - auto builder = ParamsBuilder(); - builder.push(numVector[index]); - EXPECT_EQ(codeVector[index], hex(builder.getBytes())); - } -} - -TEST(ParamsBuilder, balanceInvokeCode) { - auto balanceParam = Address("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD").data; - auto invokeCode = ParamsBuilder::buildNativeInvokeCode(Ont().contractAddress(), 0x00, - "balanceOf", balanceParam); - auto hexInvokeCode = - "1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b70962616c616e63654f661400000000000000000000000000" - "000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b65"; - EXPECT_EQ(hexInvokeCode, hex(invokeCode)); -} - -TEST(ParamsBuilder, transferInvokeCode) { - auto fromAddress = Address("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD").data; - auto toAddress = Address("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn").data; - uint64_t amount = 1; - std::list transferParam{fromAddress, toAddress, amount}; - std::vector args{transferParam}; - auto invokeCode = - ParamsBuilder::buildNativeInvokeCode(Ont().contractAddress(), 0x00, "transfer", args); - auto hexInvokeCode = - "00c66b1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b76a7cc814feec06b79ed299ea06fcb94abac41aaf3e" - "ad76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068" - "164f6e746f6c6f67792e4e61746976652e496e766f6b65"; - EXPECT_EQ(hexInvokeCode, hex(invokeCode)); -} \ No newline at end of file diff --git a/tests/Ontology/TWAnySignerTests.cpp b/tests/Ontology/TWAnySignerTests.cpp deleted file mode 100644 index bb6786dc0ef..00000000000 --- a/tests/Ontology/TWAnySignerTests.cpp +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "../interface/TWTestUtilities.h" - -#include "Ontology/OngTxBuilder.h" -#include "Ontology/OntTxBuilder.h" - -#include - -#include - -using namespace TW; -using namespace TW::Ontology; - -TEST(TWAnySingerOntology, OntBalanceOf) { - // curl -H "Content-Type: application/json" -X POST -d '{"Action":"sendrawtransaction", - // "Version":"1.0.0","00d1885602ec0000000000000000000000000000000000000000000000000000000000000000000000004d1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b70962616c616e63654f661400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b650000"}' - // http://polaris2.ont.io:20334/api/v1/transaction?preExec=1 - // - // {"Action":"sendrawtransaction","Desc":"SUCCESS","Error":0,"Result":{"State":1,"Gas":20000,"Result":"00","Notify":[]},"Version":"1.0.0"} - auto input = Proto::SigningInput(); - input.set_contract("ONT"); - input.set_method("balanceOf"); - input.set_query_address("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD"); - input.set_nonce(3959576200); - auto data = OntTxBuilder::build(input); - auto rawTx = hex(data); - EXPECT_EQ("00d1885602ec000000000000000000000000000000000000000000000000000000000000000000000000" - "4d1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b70962616c616e63654f6614000000000000000000" - "00000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b650000", - rawTx); -} - -TEST(TWAnySingerOntology, OntDecimals) { - // curl -H "Content-Type: application/json" -X POST -d '{"Action":"sendrawtransaction", - // "Version":"1.0.0","Data":"00d1bdc12a48000000000000000000000000000000000000000000000000000000000000000000000000380008646563696d616c731400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b650000"}' - // http://polaris2.ont.io:20334/api/v1/transaction?preExec=1 - // - //{"Action":"sendrawtransaction","Desc":"SUCCESS","Error":0,"Result":{"State":1,"Gas":20000,"Result":"","Notify":[]},"Version":"1.0.0"} - auto input = Proto::SigningInput(); - input.set_contract("ONT"); - input.set_method("decimals"); - input.set_nonce(1210761661); - auto data = OntTxBuilder::build(input); - auto rawTx = hex(data); - EXPECT_EQ("00d1bdc12a48000000000000000000000000000000000000000000000000000000000000000000000000" - "380008646563696d616c731400000000000000000000000000000000000000010068164f6e746f6c6f67" - "792e4e61746976652e496e766f6b650000", - rawTx); -} - -TEST(TWAnySingerOntology, OntTransfer) { - // tx on polaris test net. - // https://explorer.ont.io/transaction/4a672ce813d3fac9042e9472cf9b470f8a5e59a2deb41fd7b23a1f7479a155d5/testnet - auto ownerPrivateKey = - parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); - auto payerPrivateKey = - parse_hex("4646464646464646464646464646464646464646464646464646464646464652"); - auto input = Proto::SigningInput(); - input.set_contract("ONT"); - input.set_method("transfer"); - input.set_nonce(2338116610); - input.set_owner_private_key(ownerPrivateKey.data(), ownerPrivateKey.size()); - input.set_payer_private_key(payerPrivateKey.data(), payerPrivateKey.size()); - input.set_to_address("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn"); - input.set_amount(1); - input.set_gas_price(500); - input.set_gas_limit(20000); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeOntology); - - EXPECT_EQ("00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" - "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" - "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" - "00000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6e" - "bb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb7cff28ddaf7f1048822c0ca21a0c4926323a" - "2497875b963f3b8cbd3717aa6e7c2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" - "47125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860" - "305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403" - "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", - hex(output.encoded())); -} - -TEST(TWAnySingerOntology, OngDecimals) { - // curl -H "Content-Type: application/json" -X POST -d '{"Action":"sendrawtransaction", - // "Version":"1.0.0","Data":"00d1e3f2e679000000000000000000000000000000000000000000000000000000000000000000000000380008646563696d616c731400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b650000"}' - // http://polaris2.ont.io:20334/api/v1/transaction?preExec=1 - // - // {"Action":"sendrawtransaction","Desc":"SUCCESS","Error":0,"Result":{"State":1,"Gas":20000,"Result":"09","Notify":[]},"Version":"1.0.0"} - auto input = Proto::SigningInput(); - input.set_contract("ONG"); - input.set_method("decimals"); - input.set_nonce(2045178595); - auto data = OngTxBuilder::build(input); - auto rawTx = hex(data); - EXPECT_EQ("00d1e3f2e679000000000000000000000000000000000000000000000000000000000000000000000000" - "380008646563696d616c731400000000000000000000000000000000000000020068164f6e746f6c6f67" - "792e4e61746976652e496e766f6b650000", - rawTx); -} - -TEST(TWAnySingerOntology, OngBalanceOf) { - // curl -H "Content-Type: application/json" -X POST -d '{"Action":"sendrawtransaction", - // "Version":"1.0.0","Data":"00d1ab1ad0cf0000000000000000000000000000000000000000000000000000000000000000000000004d1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b70962616c616e63654f661400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b650000"}' - // http://polaris2.ont.io:20334/api/v1/transaction?preExec=1 - // - //{"Action":"sendrawtransaction","Desc":"SUCCESS","Error":0,"Result":{"State":1,"Gas":20000,"Result":"27e74d240609","Notify":[]},"Version":"1.0.0"} - auto input = Proto::SigningInput(); - input.set_contract("ONG"); - input.set_method("balanceOf"); - input.set_query_address("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD"); - input.set_nonce(3486522027); - auto data = OngTxBuilder::build(input); - auto rawTx = hex(data); - EXPECT_EQ("00d1ab1ad0cf000000000000000000000000000000000000000000000000000000000000000000000000" - "4d1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b70962616c616e63654f6614000000000000000000" - "00000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b650000", - rawTx); -} - -TEST(TWAnySingerOntology, OngTransfer) { - // tx on polaris test net. - // https://explorer.ont.io/transaction/8a1e59396dcb72d9095088f50d1023294bf9c7b79ba693bd641578f748cbd4e6/testnet - auto ownerPrivateKey = - parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); - auto payerPrivateKey = - parse_hex("4646464646464646464646464646464646464646464646464646464646464652"); - auto input = Proto::SigningInput(); - input.set_contract("ONG"); - input.set_method("transfer"); - input.set_owner_private_key(ownerPrivateKey.data(), ownerPrivateKey.size()); - input.set_payer_private_key(payerPrivateKey.data(), payerPrivateKey.size()); - input.set_to_address("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn"); - input.set_amount(1); - input.set_gas_price(500); - input.set_gas_limit(20000); - input.set_nonce(2827104669); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeOntology); - - EXPECT_EQ("00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" - "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" - "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" - "00000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efa" - "d62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f73b8ab199a4d757b4c7b9ed46c4ff8cfa8ae" - "faa90b7fb6485e358034448cba752321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" - "47125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d74" - "3b078bd4e21bb4404c0182a32ee05260e22454dffb34dacccf458dfbee6d32db232103d9fd62df332403" - "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", - hex(output.encoded())); -} - -TEST(TWAnySingerOntology, OngWithdraw) { - // tx on polaris test net. - // https://explorer.ont.io/transaction/433cb7ed4dec32d55be0db104aaa7ade4c7dbe0f62ef94f7b17829f7ac7cd75b/testnet - auto ownerPrivateKey = - parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); - auto payerPrivateKey = - parse_hex("4646464646464646464646464646464646464646464646464646464646464652"); - auto input = Proto::SigningInput(); - input.set_contract("ONG"); - input.set_method("withdraw"); - input.set_owner_private_key(ownerPrivateKey.data(), ownerPrivateKey.size()); - input.set_payer_private_key(payerPrivateKey.data(), payerPrivateKey.size()); - input.set_to_address("AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5"); - input.set_amount(1); - input.set_gas_price(500); - input.set_gas_limit(20000); - input.set_nonce(3784713724); - auto data = OngTxBuilder::build(input); - auto rawTx = hex(data); - EXPECT_EQ( - "00d1fc2596e1f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df68b00c6" - "6b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc81400000000000000000000000000000000000000" - "016a7cc814fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc8516a7cc86c0c7472616e7366657246726f" - "6d1400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f" - "6b65000241400ef868766eeafce71b6ff2a4332aa4363980e66c55ef70aea80e3baee1daf02b43ae6d4c7c8a17" - "8b92f523602426eaa4205ab0ae5944b0fdae0abcbabaefbc4c2321031bec1250aa8f78275f99a6663688f31085" - "848d0ed92f1203e447125f927b7486ac4140c49c23092cd9003247a55792211d816010c7d6204c6e07a6e017da" - "70007b25ee2ab3665103f846300cd03512040275b78ae46812d40cd611058decdff5551e1f232103d9fd62df33" - "2403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", - rawTx); -} diff --git a/tests/Ontology/TWCoinTypeTests.cpp b/tests/Ontology/TWCoinTypeTests.cpp deleted file mode 100644 index 30bb745ab50..00000000000 --- a/tests/Ontology/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWOntologyCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeOntology)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeOntology, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeOntology, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeOntology)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeOntology)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeOntology), 0); - ASSERT_EQ(TWBlockchainOntology, TWCoinTypeBlockchain(TWCoinTypeOntology)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeOntology)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeOntology)); - assertStringsEqual(symbol, "ONT"); - assertStringsEqual(txUrl, "https://explorer.ont.io/transaction/t123"); - assertStringsEqual(accUrl, "https://explorer.ont.io/address/a12"); - assertStringsEqual(id, "ontology"); - assertStringsEqual(name, "Ontology"); -} diff --git a/tests/Ontology/TransactionTests.cpp b/tests/Ontology/TransactionTests.cpp deleted file mode 100644 index 0c8d4c7b254..00000000000 --- a/tests/Ontology/TransactionTests.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "PrivateKey.h" - -#include "Ontology/ParamsBuilder.h" -#include "Ontology/Signer.h" -#include "Ontology/Transaction.h" - -#include - -#include -#include - -using namespace TW; -using namespace TW::Ontology; - -TEST(OntologyTransaction, validity) { - std::vector ontContract{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; - auto fromAddress = Address("AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5"); - auto toAddress = Address("APniYDGozkhUh8Tk7pe35aah2HGJ4fJfVd"); - uint64_t amount = 1; - std::list transferParam{fromAddress.data, toAddress.data, amount}; - std::vector args{transferParam}; - auto invokeCode = ParamsBuilder::buildNativeInvokeCode(ontContract, 0x00, "transfer", args); - uint8_t version = 0; - uint8_t txType = 0xd1; - uint32_t nonce = 1552759011; - uint64_t gasPrice = 600; - uint64_t gasLimit = 300000; - auto tx = - Transaction(version, txType, nonce, gasPrice, gasLimit, toAddress.string(), invokeCode); - std::string hexTx = - "00d1e3388d5c5802000000000000e09304000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c6" - "6b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc81457e9d1a61f9aafa798b6c7fbeae35639681d7d" - "f66a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f" - "6e746f6c6f67792e4e61746976652e496e766f6b650000"; - EXPECT_EQ(hexTx, hex(tx.serialize())); - auto signer1 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); - signer1.sign(tx); - hexTx = - "00d1e3388d5c5802000000000000e09304000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c6" - "6b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc81457e9d1a61f9aafa798b6c7fbeae35639681d7d" - "f66a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f" - "6e746f6c6f67792e4e61746976652e496e766f6b6500014140e03a09d85f56d2ceb5817a1f3a430bab9bf0f469" - "da38afe4a5b33de258a06236d8e0a59d25918a49825455c99f91de9caf8071e38a589a530519705af9081eca23" - "21031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac"; - EXPECT_EQ(520, hex(tx.serialize()).length()); - EXPECT_EQ(hexTx.substr(0, 20), hex(tx.serialize()).substr(0, 20)); - auto signer2 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); - signer2.addSign(tx); - auto result = tx.serialize(); - auto verifyPosition1 = - hex(result).find("21031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac"); - auto verifyPosition2 = - hex(result).find("2103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac"); - EXPECT_EQ(450, verifyPosition1); - EXPECT_EQ(654, verifyPosition2); - EXPECT_EQ(724, hex(result).length()); -} \ No newline at end of file diff --git a/tests/Optimism/TWCoinTypeTests.cpp b/tests/Optimism/TWCoinTypeTests.cpp deleted file mode 100644 index 4073c8896b8..00000000000 --- a/tests/Optimism/TWCoinTypeTests.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWOptimismCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeOptimism)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x6fd99288be9bf71eb002bb31da10a4fb0fbbb3c45ae73693b212f49c9db7df8f")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeOptimism, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x1f932361e31d206b4f6b2478123a9d0f8c761031")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeOptimism, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeOptimism)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeOptimism)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeOptimism), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeOptimism)); - - assertStringsEqual(symbol, "ETH"); - assertStringsEqual(txUrl, "https://optimistic.etherscan.io/tx/0x6fd99288be9bf71eb002bb31da10a4fb0fbbb3c45ae73693b212f49c9db7df8f"); - assertStringsEqual(accUrl, "https://optimistic.etherscan.io/address/0x1f932361e31d206b4f6b2478123a9d0f8c761031"); - assertStringsEqual(id, "optimism"); - assertStringsEqual(name, "Optimistic Ethereum"); -} diff --git a/tests/Osmosis/AddressTests.cpp b/tests/Osmosis/AddressTests.cpp deleted file mode 100644 index ee7b98a56a8..00000000000 --- a/tests/Osmosis/AddressTests.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Cosmos/Address.h" -#include "HexCoding.h" -#include "PublicKey.h" -#include "PrivateKey.h" -#include -#include - -using namespace TW; -using namespace TW::Cosmos; - -TEST(OsmosisAddress, Valid) { - EXPECT_TRUE(Address::isValid(TWCoinTypeOsmosis, "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5")); - EXPECT_TRUE(Address::isValid(TWCoinTypeOsmosis, "osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn")); -} - -TEST(OsmosisAddress, Invalid) { - EXPECT_FALSE(Address::isValid(TWCoinTypeOsmosis, "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f6")); - EXPECT_FALSE(Address::isValid(TWCoinTypeOsmosis, "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")); // valid cosmos -} - -TEST(OsmosisAddress, FromPrivateKey) { - auto privateKey = PrivateKey(parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - auto address = Address(TWCoinTypeOsmosis, publicKey); - ASSERT_EQ(address.string(), "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5"); -} - -TEST(OsmosisAddress, FromPublicKey) { - auto publicKey = PublicKey(parse_hex("02ecef5ce437a302c67f95468de4b31f36e911f467d7e6a52b41c1e13e1d563649"), TWPublicKeyTypeSECP256k1); - auto address = Address(TWCoinTypeOsmosis, publicKey); - ASSERT_EQ(address.string(), "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5"); -} - -TEST(OsmosisAddress, FromString) { - Address address; - EXPECT_TRUE(Address::decode("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5", address)); - EXPECT_EQ(address.string(), "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5"); - EXPECT_EQ(hex(address.getKeyHash()), "dd89a2e267cd96e23cf5a33382f030686f65996f"); -} diff --git a/tests/Osmosis/SignerTests.cpp b/tests/Osmosis/SignerTests.cpp deleted file mode 100644 index f7f33163576..00000000000 --- a/tests/Osmosis/SignerTests.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Cosmos/Address.h" -#include "Cosmos/Signer.h" -#include "proto/Cosmos.pb.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include "../interface/TWTestUtilities.h" - -#include - -using namespace TW; -using namespace TW::Cosmos; - -TEST(OsmosisSigner, SignTransfer_81B4) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::Protobuf); - input.set_account_number(124703); - input.set_chain_id("osmosis-1"); - input.set_memo(""); - input.set_sequence(0); - - Address fromAddress; - Address toAddress; - EXPECT_TRUE(Address::decode("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5", fromAddress)); - EXPECT_TRUE(Address::decode("osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn", toAddress)); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_send_coins_message(); - message.set_from_address(fromAddress.string()); - message.set_to_address(toAddress.string()); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("uosmo"); - amountOfTx->set_amount("99800"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("uosmo"); - amountOfFee->set_amount("200"); - - auto privateKey = parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeOsmosis); - - // https://www.mintscan.io/osmosis/txs/81B4F01BDE72AF7FF4536E5D7E66EB218E9FC9ACAA7C5EB5DB237DD0595D5F5F - // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Co0B...rYVj", "mode": "BROADCAST_MODE_BLOCK"}' https://lcd-osmosis.keplr.app/cosmos/tx/v1beta1/txs - - assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKK29zbW8xbWt5Njljbjhla3R3eTA4NDV2ZWM5dXBzZHBoa3R4dDBlbjk3ZjUSK29zbW8xOHMwaGRuc2xsZ2NjbHdldTlheW13NG5na3RyMmswcmt2bjdqbW4aDgoFdW9zbW8SBTk5ODAwEmQKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLs71zkN6MCxn+VRo3ksx826RH0Z9fmpStBweE+HVY2SRIECgIIARISCgwKBXVvc21vEgMyMDAQwJoMGkAMY//Md5GRUR4lVZhk558hFS3kii9QZYoYKfg4+ac/xgNeyoiEweVDhcmEvlH1orVwjLUOnYs4ly2a/yIurYVj\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); - EXPECT_EQ(hex(output.signature()), "0c63ffcc779191511e25559864e79f21152de48a2f50658a1829f838f9a73fc6035eca8884c1e54385c984be51f5a2b5708cb50e9d8b38972d9aff222ead8563"); - EXPECT_EQ(output.error(), ""); - EXPECT_EQ(output.json(), ""); -} diff --git a/tests/Osmosis/TWAnyAddressTests.cpp b/tests/Osmosis/TWAnyAddressTests.cpp deleted file mode 100644 index 58e8d8abb24..00000000000 --- a/tests/Osmosis/TWAnyAddressTests.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include "HexCoding.h" - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; - -TEST(TWOsmosisAnyAddress, IsValid) { - EXPECT_TRUE(TWAnyAddressIsValid(STRING("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5").get(), TWCoinTypeOsmosis)); - EXPECT_TRUE(TWAnyAddressIsValid(STRING("osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn").get(), TWCoinTypeOsmosis)); - EXPECT_FALSE(TWAnyAddressIsValid(STRING("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02").get(), TWCoinTypeOsmosis)); -} - -TEST(TWOsmosisAnyAddress, Create) { - auto string = STRING("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5"); - auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeOsmosis)); - auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); - EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); - auto keyHash = WRAPD(TWAnyAddressData(addr.get())); - assertHexEqual(keyHash, "dd89a2e267cd96e23cf5a33382f030686f65996f"); -} diff --git a/tests/Osmosis/TWAnySignerTests.cpp b/tests/Osmosis/TWAnySignerTests.cpp deleted file mode 100644 index 13b560f0e8a..00000000000 --- a/tests/Osmosis/TWAnySignerTests.cpp +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include "HexCoding.h" -#include "Cosmos/Address.h" -#include "proto/Cosmos.pb.h" - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; -using namespace TW::Cosmos; - -TEST(TWAnySignerOsmosis, Sign) { - auto privateKey = parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"); - Proto::SigningInput input; - input.set_signing_mode(Proto::Protobuf); - input.set_account_number(124703); - input.set_chain_id("osmosis-1"); - input.set_memo(""); - input.set_sequence(0); - input.set_private_key(privateKey.data(), privateKey.size()); - - Address fromAddress; - Address toAddress; - EXPECT_TRUE(Address::decode("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5", fromAddress)); - EXPECT_TRUE(Address::decode("osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn", toAddress)); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_send_coins_message(); - message.set_from_address(fromAddress.string()); - message.set_to_address(toAddress.string()); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("uosmo"); - amountOfTx->set_amount("99800"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("uosmo"); - amountOfFee->set_amount("200"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeOsmosis); - - // https://www.mintscan.io/osmosis/txs/81B4F01BDE72AF7FF4536E5D7E66EB218E9FC9ACAA7C5EB5DB237DD0595D5F5F - // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Co0B...rYVj", "mode": "BROADCAST_MODE_BLOCK"}' https://lcd-osmosis.keplr.app/cosmos/tx/v1beta1/txs - assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKK29zbW8xbWt5Njljbjhla3R3eTA4NDV2ZWM5dXBzZHBoa3R4dDBlbjk3ZjUSK29zbW8xOHMwaGRuc2xsZ2NjbHdldTlheW13NG5na3RyMmswcmt2bjdqbW4aDgoFdW9zbW8SBTk5ODAwEmQKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLs71zkN6MCxn+VRo3ksx826RH0Z9fmpStBweE+HVY2SRIECgIIARISCgwKBXVvc21vEgMyMDAQwJoMGkAMY//Md5GRUR4lVZhk558hFS3kii9QZYoYKfg4+ac/xgNeyoiEweVDhcmEvlH1orVwjLUOnYs4ly2a/yIurYVj\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); - EXPECT_EQ(hex(output.signature()), "0c63ffcc779191511e25559864e79f21152de48a2f50658a1829f838f9a73fc6035eca8884c1e54385c984be51f5a2b5708cb50e9d8b38972d9aff222ead8563"); - EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); -} diff --git a/tests/Osmosis/TWCoinTypeTests.cpp b/tests/Osmosis/TWCoinTypeTests.cpp deleted file mode 100644 index dc9b81fe90e..00000000000 --- a/tests/Osmosis/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWOsmosisCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeOsmosis)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("5A6E50A6F2927E4B8C87BB094D5FBF15F1287429A09111806FC44B3CD86CACA8")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeOsmosis, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeOsmosis, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeOsmosis)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeOsmosis)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeOsmosis), 6); - ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeOsmosis)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeOsmosis)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeOsmosis)); - assertStringsEqual(symbol, "OSMO"); - assertStringsEqual(txUrl, "https://mintscan.io/osmosis/txs/5A6E50A6F2927E4B8C87BB094D5FBF15F1287429A09111806FC44B3CD86CACA8"); - assertStringsEqual(accUrl, "https://mintscan.io/osmosis/account/osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5"); - assertStringsEqual(id, "osmosis"); - assertStringsEqual(name, "Osmosis"); -} diff --git a/tests/POANetwork/TWCoinTypeTests.cpp b/tests/POANetwork/TWCoinTypeTests.cpp deleted file mode 100644 index 37ca284bc69..00000000000 --- a/tests/POANetwork/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWPOANetworkCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypePOANetwork)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypePOANetwork, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypePOANetwork, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypePOANetwork)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypePOANetwork)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypePOANetwork), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypePOANetwork)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypePOANetwork)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypePOANetwork)); - assertStringsEqual(symbol, "POA"); - assertStringsEqual(txUrl, "https://blockscout.com/poa/core/tx/t123"); - assertStringsEqual(accUrl, "https://blockscout.com/poa/core/address/a12"); - assertStringsEqual(id, "poa"); - assertStringsEqual(name, "POA Network"); -} diff --git a/tests/Polkadot/AddressTests.cpp b/tests/Polkadot/AddressTests.cpp deleted file mode 100644 index 6cc3fb06368..00000000000 --- a/tests/Polkadot/AddressTests.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "Polkadot/Address.h" -#include "PublicKey.h" -#include "PrivateKey.h" -#include -#include - -using namespace TW; -using namespace TW::Polkadot; - -TEST(PolkadotAddress, Validation) { - // Substrate ed25519 - ASSERT_FALSE(Address::isValid("5FqqU2rytGPhcwQosKRtW1E3ha6BJKAjHgtcodh71dSyXhoZ")); - // Bitcoin - ASSERT_FALSE(Address::isValid("1ES14c7qLb5CYhLMUekctxLgc1FV2Ti9DA")); - // Kusama ed25519 - ASSERT_FALSE(Address::isValid("FHKAe66mnbk8ke8zVWE9hFVFrJN1mprFPVmD5rrevotkcDZ")); - // Kusama secp256k1 - ASSERT_FALSE(Address::isValid("FxQFyTorsjVsjjMyjdgq8w5vGx8LiA1qhWbRYcFijxKKchx")); - // Kusama sr25519 - ASSERT_FALSE(Address::isValid("EJ5UJ12GShfh7EWrcNZFLiYU79oogdtXFUuDDZzk7Wb2vCe")); - - // Polkadot ed25519 - ASSERT_TRUE(Address::isValid("15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu")); - // Polkadot sr25519 - ASSERT_TRUE(Address::isValid("15AeCjMpcSt3Fwa47jJBd7JzQ395Kr2cuyF5Zp4UBf1g9ony")); -} - -TEST(PolkadotAddress, FromPrivateKey) { - // subkey phrase `chief menu kingdom stereo hope hazard into island bag trick egg route` - auto privateKey = PrivateKey(parse_hex("0x612d82bc053d1b4729057688ecb1ebf62745d817ddd9b595bc822f5f2ba0e41a")); - auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519)); - ASSERT_EQ(address.string(), "15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu"); -} - -TEST(PolkadotAddress, FromPublicKey) { - auto publicKey = PublicKey(parse_hex("0xbeff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519); - auto address = Address(publicKey); - ASSERT_EQ(address.string(), "15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu"); -} - -TEST(PolkadotAddress, FromString) { - auto address = Address("15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu"); - ASSERT_EQ(address.string(), "15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu"); -} diff --git a/tests/Polkadot/SignerTests.cpp b/tests/Polkadot/SignerTests.cpp deleted file mode 100644 index 376a9402edf..00000000000 --- a/tests/Polkadot/SignerTests.cpp +++ /dev/null @@ -1,339 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Polkadot/Signer.h" -#include "Polkadot/Extrinsic.h" -#include "Polkadot/Address.h" -#include "Polkadot/SS58Address.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include "proto/Polkadot.pb.h" -#include "uint256.h" - -#include -#include - - -namespace TW::Polkadot { - auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); - auto privateKeyIOS = PrivateKey(parse_hex("37932b086586a6675e66e562fe68bd3eeea4177d066619c602fe3efc290ada62")); - auto privateKeyThrow2 = PrivateKey(parse_hex("70a794d4f1019c3ce002f33062f45029c4f930a56b3d20ec477f7668c6bbc37f")); - auto privateKeyPolkadot = PrivateKey(parse_hex("298fcced2b497ed48367261d8340f647b3fca2d9415d57c2e3c5ef90482a2266")); - auto addressThrow2 = "14Ztd3KJDaB9xyJtRkREtSZDdhLSbm7UUKt8Z7AwSv7q85G2"; - auto toPublicKey = PublicKey(parse_hex("0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"), TWPublicKeyTypeED25519); - auto genesisHash = parse_hex("91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"); - auto controller1 = "14xKzzU1ZYDnzFj7FgdtDAYSMJNARjDc2gNw4XAFDgr4uXgp"; - -TEST(PolkadotSigner, SignTransfer_9fd062) { - auto toAddress = Address("13ZLCqJNPsRZYEbwjtZZFpWt9GyFzg5WahXCVWKpWdUJqrQ5"); - - auto input = Proto::SigningInput(); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - auto blockHash = parse_hex("0x5d2143bb808626d63ad7e1cda70fa8697059d670a992e82cd440fbb95ea40351"); - input.set_block_hash(blockHash.data(), blockHash.size()); - input.set_nonce(3); - input.set_spec_version(26); - { - PublicKey publicKey = privateKeyThrow2.getPublicKey(TWPublicKeyTypeED25519); - Address address = Address(publicKey); - EXPECT_EQ(address.string(), addressThrow2); - } - input.set_private_key(privateKeyThrow2.bytes.data(), privateKeyThrow2.bytes.size()); - input.set_network(Proto::Network::POLKADOT); - input.set_transaction_version(5); - - // era: for blockhash and block number, use curl -H "Content-Type: application/json" -H "Accept: text/plain" https:///transaction/material?noMeta=true - auto era = input.mutable_era(); - era->set_block_number(3541050); - era->set_period(64); - - auto balanceCall = input.mutable_balance_call(); - auto transfer = balanceCall->mutable_transfer(); - auto value = store(uint256_t(2000000000)); // 0.2 - transfer->set_to_address(toAddress.string()); - transfer->set_value(value.data(), value.size()); - - auto extrinsic = Extrinsic(input); - auto preimage = extrinsic.encodePayload(); - EXPECT_EQ(hex(preimage), "05007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0300943577a5030c001a0000000500000091b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c35d2143bb808626d63ad7e1cda70fa8697059d670a992e82cd440fbb95ea40351"); - - auto output = Signer::sign(input); - // https://polkadot.subscan.io/extrinsic/0x9fd06208a6023e489147d8d93f0182b0cb7e45a40165247319b87278e08362d8 - EXPECT_EQ(hex(output.encoded()), "3502849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f7830073e59cef381aedf56d7af076bafff9857ffc1e3bd7d1d7484176ff5b58b73f1211a518e1ed1fd2ea201bd31869c0798bba4ffe753998c409d098b65d25dff801a5030c0005007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0300943577"); -} - -TEST(PolkadotSigner, SignTransferDOT) { - - auto blockHash = parse_hex("0x343a3f4258fd92f5ca6ca5abdf473d86a78b0bcd0dc09c568ca594245cc8c642"); - auto toAddress = SS58Address(toPublicKey, TWSS58AddressTypePolkadot); - - auto input = Proto::SigningInput(); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_block_hash(blockHash.data(), blockHash.size()); - - input.set_nonce(0); - input.set_spec_version(17); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::POLKADOT); - input.set_transaction_version(3); - - auto& era = *input.mutable_era(); - era.set_block_number(927699); - era.set_period(8); - - auto balanceCall = input.mutable_balance_call(); - auto& transfer = *balanceCall->mutable_transfer(); - auto value = store(uint256_t(12345)); - transfer.set_to_address(toAddress.string()); - transfer.set_value(value.data(), value.size()); - - auto extrinsic = Extrinsic(input); - auto preimage = extrinsic.encodePayload(); - auto output = Signer::sign(input); - - ASSERT_EQ(hex(preimage), "05008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c032000000110000000300000091b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3343a3f4258fd92f5ca6ca5abdf473d86a78b0bcd0dc09c568ca594245cc8c642"); - ASSERT_EQ(hex(output.encoded()), "29028488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee003d91a06263956d8ce3ce5c55455baefff299d9cb2bb3f76866b6828ee4083770b6c03b05d7b6eb510ac78d047002c1fe5c6ee4b37c9c5a8b09ea07677f12e50d3200000005008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); -} - -TEST(PolkadotSigner, SignTransfer_72dd5b) { - - auto blockHash = parse_hex("7d5fa17b70251d0806f26156b1b698dfd09e040642fa092595ce0a78e9e84fcd"); - - auto input = Proto::SigningInput(); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_block_hash(blockHash.data(), blockHash.size()); - - input.set_nonce(1); - input.set_spec_version(28); - input.set_private_key(privateKeyIOS.bytes.data(), privateKeyIOS.bytes.size()); - input.set_network(Proto::Network::POLKADOT); - input.set_transaction_version(6); - - auto& era = *input.mutable_era(); - era.set_block_number(3910736); - era.set_period(64); - - auto balanceCall = input.mutable_balance_call(); - auto& transfer = *balanceCall->mutable_transfer(); - auto value = store(uint256_t(10000000000)); - transfer.set_to_address("13ZLCqJNPsRZYEbwjtZZFpWt9GyFzg5WahXCVWKpWdUJqrQ5"); - transfer.set_value(value.data(), value.size()); - - auto extrinsic = Extrinsic(input); - auto preimage = extrinsic.encodePayload(); - auto output = Signer::sign(input); - - ASSERT_EQ(hex(preimage), "0500007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0700e40b5402050104001c0000000600000091b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c37d5fa17b70251d0806f26156b1b698dfd09e040642fa092595ce0a78e9e84fcd"); - ASSERT_EQ(hex(output.encoded()), "410284008d96660f14babe708b5e61853c9f5929bc90dd9874485bf4d6dc32d3e6f22eaa0038ec4973ab9773dfcbf170b8d27d36d89b85c3145e038d68914de83cf1f7aca24af64c55ec51ba9f45c5a4d74a9917dee380e9171108921c3e5546e05be15206050104000500007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0700e40b5402"); -} - -TEST(PolkadotSigner, SignBond_8da66d) { - auto input = Proto::SigningInput(); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - auto blockHash = parse_hex("0xf1eee612825f29abd3299b486e401299df2faa55b7ce1e34bf2243bd591905fc"); - input.set_block_hash(blockHash.data(), blockHash.size()); - input.set_nonce(0); - input.set_spec_version(26); - { - PublicKey publicKey = privateKeyThrow2.getPublicKey(TWPublicKeyTypeED25519); - Address address = Address(publicKey); - EXPECT_EQ(address.string(), addressThrow2); - } - input.set_private_key(privateKeyThrow2.bytes.data(), privateKeyThrow2.bytes.size()); - input.set_network(Proto::Network::POLKADOT); - input.set_transaction_version(5); - - // era: for blockhash and block number, use curl -H "Content-Type: application/json" -H "Accept: text/plain" https:///transaction/material?noMeta=true - auto era = input.mutable_era(); - era->set_block_number(3540912); - era->set_period(64); - - auto stakingCall = input.mutable_staking_call(); - auto bond = stakingCall->mutable_bond(); - auto value = store(uint256_t(11000000000)); // 1.1 - bond->set_controller(addressThrow2); // myself - bond->set_value(value.data(), value.size()); - bond->set_reward_destination(Proto::RewardDestination::STASH); - - auto output = Signer::sign(input); - // https://polkadot.subscan.io/extrinsic/0x8da66d3fe0f592cff714ec107289370365117a1abdb72a19ac91181fdcf62bba - ASSERT_EQ(hex(output.encoded()), "3d02849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f783009025843bc49c1c4fbc99dbbd290c92f9879665d55b02f110abfb4800f0e7630877d2cffd853deae7466c22fbc8616a609e1b92615bb365ea8adccba5ef7624050503000007009dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f7830700aea68f0201"); -} - -TEST(PolkadotSigner, SignBondAndNominate_4955314_2) { - - auto key = parse_hex("7f44b19b391a8015ca4c7d94097b3695867a448d1391e7f3243f06987bdb6858"); - auto input = Proto::SigningInput(); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_block_hash(genesisHash.data(), genesisHash.size()); - input.set_nonce(4); - input.set_spec_version(30); - input.set_private_key(key.data(), key.size()); - input.set_network(Proto::Network::POLKADOT); - input.set_transaction_version(7); - - auto stakingCall = input.mutable_staking_call(); - auto bondnom = stakingCall->mutable_bond_and_nominate(); - auto value = store(uint256_t(10000000000)); // 1 DOT - bondnom->set_controller("13ZLCqJNPsRZYEbwjtZZFpWt9GyFzg5WahXCVWKpWdUJqrQ5"); - bondnom->set_value(value.data(), value.size()); - bondnom->set_reward_destination(Proto::RewardDestination::STASH); - bondnom->add_nominators("1zugcavYA9yCuYwiEYeMHNJm9gXznYjNfXQjZsZukF1Mpow"); - bondnom->add_nominators("15oKi7HoBQbwwdQc47k71q4sJJWnu5opn1pqoGx4NAEYZSHs"); - - auto output = Signer::sign(input); - // https://polkadot.subscan.io/extrinsic/4955314-2 - ASSERT_EQ(hex(output.encoded()), "6103840036092fac541e0e5feda19e537c679b487566d7101141c203ac8322c27e5f076a00a8b1f859d788f11a958e98b731358f89cf3fdd41a667ea992522e8d4f46915f4c03a1896f2ac54bdc5f16e2ce8a2a3bf233d02aad8192332afd2113ed6688e0d0010001a02080700007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0700e40b540201070508002c2a55b5ffdca266bd0207df97565b03255f70783ca1a349be5ed9f44589c36000d44533a4d21fd9d6f5d57c8cd05c61a6f23f9131cec8ae386b6b437db399ec3d"); -} - -TEST(PolkadotSigner, SignNominate_452522) { - auto input = Proto::SigningInput(); - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - auto blockHash = parse_hex("0x211787d016e39007ac054547737a10542620013e73648b3134541d536cb44e2c"); - input.set_block_hash(blockHash.data(), blockHash.size()); - input.set_nonce(1); - input.set_spec_version(26); - input.set_private_key(privateKeyThrow2.bytes.data(), privateKeyThrow2.bytes.size()); - input.set_network(Proto::Network::POLKADOT); - input.set_transaction_version(5); - - // era: for blockhash and block number, use curl -H "Content-Type: application/json" -H "Accept: text/plain" https:///transaction/material?noMeta=true - auto era = input.mutable_era(); - era->set_block_number(3540945); - era->set_period(64); - - auto stakingCall = input.mutable_staking_call(); - auto nominate = stakingCall->mutable_nominate(); - - nominate->add_nominators(controller1); - nominate->add_nominators("1REAJ1k691g5Eqqg9gL7vvZCBG7FCCZ8zgQkZWd4va5ESih"); - - auto output = Signer::sign(input); - // https://polkadot.subscan.io/extrinsic/0x4525224b7d8f3e58de3a54a9fbfd071401c2b737f314c972a2bb087a0ff508a6 - ASSERT_EQ(hex(output.encoded()), "a502849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f78300d73ff0dc456704743f70173a56e6c13e88a6e1dddb38a23552a066e44fb64e2c9d8a5e9a76afb9489b8540365f668bddd34b7d9c8dbdc4600e6316080e55a30315010400070508aee72821ca00e62304e4f0d858122a65b87c8df4f0eae224ae064b951d39f610127a30e486492921e58f2564b36ab1ca21ff630672f0e76920edd601f8f2b89a"); -} - -TEST(PolkadotSigner, SignNominate2) { - auto blockHash = parse_hex("d22a6b2e3e61325050718bd04a14da9efca1f41c9f0a525c375d36106e25af68"); - auto input = Proto::SigningInput(); - - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_block_hash(blockHash.data(), blockHash.size()); - input.set_nonce(0); - input.set_spec_version(17); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::POLKADOT); - input.set_transaction_version(3); - - auto stakingCall = input.mutable_staking_call(); - auto& nominate = *stakingCall->mutable_nominate(); - // payload size larger than 256, will be hashed - nominate.add_nominators("1zugcabYjgfQdMLC3cAzQ8tJZMo45tMnGpivpAzpxB4CZyK"); - nominate.add_nominators("1REAJ1k691g5Eqqg9gL7vvZCBG7FCCZ8zgQkZWd4va5ESih"); - nominate.add_nominators("1WG3jyNqniQMRZGQUc7QD2kVLT8hkRPGMSqAb5XYQM1UDxN"); - nominate.add_nominators("16QFrtU6kDdBjxY8qEKz5EEfuDkHxqG8pix3wSGKQzRcuWHo"); - nominate.add_nominators("14ShUZUYUR35RBZW6uVVt1zXDxmSQddkeDdXf1JkMA6P721N"); - nominate.add_nominators("15MUBwP6dyVw5CXF9PjSSv7SdXQuDSwjX86v1kBodCSWVR7c"); - - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "a1048488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee00135bbc68b67fffadaf7e98b6402c4fc60382765f543225083a024b0e0ff8071d4ec4ddd67a65828113cc76f3208765608be010d2fcfdcd47e8fe342872704c000000000705182c2a55b5a116a4c88aff57e8f2b70ba72dda72dda4b78630e16ad0ca69006f18127a30e486492921e58f2564b36ab1ca21ff630672f0e76920edd601f8f2b89a1650c532ed1a8641e8922aa24ade0ff411d03edd9ed1c6b7fe42f1a801cee37ceee9d5d071a418b51c02b456d5f5cefd6231041ad59b0e8379c59c11ba4a2439984e16482c99cfad1436111e321a86d87d0fac203bf64538f888e45d793b5413c08d5de7a5d97bea2c7ddf516d0635bddc43f326ae2f80e2595b49d4a08c4619"); -} - -TEST(PolkadotSigner, SignChill) { - auto blockHash = parse_hex("1d4a1ecc8b1c37bf0ba5d3e0bf14ec5402fbb035eeaf6d8042c07ca5f8c57429"); - auto input = Proto::SigningInput(); - - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_block_hash(blockHash.data(), blockHash.size()); - input.set_nonce(0); - input.set_spec_version(17); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::POLKADOT); - input.set_transaction_version(3); - - auto stakingCall = input.mutable_staking_call(); - auto __attribute__((unused)) &chill = *stakingCall->mutable_chill(); - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "9d018488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0088b5e1cd93ba74b82e329f95e1b22660385970182172b2ae280801fdd1ee5652cf7bf319e5e176ccc299dd8eb1e7fccb0ea7717efaf4aacd7640789dd09c1e070000000706"); -} - -TEST(PolkadotSigner, SignWithdraw) { - auto blockHash = parse_hex("7b4d1d1e2573eabcc90a3e96058eb0d8d21d7a0b636e8030d152d9179a345dda"); - auto input = Proto::SigningInput(); - - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_block_hash(blockHash.data(), blockHash.size()); - input.set_nonce(0); - input.set_spec_version(17); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_network(Proto::Network::POLKADOT); - input.set_transaction_version(3); - - auto stakingCall = input.mutable_staking_call(); - auto& withdraw = *stakingCall->mutable_withdraw_unbonded(); - withdraw.set_slashing_spans(10); - - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.encoded()), "ad018488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee002e49bf0dec9bef01dd3bd25419e2147dc983613d0860108f889f9ff2d062c5e3267e309e2dbc35dd2fc2b877b57d86a5f12cbeb8217485be32be3c34d2507d0e00000007030a000000"); -} - -TEST(PolkadotSigner, SignUnbond_070957) { - auto input = Proto::SigningInput(); - - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - auto blockHash = parse_hex("0x53040c71c6061bd256346b81fcb3545c13b5c34c7cd0c2c25f00aa6e564b16d5"); - input.set_block_hash(blockHash.data(), blockHash.size()); - input.set_nonce(2); - input.set_spec_version(26); - input.set_private_key(privateKeyThrow2.bytes.data(), privateKeyThrow2.bytes.size()); - input.set_network(Proto::Network::POLKADOT); - input.set_transaction_version(5); - - auto era = input.mutable_era(); - era->set_block_number(3540983); - era->set_period(64); - - auto stakingCall = input.mutable_staking_call(); - auto unbond = stakingCall->mutable_unbond(); - auto value = store(uint256_t(4000000000)); - unbond->set_value(value.data(), value.size()); - - auto output = Signer::sign(input); - // https://polkadot.subscan.io/extrinsic/0x070957ab697adbe11f7d72a1314d0a81d272a747d2e6880818073317125f980a - ASSERT_EQ(hex(output.encoded()), "b501849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f783003a762d9dc3f2aba8922c4babf7e6622ca1d74da17ab3f152d8f29b0ffee53c7e5e150915912a9dfd98ef115d272e096543eef9f513207dd606eea97d023a64087503080007020300286bee"); -} - -TEST(PolkadotSigner, SignChillAndUnbond) { - auto blockHash = parse_hex("0x35ba668bb19453e8da6334cadcef2a27c8d4141bfc8b49e78e853c3d73e1ecd0"); - auto input = Proto::SigningInput(); - - input.set_genesis_hash(genesisHash.data(), genesisHash.size()); - input.set_block_hash(blockHash.data(), blockHash.size()); - input.set_nonce(6); - input.set_spec_version(9200); - input.set_private_key(privateKeyPolkadot.bytes.data(), privateKeyPolkadot.bytes.size()); - input.set_network(Proto::Network::POLKADOT); - input.set_transaction_version(12); - - auto era = input.mutable_era(); - era->set_block_number(10541373); - era->set_period(64); - - auto stakingCall = input.mutable_staking_call(); - auto chillBond = stakingCall->mutable_chill_and_unbond(); - auto value = store(uint256_t(100500000000)); // 10.05 DOT - chillBond->set_value(value.data(), value.size()); - - auto output = Signer::sign(input); - // https://polkadot.subscan.io/extrinsic/10541383-2 - ASSERT_EQ(hex(output.encoded()), "d10184008361bd08ddca5fda28b5e2aa84dc2621de566e23e089e555a42194c3eaf2da7900c891ba102db672e378945d74cf7f399226a76b43cab502436971599255451597fc2599902e4b62c7ce85ecc3f653c693fef3232be620984b5bb5bcecbbd7b209d50318001a02080706070207004d446617"); -} - -} // namespace diff --git a/tests/Polkadot/TWCoinTypeTests.cpp b/tests/Polkadot/TWCoinTypeTests.cpp deleted file mode 100644 index 82030aaeeec..00000000000 --- a/tests/Polkadot/TWCoinTypeTests.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWPolkadotCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypePolkadot)); - - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xb96f97d8ee508f420e606e1a6dcc74b88844713ddec2bd7cf4e3aa6b1d6beef4")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypePolkadot, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("13hJFqnkqQbmgnGQteGntjMjTdmTBRE8Z93JqxsrpgT7Yjd2")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypePolkadot, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypePolkadot)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypePolkadot)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypePolkadot), 10); - ASSERT_EQ(TWBlockchainPolkadot, TWCoinTypeBlockchain(TWCoinTypePolkadot)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypePolkadot)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypePolkadot)); - assertStringsEqual(symbol, "DOT"); - assertStringsEqual(txUrl, "https://polkadot.subscan.io/extrinsic/0xb96f97d8ee508f420e606e1a6dcc74b88844713ddec2bd7cf4e3aa6b1d6beef4"); - assertStringsEqual(accUrl, "https://polkadot.subscan.io/account/13hJFqnkqQbmgnGQteGntjMjTdmTBRE8Z93JqxsrpgT7Yjd2"); - assertStringsEqual(id, "polkadot"); - assertStringsEqual(name, "Polkadot"); -} diff --git a/tests/Polygon/TWCoinTypeTests.cpp b/tests/Polygon/TWCoinTypeTests.cpp deleted file mode 100644 index a1810ade109..00000000000 --- a/tests/Polygon/TWCoinTypeTests.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWPolygonCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypePolygon)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xe26ed1470d5bf99a53d687843e7acdf7e4ba6620af93b4d672e714de90476e8e")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypePolygon, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x720E1fa107A1Df39Db4E78A3633121ac36Bec132")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypePolygon, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypePolygon)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypePolygon)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypePolygon), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypePolygon)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypePolygon)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypePolygon)); - assertStringsEqual(symbol, "MATIC"); - assertStringsEqual(txUrl, "https://polygonscan.com/tx/0xe26ed1470d5bf99a53d687843e7acdf7e4ba6620af93b4d672e714de90476e8e"); - assertStringsEqual(accUrl, "https://polygonscan.com/address/0x720E1fa107A1Df39Db4E78A3633121ac36Bec132"); - assertStringsEqual(id, "polygon"); - assertStringsEqual(name, "Polygon"); -} diff --git a/tests/PrivateKeyTests.cpp b/tests/PrivateKeyTests.cpp deleted file mode 100644 index bde1b2124a4..00000000000 --- a/tests/PrivateKeyTests.cpp +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "PrivateKey.h" -#include "PublicKey.h" -#include "HexCoding.h" -#include "Hash.h" - -#include - -using namespace TW; -using namespace std; - - -TEST(PrivateKey, CreateValid) { - Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - EXPECT_TRUE(PrivateKey::isValid(privKeyData, TWCurveSECP256k1)); - auto privateKey = PrivateKey(privKeyData); - EXPECT_EQ(hex(privKeyData), hex(privateKey.bytes)); -} - -string TestInvalid(const Data& privKeyData) { - try { - auto privateKey = PrivateKey(privKeyData); - return hex(privateKey.bytes); - } catch (invalid_argument& ex) { - // expected exception - return string("EXCEPTION: ") + string(ex.what()); - } -} - -TEST(PrivateKey, InvalidShort) { - string res = TestInvalid(parse_hex("deadbeef")); - EXPECT_EQ("EXCEPTION: Invalid private key data", res); -} - -TEST(PrivateKey, InvalidAllZeros) { - string res = TestInvalid(Data(32)); - EXPECT_EQ("EXCEPTION: Invalid private key data", res); -} - -TEST(PrivateKey, InvalidSECP256k1) { - { - auto privKeyData = parse_hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); - auto valid = PrivateKey::isValid(privKeyData, TWCurveSECP256k1); - EXPECT_EQ(valid, false); - } - { - auto privKeyData = parse_hex("0000000000000000000000000000000000000000000000000000000000000000"); - auto valid = PrivateKey::isValid(privKeyData, TWCurveSECP256k1); - EXPECT_EQ(valid, false); - } -} - -string TestInvalidExtended(const Data& data, const Data& ext, const Data& chainCode, const Data& data2, const Data& ext2, const Data& chainCode2) { - try { - auto privateKey = PrivateKey(data, ext, chainCode, data2, ext2, chainCode2); - return hex(privateKey.bytes); - } catch (invalid_argument& ex) { - // expected exception - return string("EXCEPTION: ") + string(ex.what()); - } -} - -TEST(PrivateKey, CreateExtendedInvalid) { - { - string res = TestInvalidExtended( - parse_hex("deadbeed"), - parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), - parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), - parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), - parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), - parse_hex("1111111111111111111111111111111111111111111111111111111111111111") - ); - EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); - } - { - string res = TestInvalidExtended( - parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), - parse_hex("deadbeed"), - parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), - parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), - parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), - parse_hex("1111111111111111111111111111111111111111111111111111111111111111") - ); - EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); - } - { - string res = TestInvalidExtended( - parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), - parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), - parse_hex("deadbeed"), - parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), - parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), - parse_hex("1111111111111111111111111111111111111111111111111111111111111111") - ); - EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); - } - { - string res = TestInvalidExtended( - parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), - parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), - parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), - parse_hex("deadbeed"), - parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), - parse_hex("1111111111111111111111111111111111111111111111111111111111111111") - ); - EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); - } -} - -TEST(PrivateKey, Valid) { - Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - EXPECT_TRUE(PrivateKey::isValid(privKeyData, TWCurveSECP256k1)); - EXPECT_TRUE(PrivateKey::isValid(privKeyData, TWCurveED25519)); -} - -TEST(PrivateKey, PublicKey) { - Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(privKeyData); - { - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - EXPECT_EQ( - "4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867", - hex(publicKey.bytes) - ); - } - { - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_EQ( - "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1", - hex(publicKey.bytes) - ); - } - { - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); - EXPECT_EQ( - "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91", - hex(publicKey.bytes) - ); - } - { - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1Extended); - EXPECT_EQ( - "046d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab918b4fe46ccbf56701fb210d67d91c5779468f6b3fdc7a63692b9b62543f47ae", - hex(publicKey.bytes) - ); - } -} - -TEST(PrivateKey, Cleanup) { - Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = new PrivateKey(privKeyData); - auto ptr = privateKey->bytes.data(); - ASSERT_EQ(hex(privKeyData), hex(data(ptr, 32))); - - privateKey->cleanup(); - - // Memory cleaned (filled with 0s). They may be overwritten by something else; we check that it is not equal to original, most of it has changed. - ASSERT_EQ(hex(data(ptr, 32)), "0000000000000000000000000000000000000000000000000000000000000000"); - - delete privateKey; - - // Note: it would be good to check the memory area after deletion of the object, but this is not possible -} - -TEST(PrivateKey, PrivateKeyExtended) { - // Non-extended: both keys are 32 bytes. - auto privateKeyNonext = PrivateKey(parse_hex( - "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5" - )); - EXPECT_EQ("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", hex(privateKeyNonext.bytes)); - auto publicKeyNonext = privateKeyNonext.getPublicKey(TWPublicKeyTypeED25519); - EXPECT_EQ(32, publicKeyNonext.bytes.size()); - - const auto fullkey = - "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744" - "309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff" - "bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" - "639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05" - "d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7b" - "ed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a"; - // Extended keys: private key is 2x3x32 bytes, public key is 2x64 bytes - auto privateKeyExt = PrivateKey(parse_hex(fullkey)); - EXPECT_EQ(fullkey, hex(privateKeyExt.bytes)); - EXPECT_EQ("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744", hex(privateKeyExt.key())); - EXPECT_EQ("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff", hex(privateKeyExt.extension())); - EXPECT_EQ("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4", hex(privateKeyExt.chainCode())); - EXPECT_EQ("639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05", hex(privateKeyExt.secondKey())); - EXPECT_EQ("d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7b", hex(privateKeyExt.secondExtension())); - EXPECT_EQ("ed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a", hex(privateKeyExt.secondChainCode())); - - auto publicKeyExt = privateKeyExt.getPublicKey(TWPublicKeyTypeED25519Extended); - EXPECT_EQ(2*64, publicKeyExt.bytes.size()); - - // Try other constructor for extended key - auto privateKeyExtOne = PrivateKey( - parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), - parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), - parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), - parse_hex("639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05"), - parse_hex("d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7b"), - parse_hex("ed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a") - ); - EXPECT_EQ(fullkey, hex(privateKeyExt.bytes)); -} - -TEST(PrivateKey, PrivateKeyExtendedError) { - // TWPublicKeyTypeED25519Extended pubkey with non-extended private: error - auto privateKeyNonext = PrivateKey(parse_hex( - "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5" - )); - try { - auto publicKeyError = privateKeyNonext.getPublicKey(TWPublicKeyTypeED25519Extended); - } catch (invalid_argument& ex) { - // expected exception - return; - } - FAIL() << "Should throw Invalid empty key extension"; -} - -TEST(PrivateKey, getSharedKey) { - Data privKeyData = parse_hex("9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0"); - EXPECT_TRUE(PrivateKey::isValid(privKeyData, TWCurveSECP256k1)); - auto privateKey = PrivateKey(privKeyData); - - const Data pubKeyData = parse_hex("02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992"); - EXPECT_TRUE(PublicKey::isValid(pubKeyData, TWPublicKeyTypeSECP256k1)); - PublicKey publicKey(pubKeyData, TWPublicKeyTypeSECP256k1); - EXPECT_TRUE(publicKey.isCompressed()); - - const Data derivedKeyData = privateKey.getSharedKey(publicKey, TWCurveSECP256k1); - - EXPECT_EQ( - "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a", - hex(derivedKeyData) - ); -} - -/** - * Valid test vector from Wycherproof project - * Source: https://github.com/google/wycheproof/blob/master/testvectors/ecdh_secp256k1_test.json#L31 - */ -TEST(PrivateKey, getSharedKeyWycherproof) { - // Stripped left-padded zeroes from: `00f4b7ff7cccc98813a69fae3df222bfe3f4e28f764bf91b4a10d8096ce446b254` - Data privKeyData = parse_hex("f4b7ff7cccc98813a69fae3df222bfe3f4e28f764bf91b4a10d8096ce446b254"); - EXPECT_TRUE(PrivateKey::isValid(privKeyData, TWCurveSECP256k1)); - auto privateKey = PrivateKey(privKeyData); - - // Decoded from ASN.1 & uncompressed `3056301006072a8648ce3d020106052b8104000a03420004d8096af8a11e0b80037e1ee68246b5dcbb0aeb1cf1244fd767db80f3fa27da2b396812ea1686e7472e9692eaf3e958e50e9500d3b4c77243db1f2acd67ba9cc4` - const Data pubKeyData = parse_hex("02d8096af8a11e0b80037e1ee68246b5dcbb0aeb1cf1244fd767db80f3fa27da2b"); - EXPECT_TRUE(PublicKey::isValid(pubKeyData, TWPublicKeyTypeSECP256k1)); - PublicKey publicKey(pubKeyData, TWPublicKeyTypeSECP256k1); - EXPECT_TRUE(publicKey.isCompressed()); - - const Data derivedKeyData = privateKey.getSharedKey(publicKey, TWCurveSECP256k1); - - // SHA-256 of encoded x-coordinate `02544dfae22af6af939042b1d85b71a1e49e9a5614123c4d6ad0c8af65baf87d65` - EXPECT_EQ( - "81165066322732362ca5d3f0991d7f1f7d0aad7ea533276496785d369e35159a", - hex(derivedKeyData) - ); -} - -TEST(PrivateKey, getSharedKeyBidirectional) { - Data privKeyData1 = parse_hex("9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0"); - EXPECT_TRUE(PrivateKey::isValid(privKeyData1, TWCurveSECP256k1)); - auto privateKey1 = PrivateKey(privKeyData1); - auto publicKey1 = privateKey1.getPublicKey(TWPublicKeyTypeSECP256k1); - - Data privKeyData2 = parse_hex("ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a"); - EXPECT_TRUE(PrivateKey::isValid(privKeyData2, TWCurveSECP256k1)); - auto privateKey2 = PrivateKey(privKeyData2); - auto publicKey2 = privateKey2.getPublicKey(TWPublicKeyTypeSECP256k1); - - const Data derivedKeyData1 = privateKey1.getSharedKey(publicKey2, TWCurveSECP256k1); - const Data derivedKeyData2 = privateKey2.getSharedKey(publicKey1, TWCurveSECP256k1); - - EXPECT_EQ(hex(derivedKeyData1), hex(derivedKeyData2)); -} - -TEST(PrivateKey, getSharedKeyError) { - Data privKeyData = parse_hex("9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0"); - auto privateKey = PrivateKey(privKeyData); - - const Data pubKeyData = parse_hex("02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992"); - PublicKey publicKey(pubKeyData, TWPublicKeyTypeSECP256k1); - - const Data derivedKeyData = privateKey.getSharedKey(publicKey, TWCurveCurve25519); - const Data expected = {}; - - EXPECT_EQ(expected, derivedKeyData); -} - -TEST(PrivateKey, SignSECP256k1) { - Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(privKeyData); - Data messageData = TW::data("hello"); - Data hash = Hash::keccak256(messageData); - Data actual = privateKey.sign(hash, TWCurveSECP256k1); - - EXPECT_EQ( - "8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901", - hex(actual) - ); -} - -TEST(PrivateKey, SignExtended) { - const auto privateKeyExt = PrivateKey(parse_hex( - "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" - "639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a" - )); - Data messageData = TW::data("hello"); - Data hash = Hash::keccak256(messageData); - Data actual = privateKeyExt.sign(hash, TWCurveED25519Extended); - - EXPECT_EQ( - "375df53b6a4931dcf41e062b1c64288ed4ff3307f862d5c1b1c71964ce3b14c99422d0fdfeb2807e9900a26d491d5e8a874c24f98eec141ed694d7a433a90f08", - hex(actual) - ); -} - -TEST(PrivateKey, SignSchnorr) { - const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - const Data messageData = TW::data("hello schnorr"); - const Data digest = Hash::sha256(messageData); - const auto signature = privateKey.signSchnorr(digest, TWCurveSECP256k1); - EXPECT_EQ(hex(signature), - "b8118ccb99563fe014279c957b0a9d563c1666e00367e9896fe541765246964f64a53052513da4e6dc20fdaf69ef0d95b4ca51c87ad3478986cf053c2dd0b853" - ); -} - -TEST(PrivateKey, SignSchnorrWrongType) { - const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - const Data messageData = TW::data("hello schnorr"); - const Data digest = Hash::sha256(messageData); - const auto signature = privateKey.signSchnorr(digest, TWCurveNIST256p1); - EXPECT_EQ(signature.size(), 0); -} - -TEST(PrivateKey, SignNIST256p1) { - Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(privKeyData); - Data messageData = TW::data("hello"); - Data hash = Hash::keccak256(messageData); - Data actual = privateKey.sign(hash, TWCurveNIST256p1); - - EXPECT_EQ( - "8859e63a0c0cc2fc7f788d7e78406157b288faa6f76f76d37c4cd1534e8d83c468f9fd6ca7dde378df594625dcde98559389569e039282275e3d87c26e36447401", - hex(actual) - ); -} - -int isCanonical(uint8_t by, uint8_t sig[64]) { - return 1; -} - -TEST(PrivateKey, SignCanonicalSECP256k1) { - Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(privKeyData); - Data messageData = TW::data("hello"); - Data hash = Hash::keccak256(messageData); - Data actual = privateKey.sign(hash, TWCurveSECP256k1, isCanonical); - - EXPECT_EQ( - "208720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de9", - hex(actual) - ); -} - -TEST(PrivateKey, SignShortDigest) { - Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(privKeyData); - Data shortDigest = TW::data("12345"); - { - Data actual = privateKey.sign(shortDigest, TWCurveSECP256k1); - EXPECT_EQ(actual.size(), 0); - } - { - Data actual = privateKey.sign(shortDigest, TWCurveNIST256p1); - EXPECT_EQ(actual.size(), 0); - } - { - Data actual = privateKey.sign(shortDigest, TWCurveSECP256k1, isCanonical); - EXPECT_EQ(actual.size(), 0); - } -} diff --git a/tests/PublicKeyTests.cpp b/tests/PublicKeyTests.cpp deleted file mode 100644 index 47176838204..00000000000 --- a/tests/PublicKeyTests.cpp +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "PublicKey.h" - -#include "Hash.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "interface/TWTestUtilities.h" - -#include - -using namespace TW; - -TEST(PublicKeyTests, CreateFromPrivateSecp256k1) { - const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(key); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_EQ(publicKey.bytes.size(), 33); - EXPECT_EQ(hex(publicKey.bytes), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); - EXPECT_EQ(publicKey.isCompressed(), true); - EXPECT_TRUE(PublicKey::isValid(publicKey.bytes, TWPublicKeyTypeSECP256k1)); -} - -TEST(PublicKeyTests, CreateFromDataSecp256k1) { - const Data key = parse_hex("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); - PublicKey publicKey(key, TWPublicKeyTypeSECP256k1); - EXPECT_EQ(hex(publicKey.bytes), hex(key)); -} - -TEST(PublicKeyTests, CreateInvalid) { - const Data keyInvalid = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32af"); // too short - try { - PublicKey publicKey(keyInvalid, TWPublicKeyTypeSECP256k1); - } catch (const std::invalid_argument&) { - return; // OK - } - FAIL() << "Missing expected exception"; -} - -TEST(PublicKeyTests, CreateBlake) { - const auto privateKeyHex = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; - const auto publicKeyKeyHex = "b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"; - { - auto publicKey = PrivateKey(parse_hex(privateKeyHex)).getPublicKey(TWPublicKeyTypeED25519Blake2b); - EXPECT_EQ(hex(publicKey.bytes), publicKeyKeyHex); - EXPECT_EQ(publicKey.bytes.size(), 32); - } - { - const auto publicKey = PublicKey(parse_hex(publicKeyKeyHex), TWPublicKeyTypeED25519Blake2b); - EXPECT_EQ(hex(publicKey.bytes), publicKeyKeyHex); - } -} - -TEST(PublicKeyTests, CompressedExtended) { - const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(key); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_EQ(publicKey.type, TWPublicKeyTypeSECP256k1); - EXPECT_EQ(publicKey.bytes.size(), 33); - EXPECT_EQ(publicKey.isCompressed(), true); - EXPECT_TRUE(PublicKey::isValid(publicKey.bytes, TWPublicKeyTypeSECP256k1)); - EXPECT_EQ(hex(publicKey.bytes), std::string("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1")); - - auto extended = publicKey.extended(); - EXPECT_EQ(extended.type, TWPublicKeyTypeSECP256k1Extended); - EXPECT_EQ(extended.bytes.size(), 65); - EXPECT_EQ(extended.isCompressed(), false); - EXPECT_TRUE(PublicKey::isValid(extended.bytes, TWPublicKeyTypeSECP256k1Extended)); - EXPECT_EQ(hex(extended.bytes), std::string("0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91")); - - auto compressed = extended.compressed(); - EXPECT_EQ(compressed.type, TWPublicKeyTypeSECP256k1); - EXPECT_TRUE(compressed == publicKey); - EXPECT_EQ(compressed.bytes.size(), 33); - EXPECT_EQ(compressed.isCompressed(), true); - EXPECT_TRUE(PublicKey::isValid(compressed.bytes, TWPublicKeyTypeSECP256k1)); - EXPECT_EQ(hex(compressed.bytes), std::string("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1")); - - auto extended2 = extended.extended(); - EXPECT_EQ(extended2.type, TWPublicKeyTypeSECP256k1Extended); - EXPECT_EQ(extended2.bytes.size(), 65); - EXPECT_EQ(extended2.isCompressed(), false); - - auto compressed2 = compressed.compressed(); - EXPECT_EQ(compressed2.type, TWPublicKeyTypeSECP256k1); - EXPECT_TRUE(compressed2 == publicKey); - EXPECT_EQ(compressed2.bytes.size(), 33); - EXPECT_EQ(compressed2.isCompressed(), true); -} - -TEST(PublicKeyTests, CompressedExtendedNist) { - const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(key); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); - EXPECT_EQ(publicKey.type, TWPublicKeyTypeNIST256p1); - EXPECT_EQ(publicKey.bytes.size(), 33); - EXPECT_EQ(publicKey.isCompressed(), true); - EXPECT_TRUE(PublicKey::isValid(publicKey.bytes, TWPublicKeyTypeNIST256p1)); - EXPECT_EQ(hex(publicKey.bytes), std::string("026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab")); - - auto extended = publicKey.extended(); - EXPECT_EQ(extended.type, TWPublicKeyTypeNIST256p1Extended); - EXPECT_EQ(extended.bytes.size(), 65); - EXPECT_EQ(extended.isCompressed(), false); - EXPECT_TRUE(PublicKey::isValid(extended.bytes, TWPublicKeyTypeNIST256p1Extended)); - EXPECT_EQ(hex(extended.bytes), std::string("046d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab918b4fe46ccbf56701fb210d67d91c5779468f6b3fdc7a63692b9b62543f47ae")); - - auto compressed = extended.compressed(); - EXPECT_EQ(compressed.type, TWPublicKeyTypeNIST256p1); - EXPECT_TRUE(compressed == publicKey); - EXPECT_EQ(compressed.bytes.size(), 33); - EXPECT_EQ(compressed.isCompressed(), true); - EXPECT_TRUE(PublicKey::isValid(compressed.bytes, TWPublicKeyTypeNIST256p1)); - EXPECT_EQ(hex(compressed.bytes), std::string("026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab")); - - auto extended2 = extended.extended(); - EXPECT_EQ(extended2.type, TWPublicKeyTypeNIST256p1Extended); - EXPECT_EQ(extended2.bytes.size(), 65); - EXPECT_EQ(extended2.isCompressed(), false); - - auto compressed2 = compressed.compressed(); - EXPECT_EQ(compressed2.type, TWPublicKeyTypeNIST256p1); - EXPECT_TRUE(compressed2 == publicKey); - EXPECT_EQ(compressed2.bytes.size(), 33); - EXPECT_EQ(compressed2.isCompressed(), true); -} - -TEST(PublicKeyTests, CompressedExtendedED25519) { - const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(key); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - EXPECT_EQ(publicKey.type, TWPublicKeyTypeED25519); - EXPECT_EQ(publicKey.bytes.size(), 32); - EXPECT_EQ(publicKey.isCompressed(), true); - EXPECT_TRUE(PublicKey::isValid(publicKey.bytes, TWPublicKeyTypeED25519)); - EXPECT_EQ(hex(publicKey.bytes), std::string("4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867")); - - auto extended = publicKey.extended(); - EXPECT_EQ(extended.type, TWPublicKeyTypeED25519); - EXPECT_TRUE(extended == publicKey); - EXPECT_EQ(extended.bytes.size(), 32); - EXPECT_EQ(extended.isCompressed(), true); - - auto compressed = publicKey.compressed(); - EXPECT_EQ(compressed.type, TWPublicKeyTypeED25519); - EXPECT_TRUE(compressed == publicKey); - EXPECT_EQ(compressed.bytes.size(), 32); - EXPECT_EQ(compressed.isCompressed(), true); -} - -TEST(PublicKeyTests, IsValidWrongType) { - EXPECT_FALSE(PublicKey::isValid(parse_hex("deadbeef"), (enum TWPublicKeyType)99)); -} - -TEST(PublicKeyTests, Verify) { - const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - - const char* message = "Hello"; - const Data messageData = TW::data(message); - const Data digest = Hash::sha256(messageData); - - { - const auto signature = privateKey.sign(digest, TWCurveSECP256k1); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_TRUE(publicKey.verify(signature, digest)); - EXPECT_EQ(hex(signature), "0f5d5a9e5fc4b82a625312f3be5d3e8ad017d882de86c72c92fcefa924e894c12071772a14201a3a0debf381b5e8dea39fadb9bcabdc02ee71ab018f55bf717f01"); - } - { - const auto signature = privateKey.sign(digest, TWCurveED25519); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - EXPECT_TRUE(publicKey.verify(signature, digest)); - EXPECT_EQ(hex(signature), "42848abf2641a731e18b8a1fb80eff341a5acebdc56faeccdcbadb960aef775192842fccec344679446daa4d02d264259c8f9aa364164ebe0ebea218581e2e03"); - } - { - const auto signature = privateKey.sign(digest, TWCurveED25519Blake2bNano); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); - EXPECT_TRUE(publicKey.verify(signature, digest)); - EXPECT_EQ(hex(signature), "5c1473944cd0234ebc5a91b2966b9e707a33b936dadd149417a2e53b6b3fc97bef17b767b1690708c74d7b4c8fe48703fd44a6ef59d4cc5b9f88ba992db0a003"); - } - { - const auto signature = privateKey.sign(digest, TWCurveNIST256p1); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1Extended); - EXPECT_TRUE(publicKey.verify(signature, digest)); - EXPECT_EQ(hex(signature), "2e4655831f0c60729583595c103bf0d862af6313e4326f03f512682106c792822f5a9cd21e7d4a3316c2d337e5eee649b09c34f7b4407344f0d32e8d33167d8901"); - } -} - -TEST(PublicKeyTests, VerifyAsDER) { - const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - - const char* message = "Hello"; - const Data messageData = TW::data(message); - const Data digest = Hash::sha256(messageData); - - const auto signature = privateKey.signAsDER(digest, TWCurveSECP256k1); - EXPECT_EQ(signature.size(), 70); - EXPECT_EQ(hex(signature), "304402200f5d5a9e5fc4b82a625312f3be5d3e8ad017d882de86c72c92fcefa924e894c102202071772a14201a3a0debf381b5e8dea39fadb9bcabdc02ee71ab018f55bf717f"); - - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_EQ(hex(publicKey.bytes), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); - - EXPECT_TRUE(publicKey.verifyAsDER(signature, digest)); - - EXPECT_FALSE(publicKey.verify(signature, digest)); - - { // Negative: wrong key type - const auto publicKeyWrong = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1Extended); - EXPECT_FALSE(publicKeyWrong.verifyAsDER(signature, digest)); - } -} - -TEST(PublicKeyTests, VerifyEd25519Extended) { - const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - - const Data messageData = TW::data("Hello"); - const Data digest = Hash::sha256(messageData); - - try { - privateKey.sign(digest, TWCurveED25519Extended); - } catch (const std::invalid_argument&) { - return; // OK, not implemented - } - FAIL() << "Missing expected exception"; -} - -TEST(PublicKeyTests, VerifySchnorr) { - const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - const auto privateKey = PrivateKey(key); - - const Data messageData = TW::data("hello schnorr"); - const Data digest = Hash::sha256(messageData); - - const auto signature = privateKey.signSchnorr(digest, TWCurveSECP256k1); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_TRUE(publicKey.verifySchnorr(signature, digest)); - EXPECT_EQ(hex(signature), "b8118ccb99563fe014279c957b0a9d563c1666e00367e9896fe541765246964f64a53052513da4e6dc20fdaf69ef0d95b4ca51c87ad3478986cf053c2dd0b853"); -} - -TEST(PublicKeyTests, VerifySchnorrWrongType) { - const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - const auto privateKey = PrivateKey(key); - - const Data messageData = TW::data("hello schnorr"); - const Data digest = Hash::sha256(messageData); - - const auto signature = privateKey.signSchnorr(digest, TWCurveSECP256k1); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); - EXPECT_FALSE(publicKey.verifySchnorr(signature, digest)); -} - -TEST(PublicKeyTests, Recover) { - { - const auto message = parse_hex("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb11432"); - const auto signature = parse_hex("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef80"); - const auto publicKey = PublicKey::recover(signature, message); - EXPECT_EQ(publicKey.type, TWPublicKeyTypeSECP256k1Extended); - EXPECT_EQ(hex(publicKey.bytes), - "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed8090329274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0"); - } - - const auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); - EXPECT_EQ(hex(publicKey.bytes), "0463ade8ebc212b85e7e4278dc3dcb4f9cc18aab912ef5d302b5d1940e772e9e1a9213522efddad487bbd5dd7907e8e776f918e9a5e4cb51893724e9fe76792a4f"); - { - const auto message = parse_hex("6468eb103d51c9a683b51818fdb73390151c9973831d2cfb4e9587ad54273155"); - const auto signature = parse_hex("92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c646487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e5800"); - const auto recovered = PublicKey::recover(signature, message); - EXPECT_EQ(hex(recovered.bytes), hex(publicKey.bytes)); - } - { // same with v=27 - const auto message = parse_hex("6468eb103d51c9a683b51818fdb73390151c9973831d2cfb4e9587ad54273155"); - const auto signature = parse_hex("92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c646487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e581b"); - const auto recovered = PublicKey::recover(signature, message); - EXPECT_EQ(hex(recovered.bytes), hex(publicKey.bytes)); - } - { // same with v=35+2 - const auto message = parse_hex("6468eb103d51c9a683b51818fdb73390151c9973831d2cfb4e9587ad54273155"); - const auto signature = parse_hex("92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c646487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e5825"); - const auto recovered = PublicKey::recover(signature, message); - EXPECT_EQ(hex(recovered.bytes), hex(publicKey.bytes)); - } -} - -TEST(PublicKeyTests, isValidED25519) { - EXPECT_TRUE(PublicKey::isValid(parse_hex("beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519)); - EXPECT_TRUE(PublicKey(parse_hex("beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519).isValidED25519()); - EXPECT_TRUE(PublicKey::isValid(parse_hex("fc8c425a8a94a55ce42f2c24b2fb2ef5ab4a69142d2d97f6c11e0106c84136d5"), TWPublicKeyTypeED25519)); - EXPECT_TRUE(PublicKey(parse_hex("fc8c425a8a94a55ce42f2c24b2fb2ef5ab4a69142d2d97f6c11e0106c84136d5"), TWPublicKeyTypeED25519).isValidED25519()); - EXPECT_TRUE(PublicKey::isValid(parse_hex("01beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519)); - EXPECT_TRUE(PublicKey(parse_hex("01beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519).isValidED25519()); - // Following 32 bytes are not valid public keys (not on the curve) - EXPECT_TRUE(PublicKey::isValid(parse_hex("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"), TWPublicKeyTypeED25519)); - EXPECT_FALSE(PublicKey(parse_hex("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"), TWPublicKeyTypeED25519).isValidED25519()); - EXPECT_TRUE(PublicKey::isValid(parse_hex("51fdd5feae59d7dcbf5ebea99c05593ebee302577a5486ceac706ed568aa1e0e"), TWPublicKeyTypeED25519)); - EXPECT_FALSE(PublicKey(parse_hex("51fdd5feae59d7dcbf5ebea99c05593ebee302577a5486ceac706ed568aa1e0e"), TWPublicKeyTypeED25519).isValidED25519()); - // invalid input size/format - EXPECT_FALSE(PublicKey::isValid(parse_hex("1234"), TWPublicKeyTypeED25519)); - EXPECT_FALSE(PublicKey::isValid(parse_hex("beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa5279"), TWPublicKeyTypeED25519)); - EXPECT_FALSE(PublicKey::isValid(parse_hex("02beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519)); - EXPECT_FALSE(PublicKey::isValid(parse_hex("0101beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519)); - EXPECT_FALSE(PublicKey(parse_hex("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"), TWPublicKeyTypeSECP256k1).isValidED25519()); -} diff --git a/tests/Qtum/TWCoinTypeTests.cpp b/tests/Qtum/TWCoinTypeTests.cpp deleted file mode 100644 index bef8e814b59..00000000000 --- a/tests/Qtum/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWQtumCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeQtum)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeQtum, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeQtum, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeQtum)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeQtum)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeQtum), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeQtum)); - ASSERT_EQ(0x32, TWCoinTypeP2shPrefix(TWCoinTypeQtum)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeQtum)); - assertStringsEqual(symbol, "QTUM"); - assertStringsEqual(txUrl, "https://qtum.info/tx/t123"); - assertStringsEqual(accUrl, "https://qtum.info/address/a12"); - assertStringsEqual(id, "qtum"); - assertStringsEqual(name, "Qtum"); -} diff --git a/tests/Ravencoin/TWCoinTypeTests.cpp b/tests/Ravencoin/TWCoinTypeTests.cpp deleted file mode 100644 index f639dfe6bba..00000000000 --- a/tests/Ravencoin/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWRavencoinCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeRavencoin)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeRavencoin, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeRavencoin, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeRavencoin)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeRavencoin)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeRavencoin), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeRavencoin)); - ASSERT_EQ(0x7a, TWCoinTypeP2shPrefix(TWCoinTypeRavencoin)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeRavencoin)); - assertStringsEqual(symbol, "RVN"); - assertStringsEqual(txUrl, "https://ravencoin.network/tx/t123"); - assertStringsEqual(accUrl, "https://ravencoin.network/address/a12"); - assertStringsEqual(id, "ravencoin"); - assertStringsEqual(name, "Ravencoin"); -} diff --git a/tests/Ripple/AddressTests.cpp b/tests/Ripple/AddressTests.cpp deleted file mode 100644 index f73660b0090..00000000000 --- a/tests/Ripple/AddressTests.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Ripple/Address.h" -#include "Ripple/XAddress.h" -#include "HexCoding.h" -#include "PrivateKey.h" - -#include - -using namespace std; -using namespace TW; -using namespace TW::Ripple; - -TEST(RippleAddress, FromPublicKey) { - const auto publicKey = PublicKey(parse_hex("0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D"), TWPublicKeyTypeSECP256k1); - const auto address = Address(publicKey); - ASSERT_EQ(string("rnBFvgZphmN39GWzUJeUitaP22Fr9be75H"), address.string()); -} - -TEST(RippleAddress, FromString) { - string classic = "rnBFvgZphmN39GWzUJeUitaP22Fr9be75H"; - const auto address = Address(classic); - - ASSERT_EQ(address.string(), classic); -} - -TEST(RippleXAddress, FromPublicKey) { - const auto publicKey = PublicKey(parse_hex("0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D"), TWPublicKeyTypeSECP256k1); - const auto address = XAddress(publicKey, 12345); - ASSERT_EQ(string("X76UnYEMbQfEs3mUqgtjp4zFy9exgThRj7XVZ6UxsdrBptF"), address.string()); -} - -TEST(RippleXAddress, FromString) { - string xAddress = "X76UnYEMbQfEs3mUqgtjp4zFy9exgThRj7XVZ6UxsdrBptF"; - string xAddress2 = "X76UnYEMbQfEs3mUqgtjp4zFy9exgTsM93nriVZAPufrpE3"; - const auto address = XAddress(xAddress); - const auto address2 = XAddress(xAddress2); - - ASSERT_EQ(address.tag, 12345); - ASSERT_EQ(address.string(), xAddress); - - ASSERT_EQ(address2.tag, 0); - ASSERT_EQ(address2.string(), xAddress2); -} - -TEST(RippleAddress, isValid) { - string classicAddress = "r36yxStAh7qgTQNHTzjZvXybCTzUFhrfav"; - string bitcoinAddress = "1Ma2DrB78K7jmAwaomqZNRMCvgQrNjE2QC"; - string xAddress = "XVfvixWZQKkcenFRYApCjpTUyJ4BePTe3jJv7beatUZvQYh"; - - ASSERT_TRUE(Address::isValid(classicAddress)); - ASSERT_TRUE(XAddress::isValid(xAddress)); - ASSERT_FALSE(Address::isValid(bitcoinAddress)); - ASSERT_FALSE(XAddress::isValid(bitcoinAddress)); -} diff --git a/tests/Ripple/TWAnySignerTests.cpp b/tests/Ripple/TWAnySignerTests.cpp deleted file mode 100644 index d7bb05fce48..00000000000 --- a/tests/Ripple/TWAnySignerTests.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "proto/Common.pb.h" -#include "proto/Ripple.pb.h" -#include - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; -using namespace TW::Ripple; - -TEST(TWAnySignerRipple, Sign) { - auto key = parse_hex("ba005cd605d8a02e3d5dfd04234cef3a3ee4f76bfbad2722d1fb5af8e12e6764"); - Proto::SigningInput input; - - input.set_amount(29000000); - input.set_fee(200000); - input.set_sequence(1); - input.set_account("rDpysuumkweqeC7XdNgYNtzL5GxbdsmrtF"); - input.set_destination("rU893viamSnsfP3zjzM2KPxjqZjXSXK6VF"); - input.set_private_key(key.data(), key.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeXRP); - - EXPECT_EQ(hex(output.encoded()), "12000022800000002400000001614000000001ba8140684000000000030d407321026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6744630440220067f20b3eebfc7107dd0bcc72337a236ac3be042c0469f2341d76694a17d4bb9022048393d7ee7dcb729783b33f5038939ddce1bb8337e66d752974626854556bbb681148400b6b6d08d5d495653d73eda6804c249a5148883148132e4e20aecf29090ac428a9c43f230a829220d"); - - // invalid tag - input.set_destination_tag(641505641505); - - ANY_SIGN(input, TWCoinTypeXRP); - - EXPECT_EQ(output.error(), Common::Proto::SigningError::Error_invalid_memo); - EXPECT_EQ(output.encoded(), ""); -} diff --git a/tests/Ripple/TWCoinTypeTests.cpp b/tests/Ripple/TWCoinTypeTests.cpp deleted file mode 100644 index 5f4249ca2e1..00000000000 --- a/tests/Ripple/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWXRPCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeXRP)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("E26AB8F3372D2FC02DEC1FD5674ADAB762D684BFFDBBDF5D674E9D7CF4A47054")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeXRP, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("rfkH7EuS1XcSkB9pocy1R6T8F4CsNYixYU")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeXRP, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeXRP)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeXRP)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeXRP), 6); - ASSERT_EQ(TWBlockchainRipple, TWCoinTypeBlockchain(TWCoinTypeXRP)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeXRP)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeXRP)); - assertStringsEqual(symbol, "XRP"); - assertStringsEqual(txUrl, "https://bithomp.com/explorer/E26AB8F3372D2FC02DEC1FD5674ADAB762D684BFFDBBDF5D674E9D7CF4A47054"); - assertStringsEqual(accUrl, "https://bithomp.com/explorer/rfkH7EuS1XcSkB9pocy1R6T8F4CsNYixYU"); - assertStringsEqual(id, "ripple"); - assertStringsEqual(name, "XRP"); -} diff --git a/tests/Ripple/TWRippleAddressTests.cpp b/tests/Ripple/TWRippleAddressTests.cpp deleted file mode 100644 index 7fd89c52c43..00000000000 --- a/tests/Ripple/TWRippleAddressTests.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" - -#include - -#include - -TEST(TWRipple, ExtendedKeys) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( - STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), - STRING("TREZOR").get() - )); - - auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeXRP, TWHDVersionXPUB)); - auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeXRP, TWHDVersionXPRV)); - - assertStringsEqual(xpub, "xpub6D9oDY4gqFBtsFEonh5GTDiUm6nmij373YWzmYdshcnM4AFzdhUf55iZD33vNU2ZqfQJU5wiCJUgisMt2RHKDzhi1PbZfh5Y2NiiYJAQqUn"); - assertStringsEqual(xprv, "xprv9zASp2XnzsdbemALgfYG65mkD4xHKGKFgKbPyAEG9HFNBMvr6AAQXHQ5MmqM66EnbJfe9TvYMy1bucz7hSQjG43NVizRZwJJYfLmeKo4nVB"); -} diff --git a/tests/Ripple/TransactionTests.cpp b/tests/Ripple/TransactionTests.cpp deleted file mode 100644 index 4162ebbfe2a..00000000000 --- a/tests/Ripple/TransactionTests.cpp +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Ripple/Address.h" -#include "Ripple/Transaction.h" -#include "Ripple/BinaryCoding.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" - -#include - -using namespace std; -using namespace TW; -using namespace TW::Ripple; - -TEST(RippleTransaction, serializeAmount) { - /// From https://github.com/trezor/trezor-core/blob/master/tests/test_apps.ripple.serializer.py - auto data0 = Transaction::serializeAmount(0); - auto data1 = Transaction::serializeAmount(1); - auto data2 = Transaction::serializeAmount(93493429243); - auto data3 = Transaction::serializeAmount(25000000); - auto data4 = Transaction::serializeAmount(100000000000); - /// more than max supply - auto data5 = Transaction::serializeAmount(200000000000000000); - /// negative value - auto data6 = Transaction::serializeAmount(-1); - - ASSERT_EQ(hex(data0), "4000000000000000"); - ASSERT_EQ(hex(data1), "4000000000000001"); - ASSERT_EQ(hex(data2), "40000015c4a483fb"); - ASSERT_EQ(hex(data3), "40000000017d7840"); - ASSERT_EQ(hex(data4), "400000174876e800"); - ASSERT_EQ(hex(data5), "42c68af0bb140000"); - ASSERT_EQ(hex(data6), ""); -} - -TEST(RippleTransaction, serialize) { - /// From https://github.com/trezor/trezor-core/blob/master/tests/test_apps.ripple.serializer.py - auto account = Address("r9TeThyi5xiuUUrFjtPKZiHcDxs7K9H6Rb"); - auto destination = "r4BPgS7DHebQiU31xWELvZawwSG2fSPJ7C"; - auto tx1 = Transaction( - /* amount */25000000, - /* fee */10, - /* flags */0, - /* sequence */2, - /* last_ledger_sequence */0, - /* account */account, - /* destination */destination, - /* destination_tag*/0 - ); - auto serialized1 = tx1.serialize(); - ASSERT_EQ(hex(serialized1), "120000220000000024000000026140000000017d784068400000000000000a81145ccb151f6e9d603f394ae778acf10d3bece874f68314e851bbbe79e328e43d68f43445368133df5fba5a"); - - auto tx2 = Transaction( - /* amount */200000, - /* fee */15, - /* flags */0, - /* sequence */144, - /* last_ledger_sequence */0, - /* account */Address("rGWTUVmm1fB5QUjMYn8KfnyrFNgDiD9H9e"), - /* destination */"rw71Qs1UYQrSQ9hSgRohqNNQcyjCCfffkQ", - /* destination_tag*/0 - ); - auto serialized2 = tx2.serialize(); - ASSERT_EQ(hex(serialized2), "12000022000000002400000090614000000000030d4068400000000000000f8114aa1bd19d9e87be8069fdbf6843653c43837c03c6831467fe6ec28e0464dd24fb2d62a492aac697cfad02"); - - auto tx3 = Transaction( - /* amount */25000000, - /* fee */12, - /* flags */0, - /* sequence */1, - /* last_ledger_sequence */0, - /* account */Address("r4BPgS7DHebQiU31xWELvZawwSG2fSPJ7C"), - /* destination */"rBqSFEFg2B6GBMobtxnU1eLA1zbNC9NDGM", - /* destination_tag*/4146942154 - ); - auto serialized3 = tx3.serialize(); - ASSERT_EQ(hex(serialized3), "120000220000000024000000012ef72d50ca6140000000017d784068400000000000000c8114e851bbbe79e328e43d68f43445368133df5fba5a831476dac5e814cd4aa74142c3ab45e69a900e637aa2"); - - auto tx4 = Transaction( - /* amount */25000000, - /* fee */12, - /* flags */0, - /* sequence */1, - /* last_ledger_sequence */0, - /* account */Address("r4BPgS7DHebQiU31xWELvZawwSG2fSPJ7C"), - /* destination */"XVhidoXkozM5DTZFdDnJ5nYC8FPrTuJiyGh1VxSGS6RNJJ5", - /* ignore destination_tag*/12345 - ); - auto serialized4 = tx4.serialize(); - ASSERT_EQ(hex(serialized4), hex(serialized3)); -} - -TEST(RippleTransaction, preImage) { - auto account = Address("r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ"); - auto destination = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"; - auto tx1 = Transaction( - /* amount */1000, - /* fee */10, - /* flags */2147483648, - /* sequence */1, - /* last_ledger_sequence */0, - /* account */account, - /* destination */destination, - /* destination_tag*/0 - ); - tx1.pub_key = parse_hex("ed5f5ac8b98974a3ca843326d9b88cebd0560177b973ee0b149f782cfaa06dc66a"); - auto unsignedTx = tx1.getPreImage(); - - ASSERT_EQ(hex(unsignedTx), - /* prefix */ "53545800" - /* tx type */ "120000" - /* flags */ "2280000000" - /* sequence */ "2400000001" - /* amount */ "6140000000000003e8" - /* fee */ "68400000000000000a" - /* pub key */ "7321ed5f5ac8b98974a3ca843326d9b88cebd0560177b973ee0b149f782cfaa06dc66a" - /* account */ "81145b812c9d57731e27a2da8b1830195f88ef32a3b6" - /* destination */ "8314b5f762798a53d543a014caf8b297cff8f2f937e8" - ); - ASSERT_EQ(unsignedTx.size(), 114); -} diff --git a/tests/Ronin/TWAnyAddressTests.cpp b/tests/Ronin/TWAnyAddressTests.cpp deleted file mode 100644 index 20c88c5eeac..00000000000 --- a/tests/Ronin/TWAnyAddressTests.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" - -#include -#include - -#include "Ronin/Address.h" -#include "Ronin/Entry.h" - -#include - -const auto roninPrefixChecksummed = "ronin:EC49280228b0D05Aa8e8b756503254e1eE7835ab"; - -const auto tests = { - roninPrefixChecksummed, - "ronin:ec49280228b0d05aa8e8b756503254e1ee7835ab", - "0xEC49280228b0D05Aa8e8b756503254e1eE7835ab", - "ronin:0xEC49280228b0D05Aa8e8b756503254e1eE7835ab", -}; - -TEST(RoninAnyAddress, Validate) { - for (const auto& t: tests) { - EXPECT_TRUE(TWAnyAddressIsValid(STRING(t).get(), TWCoinTypeRonin)); - } -} - -TEST(RoninAnyAddress, Normalize) { - for (const auto& t: tests) { - auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING(t).get(), TWCoinTypeRonin)); - auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); - EXPECT_TRUE(TWStringEqual(string2.get(), STRING(roninPrefixChecksummed).get())); - - auto keyHash = WRAPD(TWAnyAddressData(addr.get())); - assertHexEqual(keyHash, "ec49280228b0d05aa8e8b756503254e1ee7835ab"); - } -} - -TEST(RoninAnyAddress, Invalid) { - const auto tests = { - "EC49280228b0D05Aa8e8b756503254e1eE7835ab", // no prefix - "ec49280228b0d05aa8e8b756503254e1ee7835ab", // no prefix - "roni:EC49280228b0D05Aa8e8b756503254e1eE7835ab", // wrong prefix - "ronin=EC49280228b0D05Aa8e8b756503254e1eE7835ab", // wrong prefix - "0xronin:EC49280228b0D05Aa8e8b756503254e1eE7835ab", // wrong prefix - "EC49280228b0D05Aa8e8b756503254e1eE7835", // too short - "ronin:EC49280228b0D05Aa8e8b756503254e1eE7835", // too short - "ronin:ec49280228b0d05aa8e8b756503254e1ee7835", // too short - }; - - for (const auto& t: tests) { - EXPECT_FALSE(TWAnyAddressIsValid(STRING(t).get(), TWCoinTypeRonin)); - } -} diff --git a/tests/Ronin/TWAnySignerTests.cpp b/tests/Ronin/TWAnySignerTests.cpp deleted file mode 100644 index 49e6b5ddbc0..00000000000 --- a/tests/Ronin/TWAnySignerTests.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" -#include -#include "HexCoding.h" -#include "uint256.h" -#include "proto/Ethereum.pb.h" - -#include - -using namespace TW; -using namespace TW::Ethereum; - -TEST(TWAnySignerRonin, Sign) { - // https://explorer.roninchain.com/tx/0xf13a2c4421700f8782ca73eaf16bb8baf82bcf093e23570a1ff062cdd8dbf6c3 - Proto::SigningInput input; - auto chainId = store(uint256_t(2020)); - auto nonce = store(uint256_t(0)); - auto gasPrice = store(uint256_t(1000000000)); - auto gasLimit = store(uint256_t(21000)); - auto key = parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); - - input.set_chain_id(chainId.data(), chainId.size()); - input.set_nonce(nonce.data(), nonce.size()); - input.set_gas_price(gasPrice.data(), gasPrice.size()); - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_private_key(key.data(), key.size()); - input.set_to_address("ronin:c36edf48e21cf395b206352a1819de658fd7f988"); - - auto& transfer = *input.mutable_transaction()->mutable_transfer(); - auto amount = store(uint256_t(276447)); - transfer.set_amount(amount.data(), amount.size()); - - std::string expected = "f86880843b9aca0082520894c36edf48e21cf395b206352a1819de658fd7f988830437df80820feca0442aa06b0d0465bfecf84b28e2ce614a32a1ccc12735dc03a5799517d6659d7aa004e1bf2efa30743f1b6d49dbec2671e9fb5ead1e7da15e352ca1df6fb86a8ba7"; - - // sign test - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeRonin); - - ASSERT_EQ(hex(output.encoded()), expected); -} diff --git a/tests/Ronin/TWCoinTypeTests.cpp b/tests/Ronin/TWCoinTypeTests.cpp deleted file mode 100644 index 5d29a67771e..00000000000 --- a/tests/Ronin/TWCoinTypeTests.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWRoninCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeRonin)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xc09835aaf9c1cacea8ce322865583c791d3a4dfbd9a3b72f79539db88d3697ab")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeRonin, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xdc05ecd5fbdb64058d94f3182d66f44342b9adcb")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeRonin, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeRonin)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeRonin)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeRonin), 18); - ASSERT_EQ(TWBlockchainRonin, TWCoinTypeBlockchain(TWCoinTypeRonin)); - - assertStringsEqual(symbol, "RON"); - assertStringsEqual(txUrl, "https://explorer.roninchain.com/tx/0xc09835aaf9c1cacea8ce322865583c791d3a4dfbd9a3b72f79539db88d3697ab"); - assertStringsEqual(accUrl, "https://explorer.roninchain.com/address/0xdc05ecd5fbdb64058d94f3182d66f44342b9adcb"); - assertStringsEqual(id, "ronin"); - assertStringsEqual(name, "Ronin"); -} diff --git a/tests/SmartBitcoinCash/TWCoinTypeTests.cpp b/tests/SmartBitcoinCash/TWCoinTypeTests.cpp deleted file mode 100644 index 04b97444049..00000000000 --- a/tests/SmartBitcoinCash/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWSmartBitcoinCashCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeSmartBitcoinCash)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x6413466b455b17d03c7a8ce2d7f99fec34bcd338628bdd2d0580a21e3197a4d9")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeSmartBitcoinCash, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xFeEc227410E1DF9f3b4e6e2E284DC83051ae468F")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeSmartBitcoinCash, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeSmartBitcoinCash)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeSmartBitcoinCash)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeSmartBitcoinCash), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeSmartBitcoinCash)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeSmartBitcoinCash)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeSmartBitcoinCash)); - assertStringsEqual(symbol, "BCH"); - assertStringsEqual(txUrl, "https://www.smartscan.cash/tx/0x6413466b455b17d03c7a8ce2d7f99fec34bcd338628bdd2d0580a21e3197a4d9"); - assertStringsEqual(accUrl, "https://www.smartscan.cash/address/0xFeEc227410E1DF9f3b4e6e2E284DC83051ae468F"); - assertStringsEqual(id, "smartbch"); - assertStringsEqual(name, "Smart Bitcoin Cash"); -} diff --git a/tests/Solana/AddressTests.cpp b/tests/Solana/AddressTests.cpp deleted file mode 100644 index 5af714c46c6..00000000000 --- a/tests/Solana/AddressTests.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Solana/Address.h" -#include "Solana/Program.h" -#include "Base58.h" -#include "PrivateKey.h" -#include "HexCoding.h" - -#include - -using namespace std; -using namespace TW; -using namespace TW::Solana; - -TEST(SolanaAddress, FromPublicKey) { - const auto addressString = "2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"; - const auto publicKey = PublicKey(Base58::bitcoin.decode(addressString), TWPublicKeyTypeED25519); - const auto address = Address(publicKey); - ASSERT_EQ(addressString, address.string()); -} - -TEST(SolanaAddress, FromString) { - string addressString = "2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"; - const auto address = Address(addressString); - ASSERT_EQ(address.string(), addressString); -} - -TEST(SolanaAddress, isValid) { - ASSERT_TRUE(Address::isValid("2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST")); - ASSERT_FALSE(Address::isValid( - "2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdSl")); // Contains invalid base-58 character - ASSERT_FALSE( - Address::isValid("2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpd")); // Is invalid length -} - -TEST(SolanaAddress, isValidOnCurve) { - EXPECT_TRUE(PublicKey(Base58::bitcoin.decode("HzqnaMjWFbK2io6WgV2Z5uBguCBU21RMUS16wsDUHkon"), TWPublicKeyTypeED25519).isValidED25519()); - EXPECT_TRUE(PublicKey(Base58::bitcoin.decode("68io7dTfyeWua1wD1YcCMka4y5iiChceaFRCBjqCM5PK"), TWPublicKeyTypeED25519).isValidED25519()); - EXPECT_TRUE(PublicKey(Base58::bitcoin.decode("Dra34QLFCjxnk8tUNcBwxs6pgb5spF4oseQYF2xn7ABZ"), TWPublicKeyTypeED25519).isValidED25519()); - // negative case - EXPECT_FALSE(PublicKey(Base58::bitcoin.decode("6X4X1Ae24mkoWeCEpktevySVG9jzeCufut5vtUW3wFrD"), TWPublicKeyTypeED25519).isValidED25519()); - EXPECT_FALSE(PublicKey(Base58::bitcoin.decode("EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"), TWPublicKeyTypeED25519).isValidED25519()); - EXPECT_FALSE(PublicKey(Base58::bitcoin.decode("ANVCrmRw7Ww7rTFfMbrjApSPXEEcZpBa6YEiBdf98pAf"), TWPublicKeyTypeED25519).isValidED25519()); - EXPECT_FALSE(PublicKey(Base58::bitcoin.decode("AbygL37RheNZv327cMvZPqKYLLkZ6wqWYexRxgNiZyeP"), TWPublicKeyTypeED25519).isValidED25519()); -} - -TEST(SolanaAddress, defaultTokenAddress) { - const Address serumToken = Address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); - EXPECT_EQ(Address("HBYC51YrGFAZ8rM7Sj8e9uqKggpSrDYrinQDZzvMtqQp").defaultTokenAddress(serumToken).string(), - "6X4X1Ae24mkoWeCEpktevySVG9jzeCufut5vtUW3wFrD"); - EXPECT_EQ(Address("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V").defaultTokenAddress(serumToken).string(), - "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); - EXPECT_EQ(Address("Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ").defaultTokenAddress(serumToken).string(), - "ANVCrmRw7Ww7rTFfMbrjApSPXEEcZpBa6YEiBdf98pAf"); -} \ No newline at end of file diff --git a/tests/Solana/ProgramTests.cpp b/tests/Solana/ProgramTests.cpp deleted file mode 100644 index 0a5c413dafd..00000000000 --- a/tests/Solana/ProgramTests.cpp +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Solana/Address.h" -#include "Solana/Program.h" -#include "Base58.h" -#include "PrivateKey.h" - -#include - -using namespace std; -using namespace TW; -using namespace TW::Solana; - -TEST(SolanaStakeProgram, addressFromValidatorSeed) { - auto user = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); - auto validator = Address("4jpwTqt1qZoR7u6u639z2AngYFGN3nakvKhowcnRZDEC"); - auto programId = Address("Stake11111111111111111111111111111111111111"); - auto expected = Address("6u9vJH9pRj66N5oJFCBADEbpMTrLxQATcL6q5p5MXwYv"); - EXPECT_EQ(StakeProgram::addressFromValidatorSeed(user, validator, programId), expected); -} - -TEST(SolanaStakeProgram, addressFromRecentBlockhash) { - { - auto user = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); - Solana::Hash recentBlockhash("11111111111111111111111111111111"); - auto programId = Address("Stake11111111111111111111111111111111111111"); - auto expected = Address("GQDDc5EVGJZFC7AvpEJ8eoCQ75Yy4gr7eu17frCjvQRQ"); - EXPECT_EQ(StakeProgram::addressFromRecentBlockhash(user, recentBlockhash, programId), expected); - } - { - auto user = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); - Solana::Hash recentBlockhash("9ipJh5xfyoyDaiq8trtrdqQeAhQbQkWy2eANizKvx75K"); - auto programId = Address("Stake11111111111111111111111111111111111111"); - auto expected = Address("2Kos1xJRBq3Ae1GnVNBx7HgJhq8KvdUe2bXE4QGdNaXb"); - EXPECT_EQ(StakeProgram::addressFromRecentBlockhash(user, recentBlockhash, programId), expected); - } -} - -TEST(SolanaTokenProgram, defaultTokenAddress) { - const Address serumToken = Address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); - EXPECT_EQ(TokenProgram::defaultTokenAddress(Address("HBYC51YrGFAZ8rM7Sj8e9uqKggpSrDYrinQDZzvMtqQp"), serumToken).string(), - "6X4X1Ae24mkoWeCEpktevySVG9jzeCufut5vtUW3wFrD"); -} - -TEST(SolanaTokenProgram, findProgramAddress) { - std::vector seeds = { - Base58::bitcoin.decode("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"), - Base58::bitcoin.decode("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), - Base58::bitcoin.decode("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"), - }; - Address address = TokenProgram::findProgramAddress(seeds, Address("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")); - EXPECT_EQ(address.string(), "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); -} - -TEST(SolanaTokenProgram, createProgramAddress) { - { - std::vector seeds = { - Base58::bitcoin.decode("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"), - Base58::bitcoin.decode("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), - Base58::bitcoin.decode("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"), - Data{255} - }; - Address address = TokenProgram::createProgramAddress(seeds, Address("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")); - EXPECT_EQ(address.string(), "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); - } - // https://github.com/solana-labs/solana/blob/f25c969ad87e64e6d1fd07d2d37096ac71cf8d06/sdk/program/src/pubkey.rs#L353-L435 - { - std::vector seeds = {TW::data(""), {1}}; - Address address = TokenProgram::createProgramAddress(seeds, Address("BPFLoader1111111111111111111111111111111111")); - EXPECT_EQ(address.string(), "3gF2KMe9KiC6FNVBmfg9i267aMPvK37FewCip4eGBFcT"); - } - { - std::vector seeds = {TW::data("Talking"), TW::data("Squirrels")}; - Address address = TokenProgram::createProgramAddress(seeds, Address("BPFLoader1111111111111111111111111111111111")); - EXPECT_EQ(address.string(), "HwRVBufQ4haG5XSgpspwKtNd3PC9GM9m1196uJW36vds"); - } - { - std::vector seeds = {Base58::bitcoin.decode("SeedPubey1111111111111111111111111111111111")}; - Address address = TokenProgram::createProgramAddress(seeds, Address("BPFLoader1111111111111111111111111111111111")); - EXPECT_EQ(address.string(), "GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K"); - } -} diff --git a/tests/Solana/SignerTests.cpp b/tests/Solana/SignerTests.cpp deleted file mode 100644 index 12ed74128a5..00000000000 --- a/tests/Solana/SignerTests.cpp +++ /dev/null @@ -1,413 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Solana/Signer.h" -#include "Solana/Transaction.h" -#include "Solana/Program.h" -#include "HexCoding.h" -#include "PublicKey.h" - -#include - -using namespace TW; -using namespace TW::Solana; - -TEST(SolanaSigner, CompiledInstruction) { - const auto privateKey0 = - PrivateKey(Base58::bitcoin.decode("96PKHuMPtniu1T74RvUNkbDPXPPRZ8Mg1zXwciCAyaDq")); - const auto publicKey0 = privateKey0.getPublicKey(TWPublicKeyTypeED25519); - const auto address0 = Address(publicKey0); - ASSERT_EQ(Data(publicKey0.bytes.begin(), publicKey0.bytes.end()), - Base58::bitcoin.decode("GymAh18wHuFTytfSJWi8eYTA9x5S3sNb9CJSGBWoPRE3")); - const auto privateKey1 = - PrivateKey(Base58::bitcoin.decode("GvGmNPMQLZE2VNx3KG2GdiC4ndS8uCqd7PjioPgm9Qhi")); - const auto publicKey1 = privateKey1.getPublicKey(TWPublicKeyTypeED25519); - const auto address1 = Address(publicKey1); - ASSERT_EQ(Data(publicKey1.bytes.begin(), publicKey1.bytes.end()), - Base58::bitcoin.decode("2oKoYSAHgveX91917v4DUEuN8BNKXDg8KJWpaGyEay9V")); - Address programId("11111111111111111111111111111111"); - - std::vector
addresses = {address0, address1, programId}; - - std::vector instrAddresses = { - AccountMeta(address1, false, false), - AccountMeta(address0, false, false), - AccountMeta(programId, false, false), - AccountMeta(address1, false, false), - AccountMeta(address0, false, false), - }; - Data data = {0, 1, 2, 4}; - Instruction instruction(programId, instrAddresses, data); - - auto compiledInstruction = CompiledInstruction(instruction, addresses); - - EXPECT_EQ(compiledInstruction.programIdIndex, 2); - ASSERT_EQ(compiledInstruction.accounts.size(), 5); - EXPECT_EQ(compiledInstruction.accounts[0], 1); - EXPECT_EQ(compiledInstruction.accounts[1], 0); - EXPECT_EQ(compiledInstruction.accounts[2], 2); - EXPECT_EQ(compiledInstruction.accounts[3], 1); - EXPECT_EQ(compiledInstruction.accounts[4], 0); - ASSERT_EQ(compiledInstruction.data.size(), 4); -} - -TEST(SolanaSigner, CompiledInstructionFindAccount) { - Address address1 = Address(parse_hex("0102030405060708090a0102030405060708090a0102030405060708090a0101")); - Address address2 = Address(parse_hex("0102030405060708090a0102030405060708090a0102030405060708090a0102")); - Address address3 = Address(parse_hex("0102030405060708090a0102030405060708090a0102030405060708090a0103")); - Address programId("11111111111111111111111111111111"); - Instruction instruction(programId, std::vector{ - AccountMeta(address1, true, false), - AccountMeta(address2, false, false), - }, Data{1, 2, 3, 4}); - std::vector
addresses = { - address1, - address2, - programId, - }; - CompiledInstruction compiledInstruction = CompiledInstruction(instruction, addresses); - ASSERT_EQ(compiledInstruction.findAccount(address1), 0); - ASSERT_EQ(compiledInstruction.findAccount(address2), 1); - ASSERT_EQ(compiledInstruction.findAccount(programId), 2); - // negative case - try { - compiledInstruction.findAccount(address3); - FAIL() << "Missing expected exception"; - } catch (...) { - // ok - } -} - -TEST(SolanaSigner, SingleSignTransaction) { - const auto privateKey = - PrivateKey(Base58::bitcoin.decode("A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr")); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - ASSERT_EQ(Data(publicKey.bytes.begin(), publicKey.bytes.end()), - Base58::bitcoin.decode("7v91N7iZ9mNicL8WfG6cgSCKyRXydQjLh6UYBWwm6y1Q")); - - const auto from = Address(publicKey); - auto to = Address("EN2sCsJ1WDV8UFqsiTXHcUPUxQ4juE71eCknHYYMifkd"); - Solana::Hash recentBlockhash("11111111111111111111111111111111"); - auto transaction = Transaction(from, to, 42, recentBlockhash); - - std::vector signerKeys; - signerKeys.push_back(privateKey); - Signer::sign(signerKeys, transaction); - - std::vector expectedSignatures; - Signature expectedSignature( - "5T6uZBHnHFd8uWErDBTFRVkbKuhbcm94K5MJ2beTYDruzqv4FjS7EMKvC94ZfxNAiWUXZ6bZxS3WXUbhJwYNPWn"); - expectedSignatures.push_back(expectedSignature); - ASSERT_EQ(transaction.signatures, expectedSignatures); - - auto expectedString = - "3p2kzZ1DvquqC6LApPuxpTg5CCDVPqJFokGSnGhnBHrta4uq7S2EyehV1XNUVXp51D69GxGzQZU" - "jikfDzbWBG2aFtG3gHT1QfLzyFKHM4HQtMQMNXqay1NAeiiYZjNhx9UvMX4uAQZ4Q6rx6m2AYfQ" - "7aoMUrejq298q1wBFdtS9XVB5QTiStnzC7zs97FUEK2T4XapjF1519EyFBViTfHpGpnf5bfizDz" - "sW9kYUtRDW1UC2LgHr7npgq5W9TBmHf9hSmRgM9XXucjXLqubNWE7HUMhbKjuBqkirRM"; - ASSERT_EQ(transaction.serialize(), expectedString); - - const auto additionalPrivateKey = - PrivateKey(Base58::bitcoin.decode("96PKHuMPtniu1T74RvUNkbDPXPPRZ8Mg1zXwciCAyaDq")); - signerKeys.push_back(additionalPrivateKey); - try { - Signer::sign(signerKeys, transaction); - FAIL() << "publicKey not found in message.accountKeys"; - } catch (std::invalid_argument const& err) { - EXPECT_EQ(err.what(), std::string("publicKey not found in message.accountKeys")); - } -} - -TEST(SolanaSigner, SignTransactionToSelf) { - const auto privateKey = - PrivateKey(Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746")); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - ASSERT_EQ(Data(publicKey.bytes.begin(), publicKey.bytes.end()), - Base58::bitcoin.decode("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu")); - - const auto from = Address(publicKey); - auto to = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); - Solana::Hash recentBlockhash("11111111111111111111111111111111"); - auto transaction = Transaction(from, to, 42, recentBlockhash); - - std::vector signerKeys; - signerKeys.push_back(privateKey); - Signer::sign(signerKeys, transaction); - - std::vector expectedSignatures; - Signature expectedSignature( - "3CFWDEK51noPJP4v2t8JZ3qj7kC7kLKyws9akfHMyuJnQ35EtzBptHqvaHfeswiLsvUSxzMVNoj4CuRxWtDD9zB1"); - expectedSignatures.push_back(expectedSignature); - ASSERT_EQ(transaction.signatures, expectedSignatures); - - auto expectedString = - "EKUmihvvUPKVN4GSCFwZRtz8WiyAuPvthW69Smo19SCjcPLQ6T7EVZd1HU71WAoe1bfgmPNS5JhU7ZLA9XKG3qbZqe" - "EFJ1xmRwW9ZKw8SKMAL6VRWxp87oLu7PSmf5b8R34vCaww3XLKtZkoP49a7TUK31DqPN5xJCceMB3BZJyaojQaKU8n" - "UkzSGf89LY6abZXp9krKAebvc6bSMzTP8SHSvbmZbf3VtejmpQeN9X6e7WVDn6oDa2bGT"; - ASSERT_EQ(transaction.serialize(), expectedString); -} - -TEST(SolanaSigner, MultipleSignTransaction) { - const auto privateKey0 = - PrivateKey(Base58::bitcoin.decode("96PKHuMPtniu1T74RvUNkbDPXPPRZ8Mg1zXwciCAyaDq")); - const auto publicKey0 = privateKey0.getPublicKey(TWPublicKeyTypeED25519); - const auto address0 = Address(publicKey0); - ASSERT_EQ(Data(publicKey0.bytes.begin(), publicKey0.bytes.end()), - Base58::bitcoin.decode("GymAh18wHuFTytfSJWi8eYTA9x5S3sNb9CJSGBWoPRE3")); - const auto privateKey1 = - PrivateKey(Base58::bitcoin.decode("GvGmNPMQLZE2VNx3KG2GdiC4ndS8uCqd7PjioPgm9Qhi")); - const auto publicKey1 = privateKey1.getPublicKey(TWPublicKeyTypeED25519); - const auto address1 = Address(publicKey1); - ASSERT_EQ(Data(publicKey1.bytes.begin(), publicKey1.bytes.end()), - Base58::bitcoin.decode("2oKoYSAHgveX91917v4DUEuN8BNKXDg8KJWpaGyEay9V")); - - Data data = {0, 0, 0, 0}; - Address programId("11111111111111111111111111111111"); - std::vector instrAddresses = { - AccountMeta(address0, true, false), - AccountMeta(address1, false, false), - }; - Instruction instruction(programId, instrAddresses, data); - std::vector instructions = {instruction}; - - MessageHeader header = {2, 0, 1}; - std::vector
accountKeys = {address0, address1, programId}; - Solana::Hash recentBlockhash("11111111111111111111111111111111"); - Message message; - message.header = header; - message.accountKeys = accountKeys; - message.recentBlockhash = recentBlockhash; - message.instructions = instructions; - message.compileInstructions(); - - auto transaction = Transaction(message); - - std::vector signerKeys; - // Sign order should not matter - signerKeys.push_back(privateKey1); - signerKeys.push_back(privateKey0); - Signer::sign(signerKeys, transaction); - - std::vector expectedSignatures; - Signature expectedSignature0( - "37beWPhNMfWUz75Tb24TX3PCS89FZscbCgwwLpFnzVfZYPqDpAWruvqzc9eeQYft35H23Vm9Tv1dPwEKWT3vAVPb"); - expectedSignatures.push_back(expectedSignature0); - Signature expectedSignature1( - "5NxQshVaAXtQ8YVdcBtCanT62KbxnRfhubjGndFvetgn9AiaoLVZvRGutR5D7FJebRxq8bd6nQXn59LFzavEUrdQ"); - expectedSignatures.push_back(expectedSignature1); - ASSERT_EQ(transaction.signatures, expectedSignatures); - - auto expectedString = - "oL2CmkcP9xf2DiU7eo6hh3JdHnX3NGjunheXYo6SjVchzc8LtFJpPs4jccWUd7oPZUPQNTcR7Ee" - "Hn259ror9A7aXgJdP4djhntoD8irF1kuBZCj7pubtoWfiAKzagSL4hChQsTSe7e9jaGtoXu58mP" - "HCMKTz55TLjhdmCj7ixoWRowWEzkrF49MxXnurb4yf6ASru1XdHPFn3DdzkRHgypYwvRM6ci8p2" - "7trQvXFukhWX6qG6JkxqsWYSzACcAAGGWfAxSi63Yx1RxkxGUzyxy5f2thQhWZ6Nx6pR1im65yV" - "YMYPXj94kgtHxXw9h5V4p7xSAwRpmhw4jewYyQVX4jmnfro3gFNdX9AqpqMs4uGHA4rZM"; - ASSERT_EQ(transaction.serialize(), expectedString); -} - -TEST(SolanaSigner, SignUpdateBlockhash) { - const auto privateKey = - PrivateKey(Base58::bitcoin.decode("G4VSzrknPBWZ1z2YwUnWTxD1td7wmqR5jMPEJRN6wm8S")); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - ASSERT_EQ(Data(publicKey.bytes.begin(), publicKey.bytes.end()), - Base58::bitcoin.decode("41a5jYky56M6EWDsFfLaZRxoRtgAJSRWxJnxaJNJELn5")); - - const auto from = Address(publicKey); - auto to = Address("4iSnyfDKaejniaPc2pBBckwQqV3mDS93go15NdxWJq2y"); - Solana::Hash recentBlockhash("11111111111111111111111111111111"); - auto transaction = Transaction(from, to, 42, recentBlockhash); - - std::vector signerKeys; - signerKeys.push_back(privateKey); - Signer::sign(signerKeys, transaction); - - Solana::Hash newBlockhash("GgBaCs3NCBuZN12kCJgAW63ydqohFkHEdfdEXBPzLHq"); - Signer::signUpdateBlockhash(signerKeys, transaction, newBlockhash); - - std::vector expectedSignatures; - Signature expectedSignature( - "5AFhXjvGdENXCAe9MPvUA2qjoL4XtZwZKG7kK2HmZf1ibpxjx5kzogHZjN39uYB9J33UFJN15KhSggBZhzyNQmta"); - expectedSignatures.push_back(expectedSignature); - ASSERT_EQ(transaction.signatures, expectedSignatures); - - auto expectedString = - "62ABadDCoPfGGRnhLoBhfcPekMHyN5ee8DgTY8wD4iwKDjyFAsNbsaahTcqMWxmwa61q9iAGCQB" - "v1bETcYzWsTwLKMVGLoEpwqA84mPjqHyr5sQD5dcghyQiQ1ckYNub9K7s8FspVwwowK8gJG69xe" - "DEaqi7G1zrChBVbQYTmVUwJETyDmP1Vs8QU3CaxBs8qwcxoziU52KWLBpRj9o38QVBdxJtJ7hig" - "hgPKJubfqUfTWdN94PzqEfyPqwoCpFD39nvBn8C5xe1caPKivicg6U7Lzm9s8RYTLCEB"; - ASSERT_EQ(transaction.serialize(), expectedString); -} - -TEST(SolanaSigner, SignRawMessage) { - const auto privateKey = - PrivateKey(Base58::bitcoin.decode("GjXseuD8JavBjKMdd6GEsPYZPV7tMMa46GS2JRS5tHRq")); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - ASSERT_EQ(Data(publicKey.bytes.begin(), publicKey.bytes.end()), - Base58::bitcoin.decode("3BocAWPm1oNXN5qkAV4QeDUmAPpkTcN1rrmCMWAfsXJY")); - - auto rawMessageData = - "01000203207be13c43c4528592eaf3fd34e064c641c5be3cb6691877d7ade94dff36734108eaea30723c33b525" - "07bc54024910612f885e4c80c10b99a047fd42c0acbace00000000000000000000000000000000000000000000" - "000000000000000000000404040404040404040404040404040404040404040404040404040404040404010202" - "00010c020000002a00000000000000"; - - std::vector signerKeys; - signerKeys.push_back(privateKey); - Data rawTransaction = Signer::signRawMessage(signerKeys, parse_hex(rawMessageData)); - - auto expectedHex = - "016e7f8349977b482bccf0bfc202ad917295803831e59ccb865b97d657464791ebfe3336879b84b9f165e464a3" - "4751fe30d54b01f3c9f33f969aafe1e85951b10901000203207be13c43c4528592eaf3fd34e064c641c5be3cb6" - "691877d7ade94dff36734108eaea30723c33b52507bc54024910612f885e4c80c10b99a047fd42c0acbace0000" - "000000000000000000000000000000000000000000000000000000000000040404040404040404040404040404" - "040404040404040404040404040404040401020200010c020000002a00000000000000"; - ASSERT_EQ(hex(rawTransaction), expectedHex); -} - -TEST(SolanaSigner, SignDelegateStakeV2) { - const auto privateKeySigner = - PrivateKey(Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746")); - const auto publicKeySigner = privateKeySigner.getPublicKey(TWPublicKeyTypeED25519); - auto signer = Address(publicKeySigner); - ASSERT_EQ(signer.string(), "zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); - - auto voteAddress = Address("4jpwTqt1qZoR7u6u639z2AngYFGN3nakvKhowcnRZDEC"); - auto programId = Address("Stake11111111111111111111111111111111111111"); - Solana::Hash recentBlockhash("11111111111111111111111111111111"); - auto stakeAddress = StakeProgram::addressFromRecentBlockhash(signer, recentBlockhash, programId); - - auto message = Message::createStake(signer, stakeAddress, voteAddress, 42, recentBlockhash); - auto transaction = Transaction(message); - - std::vector signerKeys; - signerKeys.push_back(privateKeySigner); - Signer::sign(signerKeys, transaction); - - std::vector expectedSignatures; - Signature expectedSignature("58iogHzSJZmvTxi71W8k2yZXSPVfGAgtgqrk1RaBtfVFewU9yiJCkvSF1Hhjyax5DuexzR7ryWZDAWKQ73pyqvMs"); - expectedSignatures.push_back(expectedSignature); - EXPECT_EQ(transaction.signatures, expectedSignatures); - - auto expectedString = "j24mVM9Zgu5vDZhPLGGuCRXQnP9djNtxdHh4txN3S7dwJsNNL5fbhzGpPgSUAcLGoMVCfF9TuqTYfpfJnb4sJFe1ahM8yPL5HwuKL6py5AZJFi8SWx9fvaVB699dCPo1GT3JoEBLPCZ9o2jQtnwzLkzTYJnKv2axqhKWFE2sz6TBA5J39eZcjMFUYgyxz6Q5S4MWqYQCb8UET2NAEZoKcfy7j8N25WXL6Gj4j3hBZjpHQQNaGaNEprEqyma3ZuVhpGiCALSsuzVLX3wZVo4icXwe952deMFA4tH3BK1jcSQCgfmcKDJ9nd7bdrnUUs4BoMdF1uDZB5LxE2UH8QiqtYvaUcorF4SJ3gPxM5ykbyPsNK1cSYZF9NMpW2GofyC17eELwnHQTQB2kqphxJZu7BahvkwiDPPeeydiXAkBspJ3nc3PCBujv6WJw22ZHw5j6zAP8ZGnCW44pqtWD5qifF9tTKhySKdANNiWifs3tSCCPQqjfJXu14drNinR6VG8rJxS1qgmRYiRQUa7m1vtoaZFRN5qKUeAfoFKkAVaNnMdwgsNqNH4dqBodTCJFs1LkYwhgRZdZGbwXTn1j7vpR3DSnv4g72i2H556srzK53jdUmdv6yfxt516XDSshqZtHnKZ1tudxKjBXwsqT3imDiZFVka9wKWUAYMCi4XZ79CY6Xpsd9c18U2e9TCngQmgkTATFgrqysfraokNffgqWxvsPMugksbvbPjJs3iCzByvphkC9p7hCf6LwbeF8XnVB91EAgRDA4VLE1f9wkcq5zjy879YWJ4r516h3PQszTz1EaJXNAXdbk5Em7eyuuabGP1Q3nijFTL2yhMDsXpgrjAuEAABNxFMd4J1JRMaic615mHrhwociksrsfQK"; - EXPECT_EQ(transaction.serialize(), expectedString); -} - -TEST(SolanaSigner, SignDelegateStakeV1) { - const auto privateKeySigner = - PrivateKey(Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746")); - const auto publicKeySigner = privateKeySigner.getPublicKey(TWPublicKeyTypeED25519); - auto signer = Address(publicKeySigner); - ASSERT_EQ(signer.string(), "zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); - - auto voteAddress = Address("4jpwTqt1qZoR7u6u639z2AngYFGN3nakvKhowcnRZDEC"); - auto programId = Address("Stake11111111111111111111111111111111111111"); - Solana::Hash recentBlockhash("11111111111111111111111111111111"); - auto stakeAddress = StakeProgram::addressFromValidatorSeed(signer, voteAddress, programId); - - auto message = Message::createStake(signer, stakeAddress, voteAddress, 42, recentBlockhash); - auto transaction = Transaction(message); - - std::vector signerKeys; - signerKeys.push_back(privateKeySigner); - Signer::sign(signerKeys, transaction); - - std::vector expectedSignatures; - Signature expectedSignature("gDPbnakbktrASmnUwKGpmftvQRbcyAvxyAyVXq3oVLfAdTPDqY8hhLPHTgidEZGWcmiaXnEyKg2GQLkkAh3JYr3"); - expectedSignatures.push_back(expectedSignature); - EXPECT_EQ(transaction.signatures, expectedSignatures); - - auto expectedString = "TKPiN35HzeD3zdwxDFvnkgoqud7CZsda15JkBwM4nDpr623rM7MZsH6QvMMyKpiz7MeRNTrfyHkRLQSBT9Tbg2mgTdfrbhhqeF3Suu5ECphqn8DFYPoMnFzeg5u9gaqevfjhuizzeo2YDJF8aVGy1pez8gMbp5vHz1SuvQUgfcvFctggUMwNiJorSmmp3N6TzQSd38CZrA8ZLhaJjuwDwVMjmj18rGTV1gkX19L7byTFrus2vNvPeUa2AawwUnFpYMPgvCKkHTrpnjvypjoLof9yMUFQ5M1S3Ntv53KJyXwXq6ejJnBDtisnDcdMDNSZp3VeKz6XCr8XVM5xNVh3LX12V4kc3ueqkokYJLP1JmuhA3nNZA1G5KTNno93HUoBkEa1x5h3haoCSgmQC97LoJbJM6B6C2NbaDj2J6iiTaVQdin4He4Jpj575WDhNTqsLjzFUHPUHQF1CRnuss8UpVyMsa4kdVqCDQGeh5DKbkikgcB8GKPBuC91DRxGEqgoygNsu5nnQy4o3YAJnBBK6HsKxpdjbYD8wCUdLw8muhjpEqeBTPShEaogm9zfehidiCcnxbeoX3gmW8oH9gpWoX7GrkJgF6Wn7iWohmrzqzAjoBz8hpeY5nkkhHrf9iswVGMpakdLGy3YxkGJVpsW8KJACwEKXGLq8SVLtXSUHG8EP16zfYHxKjkCSs8PkdFsA5esxsxppPTVZivuEPqJ5og55aNmugdNDrAFYWdcH1Q4rm7BXN6oHECdz2yY4HFVWh9u592oqozt2gQKu3vmhcNFzzQe1xgs6zKSv38kSGTnipd7Hx2VL3qNAR6XBRiwAi226qSTzxi6R82p7cMB7TMy6fk5AZ3sXDSXFNJ9S5SSU1V63ruw75QMtVio"; - EXPECT_EQ(transaction.serialize(), expectedString); -} - -TEST(SolanaSigner, SignCreateTokenAccount) { - const auto privateKeySigner = - PrivateKey(Base58::bitcoin.decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5")); - const auto publicKeySigner = privateKeySigner.getPublicKey(TWPublicKeyTypeED25519); - auto signer = Address(publicKeySigner); - EXPECT_EQ(signer.string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); - - auto token = Address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); - auto tokenAddress = Address("EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); - Solana::Hash recentBlockhash("9ipJh5xfyoyDaiq8trtrdqQeAhQbQkWy2eANizKvx75K"); - - auto message = Message::createTokenCreateAccount(signer, signer, token, tokenAddress, recentBlockhash); - auto transaction = Transaction(message); - - std::vector signerKeys; - signerKeys.push_back(privateKeySigner); - Signer::sign(signerKeys, transaction); - - std::vector expectedSignatures; - Signature expectedSignature("3doYbPs5rES3TeDSrntqUvMgXCDE2ViJX2SFhLtiptVNkqPuixXs1SwU5LUZ3KwHnCzDUth6BRr3vU3gqnuUgRvQ"); - expectedSignatures.push_back(expectedSignature); - EXPECT_EQ(transaction.signatures, expectedSignatures); - - auto expectedString = - // test data obtained from spl-token create-account - "CKzRLx3AQeVeLQ7T4hss2rdbUpuAHdbwXDazxtRnSKBuncCk3WnYgy7XTrEiya19MJviYHYdTxi9gmWJY8qnR2vHVnH2DbPiKA8g72rD3VvMnjosGUBBvCwbBLge6FeQdgczMyRo9n5PcHvg9yJBTJaEEvuewyBVHwCGyGQci7eYd26xtZtCjAjwcTq4gGr3NZbeRW6jZp6j6APuew7jys4MKYRV4xPodua1TZFCkyWZr1XKzmPh7KTavtN5VzPDA8rbsvoEjHnKzjB2Bszs6pDjcBFSHyQqGsHoF8XPD35BLfjDghNtBmf9cFqo5axa6oSjANAuYg6cMSP4Hy28waSj8isr6gQjE315hWi3W1swwwPcn322gYZx6aMAcmjczaxX9aktpHYgZxixF7cYWEHxJs5QUK9mJePu9Xc6yW75UB4Ynx6dUgaSTEUzoQthF2TN3xXwu1"; - EXPECT_EQ(transaction.serialize(), expectedString); -} - -TEST(SolanaSigner, SignCreateTokenAccountForOther_3E6UFV) { - const auto privateKeySigner = - PrivateKey(parse_hex("4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7")); - const auto publicKeySigner = privateKeySigner.getPublicKey(TWPublicKeyTypeED25519); - auto signer = Address(publicKeySigner); - EXPECT_EQ(signer.string(), "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); - - auto otherMainAddress = Address("3xJ3MoUVFPNFEHfWdtNFa8ajXUHsJPzXcBSWMKLd76ft"); - auto token = Address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); - auto tokenAddress = Address("67BrwFYt7qUnbAcYBVx7sQ4jeD2KWN1ohP6bMikmmQV3"); - Solana::Hash recentBlockhash("HmWyvrif3QfZJnDiRyrojmH9iLr7eMxxqiC9RJWFeunr"); - - auto message = Message::createTokenCreateAccount(signer, otherMainAddress, token, tokenAddress, recentBlockhash); - auto transaction = Transaction(message); - - std::vector signerKeys; - signerKeys.push_back(privateKeySigner); - Signer::sign(signerKeys, transaction); - - auto expectedString = - // https://explorer.solana.com/tx/3E6UFVamHCm6Bgk8gXdZex7R7tJAVxqJm6t9ephAKu1PjcfZrD7CJqMwKu6RrvWSUESbZFqzdUyLXuxAFaawPHvJ - "4BsrHedHuForcKDhLdnLYDXgtQgQEj3EQVDtEhqa7o6ukFjW3shpTWv6PeKQdMp6af4ASjD4xQeZvXxLK5WUjguVMUf3xdJn7RnFeM7hdDJ56RDBM5PRJbRJVHjz6FJ7SVNTvr9y3gVYQtWx7NfKRxiyEAfq9JG7nqxSWaW6raMr9t35aVcdAVuXE9iXj3rzhVfCS69vVzy5KcFEK3mvDYG6L12V2CfviCydmeCvPw5r3zBUrZSQv7Ti4XFNBrPbk28gcqQwsBknBqasHxHqD9VUyPmBTuUyXq75QN8rhqN55NjxKBUw37tEUS1jKVpWnTeLFq1eRAMdXvjftNuQ5Bmm8Zc12PGWj9vdorBaYyvZXexJST5xNjR4SCkXvXZoRScETck95chv3VBn54jP8DpB4GGUmATFKSxpdtnNV64i1SQXW13KJwswthJvAaDiqevQLKLkvrTEAdb4BxEfPkFjDVti6P58rTZCMg5CTVLqdmWwpTSW5V"; - EXPECT_EQ(transaction.serialize(), expectedString); -} - -TEST(SolanaSigner, SignTransferToken_3vZ67C) { - const auto privateKeySigner = - PrivateKey(Base58::bitcoin.decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5")); - const auto publicKeySigner = privateKeySigner.getPublicKey(TWPublicKeyTypeED25519); - auto signer = Address(publicKeySigner); - EXPECT_EQ(signer.string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); - - auto token = Address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); - auto senderTokenAddress = Address("EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); - auto recipientTokenAddress = Address("3WUX9wASxyScbA7brDipioKfXS1XEYkQ4vo3Kej9bKei"); - uint64_t amount = 4000; - uint8_t decimals = 6; - Solana::Hash recentBlockhash("CNaHfvqePgGYMvtYi9RuUdVxDYttr1zs4TWrTXYabxZi"); - - auto message = Message::createTokenTransfer(signer, token, - senderTokenAddress, recipientTokenAddress, amount, decimals, recentBlockhash); - auto transaction = Transaction(message); - - std::vector signerKeys; - signerKeys.push_back(privateKeySigner); - Signer::sign(signerKeys, transaction); - - std::vector expectedSignatures; - Signature expectedSignature("3vZ67CGoRYkuT76TtpP2VrtTPBfnvG2xj6mUTvvux46qbnpThgQDgm27nC3yQVUZrABFjT9Qo7vA74tCjtV5P9Xg"); - expectedSignatures.push_back(expectedSignature); - EXPECT_EQ(transaction.signatures, expectedSignatures); - - auto expectedString = - // https://explorer.solana.com/tx/3vZ67CGoRYkuT76TtpP2VrtTPBfnvG2xj6mUTvvux46qbnpThgQDgm27nC3yQVUZrABFjT9Qo7vA74tCjtV5P9Xg - // test data obtained from spl-token transfer - "PGfKqEaH2zZXDMZLcU6LUKdBSzU1GJWJ1CJXtRYCxaCH7k8uok38WSadZfrZw3TGejiau7nSpan2GvbK26hQim24jRe2AupmcYJFrgsdaCt1Aqs5kpGjPqzgj9krgxTZwwob3xgC1NdHK5BcNwhxwRtrCphGEH7zUFpGFrFrHzgpf2KY8FvPiPELQyxzTBuyNtjLjMMreehSKShEjD9Xzp1QeC1pEF8JL6vUKzxMXuveoEYem8q8JiWszYzmTMfDk13JPgv7pXFGMqDV3yNGCLsWccBeSFKN4UKECre6x2QbUEiKGkHkMc4zQwwyD8tGmEMBAGm339qdANssEMNpDeJp2LxLDStSoWShHnotcrH7pUa94xCVvCPPaomF"; - EXPECT_EQ(transaction.serialize(), expectedString); -} diff --git a/tests/Solana/TWAnySignerTests.cpp b/tests/Solana/TWAnySignerTests.cpp deleted file mode 100644 index d37800122a1..00000000000 --- a/tests/Solana/TWAnySignerTests.cpp +++ /dev/null @@ -1,395 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Base58.h" -#include "HexCoding.h" -#include "proto/Solana.pb.h" -#include "Solana/Address.h" -#include "Solana/Program.h" -#include "PrivateKey.h" -#include "../interface/TWTestUtilities.h" -#include - -#include - -using namespace TW; -using namespace TW::Solana; - -const auto expectedString1 = - "3p2kzZ1DvquqC6LApPuxpTg5CCDVPqJFokGSnGhnBHrta4uq7S2EyehV1XNUVXp51D69GxGzQZU" - "jikfDzbWBG2aFtG3gHT1QfLzyFKHM4HQtMQMNXqay1NAeiiYZjNhx9UvMX4uAQZ4Q6rx6m2AYfQ" - "7aoMUrejq298q1wBFdtS9XVB5QTiStnzC7zs97FUEK2T4XapjF1519EyFBViTfHpGpnf5bfizDz" - "sW9kYUtRDW1UC2LgHr7npgq5W9TBmHf9hSmRgM9XXucjXLqubNWE7HUMhbKjuBqkirRM"; - -TEST(TWAnySignerSolana, SignTransfer) { - auto privateKey = Base58::bitcoin.decode("A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr"); - auto input = Proto::SigningInput(); - - auto& message = *input.mutable_transfer_transaction(); - message.set_recipient("EN2sCsJ1WDV8UFqsiTXHcUPUxQ4juE71eCknHYYMifkd"); - message.set_value((uint64_t)42L); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_recent_blockhash("11111111111111111111111111111111"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - ASSERT_EQ(output.encoded(), expectedString1); -} - -TEST(TWAnySignerSolana, SignTransferToSelf) { - auto privateKey = Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746"); - auto input = Proto::SigningInput(); - - auto& message = *input.mutable_transfer_transaction(); - message.set_recipient("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); - message.set_value((uint64_t)42L); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_recent_blockhash("11111111111111111111111111111111"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - auto expectedString = - "EKUmihvvUPKVN4GSCFwZRtz8WiyAuPvthW69Smo19SCjcPLQ6T7EVZd1HU71WAoe1bfgmPNS5JhU7ZLA9XKG3qbZqe" - "EFJ1xmRwW9ZKw8SKMAL6VRWxp87oLu7PSmf5b8R34vCaww3XLKtZkoP49a7TUK31DqPN5xJCceMB3BZJyaojQaKU8n" - "UkzSGf89LY6abZXp9krKAebvc6bSMzTP8SHSvbmZbf3VtejmpQeN9X6e7WVDn6oDa2bGT"; - ASSERT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignTransferWithMemoAndReference) { - const auto privateKey = Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746"); - auto input = Solana::Proto::SigningInput(); - - auto& message = *input.mutable_transfer_transaction(); - message.set_recipient("71e8mDsh3PR6gN64zL1HjwuxyKpgRXrPDUJT7XXojsVd"); - message.set_value((uint64_t)10000000L); - message.set_memo("HelloSolanaMemo"); - message.add_references("CuieVDEDtLo7FypA9SbLM9saXFdb1dsshEkyErMqkRQq"); - message.add_references("tFpP7tZUt6zb7YZPpQ11kXNmsc5YzpMXmahGMvCHhqS"); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_recent_blockhash("11111111111111111111111111111111"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - const auto expectedString = "NfNH76sST3nJ4FmFGTZJBUpJou7DRuHM3YNprT1HeEau699CQF65xNf21Hoi491bbtVKUXfqCJyeZhfTCEnABuXNC1JrhGBeCv2AbQdaS9gpp9j4xHHomhCYdwYaBWFMcKkdMXrx9xHqL9Vkny4HezkwQfb3wGqcaE9XVRdkkNxsoJnVKddRnrQbjhsZGTcKdfmbTghoUeRECNPTm6nZTA1owWF1Dq6mfr6M3GZRh4ucqEquxKsQC2HQwNRrGZahsfyUvwspPWwMt78q5Jpjd9kHqkFDspZL6Pepv4dAA4uHhYDCHeP2bbDiFMBYxxWCVDDtRKSh3H92xUgh1GCSgNcjGdbVfQUhSDPX3k9xuuszPTsVZ2GnsavAsRp6Vf6fFEikBX6pVV9zjW1cx94EepQ2aGEBSsVu4RzX7rJjCLCq87h8cxxf1XnF8mvYGEK7wzF"; - EXPECT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignDelegateStakeTransaction_noStakeAccount) { - auto privateKey = Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746"); - auto input = Solana::Proto::SigningInput(); - - auto& message = *input.mutable_delegate_stake_transaction(); - message.set_validator_pubkey("4jpwTqt1qZoR7u6u639z2AngYFGN3nakvKhowcnRZDEC"); - message.set_value((uint64_t)42L); - message.set_stake_account(""); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_recent_blockhash("11111111111111111111111111111111"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - auto expectedString = "j24mVM9Zgu5vDZhPLGGuCRXQnP9djNtxdHh4txN3S7dwJsNNL5fbhzGpPgSUAcLGoMVCfF9TuqTYfpfJnb4sJFe1ahM8yPL5HwuKL6py5AZJFi8SWx9fvaVB699dCPo1GT3JoEBLPCZ9o2jQtnwzLkzTYJnKv2axqhKWFE2sz6TBA5J39eZcjMFUYgyxz6Q5S4MWqYQCb8UET2NAEZoKcfy7j8N25WXL6Gj4j3hBZjpHQQNaGaNEprEqyma3ZuVhpGiCALSsuzVLX3wZVo4icXwe952deMFA4tH3BK1jcSQCgfmcKDJ9nd7bdrnUUs4BoMdF1uDZB5LxE2UH8QiqtYvaUcorF4SJ3gPxM5ykbyPsNK1cSYZF9NMpW2GofyC17eELwnHQTQB2kqphxJZu7BahvkwiDPPeeydiXAkBspJ3nc3PCBujv6WJw22ZHw5j6zAP8ZGnCW44pqtWD5qifF9tTKhySKdANNiWifs3tSCCPQqjfJXu14drNinR6VG8rJxS1qgmRYiRQUa7m1vtoaZFRN5qKUeAfoFKkAVaNnMdwgsNqNH4dqBodTCJFs1LkYwhgRZdZGbwXTn1j7vpR3DSnv4g72i2H556srzK53jdUmdv6yfxt516XDSshqZtHnKZ1tudxKjBXwsqT3imDiZFVka9wKWUAYMCi4XZ79CY6Xpsd9c18U2e9TCngQmgkTATFgrqysfraokNffgqWxvsPMugksbvbPjJs3iCzByvphkC9p7hCf6LwbeF8XnVB91EAgRDA4VLE1f9wkcq5zjy879YWJ4r516h3PQszTz1EaJXNAXdbk5Em7eyuuabGP1Q3nijFTL2yhMDsXpgrjAuEAABNxFMd4J1JRMaic615mHrhwociksrsfQK"; - EXPECT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignDelegateStakeTransaction_withAccount) { - auto privateKey = Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746"); - auto input = Solana::Proto::SigningInput(); - - auto& message = *input.mutable_delegate_stake_transaction(); - message.set_validator_pubkey("4jpwTqt1qZoR7u6u639z2AngYFGN3nakvKhowcnRZDEC"); - message.set_stake_account("6u9vJH9pRj66N5oJFCBADEbpMTrLxQATcL6q5p5MXwYv"); - message.set_value((uint64_t)42L); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_recent_blockhash("11111111111111111111111111111111"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - auto expectedString = "TKPiN35HzeD3zdwxDFvnkgoqud7CZsda15JkBwM4nDpr623rM7MZsH6QvMMyKpiz7MeRNTrfyHkRLQSBT9Tbg2mgTdfrbhhqeF3Suu5ECphqn8DFYPoMnFzeg5u9gaqevfjhuizzeo2YDJF8aVGy1pez8gMbp5vHz1SuvQUgfcvFctggUMwNiJorSmmp3N6TzQSd38CZrA8ZLhaJjuwDwVMjmj18rGTV1gkX19L7byTFrus2vNvPeUa2AawwUnFpYMPgvCKkHTrpnjvypjoLof9yMUFQ5M1S3Ntv53KJyXwXq6ejJnBDtisnDcdMDNSZp3VeKz6XCr8XVM5xNVh3LX12V4kc3ueqkokYJLP1JmuhA3nNZA1G5KTNno93HUoBkEa1x5h3haoCSgmQC97LoJbJM6B6C2NbaDj2J6iiTaVQdin4He4Jpj575WDhNTqsLjzFUHPUHQF1CRnuss8UpVyMsa4kdVqCDQGeh5DKbkikgcB8GKPBuC91DRxGEqgoygNsu5nnQy4o3YAJnBBK6HsKxpdjbYD8wCUdLw8muhjpEqeBTPShEaogm9zfehidiCcnxbeoX3gmW8oH9gpWoX7GrkJgF6Wn7iWohmrzqzAjoBz8hpeY5nkkhHrf9iswVGMpakdLGy3YxkGJVpsW8KJACwEKXGLq8SVLtXSUHG8EP16zfYHxKjkCSs8PkdFsA5esxsxppPTVZivuEPqJ5og55aNmugdNDrAFYWdcH1Q4rm7BXN6oHECdz2yY4HFVWh9u592oqozt2gQKu3vmhcNFzzQe1xgs6zKSv38kSGTnipd7Hx2VL3qNAR6XBRiwAi226qSTzxi6R82p7cMB7TMy6fk5AZ3sXDSXFNJ9S5SSU1V63ruw75QMtVio"; - ASSERT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignDeactivateStakeTransaction) { - auto privateKey = Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746"); - auto input = Solana::Proto::SigningInput(); - - auto& message = *input.mutable_deactivate_stake_transaction(); - message.set_stake_account("6XMLCn47d5kPi3g4YcjqFvDuxWnpVADpN2tXpeRc4XUB"); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_recent_blockhash("11111111111111111111111111111111"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - auto expectedString = "6x3fSstNz4GpPxmT5jHXwyD62uyJMKaPWeBDNNcwXZA9NJ3E7KavCXPNUd8ZYTX5VpkfHKGszkwzM6AdAp4giLD29jvWdNYjkV1Nvb42xFwGD6ryMPZzXkJijaRTrA7SvPTDSRU2haGVmorqkywAXLQUCw47NmBUfLTb5gDcKoBeaAsahckv1eCE746thJVTg2dQNvUTULKF6xckUg7kwFkcUuRe4HCcRgrKcNAUKLR2rEM3brVQkUyAaAtMMtc3gVDXxxpbtW5Fa9wGaEnh31FdRo4z5YBzAUaz7vcrvzF2j81KCPTVnYyTmeJzCzJafzCVCtw"; - EXPECT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignDeactivateAllStakeTransaction) { - auto privateKey = Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746"); - auto input = Solana::Proto::SigningInput(); - - auto& message = *input.mutable_deactivate_all_stake_transaction(); - *message.add_stake_accounts() = "CJQStmfyoHbosX1GfVn64yWrNJAo214q2aqxwS6FGh4k"; - *message.add_stake_accounts() = "6XMLCn47d5kPi3g4YcjqFvDuxWnpVADpN2tXpeRc4XUB"; - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_recent_blockhash("11111111111111111111111111111111"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - auto expectedString = "U9azMJWRfDhypoDeQLYWyBYFZCwRNZy8sbrVX9awKK84zNGbSQfYTTJ3ZyzjNUVbU5npbw2MsWfmZGHZRvpfN7G7o3sVePyFRXrmLxrGZzGycFv25Zff4zPxDarbsugbCBgzVGpgwu8x7MdkwBAVHVtNsgMcHgArEAjEmk7YEGpZ15rjo39bCRvmuprWLqSv2SK1RyTZPpTPXVevAbA4i9vvcY8eUbwW29SZCoyGaagLU5EBV9vckMjzGa7gq2yMR6rbq8tDdWaXapYs8RavU49WN94yg4wdE4fzYq8DjqXHq3MuUBLxeYDKJnvj84ioeM4eR1EwjBNrGyz5GHTRuhbNg1nc57SpKsSMVSZW5Ra3tUk84YZXYFHxzeQ9Tv4o"; - EXPECT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignWithdrawStakeTransaction) { - auto privateKey = Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746"); - auto input = Solana::Proto::SigningInput(); - - auto& message = *input.mutable_withdraw_transaction(); - message.set_stake_account("6XMLCn47d5kPi3g4YcjqFvDuxWnpVADpN2tXpeRc4XUB"); - message.set_value((uint64_t)42L); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_recent_blockhash("11111111111111111111111111111111"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - auto expectedString = "gxr4o1trVP8DGG8UC21AA964YqAPFA3rBCF9MwmBQpn5fDtcujM9wp1gzT466MxWGR8wMciS6dSL771q29eURrEEuvhJzRaFDGPLgVB3UL4gd4T2amPQkR4Dzq5drKEtPJRBR86KVVc2kjDsbWNpdL8S7pZqW3VUijAbm9TS8ezG8NExSCkhxExKhUjXWWguEL4qXra7s2JZfhtmvuJneWnEY3isUVfC9knWtGNwpNFvRvzbH2sgHzwtSsD7mkYrBJoazLCwT8r9yypxycHL41XcGtH425MA16kVSunvvBfzG9PzBTS65YJBs64tzttasCU9uEphkwgmfrmoEC8iKt8xD47Ra79RyXd95yURsaxvpb1tVAH8kMNtj8iV1Pfm"; - EXPECT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignWithdrawAllStakeTransaction) { - auto privateKey = Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746"); - auto input = Solana::Proto::SigningInput(); - - auto& message = *input.mutable_withdraw_all_transaction(); - message.add_stake_accounts(); - message.add_stake_accounts(); - message.mutable_stake_accounts(0)->set_stake_account("CJQStmfyoHbosX1GfVn64yWrNJAo214q2aqxwS6FGh4k"); - message.mutable_stake_accounts(0)->set_value((uint64_t)42L); - message.mutable_stake_accounts(1)->set_stake_account("6XMLCn47d5kPi3g4YcjqFvDuxWnpVADpN2tXpeRc4XUB"); - message.mutable_stake_accounts(1)->set_value((uint64_t)67L); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_recent_blockhash("11111111111111111111111111111111"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - auto expectedString = "cvBNusjtHkR74EfWsvFPEe2Mydcr7eoLeY2wJw2ZMZYViotbb63Adai7UD1PW9uLusoVHGLeJC5cPgVBC4F693P9tPAxLs9yiZj1ZJQ4DgnYbeXafqzjdWje1Ly5FgpDUJaaU2RnLCG51CcrmiTJ4KB5fwai6egZaNjbiqo1DEC1wJz4FgKug2aKQWLdeCiH9WhCuvqfhNV6mEE4qRCkU8uS2gfSqBd1AdrczvoDEbKQszosrwmawxqmvTE5EWaFzMb48x9nLqxvpQCvGQu1nX6FxZJjv2swekA7wGLEAA4uSdFLTHNrYSi8pn8hVYGwESEzth9oiPkJCvW7Y2KvGALeERUZn8knHiz2eqaaT72Ajp9UogMdZtiuFHufveLXpBLWUERchhB7eU1magYcPNHcZuEE4uQv5kZJhHAqYCGU6dyUFLVA9Edus7o6fTktYVCjoGb"; - EXPECT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignDeactivateStakeTransaction_1) { - auto privateKey = Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746"); - auto input = Solana::Proto::SigningInput(); - - auto& message = *input.mutable_deactivate_stake_transaction(); - message.set_stake_account("6u9vJH9pRj66N5oJFCBADEbpMTrLxQATcL6q5p5MXwYv"); - - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_recent_blockhash("11111111111111111111111111111111"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - auto expectedString = - "AhfB77PTGTKBfbGPGuEz2khbBy8m8Kou1zqZST9dP7PLJNSeEze5NJuCh5qecPLa3S8xAQ6mTULmnAWiW81ib87nhy" - "wFtx5nKiUvmhdXsvKCSX6NNtNXdRz5yZi3UEop4obco85SY2czS6n4SJwmtDedHLtg9urqdZVth7AUM8KAtrRsksyv" - "ZRYXh64Z8QGyNY7ekj31ae11avGiSDNWYZZHqx7VPWRsKeatGyGk5zPmnRdL8ABMQgJ1Te7wAWwVnNn5QcoAxDuPw6" - "uDctP8Q5S4TieRVatCnukQFj5BTJisez3E2ZJPWhVrMh4K3wEFkPHA7dR"; - ASSERT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignWithdrawStakeTransaction_1) { - auto privateKey = Base58::bitcoin.decode("AevJ4EWcvQ6dptBDvF2Ri5pU6QSBjkzSGHMfbLFKa746"); - auto input = Solana::Proto::SigningInput(); - - auto& message = *input.mutable_withdraw_transaction(); - message.set_stake_account("6u9vJH9pRj66N5oJFCBADEbpMTrLxQATcL6q5p5MXwYv"); - message.set_value((uint64_t)42L); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_recent_blockhash("11111111111111111111111111111111"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - auto expectedString = "NL7WgagucfLd6AkTtcKe1dqd47xxzF356Q7tEhPrz1LRzZiAmokAaUkpwJ7X71Pmz97zZf9gZQU5BNswdcdpqUL8n1jwn4CoZMaPJhX5LF43Sj817cgreSG14TEWfKertpVpTtc5zY7vkDM7t9wjYhkaqgYz76HQtqAqRHnHF2Qr9EEfLj4zYRerWtyfS3EVyVUaasPxJ5vkcaonEfpGc6uWecaFr2A3YbzEBQpWXjMaXLqmMDtNS8rTNZmwvToa71ddFZKDgaHDcc6Lkg8qriZ3aQbUqL1TbeYp2mk9dWTKY62L1YFE2DyZV5P2qz5feywcMZ9JW6X1wBmiHFCseC42QbnbTibr1VdqLbGx7UWn5tHWk5jCN2aatEPfbFDZ"; - ASSERT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignCreateTokenAccount1) { - auto privateKeyData = Base58::bitcoin.decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"); - ASSERT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); - - auto input = Solana::Proto::SigningInput(); - auto& message = *input.mutable_create_token_account_transaction(); - message.set_main_address("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); - message.set_token_mint_address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); - message.set_token_address("EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); - input.set_private_key(privateKeyData.data(), privateKeyData.size()); - input.set_recent_blockhash("9ipJh5xfyoyDaiq8trtrdqQeAhQbQkWy2eANizKvx75K"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - auto expectedString = - "CKzRLx3AQeVeLQ7T4hss2rdbUpuAHdbwXDazxtRnSKBuncCk3WnYgy7XTrEiya19MJviYHYdTxi9gmWJY8qnR2vHVnH2DbPiKA8g72rD3VvMnjosGUBBvCwbBLge6FeQdgczMyRo9n5PcHvg9yJBTJaEEvuewyBVHwCGyGQci7eYd26xtZtCjAjwcTq4gGr3NZbeRW6jZp6j6APuew7jys4MKYRV4xPodua1TZFCkyWZr1XKzmPh7KTavtN5VzPDA8rbsvoEjHnKzjB2Bszs6pDjcBFSHyQqGsHoF8XPD35BLfjDghNtBmf9cFqo5axa6oSjANAuYg6cMSP4Hy28waSj8isr6gQjE315hWi3W1swwwPcn322gYZx6aMAcmjczaxX9aktpHYgZxixF7cYWEHxJs5QUK9mJePu9Xc6yW75UB4Ynx6dUgaSTEUzoQthF2TN3xXwu1"; - ASSERT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignCreateTokenAccount2_5KtPn1) { - auto privateKeyData = parse_hex("4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7"); - ASSERT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); - - auto input = Solana::Proto::SigningInput(); - auto& message = *input.mutable_create_token_account_transaction(); - message.set_main_address("Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); - message.set_token_mint_address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); - message.set_token_address("ANVCrmRw7Ww7rTFfMbrjApSPXEEcZpBa6YEiBdf98pAf"); - input.set_private_key(privateKeyData.data(), privateKeyData.size()); - input.set_recent_blockhash("HxaCmxrXgzkzXYvDFTToENtf9rVKk7cbiuSUqnqNheHq"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - auto expectedString = - // https://explorer.solana.com/tx/5KtPn1LGuxhFiwjxErkxTb7XxtLVYUBe6Cn33ej7ATNVyorrkk3UAFJWDBUmzP8CZjmkocCxiMAdYnvrKoGpMsJx - "EoJGDRFZdnjmx7rgwYSuDGTMTUdxCBeh8RggrQDzGht9bwzLPpCWkCrN4iQJqg3R6JxP7z2QZuf7dGCZcjMVBmmisYE8waRsohcvygRwmGr6nefbaujR5avm2x3EUvoTGyy8cMZJxX7URx45qQJyCgqFLNFCQzD1Kej3xCEPAJqCdGZgmqkryw2E2nkpGKXgRmbyEg2rFgd5kpvjG6jSLLYzGomxVnaKK2XyMQbcedkTMYJ8Ara71iWPRFUziWfgivZcA1qsQp92Fpao3FSsRprhoQz9u1VyAnh8zEM9jCKiE5s4dwCknqCJYeYsbMLn1be2vNP9bMQfu1jjGSHmbb9WR3E2vakTUEUByASXqSAJZuXYE5scopEzB28rC8nrC31ArLMZng5wWym3QbqEv2Syd6RHoEeoXR6vA5LPqvJKyvtH82p4hc4XbD18128aNrFG3GTD2P"; - ASSERT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignCreateTokenAccountForOther_3E6UFV) { - auto privateKeyData = parse_hex("4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7"); - ASSERT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); - - auto input = Solana::Proto::SigningInput(); - auto& message = *input.mutable_create_token_account_transaction(); - message.set_main_address("3xJ3MoUVFPNFEHfWdtNFa8ajXUHsJPzXcBSWMKLd76ft"); - message.set_token_mint_address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); - message.set_token_address("67BrwFYt7qUnbAcYBVx7sQ4jeD2KWN1ohP6bMikmmQV3"); - input.set_private_key(privateKeyData.data(), privateKeyData.size()); - input.set_recent_blockhash("HmWyvrif3QfZJnDiRyrojmH9iLr7eMxxqiC9RJWFeunr"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - auto expectedString = - // https://explorer.solana.com/tx/3E6UFVamHCm6Bgk8gXdZex7R7tJAVxqJm6t9ephAKu1PjcfZrD7CJqMwKu6RrvWSUESbZFqzdUyLXuxAFaawPHvJ - "4BsrHedHuForcKDhLdnLYDXgtQgQEj3EQVDtEhqa7o6ukFjW3shpTWv6PeKQdMp6af4ASjD4xQeZvXxLK5WUjguVMUf3xdJn7RnFeM7hdDJ56RDBM5PRJbRJVHjz6FJ7SVNTvr9y3gVYQtWx7NfKRxiyEAfq9JG7nqxSWaW6raMr9t35aVcdAVuXE9iXj3rzhVfCS69vVzy5KcFEK3mvDYG6L12V2CfviCydmeCvPw5r3zBUrZSQv7Ti4XFNBrPbk28gcqQwsBknBqasHxHqD9VUyPmBTuUyXq75QN8rhqN55NjxKBUw37tEUS1jKVpWnTeLFq1eRAMdXvjftNuQ5Bmm8Zc12PGWj9vdorBaYyvZXexJST5xNjR4SCkXvXZoRScETck95chv3VBn54jP8DpB4GGUmATFKSxpdtnNV64i1SQXW13KJwswthJvAaDiqevQLKLkvrTEAdb4BxEfPkFjDVti6P58rTZCMg5CTVLqdmWwpTSW5V"; - ASSERT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignTokenTransfer1_3vZ67C) { - auto privateKeyData = Base58::bitcoin.decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"); - ASSERT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); - - auto input = Solana::Proto::SigningInput(); - auto& message = *input.mutable_token_transfer_transaction(); - message.set_token_mint_address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); - message.set_sender_token_address("EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); - message.set_recipient_token_address("3WUX9wASxyScbA7brDipioKfXS1XEYkQ4vo3Kej9bKei"); - message.set_amount(4000); // 0.004 - message.set_decimals(6); - input.set_private_key(privateKeyData.data(), privateKeyData.size()); - input.set_recent_blockhash("CNaHfvqePgGYMvtYi9RuUdVxDYttr1zs4TWrTXYabxZi"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - auto expectedString = - // https://explorer.solana.com/tx/3vZ67CGoRYkuT76TtpP2VrtTPBfnvG2xj6mUTvvux46qbnpThgQDgm27nC3yQVUZrABFjT9Qo7vA74tCjtV5P9Xg - "PGfKqEaH2zZXDMZLcU6LUKdBSzU1GJWJ1CJXtRYCxaCH7k8uok38WSadZfrZw3TGejiau7nSpan2GvbK26hQim24jRe2AupmcYJFrgsdaCt1Aqs5kpGjPqzgj9krgxTZwwob3xgC1NdHK5BcNwhxwRtrCphGEH7zUFpGFrFrHzgpf2KY8FvPiPELQyxzTBuyNtjLjMMreehSKShEjD9Xzp1QeC1pEF8JL6vUKzxMXuveoEYem8q8JiWszYzmTMfDk13JPgv7pXFGMqDV3yNGCLsWccBeSFKN4UKECre6x2QbUEiKGkHkMc4zQwwyD8tGmEMBAGm339qdANssEMNpDeJp2LxLDStSoWShHnotcrH7pUa94xCVvCPPaomF"; - ASSERT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignTokenTransfer2_2pMvzp) { - auto privateKeyData = Base58::bitcoin.decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"); - ASSERT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); - - auto input = Solana::Proto::SigningInput(); - auto& message = *input.mutable_token_transfer_transaction(); - message.set_token_mint_address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); - message.set_sender_token_address("EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); - message.set_recipient_token_address("ANVCrmRw7Ww7rTFfMbrjApSPXEEcZpBa6YEiBdf98pAf"); - message.set_amount(6100); // 0.0061 - message.set_decimals(6); - input.set_private_key(privateKeyData.data(), privateKeyData.size()); - input.set_recent_blockhash("zMEbroNLJ4vfDTdQyA72rk35c7nPo4K38efHLujbSuz"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - auto expectedString = - // https://explorer.solana.com/tx/2pMvzparE16evNgNhiexBfj15eurQgqFJXemYkuGasWV8RfT5tQseadqXA2VXbgGZPM1MpLcGwfkKKqvYvrKTmnR - "LCtawaKHmvh9WEjYPFFMDQXsdKMQbVyK4Q3aRRfLCouqw6GE4p31PRPFoQqtazTziEj3ex3iLgnCspz1MN4SUE9d33g3HiiA6oCS6wGMvB2i3ojtmJzndCiLoDmuZgiuGouVSeS2MAEUoS3CRjdnbNKbRwgKn8YsDe1bZ57ueipfBLJfiE7xr8ji678uAv8FcMgo8Mq88SBGxVCUhjMS2VGQZhRUHHzDmvnzxhbbUzsLDfApzjHExkUm7ws3cQ2i1cSpQNCQWJd6rcDv1sYwDAavPS571Ny3CUq4cZxABh45Gj88LkRpzBMRdoebrh9hPy8ZRnu7PocBVjZytCgdF4CuhzdYNsmdcuU2WN5CEmv5zQ7pBrFdLZ8bBifP"; - ASSERT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignCreateAndTransferToken_449VaY) { - auto privateKeyData = Base58::bitcoin.decode("66ApBuKpo2uSzpjGBraHq7HP8UZMUJzp3um8FdEjkC9c"); - ASSERT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); - - auto input = Solana::Proto::SigningInput(); - auto& message = *input.mutable_create_and_transfer_token_transaction(); - message.set_recipient_main_address("71e8mDsh3PR6gN64zL1HjwuxyKpgRXrPDUJT7XXojsVd"); - message.set_token_mint_address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); - message.set_recipient_token_address("EF6L8yJT1SoRoDCkAZfSVmaweqMzfhxZiptKi7Tgj5XY"); - message.set_sender_token_address("ANVCrmRw7Ww7rTFfMbrjApSPXEEcZpBa6YEiBdf98pAf"); - message.set_amount(2900); - message.set_decimals(6); - input.set_private_key(privateKeyData.data(), privateKeyData.size()); - input.set_recent_blockhash("DMmDdJP41M9mw8Z4586VSvxqGCrqPy5uciF6HsKUVDja"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - auto expectedString = - // https://explorer.solana.com/tx/449VaYo48LrkMJF6XVKt9sJwVQN6Seqrmh9erDCLtiuj6BgFG3wpF5TwjNkxgJ7qzNa6NTj3TFsU3h9hKszfkA7w - "3Y2MVz2VVi7aEyC9q1awwdk1ModDBPHRSacKmTYnSgkmbbJeZ62Fub1bVPSHaTy4LUcQpzCQYhHAKtTKXUDYijEeLsMAUqPBEMAq1w8zCdqDpdXy6M4PuwNtYVV1WgqeiEsiMWpPp4BGWKfcziwFbmYueUGituacJq4wTnt92fho8mFi49XW64gEG4iNGScDtJkY7Geq8PKiLh1E9JMJoceiHxKbmxzCmmLTxEHdhySYHcDUSXnXWogZskeZNBMtR9dNjEMkCzEjrxRpBtJPtUNshciY45mDPNmw4j3xyLCBTRikyfFLc5g11r3UgyVD4YokoPRvrEXsgt6W3yjBshropBm6mY2eJYvfY2eZz4Yq8kLcUatCHVKtjcb1mP9Ww57KisJ9bRhipC8sodFaMYhZARMEa4a1u9eH4MyNUATRGNXarwQSBY46PWS3nKP6QBK7Dw7Ppp9MmYkdPcXKaLScbyLF3jKu6dHWMkHw3WdXSsM1wwXjXnWF9LxdwaEVcDmySWybj6aKD9QCWTU5kdncqJU56f7SYNRTN289WdUFGNDmSh56tj2v1"; - ASSERT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignCreateAndTransferTokenWithMemoReferences) { - const auto privateKeyData = Base58::bitcoin.decode("66ApBuKpo2uSzpjGBraHq7HP8UZMUJzp3um8FdEjkC9c"); - EXPECT_EQ(Address(PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519)).string(), "Eg5jqooyG6ySaXKbQUu4Lpvu2SqUPZrNkM4zXs9iUDLJ"); - - auto input = Solana::Proto::SigningInput(); - auto& message = *input.mutable_create_and_transfer_token_transaction(); - message.set_recipient_main_address("71e8mDsh3PR6gN64zL1HjwuxyKpgRXrPDUJT7XXojsVd"); - message.set_token_mint_address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); - message.set_recipient_token_address("EF6L8yJT1SoRoDCkAZfSVmaweqMzfhxZiptKi7Tgj5XY"); - message.set_sender_token_address("ANVCrmRw7Ww7rTFfMbrjApSPXEEcZpBa6YEiBdf98pAf"); - message.set_amount(2900); - message.set_decimals(6); - message.set_memo("HelloSolanaMemo370"); - message.add_references("CuieVDEDtLo7FypA9SbLM9saXFdb1dsshEkyErMqkRQq"); - message.add_references("tFpP7tZUt6zb7YZPpQ11kXNmsc5YzpMXmahGMvCHhqS"); - input.set_private_key(privateKeyData.data(), privateKeyData.size()); - input.set_recent_blockhash("DMmDdJP41M9mw8Z4586VSvxqGCrqPy5uciF6HsKUVDja"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeSolana); - - auto expectedString = "FuUw2MoEGPATE38roXAw9mGQhCfdsdpVDdhuf5h8LKc8iWj2HzNS3SteXqyUoZtQ7L1ufLvu7cTMwNzxT8snnVimcknsA52CeN7bgMz1Ad1hRTAr77zE5efzAi8B124kaQ1cBEb6nFMr5Zq4wwDRoJgBaiUaM1U9ZY6GofCKHGMQN7ZNqEFG4fFvPaMXB59dFtiqrtApBGzvDho3nGshyQWZVWfMY44hvVk45FqiGrXuqUwkiJqeRaDhooZdXiFR9ubwJLXo3Ux23ZyijWKXYNsx1Lm5zMFEgRz3kXhzxzb8uzHVSrFYNieXXCQEv1GtErMKeQWuAHcwS3zxC6avTnTWJhTz3kVSXfSTYEg4MF2MBWeGrzKZ7id88ZfbpG4ZwzsDsdUCSMV6YYRNmx9P3B6oC4DL7cbi2g8hwtBdeKojY4G6JMPeg629V9sPyg2KKeYxD3cjhMKAYtrsJEbixep4LZENtdQxmgZFouJVvGy9MVhiTzGEFVwm4G25p5FhWhiS9HxHWVRXpUFHi2K9K2ttoo4Ug39V9f8s9cG1Xb5A4bHhGSuKLeCCBcrBqPWEsuLdVhjxsKJrRBJhyrZ6mpxtDhUWivZa6skmEawTts9rN2aP3dXW3cNch3s3LTXZWXG9QPUARJJPy5QAYsBoR8GunF5FFgHVuEHVpjXAd8ku9f7aoF8RNiMnXAqQHxiM3ug6HZpLHLX8aGoUbJ7vVAnEDLH"; - ASSERT_EQ(output.encoded(), expectedString); -} - -TEST(TWAnySignerSolana, SignJSON) { - auto json = STRING(R"({"recentBlockhash":"11111111111111111111111111111111","transferTransaction":{"recipient":"EN2sCsJ1WDV8UFqsiTXHcUPUxQ4juE71eCknHYYMifkd","value":"42"}})"); - Data keyData = Base58::bitcoin.decode("A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr"); - EXPECT_EQ(hex(keyData), "8778cc93c6596387e751d2dc693bbd93e434bd233bc5b68a826c56131821cb63"); - auto key = WRAPD(TWDataCreateWithBytes(keyData.data(), keyData.size())); - - auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeSolana)); - - ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeSolana)); - assertStringsEqual(result, expectedString1); -} diff --git a/tests/Solana/TWCoinTypeTests.cpp b/tests/Solana/TWCoinTypeTests.cpp deleted file mode 100644 index 5bd9998488a..00000000000 --- a/tests/Solana/TWCoinTypeTests.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - -TEST(TWSolanaCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeSolana)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("5LmxrEKGchhMuYfw6Qut6CbsvE9pVfb8YvwZKvWssSesDVjHioBCmWKSJQh1WhvcM6CpemhpHNmEMA2a36rzwTa8")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeSolana, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("Bxp8yhH9zNwxyE4UqxP7a7hgJ5xTZfxNNft7YJJ2VRjT")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeSolana, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeSolana)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeSolana)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeSolana), 9); - ASSERT_EQ(TWBlockchainSolana, TWCoinTypeBlockchain(TWCoinTypeSolana)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeSolana)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeSolana)); - assertStringsEqual(symbol, "SOL"); - assertStringsEqual(txUrl, "https://solscan.io/tx/5LmxrEKGchhMuYfw6Qut6CbsvE9pVfb8YvwZKvWssSesDVjHioBCmWKSJQh1WhvcM6CpemhpHNmEMA2a36rzwTa8"); - assertStringsEqual(accUrl, "https://solscan.io/account/Bxp8yhH9zNwxyE4UqxP7a7hgJ5xTZfxNNft7YJJ2VRjT"); - assertStringsEqual(id, "solana"); - assertStringsEqual(name, "Solana"); -} diff --git a/tests/Solana/TWSolanaAddressTests.cpp b/tests/Solana/TWSolanaAddressTests.cpp deleted file mode 100644 index 0ce32caf5db..00000000000 --- a/tests/Solana/TWSolanaAddressTests.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" - -#include -#include -#include - -#include - -TEST(TWSolanaAddress, HDWallet) { - auto mnemonic = - "shoot island position soft burden budget tooth cruel issue economy destroy above"; - auto passphrase = ""; - - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(STRING(mnemonic).get(), STRING(passphrase).get())); - - auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypeSolana, WRAPS(TWCoinTypeDerivationPath(TWCoinTypeSolana)).get())); - auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey.get())); - auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeSolana)); - auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); - - assertStringsEqual(addressStr, "2bUBiBNZyD29gP1oV6de7nxowMLoDBtopMMTGgMvjG5m"); -} - -TEST(TWSolanaProgram, defaultTokenAddress) { - const char* serumToken = "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"; - auto solanaAddress = WRAP(TWSolanaAddress, TWSolanaAddressCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V")).get())); - auto address1 = WRAPS(TWSolanaAddressDefaultTokenAddress(solanaAddress.get(), WRAPS(TWStringCreateWithUTF8Bytes(serumToken)).get())); - EXPECT_EQ(std::string(TWStringUTF8Bytes(address1.get())), "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); -} diff --git a/tests/Solana/TransactionTests.cpp b/tests/Solana/TransactionTests.cpp deleted file mode 100644 index 9ee66466647..00000000000 --- a/tests/Solana/TransactionTests.cpp +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Solana/Address.h" -#include "Solana/Transaction.h" -#include "Solana/Program.h" -#include "HexCoding.h" -#include "PublicKey.h" - -#include "BinaryCoding.h" - -#include - -using namespace TW; -using namespace TW::Solana; - -TEST(SolanaTransaction, TransferMessageData) { - auto from = Address("6eoo7i1khGhVm8tLBMAdq4ax2FxkKP4G7mCcfHyr3STN"); - auto to = Address("56B334QvCDMSirsmtEJGfanZm8GqeQarrSjdAb2MbeNM"); - Solana::Hash recentBlockhash("11111111111111111111111111111111"); - auto transaction = Transaction(from, to, 42, recentBlockhash); - - auto expectedHex = - "0100010353f9d600fe925083bb399907ea648d23a6a081fc7e9059202fd725f7edd281dd3cc1ff9ba3c7a876c8" - "082df2f8a36ea9342ce3819dd4b6fa72d4a18e04a5363a00000000000000000000000000000000000000000000" - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000010202" - "00010c020000002a00000000000000"; - ASSERT_EQ(hex(transaction.messageData()), expectedHex); -} - -TEST(SolanaTransaction, TransferSerializeTransaction) { - auto from = Address("41a5jYky56M6EWDsFfLaZRxoRtgAJSRWxJnxaJNJELn5"); - auto to = Address("4iSnyfDKaejniaPc2pBBckwQqV3mDS93go15NdxWJq2y"); - Solana::Hash recentBlockhash("11111111111111111111111111111111"); - auto transaction = Transaction(from, to, 42, recentBlockhash); - Signature signature( - "46SRiQGvtPb1iivDfnuC3dW1GzXkfQPTjdUyvFqF2sdPvFrsfx94fys2xpNKR6UiAj7RgKWdJG6mEfe85up6i1JT"); - transaction.signatures.clear(); - transaction.signatures.push_back(signature); - - auto expectedString = - "5SiHeYyuDgjHxWHbYXSSPfmYc8s7EYZ8bdZ7j15z9Bj1yyZA3Bia9uWkRdXVkuqifXiiQj6fVKy" - "7UkCL5kvv6iKrfjWTZ3szMVssTFxgJ7p8UJ7Mgg2uhHejVJvbzbiHHLbNVuJFs6kBxddnJ2yjWU" - "Cp2dYJgjmphfA8hRHHdPH4Rv6znxEhD8q9XY4nByRPL7oMCo32oxeJn5rGbUZdCkapRUXG7zU9w" - "hv6KjBktcUQZRCahhowGJT4UM5yCNCsUcqY9yan7UxqPyJgaFPuq4duqWJtQ39bTQ36X"; - ASSERT_EQ(transaction.serialize(), expectedString); -} - -TEST(SolanaTransaction, TransferTransactionPayToSelf) { - auto from = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); - auto to = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); - Solana::Hash recentBlockhash("11111111111111111111111111111111"); - auto transaction = Transaction(from, to, 42, recentBlockhash); - Signature signature( - "3CFWDEK51noPJP4v2t8JZ3qj7kC7kLKyws9akfHMyuJnQ35EtzBptHqvaHfeswiLsvUSxzMVNoj4CuRxWtDD9zB1"); - transaction.signatures.clear(); - transaction.signatures.push_back(signature); - - auto expectedString = - "EKUmihvvUPKVN4GSCFwZRtz8WiyAuPvthW69Smo19SCjcPLQ6T7EVZd1HU71WAoe1bfgmPNS5JhU7ZLA9XKG3qbZqe" - "EFJ1xmRwW9ZKw8SKMAL6VRWxp87oLu7PSmf5b8R34vCaww3XLKtZkoP49a7TUK31DqPN5xJCceMB3BZJyaojQaKU8n" - "UkzSGf89LY6abZXp9krKAebvc6bSMzTP8SHSvbmZbf3VtejmpQeN9X6e7WVDn6oDa2bGT"; - ASSERT_EQ(transaction.serialize(), expectedString); -} - -TEST(SolanaTransaction, TransferWithMemoAndReferenceTransaction) { - const auto from = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); - const auto to = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); - const Solana::Hash recentBlockhash("11111111111111111111111111111111"); - const auto memo = "HelloSolana73"; - std::vector
references = {Address("GaeTAQZyhVEocTC7iY8GztSyY5cBAJTkAUUA1kLFLMV")}; - auto transaction = Transaction(from, to, 42, recentBlockhash, memo, references); - const Signature signature("3CFWDEK51noPJP4v2t8JZ3qj7kC7kLKyws9akfHMyuJnQ35EtzBptHqvaHfeswiLsvUSxzMVNoj4CuRxWtDD9zB1"); - transaction.signatures.clear(); - transaction.signatures.push_back(signature); - - auto expectedString = "3pzQEdU38uMQgegTyRsRLi23NK4YokgZeSVLXYzFB7HShqZZH8FdBLqj6CeA2d2L8oR9KF2UaJPWbE8YBFmSdaafegoSXJtyj7ciwTjk5ieSXnPXtqH1TEcnMntZATg7gKpeFg6iehqdSUtZuQD1PGmHA1TrzzqLpRSRrc1sqPz8EpSJcQr1Y41B1XCEAfSJDfcuNKrfFrnQaVtRz6tseQfd9uXNYNuR1NQSepWdav5wQiohLUMDiZtxuwb7FQkQ68WE1FDsHmd4JpbWKmDEjz7HFyQY37vf6NBJyX5qWJpFMSg5qGKWvhNCDM32yM4A7HhPeoTWEywE5CXcNmQqdbRt4BzF1A11uqv4etWj"; - EXPECT_EQ(transaction.serialize(), expectedString); -} - -TEST(SolanaTransaction, StakeSerializeTransactionV2) { - auto signer = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); - auto voteAddress = Address("4jpwTqt1qZoR7u6u639z2AngYFGN3nakvKhowcnRZDEC"); - auto programId = Address("Stake11111111111111111111111111111111111111"); - Solana::Hash recentBlockhash("11111111111111111111111111111111"); - auto stakeAddress = StakeProgram::addressFromRecentBlockhash(signer, recentBlockhash, programId); - auto message = Message::createStake(signer, stakeAddress, voteAddress, 42, recentBlockhash); - auto transaction = Transaction(message); - Signature signature( - "2GXRrZMMWTaY8ycwFTLFojAVZ1EepFqnVGW7b5bBuuKPiVrpaPXMAwyYsSmYc2okCa1MuJjNguu1emSJRtZxVdwt"); - transaction.signatures.clear(); - transaction.signatures.push_back(signature); - - auto expectedString = "W1EAswaWK7mF4r9eZ2hHBZnfPnqLuNPiYkEMzFbwQsgSQu6XbSTL9AN92iyMbAMxPoRpt9ipUyztrmszAnm688N3k7uhiKn2osm9nxi6YkGLfu31jHTSu7mn3RtmenV3qopfPDAM92cXrzUhKGWtQ6cATeQh8i8ZfHpmjyuik7Eg3SQ4sa2543CmcozzjmTTWThThuLdvFCZJzBeRBFWLujqjbs5mA66XVtiDwsEqYByznoo4BN45XUHxnZebmPfo4hi5sf27UkhzPHik371BGxbVDexQp4y5nCEHy8ybfNCvMPLr2SEBiWSifwPkmwYN3hGCkBpqLoHCCiRcyJuRHW8hSDFR4JPQ3Xe3FGfpgbayaawZigUnFuPGSpoGrURZRoLCzc6V4ApqcJbmzFhg5zJz2yTX5GvQSYWLFnTKbPYcgBNpdyMLJTivonrKtgkfdymZVKjDwnHUApC7WD4L9mqzTf1dzR61Fxhu3Rdh8ECiVEDgB1wkWZWkTKEdANmtaYLKCMUs3n4VhuZbSFLEiTg7yRWM2pjBgiBB4qywbF7SE75UtzSFCaDnn27mKkxRBqZEGEgfpEoK2AxjsiCZEZxfLeyZFbwWe7xasmNiXr6CnAQhwsmxJk79h7SYmaje76JLxHVX5gbQmLfn5bc1xthS3YhteSovQ8xYq1jiHCfsXRwbxKrNA4kVMiSa6spoU9AhFL8cDAZjAqoU4YRwBihZVhXSFCRnYAK8FabzEv1M44EeHX1sfMG8T1U7y3DEjom7jv6rqZfLumWpbXDTqanB7zTbTjGyDcBBf21edjpZzBZ7osS5fTVYJ5mZBSvjjhuGkUgZZWgYozAKvdyyrJH6UdcPvNm2XgMRYJxqyCin1zhCeQ25vK1H8Jj"; - EXPECT_EQ(transaction.serialize(), expectedString); -} - -TEST(SolanaTransaction, StakeSerializeTransactionV1) { - auto signer = Address("zVSpQnbBZ7dyUWzXhrUQRsTYYNzoAdJWHsHSqhPj3Xu"); - auto voteAddress = Address("4jpwTqt1qZoR7u6u639z2AngYFGN3nakvKhowcnRZDEC"); - auto programId = Address("Stake11111111111111111111111111111111111111"); - Solana::Hash recentBlockhash("11111111111111111111111111111111"); - auto stakeAddress = StakeProgram::addressFromValidatorSeed(signer, voteAddress, programId); - auto message = Message::createStake(signer, stakeAddress, voteAddress, 42, recentBlockhash); - auto transaction = Transaction(message); - Signature signature( - "2GXRrZMMWTaY8ycwFTLFojAVZ1EepFqnVGW7b5bBuuKPiVrpaPXMAwyYsSmYc2okCa1MuJjNguu1emSJRtZxVdwt"); - transaction.signatures.clear(); - transaction.signatures.push_back(signature); - - auto expectedString = "W1EAswaWK7mF4r9eZ2hHBZnfPnqLuNPiYkEMzFbwQsgSQu6XbSTL9AN92iyMbAMxPoRpt9ipUyztrmszAnm688N3k7uhiKn2osm9nxi6YkGLfu31jHTSu7mn3RtmenV3qopfPDAM7jtGoYQFb7eFVbujUb6tbeQ9UqLJq1sJ7uMZ4wqecmQPouDmJnpmJk4CHMzLnPNTwyGmGio6sYAS3xKZ7DFXvjwGPuD8PyYHSfdPro1p3jy9igPZNAbQ6fgK7LL3sERKCUdvPy7k14xgHbtsVy2mu54LY5c8F9sFst2uzQiTsXRTdjPFAyCVwB5pccNVotCrJ6Q2aKSC2D2knVH7LgWzSBMSreJG75xyATneu922wSzz7QJDieqhDtdePtSbPtoCdtPNmDfdaeDbHxVAxMios9F7RSRmH2dq86NfWDvF8TuEbYY7gPnygz6jGvwfqSSoSnY8TnUhhceC7wJSMc8Hcf1kyfi8dqKm7rF57YjnrQoMmL5bWqJLKoJtdfFu24ceQN21k38U2tUMWJaBASWukgTJUbNSCemNPZt4P3cNbeB3L1wBj4GEYXVTbTFYKME5JscU5RsnkMJZZ1PgxSi63HT4hwQLok4c18UdJgzMFu1njpZj3Sw76mwV3ea7ruHnP4yyM3YhUGbNjpx5fAcnvdLcXChdsgeUpJhutME6V86Rk2EEskoJeD3qNWi3hvfQx172hZRHyKyr29Ts1uLQxcMJq7oeQUxvTfXxSe6cBuPJUDFkAET3qpS7rWM7rvQQ8rDLQF5QvcJnrYTq12pVgw28WXdgi45811a7DWHGuwHRj5FJdLQAHkKe4EXVeTCdbYHREVwuyTJgAvb8SXjRE5a9n3qpRDr7iEd5UDZKB5HgvMsMYWh5"; - ASSERT_EQ(transaction.serialize(), expectedString); -} - -TEST(SolanaTransaction, CreateTokenAccountTransaction) { - auto signer = Address("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); - auto token = Address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); - auto tokenAddress = Address("EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); - Solana::Hash recentBlockhash("9ipJh5xfyoyDaiq8trtrdqQeAhQbQkWy2eANizKvx75K"); - auto message = Message::createTokenCreateAccount(signer, signer, token, tokenAddress, recentBlockhash); - EXPECT_EQ(message.header.numRequiredSignatures, 1); - EXPECT_EQ(message.header.numCreditOnlySignedAccounts, 0); - EXPECT_EQ(message.header.numCreditOnlyUnsignedAccounts, 5); - ASSERT_EQ(message.accountKeys.size(), 7); - EXPECT_EQ(message.accountKeys[0].string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); - EXPECT_EQ(message.accountKeys[1].string(), "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); - EXPECT_EQ(message.accountKeys[2].string(), "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); - EXPECT_EQ(message.accountKeys[3].string(), "11111111111111111111111111111111"); - EXPECT_EQ(message.accountKeys[4].string(), "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); - EXPECT_EQ(message.accountKeys[5].string(), "SysvarRent111111111111111111111111111111111"); - EXPECT_EQ(message.accountKeys[6].string(), "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); - EXPECT_EQ(Base58::bitcoin.encode(message.recentBlockhash.bytes), "9ipJh5xfyoyDaiq8trtrdqQeAhQbQkWy2eANizKvx75K"); - ASSERT_EQ(message.instructions.size(), 1); - EXPECT_EQ(message.instructions[0].programId.string(), "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); - ASSERT_EQ(message.instructions[0].accounts.size(), 7); - EXPECT_EQ(message.instructions[0].accounts[0].account.string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); - EXPECT_EQ(message.instructions[0].accounts[1].account.string(), "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); - EXPECT_EQ(message.instructions[0].accounts[2].account.string(), "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); - EXPECT_EQ(message.instructions[0].accounts[3].account.string(), "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); - EXPECT_EQ(message.instructions[0].accounts[4].account.string(), "11111111111111111111111111111111"); - EXPECT_EQ(message.instructions[0].accounts[5].account.string(), "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); - EXPECT_EQ(message.instructions[0].accounts[6].account.string(), "SysvarRent111111111111111111111111111111111"); - auto transaction = Transaction(message); - transaction.signatures.clear(); - Signature signature("3doYbPs5rES3TeDSrntqUvMgXCDE2ViJX2SFhLtiptVNkqPuixXs1SwU5LUZ3KwHnCzDUth6BRr3vU3gqnuUgRvQ"); - transaction.signatures.push_back(signature); - - auto expectedString = - // test data obtained from spl-token create-account - "CKzRLx3AQeVeLQ7T4hss2rdbUpuAHdbwXDazxtRnSKBuncCk3WnYgy7XTrEiya19MJviYHYdTxi9gmWJY8qnR2vHVnH2DbPiKA8g72rD3VvMnjosGUBBvCwbBLge6FeQdgczMyRo9n5PcHvg9yJBTJaEEvuewyBVHwCGyGQci7eYd26xtZtCjAjwcTq4gGr3NZbeRW6jZp6j6APuew7jys4MKYRV4xPodua1TZFCkyWZr1XKzmPh7KTavtN5VzPDA8rbsvoEjHnKzjB2Bszs6pDjcBFSHyQqGsHoF8XPD35BLfjDghNtBmf9cFqo5axa6oSjANAuYg6cMSP4Hy28waSj8isr6gQjE315hWi3W1swwwPcn322gYZx6aMAcmjczaxX9aktpHYgZxixF7cYWEHxJs5QUK9mJePu9Xc6yW75UB4Ynx6dUgaSTEUzoQthF2TN3xXwu1"; - EXPECT_EQ(transaction.serialize(), expectedString); -} - -TEST(SolanaTransaction, TransferTokenTransaction_3vZ67C) { - auto signer = Address("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); - auto token = Address("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); - auto senderTokenAddress = Address("EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); - auto recipientTokenAddress = Address("3WUX9wASxyScbA7brDipioKfXS1XEYkQ4vo3Kej9bKei"); - uint64_t amount = 4000; - uint8_t decimals = 6; - Solana::Hash recentBlockhash("CNaHfvqePgGYMvtYi9RuUdVxDYttr1zs4TWrTXYabxZi"); - auto message = Message::createTokenTransfer(signer, token, senderTokenAddress, recipientTokenAddress, amount, decimals, recentBlockhash); - EXPECT_EQ(message.header.numRequiredSignatures, 1); - EXPECT_EQ(message.header.numCreditOnlySignedAccounts, 0); - EXPECT_EQ(message.header.numCreditOnlyUnsignedAccounts, 2); - ASSERT_EQ(message.accountKeys.size(), 5); - ASSERT_EQ(message.instructions.size(), 1); - EXPECT_EQ(message.instructions[0].programId.string(), "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); - ASSERT_EQ(message.instructions[0].accounts.size(), 4); - auto transaction = Transaction(message); - transaction.signatures.clear(); - Signature signature("3vZ67CGoRYkuT76TtpP2VrtTPBfnvG2xj6mUTvvux46qbnpThgQDgm27nC3yQVUZrABFjT9Qo7vA74tCjtV5P9Xg"); - transaction.signatures.push_back(signature); - - auto expectedString = - // https://explorer.solana.com/tx/3vZ67CGoRYkuT76TtpP2VrtTPBfnvG2xj6mUTvvux46qbnpThgQDgm27nC3yQVUZrABFjT9Qo7vA74tCjtV5P9Xg - // test data obtained from spl-token transfer - "PGfKqEaH2zZXDMZLcU6LUKdBSzU1GJWJ1CJXtRYCxaCH7k8uok38WSadZfrZw3TGejiau7nSpan2GvbK26hQim24jRe2AupmcYJFrgsdaCt1Aqs5kpGjPqzgj9krgxTZwwob3xgC1NdHK5BcNwhxwRtrCphGEH7zUFpGFrFrHzgpf2KY8FvPiPELQyxzTBuyNtjLjMMreehSKShEjD9Xzp1QeC1pEF8JL6vUKzxMXuveoEYem8q8JiWszYzmTMfDk13JPgv7pXFGMqDV3yNGCLsWccBeSFKN4UKECre6x2QbUEiKGkHkMc4zQwwyD8tGmEMBAGm339qdANssEMNpDeJp2LxLDStSoWShHnotcrH7pUa94xCVvCPPaomF"; - EXPECT_EQ(transaction.serialize(), expectedString); -} diff --git a/tests/Stellar/AddressTests.cpp b/tests/Stellar/AddressTests.cpp deleted file mode 100644 index 9fc5dc06b19..00000000000 --- a/tests/Stellar/AddressTests.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Stellar/Address.h" -#include "Bitcoin/Address.h" -#include "HexCoding.h" -#include "PrivateKey.h" - -#include - -using namespace std; -using namespace TW; -using namespace TW::Stellar; - -TEST(StellarAddress, FromPublicKey) { - const auto publicKey = PublicKey(parse_hex("0103E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D"), TWPublicKeyTypeED25519); - const auto address = Address(publicKey); - auto str = hex(address.bytes); - ASSERT_EQ(string("GAB6EDWGWSRZUYUYCWXAFQFBHE5ZEJPDXCIMVZC3LH2C7IU35FTI2NOQ"), address.string()); -} - -TEST(StellarAddress, FromString) { - string stellarAddress = "GAB6EDWGWSRZUYUYCWXAFQFBHE5ZEJPDXCIMVZC3LH2C7IU35FTI2NOQ"; - const auto address = Address(stellarAddress); - ASSERT_EQ(address.string(), stellarAddress); -} - -TEST(StellarAddress, isValid) { - string stellarAddress = "GABQHYQOY22KHGTCTAK24AWAUE4TXERF4O4JBSXELNM7IL5CTPUWM3SC"; - string bitcoinAddress = "1Ma2DrB78K7jmAwaomqZNRMCvgQrNjE2QC"; - - ASSERT_TRUE(Address::isValid(stellarAddress)); - ASSERT_FALSE(Address::isValid(bitcoinAddress)); -} diff --git a/tests/Stellar/TWAnySignerTests.cpp b/tests/Stellar/TWAnySignerTests.cpp deleted file mode 100644 index b05c73c459c..00000000000 --- a/tests/Stellar/TWAnySignerTests.cpp +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" - -#include "HexCoding.h" -#include "PrivateKey.h" -#include "Stellar/Address.h" -#include "proto/Stellar.pb.h" -#include -#include -#include - -using namespace TW; -using namespace TW::Stellar; - -TEST(TWAnySingerStellar, Sign_Payment) { - auto key = parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722"); - Proto::SigningInput input; - input.set_passphrase(TWStellarPassphrase_Stellar); - input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); - input.set_fee(1000); - input.set_sequence(2); - input.mutable_op_payment()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); - input.mutable_op_payment()->set_amount(10000000); - input.set_private_key(key.data(), key.size()); - auto& memoText = *input.mutable_memo_text(); - memoText.set_text("Hello, world!"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeStellar); - - EXPECT_EQ(output.signature(), "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAEAAAANSGVsbG8sIHdvcmxkIQAAAAAAAAEAAAAAAAAAAQAAAADFgLYxeg6zm/f81Po8Gf2rS4m7q79hCV7kUFr27O16rgAAAAAAAAAAAJiWgAAAAAAAAAABGd9cogAAAEBQQldEkYJ6rMvOHilkwFCYyroGGUvrNeWVqr/sn3iFFqgz91XxgUT0ou7bMSPRgPROfBYDfQCFfFxbcDPrrCwB"); -} - -TEST(TWAnySingerStellar, Sign_Payment_66b5) { - auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); - PrivateKey privKey = PrivateKey(key); - PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); - Address addr = Address(pubKey); - EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); - - Proto::SigningInput input; - input.set_passphrase(TWStellarPassphrase_Stellar); - input.set_account("GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); - input.set_fee(1000); - input.set_sequence(144098454883270657); - input.mutable_op_payment()->set_destination("GA3ISGYIE2ZTH3UAKEKBVHBPKUSL3LT4UQ6C5CUGP2IM5F467O267KI7"); - input.mutable_op_payment()->set_amount(1000000); - input.set_private_key(key.data(), key.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeStellar); - - // curl "https://horizon.stellar.org/transactions/66b5bca4b4293bdd85a6a559b08918482774b76bcc170b4533411f1d6422ce24" - EXPECT_EQ(output.signature(), "AAAAAMpFJQVVMv16RJUPlzQUTlgZOHVurhw3igGacP1305F1AAAD6AH/8MgAAAABAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAANokbCCazM+6AURQanC9VJL2ufKQ8LoqGfpDOl577te8AAAAAAAAAAAAPQkAAAAAAAAAAAXfTkXUAAABAM9Nhzr8iWKzqnHknrxSVoa4b2qzbTzgyE2+WWxg6XHH50xiFfmvtRKVhzp0Jg8PfhatOb6KNheKRWEw4OvqEDw=="); -} - -TEST(TWAnySingerStellar, Sign_Payment_Asset_ea50) { - auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); - PrivateKey privKey = PrivateKey(key); - PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); - Address addr = Address(pubKey); - EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); - - Proto::SigningInput input; - input.set_passphrase(TWStellarPassphrase_Stellar); - input.set_account("GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); - input.set_fee(1000); - input.set_sequence(144098454883270661); - input.mutable_op_payment()->set_destination("GA3ISGYIE2ZTH3UAKEKBVHBPKUSL3LT4UQ6C5CUGP2IM5F467O267KI7"); - input.mutable_op_payment()->mutable_asset()->set_issuer("GA6HCMBLTZS5VYYBCATRBRZ3BZJMAFUDKYYF6AH6MVCMGWMRDNSWJPIH"); - input.mutable_op_payment()->mutable_asset()->set_alphanum4("MOBI"); - input.mutable_op_payment()->set_amount(12000000); - input.set_private_key(key.data(), key.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeStellar); - - // curl "https://horizon.stellar.org/transactions/ea50884cd1288d2d5420065995d13d750d812258e0e79280c4033a434e625c99 - EXPECT_EQ(output.signature(), "AAAAAMpFJQVVMv16RJUPlzQUTlgZOHVurhw3igGacP1305F1AAAD6AH/8MgAAAAFAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAANokbCCazM+6AURQanC9VJL2ufKQ8LoqGfpDOl577te8AAAABTU9CSQAAAAA8cTArnmXa4wEQJxDHOw5SwBaDVjBfAP5lRMNZkRtlZAAAAAAAtxsAAAAAAAAAAAF305F1AAAAQEuWZZvKZuF6SMuSGIyfLqx5sn5O55+Kd489uP4g9jZH4UE7zZ4ME0+74I0BU8YDsYOmmxcfp/vdwTd+n3oGCQw="); -} - -TEST(TWAnySingerStellar, Sign_Change_Trust_ad9c) { - auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); - PrivateKey privKey = PrivateKey(key); - PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); - Address addr = Address(pubKey); - EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); - - Proto::SigningInput input; - input.set_passphrase(TWStellarPassphrase_Stellar); - input.set_account("GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); - input.set_fee(10000); - input.set_sequence(144098454883270659); - input.mutable_op_change_trust()->mutable_asset()->set_issuer("GA6HCMBLTZS5VYYBCATRBRZ3BZJMAFUDKYYF6AH6MVCMGWMRDNSWJPIH"); - input.mutable_op_change_trust()->mutable_asset()->set_alphanum4("MOBI"); - input.mutable_op_change_trust()->set_valid_before(1613336576); - input.set_private_key(key.data(), key.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeStellar); - - // curl "https://horizon.stellar.org/transactions/ad9cd0f3d636096b6502ccae07adbcf2cd3c0da5393fc2b07813dbe90ecc0d7b" - EXPECT_EQ(output.signature(), "AAAAAMpFJQVVMv16RJUPlzQUTlgZOHVurhw3igGacP1305F1AAAnEAH/8MgAAAADAAAAAQAAAAAAAAAAAAAAAGApkAAAAAAAAAAAAQAAAAAAAAAGAAAAAU1PQkkAAAAAPHEwK55l2uMBECcQxzsOUsAWg1YwXwD+ZUTDWZEbZWR//////////wAAAAAAAAABd9ORdQAAAEAnfyXyaNQX5Bq3AEQVBIaYd+cLib+y2sNY7DF/NYVSE51dZ6swGGElz094ObsPefmVmeRrkGsSc/fF5pmth+wJ"); -} - -TEST(TWAnySingerStellar, Sign_Change_Trust_2) { - auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); - PrivateKey privKey = PrivateKey(key); - PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); - Address addr = Address(pubKey); - EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); - - Proto::SigningInput input; - input.set_passphrase(TWStellarPassphrase_Stellar); - input.set_account("GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); - input.set_fee(10000); - input.set_sequence(144098454883270659); - input.mutable_op_change_trust()->mutable_asset()->set_issuer("GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX"); - input.mutable_op_change_trust()->mutable_asset()->set_alphanum4("USD"); - input.mutable_op_change_trust()->set_valid_before(1613336576); - input.set_private_key(key.data(), key.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeStellar); - - EXPECT_EQ(output.signature(), "AAAAAMpFJQVVMv16RJUPlzQUTlgZOHVurhw3igGacP1305F1AAAnEAH/8MgAAAADAAAAAQAAAAAAAAAAAAAAAGApkAAAAAAAAAAAAQAAAAAAAAAGAAAAAVVTRAAAAAAA6KYahh5gr2D4B3PgY0blxyy+Wdyt2jdgjVjvQlEdn9x//////////wAAAAAAAAABd9ORdQAAAEDMZtN05ZsZB4OKOZSFkQvuRqDIvMME3PYMTAGJPQlO6Ee0nOtaRn2q0uf0IhETSSfqcsK5asAZzNj07tG0SPwM"); -} - -TEST(TWAnySingerStellar, Sign_Create_Claimable_Balance_1f1f84) { - auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); - PrivateKey privKey = PrivateKey(key); - PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); - Address addr = Address(pubKey); - EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); - - Proto::SigningInput input; - input.set_passphrase(TWStellarPassphrase_Stellar); - input.set_account("GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); - input.set_fee(10000); - input.set_sequence(144098454883270687); - input.mutable_op_create_claimable_balance()->set_amount(90000000); - input.mutable_op_create_claimable_balance()->add_claimants(); - input.mutable_op_create_claimable_balance()->mutable_claimants(0)->set_account("GC6CJDAY54D3O4RHEH33LUTBKDZGVOTR6NHBOTL4PIWI2CDKVRSZZJGJ"); - input.mutable_op_create_claimable_balance()->mutable_claimants(0)->set_predicate(Proto::Predicate_unconditional); - input.set_private_key(key.data(), key.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeStellar); - - // https://stellar.expert/explorer/public/tx/1f1f849ff2560901c91226f2fc866ef4ed1c67d672262c1f5829abe2348ac638 - // curl -X POST -F "tx=AAAAAMpF..Bg==" "https://horizon.stellar.org/transactions" - EXPECT_EQ(output.signature(), "AAAAAMpFJQVVMv16RJUPlzQUTlgZOHVurhw3igGacP1305F1AAAnEAH/8MgAAAAfAAAAAAAAAAAAAAABAAAAAAAAAA4AAAAAAAAAAAVdSoAAAAABAAAAAAAAAAC8JIwY7we3cich97XSYVDyarpx804XTXx6LI0IaqxlnAAAAAAAAAAAAAAAAXfTkXUAAABAgms/HPhEP/EYtVr5aWwhKJsn3pIVEZGFnTD2Xd/VPVsn8qogI7RYyjyBxSFPiLAljgGsPaUMfU3WFvyJCWNwBg=="); -} - -TEST(TWAnySingerStellar, Sign_Claim_Claimable_Balance_c1fb3c) { - auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); - PrivateKey privKey = PrivateKey(key); - PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); - Address addr = Address(pubKey); - EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); - - Proto::SigningInput input; - input.set_passphrase(TWStellarPassphrase_Stellar); - input.set_account("GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); - input.set_fee(10000); - input.set_sequence(144098454883270689); - const Data balanceIdHash = parse_hex("9c7b794b7b150f3e4c6dcfa260672bbe0c248b360129112e927e0f7ee2f9faf8"); - input.mutable_op_claim_claimable_balance()->set_balance_id(balanceIdHash.data(), balanceIdHash.size()); - input.set_private_key(key.data(), key.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeStellar); - - // https://stellar.expert/explorer/public/tx/c1fb3cf348aeb72bb2e1030c1d7f7f9c6c6d1bbab071b3e7c7c1cadafa795e8e - // curl -X POST -F "tx=AAAAAMpF..DQ==" "https://horizon.stellar.org/transactions" - EXPECT_EQ(output.signature(), "AAAAAMpFJQVVMv16RJUPlzQUTlgZOHVurhw3igGacP1305F1AAAnEAH/8MgAAAAhAAAAAAAAAAAAAAABAAAAAAAAAA8AAAAAnHt5S3sVDz5Mbc+iYGcrvgwkizYBKREukn4PfuL5+vgAAAAAAAAAAXfTkXUAAABAWL7dKkR1JuPZGFbDTRDgGBHW/vLPMWNRkAew+wPfGiCnZhpJJDcyX197EDDZMsJ7ungPUyhczRaeQOwZKx4DDQ=="); - - { // negative test: hash wrong size - const Data invalidBalanceIdHash = parse_hex("010203"); - input.mutable_op_claim_claimable_balance()->set_balance_id(invalidBalanceIdHash.data(), invalidBalanceIdHash.size()); - ANY_SIGN(input, TWCoinTypeStellar); - EXPECT_EQ(output.signature(), "AAAAAXfTkXUAAABAFCywEfLs3q5Tv9eZCIcjhkJR0s8J4Us9G5YjVKUSaMoUz/AadC8dM2oQSLhpC5wjrNBi7hevg7jlkPx5/4AJCQ=="); - } -} diff --git a/tests/Stellar/TWCoinTypeTests.cpp b/tests/Stellar/TWCoinTypeTests.cpp deleted file mode 100644 index 12164f9b2d6..00000000000 --- a/tests/Stellar/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWStellarCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeStellar)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("d9aeabfa9d24df8c5755125f8af243b74cd3ff878656cfa72c566a8824bf6e84")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeStellar, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("GCILJZQ3CKBKBUJWW4TAM6Q37LJA5MQX6GMSFSQN75BPLWIZ33OPRG52")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeStellar, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeStellar)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeStellar)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeStellar), 7); - ASSERT_EQ(TWBlockchainStellar, TWCoinTypeBlockchain(TWCoinTypeStellar)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeStellar)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeStellar)); - assertStringsEqual(symbol, "XLM"); - assertStringsEqual(txUrl, "https://blockchair.com/stellar/transaction/d9aeabfa9d24df8c5755125f8af243b74cd3ff878656cfa72c566a8824bf6e84"); - assertStringsEqual(accUrl, "https://blockchair.com/stellar/account/GCILJZQ3CKBKBUJWW4TAM6Q37LJA5MQX6GMSFSQN75BPLWIZ33OPRG52"); - assertStringsEqual(id, "stellar"); - assertStringsEqual(name, "Stellar"); -} diff --git a/tests/Stellar/TransactionTests.cpp b/tests/Stellar/TransactionTests.cpp deleted file mode 100644 index d9324c4c339..00000000000 --- a/tests/Stellar/TransactionTests.cpp +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" - -#include "Stellar/Address.h" -#include "Stellar/Signer.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include -#include -#include -#include "BinaryCoding.h" - -#include - -using namespace std; -using namespace TW; -using namespace TW::Stellar; - -TEST(StellarTransaction, sign) { - auto words = STRING("indicate rival expand cave giant same grocery burden ugly rose tuna blood"); - auto passphrase = STRING(""); - - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); - auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeStellar)); - auto input = TW::Stellar::Proto::SigningInput(); - input.set_passphrase(TWStellarPassphrase_Stellar); - input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); - input.set_fee(1000); - input.set_sequence(2); - input.mutable_op_payment()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); - input.mutable_op_payment()->set_amount(10000000); - input.set_private_key(privateKey.get()->impl.bytes.data(), privateKey.get()->impl.bytes.size()); - - const auto signer = TW::Stellar::Signer(input); - - const auto signature = signer.sign(); - ASSERT_EQ(signature, "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAxYC2MXoOs5v3/NT6PBn9q0uJu6u/YQle5FBa9uzteq4AAAAAAAAAAACYloAAAAAAAAAAARnfXKIAAABAocQZwTnVvGMQlpdGacWvgenxN5ku8YB8yhEGrDfEV48yDqcj6QaePAitDj/N2gxfYD9Q2pJ+ZpkQMsZZG4ACAg=="); -} - -TEST(StellarTransaction, signWithMemoText) { - auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); - auto input = Proto::SigningInput(); - input.set_passphrase(TWStellarPassphrase_Stellar); - input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); - input.set_fee(1000); - input.set_sequence(2); - auto memoText = Proto::MemoText(); - memoText.set_text("Hello, world!"); - *input.mutable_memo_text() = memoText; - input.mutable_op_payment()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); - input.mutable_op_payment()->set_amount(10000000); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto signer = Signer(input); - - const auto signature = signer.sign(); - ASSERT_EQ(signature, "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAEAAAANSGVsbG8sIHdvcmxkIQAAAAAAAAEAAAAAAAAAAQAAAADFgLYxeg6zm/f81Po8Gf2rS4m7q79hCV7kUFr27O16rgAAAAAAAAAAAJiWgAAAAAAAAAABGd9cogAAAEBQQldEkYJ6rMvOHilkwFCYyroGGUvrNeWVqr/sn3iFFqgz91XxgUT0ou7bMSPRgPROfBYDfQCFfFxbcDPrrCwB"); -} - -TEST(StellarTransaction, signWithMemoHash) { - auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); - auto input = Proto::SigningInput(); - input.set_passphrase(TWStellarPassphrase_Stellar); - input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); - input.set_fee(1000); - input.set_sequence(2); - auto memoHash = Proto::MemoHash(); - auto fromHex = parse_hex("315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3"); - memoHash.set_hash(fromHex.data(), fromHex.size()); - *input.mutable_memo_hash() = memoHash; - input.mutable_op_payment()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); - input.mutable_op_payment()->set_amount(10000000); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto signer = Signer(input); - - const auto signature = signer.sign(); - ASSERT_EQ(signature, "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAMxX1vbdtB4xDuKwAZOSgFkYSsfznfIaTRb/JTHWJTt0wAAAAEAAAAAAAAAAQAAAADFgLYxeg6zm/f81Po8Gf2rS4m7q79hCV7kUFr27O16rgAAAAAAAAAAAJiWgAAAAAAAAAABGd9cogAAAECIyh1BG+hER5W+dgHDKe49X6VEYRWIjajM4Ufq3DUG/yw7Xv1MMF4eax3U0TRi7Qwj2fio/DRD3+/Ljtvip2MD"); -} - -TEST(StellarTransaction, signWithMemoReturn) { - auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); - auto input = Proto::SigningInput(); - input.set_passphrase(TWStellarPassphrase_Stellar); - input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); - input.set_fee(1000); - input.set_sequence(2); - auto memoHash = Proto::MemoHash(); - auto fromHex = parse_hex("315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3"); - memoHash.set_hash(fromHex.data(), fromHex.size()); - *input.mutable_memo_return_hash() = memoHash; - input.mutable_op_payment()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); - input.mutable_op_payment()->set_amount(10000000); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto signer = Signer(input); - - const auto signature = signer.sign(); - ASSERT_EQ(signature, "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAQxX1vbdtB4xDuKwAZOSgFkYSsfznfIaTRb/JTHWJTt0wAAAAEAAAAAAAAAAQAAAADFgLYxeg6zm/f81Po8Gf2rS4m7q79hCV7kUFr27O16rgAAAAAAAAAAAJiWgAAAAAAAAAABGd9cogAAAEBd77iui04quoaoWMfeJO06nRfn3Z9bptbAj7Ol44j3ApU8c9dJwVhJbQ7La4mKgIkYviEhGx3AIulFYCkokb8M"); -} - -TEST(StellarTransaction, signWithMemoID) { - auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); - auto input = Proto::SigningInput(); - input.set_passphrase(TWStellarPassphrase_Stellar); - input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); - input.set_fee(1000); - input.set_sequence(2); - auto memoId = Proto::MemoId(); - memoId.set_id(1234567890); - *input.mutable_memo_id() = memoId; - input.mutable_op_payment()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); - input.mutable_op_payment()->set_amount(10000000); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto signer = Signer(input); - - const auto signature = signer.sign(); - ASSERT_EQ(signature, "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAIAAAAASZYC0gAAAAEAAAAAAAAAAQAAAADFgLYxeg6zm/f81Po8Gf2rS4m7q79hCV7kUFr27O16rgAAAAAAAAAAAJiWgAAAAAAAAAABGd9cogAAAEAOJ8wwCizQPf6JmkCsCNZolQeqet2qN7fgLUUQlwx3TNzM0+/GJ6Qc2faTybjKy111rE60IlnfaPeMl/nyxKIB"); -} - -TEST(StellarTransaction, signAcreateAccount) { - auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); - auto input = Proto::SigningInput(); - input.set_passphrase(TWStellarPassphrase_Stellar); - input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); - input.set_fee(1000); - input.set_sequence(2); - auto memoId = Proto::MemoId(); - memoId.set_id(1234567890); - *input.mutable_memo_id() = memoId; - input.mutable_op_create_account()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); - input.mutable_op_create_account()->set_amount(10000000); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto signer = Signer(input); - - const auto signature = signer.sign(); - ASSERT_EQ(signature, "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAIAAAAASZYC0gAAAAEAAAAAAAAAAAAAAADFgLYxeg6zm/f81Po8Gf2rS4m7q79hCV7kUFr27O16rgAAAAAAmJaAAAAAAAAAAAEZ31yiAAAAQNgqNDqbe0X60gyH+1xf2Tv2RndFiJmyfbrvVjsTfjZAVRrS2zE9hHlqPQKpZkGKEFka7+1ElOS+/m/1JDnauQg="); -} diff --git a/tests/THORChain/SignerTests.cpp b/tests/THORChain/SignerTests.cpp deleted file mode 100644 index c283cae8bf2..00000000000 --- a/tests/THORChain/SignerTests.cpp +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "proto/Cosmos.pb.h" -#include "THORChain/Signer.h" -#include "HexCoding.h" -#include "Bech32Address.h" -#include "../interface/TWTestUtilities.h" - -#include -#include - -using namespace TW; - - -TEST(THORChainSigner, SignTx_Protobuf_7E480F) { - auto input = Cosmos::Proto::SigningInput(); - input.set_signing_mode(Cosmos::Proto::Protobuf); - input.set_chain_id("thorchain-mainnet-v1"); - input.set_account_number(593); - input.set_sequence(21); - input.set_memo(""); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_thorchain_send_message(); - Bech32Address fromAddress("thor"); - EXPECT_TRUE(Bech32Address::decode("thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", fromAddress, "thor")); - Bech32Address toAddress("thor"); - EXPECT_TRUE(Bech32Address::decode("thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn", toAddress, "thor")); - message.set_from_address(std::string(fromAddress.getKeyHash().begin(), fromAddress.getKeyHash().end())); - message.set_to_address(std::string(toAddress.getKeyHash().begin(), toAddress.getKeyHash().end())); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("rune"); - amountOfTx->set_amount("38000000"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(2500000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("rune"); - amountOfFee->set_amount("200"); - - std::string json; - google::protobuf::util::MessageToJsonString(input, &json); - - assertJSONEqual(json, R"( - { - "accountNumber": "593", - "chainId": "thorchain-mainnet-v1", - "fee": { - "amounts": [ - { - "amount": "200", - "denom": "rune" - } - ], - "gas": "2500000" - }, - "messages": [ - { - "thorchainSendMessage": { - "amounts": [ - { - "amount": "38000000", - "denom": "rune" - } - ], - "fromAddress": "FSLnZ9tusZcIsAOAKb+9YHvJvQ4=", - "toAddress": "yoZFn7AFUcffQlQMXnhpGSyDOts=" - } - } - ], - "sequence": "21", - "signingMode": "Protobuf" - } - )"); - - auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = THORChain::Signer::sign(input); - - // https://viewblock.io/thorchain/tx/7E480FA163F6C6AFA17593F214C7BBC218F69AE3BC72366E39042AF381BFE105 - // curl -H 'Content-Type: application/json' --data-binary '{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"ClIKUAoO..89g="}' https:///cosmos/tx/v1beta1/txs - assertJSONEqual(output.serialized(), R"( - { - "mode": "BROADCAST_MODE_BLOCK", - "tx_bytes": "ClIKUAoOL3R5cGVzLk1zZ1NlbmQSPgoUFSLnZ9tusZcIsAOAKb+9YHvJvQ4SFMqGRZ+wBVHH30JUDF54aRksgzrbGhAKBHJ1bmUSCDM4MDAwMDAwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQPtmX45bPQpL1/OWkK7pBWZzNXZbjExVKfJ6nBJ3jF8dxIECgIIARgVEhIKCwoEcnVuZRIDMjAwEKDLmAEaQKZtS3ATa26OOGvqdKm14ZbHeNfkPtIajXi5MkZ5XaX2SWOeX+YnCPZ9TxF9Jj5cVIo71m55xq4hVL3yDbRe89g=" - } - )"); - EXPECT_EQ(hex(output.signature()), "a66d4b70136b6e8e386bea74a9b5e196c778d7e43ed21a8d78b93246795da5f649639e5fe62708f67d4f117d263e5c548a3bd66e79c6ae2154bdf20db45ef3d8"); - EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); -} - -TEST(THORChainSigner, SignTx_Json_Deprecated) { - auto input = Cosmos::Proto::SigningInput(); - input.set_memo("memo1234"); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_send_coins_message(); - message.set_from_address("thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r"); - message.set_to_address("thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("rune"); - amountOfTx->set_amount("50000000"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(2000000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("rune"); - amountOfFee->set_amount("200"); - - std::string json; - google::protobuf::util::MessageToJsonString(input, &json); - - assertJSONEqual(json, R"( - { - "fee": { - "amounts": [ - { - "denom": "rune", - "amount": "200" - } - ], - "gas": "2000000" - }, - "memo": "memo1234", - "messages": [ - { - "sendCoinsMessage": { - "fromAddress": "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", - "toAddress": "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn", - "amounts": [ - { - "denom": "rune", - "amount": "50000000" - } - ] - } - } - ] - } - )"); - - auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = THORChain::Signer::sign(input); - - assertJSONEqual(output.json(), R"( - { - "mode": "block", - "tx": { - "fee": { - "amount": [ - { - "amount": "200", - "denom": "rune" - } - ], - "gas": "2000000" - }, - "memo": "memo1234", - "msg": [ - { - "type": "thorchain/MsgSend", - "value": { - "amount": [ - { - "amount": "50000000", - "denom": "rune" - } - ], - "from_address": "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", - "to_address": "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn" - } - } - ], - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3" - }, - "signature": "12AaNC0v51Rhz8rBf7V7rpI6oksREWrjzba3RK1v1NNlqZq62sG0aXWvStp9zZXe07Pp2FviFBAx+uqWsO30NQ==" - } - ] - } - } - )"); - EXPECT_EQ(hex(output.signature()), "d7601a342d2fe75461cfcac17fb57bae923aa24b11116ae3cdb6b744ad6fd4d365a99abadac1b46975af4ada7dcd95ded3b3e9d85be2141031faea96b0edf435"); -} - -TEST(THORChainSigner, SignJson) { - auto inputJson = R"({"fee":{"amounts":[{"denom":"rune","amount":"200"}],"gas":"2000000"},"memo":"memo1234","messages":[{"sendCoinsMessage":{"fromAddress":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","toAddress":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn","amounts":[{"denom":"rune","amount":"50000000"}]}}]})"; - auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); - - auto outputJson = THORChain::Signer::signJSON(inputJson, privateKey); - - EXPECT_EQ(R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"rune"}],"gas":"2000000"},"memo":"memo1234","msg":[{"type":"thorchain/MsgSend","value":{"amount":[{"amount":"50000000","denom":"rune"}],"from_address":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","to_address":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"12AaNC0v51Rhz8rBf7V7rpI6oksREWrjzba3RK1v1NNlqZq62sG0aXWvStp9zZXe07Pp2FviFBAx+uqWsO30NQ=="}]}})", outputJson); -} diff --git a/tests/THORChain/SwapTests.cpp b/tests/THORChain/SwapTests.cpp deleted file mode 100644 index e5bd97af933..00000000000 --- a/tests/THORChain/SwapTests.cpp +++ /dev/null @@ -1,423 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "THORChain/Swap.h" -#include "Bitcoin/Script.h" -#include "Bitcoin/SegwitAddress.h" -#include "Ethereum/Address.h" -#include "Ethereum/ABI/Function.h" -#include "Ethereum/ABI/ParamBase.h" -#include "Ethereum/ABI/ParamAddress.h" -#include "Binance/Address.h" -#include "proto/THORChainSwap.pb.h" -#include "proto/Bitcoin.pb.h" -#include "proto/Ethereum.pb.h" -#include "proto/Binance.pb.h" - -#include "HexCoding.h" -#include "Coin.h" -#include -#include -#include "uint256.h" -#include "../interface/TWTestUtilities.h" - -#include - -namespace TW::THORChainSwap { - -// Addresses for wallet 'isolate dismiss fury ... note' -const auto Address1Btc = "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"; -const auto Address1Eth = "0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7"; -const auto Address1Bnb = "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx"; -const auto Address1Thor = "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r"; -const Data TestKey1Btc = parse_hex("13fcaabaf9e71ffaf915e242ec58a743d55f102cf836968e5bd4881135e0c52c"); -const Data TestKey1Eth = parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904"); -const Data TestKey1Bnb = parse_hex("bcf8b072560dda05122c99390def2c385ec400e1a93df0657a85cf6b57a715da"); -const auto VaultBtc = "bc1q6m9u2qsu8mh8y7v8rr2ywavtj8g5arzlyhcej7"; -const auto VaultEth = "0x1091c4De6a3cF09CdA00AbDAeD42c7c3B69C83EC"; -const auto VaultBnb = "bnb1n9esxuw8ca7ts8l6w66kdh800s09msvul6vlse"; -const auto RouterEth = "0x42A5Ed456650a09Dc10EBc6361A7480fDd61f27B"; - - -TEST(THORChainSwap, SwapBtcEth) { - auto res = Swap::build(Chain::BTC, Chain::ETH, Address1Btc, "ETH", "", Address1Eth, VaultBtc, "", "1000000", "140000000000000000"); - ASSERT_EQ(std::get<1>(res), 0); - ASSERT_EQ(std::get<2>(res), ""); - EXPECT_EQ(hex(std::get<0>(res)), "080110c0843d1801222a62633171366d397532717375386d68387937763872723279776176746a38673561727a6c796863656a372a2a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070386a473d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a313430303030303030303030303030303030"); - - auto tx = Bitcoin::Proto::SigningInput(); - ASSERT_TRUE(tx.ParseFromArray(std::get<0>(res).data(), (int)std::get<0>(res).size())); - - // check fields - EXPECT_EQ(tx.amount(), 1000000); - EXPECT_EQ(tx.to_address(), VaultBtc); - EXPECT_EQ(tx.change_address(), Address1Btc); - EXPECT_EQ(tx.output_op_return(), "=:ETH.ETH:0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7:140000000000000000"); - EXPECT_EQ(tx.coin_type(), 0); - EXPECT_EQ(tx.private_key_size(), 0); - EXPECT_FALSE(tx.has_plan()); - - // set few fields before signing - tx.set_byte_fee(20); - EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); - tx.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); - auto& utxo = *tx.add_utxo(); - Data utxoHash = parse_hex("1234000000000000000000000000000000000000000000000000000000005678"); - utxo.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); - utxo.mutable_out_point()->set_index(0); - utxo.mutable_out_point()->set_sequence(UINT32_MAX); - auto utxoScript = Bitcoin::Script::lockScriptForAddress(Address1Btc, TWCoinTypeBitcoin); - utxo.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); - utxo.set_amount(50000000); - tx.set_use_max_amount(false); - - // sign and encode resulting input - Bitcoin::Proto::SigningOutput output; - ANY_SIGN(tx, TWCoinTypeBitcoin); - EXPECT_EQ(output.error(), 0); - EXPECT_EQ(hex(output.encoded()), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "01" // inputs - "1234000000000000000000000000000000000000000000000000000000005678" "00000000" "00" "" "ffffffff" - "03" // outputs - "40420f0000000000" "16" "0014d6cbc5021c3eee72798718d447758b91d14e8c5f" - "609deb0200000000" "16" "00140cb9f5c6b62c03249367bc20a90dd2425e6926af" - "0000000000000000" "42" "6a403d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a3134303030303030303030" - // witness - "02" - "47" "304402205de19c68b5ea683b9d701d45b09f96658088db76e59ad27bd7b8383ee5d484ec0220245459a4d6d679d8b457564fccc7ecc5831c7ebed49e0366c65ac031e8a5b49201" - "21" "021e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee" - "00000000" // nLockTime - ); -} - -TEST(THORChainSwap, SwapBtcBnb) { - auto res = Swap::build(Chain::BTC, Chain::BNB, Address1Btc, "BNB", "", Address1Bnb, VaultBtc, "", "200000", "140000000"); - ASSERT_EQ(std::get<1>(res), 0); - ASSERT_EQ(std::get<2>(res), ""); - EXPECT_EQ(hex(std::get<0>(res)), "080110c09a0c1801222a62633171366d397532717375386d68387937763872723279776176746a38673561727a6c796863656a372a2a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070386a41535741503a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a313430303030303030"); - - auto tx = Bitcoin::Proto::SigningInput(); - ASSERT_TRUE(tx.ParseFromArray(std::get<0>(res).data(), (int)std::get<0>(res).size())); - - // check fields - EXPECT_EQ(tx.amount(), 200000); - EXPECT_EQ(tx.to_address(), VaultBtc); - EXPECT_EQ(tx.change_address(), Address1Btc); - EXPECT_EQ(tx.output_op_return(), "SWAP:BNB.BNB:bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx:140000000"); - EXPECT_EQ(tx.coin_type(), 0); - EXPECT_EQ(tx.private_key_size(), 0); - EXPECT_FALSE(tx.has_plan()); - - // set few fields before signing - tx.set_byte_fee(80); - EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); - tx.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); - auto& utxo = *tx.add_utxo(); - Data utxoHash = parse_hex("8eae5c3a4c75058d4e3facd5d72f18a40672bcd3d1f35ebf3094bd6c78da48eb"); - std::reverse(utxoHash.begin(), utxoHash.end()); - utxo.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); - utxo.mutable_out_point()->set_index(0); - utxo.mutable_out_point()->set_sequence(UINT32_MAX - 3); - auto utxoScript = Bitcoin::Script::lockScriptForAddress(Address1Btc, TWCoinTypeBitcoin); - utxo.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); - utxo.set_amount(450000); - tx.set_use_max_amount(false); - - // sign and encode resulting input - Bitcoin::Proto::SigningOutput output; - ANY_SIGN(tx, TWCoinTypeBitcoin); - EXPECT_EQ(output.error(), 0); - EXPECT_EQ(hex(output.encoded()), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "01" // inputs - "eb48da786cbd9430bf5ef3d1d3bc7206a4182fd7d5ac3f4e8d05754c3a5cae8e" "00000000" "00" "" "fcffffff" - "03" // outputs - "400d030000000000" "16" "0014d6cbc5021c3eee72798718d447758b91d14e8c5f" - "108d030000000000" "16" "00140cb9f5c6b62c03249367bc20a90dd2425e6926af" - "0000000000000000" "42" "6a40535741503a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a3134303030303030" - // witness - "02" - "48" "30450221008427ac07af830abbf9f2e1b182096d9faefc9e5b4324786ec68386579b05d02102204fd062817a59255d62aba24b1b0c66bc070d0ddbb70bf130a6159cc057e7f6c801210" - "21" "e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee" - "00000000" // nLockTime - ); - - // similar real transaction: - // https://blockchair.com/bitcoin/transaction/1cd9056b212b85d9d7d34d0795a746dd8691b8cd34ef56df0aa9622fbdec5f88 - // https://viewblock.io/thorchain/tx/1CD9056B212B85D9D7D34D0795A746DD8691B8CD34EF56DF0AA9622FBDEC5F88 - // https://explorer.binance.org/tx/8D78469069118E9B9546696214CCD46E63D3FA0D7E854C094D63C8F6061278B7 -} - -Data SwapTest_ethAddressStringToData(const std::string& asString) { - if (asString.empty()) { - return Data(); - } - auto address = Ethereum::Address(asString); - Data asData; - asData.resize(20); - std::copy(address.bytes.begin(), address.bytes.end(), asData.data()); - return asData; -} - -TEST(THORChainSwap, SwapEthBnb) { - auto res = Swap::build(Chain::ETH, Chain::BNB, Address1Eth, "BNB", "", Address1Bnb, VaultEth, RouterEth, "50000000000000000", "600003"); - ASSERT_EQ(std::get<1>(res), 0); - ASSERT_EQ(std::get<2>(res), ""); - EXPECT_EQ(hex(std::get<0>(res)), "0a01001201002201002a0100422a30783432413545643435363635306130394463313045426336333631413734383066446436316632374252f30132f0010a07b1a2bc2ec5000012e4011fece7b40000000000000000000000001091c4de6a3cf09cda00abdaed42c7c3b69c83ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000003e535741503a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a3630303030330000"); - - auto tx = Ethereum::Proto::SigningInput(); - ASSERT_TRUE(tx.ParseFromArray(std::get<0>(res).data(), (int)std::get<0>(res).size())); - - // check fields - EXPECT_EQ(tx.to_address(), RouterEth); - ASSERT_TRUE(tx.transaction().has_contract_generic()); - - Data vaultAddressBin = SwapTest_ethAddressStringToData(VaultEth); - EXPECT_EQ(hex(vaultAddressBin), "1091c4de6a3cf09cda00abdaed42c7c3b69c83ec"); - auto func = Ethereum::ABI::Function("deposit", std::vector>{ - std::make_shared(vaultAddressBin), - std::make_shared(parse_hex("0000000000000000000000000000000000000000")), - std::make_shared(uint256_t(50000000000000000)), - std::make_shared("SWAP:BNB.BNB:bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx:600003") - }); - Data payload; - func.encode(payload); - EXPECT_EQ(hex(payload), "1fece7b4" - "0000000000000000000000001091c4de6a3cf09cda00abdaed42c7c3b69c83ec" - "0000000000000000000000000000000000000000000000000000000000000000" - "00000000000000000000000000000000000000000000000000b1a2bc2ec50000" - "0000000000000000000000000000000000000000000000000000000000000080" - "000000000000000000000000000000000000000000000000000000000000003e" - "535741503a424e422e424e423a626e6231757334377764686678303863683937" - "7a6475656833783375356d757266727833306a656372783a3630303030330000"); - EXPECT_EQ(hex(TW::data(tx.transaction().contract_generic().amount())), "b1a2bc2ec50000"); - EXPECT_EQ(hex(TW::data(tx.transaction().contract_generic().data())), hex(payload)); - - EXPECT_EQ(hex(TW::data(tx.private_key())), ""); - - // set few fields before signing - auto chainId = store(uint256_t(1)); - tx.set_chain_id(chainId.data(), chainId.size()); - auto nonce = store(uint256_t(3)); - tx.set_nonce(nonce.data(), nonce.size()); - auto gasPrice = store(uint256_t(30000000000)); - tx.set_gas_price(gasPrice.data(), gasPrice.size()); - auto gasLimit = store(uint256_t(80000)); - tx.set_gas_limit(gasLimit.data(), gasLimit.size()); - tx.set_private_key(""); - tx.set_private_key(TestKey1Eth.data(), TestKey1Eth.size()); - - // sign and encode resulting input - Ethereum::Proto::SigningOutput output; - ANY_SIGN(tx, TWCoinTypeEthereum); - EXPECT_EQ(hex(output.encoded()), "f90151038506fc23ac00830138809442a5ed456650a09dc10ebc6361a7480fdd61f27b87b1a2bc2ec50000b8e41fece7b40000000000000000000000001091c4de6a3cf09cda00abdaed42c7c3b69c83ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000003e535741503a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a363030303033000025a06ae104be3201baca38315352f81fac70ca4dd47339981914e64e91149813e780a066a3f0b2c44ddf5a96a38481274f623f552a593d723237d6742185f4885c0064"); -} - -TEST(THORChainSwap, SwapBnbBtc) { - auto res = Swap::build(Chain::BNB, Chain::BTC, Address1Bnb, "BTC", "", Address1Btc, VaultBnb, "", "10000000", "10000000"); - ASSERT_EQ(std::get<1>(res), 0); - ASSERT_EQ(std::get<2>(res), ""); - EXPECT_EQ(hex(std::get<0>(res)), "2a40535741503a4254432e4254433a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070383a313030303030303052480a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e421080ade204"); - - auto tx = Binance::Proto::SigningInput(); - ASSERT_TRUE(tx.ParseFromArray(std::get<0>(res).data(), (int)std::get<0>(res).size())); - - // check fields - EXPECT_EQ(tx.memo(), "SWAP:BTC.BTC:bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8:10000000"); - ASSERT_TRUE(tx.has_send_order()); - ASSERT_EQ(tx.send_order().inputs_size(), 1); - ASSERT_EQ(tx.send_order().outputs_size(), 1); - EXPECT_EQ(hex(tx.send_order().inputs(0).address()), "e42be736e933cf8b97c26f33789a3ca6f8348cd1"); - EXPECT_EQ(hex(tx.send_order().outputs(0).address()), "99730371c7c77cb81ffa76b566dcef7c1e5dc19c"); - EXPECT_EQ(hex(TW::data(tx.private_key())), ""); - - // set few fields before signing - tx.set_chain_id("Binance-Chain-Tigris"); - tx.set_private_key(TestKey1Bnb.data(), TestKey1Bnb.size()); - - // sign and encode resulting input - Binance::Proto::SigningOutput output; - ANY_SIGN(tx, TWCoinTypeBinance); - EXPECT_EQ(hex(output.encoded()), "8002f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e421080ade204126a0a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e191240af2117ebd42e31a9562738e9f8933b3b54b59e6305b5675956525e4edb6a6ac65abea614e90959ae388664e2b36bf720024879b6047e174e3cff95f8f364a4e71a40535741503a4254432e4254433a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070383a3130303030303030"); -} - -TEST(THORChainSwap, SwapBnbEth) { - auto res = Swap::build(Chain::BNB, Chain::ETH, Address1Bnb, "ETH", "", Address1Eth, VaultBnb, "", "27000000", "123456"); - ASSERT_EQ(std::get<1>(res), 0); - ASSERT_EQ(std::get<2>(res), ""); - EXPECT_EQ(hex(std::get<0>(res)), "2a3b3d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a31323334353652480a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e4210c0f9ef0c12220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e4210c0f9ef0c"); - - auto tx = Binance::Proto::SigningInput(); - ASSERT_TRUE(tx.ParseFromArray(std::get<0>(res).data(), (int)std::get<0>(res).size())); - - // check fields - EXPECT_EQ(tx.memo(), "=:ETH.ETH:0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7:123456"); - ASSERT_TRUE(tx.has_send_order()); - ASSERT_EQ(tx.send_order().inputs_size(), 1); - ASSERT_EQ(tx.send_order().outputs_size(), 1); - EXPECT_EQ(hex(tx.send_order().inputs(0).address()), "e42be736e933cf8b97c26f33789a3ca6f8348cd1"); - EXPECT_EQ(hex(tx.send_order().outputs(0).address()), "99730371c7c77cb81ffa76b566dcef7c1e5dc19c"); - EXPECT_EQ(hex(TW::data(tx.private_key())), ""); - - // set private key and few other fields - EXPECT_EQ(TW::deriveAddress(TWCoinTypeBinance, PrivateKey(TestKey1Bnb)), Address1Bnb); - tx.set_private_key(TestKey1Bnb.data(), TestKey1Bnb.size()); - tx.set_chain_id("Binance-Chain-Tigris"); - tx.set_account_number(1902570); - tx.set_sequence(12); - // sign and encode resulting input - Binance::Proto::SigningOutput output; - ANY_SIGN(tx, TWCoinTypeBinance); - EXPECT_EQ(hex(output.encoded()), "8102f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e4210c0f9ef0c12220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e4210c0f9ef0c12700a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e1912409ad3d44f3cc8d5dd2701b0bf3758ef674683533fb63e3e94d39728688c0279f8410395d631075dac62dee74b972c320f5a58e88ab81be6f1bb6a9564468ae1b618ea8f74200c1a3b3d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a313233343536"); - - // real transaction: - // https://explorer.binance.org/tx/F0CFDB0D9467E83B5BBF6DF92E4E2D04FE9EFF9B0A1C71D88DCEF566233DCAA2 - // https://viewblock.io/thorchain/tx/F0CFDB0D9467E83B5BBF6DF92E4E2D04FE9EFF9B0A1C71D88DCEF566233DCAA2 - // https://etherscan.io/tx/0x8e5bb7d87e17af86e649e402bc5c182ea8c32ddaca153804679de1184e0d9747 -} - -TEST(THORChainSwap, SwapBnbRune) { - auto res = Swap::build(Chain::BNB, Chain::THOR, Address1Bnb, "RUNE", "", Address1Thor, VaultBnb, "", "4000000", "121065076"); - ASSERT_EQ(std::get<1>(res), 0); - ASSERT_EQ(std::get<2>(res), ""); - EXPECT_EQ(hex(std::get<0>(res)), "2a44535741503a54484f522e52554e453a74686f72317a3533777765376d64366365777a39737177717a6e306161767061756e3067773065786e32723a31323130363530373652480a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e42108092f40112220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e42108092f401"); - - auto tx = Binance::Proto::SigningInput(); - ASSERT_TRUE(tx.ParseFromArray(std::get<0>(res).data(), (int)std::get<0>(res).size())); - - // check fields - EXPECT_EQ(tx.memo(), "SWAP:THOR.RUNE:thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r:121065076"); - ASSERT_TRUE(tx.has_send_order()); - ASSERT_EQ(tx.send_order().inputs_size(), 1); - ASSERT_EQ(tx.send_order().outputs_size(), 1); - EXPECT_EQ(hex(tx.send_order().inputs(0).address()), "e42be736e933cf8b97c26f33789a3ca6f8348cd1"); - EXPECT_EQ(hex(tx.send_order().outputs(0).address()), "99730371c7c77cb81ffa76b566dcef7c1e5dc19c"); - EXPECT_EQ(hex(TW::data(tx.private_key())), ""); - - // set private key and few other fields - EXPECT_EQ(TW::deriveAddress(TWCoinTypeBinance, PrivateKey(TestKey1Bnb)), Address1Bnb); - tx.set_private_key(TestKey1Bnb.data(), TestKey1Bnb.size()); - tx.set_chain_id("Binance-Chain-Tigris"); - tx.set_account_number(1902570); - tx.set_sequence(4); - // sign and encode resulting input - Binance::Proto::SigningOutput output; - ANY_SIGN(tx, TWCoinTypeBinance); - EXPECT_EQ(hex(output.encoded()), "8a02f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e42108092f40112220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e42108092f40112700a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e191240d91b6655ea4ade62a90cc9b28e43ccd2887dcf1c563e42bbd0d6ae4e825c2c6a1ba7784866810f36b6e098b0c877d1daa48016d0558f7b796b3f0b410107ba2f18ea8f7420041a44535741503a54484f522e52554e453a74686f72317a3533777765376d64366365777a39737177717a6e306161767061756e3067773065786e32723a313231303635303736"); - - // real transaction: - // https://explorer.binance.org/tx/84EE429B35945F0568097527A084532A9DE7BBAB0E6A5562E511CEEFB188DE69 - // https://viewblock.io/thorchain/tx/D582E1473FE229F02F162055833C64F49FB4FF515989A4785ED7898560A448FC -} - -TEST(THORChainSwap, SwapBnbBnbToken) { - auto res = Swap::build( - Chain::BNB, - Chain::BNB, - "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx", - "BNB", - "TWT-8C2", - "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx", - "bnb1qefsjm654cdw94ejj8g4s49w7z8te75veslusz", - "", - "10000000", // 0.1 bnb - "5400000000" // 54.0 twt - ); - ASSERT_EQ(std::get<1>(res), 0); - ASSERT_EQ(std::get<2>(res), ""); - EXPECT_EQ(hex(std::get<0>(res)), "2a46535741503a424e422e5457542d3843323a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a3534303030303030303052480a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a140653096f54ae1ae2d73291d15854aef08ebcfa8c120a0a03424e421080ade204"); - - auto tx = Binance::Proto::SigningInput(); - ASSERT_TRUE(tx.ParseFromArray(std::get<0>(res).data(), (int)std::get<0>(res).size())); - - // check fields - EXPECT_EQ(tx.memo(), "SWAP:BNB.TWT-8C2:bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx:5400000000"); - ASSERT_TRUE(tx.has_send_order()); - ASSERT_EQ(tx.send_order().inputs_size(), 1); - ASSERT_EQ(tx.send_order().outputs_size(), 1); - EXPECT_EQ(hex(tx.send_order().inputs(0).address()), "e42be736e933cf8b97c26f33789a3ca6f8348cd1"); - EXPECT_EQ(hex(tx.send_order().outputs(0).address()), "0653096f54ae1ae2d73291d15854aef08ebcfa8c"); - EXPECT_EQ(hex(TW::data(tx.private_key())), ""); - - // set private key and few other fields - const Data privateKey = parse_hex("bcf8b072560dda05122c99390def2c385ec400e1a93df0657a85cf6b57a715da"); - EXPECT_EQ(Binance::Address(PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx"); - tx.set_private_key(privateKey.data(), privateKey.size()); - tx.set_chain_id("Binance-Chain-Tigris"); - tx.set_account_number(1902570); - tx.set_sequence(18); - - // sign and encode resulting input - Binance::Proto::SigningOutput output; - ANY_SIGN(tx, TWCoinTypeBinance); - EXPECT_EQ(hex(output.encoded()), "8c02f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a140653096f54ae1ae2d73291d15854aef08ebcfa8c120a0a03424e421080ade20412700a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e1912405fd64a0ed5777f5ea4556624bd096f8b20b6d2b510655e4c928db1ec967e6c7025453882ce7e10138ac92f5d6a949acc5382a5539f81347856c67c4bb678d3c418ea8f7420121a46535741503a424e422e5457542d3843323a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a35343030303030303030"); - - // real transaction: - // curl -X GET "http://dataseed1.binance.org/broadcast_tx_sync?tx=0x8c02...3030" - // https://viewblock.io/thorchain/tx/6D1EDC9BD9BFAFEF0F88F95A164191262EA02A0413BF3D9773110AD5676E1523 - // https://explorer.binance.org/tx/6D1EDC9BD9BFAFEF0F88F95A164191262EA02A0413BF3D9773110AD5676E1523 - // https://explorer.binance.org/tx/60C54C9F253B89C36A2788AB66951045E8AC5F5729597CB6C64A13013A7A54CC -} - -TEST(THORChainSwap, Memo) { - EXPECT_EQ(Swap::buildMemo(Chain::BTC, "BTC", "", "btc123", 1234), "SWAP:BTC.BTC:btc123:1234"); - EXPECT_EQ(Swap::buildMemo(Chain::BNB, "BNB", "", "bnb123", 1234), "SWAP:BNB.BNB:bnb123:1234"); - EXPECT_EQ(Swap::buildMemo(Chain::ETH, "ETH", "", "0xaabbccdd", 1234), "=:ETH.ETH:0xaabbccdd:1234"); - EXPECT_EQ(Swap::buildMemo(Chain::ETH, "ETH", "", "0xaabbccdd", 1234), "=:ETH.ETH:0xaabbccdd:1234"); - EXPECT_EQ(Swap::buildMemo(Chain::ETH, "ETH", "0x0000000000000000000000000000000000000000", "0xaabbccdd", 1234), "=:ETH.ETH:0xaabbccdd:1234"); - EXPECT_EQ(Swap::buildMemo(Chain::ETH, "ETH", "0x4B0F1812e5Df2A09796481Ff14017e6005508003", "0xaabbccdd", 1234), "=:ETH.0x4B0F1812e5Df2A09796481Ff14017e6005508003:0xaabbccdd:1234"); - EXPECT_EQ(Swap::buildMemo(Chain::BNB, "BNB", "TWT-8C2", "bnb123", 1234), "SWAP:BNB.TWT-8C2:bnb123:1234"); -} - -TEST(THORChainSwap, WrongFromAddress) { - { - auto res = Swap::build(Chain::BNB, Chain::ETH, "DummyAddress", "ETH", "", Address1Eth, VaultEth, "", "100000", "100000"); - EXPECT_EQ(std::get<1>(res), Proto::ErrorCode::Error_Invalid_from_address); - EXPECT_EQ(std::get<2>(res), "Invalid from address"); - } - { - auto res = Swap::build(Chain::BNB, Chain::ETH, Address1Btc, "ETH", "", Address1Eth, VaultEth, "", "100000", "100000"); - EXPECT_EQ(std::get<1>(res), Proto::ErrorCode::Error_Invalid_from_address); - EXPECT_EQ(std::get<2>(res), "Invalid from address"); - } -} - -TEST(THORChainSwap, WrongToAddress) { - { - auto res = Swap::build(Chain::BNB, Chain::ETH, Address1Bnb, "ETH", "", "DummyAddress", VaultEth, "", "100000", "100000"); - EXPECT_EQ(std::get<1>(res), Proto::ErrorCode::Error_Invalid_to_address); - EXPECT_EQ(std::get<2>(res), "Invalid to address"); - } - { - auto res = Swap::build(Chain::BNB, Chain::ETH, Address1Bnb, "ETH", "", Address1Btc, VaultEth, "", "100000", "100000"); - EXPECT_EQ(std::get<1>(res), Proto::ErrorCode::Error_Invalid_to_address); - EXPECT_EQ(std::get<2>(res), "Invalid to address"); - } -} - -TEST(THORChainSwap, FromRuneNotSupported) { - auto res = Swap::build(Chain::THOR, Chain::BNB, Address1Thor, "BNB", "", Address1Bnb, "", "", "1000", "1000"); - EXPECT_EQ(std::get<1>(res), Proto::ErrorCode::Error_Unsupported_from_chain); - EXPECT_EQ(std::get<2>(res), "Unsupported from chain: 3"); -} - -TEST(THORChainSwap, EthInvalidVault) { - { - auto res = Swap::build(Chain::ETH, Chain::BNB, Address1Eth, "BNB", "", Address1Bnb, "_INVALID_ADDRESS_", RouterEth, "50000000000000000", "600003"); - EXPECT_EQ(std::get<1>(res), Proto::ErrorCode::Error_Invalid_vault_address); - EXPECT_EQ(std::get<2>(res), "Invalid vault address: _INVALID_ADDRESS_"); - } - { - auto res = Swap::build(Chain::ETH, Chain::BNB, Address1Eth, "BNB", "", Address1Bnb, VaultEth, "_INVALID_ADDRESS_", "50000000000000000", "600003"); - EXPECT_EQ(std::get<1>(res), Proto::ErrorCode::Error_Invalid_router_address); - EXPECT_EQ(std::get<2>(res), "Invalid router address: _INVALID_ADDRESS_"); - } -} - -} // namespace diff --git a/tests/THORChain/TWAnyAddressTests.cpp b/tests/THORChain/TWAnyAddressTests.cpp deleted file mode 100644 index 28e1618885e..00000000000 --- a/tests/THORChain/TWAnyAddressTests.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include "HexCoding.h" - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; - -TEST(THORChainAnyAddress, IsValid) { - EXPECT_TRUE(TWAnyAddressIsValid(STRING("thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r").get(), TWCoinTypeTHORChain)); - EXPECT_TRUE(TWAnyAddressIsValid(STRING("thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65").get(), TWCoinTypeTHORChain)); - EXPECT_FALSE(TWAnyAddressIsValid(STRING("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02").get(), TWCoinTypeTHORChain)); -} - -TEST(THORChainAnyAddress, Create) { - auto string = STRING("thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r"); - auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeTHORChain)); - auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); - EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); - auto keyHash = WRAPD(TWAnyAddressData(addr.get())); - assertHexEqual(keyHash, "1522e767db6eb19708b0038029bfbd607bc9bd0e"); -} diff --git a/tests/THORChain/TWAnySignerTests.cpp b/tests/THORChain/TWAnySignerTests.cpp deleted file mode 100644 index a4fb0eebde3..00000000000 --- a/tests/THORChain/TWAnySignerTests.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include -#include "Cosmos/Address.h" -#include "proto/Cosmos.pb.h" -#include "HexCoding.h" - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; - -TEST(THORChainTWAnySigner, SignTx) { - auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); - Cosmos::Proto::SigningInput input; - input.set_account_number(593); - input.set_chain_id("thorchain"); - input.set_sequence(3); - input.set_private_key(privateKey.data(), privateKey.size()); - input.set_memo(""); - - auto fromAddress = "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r"; - auto toAddress = "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"; - - auto msg = input.add_messages(); - auto& message = *msg->mutable_send_coins_message(); - message.set_from_address(fromAddress); - message.set_to_address(toAddress); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("rune"); - amountOfTx->set_amount("10000000"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("rune"); - amountOfFee->set_amount("2000000"); - - Cosmos::Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeTHORChain); - - // https://viewblock.io/thorchain/tx/FD0445AFFC4ED9ACCB7B5D3ADE361DAE4596EA096340F1360F1020381EA454AF - ASSERT_EQ(output.json(), R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"2000000","denom":"rune"}],"gas":"200000"},"memo":"","msg":[{"type":"thorchain/MsgSend","value":{"amount":[{"amount":"10000000","denom":"rune"}],"from_address":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","to_address":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"qgpMX3WNq4DsNBnYtdmBD4ejiailK4uI/m3/YVqCSNF8AtkUOTmP48ztqCbpkWTFvw1/9S8/ivsFxOcK6AI0jA=="}]}})"); -} diff --git a/tests/THORChain/TWCoinTypeTests.cpp b/tests/THORChain/TWCoinTypeTests.cpp deleted file mode 100644 index fead42b2449..00000000000 --- a/tests/THORChain/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWTHORChainCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTHORChain)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("ADF0899E58C177E2391F22D04E9C5E1C35BB0F75B42B363A0761687907FD9476")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTHORChain, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("thor196yf4pq80hjrmz7nnh0ar0ypqg02r0w4dq4mzu")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTHORChain, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTHORChain)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTHORChain)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTHORChain), 8); - ASSERT_EQ(TWBlockchainThorchain, TWCoinTypeBlockchain(TWCoinTypeTHORChain)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTHORChain)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTHORChain)); - assertStringsEqual(symbol, "RUNE"); - assertStringsEqual(txUrl, "https://viewblock.io/thorchain/tx/ADF0899E58C177E2391F22D04E9C5E1C35BB0F75B42B363A0761687907FD9476"); - assertStringsEqual(accUrl, "https://viewblock.io/thorchain/address/thor196yf4pq80hjrmz7nnh0ar0ypqg02r0w4dq4mzu"); - assertStringsEqual(id, "thorchain"); - assertStringsEqual(name, "THORChain"); -} diff --git a/tests/THORChain/TWSwapTests.cpp b/tests/THORChain/TWSwapTests.cpp deleted file mode 100644 index 0b2281a7e23..00000000000 --- a/tests/THORChain/TWSwapTests.cpp +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright © 2017-2021 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include "proto/THORChainSwap.pb.h" -#include "proto/Ethereum.pb.h" -#include "proto/Binance.pb.h" -#include "Bitcoin/SegwitAddress.h" -#include "Bitcoin/Script.h" -#include -#include -#include "PrivateKey.h" - -#include "HexCoding.h" -#include "uint256.h" -#include "../interface/TWTestUtilities.h" - -#include - -using namespace TW::THORChainSwap; -using namespace TW; - -const auto Address1Bnb = "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx"; -const auto Address1Btc = "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"; -const auto Address1Eth = "0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7"; -const auto VaultBnb = "bnb1n9esxuw8ca7ts8l6w66kdh800s09msvul6vlse"; -const auto VaultBtc = "bc1q6m9u2qsu8mh8y7v8rr2ywavtj8g5arzlyhcej7"; -const auto VaultEth = "0x1091c4De6a3cF09CdA00AbDAeD42c7c3B69C83EC"; -const auto RouterEth = "0x42A5Ed456650a09Dc10EBc6361A7480fDd61f27B"; -const Data TestKey1Bnb = parse_hex("bcf8b072560dda05122c99390def2c385ec400e1a93df0657a85cf6b57a715da"); -const Data TestKey1Btc = parse_hex("13fcaabaf9e71ffaf915e242ec58a743d55f102cf836968e5bd4881135e0c52c"); -const Data TestKey1Eth = parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904"); - -TEST(TWTHORChainSwap, SwapBtcToEth) { - // prepare swap input - Proto::SwapInput input; - input.set_from_chain(Proto::BTC); - input.set_from_address(Address1Btc); - Proto::Asset toAsset; - toAsset.set_chain(Proto::ETH); - toAsset.set_symbol("ETH"); - toAsset.set_token_id(""); - *input.mutable_to_asset() = toAsset; - input.set_to_address(Address1Eth); - input.set_vault_address(VaultBtc); - input.set_router_address(""); - input.set_from_amount("1000000"); - input.set_to_amount_limit("140000000000000000"); - - // serialize input - const auto inputData = input.SerializeAsString(); - EXPECT_EQ(hex(inputData), "0801122a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070381a0708021203455448222a3078623966353737316332373636346266323238326439386530396437663530636563376362303161372a2a62633171366d397532717375386d68387937763872723279776176746a38673561727a6c796863656a373a07313030303030304212313430303030303030303030303030303030"); - const auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size())); - - // invoke swap - const auto outputTWData = WRAPD(TWTHORChainSwapBuildSwap(inputTWData.get())); - const auto outputData = data(TWDataBytes(outputTWData.get()), TWDataSize(outputTWData.get())); - EXPECT_EQ(outputData.size(), 178); - // parse result in proto - Proto::SwapOutput outputProto; - EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); - EXPECT_EQ(outputProto.from_chain(), Proto::BTC); - EXPECT_EQ(outputProto.to_chain(), Proto::ETH); - EXPECT_EQ(outputProto.error().code(), 0); - EXPECT_EQ(outputProto.error().message(), ""); - EXPECT_TRUE(outputProto.has_bitcoin()); - Bitcoin::Proto::SigningInput txInput = outputProto.bitcoin(); - - // tx input: check some fields - EXPECT_EQ(txInput.amount(), 1000000); - EXPECT_EQ(txInput.to_address(), "bc1q6m9u2qsu8mh8y7v8rr2ywavtj8g5arzlyhcej7"); - EXPECT_EQ(txInput.change_address(), "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"); - EXPECT_EQ(txInput.output_op_return(), "=:ETH.ETH:0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7:140000000000000000"); - EXPECT_EQ(txInput.coin_type(), 0); - - // sign tx input for signed full tx - // set few fields before signing - txInput.set_byte_fee(20); - EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); - txInput.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); - auto& utxo = *txInput.add_utxo(); - Data utxoHash = parse_hex("1234000000000000000000000000000000000000000000000000000000005678"); - utxo.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); - utxo.mutable_out_point()->set_index(0); - utxo.mutable_out_point()->set_sequence(UINT32_MAX); - auto utxoScript = Bitcoin::Script::lockScriptForAddress(Address1Btc, TWCoinTypeBitcoin); - utxo.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); - utxo.set_amount(50000000); - txInput.set_use_max_amount(false); - - // sign and encode resulting input - { - Bitcoin::Proto::SigningOutput output; - ANY_SIGN(txInput, TWCoinTypeBitcoin); - EXPECT_EQ(output.error(), 0); - EXPECT_EQ(hex(output.encoded()), // printed using prettyPrintTransaction - "01000000" // version - "0001" // marker & flag - "01" // inputs - "1234000000000000000000000000000000000000000000000000000000005678" "00000000" "00" "" "ffffffff" - "03" // outputs - "40420f0000000000" "16" "0014d6cbc5021c3eee72798718d447758b91d14e8c5f" - "609deb0200000000" "16" "00140cb9f5c6b62c03249367bc20a90dd2425e6926af" - "0000000000000000" "42" "6a403d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a3134303030303030303030" - // witness - "02" - "47" "304402205de19c68b5ea683b9d701d45b09f96658088db76e59ad27bd7b8383ee5d484ec0220245459a4d6d679d8b457564fccc7ecc5831c7ebed49e0366c65ac031e8a5b49201" - "21" "021e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee" - "00000000" // nLockTime - ); - } -} - -TEST(TWTHORChainSwap, SwapEthBnb) { - // prepare swap input - Proto::SwapInput input; - input.set_from_chain(Proto::ETH); - input.set_from_address(Address1Eth); - Proto::Asset toAsset; - toAsset.set_chain(Proto::BNB); - toAsset.set_symbol("BNB"); - toAsset.set_token_id(""); - *input.mutable_to_asset() = toAsset; - input.set_to_address(Address1Bnb); - input.set_vault_address(VaultEth); - input.set_router_address(RouterEth); - input.set_from_amount("50000000000000000"); - input.set_to_amount_limit("600003"); - - // serialize input - const auto inputData = input.SerializeAsString(); - EXPECT_EQ(hex(inputData), "0802122a3078623966353737316332373636346266323238326439386530396437663530636563376362303161371a0708031203424e42222a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372782a2a307831303931633444653661336346303943644130304162444165443432633763334236394338334543322a3078343241354564343536363530613039446331304542633633363141373438306644643631663237423a1135303030303030303030303030303030304206363030303033"); - const auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size())); - - // invoke swap - const auto outputTWData = WRAPD(TWTHORChainSwapBuildSwap(inputTWData.get())); - const auto outputData = data(TWDataBytes(outputTWData.get()), TWDataSize(outputTWData.get())); - EXPECT_EQ(outputData.size(), 311); - // parse result in proto - Proto::SwapOutput outputProto; - EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); - EXPECT_EQ(outputProto.from_chain(), Proto::ETH); - EXPECT_EQ(outputProto.to_chain(), Proto::BNB); - EXPECT_EQ(outputProto.error().code(), 0); - EXPECT_EQ(outputProto.error().message(), ""); - EXPECT_TRUE(outputProto.has_ethereum()); - Ethereum::Proto::SigningInput txInput = outputProto.ethereum(); - - // sign tx input for signed full tx - // set few fields before signing - auto chainId = store(uint256_t(1)); - txInput.set_chain_id(chainId.data(), chainId.size()); - auto nonce = store(uint256_t(3)); - txInput.set_nonce(nonce.data(), nonce.size()); - auto gasPrice = store(uint256_t(30000000000)); - txInput.set_gas_price(gasPrice.data(), gasPrice.size()); - auto gasLimit = store(uint256_t(80000)); - txInput.set_gas_limit(gasLimit.data(), gasLimit.size()); - txInput.set_private_key(""); - txInput.set_private_key(TestKey1Eth.data(), TestKey1Eth.size()); - - // sign and encode resulting input - Ethereum::Proto::SigningOutput output; - ANY_SIGN(txInput, TWCoinTypeEthereum); - EXPECT_EQ(hex(output.encoded()), "f90151038506fc23ac00830138809442a5ed456650a09dc10ebc6361a7480fdd61f27b87b1a2bc2ec50000b8e41fece7b40000000000000000000000001091c4de6a3cf09cda00abdaed42c7c3b69c83ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000003e535741503a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a363030303033000025a06ae104be3201baca38315352f81fac70ca4dd47339981914e64e91149813e780a066a3f0b2c44ddf5a96a38481274f623f552a593d723237d6742185f4885c0064"); -} - -TEST(TWTHORChainSwap, SwapBnbBtc) { - // prepare swap input - Proto::SwapInput input; - input.set_from_chain(Proto::BNB); - input.set_from_address(Address1Bnb); - Proto::Asset toAsset; - toAsset.set_chain(Proto::BTC); - toAsset.set_symbol("BTC"); - toAsset.set_token_id(""); - *input.mutable_to_asset() = toAsset; - input.set_to_address(Address1Btc); - input.set_vault_address(VaultBnb); - input.set_router_address(""); - input.set_from_amount("10000000"); - input.set_to_amount_limit("10000000"); - - // serialize input - const auto inputData = input.SerializeAsString(); - EXPECT_EQ(hex(inputData), "0803122a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372781a0708011203425443222a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070382a2a626e62316e396573787577386361377473386c367736366b64683830307330396d7376756c36766c73653a08313030303030303042083130303030303030"); - const auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size())); - - // invoke swap - const auto outputTWData = WRAPD(TWTHORChainSwapBuildSwap(inputTWData.get())); - const auto outputData = data(TWDataBytes(outputTWData.get()), TWDataSize(outputTWData.get())); - EXPECT_EQ(outputData.size(), 149); - // parse result in proto - Proto::SwapOutput outputProto; - EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); - EXPECT_EQ(outputProto.from_chain(), Proto::BNB); - EXPECT_EQ(outputProto.to_chain(), Proto::BTC); - EXPECT_EQ(outputProto.error().code(), 0); - EXPECT_EQ(outputProto.error().message(), ""); - EXPECT_TRUE(outputProto.has_binance()); - Binance::Proto::SigningInput txInput = outputProto.binance(); - - // set few fields before signing - txInput.set_chain_id("Binance-Chain-Tigris"); - txInput.set_private_key(TestKey1Bnb.data(), TestKey1Bnb.size()); - - // sign and encode resulting input - Ethereum::Proto::SigningOutput output; - ANY_SIGN(txInput, TWCoinTypeBinance); - EXPECT_EQ(hex(output.encoded()), "8002f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e421080ade204126a0a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e191240af2117ebd42e31a9562738e9f8933b3b54b59e6305b5675956525e4edb6a6ac65abea614e90959ae388664e2b36bf720024879b6047e174e3cff95f8f364a4e71a40535741503a4254432e4254433a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070383a3130303030303030"); -} - -TEST(TWTHORChainSwap, NegativeInvalidInput) { - const auto inputData = parse_hex("00112233"); - const auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size())); - - const auto outputTWData = WRAPD(TWTHORChainSwapBuildSwap(inputTWData.get())); - const auto outputData = data(TWDataBytes(outputTWData.get()), TWDataSize(outputTWData.get())); - EXPECT_EQ(outputData.size(), 39); - EXPECT_EQ(hex(outputData), "1a2508021221436f756c64206e6f7420646573657269616c697a6520696e7075742070726f746f"); - EXPECT_EQ(hex(data(std::string("Could not deserialize input proto"))), "436f756c64206e6f7420646573657269616c697a6520696e7075742070726f746f"); -} diff --git a/tests/Terra/SignerTests.cpp b/tests/Terra/SignerTests.cpp deleted file mode 100644 index 2dd1777d43e..00000000000 --- a/tests/Terra/SignerTests.cpp +++ /dev/null @@ -1,456 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Coin.h" -#include "HexCoding.h" -#include "Base64.h" -#include "proto/Cosmos.pb.h" -#include "Cosmos/Address.h" -#include "Cosmos/Signer.h" -#include "Cosmos/ProtobufSerialization.h" -#include "uint256.h" -#include "../interface/TWTestUtilities.h" - -#include -#include - -using namespace TW; -using namespace TW::Cosmos; - - -TEST(TerraSigner, SignSendTx) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::JSON); - input.set_account_number(1037); - input.set_chain_id("columbus-5"); - input.set_memo(""); - input.set_sequence(2); - - Address fromAddress; - ASSERT_TRUE(Address::decode("terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2", fromAddress)); - Address toAddress; - ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", toAddress)); - - auto msg = input.add_messages(); - auto& message = *msg->mutable_send_coins_message(); - message.set_from_address(fromAddress.string()); - message.set_to_address(toAddress.string()); - auto amountOfTx = message.add_amounts(); - amountOfTx->set_denom("luna"); - amountOfTx->set_amount("1000000"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("luna"); - amountOfFee->set_amount("200"); - - std::string json; - google::protobuf::util::MessageToJsonString(input, &json); - EXPECT_EQ(json, R"({"accountNumber":"1037","chainId":"columbus-5","fee":{"amounts":[{"denom":"luna","amount":"200"}],"gas":"200000"},"sequence":"2","messages":[{"sendCoinsMessage":{"fromAddress":"terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2","toAddress":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf","amounts":[{"denom":"luna","amount":"1000000"}]}}]})"); - - auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeTerra); - - assertJSONEqual(output.json(), R"( - { - "mode": "block", - "tx": { - "fee": { - "amount": [ - { - "amount": "200", - "denom": "luna" - } - ], - "gas": "200000" - }, - "memo": "", - "msg": [ - { - "type": "cosmos-sdk/MsgSend", - "value": { - "amount": [ - { - "amount": "1000000", - "denom": "luna" - } - ], - "from_address": "terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2", - "to_address": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf" - } - } - ], - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" - }, - "signature": "ofdIsLJzkODcQwLG89eE2g4HOaUmfKPh/08t07ehKPUqRMl4rVonzo73mkOvqtrHWjdtB+6t6R8DGudPpb6bRg==" - } - ] - } - } - )"); - EXPECT_EQ(hex(output.signature()), "a1f748b0b27390e0dc4302c6f3d784da0e0739a5267ca3e1ff4f2dd3b7a128f52a44c978ad5a27ce8ef79a43afaadac75a376d07eeade91f031ae74fa5be9b46"); - EXPECT_EQ(output.serialized(), ""); - EXPECT_EQ(output.error(), ""); -} - -TEST(TerraSigner, SignWasmTransferTxProtobuf_9FF3F0) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::Protobuf); - input.set_account_number(3407705); - input.set_chain_id("columbus-5"); - input.set_memo(""); - input.set_sequence(3); - - Address fromAddress; - ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); - Address toAddress; - ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); - const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; // ANC - - auto msg = input.add_messages(); - auto& message = *msg->mutable_wasm_terra_execute_contract_transfer_message(); - message.set_sender_address(fromAddress.string()); - message.set_contract_address(tokenContractAddress); - const auto amount = store(uint256_t(250000), 0); - message.set_amount(amount.data(), amount.size()); - message.set_recipient_address(toAddress.string()); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("uluna"); - amountOfFee->set_amount("3000"); - - std::string json; - google::protobuf::util::MessageToJsonString(input, &json); - assertJSONEqual(json, R"( - { - "signingMode": "Protobuf", - "accountNumber": "3407705", - "chainId": "columbus-5", - "fee": { - "amounts": [ - { - "denom": "uluna", - "amount": "3000" - } - ], - "gas": "200000" - }, - "sequence": "3", - "messages": [ - { - "wasmTerraExecuteContractTransferMessage": { - "senderAddress": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", - "contractAddress": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", - "amount": "A9CQ", - "recipientAddress": "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" - } - } - ] - } - )"); - - auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeTerra); - - // https://finder.terra.money/mainnet/tx/9FF3F0A16879254C22EB90D8B4D6195467FE5014381FD36BD3C23CA6698FE94B - // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "CogCCo..wld8"})' https:///cosmos/tx/v1beta1/txs - assertJSONEqual(output.serialized(), R"( - { - "tx_bytes": "CucBCuQBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBK5AQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Glt7InRyYW5zZmVyIjp7ImFtb3VudCI6IjI1MDAwMCIsInJlY2lwaWVudCI6InRlcnJhMWpsZ2FxeTludm4yaGY1dDJzcmE5eWN6OHM3N3duZjlsMGttZ2NwIn19EmcKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNwZjrHsPmJKW/rXOWfukpQ1+lOHOJW3/IlFFnKLNmsABIECgIIARgDEhMKDQoFdWx1bmESBDMwMDAQwJoMGkAaprIEMLPH2HmFdwFGoaipb2GIyhXt6ombz+WMnG2mORBI6gFt0M+IymYgzZz6w1SW52R922yafDnn7yXfutRw", - "mode": "BROADCAST_MODE_BLOCK" - } - )"); - EXPECT_EQ(hex(output.signature()), "1aa6b20430b3c7d87985770146a1a8a96f6188ca15edea899bcfe58c9c6da6391048ea016dd0cf88ca6620cd9cfac35496e7647ddb6c9a7c39e7ef25dfbad470"); - EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); -} - -TEST(TerraSigner, SignWasmTransferTxJson_078E90) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::JSON); - input.set_account_number(3407705); - input.set_chain_id("columbus-5"); - input.set_memo(""); - input.set_sequence(2); - - Address fromAddress; - ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); - Address toAddress; - ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); - const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; // ANC - - auto msg = input.add_messages(); - auto& message = *msg->mutable_wasm_terra_execute_contract_transfer_message(); - message.set_sender_address(fromAddress.string()); - message.set_contract_address(tokenContractAddress); - const auto amount = store(250000); - message.set_amount(amount.data(), amount.size()); - message.set_recipient_address(toAddress.string()); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("uluna"); - amountOfFee->set_amount("3000"); - - auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeTerra); - - // https://finder.terra.money/mainnet/tx/078E90458061611F6FD8B708882B55FF5C1FFB3FCE61322107A0A0DE39FC0F3E - // curl -H 'Content-Type: application/json' --data-binary '{"mode": "block","tx":{...}}' https:///txs - assertJSONEqual(output.json(), R"( - { - "mode": "block", - "tx": - { - "fee": {"amount":[{"amount": "3000","denom": "uluna"}],"gas": "200000"}, - "memo": "", - "msg": - [ - { - "type": "wasm/MsgExecuteContract", - "value": - { - "sender": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", - "contract": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", - "execute_msg": - { - "transfer": - { - "amount": "250000", - "recipient": "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" - } - }, - "coins": [] - } - } - ], - "signatures": - [ - { - "pub_key": - { - "type": "tendermint/PubKeySecp256k1", - "value": "A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA" - }, - "signature": "BjETdtbA97Wv1zvcsCV1tM+bdYKC8O3uGTk4mMRv6pBJB2y/Ds7qoS7s/zrkhYak1YChklQetHsI30XRXzGIkg==" - } - ] - } - })"); - EXPECT_EQ(hex(output.signature()), "06311376d6c0f7b5afd73bdcb02575b4cf9b758282f0edee19393898c46fea9049076cbf0eceeaa12eecff3ae48586a4d580a192541eb47b08df45d15f318892"); - EXPECT_EQ(output.serialized(), ""); - EXPECT_EQ(output.error(), ""); -} - -TEST(TerraSigner, SignWasmGeneric_EC4F85) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::Protobuf); - input.set_account_number(3407705); - input.set_chain_id("columbus-5"); - input.set_memo(""); - input.set_sequence(7); - - Address fromAddress; - ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); - Address toAddress; - ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); - const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; // ANC - const auto txMessage = R"({"transfer": { "amount": "250000", "recipient": "terra1d7048csap4wzcv5zm7z6tdqem2agyp9647vdyj" } })"; - - auto msg = input.add_messages(); - auto& message = *msg->mutable_wasm_terra_execute_contract_generic(); - message.set_sender_address(fromAddress.string()); - message.set_contract_address(tokenContractAddress); - message.set_execute_msg(txMessage); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("uluna"); - amountOfFee->set_amount("3000"); - - auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeTerra); - - // https://finder.terra.money/mainnet/tx/EC4F8532847E4D6AF016E6F6D3F027AE7FB6FF0B533C5132B01382D83B214A6F - // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "Cu4BC...iVt"})' https:///cosmos/tx/v1beta1/txs - assertJSONEqual(output.serialized(), R"( - { - "tx_bytes": "Cu4BCusBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLAAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2GmJ7InRyYW5zZmVyIjogeyAiYW1vdW50IjogIjI1MDAwMCIsICJyZWNpcGllbnQiOiAidGVycmExZDcwNDhjc2FwNHd6Y3Y1em03ejZ0ZHFlbTJhZ3lwOTY0N3ZkeWoiIH0gfRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYBxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAkPsS7xlSng2LMc9KiD1soN5NLaDcUh8I9okPmsdJN3le1B7yxRGNB4aQfhaRl/8Z0r5vitRT0AWuxDasd8wcFw==", - "mode": "BROADCAST_MODE_BLOCK" - } - )"); - - EXPECT_EQ(hex(output.signature()), "90fb12ef19529e0d8b31cf4a883d6ca0de4d2da0dc521f08f6890f9ac74937795ed41ef2c5118d0786907e169197ff19d2be6f8ad453d005aec436ac77cc1c17"); - EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); -} - - -TEST(TerraSigner, SignWasmGenericWithCoins_6651FC) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::Protobuf); - input.set_account_number(3407705); - input.set_chain_id("columbus-5"); - input.set_memo(""); - input.set_sequence(9); - - Address fromAddress; - ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); - Address toAddress; - ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); - const auto tokenContractAddress = "terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s"; // ANC Market - const auto txMessage = R"({ "deposit_stable": {} })"; - - auto msg = input.add_messages(); - auto& message = *msg->mutable_wasm_terra_execute_contract_generic(); - message.set_sender_address(fromAddress.string()); - message.set_contract_address(tokenContractAddress); - message.set_execute_msg(txMessage); - - auto amount = message.add_coins(); - amount->set_denom("uusd"); - amount->set_amount("1000"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(600000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("uluna"); - amountOfFee->set_amount("7000"); - - auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeTerra); - - // https://finder.terra.money/mainnet/tx/6651FCE0EE5C6D6ACB655CC49A6FD5E939FB082862854616EA0642475BCDD0C9 - // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "CrIBCq8B.....0NWg=="})' https:///cosmos/tx/v1beta1/txs - assertJSONEqual(output.serialized(), R"( - { - "tx_bytes": "CrIBCq8BCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBKEAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMXNlcGZqN3MwYWVnNTk2N3V4bmZrNHRoemxlcnJza3RrcGVsbTVzGhh7ICJkZXBvc2l0X3N0YWJsZSI6IHt9IH0qDAoEdXVzZBIEMTAwMBJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYCRITCg0KBXVsdW5hEgQ3MDAwEMDPJBpAGyi7f1ioY8XV6pjFq1s86Om4++CIUnd3rLHif2iopCcYvX0mLkTlQ6NUERg8nWTYgXcj6fOTO/ptgPuAtv0NWg==", - "mode": "BROADCAST_MODE_BLOCK" - } - )"); - - EXPECT_EQ(hex(output.signature()), "1b28bb7f58a863c5d5ea98c5ab5b3ce8e9b8fbe088527777acb1e27f68a8a42718bd7d262e44e543a35411183c9d64d8817723e9f3933bfa6d80fb80b6fd0d5a"); - EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); -} - -TEST(TerraSigner, SignWasmSendTxProtobuf) { - auto input = Proto::SigningInput(); - input.set_signing_mode(Proto::Protobuf); - input.set_account_number(3407705); - input.set_chain_id("columbus-5"); - input.set_memo(""); - input.set_sequence(4); - - Address fromAddress; - ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); - Address toAddress; - ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); - const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; // ANC - - auto msg = input.add_messages(); - auto& message = *msg->mutable_wasm_terra_execute_contract_send_message(); - message.set_sender_address(fromAddress.string()); - message.set_contract_address(tokenContractAddress); - const auto amount = store(uint256_t(250000), 0); - message.set_amount(amount.data(), amount.size()); - message.set_recipient_contract_address(toAddress.string()); - const auto msgMsg = Base64::encode(data(std::string(R"({"some_message":{}})"))); - EXPECT_EQ(msgMsg, "eyJzb21lX21lc3NhZ2UiOnt9fQ=="); - message.set_msg(msgMsg); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("uluna"); - amountOfFee->set_amount("3000"); - - std::string json; - google::protobuf::util::MessageToJsonString(input, &json); - assertJSONEqual(json, R"( - { - "signingMode": "Protobuf", - "accountNumber": "3407705", - "chainId": "columbus-5", - "fee": { - "amounts": [ - { - "denom": "uluna", - "amount": "3000" - } - ], - "gas": "200000" - }, - "sequence": "4", - "messages": [ - { - "wasmTerraExecuteContractSendMessage": { - "senderAddress": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", - "contractAddress": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", - "amount": "A9CQ", - "recipientContractAddress": "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", - "msg": "eyJzb21lX21lc3NhZ2UiOnt9fQ==" - } - } - ] - } - )"); - - auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input, TWCoinTypeTerra); - - // https://finder.terra.money/mainnet/tx/9FF3F0A16879254C22EB90D8B4D6195467FE5014381FD36BD3C23CA6698FE94B - // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "CogCCo..wld8"})' https:///cosmos/tx/v1beta1/txs - assertJSONEqual(output.serialized(), R"( - { - "tx_bytes": "CocCCoQCCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLZAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Gnt7InNlbmQiOnsiYW1vdW50IjoiMjUwMDAwIiwiY29udHJhY3QiOiJ0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcCIsIm1zZyI6ImV5SnpiMjFsWDIxbGMzTmhaMlVpT250OWZRPT0ifX0SZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awAEgQKAggBGAQSEwoNCgV1bHVuYRIEMzAwMBDAmgwaQL6NByKeRZsyq5g6CTMdmPqiM77nOe9uLO8FjpetFgkBFiG3Le7ieZZ+4vCMhD1bcFgMwSHibFI/uPil847U/+g=", - "mode": "BROADCAST_MODE_BLOCK" - } - )"); - EXPECT_EQ(hex(output.signature()), "be8d07229e459b32ab983a09331d98faa233bee739ef6e2cef058e97ad1609011621b72deee279967ee2f08c843d5b70580cc121e26c523fb8f8a5f38ed4ffe8"); - EXPECT_EQ(output.json(), ""); - EXPECT_EQ(output.error(), ""); -} - -TEST(TerraSigner, SignWasmTerraTransferPayload) { - auto proto = Proto::Message_WasmTerraExecuteContractTransfer(); - proto.set_recipient_address("recipient=address"); - const auto amount = store(uint256_t(250000), 0); - proto.set_amount(amount.data(), amount.size()); - - const auto payload = wasmTerraExecuteTransferPayload(proto); - - assertJSONEqual(payload.dump(), R"( - { - "transfer": - { - "amount": "250000", - "recipient": "recipient=address" - } - } - )"); -} diff --git a/tests/Terra/TWCoinTypeTests.cpp b/tests/Terra/TWCoinTypeTests.cpp deleted file mode 100644 index fa45497f705..00000000000 --- a/tests/Terra/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWTerraCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTerra)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("D28D8AFC7CE89F2A22FA2DBF78D2C0A36E549BB830C4D9FA7459E3F723CA7182")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTerra, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("terra16t3gx5rqvz6ru37yzn3shuu20erv4ngmfr59zf")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTerra, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTerra)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTerra)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTerra), 6); - ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeTerra)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTerra)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTerra)); - assertStringsEqual(symbol, "LUNC"); - assertStringsEqual(txUrl, "https://finder.terra.money/tx/tx/D28D8AFC7CE89F2A22FA2DBF78D2C0A36E549BB830C4D9FA7459E3F723CA7182"); - assertStringsEqual(accUrl, "https://finder.terra.money/tx/address/terra16t3gx5rqvz6ru37yzn3shuu20erv4ngmfr59zf"); - assertStringsEqual(id, "terra"); - assertStringsEqual(name, "Terra Classic"); -} diff --git a/tests/Tezos/AddressTests.cpp b/tests/Tezos/AddressTests.cpp deleted file mode 100644 index 9a9e5c090cf..00000000000 --- a/tests/Tezos/AddressTests.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Tezos/Address.h" -#include "HDWallet.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "Tezos/Forging.h" - -#include - -#include -#include -#include - -using namespace TW; -using namespace TW::Tezos; - -TEST(TezosAddress, forge_tz1) { - auto input = Address("tz1eZwq8b5cvE2bPKokatLkVMzkxz24z3Don"); - auto expected = "00cfa4aae60f5d9389752d41e320da224d43287fe2"; - - ASSERT_EQ(input.forge(), parse_hex(expected)); -} - -TEST(TezosAddress, forge_tz2) { - auto input = Address("tz2Rh3NYeLxrqTuvaZJmaMiVMqCajeXMWtYo"); - auto expected = "01be99dd914e38388ec80432818b517759e3524f16"; - - ASSERT_EQ(input.forge(), parse_hex(expected)); -} - -TEST(TezosAddress, forge_tz3) { - auto input = Address("tz3RDC3Jdn4j15J7bBHZd29EUee9gVB1CxD9"); - auto expected = "02358cbffa97149631cfb999fa47f0035fb1ea8636"; - - ASSERT_EQ(input.forge(), parse_hex(expected)); -} - -TEST(TezosAddress, isInvalid) { - std::array invalidAddresses { - "NmH7tmeJUmHcncBDvpr7aJNEBk7rp5zYsB1qt", // Invalid prefix, valid checksum - "tz1eZwq8b5cvE2bPKokatLkVMzkxz24z3AAAA", // Valid prefix, invalid checksum - "1tzeZwq8b5cvE2bPKokatLkVMzkxz24zAAAAA" // Invalid prefix, invalid checksum - }; - - for (auto& address : invalidAddresses) { - ASSERT_FALSE(Address::isValid(address)); - } -} - -TEST(TezosAddress, isValid) { - std::array validAddresses { - "tz1Yju7jmmsaUiG9qQLoYv35v5pHgnWoLWbt", - "tz2PdGc7U5tiyqPgTSgqCDct94qd6ovQwP6u", - "tz3VEZ4k6a4Wx42iyev6i2aVAptTRLEAivNN" - }; - - for (auto& address : validAddresses) { - ASSERT_TRUE(Address::isValid(address)); - } -} - -TEST(TezosAddress, string) { - auto addressString = "tz1d1qQL3mYVuiH4JPFvuikEpFwaDm85oabM"; - auto address = Address(addressString); - ASSERT_EQ(address.string(), addressString); -} - -TEST(TezosAddress, deriveOriginatedAddress) { - auto operationHash = "oo7VeTEPjEusPKnsHtKcGYbYa7i4RWpcEhUVo3Suugbbs6K62Ro"; - auto operationIndex = 0; - auto expected = "KT1WrtjtAYQSrUVvSNJPTZTebiUWoopQL5hw"; - - ASSERT_EQ(Address::deriveOriginatedAddress(operationHash, operationIndex), expected); -} - -TEST(TezosAddress, PublicKeyInit) { - Data bytes {1, 254, 21, 124, 200, 1, 23, 39, 147, 108, 89, 47, 133, 108, 144, 113, 211, 156, 244, 172, 218, 223, 166, 215, 100, 53, 228, 97, 156, 157, 197, 111, 99,}; - const auto publicKey = PublicKey(bytes, TWPublicKeyTypeED25519); - auto address = Address(publicKey); - - auto expected = "tz1cG2jx3W4bZFeVGBjsTxUAG8tdpTXtE8PT"; - ASSERT_EQ(address.string(), expected); -} diff --git a/tests/Tezos/ForgingTests.cpp b/tests/Tezos/ForgingTests.cpp deleted file mode 100644 index 187569f6021..00000000000 --- a/tests/Tezos/ForgingTests.cpp +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Tezos/BinaryCoding.h" -#include "Tezos/Address.h" -#include "HDWallet.h" -#include "HexCoding.h" -#include "PublicKey.h" -#include "PrivateKey.h" -#include "Tezos/Forging.h" -#include "proto/Tezos.pb.h" - -#include - -#include -#include -#include - -using namespace TW; -using namespace TW::Tezos; - -TEST(Forging, ForgeBoolTrue) { - auto expected = "ff"; - - auto output = forgeBool(true); - - ASSERT_EQ(output, parse_hex(expected)); -} - -TEST(Forging, ForgeBoolFalse) { - auto expected = "00"; - - auto output = forgeBool(false); - - ASSERT_EQ(output, parse_hex(expected)); -} - -TEST(Forging, ForgeZarithZero) { - auto expected = "00"; - - auto output = forgeZarith(0); - - ASSERT_EQ(hex(output), hex(parse_hex(expected))); -} - -TEST(Forging, ForgeZarithTen) { - auto expected = "0a"; - - auto output = forgeZarith(10); - - ASSERT_EQ(output, parse_hex(expected)); -} - -TEST(Forging, ForgeZarithTwenty) { - auto expected = "14"; - - auto output = forgeZarith(20); - - ASSERT_EQ(output, parse_hex(expected)); -} - -TEST(Forging, ForgeZarithOneHundredFifty) { - auto expected = "9601"; - - auto output = forgeZarith(150); - - ASSERT_EQ(output, parse_hex(expected)); -} - -TEST(Forging, ForgeZarithLarge) { - auto expected = "bbd08001"; - - auto output = forgeZarith(2107451); - - ASSERT_EQ(hex(output), expected); -} - -TEST(Forging, forge_tz1) { - auto expected = "00cfa4aae60f5d9389752d41e320da224d43287fe2"; - - auto output = forgePublicKeyHash("tz1eZwq8b5cvE2bPKokatLkVMzkxz24z3Don"); - - ASSERT_EQ(output, parse_hex(expected)); -} - -TEST(Forging, forge_tz2) { - auto expected = "01be99dd914e38388ec80432818b517759e3524f16"; - - auto output = forgePublicKeyHash("tz2Rh3NYeLxrqTuvaZJmaMiVMqCajeXMWtYo"); - - ASSERT_EQ(output, parse_hex(expected)); -} - -TEST(Forging, forge_tz3) { - auto expected = "02358cbffa97149631cfb999fa47f0035fb1ea8636"; - - auto output = forgePublicKeyHash("tz3RDC3Jdn4j15J7bBHZd29EUee9gVB1CxD9"); - - ASSERT_EQ(output, parse_hex(expected)); -} - -TEST(Forging, ForgePublicKey) { - auto expected = "00311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10906f511cff95"; - - auto privateKey = PrivateKey(parse_hex("c6377a4cc490dc913fc3f0d9cf67d293a32df4547c46cb7e9e33c3b7b97c64d8")); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - auto output = forgePublicKey(publicKey); - - ASSERT_EQ(hex(output), expected); -} - - -TEST(TezosTransaction, forgeTransaction) { - auto transactionOperationData = new TW::Tezos::Proto::TransactionOperationData(); - transactionOperationData -> set_amount(1); - transactionOperationData -> set_destination("tz1Yju7jmmsaUiG9qQLoYv35v5pHgnWoLWbt"); - - auto transactionOperation = TW::Tezos::Proto::Operation(); - transactionOperation.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); - transactionOperation.set_fee(1272); - transactionOperation.set_counter(30738); - transactionOperation.set_gas_limit(10100); - transactionOperation.set_storage_limit(257); - transactionOperation.set_kind(TW::Tezos::Proto::Operation::TRANSACTION); - transactionOperation.set_allocated_transaction_operation_data(transactionOperationData); - - auto expected = "6c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e81020100008fb5cea62d147c696afd9a93dbce962f4c8a9c9100"; - auto serialized = forgeOperation(transactionOperation); - - ASSERT_EQ(hex(serialized.begin(), serialized.end()), expected); -} - -TEST(TezosTransaction, forgeReveal) { - PublicKey publicKey = parsePublicKey("edpku9ZF6UUAEo1AL3NWy1oxHLL6AfQcGYwA5hFKrEKVHMT3Xx889A"); - - auto revealOperationData = new TW::Tezos::Proto::RevealOperationData(); - revealOperationData -> set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); - - auto revealOperation = TW::Tezos::Proto::Operation(); - revealOperation.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); - revealOperation.set_fee(1272); - revealOperation.set_counter(30738); - revealOperation.set_gas_limit(10100); - revealOperation.set_storage_limit(257); - revealOperation.set_kind(TW::Tezos::Proto::Operation::REVEAL); - revealOperation.set_allocated_reveal_operation_data(revealOperationData); - - auto expected = "6b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200429a986c8072a40a1f3a3e2ab5a5819bb1b2fb69993c5004837815b9dc55923e"; - auto serialized = forgeOperation(revealOperation); - - ASSERT_EQ(hex(serialized.begin(), serialized.end()), expected); -} - -TEST(TezosTransaction, forgeDelegate) { - auto delegateOperationData = new TW::Tezos::Proto::DelegationOperationData(); - delegateOperationData->set_delegate("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); - - auto delegateOperation = TW::Tezos::Proto::Operation(); - delegateOperation.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); - delegateOperation.set_fee(1272); - delegateOperation.set_counter(30738); - delegateOperation.set_gas_limit(10100); - delegateOperation.set_storage_limit(257); - delegateOperation.set_kind(TW::Tezos::Proto::Operation::DELEGATION); - delegateOperation.set_allocated_delegation_operation_data(delegateOperationData); - - auto expected = "6e0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e8102ff003e47f837f0467b4acde406ed5842f35e2414b1a8"; - auto serialized = forgeOperation(delegateOperation); - - ASSERT_EQ(hex(serialized.begin(), serialized.end()), expected); -} - -TEST(TezosTransaction, forgeUndelegate) { - auto delegateOperationData = new TW::Tezos::Proto::DelegationOperationData(); - delegateOperationData->set_delegate(""); - - auto delegateOperation = TW::Tezos::Proto::Operation(); - delegateOperation.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); - delegateOperation.set_fee(1272); - delegateOperation.set_counter(30738); - delegateOperation.set_gas_limit(10100); - delegateOperation.set_storage_limit(257); - delegateOperation.set_kind(TW::Tezos::Proto::Operation::DELEGATION); - delegateOperation.set_allocated_delegation_operation_data(delegateOperationData); - - auto expected = "6e0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200"; - auto serialized = forgeOperation(delegateOperation); - - ASSERT_EQ(hex(serialized.begin(), serialized.end()), expected); -} \ No newline at end of file diff --git a/tests/Tezos/OperationListTests.cpp b/tests/Tezos/OperationListTests.cpp deleted file mode 100644 index db57ea1da0e..00000000000 --- a/tests/Tezos/OperationListTests.cpp +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Tezos/Address.h" -#include "Tezos/BinaryCoding.h" -#include "Tezos/OperationList.h" -#include "proto/Tezos.pb.h" -#include "HexCoding.h" - -#include - -using namespace TW::Tezos; -using namespace TW::Tezos::Proto; - -TEST(TezosOperationList, ForgeBranch) { - auto input = TW::Tezos::OperationList("BMNY6Jkas7BzKb7wDLCFoQ4YxfYoieU7Xmo1ED3Y9Lo3ZvVGdgW"); - auto expected = "da8eb4f57f98a647588b47d29483d1edfdbec1428c11609cee0da6e0f27cfc38"; - - ASSERT_EQ(input.forgeBranch(), parse_hex(expected)); -} - -TEST(TezosOperationList, ForgeOperationList_TransactionOnly) { - auto branch = "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"; - auto op_list = TW::Tezos::OperationList(branch); - auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); - - auto transactionOperationData = new TW::Tezos::Proto::TransactionOperationData(); - transactionOperationData -> set_amount(1); - transactionOperationData -> set_destination("tz1Yju7jmmsaUiG9qQLoYv35v5pHgnWoLWbt"); - - auto transactionOperation = TW::Tezos::Proto::Operation(); - transactionOperation.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); - transactionOperation.set_fee(1272); - transactionOperation.set_counter(30738); - transactionOperation.set_gas_limit(10100); - transactionOperation.set_storage_limit(257); - transactionOperation.set_kind(TW::Tezos::Proto::Operation::TRANSACTION); - transactionOperation.set_allocated_transaction_operation_data(transactionOperationData); - - op_list.addOperation(transactionOperation); - - auto expected = "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e81020100008fb5cea62d147c696afd9a93dbce962f4c8a9c9100"; - auto forged = op_list.forge(key); - ASSERT_EQ(hex(forged.begin(), forged.end()), expected); -} - -TEST(TezosOperationList, ForgeOperationList_RevealOnly) { - auto branch = "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"; - auto op_list = TW::Tezos::OperationList(branch); - auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); - PublicKey publicKey = parsePublicKey("edpku9ZF6UUAEo1AL3NWy1oxHLL6AfQcGYwA5hFKrEKVHMT3Xx889A"); - - auto revealOperationData = new TW::Tezos::Proto::RevealOperationData(); - revealOperationData -> set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); - - auto revealOperation = TW::Tezos::Proto::Operation(); - revealOperation.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); - revealOperation.set_fee(1272); - revealOperation.set_counter(30738); - revealOperation.set_gas_limit(10100); - revealOperation.set_storage_limit(257); - revealOperation.set_kind(TW::Tezos::Proto::Operation::REVEAL); - revealOperation.set_allocated_reveal_operation_data(revealOperationData); - - op_list.addOperation(revealOperation); - auto expected = "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200429a986c8072a40a1f3a3e2ab5a5819bb1b2fb69993c5004837815b9dc55923e"; - auto forged = op_list.forge(key); - - ASSERT_EQ(hex(forged.begin(), forged.end()), expected); -} - -TEST(TezosOperationList, ForgeOperationList_Delegation_ClearDelegate) { - auto branch = "BLGJfQDFEYZBRLj5GSHskj8NPaRYhk7Kx5WAfdcDucD3q98WdeW"; - auto op_list = TW::Tezos::OperationList(branch); - auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); - auto delegationOperationData = new TW::Tezos::Proto::DelegationOperationData(); - delegationOperationData->set_delegate("tz1gSM6yiwr85jEASZ1q3UekgHEoxYt7wg2M"); - - auto delegationOperation = TW::Tezos::Proto::Operation(); - delegationOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); - delegationOperation.set_fee(1257); - delegationOperation.set_counter(67); - delegationOperation.set_gas_limit(10000); - delegationOperation.set_storage_limit(0); - delegationOperation.set_kind(TW::Tezos::Proto::Operation::DELEGATION); - delegationOperation.set_allocated_delegation_operation_data(delegationOperationData); - - op_list.addOperation(delegationOperation); - - auto expected = "48b63d801fa824013a195f7885ba522503c59e0580f7663e15c52f03ccc935e66e003e47f837f0467b4acde406ed5842f35e2414b1a8e90943904e00ff00e42504da69a7c8d5baeaaeebe157a02db6b22ed8"; - ASSERT_EQ(hex(op_list.forge(key)), expected); -} - -TEST(TezosOperationList, ForgeOperationList_Delegation_AddDelegate) { - auto branch = "BLa4GrVQTxUgQWbHv6cF7RXWSGzHGPbgecpQ795R3cLzw4cGfpD"; - auto op_list = TW::Tezos::OperationList(branch); - auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); - auto delegationOperationData = new TW::Tezos::Proto::DelegationOperationData(); - delegationOperationData -> set_delegate("tz1dYUCcrorfCoaQCtZaxi1ynGrP3prTZcxS"); - - auto delegationOperation = TW::Tezos::Proto::Operation(); - delegationOperation.set_source("KT1D5jmrBD7bDa3jCpgzo32FMYmRDdK2ihka"); - delegationOperation.set_fee(1257); - delegationOperation.set_counter(68); - delegationOperation.set_gas_limit(10000); - delegationOperation.set_storage_limit(0); - delegationOperation.set_kind(TW::Tezos::Proto::Operation::DELEGATION); - delegationOperation.set_allocated_delegation_operation_data(delegationOperationData); - - op_list.addOperation(delegationOperation); - auto expected = "7105102c032807994dd9b5edf219261896a559876ca16cbf9d31dbe3612b89f26e00315b1206ec00b1b1e64cc3b8b93059f58fa2fc39e90944904e00ff00c4650fd609f88c67356e5fe01e37cd3ff654b18c"; - auto forged = op_list.forge(key); - ASSERT_EQ(hex(forged.begin(), forged.end()), expected); -} - -TEST(TezosOperationList, ForgeOperationList_TransactionAndReveal) { - auto branch = "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"; - auto op_list = TW::Tezos::OperationList(branch); - auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); - auto publicKey = parsePublicKey("edpkuNb9N2UHtGeSc2BZCBHN8ETx7E4DwkSfz5Hw3m3tF3dLZTU8qp"); - - auto revealOperationData = new TW::Tezos::Proto::RevealOperationData(); - revealOperationData -> set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); - - auto revealOperation = TW::Tezos::Proto::Operation(); - revealOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); - revealOperation.set_fee(1272); - revealOperation.set_counter(30738); - revealOperation.set_gas_limit(10100); - revealOperation.set_storage_limit(257); - revealOperation.set_kind(TW::Tezos::Proto::Operation::REVEAL); - revealOperation.set_allocated_reveal_operation_data(revealOperationData); - - auto transactionOperationData = new TW::Tezos::Proto::TransactionOperationData(); - transactionOperationData -> set_amount(1); - transactionOperationData -> set_destination("tz1gSM6yiwr85jEASZ1q3UekgHEoxYt7wg2M"); - - auto transactionOperation = TW::Tezos::Proto::Operation(); - transactionOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); - transactionOperation.set_fee(1272); - transactionOperation.set_counter(30739); - transactionOperation.set_gas_limit(10100); - transactionOperation.set_storage_limit(257); - transactionOperation.set_kind(TW::Tezos::Proto::Operation::TRANSACTION); - transactionOperation.set_allocated_transaction_operation_data(transactionOperationData); - - op_list.addOperation(revealOperation); - op_list.addOperation(transactionOperation); - - auto expected = "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b003e47f837f0467b4acde406ed5842f35e2414b1a8f80992f001f44e810200603247bbf52501498293686da89ad8b2aca85f83b90903d4521dd2aba66054eb6c003e47f837f0467b4acde406ed5842f35e2414b1a8f80993f001f44e8102010000e42504da69a7c8d5baeaaeebe157a02db6b22ed800"; - auto forged = op_list.forge(key); - - ASSERT_EQ(hex(forged.begin(), forged.end()), expected); -} - -TEST(TezosOperationList, ForgeOperationList_RevealWithoutPublicKey) { - auto branch = "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"; - auto op_list = TW::Tezos::OperationList(branch); - auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); - - auto revealOperationData = new TW::Tezos::Proto::RevealOperationData(); - - auto revealOperation = TW::Tezos::Proto::Operation(); - revealOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); - revealOperation.set_fee(1272); - revealOperation.set_counter(30738); - revealOperation.set_gas_limit(10100); - revealOperation.set_storage_limit(257); - revealOperation.set_kind(TW::Tezos::Proto::Operation::REVEAL); - revealOperation.set_allocated_reveal_operation_data(revealOperationData); - - op_list.addOperation(revealOperation); - - auto expected = "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b003e47f837f0467b4acde406ed5842f35e2414b1a8f80992f001f44e810200603247bbf52501498293686da89ad8b2aca85f83b90903d4521dd2aba66054eb"; - auto forged = op_list.forge(key); - - ASSERT_EQ(hex(forged.begin(), forged.end()), expected); -} diff --git a/tests/Tezos/PublicKeyTests.cpp b/tests/Tezos/PublicKeyTests.cpp deleted file mode 100644 index baac0151631..00000000000 --- a/tests/Tezos/PublicKeyTests.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Tezos/BinaryCoding.h" -#include "Tezos/Forging.h" -#include "PublicKey.h" -#include "Data.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Tezos; - -TEST(TezosPublicKey, forge) { - auto input = parsePublicKey("edpkuAfEJCEatRgFpRGg3gn3FdWniLXBoubARreRwuVZPWufkgDBvR"); - auto expected = "00451bde832454ba73e6e0de313fcf5d1565ec51080edc73bb19287b8e0ab2122b"; - auto serialized = forgePublicKey(input); - ASSERT_EQ(hex(serialized.begin(), serialized.end()), expected); -} - -TEST(TezosPublicKey, parse) { - auto input = "edpkuAfEJCEatRgFpRGg3gn3FdWniLXBoubARreRwuVZPWufkgDBvR"; - auto bytes = Data({1, 69, 27, 222, 131, 36, 84, 186, 115, 230, 224, 222, 49, 63, 207, 93, 21, 101, 236, 81, 8, 14, 220, 115, 187, 25, 40, 123, 142, 10, 178, 18, 43}); - auto output = parsePublicKey(input); - auto expected = PublicKey(bytes, TWPublicKeyTypeED25519); - ASSERT_EQ(output, expected); -} diff --git a/tests/Tezos/SignerTests.cpp b/tests/Tezos/SignerTests.cpp deleted file mode 100644 index 987d3a573c8..00000000000 --- a/tests/Tezos/SignerTests.cpp +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Tezos/BinaryCoding.h" -#include "Tezos/OperationList.h" -#include "Tezos/Signer.h" -#include "PrivateKey.h" -#include "Base58.h" -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Tezos; - -TEST(TezosSigner, SignString) { - Data bytesToSign = parse_hex("ffaa"); - Data expectedSignature = parse_hex("eaab7f4066217b072b79609a9f76cdfadd93f8dde41763887e131c02324f18c8e41b1009e334baf87f9d2e917bf4c0e73165622e5522409a0c5817234a48cc02"); - Data expected = Data(); - append(expected, bytesToSign); - append(expected, expectedSignature); - - auto key = PrivateKey(parse_hex("0x2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f")); - auto signedBytes = Signer().signData(key, bytesToSign); - - ASSERT_EQ(signedBytes, expected); -} - -TEST(TezosSigner, SignOperationList) { - auto branch = "BLDnkhhVgwdBAtmDNQc5HtEMsrxq8L3t7NQbjUbbdTdw5Ug1Mpe"; - auto op_list = Tezos::OperationList(branch); - - auto transactionOperationData = new Proto::TransactionOperationData(); - transactionOperationData->set_amount(11100000); - transactionOperationData->set_destination("tz1gSM6yiwr85jEASZ1q3UekgHEoxYt7wg2M"); - - auto transactionOperation = TW::Tezos::Proto::Operation(); - transactionOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); - transactionOperation.set_fee(1283); - transactionOperation.set_counter(1878); - transactionOperation.set_gas_limit(10307); - transactionOperation.set_storage_limit(0); - transactionOperation.set_kind(Proto::Operation::TRANSACTION); - transactionOperation.set_allocated_transaction_operation_data(transactionOperationData); - - op_list.addOperation(transactionOperation); - - PublicKey publicKey = parsePublicKey("edpkuNb9N2UHtGeSc2BZCBHN8ETx7E4DwkSfz5Hw3m3tF3dLZTU8qp"); - auto revealOperationData = new TW::Tezos::Proto::RevealOperationData(); - revealOperationData->set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); - - auto revealOperation = Proto::Operation(); - revealOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); - revealOperation.set_fee(1268); - revealOperation.set_counter(1876); - revealOperation.set_gas_limit(10100); - revealOperation.set_storage_limit(0); - revealOperation.set_kind(Proto::Operation::REVEAL); - revealOperation.set_allocated_reveal_operation_data(revealOperationData); - - op_list.addOperation(revealOperation); - - auto delegateOperationData = new Tezos::Proto::DelegationOperationData(); - delegateOperationData->set_delegate("tz1gSM6yiwr85jEASZ1q3UekgHEoxYt7wg2M"); - - auto delegateOperation = Proto::Operation(); - delegateOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); - delegateOperation.set_fee(1257); - delegateOperation.set_counter(1879); - delegateOperation.set_gas_limit(10100); - delegateOperation.set_storage_limit(0); - delegateOperation.set_kind(Proto::Operation::DELEGATION); - delegateOperation.set_allocated_delegation_operation_data(delegateOperationData); - - op_list.addOperation(delegateOperation); - - auto decodedPrivateKey = Base58::bitcoin.decodeCheck("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); - auto key = PrivateKey(Data(decodedPrivateKey.begin() + 4, decodedPrivateKey.end())); - - std::string expectedForgedBytesToSign = hex(op_list.forge(key)); - std::string expectedSignature = "871693145f2dc72861ff6816e7ac3ce93c57611ac09a4c657a5a35270fa57153334c14cd8cae94ee228b6ef52f0e3f10948721e666318bc54b6c455404b11e03"; - std::string expectedSignedBytes = expectedForgedBytesToSign + expectedSignature; - - auto signedBytes = Signer().signOperationList(key, op_list); - auto signedBytesHex = hex(signedBytes.begin(), signedBytes.end()); - - ASSERT_EQ(hex(signedBytes.begin(), signedBytes.end()), expectedSignedBytes); -} diff --git a/tests/Tezos/TWAnySignerTests.cpp b/tests/Tezos/TWAnySignerTests.cpp deleted file mode 100644 index 4f2627ed2d7..00000000000 --- a/tests/Tezos/TWAnySignerTests.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "proto/Tezos.pb.h" -#include "../interface/TWTestUtilities.h" -#include - -#include - -using namespace TW; -using namespace TW::Tezos; - -TEST(TWAnySignerTezos, Sign) { - auto key = parse_hex("2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f"); - auto revealKey = parse_hex("311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10906f511cff95"); - - Proto::SigningInput input; - input.set_private_key(key.data(), key.size()); - auto& operations = *input.mutable_operation_list(); - operations.set_branch("BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"); - - auto& reveal = *operations.add_operations(); - auto& revealData = *reveal.mutable_reveal_operation_data(); - revealData.set_public_key(revealKey.data(), revealKey.size()); - reveal.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); - reveal.set_fee(1272); - reveal.set_counter(30738); - reveal.set_gas_limit(10100); - reveal.set_storage_limit(257); - reveal.set_kind(Proto::Operation::REVEAL); - - auto& transaction = *operations.add_operations(); - auto& txData = *transaction.mutable_transaction_operation_data(); - txData.set_amount(1); - txData.set_destination("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); - transaction.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); - transaction.set_fee(1272); - transaction.set_counter(30739); - transaction.set_gas_limit(10100); - transaction.set_storage_limit(257); - transaction.set_kind(Proto::Operation::TRANSACTION); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeTezos); - - EXPECT_EQ(hex(output.encoded()), "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10906f511cff956c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80993f001f44e810201000081faa75f741ef614b0e35fcc8c90dfa3b0b95721000217034271b815e5f0c0a881342838ce49d7b48cdf507c72b1568c69a10db70c98774cdad1a74df760763e25f760ff13afcbbf3a1f2c833a0beeb9576a579c05"); -} - -TEST(TWAnySignerTezos, SignJSON) { - auto json = STRING(R"({"operationList": {"branch": "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp","operations": [{"source": "tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW","fee": 1272,"counter": 30738,"gasLimit": 10100,"storageLimit": 257,"kind": 107,"revealOperationData": {"publicKey": "QpqYbIBypAofOj4qtaWBm7Gy+2mZPFAEg3gVudxVkj4="}},{"source": "tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW","fee": 1272,"counter": 30739,"gasLimit": 10100,"storageLimit": 257,"kind": 108,"transactionOperationData": {"destination": "tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW","amount": 1}}]}})"); - auto key = DATA("2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f"); - auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeTezos)); - - ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeTezos)); - assertStringsEqual(result, "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200429a986c8072a40a1f3a3e2ab5a5819bb1b2fb69993c5004837815b9dc55923e6c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80993f001f44e810201000081faa75f741ef614b0e35fcc8c90dfa3b0b957210001b86398d5b9be737dca8e4106ea18d70e69b75e92f892fb283546a99152b8d7794b919c0fbf1c31de386069a60014491c0e7505adef5781cead1cfe6608030b"); -} diff --git a/tests/Tezos/TWCoinTypeTests.cpp b/tests/Tezos/TWCoinTypeTests.cpp deleted file mode 100644 index a8d44065d59..00000000000 --- a/tests/Tezos/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWTezosCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTezos)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("onk3Z6V4StyfiXTPSHwZFvTKVAaws37cHmZacmULPr3VbVHpKrg")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTezos, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("tz1SiPXX4MYGNJNDsRc7n8hkvUqFzg8xqF9m")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTezos, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTezos)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTezos)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTezos), 6); - ASSERT_EQ(TWBlockchainTezos, TWCoinTypeBlockchain(TWCoinTypeTezos)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTezos)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTezos)); - assertStringsEqual(symbol, "XTZ"); - assertStringsEqual(txUrl, "https://tzstats.com/onk3Z6V4StyfiXTPSHwZFvTKVAaws37cHmZacmULPr3VbVHpKrg"); - assertStringsEqual(accUrl, "https://tzstats.com/tz1SiPXX4MYGNJNDsRc7n8hkvUqFzg8xqF9m"); - assertStringsEqual(id, "tezos"); - assertStringsEqual(name, "Tezos"); -} diff --git a/tests/Theta/SignerTests.cpp b/tests/Theta/SignerTests.cpp deleted file mode 100644 index 3f6cb122db8..00000000000 --- a/tests/Theta/SignerTests.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "Theta/Signer.h" - -#include - -namespace TW::Theta { - -using boost::multiprecision::uint256_t; - -TEST(Signer, Sign) { - const auto pkFrom = - PrivateKey(parse_hex("0x93a90ea508331dfdf27fb79757d4250b4e84954927ba0073cd67454ac432c737")); - const auto from = Ethereum::Address("0x2E833968E5bB786Ae419c4d13189fB081Cc43bab"); - const auto to = Ethereum::Address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); - auto transaction = Transaction(from, to, 10, 20, 1); - - auto signer = Signer("privatenet"); - auto signature = signer.sign(pkFrom, transaction); - transaction.setSignature(from, signature); - - ASSERT_EQ(hex(signature), "5190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8" - "fe9134ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501"); - ASSERT_EQ(hex(transaction.encode()), - "02f887c78085e8d4a51000f863f861942e833968e5bb786ae419c4d13189fb081cc43babc70a85e8d4a5" - "101401b8415190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134" - "ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501d9d8949f1233798e905e17356007" - "1255140b4a8abd3ec6c20a14"); -} - -} // namespace TW::Theta diff --git a/tests/Theta/TWAnySignerTests.cpp b/tests/Theta/TWAnySignerTests.cpp deleted file mode 100644 index 92d748c6ebc..00000000000 --- a/tests/Theta/TWAnySignerTests.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "uint256.h" -#include "proto/Theta.pb.h" -#include - -#include "../interface/TWTestUtilities.h" -#include - -using namespace TW; -using namespace TW::Theta; - -TEST(TWAnySignerTheta, Sign) { - auto privateKey = parse_hex("93a90ea508331dfdf27fb79757d4250b4e84954927ba0073cd67454ac432c737"); - - Proto::SigningInput input; - input.set_chain_id("privatenet"); - input.set_to_address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); - auto amount = store(uint256_t(10)); - input.set_theta_amount(amount.data(), amount.size()); - auto tfuelAmount = store(uint256_t(20)); - input.set_tfuel_amount(tfuelAmount.data(), tfuelAmount.size()); - auto fee = store(uint256_t(1000000000000)); - input.set_fee(fee.data(), fee.size()); - input.set_sequence(1); - input.set_private_key(privateKey.data(), privateKey.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeTheta); - - ASSERT_EQ(hex(output.encoded()), "02f887c78085e8d4a51000f863f861942e833968e5bb786ae419c4d13189fb081cc43babc70a85e8d4a5101401b8415190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501d9d8949f1233798e905e173560071255140b4a8abd3ec6c20a14"); -} diff --git a/tests/Theta/TWCoinTypeTests.cpp b/tests/Theta/TWCoinTypeTests.cpp deleted file mode 100644 index 55ed4e35b38..00000000000 --- a/tests/Theta/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWThetaCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTheta)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTheta, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTheta, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTheta)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTheta)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTheta), 18); - ASSERT_EQ(TWBlockchainTheta, TWCoinTypeBlockchain(TWCoinTypeTheta)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTheta)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTheta)); - assertStringsEqual(symbol, "THETA"); - assertStringsEqual(txUrl, "https://explorer.thetatoken.org/txs/t123"); - assertStringsEqual(accUrl, "https://explorer.thetatoken.org/account/a12"); - assertStringsEqual(id, "theta"); - assertStringsEqual(name, "Theta"); -} diff --git a/tests/Theta/TransactionTests.cpp b/tests/Theta/TransactionTests.cpp deleted file mode 100644 index 19699afcc78..00000000000 --- a/tests/Theta/TransactionTests.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Theta/Transaction.h" - -#include "HexCoding.h" - -#include - -using namespace TW; -using namespace TW::Theta; - -TEST(ThetaTransaction, Encode) { - const auto from = Ethereum::Address("0x2E833968E5bB786Ae419c4d13189fB081Cc43bab"); - const auto to = Ethereum::Address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); - auto transaction = Transaction(from, to, 10, 20, 1); - ASSERT_EQ(hex(transaction.encode()), - "02f843c78085e8d4a51000e0df942e833968e5bb786ae419c4d13189fb081cc43babc70a85e8d4a51014" - "0180d9d8949f1233798e905e173560071255140b4a8abd3ec6c20a14"); -} - -TEST(ThetaTransaction, EncodeWithSignature) { - const auto from = Ethereum::Address("0x2E833968E5bB786Ae419c4d13189fB081Cc43bab"); - const auto to = Ethereum::Address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); - auto transaction = Transaction(from, to, 10, 20, 1); - transaction.setSignature( - from, parse_hex("5190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134" - "ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501")); - ASSERT_EQ(hex(transaction.encode()), - "02f887c78085e8d4a51000f863f861942e833968e5bb786ae419c4d13189fb081cc43babc70a85e8d4a5" - "101401b8415190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134" - "ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501d9d8949f1233798e905e17356007" - "1255140b4a8abd3ec6c20a14"); -} diff --git a/tests/ThunderToken/TWCoinTypeTests.cpp b/tests/ThunderToken/TWCoinTypeTests.cpp deleted file mode 100644 index 3b31872c8c3..00000000000 --- a/tests/ThunderToken/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWThunderTokenCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeThunderToken)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeThunderToken, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeThunderToken, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeThunderToken)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeThunderToken)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeThunderToken), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeThunderToken)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeThunderToken)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeThunderToken)); - assertStringsEqual(symbol, "TT"); - assertStringsEqual(txUrl, "https://scan.thundercore.com/transactions/t123"); - assertStringsEqual(accUrl, "https://scan.thundercore.com/address/a12"); - assertStringsEqual(id, "thundertoken"); - assertStringsEqual(name, "Thunder Token"); -} diff --git a/tests/TomoChain/TWCoinTypeTests.cpp b/tests/TomoChain/TWCoinTypeTests.cpp deleted file mode 100644 index 0cb99362b04..00000000000 --- a/tests/TomoChain/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWTomoChainCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTomoChain)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x35a8d3ab06c94d5b7d27221b7c9a24ba3f1710dd0fcfd75c5d59b3a885fd709b")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTomoChain, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x86cCbD9bfb371c355202086882bC644A7D0b024B")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTomoChain, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTomoChain)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTomoChain)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTomoChain), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeTomoChain)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTomoChain)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTomoChain)); - assertStringsEqual(symbol, "TOMO"); - assertStringsEqual(txUrl, "https://tomoscan.io/tx/0x35a8d3ab06c94d5b7d27221b7c9a24ba3f1710dd0fcfd75c5d59b3a885fd709b"); - assertStringsEqual(accUrl, "https://tomoscan.io/address/0x86cCbD9bfb371c355202086882bC644A7D0b024B"); - assertStringsEqual(id, "tomochain"); - assertStringsEqual(name, "TomoChain"); -} diff --git a/tests/TransactionCompilerTests.cpp b/tests/TransactionCompilerTests.cpp deleted file mode 100644 index fae197ae817..00000000000 --- a/tests/TransactionCompilerTests.cpp +++ /dev/null @@ -1,437 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include "TransactionCompiler.h" -#include "Coin.h" -#include "proto/Common.pb.h" -#include "proto/Binance.pb.h" -#include "proto/Bitcoin.pb.h" -#include "proto/Ethereum.pb.h" -#include "proto/TransactionCompiler.pb.h" - -#include -#include "Bitcoin/Script.h" -#include "Bitcoin/SegwitAddress.h" - -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include "uint256.h" -#include - -#include "interface/TWTestUtilities.h" -#include - -using namespace TW; - -TEST(TransactionCompiler, BinanceCompileWithSignatures) { - /// Step 1: Prepare transaction input (protobuf) - const auto coin = TWCoinTypeBinance; - const auto txInputData = TransactionCompiler::buildInput( - coin, - "bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", // from - "bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5", // to - "1", // amount - "BNB", // asset - "", // memo - "Binance-Chain-Nile" // testnet chainId - ); - - { - // Check, by parsing - EXPECT_EQ(txInputData.size(), 88); - Binance::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); - EXPECT_EQ(input.chain_id(), "Binance-Chain-Nile"); - EXPECT_TRUE(input.has_send_order()); - ASSERT_EQ(input.send_order().inputs_size(), 1); - EXPECT_EQ(hex(data(input.send_order().inputs(0).address())), "40c2979694bbc961023d1d27be6fc4d21a9febe6"); - } - - /// Step 2: Obtain preimage hash - const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); - ASSERT_GT(preImageHashes.size(), 0); - - TxCompiler::Proto::PreSigningOutput output; - ASSERT_TRUE(output.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); - ASSERT_EQ(output.error(), 0); - - auto preImageHash = data(output.data_hash()); - EXPECT_EQ(hex(preImageHash), "3f3fece9059e714d303a9a1496ddade8f2c38fa78fc4cc2e505c5dbb0ea678d1"); - - // Simulate signature, normally obtained from signature server - const auto publicKeyData = parse_hex("026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502"); - const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); - const auto signature = parse_hex("1b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679"); - - // Verify signature (pubkey & hash & signature) - { - EXPECT_TRUE(publicKey.verify(signature, preImageHash)); - } - - /// Step 3: Compile transaction info - const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKeyData}); - - const auto ExpectedTx = "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679"; - { - EXPECT_EQ(outputData.size(), 189); - Binance::Proto::SigningOutput output; - ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); - - EXPECT_EQ(hex(output.encoded()), ExpectedTx); - } - - { // Double check: check if simple signature process gives the same result. Note that private keys were not used anywhere up to this point. - Binance::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); - auto key = parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"); - input.set_private_key(key.data(), key.size()); - - Binance::Proto::SigningOutput output; - ANY_SIGN(input, coin); - - ASSERT_EQ(hex(output.encoded()), ExpectedTx); - } -} - -TEST(TransactionCompiler, BitcoinCompileWithSignatures) { - // Test external signining with a Bircoin transaction with 3 input UTXOs, all used, but only using 2 public keys. - // Three signatures are neeeded. This illustrates that order of UTXOs/hashes is not always the same. - - const auto revUtxoHash0 = parse_hex("07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8"); - const auto revUtxoHash1 = parse_hex("d6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e"); - const auto revUtxoHash2 = parse_hex("6021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d"); - const auto inPubKey0 = parse_hex("024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382"); - const auto inPubKey1 = parse_hex("0217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc"); - const auto inPubKeyHash0 = parse_hex("bd92088bb7e82d611a9b94fbb74a0908152b784f"); - const auto inPubKeyHash1 = parse_hex("6641abedacf9483b793afe1718689cc9420bbb1c"); - - // Input UTXO infos - struct UtxoInfo { - Data revUtxoHash; - Data publicKey; - long amount; - int index; - }; - std::vector utxoInfos = { - // first - UtxoInfo {revUtxoHash0, inPubKey0, 600'000, 0}, - // second UTXO, with same pubkey - UtxoInfo {revUtxoHash1, inPubKey0, 500'000, 1}, - // third UTXO, with different pubkey - UtxoInfo {revUtxoHash2, inPubKey1, 400'000, 0}, - }; - - // Signature infos, indexed by pubkeyhash+hash - struct SignatureInfo { - Data signature; - Data publicKey; - }; - std::map signatureInfos = { - { - hex(inPubKeyHash0) + "+" + "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7", - { - parse_hex("304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a40"), - inPubKey0, - } - }, - { - hex(inPubKeyHash1) + "+" + "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6", - { - parse_hex("3044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e4"), - inPubKey1, - } - }, - { - hex(inPubKeyHash0) + "+" + "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101", - { - parse_hex("30440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc"), - inPubKey0, - } - }, - }; - - const auto coin = TWCoinTypeBitcoin; - const auto ownAddress = "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv"; - - // Setup input for Plan - Bitcoin::Proto::SigningInput input; - input.set_coin_type(coin); - input.set_hash_type(TWBitcoinSigHashTypeAll); - input.set_amount(1'200'000); - input.set_use_max_amount(false); - input.set_byte_fee(1); - input.set_to_address("bc1q2dsdlq3343vk29runkgv4yc292hmq53jedfjmp"); - input.set_change_address(ownAddress); - - // process UTXOs - int count = 0; - for (auto& u: utxoInfos) { - const auto publicKey = PublicKey(u.publicKey, TWPublicKeyTypeSECP256k1); - const auto address = Bitcoin::SegwitAddress(publicKey, "bc"); - if (count == 0) EXPECT_EQ(address.string(), ownAddress); - if (count == 1) EXPECT_EQ(address.string(), ownAddress); - if (count == 2) EXPECT_EQ(address.string(), "bc1qveq6hmdvl9yrk7f6lct3s6yue9pqhwcuxedggg"); - - const auto utxoScript = Bitcoin::Script::lockScriptForAddress(address.string(), coin); - if (count == 0) EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); - if (count == 1) EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); - if (count == 2) EXPECT_EQ(hex(utxoScript.bytes), "00146641abedacf9483b793afe1718689cc9420bbb1c"); - - Data keyHash; - EXPECT_TRUE(utxoScript.matchPayToWitnessPublicKeyHash(keyHash)); - if (count == 0) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); - if (count == 1) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); - if (count == 2) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash1)); - - const auto redeemScript = Bitcoin::Script::buildPayToPublicKeyHash(keyHash); - if (count == 0) EXPECT_EQ(hex(redeemScript.bytes), "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac"); - if (count == 1) EXPECT_EQ(hex(redeemScript.bytes), "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac"); - if (count == 2) EXPECT_EQ(hex(redeemScript.bytes), "76a9146641abedacf9483b793afe1718689cc9420bbb1c88ac"); - (*input.mutable_scripts())[hex(keyHash)] = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - - auto utxo = input.add_utxo(); - utxo->set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); - utxo->set_amount(u.amount); - utxo->mutable_out_point()->set_hash(std::string(u.revUtxoHash.begin(), u.revUtxoHash.end())); - utxo->mutable_out_point()->set_index(u.index); - utxo->mutable_out_point()->set_sequence(UINT32_MAX); - - ++count; - } - EXPECT_EQ(count, 3); - EXPECT_EQ(input.utxo_size(), 3); - - // Plan - Bitcoin::Proto::TransactionPlan plan; - ANY_PLAN(input, plan, coin); - - // Plan is checked, assume it is accepted - EXPECT_EQ(plan.amount(), 1'200'000); - EXPECT_EQ(plan.fee(), 277); - EXPECT_EQ(plan.change(), 299'723); - ASSERT_EQ(plan.utxos_size(), 3); - // Note that UTXOs happen to be in reverse order compared to the input - EXPECT_EQ(hex(plan.utxos(0).out_point().hash()), hex(revUtxoHash2)); - EXPECT_EQ(hex(plan.utxos(1).out_point().hash()), hex(revUtxoHash1)); - EXPECT_EQ(hex(plan.utxos(2).out_point().hash()), hex(revUtxoHash0)); - - // Extend input with accepted plan - *input.mutable_plan() = plan; - - // Serialize input - const auto txInputData = data(input.SerializeAsString()); - EXPECT_EQ((int)txInputData.size(), 692); - - /// Step 2: Obtain preimage hashes - const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); - TW::Bitcoin::Proto::PreSigningOutput output; - ASSERT_TRUE(output.ParseFromArray(preImageHashes.data(), (int)preImageHashes.size())); - - ASSERT_EQ(output.error(), 0); - EXPECT_EQ(hex(output.hash_public_keys()[0].data_hash()), "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6"); - EXPECT_EQ(hex(output.hash_public_keys()[1].data_hash()), "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7"); - EXPECT_EQ(hex(output.hash_public_keys()[2].data_hash()), "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101"); - EXPECT_EQ(hex(output.hash_public_keys()[0].public_key_hash()), hex(inPubKeyHash1)); - EXPECT_EQ(hex(output.hash_public_keys()[1].public_key_hash()), hex(inPubKeyHash0)); - EXPECT_EQ(hex(output.hash_public_keys()[2].public_key_hash()), hex(inPubKeyHash0)); - - // Simulate signatures, normally obtained from signature server. - std::vector signatureVec; - std::vector pubkeyVec; - for (const auto& h: output.hash_public_keys()) { - const auto& preImageHash = h.data_hash(); - const auto& pubkeyhash = h.public_key_hash(); - - const std::string key = hex(pubkeyhash) + "+" + hex(preImageHash); - const auto sigInfoFind = signatureInfos.find(key); - ASSERT_TRUE(sigInfoFind != signatureInfos.end()); - const auto& sigInfo = std::get<1>(*sigInfoFind); - const auto& publicKeyData = sigInfo.publicKey; - const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); - const auto signature = sigInfo.signature; - - signatureVec.push_back(signature); - pubkeyVec.push_back(publicKeyData); - - // Verify signature (pubkey & hash & signature) - EXPECT_TRUE(publicKey.verifyAsDER(signature, TW::Data(preImageHash.begin(), preImageHash.end()))); - } - - /// Step 3: Compile transaction info - const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, pubkeyVec); - - const auto ExpectedTx = "010000000001036021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d0000000000ffffffffd6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e0100000000ffffffff07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa80000000000ffffffff02804f1200000000001600145360df8231ac5965147c9d90ca930a2aafb05232cb92040000000000160014bd92088bb7e82d611a9b94fbb74a0908152b784f02473044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e401210217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc0247304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a400121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382024730440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc0121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb49338200000000"; - { - EXPECT_EQ(outputData.size(), 786); - Bitcoin::Proto::SigningOutput output; - ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); - - EXPECT_EQ(output.encoded().size(), 518); - EXPECT_EQ(hex(output.encoded()), ExpectedTx); - } - - { // Double check: check if simple signature process gives the same result. Note that private keys were not used anywhere up to this point. - Bitcoin::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); - - // 2 private keys are needed (despite >2 UTXOs) - auto key0 = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); - auto key1 = parse_hex("7878787878787878787878787878787878787878787878787878787878787878"); - EXPECT_EQ(hex(PrivateKey(key0).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey0)); - EXPECT_EQ(hex(PrivateKey(key1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey1)); - *input.add_private_key() = std::string(key0.begin(), key0.end()); - *input.add_private_key() = std::string(key1.begin(), key1.end()); - - Bitcoin::Proto::SigningOutput output; - ANY_SIGN(input, coin); - - ASSERT_EQ(hex(output.encoded()), ExpectedTx); - } - - { // Negative: not enough signatures - const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, {signatureVec[0]}, pubkeyVec); - EXPECT_GT(outputData.size(), 1); - Bitcoin::Proto::SigningOutput output; - ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); - EXPECT_EQ(output.encoded().size(), 0); - EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); - } - { // Negative: invalid public key - const auto publicKeyBlake = parse_hex("b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"); - EXPECT_EXCEPTION(TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, - {pubkeyVec[0], pubkeyVec[1], publicKeyBlake}), "Invalid public key"); - } - { // Negative: wrong signature (formally valid) - const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, - {parse_hex("415502201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f3b51"), - signatureVec[1], signatureVec[2]}, - pubkeyVec); - EXPECT_EQ(outputData.size(), 2); - Bitcoin::Proto::SigningOutput output; - ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); - EXPECT_EQ(output.encoded().size(), 0); - EXPECT_EQ(output.error(), Common::Proto::Error_signing); - } -} - -TEST(TransactionCompiler, EthereumCompileWithSignatures) { - /// Step 1: Prepare transaction input (protobuf) - const auto coin = TWCoinTypeEthereum; - const auto txInputData0 = TransactionCompiler::buildInput( - coin, - "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from - "0x3535353535353535353535353535353535353535", // to - "1000000000000000000", // amount - "ETH", // asset - "", // memo - "" // chainId - ); - - // Check, by parsing - EXPECT_EQ((int)txInputData0.size(), 61); - Ethereum::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(txInputData0.data(), (int)txInputData0.size())); - EXPECT_EQ(hex(input.chain_id()), "01"); - EXPECT_EQ(input.to_address(), "0x3535353535353535353535353535353535353535"); - ASSERT_TRUE(input.transaction().has_transfer()); - EXPECT_EQ(hex(input.transaction().transfer().amount()), "0de0b6b3a7640000"); - - // Set a few other values - const auto nonce = store(uint256_t(11)); - const auto gasPrice = store(uint256_t(20000000000)); - const auto gasLimit = store(uint256_t(21000)); - input.set_nonce(nonce.data(), nonce.size()); - input.set_gas_price(gasPrice.data(), gasPrice.size()); - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_tx_mode(Ethereum::Proto::Legacy); - - // Serialize back, this shows how to serialize SigningInput protobuf to byte array - const auto txInputData = data(input.SerializeAsString()); - EXPECT_EQ((int)txInputData.size(), 75); - - /// Step 2: Obtain preimage hash - const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); - ASSERT_GT(preImageHashes.size(), 0); - - TxCompiler::Proto::PreSigningOutput output; - ASSERT_TRUE(output.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); - ASSERT_EQ(output.error(), 0); - - auto preImageHash = data(output.data_hash()); - EXPECT_EQ(hex(preImageHash), "15e180a6274b2f6a572b9b51823fce25ef39576d10188ecdcd7de44526c47217"); - - // Simulate signature, normally obtained from signature server - const Data publicKeyData = parse_hex("044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad7119ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a"); - const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1Extended); - const auto signature = parse_hex("360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c9796677900"); - - // Verify signature (pubkey & hash & signature) - { - EXPECT_TRUE(publicKey.verify(signature, preImageHash)); - } - - /// Step 3: Compile transaction info - const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKeyData}); - - const auto ExpectedTx = "f86c0b8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07ba053bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c97966779"; - { - EXPECT_EQ(outputData.size(), 183); - Ethereum::Proto::SigningOutput output; - ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); - - EXPECT_EQ(output.encoded().size(), 110); - EXPECT_EQ(hex(output.encoded()), ExpectedTx); - } - - { // Double check: check if simple signature process gives the same result. Note that private keys were not used anywhere up to this point. - Ethereum::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); - auto key = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); - input.set_private_key(key.data(), key.size()); - - Ethereum::Proto::SigningOutput output; - ANY_SIGN(input, coin); - - ASSERT_EQ(hex(output.encoded()), ExpectedTx); - } -} - -TEST(TransactionCompiler, EthereumBuildTransactionInput) { - const auto coin = TWCoinTypeEthereum; - const auto txInputData0 = TransactionCompiler::buildInput( - coin, - "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from - "0x3535353535353535353535353535353535353535", // to - "1000000000000000000", // amount - "ETH", // asset - "Memo", // memo - "05" // chainId - ); - - // Check, by parsing - EXPECT_EQ((int)txInputData0.size(), 61); - Ethereum::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(txInputData0.data(), (int)txInputData0.size())); - EXPECT_EQ(hex(input.chain_id()), "05"); - EXPECT_EQ(input.to_address(), "0x3535353535353535353535353535353535353535"); - ASSERT_TRUE(input.transaction().has_transfer()); - EXPECT_EQ(hex(input.transaction().transfer().amount()), "0de0b6b3a7640000"); -} - -TEST(TransactionCompiler, EthereumBuildTransactionInputInvalidAddress) { - const auto coin = TWCoinTypeEthereum; - EXPECT_EXCEPTION(TransactionCompiler::buildInput( - coin, - "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from - "__INVALID_ADDRESS__", // to - "1000000000000000000", // amount - "ETH", // asset - "", // memo - "" // chainId - ), "Invalid to address"); -} diff --git a/tests/Tron/AddressTests.cpp b/tests/Tron/AddressTests.cpp deleted file mode 100644 index 8d160ecc856..00000000000 --- a/tests/Tron/AddressTests.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Tron/Address.h" -#include "HexCoding.h" -#include "PrivateKey.h" - -#include - -namespace TW::Tron { - -TEST(TronAddress, FromPrivateKey) { - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); - const auto address = Address(publicKey); - - ASSERT_EQ(address.string(), "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); -} - -TEST(TronAddress, FromPublicKey) { - const auto privateKey = PrivateKey(parse_hex("BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835")); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); - const auto address = Address(publicKey); - - ASSERT_EQ(address.string(), "THRF3GuPnvvPzKoaT8pJex5XHmo8NNbCb3"); -} - -TEST(TronAddress, Invalid) { - ASSERT_FALSE(Address::isValid(std::string("abc"))); - ASSERT_FALSE(Address::isValid(std::string("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"))); - ASSERT_FALSE(Address::isValid(std::string("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W"))); -} - -TEST(TronAddress, InitWithString) { - const auto address = Address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - - ASSERT_EQ(address.string(), "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); -} - -} // namespace TW::Tron diff --git a/tests/Tron/SerializationTests.cpp b/tests/Tron/SerializationTests.cpp deleted file mode 100644 index a32b5799172..00000000000 --- a/tests/Tron/SerializationTests.cpp +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "proto/Tron.pb.h" -#include "Tron/Signer.h" -#include "PrivateKey.h" -#include "HexCoding.h" -#include "uint256.h" - -#include - -namespace TW::Tron { - TEST(TronSerialization, TransferAsset) { - auto input = Proto::SigningInput(); - auto& transaction = *input.mutable_transaction(); - - auto& transfer = *transaction.mutable_transfer_asset(); - transfer.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - transfer.set_to_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); - transfer.set_amount(4); - transfer.set_asset_name("1000959"); - - transaction.set_timestamp(1539295479000); - transaction.set_expiration(1541890116000 + 10 * 60 * 60 * 1000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1541890116000); - const auto txTrieRoot = parse_hex("845ab51bf63c2c21ee71a4dc0ac3781619f07a7cd05e1e0bd8ba828979332ffa"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000003cb800a7e69e9144e3d16f0cf33f33a95c7ce274097822c67243c1"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3979265); - const auto witnessAddress = parse_hex("41b487cdc02de90f15ac89a68c82f44cbfe3d915ea"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto output = Signer::sign(input); - - ASSERT_EQ(output.json(), R"({"raw_data":{"contract":[{"parameter":{"type_url":"type.googleapis.com/protocol.TransferAssetContract","value":{"amount":4,"asset_name":"31303030393539","owner_address":"415cd0fb0ab3ce40f3051414c604b27756e69e43db","to_address":"41521ea197907927725ef36d70f25f850d1659c7c7"}},"type":"TransferAssetContract"}],"expiration":1541926116000,"ref_block_bytes":"b801","ref_block_hash":"0e2bc08d550f5f58","timestamp":1539295479000},"signature":["77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00"],"txID":"546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb"})"); - } - - TEST(TronSerialization, SignVoteAsset) { - auto input = Proto::SigningInput(); - auto& transaction = *input.mutable_transaction(); - - auto& vote = *transaction.mutable_vote_asset(); - vote.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - vote.add_vote_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); - vote.set_support(true); - vote.set_count(1); - - transaction.set_timestamp(1539295479000); - transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1539295479000); - const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3111739); - const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto output = Signer::sign(input); - - ASSERT_EQ(output.json(), R"({"raw_data":{"contract":[{"parameter":{"type_url":"type.googleapis.com/protocol.VoteAssetContract","value":{"count":1,"owner_address":"415cd0fb0ab3ce40f3051414c604b27756e69e43db","support":true,"vote_address":["41521ea197907927725ef36d70f25f850d1659c7c7"]}},"type":"VoteAssetContract"}],"expiration":1539331479000,"ref_block_bytes":"7b3b","ref_block_hash":"b21ace8d6ac20e7e","timestamp":1539295479000},"signature":["501e04b08f359116a26d9ec784abc50830f92a9dc05d2c1aceefe0eba79466d2730b63b6739edf0f1f1972181618b201ce0b4167d14a66abf40eba4097c39ec400"],"txID":"59b5736fb9756124f9470e4fadbcdafdc8c970da7157fa0ad34a41559418bf0a"})"); - } - - TEST(TronSerialization, SignVoteWitness) { - auto input = Proto::SigningInput(); - auto& transaction = *input.mutable_transaction(); - - auto& vote_witness = *transaction.mutable_vote_witness(); - vote_witness.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - vote_witness.set_support(true); - - auto& vote = *vote_witness.add_votes(); - vote.set_vote_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); - vote.set_vote_count(3); - - transaction.set_timestamp(1539295479000); - transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1539295479000); - const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3111739); - const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto output = Signer::sign(input); - - ASSERT_EQ(output.json(), R"({"raw_data":{"contract":[{"parameter":{"type_url":"type.googleapis.com/protocol.VoteWitnessContract","value":{"owner_address":"415cd0fb0ab3ce40f3051414c604b27756e69e43db","support":true,"votes":[{"vote_address":"41521ea197907927725ef36d70f25f850d1659c7c7","vote_count":3}]}},"type":"VoteWitnessContract"}],"expiration":1539331479000,"ref_block_bytes":"7b3b","ref_block_hash":"b21ace8d6ac20e7e","timestamp":1539295479000},"signature":["79ec1073ae1319ef9303a2f5a515876cfd67f8f0e155bdbde1115d391c05358a3c32f148bfafacf07e1619aaed728d9ffbc2c7e4a5046003c7b74feb86fc68e400"],"txID":"3f923e9dd9571a66624fafeda27baa3e00aba1709d3fdc5c97c77b81fda18c1f"})"); - } - - TEST(TronSerialization, SignTriggerSmartContract) { - auto input = Proto::SigningInput(); - auto data = parse_hex("736f6d652064617461"); - auto& transaction = *input.mutable_transaction(); - auto& trigger_contract = *transaction.mutable_trigger_smart_contract(); - trigger_contract.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - trigger_contract.set_contract_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); - trigger_contract.set_call_value(0); - trigger_contract.set_call_token_value(10000); - trigger_contract.set_token_id(1); - trigger_contract.set_data(data.data(), data.size()); - - transaction.set_timestamp(1539295479000); - transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1539295479000); - const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3111739); - const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto output = Signer::sign(input); - - ASSERT_EQ(output.json(), R"({"raw_data":{"contract":[{"parameter":{"type_url":"type.googleapis.com/protocol.TriggerSmartContract","value":{"call_token_value":10000,"contract_address":"41521ea197907927725ef36d70f25f850d1659c7c7","data":"736f6d652064617461","owner_address":"415cd0fb0ab3ce40f3051414c604b27756e69e43db","token_id":1}},"type":"TriggerSmartContract"}],"expiration":1539331479000,"ref_block_bytes":"7b3b","ref_block_hash":"b21ace8d6ac20e7e","timestamp":1539295479000},"signature":["21a99aafeabdddfdfae86538df048d120a83eb36bbcf5656595919ba6afddacd0a07d0ba051ae80337613174b109f36cb583b6e46ee5aecf6ffe3392fdbb8a2a01"],"txID":"9927d3daae10ad001b25ef3c1bb03073c928cc0e0823f6f3ce404c2b03ce3570"})"); - } - - TEST(TronSerialization, SignTransferTrc20Contract) { - auto input = Proto::SigningInput(); - auto& transaction = *input.mutable_transaction(); - auto& transfer_contract = *transaction.mutable_transfer_trc20_contract(); - transfer_contract.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - transfer_contract.set_contract_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); - transfer_contract.set_to_address("TW1dU4L3eNm7Lw8WvieLKEHpXWAussRG9Z"); - Data amount = store(uint256_t(1000)); - transfer_contract.set_amount(std::string(amount.begin(), amount.end())); - - transaction.set_timestamp(1539295479000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1539295479000); - const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3111739); - const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto output = Signer::sign(input); - - ASSERT_EQ(output.json(), R"({"raw_data":{"contract":[{"parameter":{"type_url":"type.googleapis.com/protocol.TriggerSmartContract","value":{"contract_address":"41521ea197907927725ef36d70f25f850d1659c7c7","data":"a9059cbb000000000000000000000041dbd7c53729b3310e1843083000fa84abad99696100000000000000000000000000000000000000000000000000000000000003e8","owner_address":"415cd0fb0ab3ce40f3051414c604b27756e69e43db"}},"type":"TriggerSmartContract"}],"expiration":1539331479000,"ref_block_bytes":"7b3b","ref_block_hash":"b21ace8d6ac20e7e","timestamp":1539295479000},"signature":["bec790877b3a008640781e3948b070740b1f6023c29ecb3f7b5835433c13fc5835e5cad3bd44360ff2ddad5ed7dc9d7dee6878f90e86a40355b7697f5954b88c01"],"txID":"0d644290e3cf554f6219c7747f5287589b6e7e30e1b02793b48ba362da6a5058"})"); - } - - TEST(TronSerialization, SignTransferTrc20Contract_LargeAmount) { - auto input = Proto::SigningInput(); - auto& transaction = *input.mutable_transaction(); - auto& transfer_contract = *transaction.mutable_transfer_trc20_contract(); - transfer_contract.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - transfer_contract.set_contract_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); - transfer_contract.set_to_address("TW1dU4L3eNm7Lw8WvieLKEHpXWAussRG9Z"); - Data amount = store(uint256_t("10000000000000000000000")); // over 64 bits, corresponds to 10000 in case of 18 decimals - transfer_contract.set_amount(std::string(amount.begin(), amount.end())); - - transaction.set_timestamp(1539295479000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1539295479000); - const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3111739); - const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto output = Signer::sign(input); - - ASSERT_EQ(output.json(), R"({"raw_data":{"contract":[{"parameter":{"type_url":"type.googleapis.com/protocol.TriggerSmartContract","value":{"contract_address":"41521ea197907927725ef36d70f25f850d1659c7c7","data":"a9059cbb000000000000000000000041dbd7c53729b3310e1843083000fa84abad99696100000000000000000000000000000000000000000000021e19e0c9bab2400000","owner_address":"415cd0fb0ab3ce40f3051414c604b27756e69e43db"}},"type":"TriggerSmartContract"}],"expiration":1539331479000,"ref_block_bytes":"7b3b","ref_block_hash":"b21ace8d6ac20e7e","timestamp":1539295479000},"signature":["8207cbae6aff799cfefa1ab4d8a0c52b6a59be43491bd25b4f03754f0e8115b006b5f1393a3934ec3489f5d3c272a7af42658bdc165dc632b36114bd3180da2e00"],"txID":"774422d8d205760876496f22b7d4395cfceda03f139b8362a3693f1f405f0c36"})"); - } -} diff --git a/tests/Tron/SignerTests.cpp b/tests/Tron/SignerTests.cpp deleted file mode 100644 index 69dc5c37cb5..00000000000 --- a/tests/Tron/SignerTests.cpp +++ /dev/null @@ -1,343 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Bitcoin/Address.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "uint256.h" -#include "proto/Tron.pb.h" -#include "Tron/Signer.h" - -#include - -namespace TW::Tron { - -TEST(TronSigner, SignTransferAsset) { - auto input = Proto::SigningInput(); - auto& transaction = *input.mutable_transaction(); - - auto& transfer = *transaction.mutable_transfer_asset(); - transfer.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - transfer.set_to_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); - transfer.set_amount(4); - transfer.set_asset_name("1000959"); - - transaction.set_timestamp(1539295479000); - transaction.set_expiration(1541890116000 + 10 * 60 * 60 * 1000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1541890116000); - const auto txTrieRoot = parse_hex("845ab51bf63c2c21ee71a4dc0ac3781619f07a7cd05e1e0bd8ba828979332ffa"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000003cb800a7e69e9144e3d16f0cf33f33a95c7ce274097822c67243c1"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3979265); - const auto witnessAddress = parse_hex("41b487cdc02de90f15ac89a68c82f44cbfe3d915ea"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.id()), "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb"); - ASSERT_EQ(hex(output.signature()), "77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00"); -} - -TEST(TronSigner, SignTransfer) { - auto input = Proto::SigningInput(); - auto& transaction = *input.mutable_transaction(); - - auto& transfer = *transaction.mutable_transfer(); - transfer.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - transfer.set_to_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); - transfer.set_amount(2000000); - - transaction.set_timestamp(1539295479000); - transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1539295479000); - const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3111739); - const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.id()), "dc6f6d9325ee44ab3c00528472be16e1572ab076aa161ccd12515029869d0451"); - ASSERT_EQ(hex(output.signature()), "ede769f6df28aefe6a846be169958c155e23e7e5c9621d2e8dce1719b4d952b63e8a8bf9f00e41204ac1bf69b1a663dacdf764367e48e4a5afcd6b055a747fb200"); -} - -TEST(TronSigner, SignFreezeBalance) { - auto input = Proto::SigningInput(); - auto& transaction = *input.mutable_transaction(); - - auto& freeze = *transaction.mutable_freeze_balance(); - freeze.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - freeze.set_receiver_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); - freeze.set_frozen_duration(1000000); - freeze.set_frozen_duration(100); - freeze.set_resource("ENERGY"); - - transaction.set_timestamp(1539295479000); - transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1539295479000); - const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3111739); - const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.id()), "d314967bc1d153d649d9f54a1cc78033f0d696a58ff6922f490ddaec82558c83"); - ASSERT_EQ(hex(output.signature()), "aa7cf79fb1692ff432a1a3e520be3355c3e8168c5fa22f6e3b96c2a9f2e2827b49d67d5e6eea5c7e7cf872047d422ce5d4d149c4df752b176d13f8f48920271201"); -} - -TEST(TronSigner, SignUnFreezeBalance) { - auto input = Proto::SigningInput(); - auto& transaction = *input.mutable_transaction(); - - auto& unfreeze = *transaction.mutable_unfreeze_balance(); - unfreeze.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - unfreeze.set_receiver_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); - unfreeze.set_resource("ENERGY"); - - transaction.set_timestamp(1539295479000); - transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1539295479000); - const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3111739); - const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.id()), "c5bd624bb53fed8ce4a7361475263b3a91ae71ef389630e0b3b8693c8c56d7a1"); - ASSERT_EQ(hex(output.signature()), "4b4b12b5fd091d5343335f14ac90bf23ea9a8167d648dd9d10d00c9c9b24731c484937bf133e5010f0338fb70a679a9a2eca8b945574005bc4015b419a68897300"); -} - -TEST(TronSigner, SignUnFreezeAsset) { - auto input = Proto::SigningInput(); - auto& transaction = *input.mutable_transaction(); - - auto& unfreeze = *transaction.mutable_unfreeze_asset(); - unfreeze.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - - transaction.set_timestamp(1539295479000); - transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1539295479000); - const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3111739); - const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.id()), "432bd5cf77ff134787712724709a672fc6e51763de00292438db02d23931e13d"); - ASSERT_EQ(hex(output.signature()), "f493d8f275538a50bb8a832d759df9cad535bb2c5cc73296b04983f551d8398b6d7a30fc0fdfd73e8a9cac77a1a6a9435dc6309bb98fbb219035e88809a0b65901"); -} - -TEST(TronSigner, SignWithdrawBalance) { - auto input = Proto::SigningInput(); - auto& transaction = *input.mutable_transaction(); - - auto& unfreeze = *transaction.mutable_withdraw_balance(); - unfreeze.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - - transaction.set_timestamp(1539295479000); - transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1539295479000); - const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3111739); - const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.id()), "69aaa954dcd61f28a6a73e979addece6e36541522e5b3374b18b4ef9bc3de4cb"); - ASSERT_EQ(hex(output.signature()), "cb7d23a5eb23284a25ba6deaa231de0f18d8d103592e3312bff101a4219a3e02167eca24b3f4ce78b34f0c1842b6f7fb8d813f530c4c54342cdedef9f8e1f85100"); -} - -TEST(TronSigner, SignVoteAsset) { - auto input = Proto::SigningInput(); - auto& transaction = *input.mutable_transaction(); - - auto& vote = *transaction.mutable_vote_asset(); - vote.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - vote.add_vote_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); - vote.set_support(true); - vote.set_count(1); - - transaction.set_timestamp(1539295479000); - transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1539295479000); - const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3111739); - const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.id()), "59b5736fb9756124f9470e4fadbcdafdc8c970da7157fa0ad34a41559418bf0a"); - ASSERT_EQ(hex(output.signature()), "501e04b08f359116a26d9ec784abc50830f92a9dc05d2c1aceefe0eba79466d2730b63b6739edf0f1f1972181618b201ce0b4167d14a66abf40eba4097c39ec400"); -} - -TEST(TronSigner, SignVoteWitness) { - auto input = Proto::SigningInput(); - auto& transaction = *input.mutable_transaction(); - - auto& vote_witness = *transaction.mutable_vote_witness(); - vote_witness.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - vote_witness.set_support(true); - - auto& vote = *vote_witness.add_votes(); - vote.set_vote_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); - vote.set_vote_count(3); - - transaction.set_timestamp(1539295479000); - transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1539295479000); - const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3111739); - const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.id()), "3f923e9dd9571a66624fafeda27baa3e00aba1709d3fdc5c97c77b81fda18c1f"); - ASSERT_EQ(hex(output.signature()), "79ec1073ae1319ef9303a2f5a515876cfd67f8f0e155bdbde1115d391c05358a3c32f148bfafacf07e1619aaed728d9ffbc2c7e4a5046003c7b74feb86fc68e400"); -} - -TEST(TronSigner, SignTriggerSmartContract) { - auto input = Proto::SigningInput(); - auto data = parse_hex("736f6d652064617461"); - auto& transaction = *input.mutable_transaction(); - auto& trigger_contract = *transaction.mutable_trigger_smart_contract(); - trigger_contract.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - trigger_contract.set_contract_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); - trigger_contract.set_call_value(0); - trigger_contract.set_call_token_value(10000); - trigger_contract.set_token_id(1); - trigger_contract.set_data(data.data(), data.size()); - - transaction.set_timestamp(1539295479000); - transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1539295479000); - const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3111739); - const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.id()), "9927d3daae10ad001b25ef3c1bb03073c928cc0e0823f6f3ce404c2b03ce3570"); - ASSERT_EQ(hex(output.signature()), "21a99aafeabdddfdfae86538df048d120a83eb36bbcf5656595919ba6afddacd0a07d0ba051ae80337613174b109f36cb583b6e46ee5aecf6ffe3392fdbb8a2a01"); -} - -TEST(TronSigner, SignTransferTrc20Contract) { - auto input = Proto::SigningInput(); - auto& transaction = *input.mutable_transaction(); - auto& transfer_contract = *transaction.mutable_transfer_trc20_contract(); - transfer_contract.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - transfer_contract.set_contract_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); - transfer_contract.set_to_address("TW1dU4L3eNm7Lw8WvieLKEHpXWAussRG9Z"); - Data amount = store(uint256_t(1000)); - transfer_contract.set_amount(std::string(amount.begin(), amount.end())); - - transaction.set_timestamp(1539295479000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1539295479000); - const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3111739); - const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - const auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.id()), "0d644290e3cf554f6219c7747f5287589b6e7e30e1b02793b48ba362da6a5058"); - ASSERT_EQ(hex(output.signature()), "bec790877b3a008640781e3948b070740b1f6023c29ecb3f7b5835433c13fc5835e5cad3bd44360ff2ddad5ed7dc9d7dee6878f90e86a40355b7697f5954b88c01"); -} -} // namespace TW::Tron diff --git a/tests/Tron/TWAnySignerTests.cpp b/tests/Tron/TWAnySignerTests.cpp deleted file mode 100644 index 0dc27c18583..00000000000 --- a/tests/Tron/TWAnySignerTests.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "proto/Tron.pb.h" -#include - -#include "../interface/TWTestUtilities.h" -#include - -namespace TW::Tron { - -TEST(TWAnySignerTron, SignTransferAsset) { - auto input = Proto::SigningInput(); - auto& transaction = *input.mutable_transaction(); - - auto& transfer = *transaction.mutable_transfer_asset(); - transfer.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - transfer.set_to_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); - transfer.set_amount(4); - transfer.set_asset_name("1000959"); - - transaction.set_timestamp(1539295479000); - transaction.set_expiration(1541890116000 + 10 * 60 * 60 * 1000); - - auto& blockHeader = *transaction.mutable_block_header(); - blockHeader.set_timestamp(1541890116000); - const auto txTrieRoot = parse_hex("845ab51bf63c2c21ee71a4dc0ac3781619f07a7cd05e1e0bd8ba828979332ffa"); - blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); - const auto parentHash = parse_hex("00000000003cb800a7e69e9144e3d16f0cf33f33a95c7ce274097822c67243c1"); - blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); - blockHeader.set_number(3979265); - const auto witnessAddress = parse_hex("41b487cdc02de90f15ac89a68c82f44cbfe3d915ea"); - blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); - blockHeader.set_version(3); - - const auto privateKey = parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"); - input.set_private_key(privateKey.data(), privateKey.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeTron); - - ASSERT_EQ(hex(output.signature()), "77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00"); -} - -} diff --git a/tests/Tron/TWCoinTypeTests.cpp b/tests/Tron/TWCoinTypeTests.cpp deleted file mode 100644 index e496477156e..00000000000 --- a/tests/Tron/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWTronCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTron)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTron, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTron, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTron)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTron)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTron), 6); - ASSERT_EQ(TWBlockchainTron, TWCoinTypeBlockchain(TWCoinTypeTron)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTron)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTron)); - assertStringsEqual(symbol, "TRX"); - assertStringsEqual(txUrl, "https://tronscan.org/#/transaction/t123"); - assertStringsEqual(accUrl, "https://tronscan.org/#/address/a12"); - assertStringsEqual(id, "tron"); - assertStringsEqual(name, "Tron"); -} diff --git a/tests/VeChain/SignerTests.cpp b/tests/VeChain/SignerTests.cpp deleted file mode 100644 index 71ab6ccef4b..00000000000 --- a/tests/VeChain/SignerTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "VeChain/Signer.h" - -#include - -namespace TW::VeChain { - -using boost::multiprecision::uint256_t; - -TEST(Signer, Sign) { - auto transaction = Transaction(); - transaction.chainTag = 1; - transaction.blockRef = 1; - transaction.expiration = 1; - transaction.clauses.push_back( - Clause(Ethereum::Address("0x3535353535353535353535353535353535353535"), 1000, {}) - ); - transaction.gasPriceCoef = 0; - transaction.gas = 21000; - transaction.nonce = 1; - - auto key = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); - auto signature = Signer::sign(key, transaction); - - ASSERT_EQ(hex(signature), "3181b1094150f8e4f51f370b805cc9c5b107504145b9e316e846d5e5dbeedb5c1c2b5d217f197a105983dfaad6a198414d5731c7447493cb6b5169907d73dbe101"); -} - -} // namespace TW::VeChain diff --git a/tests/VeChain/TWAnySignerTests.cpp b/tests/VeChain/TWAnySignerTests.cpp deleted file mode 100644 index 289c725d42b..00000000000 --- a/tests/VeChain/TWAnySignerTests.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "proto/VeChain.pb.h" -#include "../interface/TWTestUtilities.h" -#include -#include - -using namespace TW; -using namespace TW::VeChain; - -TEST(TWAnySignerVeChain, Sign) { - auto input = Proto::SigningInput(); - - input.set_chain_tag(1); - input.set_block_ref(1); - input.set_expiration(1); - input.set_gas_price_coef(0); - input.set_gas(21000); - input.set_nonce(1); - - auto key = parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); - input.set_private_key(key.data(), key.size()); - - auto& clause = *input.add_clauses(); - auto amount = parse_hex("31303030"); // 1000 - clause.set_to("0x3535353535353535353535353535353535353535"); - clause.set_value(amount.data(), amount.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeVeChain); - - ASSERT_EQ(hex(output.encoded()), "f86a010101dcdb943535353535353535353535353535353535353535843130303080808252088001c0b841bf8edf9600e645b5abd677cb52f585e7f655d1361075d511b37f707a9f31da6702d28739933b264527a1d05b046f5b74044b88c30c3f5a09d616bd7a4af4901601"); -} diff --git a/tests/VeChain/TWCoinTypeTests.cpp b/tests/VeChain/TWCoinTypeTests.cpp deleted file mode 100644 index cf41c6cba08..00000000000 --- a/tests/VeChain/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWVeChainCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeVeChain)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xa424053be0063555aee73a595ca69968c2e4d90d36f280753e503b92b11a655d")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeVeChain, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x8a0a035a33173601bfbec8b6ae7c4a6557a55103")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeVeChain, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeVeChain)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeVeChain)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeVeChain), 18); - ASSERT_EQ(TWBlockchainVechain, TWCoinTypeBlockchain(TWCoinTypeVeChain)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeVeChain)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeVeChain)); - assertStringsEqual(symbol, "VET"); - assertStringsEqual(txUrl, "https://explore.vechain.org/transactions/0xa424053be0063555aee73a595ca69968c2e4d90d36f280753e503b92b11a655d"); - assertStringsEqual(accUrl, "https://explore.vechain.org/accounts/0x8a0a035a33173601bfbec8b6ae7c4a6557a55103"); - assertStringsEqual(id, "vechain"); - assertStringsEqual(name, "VeChain"); -} diff --git a/tests/Viacoin/TWCoinTypeTests.cpp b/tests/Viacoin/TWCoinTypeTests.cpp deleted file mode 100644 index 15ead27fdc1..00000000000 --- a/tests/Viacoin/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWViacoinCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeViacoin)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeViacoin, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeViacoin, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeViacoin)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeViacoin)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeViacoin), 8); - ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeViacoin)); - ASSERT_EQ(0x21, TWCoinTypeP2shPrefix(TWCoinTypeViacoin)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeViacoin)); - assertStringsEqual(symbol, "VIA"); - assertStringsEqual(txUrl, "https://explorer.viacoin.org/tx/t123"); - assertStringsEqual(accUrl, "https://explorer.viacoin.org/address/a12"); - assertStringsEqual(id, "viacoin"); - assertStringsEqual(name, "Viacoin"); -} diff --git a/tests/Wanchain/TWCoinTypeTests.cpp b/tests/Wanchain/TWCoinTypeTests.cpp deleted file mode 100644 index f3ba2ce8ee7..00000000000 --- a/tests/Wanchain/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWWanchainCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeWanchain)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x180ea96a3218b82b9b35d796823266d8a425c182507adfe5eeffc96e6a14d856")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeWanchain, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x69B492D57bb777e97aa7044D0575228434e2E8B1")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeWanchain, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeWanchain)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeWanchain)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeWanchain), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeWanchain)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeWanchain)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeWanchain)); - assertStringsEqual(symbol, "WAN"); - assertStringsEqual(txUrl, "https://www.wanscan.org/tx/0x180ea96a3218b82b9b35d796823266d8a425c182507adfe5eeffc96e6a14d856"); - assertStringsEqual(accUrl, "https://www.wanscan.org/address/0x69B492D57bb777e97aa7044D0575228434e2E8B1"); - assertStringsEqual(id, "wanchain"); - assertStringsEqual(name, "Wanchain"); -} diff --git a/tests/Waves/AddressTests.cpp b/tests/Waves/AddressTests.cpp deleted file mode 100644 index b5a8afe9232..00000000000 --- a/tests/Waves/AddressTests.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "PrivateKey.h" -#include "Waves/Address.h" - -#include -#include -#include - -using namespace std; -using namespace TW; -using namespace TW::Waves; - -TEST(WavesAddress, SecureHash) { - const auto secureHash = - hex(Address::secureHash(parse_hex("0157c7fefc0c6acc54e9e4354a81ac1f038e01745731"))); - - ASSERT_EQ(secureHash, "a7978a753c6496866dc75ba3abcaaec796f2380037a1fa7c46cbf9762ee380df"); -} - -TEST(WavesAddress, FromPrivateKey) { - const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); - const auto publicKeyEd25519 = privateKey.getPublicKey(TWPublicKeyTypeED25519); - ASSERT_EQ(hex(Data(publicKeyEd25519.bytes.begin(), publicKeyEd25519.bytes.end())), - "ff84c4bfc095df25b01e48807715856d95af93d88c5b57f30cb0ce567ca4ced6"); - const auto publicKeyCurve25519 = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); - ASSERT_EQ(hex(Data(publicKeyCurve25519.bytes.begin(), publicKeyCurve25519.bytes.end())), - "559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"); - const auto address = Address(publicKeyCurve25519); - - ASSERT_EQ(address.string(), "3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds"); -} - -TEST(WavesAddress, FromPublicKey) { - const auto publicKey = - PublicKey(parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"), - TWPublicKeyTypeCURVE25519); - const auto address = Address(publicKey); - - ASSERT_EQ(address.string(), "3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds"); -} - -TEST(WavesAddress, Invalid) { - ASSERT_FALSE(Address::isValid(std::string("abc"))); - ASSERT_FALSE(Address::isValid(std::string("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"))); - ASSERT_FALSE(Address::isValid(std::string("3PLANf4MgtNN5v5k4NNnyx2m4zKJiw1tF9v"))); - ASSERT_FALSE(Address::isValid(std::string("3PLANf4MgtNN5v6k4NNnyx2m4zKJiw1tF8v"))); -} - -TEST(WavesAddress, Valid) { - ASSERT_TRUE(Address::isValid(std::string("3PLANf4MgtNN5v6k4NNnyx2m4zKJiw1tF9v"))); - ASSERT_TRUE(Address::isValid(std::string("3PDjjLFDR5aWkKgufika7KSLnGmAe8ueDpC"))); - ASSERT_TRUE(Address::isValid(std::string("3PLjucTjqEfmgBF7fs2CER3fHQapCtknPeW"))); - ASSERT_TRUE(Address::isValid(std::string("3PB9ffP1YKQer3e7t283gPCLyjEfK8xrGp7"))); -} - -TEST(WavesAddress, InitWithString) { - const auto address = Address("3PQupTC1yRiHneotFt79LF2pkN6GrGMwEy3"); - ASSERT_EQ(address.string(), "3PQupTC1yRiHneotFt79LF2pkN6GrGMwEy3"); -} - -TEST(WavesAddress, InitWithInvalidString) { - EXPECT_THROW(Address("3PQupTC1yRiHneotFt79LF2pkN6GrGMwEy2"), invalid_argument); -} - -TEST(WavesAddress, Derive) { - const auto mnemonic = - "water process satisfy repeat flag avoid town badge sketch surge split between cabin sugar " - "ill special axis adjust pull useful craft peace flee physical"; - const auto wallet = HDWallet(mnemonic, ""); - const auto address1 = TW::deriveAddress( - TWCoinTypeWaves, wallet.getKey(TWCoinTypeWaves, DerivationPath("m/44'/5741564'/0'/0'/0'"))); - const auto address2 = TW::deriveAddress( - TWCoinTypeWaves, wallet.getKey(TWCoinTypeWaves, DerivationPath("m/44'/5741564'/0'/0'/1'"))); - - ASSERT_EQ(address1, "3PQupTC1yRiHneotFt79LF2pkN6GrGMwEy3"); - ASSERT_EQ(address2, "3PEXw52bkS9XuLhttWoKyykZjXqEY8zeLxf"); -} \ No newline at end of file diff --git a/tests/Waves/LeaseTests.cpp b/tests/Waves/LeaseTests.cpp deleted file mode 100644 index 41de50d1980..00000000000 --- a/tests/Waves/LeaseTests.cpp +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include "Waves/Address.h" -#include "proto/Waves.pb.h" -#include "Waves/Transaction.h" - -#include -#include - -using json = nlohmann::json; - -using namespace std; -using namespace TW; -using namespace TW::Waves; - -TEST(WavesLease, serialize) { - const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); - auto input = Proto::SigningInput(); - input.set_timestamp(int64_t(1526646497465)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - auto& message = *input.mutable_lease_message(); - message.set_amount(int64_t(100000000)); - message.set_fee(int64_t(100000)); - message.set_to("3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc"); - auto tx1 = Transaction( - input, - /* pub_key */ - parse_hex("425f57a8cb5439e4e912e66376f7041565d029ae4437dae1a3ebe15649d43461")); - auto serialized1 = tx1.serializeToSign(); - ASSERT_EQ(hex(serialized1), "080200425f57a8cb5439e4e912e66376f7041565d029ae4437dae1a3ebe15649d4346101574" - "fdfcd1bfb19114bd2ac369e32013c70c6d03a4627879cbf0000000005f5e100000000000001" - "86a0000001637338e0b9"); -} - -TEST(WavesLease, CancelSerialize) { - const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); - auto input = Proto::SigningInput(); - input.set_timestamp(int64_t(1568831000826)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - auto& message = *input.mutable_cancel_lease_message(); - message.set_fee(int64_t(100000)); - message.set_lease_id("44re3UEDw1QwPFP8dKzfuGHVMNBejUW9NbhxG6b4KJ1T"); - auto tx1 = Transaction( - input, - /* pub_key */ - parse_hex("425f57a8cb5439e4e912e66376f7041565d029ae4437dae1a3ebe15649d43461")); - auto serialized1 = tx1.serializeToSign(); - ASSERT_EQ(hex(serialized1), "090257425f57a8cb5439e4e912e66376f7041565d029ae4437dae1a3ebe15649d" - "4346100000000000186a00000016d459d50fa2d8fee08efc97f79bcd97a4d977c" - "76183580d723909af2b50e72b02f1e36707e"); -} - -TEST(WavesLease, jsonSerialize) { - const auto privateKey = PrivateKey(parse_hex( - "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); - const auto publicKeyCurve25519 = - privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); - auto input = Proto::SigningInput(); - input.set_timestamp(int64_t(1568973547102)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - auto& message = *input.mutable_lease_message(); - message.set_amount(int64_t(100000)); - message.set_fee(int64_t(100000)); - message.set_to("3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc"); - auto tx1 = Transaction(input, - /* pub_key */ - parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d")); - - auto signature = Signer::sign(privateKey, tx1); - auto json = tx1.buildJson(signature); - - ASSERT_EQ(json["type"], TransactionType::lease); - ASSERT_EQ(json["version"], TransactionVersion::V2); - ASSERT_EQ(json["fee"], int64_t(100000)); - ASSERT_EQ(json["senderPublicKey"], - "6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU"); - ASSERT_EQ(json["timestamp"], int64_t(1568973547102)); - ASSERT_EQ(json["proofs"].dump(), - "[\"4opce9e99827upK3m3D3NicnvBqbMLtAJ4Jc8ksTLiScqBgjdqzr9JyXG" - "C1NAGZUbkqJvix9bNrBokrxtGruwmu3\"]"); - ASSERT_EQ(json["recipient"], "3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc"); - ASSERT_EQ(json["amount"], int64_t(100000)); - ASSERT_EQ(json.dump(), - "{\"amount\":100000,\"fee\":100000,\"proofs\":[" - "\"4opce9e99827upK3m3D3NicnvBqbMLtAJ4Jc8ksTLiScqBgjdqzr9JyXGC1NAGZUbkqJ" - "vix9bNrBokrxtGruwmu3\"],\"recipient\":" - "\"3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc\",\"senderPublicKey\":" - "\"6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU\",\"timestamp\":" - "1568973547102,\"type\":8,\"version\":2}"); -} - -TEST(WavesLease, jsonCancelSerialize) { - const auto privateKey = PrivateKey(parse_hex( - "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); - const auto publicKeyCurve25519 = - privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); - auto input = Proto::SigningInput(); - input.set_timestamp(int64_t(1568973547102)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - auto& message = *input.mutable_cancel_lease_message(); - message.set_lease_id("DKhmXrCsBwf6WVhGh8bYVBnjtAXGpk2K4Yd3CW4u1huG"); - message.set_fee(int64_t(100000)); - auto tx1 = Transaction(input, - /* pub_key */ - parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d")); - auto signature = Signer::sign(privateKey, tx1); - auto json = tx1.buildJson(signature); - - ASSERT_EQ(json["type"], TransactionType::cancelLease); - ASSERT_EQ(json["version"], TransactionVersion::V2); - ASSERT_EQ(json["fee"], int64_t(100000)); - ASSERT_EQ(json["senderPublicKey"], - "6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU"); - ASSERT_EQ(json["leaseId"], "DKhmXrCsBwf6WVhGh8bYVBnjtAXGpk2K4Yd3CW4u1huG"); - ASSERT_EQ(json["chainId"], 87); - ASSERT_EQ(json["timestamp"], int64_t(1568973547102)); - ASSERT_EQ(json["proofs"].dump(), - "[\"Mwhh7kdbhPv9vtnPh6pjEcHTFJ5h5JtAziwFpqH8Ykw1yWYie4Nquh" - "eYtAWPbRowgpDVBxvG1rTrv82LnFdByQY\"]"); - ASSERT_EQ(json.dump(), - "{\"chainId\":87,\"fee\":100000,\"leaseId\":\"DKhmXrCsBwf6WVhGh8bYVBnjtAXGpk2K4Yd3CW4u1huG\"," - "\"proofs\":[\"Mwhh7kdbhPv9vtnPh6pjEcHTFJ5h5JtAziwFpqH8Ykw1yWYie4NquheYtAWP" - "bRowgpDVBxvG1rTrv82LnFdByQY\"],\"senderPublicKey\":" - "\"6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU\",\"timestamp\":" - "1568973547102,\"type\":9,\"version\":2}"); -} - - diff --git a/tests/Waves/SignerTests.cpp b/tests/Waves/SignerTests.cpp deleted file mode 100644 index bf45d2db69e..00000000000 --- a/tests/Waves/SignerTests.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "PublicKey.h" -#include "Waves/Signer.h" -#include "Waves/Transaction.h" - -#include -#include - -using namespace TW; -using namespace TW::Waves; - -TEST(WavesSigner, SignTransaction) { - const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); - const auto publicKeyCurve25519 = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); - ASSERT_EQ(hex(Data(publicKeyCurve25519.bytes.begin(), publicKeyCurve25519.bytes.end())), - "559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"); - // 3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds - const auto address = Address(publicKeyCurve25519); - auto input = Proto::SigningInput(); - input.set_timestamp(int64_t(1526641218066)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - auto& message = *input.mutable_transfer_message(); - message.set_amount(int64_t(100000000)); - message.set_asset(Transaction::WAVES); - message.set_fee(int64_t(100000000)); - message.set_fee_asset(Transaction::WAVES); - message.set_to(address.string()); - message.set_attachment("falafel"); - auto tx1 = Transaction( - input, - /* pub_key */ - parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d")); - - auto signature = Signer::sign(privateKey, tx1); - - EXPECT_EQ(hex(tx1.serializeToSign()), - "0402559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d00000000016372e8" - "52120000000005f5e1000000000005f5e10001570acc4110b78a6d38b34d879b5bba38806202ecf1732f" - "8542000766616c6166656c"); - EXPECT_EQ(hex(signature), "af7989256f496e103ce95096b3f52196dd9132e044905fe486da3b829b5e403bcba9" - "5ab7e650a4a33948c2d05cfca2dce4d4df747e26402974490fb4c49fbe8f"); - - ASSERT_TRUE(publicKeyCurve25519.verify(signature, tx1.serializeToSign())); -} - -TEST(WavesSigner, curve25519_pk_to_ed25519) { - const auto publicKeyCurve25519 = - parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"); - auto r = Data(); - r.resize(32); - curve25519_pk_to_ed25519(r.data(), publicKeyCurve25519.data()); - EXPECT_EQ(hex(r), "ff84c4bfc095df25b01e48807715856d95af93d88c5b57f30cb0ce567ca4ce56"); -} diff --git a/tests/Waves/TWAnySignerTests.cpp b/tests/Waves/TWAnySignerTests.cpp deleted file mode 100644 index 9bb037ba987..00000000000 --- a/tests/Waves/TWAnySignerTests.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Base58.h" -#include "HexCoding.h" -#include "proto/Waves.pb.h" -#include "../interface/TWTestUtilities.h" -#include -#include - -using namespace TW; -using namespace TW::Waves; - -TEST(TWAnySignerWaves, Sign) { - auto input = Proto::SigningInput(); - const auto privateKey = Base58::bitcoin.decode("83mqJpmgB5Mko1567sVAdqZxVKsT6jccXt3eFSi4G1zE"); - - input.set_timestamp(int64_t(1559146613)); - input.set_private_key(privateKey.data(), privateKey.size()); - auto& message = *input.mutable_transfer_message(); - message.set_amount(int64_t(100000000)); - message.set_asset("DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq"); - message.set_fee(int64_t(100000)); - message.set_fee_asset("DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq"); - message.set_to("3PPCZQkvdMJpmx7Zrz1cnYsPe9Bt1XT2Ckx"); - message.set_attachment("hello"); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeWaves); - - ASSERT_EQ(hex(output.signature()), "5d6a77b1fd9b53d9735cd2543ba94215664f2b07d6c7befb081221fcd49f5b6ad6b9ac108582e8d3e74943bdf35fd80d985edf4b4de1fb1c5c427e84d0879f8f"); -} diff --git a/tests/Waves/TWCoinTypeTests.cpp b/tests/Waves/TWCoinTypeTests.cpp deleted file mode 100644 index b9043540b04..00000000000 --- a/tests/Waves/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWWavesCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeWaves)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeWaves, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeWaves, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeWaves)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeWaves)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeWaves), 8); - ASSERT_EQ(TWBlockchainWaves, TWCoinTypeBlockchain(TWCoinTypeWaves)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeWaves)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeWaves)); - assertStringsEqual(symbol, "WAVES"); - assertStringsEqual(txUrl, "https://wavesexplorer.com/tx/t123"); - assertStringsEqual(accUrl, "https://wavesexplorer.com/address/a12"); - assertStringsEqual(id, "waves"); - assertStringsEqual(name, "Waves"); -} diff --git a/tests/Waves/TransactionTests.cpp b/tests/Waves/TransactionTests.cpp deleted file mode 100644 index bfb7998d99f..00000000000 --- a/tests/Waves/TransactionTests.cpp +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include "Waves/Address.h" -#include "proto/Waves.pb.h" -#include "Waves/Transaction.h" - -#include -#include - -using json = nlohmann::json; - -using namespace std; -using namespace TW; -using namespace TW::Waves; - -TEST(WavesTransaction, serialize) { - const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); - auto input = Proto::SigningInput(); - input.set_timestamp(int64_t(1526641218066)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - auto& message = *input.mutable_transfer_message(); - message.set_amount(int64_t(100000000)); - message.set_asset(""); - message.set_fee(int64_t(100000000)); - message.set_fee_asset(Transaction::WAVES); - message.set_to("3PLgzJXQiN77G7KgnR1WVa8jBYhF2dmWndx"); - message.set_attachment("falafel"); - auto tx1 = Transaction( - input, - /* pub_key */ - parse_hex("d528aabec35ca100d87c7b7a128632faf19cd44531819457445113a32a21ef22")); - auto serialized1 = tx1.serializeToSign(); - ASSERT_EQ(hex(serialized1), "0402d528aabec35ca100d87c7b7a128632faf19cd44531819457445113a32a21ef" - "2200000000016372e852120000000005f5e1000000000005f5e1000157cdc9381c" - "071beb5abd27738d5cd36cf75f3cbfdd69e8e6bb000766616c6166656c"); - - - auto input2 = Proto::SigningInput(); - input2.set_timestamp(int64_t(1)); - input2.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - auto& message2 = *input2.mutable_transfer_message(); - message2.set_amount(int64_t(1)); - message2.set_asset("DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq"); - message2.set_fee(int64_t(1)); - message2.set_fee_asset("DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq"); - message2.set_to("3PLgzJXQiN77G7KgnR1WVa8jBYhF2dmWndx"); - message2.set_attachment(""); - - auto tx2 = Transaction( - input2, - /* pub_key */ - parse_hex("d528aabec35ca100d87c7b7a128632faf19cd44531819457445113a32a21ef22")); - auto serialized2 = tx2.serializeToSign(); - ASSERT_EQ(hex(serialized2), - "0402d528aabec35ca100d87c7b7a128632faf19cd44531819457445113a32a21ef2201bae8ddc9955fa6" - "f69f8e7b155efcdb97bc3bb3a95db4c4604408cec245cd187201bae8ddc9955fa6f69f8e7b155efcdb97" - "bc3bb3a95db4c4604408cec245cd18720000000000000001000000000000000100000000000000010157" - "cdc9381c071beb5abd27738d5cd36cf75f3cbfdd69e8e6bb0000"); -} - -TEST(WavesTransaction, failedSerialize) { - // 141 bytes attachment - const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); - auto input = Proto::SigningInput(); - input.set_timestamp(int64_t(1526641218066)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - auto& message = *input.mutable_transfer_message(); - message.set_amount(int64_t(100000000)); - message.set_asset(""); - message.set_fee(int64_t(100000000)); - message.set_fee_asset(""); - message.set_to("3PLgzJXQiN77G7KgnR1WVa8jBYhF2dmWndx"); - message.set_attachment("falafelfalafelfalafelfalafelfalafelfalafelfalafel" - "falafelfalafelfalafelfalafelfalafelfalafelfalafel" - "falafelfalafelfalafelfalafelfalafelfalafel"); - auto tx1 = Transaction( - input, - /* pub_key */ - parse_hex("d528aabec35ca100d87c7b7a128632faf19cd44531819457445113a32a21ef22")); - EXPECT_THROW(tx1.serializeToSign(), invalid_argument); -} - -TEST(WavesTransaction, jsonSerialize) { - - const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); - const auto publicKeyCurve25519 = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); - ASSERT_EQ(hex(Data(publicKeyCurve25519.bytes.begin(), publicKeyCurve25519.bytes.end())), - "559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"); - const auto address = Address(publicKeyCurve25519); - - auto input = Proto::SigningInput(); - input.set_timestamp(int64_t(1526641218066)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - auto& message = *input.mutable_transfer_message(); - message.set_amount(int64_t(10000000)); - message.set_asset("DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq"); - message.set_fee(int64_t(100000000)); - message.set_fee_asset("DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD82zq"); - message.set_to(address.string()); - message.set_attachment("falafel"); - auto tx1 = Transaction( - input, - /* pub_key */ - parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d")); - - - auto signature = Signer::sign(privateKey, tx1); - - auto json = tx1.buildJson(signature); - - ASSERT_EQ(json["type"], TransactionType::transfer); - ASSERT_EQ(json["version"], TransactionVersion::V2); - ASSERT_EQ(json["fee"], int64_t(100000000)); - ASSERT_EQ(json["senderPublicKey"], "6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU"); - ASSERT_EQ(json["timestamp"], int64_t(1526641218066)); - ASSERT_EQ(json["proofs"].dump(), "[\"5ynN2NUiFHkQzw9bK8R7dZcNfTWMAtcWRJsrMvFFM6dUT3fSnPCCX7CTajNU8bJCB" - "H69vU1mnwfx4zpDtF1SkzKg\"]"); - ASSERT_EQ(json["recipient"], "3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds"); - ASSERT_EQ(json["assetId"], "DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq"); - ASSERT_EQ(json["feeAssetId"], "DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD82zq"); - ASSERT_EQ(json["amount"], int64_t(10000000)); - ASSERT_EQ(json["attachment"], "4t2Xazb2SX"); - ASSERT_EQ(json.dump(), "{\"amount\":10000000,\"assetId\":\"DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq\",\"attachment\":\"4t2Xazb2SX\",\"fee\":100000000,\"feeAssetId\":\"DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD82zq\",\"proofs\":[\"5ynN2NUiFHkQzw9bK8R7dZcNfTWMAtcWRJsrMvFFM6dUT3fSnPCCX7CTajNU8bJCBH69vU1mnwfx4zpDtF1SkzKg\"],\"recipient\":\"3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds\",\"senderPublicKey\":\"6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU\",\"timestamp\":1526641218066,\"type\":4,\"version\":2}"); -} diff --git a/tests/Zcash/AddressTests.cpp b/tests/Zcash/AddressTests.cpp deleted file mode 100644 index d539ac10b77..00000000000 --- a/tests/Zcash/AddressTests.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Zcash/TAddress.h" -#include "HexCoding.h" -#include "PrivateKey.h" - -#include - -namespace TW::Zcash { - -TEST(ZcashAddress, FromPrivateKey) { - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - const auto address = TAddress(publicKey); - - EXPECT_EQ(address.string(), "t1Wg9uPPAfwhBWeRjtDPa5ZHNzyBx9rJVKY"); - EXPECT_EQ(address.bytes[0], 0x1c); - EXPECT_EQ(address.bytes[1], 0xb8); -} - -TEST(ZcashAddress, FromPublicKey) { - const auto privateKey = PrivateKey(parse_hex("BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835")); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - const auto address = TAddress(publicKey); - - EXPECT_EQ(address.string(), "t1gaySCXCYtXE3ygP38YuWtVZczsEbdjG49"); - EXPECT_EQ(address.bytes[0], 0x1c); - EXPECT_EQ(address.bytes[1], 0xb8); -} - -TEST(ZcashAddress, Valid) { - EXPECT_TRUE(TAddress::isValid(std::string("t1RygJmrLdNGgi98gUgEJDTVaELTAYWoMBy"))); - EXPECT_TRUE(TAddress::isValid(std::string("t1TWk2mmvESDnE4dmCfT7MQ97ij6ZqLpNVU"))); - EXPECT_TRUE(TAddress::isValid(std::string("t3RD6RFKhWSotNbPEY4Vw7Ku9QCfKkzrbBL"))); -} - -TEST(ZcashAddress, Invalid) { - EXPECT_FALSE(TAddress::isValid(std::string("abc"))); - EXPECT_FALSE(TAddress::isValid(std::string("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"))); - EXPECT_FALSE(TAddress::isValid(std::string("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W"))); - EXPECT_FALSE(TAddress::isValid(std::string("t1RygJmrLdNGgi98+UgEJDTVaELTAYWoMBy"))); // Invalid Base58 - EXPECT_FALSE(TAddress::isValid(std::string("t1RygJmrLdNGgi98gUgEJDTVaELTAYW"))); // too short - EXPECT_FALSE(TAddress::isValid(std::string("t1RygJmrLdNGgi98gUgEJDTVaELTAYWoMBz"))); // bad checksum - EXPECT_FALSE(TAddress::isValid(std::string("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"))); // too short - EXPECT_FALSE(TAddress::isValid(std::string("2NRbuP5YfzRNEa1RibT5kXay1VgvQHnydZY1"))); // invalid prefix -} - -TEST(ZcashAddress, InitWithString) { - { - const auto address = TAddress("t1Wg9uPPAfwhBWeRjtDPa5ZHNzyBx9rJVKY"); - EXPECT_EQ(address.string(), "t1Wg9uPPAfwhBWeRjtDPa5ZHNzyBx9rJVKY"); - EXPECT_EQ(address.bytes[0], 0x1c); - EXPECT_EQ(address.bytes[1], 0xb8); - } - { - const auto address = TAddress("t3RD6RFKhWSotNbPEY4Vw7Ku9QCfKkzrbBL"); - EXPECT_EQ(address.string(), "t3RD6RFKhWSotNbPEY4Vw7Ku9QCfKkzrbBL"); - EXPECT_EQ(address.bytes[0], 0x1c); - EXPECT_EQ(address.bytes[1], 0xbd); - } -} - -} // namespace TW::Zcash diff --git a/tests/Zcash/TWCoinTypeTests.cpp b/tests/Zcash/TWCoinTypeTests.cpp deleted file mode 100644 index 475ffafd17b..00000000000 --- a/tests/Zcash/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWZcashCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeZcash)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("f2438a93039faf08d39bd3df1f7b5f19a2c29ffe8753127e2956ab4461adab35")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeZcash, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("t1Yfrf1dssDLmaMBsq2LFKWPbS5vH3nGpa2")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeZcash, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeZcash)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeZcash)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeZcash), 8); - ASSERT_EQ(TWBlockchainZcash, TWCoinTypeBlockchain(TWCoinTypeZcash)); - ASSERT_EQ(0xbd, TWCoinTypeP2shPrefix(TWCoinTypeZcash)); - ASSERT_EQ(0x1c, TWCoinTypeStaticPrefix(TWCoinTypeZcash)); - assertStringsEqual(symbol, "ZEC"); - assertStringsEqual(txUrl, "https://blockchair.com/zcash/transaction/f2438a93039faf08d39bd3df1f7b5f19a2c29ffe8753127e2956ab4461adab35"); - assertStringsEqual(accUrl, "https://blockchair.com/zcash/address/t1Yfrf1dssDLmaMBsq2LFKWPbS5vH3nGpa2"); - assertStringsEqual(id, "zcash"); - assertStringsEqual(name, "Zcash"); -} diff --git a/tests/Zcash/TWZcashTransactionTests.cpp b/tests/Zcash/TWZcashTransactionTests.cpp deleted file mode 100644 index 96861a4a507..00000000000 --- a/tests/Zcash/TWZcashTransactionTests.cpp +++ /dev/null @@ -1,188 +0,0 @@ - -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" - -#include "Bitcoin/OutPoint.h" -#include "Bitcoin/Script.h" -#include "Zcash/TransactionBuilder.h" -#include "Bitcoin/TransactionSigner.h" -#include "HexCoding.h" -#include "PublicKey.h" -#include "Data.h" -#include "Coin.h" -#include "Zcash/Transaction.h" - -#include - -#include - -using namespace TW; - -TEST(TWZcashTransaction, Encode) { - // Test vector 3 https://github.com/zcash/zips/blob/master/zip-0243.rst - auto transaction = Zcash::Transaction(); - transaction.lockTime = 0x0004b029; - transaction.expiryHeight = 0x0004b048; - transaction.branchId = Zcash::SaplingBranchID; - - auto outpoint0 = Bitcoin::OutPoint(parse_hex("a8c685478265f4c14dada651969c45a65e1aeb8cd6791f2f5bb6a1d9952104d9"), 1); - transaction.inputs.emplace_back(outpoint0, Bitcoin::Script(parse_hex("483045022100a61e5d557568c2ddc1d9b03a7173c6ce7c996c4daecab007ac8f34bee01e6b9702204d38fdc0bcf2728a69fde78462a10fb45a9baa27873e6a5fc45fb5c76764202a01210365ffea3efa3908918a8b8627724af852fc9b86d7375b103ab0543cf418bcaa7f")), 0xfffffffe); - - auto script0 = Bitcoin::Script(parse_hex("76a9148132712c3ff19f3a151234616777420a6d7ef22688ac")); - transaction.outputs.emplace_back(0x02625a00, script0); - - auto script1 = Bitcoin::Script(parse_hex("76a9145453e4698f02a38abdaa521cd1ff2dee6fac187188ac")); - transaction.outputs.emplace_back(0x0098958b, script1); - - auto unsignedData = Data{}; - transaction.encode(unsignedData); - - ASSERT_EQ(hex(unsignedData.begin(), unsignedData.end()), - /* header */ "04000080" - /* versionGroupId */ "85202f89" - /* vin */ "01""a8c685478265f4c14dada651969c45a65e1aeb8cd6791f2f5bb6a1d9952104d9""01000000""6b483045022100a61e5d557568c2ddc1d9b03a7173c6ce7c996c4daecab007ac8f34bee01e6b9702204d38fdc0bcf2728a69fde78462a10fb45a9baa27873e6a5fc45fb5c76764202a01210365ffea3efa3908918a8b8627724af852fc9b86d7375b103ab0543cf418bcaa7f""feffffff" - /* vout */ "02""005a620200000000""1976a9148132712c3ff19f3a151234616777420a6d7ef22688ac" - "8b95980000000000""1976a9145453e4698f02a38abdaa521cd1ff2dee6fac187188ac" - /* lockTime */ "29b00400" - /* expiryHeight */ "48b00400" - /* valueBalance */ "0000000000000000" - /* vShieldedSpend */ "00" - /* vShieldedOutput */ "00" - /* vJoinSplit */ "00" - ); - - auto scriptCode = Bitcoin::Script(parse_hex("76a914507173527b4c3318a2aecd793bf1cfed705950cf88ac")); - auto preImage = transaction.getPreImage(scriptCode, 0, TWBitcoinSigHashTypeAll, 0x02faf080); - ASSERT_EQ(hex(preImage.begin(), preImage.end()), - /* header */ "04000080" - /* versionGroupId */ "85202f89" - /* hashPrevouts */ "fae31b8dec7b0b77e2c8d6b6eb0e7e4e55abc6574c26dd44464d9408a8e33f11" - /* hashSequence */ "6c80d37f12d89b6f17ff198723e7db1247c4811d1a695d74d930f99e98418790" - /* hashOutputs */ "d2b04118469b7810a0d1cc59568320aad25a84f407ecac40b4f605a4e6868454" - /* hashJoinSplits */ "0000000000000000000000000000000000000000000000000000000000000000" - /* hashShieldedSpends */ "0000000000000000000000000000000000000000000000000000000000000000" - /* hashShieldedOutputs */ "0000000000000000000000000000000000000000000000000000000000000000" - /* lockTime */ "29b00400" - /* expiryHeight */ "48b00400" - /* valueBalance */ "0000000000000000" - /* hashType */ "01000000" - /* prevout */ "a8c685478265f4c14dada651969c45a65e1aeb8cd6791f2f5bb6a1d9952104d9""01000000" - /* scriptCode */ "1976a914507173527b4c3318a2aecd793bf1cfed705950cf88ac" - /* amount */ "80f0fa0200000000" - /* sequence */ "feffffff" - ); - - auto sighash = transaction.getSignatureHash(scriptCode, 0, TWBitcoinSigHashTypeAll, 0x02faf080, Bitcoin::BASE); - ASSERT_EQ(hex(sighash.begin(), sighash.end()), "f3148f80dfab5e573d5edfe7a850f5fd39234f80b5429d3a57edcc11e34c585b"); -} - -TEST(TWZcashTransaction, SaplingSigning) { - // tx on mainnet - // https://explorer.zcha.in/transactions/ec9033381c1cc53ada837ef9981c03ead1c7c41700ff3a954389cfaddc949256 - const int64_t amount = 488000; - const int64_t fee = 6000; - - auto input = Bitcoin::Proto::SigningInput(); - input.set_hash_type(TWBitcoinSigHashTypeAll); - input.set_amount(amount); - input.set_byte_fee(1); - input.set_to_address("t1QahNjDdibyE4EdYkawUSKBBcVTSqv64CS"); - - auto hash0 = DATA("53685b8809efc50dd7d5cb0906b307a1b8aa5157baa5fc1bd6fe2d0344dd193a"); - auto utxo0 = input.add_utxo(); - utxo0->mutable_out_point()->set_hash(TWDataBytes(hash0.get()), TWDataSize(hash0.get())); - utxo0->mutable_out_point()->set_index(0); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX); - utxo0->set_amount(494000); - auto script0 = parse_hex("76a914f84c7f4dd3c3dc311676444fdead6e6d290d50e388ac"); - utxo0->set_script(script0.data(), script0.size()); - - auto utxoKey0 = DATA("a9684f5bebd0e1208aae2e02bc9e9163bd1965ad23d8538644e1df8b99b99559"); - input.add_private_key(TWDataBytes(utxoKey0.get()), TWDataSize(utxoKey0.get())); - - auto plan = Zcash::TransactionBuilder::plan(input); - plan.amount = amount; - plan.fee = fee; - plan.change = 0; - plan.branchId = Data(Zcash::SaplingBranchID.begin(), Zcash::SaplingBranchID.end()); - - auto& protoPlan = *input.mutable_plan(); - protoPlan = plan.proto(); - - // Sign - auto result = Bitcoin::TransactionSigner::sign(input); - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - // txid = "ec9033381c1cc53ada837ef9981c03ead1c7c41700ff3a954389cfaddc949256" - - Data serialized; - signedTx.encode(serialized); - ASSERT_EQ(hex(serialized), - "04000080" - "85202f89" - "01" - "53685b8809efc50dd7d5cb0906b307a1b8aa5157baa5fc1bd6fe2d0344dd193a""00000000""6b483045022100ca0be9f37a4975432a52bb65b25e483f6f93d577955290bb7fb0060a93bfc92002203e0627dff004d3c72a957dc9f8e4e0e696e69d125e4d8e275d119001924d3b48012103b243171fae5516d1dc15f9178cfcc5fdc67b0a883055c117b01ba8af29b953f6""ffffffff" - "01" - "4072070000000000""1976a91449964a736f3713d64283fd0018626ba50091c7e988ac" - "00000000" - "00000000" - "0000000000000000" - "00" - "00" - "00" - ); -} - -TEST(TWZcashTransaction, BlossomSigning) { - // tx on mainnet - // https://explorer.zcha.in/transactions/387939ff8eb07dd264376eeef2e126394ab139802b1d80e92b21c1a2ae54fe92 - const int64_t amount = 17615; - const int64_t fee = 10000; - const std::string toAddress = "t1biXYN8wJahR76SqZTe1LBzTLf3JAsmT93"; - - auto input = Bitcoin::Proto::SigningInput(); - input.set_hash_type(TWBitcoinSigHashTypeAll); - input.set_amount(amount); - input.set_byte_fee(1); - input.set_to_address(toAddress); - input.set_coin_type(TWCoinTypeZcash); - - auto txHash0 = parse_hex("2381825cd9069a200944996257e25b9403ba3e296bbc1dd98b01019cc7028cde"); - std::reverse(txHash0.begin(), txHash0.end()); - - auto utxo0 = input.add_utxo(); - utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); - utxo0->mutable_out_point()->set_index(0); - utxo0->mutable_out_point()->set_sequence(UINT32_MAX); - utxo0->set_amount(27615); - - // real key 1p "m/44'/133'/0'/0/14" - auto utxoKey0 = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); - auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZcash, utxoKey0); - auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZcash); - utxo0->set_script(script0.bytes.data(), script0.bytes.size()); - input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); - - auto plan = Zcash::TransactionBuilder::plan(input); - plan.amount = amount; - plan.fee = fee; - plan.change = 0; - - auto& protoPlan = *input.mutable_plan(); - protoPlan = plan.proto(); - - // Sign - auto result = Bitcoin::TransactionSigner::sign(input); - ASSERT_TRUE(result) << std::to_string(result.error()); - auto signedTx = result.payload(); - - Data serialized; - signedTx.encode(serialized); - ASSERT_EQ(hex(serialized), "0400008085202f8901de8c02c79c01018bd91dbc6b293eba03945be25762994409209a06d95c828123000000006b483045022100e6e5071811c08d0c2e81cb8682ee36a8c6b645f5c08747acd3e828de2a4d8a9602200b13b36a838c7e8af81f2d6e7e694ede28833a480cfbaaa68a47187655298a7f0121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ffffffff01cf440000000000001976a914c3bacb129d85288a3deb5890ca9b711f7f71392688ac00000000000000000000000000000000000000"); -} diff --git a/tests/Zelcash/TWCoinTypeTests.cpp b/tests/Zelcash/TWCoinTypeTests.cpp deleted file mode 100644 index ee7bce7b346..00000000000 --- a/tests/Zelcash/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWZelcashCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeZelcash)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeZelcash, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeZelcash, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeZelcash)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeZelcash)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeZelcash), 8); - ASSERT_EQ(TWBlockchainZcash, TWCoinTypeBlockchain(TWCoinTypeZelcash)); - ASSERT_EQ(0xbd, TWCoinTypeP2shPrefix(TWCoinTypeZelcash)); - ASSERT_EQ(0x1c, TWCoinTypeStaticPrefix(TWCoinTypeZelcash)); - assertStringsEqual(symbol, "FLUX"); - assertStringsEqual(txUrl, "https://explorer.runonflux.io/tx/t123"); - assertStringsEqual(accUrl, "https://explorer.runonflux.io/address/a12"); - assertStringsEqual(id, "zelcash"); - assertStringsEqual(name, "Flux"); -} diff --git a/tests/Zilliqa/AddressTests.cpp b/tests/Zilliqa/AddressTests.cpp deleted file mode 100644 index e4f0a49e269..00000000000 --- a/tests/Zilliqa/AddressTests.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "PrivateKey.h" -#include "Zilliqa/Address.h" -#include "Zilliqa/AddressChecksum.h" - -#include - -#include - -using namespace TW; -using namespace TW::Zilliqa; - -TEST(ZilliqaAddress, FromPrivateKey) { - const auto privateKey = - PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); - const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const auto address = Address(publicKey); - auto expectedAddress = "zil1j8xae6lggm8y63m3y2r7aefu797ze7mhzulnqg"; - - ASSERT_EQ(address.getHrp(), stringForHRP(TWHRPZilliqa)); - ASSERT_EQ(address.string(), expectedAddress); -} - -TEST(ZilliqaAddress, Validation) { - ASSERT_FALSE(Zilliqa::Address::isValid("0x91cddcebe846ce4d47712287eee53cf17c2cfb7")); - ASSERT_FALSE(Zilliqa::Address::isValid("")); - ASSERT_FALSE(Zilliqa::Address::isValid("0x")); - ASSERT_FALSE(Zilliqa::Address::isValid("91cddcebe846ce4d47712287eee53cf17c2cfb7")); - - ASSERT_TRUE(Zilliqa::Address::isValid("zil1fwh4ltdguhde9s7nysnp33d5wye6uqpugufkz7")); -} - -TEST(ZilliqaAddress, Checksum) { - ASSERT_EQ( - checksum(parse_hex("4BAF5FADA8E5DB92C3D3242618C5B47133AE003C")), - "4BAF5faDA8e5Db92C3d3242618c5B47133AE003C" - ); - ASSERT_EQ( - checksum(parse_hex("448261915A80CDE9BDE7C7A791685200D3A0BF4E")), - "448261915a80cdE9BDE7C7a791685200D3A0bf4E" - ); - ASSERT_EQ( - checksum(parse_hex("0xDED02FD979FC2E55C0243BD2F52DF022C40ADA1E")), - "Ded02fD979fC2e55c0243bd2F52df022c40ADa1E" - ); - ASSERT_EQ( - checksum(parse_hex("0x13F06E60297BEA6A3C402F6F64C416A6B31E586E")), - "13F06E60297bea6A3c402F6f64c416A6b31e586e" - ); - ASSERT_EQ( - checksum(parse_hex("0x1A90C25307C3CC71958A83FA213A2362D859CF33")), - "1a90C25307C3Cc71958A83fa213A2362D859CF33" - ); -} diff --git a/tests/Zilliqa/SignatureTests.cpp b/tests/Zilliqa/SignatureTests.cpp deleted file mode 100644 index 386e655cfbf..00000000000 --- a/tests/Zilliqa/SignatureTests.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "../interface/TWTestUtilities.h" -#include "HexCoding.h" -#include "Data.h" -#include -#include - -#include - -using namespace TW; - -TEST(ZilliqaSignature, Signing) { - auto keyData = WRAPD(TWDataCreateWithHexString(STRING("0xafeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get())); - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(keyData.get())); - auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); - - auto message = "hello schnorr"; - auto messageData = WRAPD(TWDataCreateWithBytes((uint8_t *)message, strnlen(message, 13))); - auto signatureData = WRAPD(TWPrivateKeySignSchnorr(privateKey.get(), messageData.get(), TWCurveSECP256k1)); - auto signature = data(TWDataBytes(signatureData.get()), TWDataSize(signatureData.get())); - - ASSERT_TRUE(TWPublicKeyVerifySchnorr(pubKey.get(), signatureData.get(), messageData.get())); - EXPECT_EQ(hex(signature), "d166b1ae7892c5ef541461dc12a50214d0681b63d8037cda29a3fe6af8bb973e4ea94624d85bc0010bdc1b38d05198328fae21254adc2bf5feaf2804d54dba55"); -} diff --git a/tests/Zilliqa/SignerTests.cpp b/tests/Zilliqa/SignerTests.cpp deleted file mode 100644 index f261634d650..00000000000 --- a/tests/Zilliqa/SignerTests.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "PrivateKey.h" -#include "Zilliqa/Address.h" -#include "Zilliqa/Signer.h" -#include "proto/Zilliqa.pb.h" -#include "uint256.h" - -#include - -using namespace TW; -using namespace TW::Zilliqa; - -TEST(ZilliqaSigner, PreImage) { - auto privateKey = PrivateKey(parse_hex("0E891B9DFF485000C7D1DC22ECF3A583CC50328684321D61947A86E57CF6C638")); - auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - ASSERT_EQ(hex(pubKey.bytes), "034ae47910d58b9bde819c3cffa8de4441955508db00aa2540db8e6bf6e99abc1b"); - - auto amount = uint256_t(15000000000000); - auto gasPrice = uint256_t(1000000000); - auto amountData = store(amount); - auto gasData = store(gasPrice); - auto toAddress = Address(parse_hex("0x9Ca91EB535Fb92Fda5094110FDaEB752eDb9B039")); - - auto input = Proto::SigningInput(); - auto& tx = *input.mutable_transaction(); - auto& transfer = *tx.mutable_transfer(); - transfer.set_amount(amountData.data(), amountData.size()); - - input.set_version(65537); - input.set_nonce(4); - input.set_to(toAddress.string()); - input.set_gas_price(gasData.data(), gasData.size()); - input.set_gas_limit(uint64_t(1)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - Address address; - auto preImage = Signer::getPreImage(input, address); - auto signature = Signer::sign(input).signature(); - - ASSERT_EQ(hex(preImage.begin(), preImage.end()), "0881800410041a149ca91eb535fb92fda5094110fdaeb752edb9b03922230a21034ae47910d58b9bde819c3cffa8de4441955508db00aa2540db8e6bf6e99abc1b2a120a10000000000000000000000da475abf00032120a100000000000000000000000003b9aca003801"); - - ASSERT_TRUE(pubKey.verifySchnorr(Data(signature.begin(), signature.end()), preImage)); -} - -TEST(ZilliqaSigner, Signing) { - auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")); - auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - - // 1 ZIL - auto amount = uint256_t(1000000000000); - auto gasPrice = uint256_t(1000000000); - auto amountData = store(amount); - auto gasData = store(gasPrice); - auto toAddress = Address(parse_hex("0x7FCcaCf066a5F26Ee3AFfc2ED1FA9810Deaa632C")); - - auto input = Proto::SigningInput(); - auto& tx = *input.mutable_transaction(); - auto& transfer = *tx.mutable_transfer(); - transfer.set_amount(amountData.data(), amountData.size()); - - input.set_version(65537); - input.set_nonce(2); - input.set_to(toAddress.string()); - input.set_gas_price(gasData.data(), gasData.size()); - input.set_gas_limit(uint64_t(1)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - auto output = Signer::sign(input); - - ASSERT_EQ(hex(output.signature().begin(), output.signature().end()), "001fa4df08c11a4a79e96e69399ee48eeecc78231a78b0355a8ca783c77c139436e37934fecc2252ed8dac00e235e22d18410461fb896685c4270642738ed268"); - ASSERT_EQ(output.json(), R"({"amount":"1000000000000","code":"","data":"","gasLimit":"1","gasPrice":"1000000000","nonce":2,"pubKey":"03fb30b196ce3e976593ecc2da220dca9cdea8c84d2373770042a930b892ac0f5c","signature":"001fa4df08c11a4a79e96e69399ee48eeecc78231a78b0355a8ca783c77c139436e37934fecc2252ed8dac00e235e22d18410461fb896685c4270642738ed268","toAddr":"7FCcaCf066a5F26Ee3AFfc2ED1FA9810Deaa632C","version":65537})"); -} - -TEST(ZilliqaSigner, SigningData) { - // https://viewblock.io/zilliqa/tx/0x6228b3d7e69fc3481b84fd00e892cec359a41654f58948ff7b1b932396b00ad9 - auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")); - auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - - // 10 ZIL - auto amount = uint256_t(10000000000000); - auto gasPrice = uint256_t(2000000000); - auto amountData = store(amount); - auto gasData = store(gasPrice); - - std::string json = "{\"_tag\":\"DelegateStake\",\"params\":[{\"type\":\"ByStr20\",\"value\":\"0x122219cCeAb410901e96c3A0e55E46231480341b\",\"vname\":\"ssnaddr\"}]}"; - auto jsonData = Data(json.begin(), json.end()); - - auto input = Proto::SigningInput(); - auto& tx = *input.mutable_transaction(); - auto& raw = *tx.mutable_raw_transaction(); - raw.set_amount(amountData.data(), amountData.size()); - raw.set_data(jsonData.data(), jsonData.size()); - - input.set_version(65537); - input.set_nonce(56); - input.set_to("zil1g029nmzsf36r99vupp4s43lhs40fsscx3jjpuy"); - input.set_gas_price(gasData.data(), gasData.size()); - input.set_gas_limit(uint64_t(5000)); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - - auto output = Signer::sign(input); - ASSERT_EQ(output.json(), R"({"amount":"10000000000000","code":"","data":"{\"_tag\":\"DelegateStake\",\"params\":[{\"type\":\"ByStr20\",\"value\":\"0x122219cCeAb410901e96c3A0e55E46231480341b\",\"vname\":\"ssnaddr\"}]}","gasLimit":"5000","gasPrice":"2000000000","nonce":56,"pubKey":"03fb30b196ce3e976593ecc2da220dca9cdea8c84d2373770042a930b892ac0f5c","signature":"437fb5c3ce2c6b01f9d490f670539fae4533c82a21fa7edfe6b23df70d732937e8c578c8d6ed24be9150f5126f7b7c977a467af8947ef92a720908a761a6eb0d","toAddr":"43D459eC504C7432959c086B0ac7F7855E984306","version":65537})"); - ASSERT_EQ(hex(output.signature().begin(), output.signature().end()), "437fb5c3ce2c6b01f9d490f670539fae4533c82a21fa7edfe6b23df70d732937e8c578c8d6ed24be9150f5126f7b7c977a467af8947ef92a720908a761a6eb0d"); -} diff --git a/tests/Zilliqa/TWAnySignerTests.cpp b/tests/Zilliqa/TWAnySignerTests.cpp deleted file mode 100644 index e9cbe99aa3f..00000000000 --- a/tests/Zilliqa/TWAnySignerTests.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "HexCoding.h" -#include "uint256.h" -#include "proto/Zilliqa.pb.h" -#include "../interface/TWTestUtilities.h" -#include -#include - -using namespace TW; -using namespace TW::Zilliqa; - -TEST(TWAnySignerZilliqa, Sign) { - auto input = Proto::SigningInput(); - auto& tx = *input.mutable_transaction(); - auto& transfer = *tx.mutable_transfer(); - auto key = parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748"); - auto amount = store(uint256_t(1000000000000)); - auto gasPrice = store(uint256_t(1000000000)); - - input.set_version(65537); - input.set_nonce(2); - input.set_to("zil10lx2eurx5hexaca0lshdr75czr025cevqu83uz"); - input.set_gas_price(gasPrice.data(), gasPrice.size()); - input.set_gas_limit(1); - input.set_private_key(key.data(), key.size()); - transfer.set_amount(amount.data(), amount.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeZilliqa); - - EXPECT_EQ(hex(output.signature()), "001fa4df08c11a4a79e96e69399ee48eeecc78231a78b0355a8ca783c77c139436e37934fecc2252ed8dac00e235e22d18410461fb896685c4270642738ed268"); - EXPECT_EQ(hex(output.json()), "7b22616d6f756e74223a2231303030303030303030303030222c22636f6465223a22222c2264617461223a22222c226761734c696d6974223a2231222c226761735072696365223a2231303030303030303030222c226e6f6e6365223a322c227075624b6579223a22303366623330623139366365336539373635393365636332646132323064636139636465613863383464323337333737303034326139333062383932616330663563222c227369676e6174757265223a223030316661346466303863313161346137396539366536393339396565343865656563633738323331613738623033353561386361373833633737633133393433366533373933346665636332323532656438646163303065323335653232643138343130343631666238393636383563343237303634323733386564323638222c22746f41646472223a2237464363614366303636613546323645653341466663324544314641393831304465616136333243222c2276657273696f6e223a36353533377d"); -} - -TEST(TWAnySignerZilliqa, SignJSON) { - auto json = STRING(R"({"version":65537,"nonce":"2","to":"zil10lx2eurx5hexaca0lshdr75czr025cevqu83uz","gasPrice":"O5rKAA==","gasLimit":"1","transaction":{"transfer":{"amount":"6NSlEAA="}}})"); - auto key = DATA("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748"); - auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeZilliqa)); - - ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeZilliqa)); - assertStringsEqual(result, "7b22616d6f756e74223a2231303030303030303030303030222c22636f6465223a22222c2264617461223a22222c226761734c696d6974223a2231222c226761735072696365223a2231303030303030303030222c226e6f6e6365223a322c227075624b6579223a22303366623330623139366365336539373635393365636332646132323064636139636465613863383464323337333737303034326139333062383932616330663563222c227369676e6174757265223a223030316661346466303863313161346137396539366536393339396565343865656563633738323331613738623033353561386361373833633737633133393433366533373933346665636332323532656438646163303065323335653232643138343130343631666238393636383563343237303634323733386564323638222c22746f41646472223a2237464363614366303636613546323645653341466663324544314641393831304465616136333243222c2276657273696f6e223a36353533377d"); -} diff --git a/tests/Zilliqa/TWCoinTypeTests.cpp b/tests/Zilliqa/TWCoinTypeTests.cpp deleted file mode 100644 index 0b1d57ea02b..00000000000 --- a/tests/Zilliqa/TWCoinTypeTests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. -// -// This is a GENERATED FILE, changes made here MAY BE LOST. -// Generated one-time (codegen/bin/cointests) -// - -#include "../interface/TWTestUtilities.h" -#include -#include - - -TEST(TWZilliqaCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeZilliqa)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeZilliqa, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeZilliqa, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeZilliqa)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeZilliqa)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeZilliqa), 12); - ASSERT_EQ(TWBlockchainZilliqa, TWCoinTypeBlockchain(TWCoinTypeZilliqa)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeZilliqa)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeZilliqa)); - assertStringsEqual(symbol, "ZIL"); - assertStringsEqual(txUrl, "https://viewblock.io/zilliqa/tx/t123"); - assertStringsEqual(accUrl, "https://viewblock.io/zilliqa/address/a12"); - assertStringsEqual(id, "zilliqa"); - assertStringsEqual(name, "Zilliqa"); -} diff --git a/tests/algorithm/erase_tests.cpp b/tests/algorithm/erase_tests.cpp deleted file mode 100644 index 39a237e375a..00000000000 --- a/tests/algorithm/erase_tests.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "algorithm/erase.h" - -#include "gtest/gtest.h" -#include // std::iota - -using namespace TW; - -TEST(Algorithm, Erase) { - std::vector cnt(10); - std::iota(cnt.begin(), cnt.end(), '0'); - cnt.back() = '3'; - std::size_t nbElementsErased = erase(cnt, '3'); - ASSERT_EQ(cnt.size(), 8); - ASSERT_EQ(nbElementsErased, 2); -} - -TEST(Algorithm, EraseIf) { - std::vector cnt(10); - std::iota(cnt.begin(), cnt.end(), '0'); - auto erased = erase_if(cnt, [](char x) { return (x - '0') % 2 == 0; }); - ASSERT_EQ(cnt.size(), 5); - ASSERT_EQ(erased, 5); -} \ No newline at end of file diff --git a/tests/algorithm/sort_copy_tests.cpp b/tests/algorithm/sort_copy_tests.cpp deleted file mode 100644 index 2603ee27b5a..00000000000 --- a/tests/algorithm/sort_copy_tests.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright © 2017-2022 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "algorithm/sort_copy.h" - -#include "gtest/gtest.h" - -using namespace TW; - -struct Amount { - int value; -}; - -TEST(SortCopy, IsSorted) { - std::vector data{9, 1, 2, 4, 5}; - const auto sorted = sortCopy(data); - std::vector anotherData{Amount{.value = 9}, Amount{.value = 1}, Amount{.value = 0}}; - auto sortFunctor = [](auto&& lhs, auto&& rhs) { return lhs.value < rhs.value; }; - const auto anotherSorted = sortCopy(anotherData, sortFunctor); - ASSERT_TRUE(std::is_sorted(cbegin(sorted), cend(sorted))); - ASSERT_TRUE(std::is_sorted(cbegin(anotherSorted), cend(anotherSorted), sortFunctor)); -} \ No newline at end of file diff --git a/tests/chains/Acala/TWAnyAddressTests.cpp b/tests/chains/Acala/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..e62412a1736 --- /dev/null +++ b/tests/chains/Acala/TWAnyAddressTests.cpp @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include "Hash.h" +#include "PublicKey.h" + +#include "TestUtilities.h" +#include + +namespace TW::Polkadot::tests { + +inline constexpr uint32_t acalaPrefix{10}; + +TEST(TWAcalaAnyAddress, IsValid) { + EXPECT_TRUE(TWAnyAddressIsValidSS58(STRING("212ywJGVK2Nxnt5bjKXVHi4YY7FCFd4rVvhyt95CjpeHGZee").get(), TWCoinTypePolkadot, acalaPrefix)); + EXPECT_TRUE(TWAnyAddressIsValid(STRING("212ywJGVK2Nxnt5bjKXVHi4YY7FCFd4rVvhyt95CjpeHGZee").get(), TWCoinTypeAcala)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("212ywJGVK2Nxnt5bjKXVHi4YY7FCFd4rVvhyt95CjpeHGZee").get(), TWCoinTypePolkadot)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("212ywJGVK2Nxnt5bjKXVHi4YY7FCFd4rVvhyt95CjpeHGZee").get(), TWCoinTypeBitcoin)); + EXPECT_FALSE(TWAnyAddressIsValidSS58(STRING("15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu").get(), TWCoinTypePolkadot, acalaPrefix)); +} + +TEST(TWAcalaAnyAddress, createFromPubKey) { + const auto data = DATA("e9590e4d99264a14a85e21e69537e4a64f66a875d38cb8f76b305f41fabe24a9"); + const auto pubkey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(data.get(), TWPublicKeyTypeED25519)); + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubkey.get(), TWCoinTypeAcala)); + const auto addrDescription = TWAnyAddressDescription(addr.get()); + EXPECT_EQ("269ZCS3WLGydTN8ynhyhZfzJrXkePUcdhwgLQs6TWFs5wVL5", *reinterpret_cast(addrDescription)); + TWStringDelete(addrDescription); +} + +TEST(TWAcalaAnyAddress, createFromString) { + const auto acalaAddress = STRING("24CKv1LJ1T3U9ujCN63YzTPuQjcmURGA2xTjim98UKXxgNXT"); + const auto anyAddr = TWAnyAddressCreateWithString(acalaAddress.get(), TWCoinTypeAcala); + const auto addrDescription = TWAnyAddressDescription(anyAddr); + ASSERT_TRUE(TWAnyAddressIsValidSS58(addrDescription, TWCoinTypePolkadot, acalaPrefix)); + ASSERT_TRUE(TWAnyAddressIsValid(addrDescription, TWCoinTypeAcala)); + TWStringDelete(addrDescription); + TWAnyAddressDelete(anyAddr); +} + +TEST(TWAcalaAnyAddress, createFromPubKeyAcalaPrefix) { + const auto data = DATA("92fd9c237030356e26cfcc4568dc71055d5ec92dfe0ff903767e00611971bad3"); + const auto pubkey = TWPublicKeyCreateWithData(data.get(), TWPublicKeyTypeED25519); + const auto twAddress = TWAnyAddressCreateSS58WithPublicKey(pubkey, TWCoinTypePolkadot, acalaPrefix); + auto address = TWAnyAddressDescription(twAddress); + EXPECT_EQ("24CKv1LJ1T3U9ujCN63YzTPuQjcmURGA2xTjim98UKXxgNXT", *reinterpret_cast(address)); + TWStringDelete(address); + TWAnyAddressDelete(twAddress); + TWPublicKeyDelete(pubkey); +} + +TEST(TWAcalaAnyAddress, createFromStringAcalaPrefix) { + const auto acalaAddress = STRING("24CKv1LJ1T3U9ujCN63YzTPuQjcmURGA2xTjim98UKXxgNXT"); + const auto anyAddr = TWAnyAddressCreateSS58(acalaAddress.get(), TWCoinTypePolkadot, acalaPrefix); + const auto addrDescription = TWAnyAddressDescription(anyAddr); + ASSERT_TRUE(TWAnyAddressIsValidSS58(addrDescription, TWCoinTypePolkadot, acalaPrefix)); + TWStringDelete(addrDescription); + TWAnyAddressDelete(anyAddr); +} + +} diff --git a/tests/chains/Acala/TWAnySignerTests.cpp b/tests/chains/Acala/TWAnySignerTests.cpp new file mode 100644 index 00000000000..c69ada618f0 --- /dev/null +++ b/tests/chains/Acala/TWAnySignerTests.cpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "uint256.h" +#include "proto/Polkadot.pb.h" +#include "Polkadot/Extrinsic.h" +#include "PrivateKey.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::Polkadot::tests { + +TEST(TWAnySignerAcala, Sign) { + // Successfully broadcasted: https://acala.subscan.io/extrinsic/3893620-3 + auto secret = parse_hex("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"); + + auto genesisHash = parse_hex("fc41b9bd8ef8fe53d58c7ea67c794c7ec9a73daf05e6d54b14ff6342c99ba64c"); + auto blockHash = parse_hex("707ffa05b7dc6cdb6356bd8bd51ff20b2757c3214a76277516080a10f1bc7537"); + + auto input = Proto::SigningInput(); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + + input.set_nonce(0); + input.set_spec_version(2170); + input.set_private_key(secret.data(), secret.size()); + input.set_network(10); // Acala + input.set_transaction_version(2); + input.set_multi_address(true); + + auto &era = *input.mutable_era(); + era.set_block_number(3893613); + era.set_period(64); + + auto balanceCall = input.mutable_balance_call(); + auto &transfer = *balanceCall->mutable_transfer(); + auto value = store(uint256_t(1'000'000'000'000)); + transfer.set_to_address("25Qqz3ARAvnZbahGZUzV3xpP1bB3eRrupEprK7f2FNbHbvsz"); + transfer.set_value(value.data(), value.size()); + + auto* callIndices = transfer.mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x0a); + callIndices->set_method_index(0x00); + + auto extrinsic = Extrinsic(input); + auto preimage = extrinsic.encodePayload(); + EXPECT_EQ(hex(preimage), "0a0000c8c602ded977c56076ae38d98026fa669ca10d6a2b5a0bfc4086ae7668ed1c60070010a5d4e8d50200007a08000002000000fc41b9bd8ef8fe53d58c7ea67c794c7ec9a73daf05e6d54b14ff6342c99ba64c707ffa05b7dc6cdb6356bd8bd51ff20b2757c3214a76277516080a10f1bc7537"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypePolkadot); + + EXPECT_EQ(hex(output.encoded()), "41028400e9590e4d99264a14a85e21e69537e4a64f66a875d38cb8f76b305f41fabe24a900dd54466dffd1e3c80b76013e9459fbdcd17805bd5fdbca0961a643bad1cbd2b7fe005c62c51c18b67f31eb9e61b187a911952fee172ef18402d07c703eec3100d50200000a0000c8c602ded977c56076ae38d98026fa669ca10d6a2b5a0bfc4086ae7668ed1c60070010a5d4e8"); +} + +} // namespace TW::Polkadot::tests diff --git a/tests/chains/Acala/TWCoinTypeTests.cpp b/tests/chains/Acala/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..bf861303797 --- /dev/null +++ b/tests/chains/Acala/TWCoinTypeTests.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWAcalaCoinType, TWCoinType) { + const auto coin = TWCoinTypeAcala; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xf3d58aafb1208bc09d10ba74bbf1c7811dc55a9149c1505256b6fb5603f5047f")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("26JqMKx4HJJcmb1kXo24HYYobiK2jURGCq6zuEzFBK3hQ9Ti")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "acala"); + assertStringsEqual(name, "Acala"); + assertStringsEqual(symbol, "ACA"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 12); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainPolkadot); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(txUrl, "https://acala.subscan.io/extrinsic/0xf3d58aafb1208bc09d10ba74bbf1c7811dc55a9149c1505256b6fb5603f5047f"); + assertStringsEqual(accUrl, "https://acala.subscan.io/account/26JqMKx4HJJcmb1kXo24HYYobiK2jURGCq6zuEzFBK3hQ9Ti"); +} diff --git a/tests/chains/AcalaEVM/TWCoinTypeTests.cpp b/tests/chains/AcalaEVM/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..9381d5ac91a --- /dev/null +++ b/tests/chains/AcalaEVM/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWAcalaEVMCoinType, TWCoinType) { + const auto coin = TWCoinTypeAcalaEVM; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x4b0b151dd71ed8ef3174da18565790bf14f0a903a13e4f3266c7848bc8841593")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "acalaevm"); + assertStringsEqual(name, "Acala EVM"); + assertStringsEqual(symbol, "ACA"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "787"); + assertStringsEqual(txUrl, "https://blockscout.acala.network/tx/0x4b0b151dd71ed8ef3174da18565790bf14f0a903a13e4f3266c7848bc8841593"); + assertStringsEqual(accUrl, "https://blockscout.acala.network/address/0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1"); +} diff --git a/tests/chains/Aeternity/AddressTests.cpp b/tests/chains/Aeternity/AddressTests.cpp new file mode 100644 index 00000000000..581a9d75b86 --- /dev/null +++ b/tests/chains/Aeternity/AddressTests.cpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include +#include + +namespace TW::Aeternity::tests { + +TEST(AeternityAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148"), TWPublicKeyTypeED25519); + auto address = Address(publicKey); + ASSERT_EQ(address.string(), "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); + ASSERT_ANY_THROW(Address(PublicKey(parse_hex("03df9a5e4089f89d45913fb2b856de984c7e8bf1344cc6444cc9705899a48c939d"), TWPublicKeyTypeSECP256k1))); +} + +TEST(AeternityAddress, FromString) { + auto address = Address("ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); + ASSERT_EQ(address.string(), "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); + ASSERT_ANY_THROW(Address("invalid")); + ASSERT_ANY_THROW(Address("behave@wallet")); +} + +} // namespace TW::Aeternity::tests diff --git a/tests/chains/Aeternity/SignerTests.cpp b/tests/chains/Aeternity/SignerTests.cpp new file mode 100644 index 00000000000..3fe61a03851 --- /dev/null +++ b/tests/chains/Aeternity/SignerTests.cpp @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Aeternity/Signer.h" +#include "Aeternity/Transaction.h" +#include "HexCoding.h" + +#include "Aeternity/Address.h" +#include +#include "uint256.h" + +namespace TW::Aeternity::tests { + +TEST(AeternitySigner, Sign) { + std::string sender_id = "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"; + std::string recipient_id = "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"; + uint256_t amount = 10; + uint256_t fee = 20000000000000; + std::string payload = "Hello World"; + uint64_t ttl = 82757; + uint64_t nonce = 49; + + auto transaction = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); + auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + + auto result = Signer::sign(privateKey, transaction); + EXPECT_EQ(result.signature(), "sg_VW42qDPP3MMNFAStYaumjZz7mC7BZYpbNa15E57ejqUe7JdQFWCiX65eLNUpGMpt8tSpfgCfkYzcaFppqx7W75CrcWdC8"); + EXPECT_EQ(result.encoded(), "tx_+KkLAfhCuEDZ2XDV5OuHv1iuLn66sFLBUwnzp1K8JW1Zz+fEgmuEh6HEvNu0R112M3IYkVzvTSnT0pJ3TWhVOumgJ+IWwW8HuGH4XwwBoQHuk6T2b40WuBm7m+uf/M383BQS6H/uajJMKpmh4OZxSKEBHxOjsIvwAUAGYqaLadh194A87EwIZH9u1dhMeJe9UKMKhhIwnOVAAIMBQ0Uxi0hlbGxvIFdvcmxkDZqNSg=="); +} + +TEST(AeternitySigner, SignTxWithZeroTtl) { + std::string sender_id = "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"; + std::string recipient_id = "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"; + uint256_t amount = 10; + uint256_t fee = 20000000000000; + std::string payload = "Hello World"; + uint64_t ttl = 0; + uint64_t nonce = 49; + + auto transaction = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); + auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + + auto result = Signer::sign(privateKey, transaction); + EXPECT_EQ(result.signature(), "sg_7qJK868bqEZ5ciC2P3WCKYfhayvKTHvPsz3bdPgpfF3Ky7yNg9f8k22A3gxjjSm9afa6JmP8TJpF4GJkFh2k7gGaog9KS"); + EXPECT_EQ(result.encoded(), "tx_+KYLAfhCuEA0OgWhpq/VfS6ksMS+Df4ewZxIITEhjaaMOiyT0aRuAEe6b5+d2cQtzoyz58NNr+N4MFowctrGXrCrrkhNIywLuF74XAwBoQHuk6T2b40WuBm7m+uf/M383BQS6H/uajJMKpmh4OZxSKEBHxOjsIvwAUAGYqaLadh194A87EwIZH9u1dhMeJe9UKMKhhIwnOVAAAAxi0hlbGxvIFdvcmxkjoDNvQ=="); +} + +TEST(AeternitySigner, SignTxWithZeroAmount) { + std::string sender_id = "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"; + std::string recipient_id = "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"; + uint256_t amount = 0; + uint256_t fee = 20000000000000; + std::string payload = "Zero amount test"; + uint64_t ttl = 113579; + uint64_t nonce = 7; + + auto transaction = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); + auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + + auto result = Signer::sign(privateKey, transaction); + EXPECT_EQ(result.signature(), "sg_ShWvujPnyKBT1Ng2X5k6XSchVK8Bq7LYEisPMH11DUoPkXZcooBzqw81j9j5JewoFFpT9xEhUptj1azcLA21ogURYh4Lz"); + EXPECT_EQ(result.encoded(), "tx_+K4LAfhCuEDEbeoiVYmJCXm91KNfZXOvZMoT9x/sZja09EXZmErFBxm52b1IVoM4806Zr+TsliAYzUyKfUUFo3jGfXEPdZ8PuGb4ZAwBoQHuk6T2b40WuBm7m+uf/M383BQS6H/uajJMKpmh4OZxSKEBHxOjsIvwAUAGYqaLadh194A87EwIZH9u1dhMeJe9UKMAhhIwnOVAAIMBu6sHkFplcm8gYW1vdW50IHRlc3S5L3Vn"); +} + +TEST(AeternitySigner, SignTxWithZeroNonce) { + std::string sender_id = "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"; + std::string recipient_id = "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"; + uint256_t amount = 3369980000000000000; + uint256_t fee = 20000000000000; + std::string payload = "Zero nonce test"; + uint64_t ttl = 113579; + uint64_t nonce = 0; + + auto transaction = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); + auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + + auto result = Signer::sign(privateKey, transaction); + EXPECT_EQ(result.signature(), "sg_MaJc4ptSUhq5kH6mArszDAvu4f7PejyuhmgM6U8GEr8bRUTaSFbdFPx4C6FEYA5v5Lgwu9EToaWnHgR2xkqZ9JjHnaBpA"); + EXPECT_EQ(result.encoded(), "tx_+LULAfhCuECdQsgcE8bp+9CANdasxkt5gxfjBSI1ztyPl1aNJbm+MwUvE7Lu/qvAkHijfe+Eui2zrqhZRYc5mblRa+oLOIIEuG34awwBoQHuk6T2b40WuBm7m+uf/M383BQS6H/uajJMKpmh4OZxSKEBHxOjsIvwAUAGYqaLadh194A87EwIZH9u1dhMeJe9UKOILsSS9IArwACGEjCc5UAAgwG7qwCPWmVybyBub25jZSB0ZXN0piWfFA=="); +} + +} // namespace TW::Aeternity::tests diff --git a/tests/Aeternity/TWAeternityAddressTests.cpp b/tests/chains/Aeternity/TWAeternityAddressTests.cpp similarity index 75% rename from tests/Aeternity/TWAeternityAddressTests.cpp rename to tests/chains/Aeternity/TWAeternityAddressTests.cpp index 51376f5cf7a..79cb259b3f6 100644 --- a/tests/Aeternity/TWAeternityAddressTests.cpp +++ b/tests/chains/Aeternity/TWAeternityAddressTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/Aeternity/TWAnyAddressTests.cpp b/tests/chains/Aeternity/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..f747af3e862 --- /dev/null +++ b/tests/chains/Aeternity/TWAnyAddressTests.cpp @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include + +namespace TW::Aeternity::tests { + +// `TWAnyAddressIsValid` must catch exceptions and return false. +TEST(AeternityAddress, IsValid) { + ASSERT_FALSE(TWAnyAddressIsValid(STRING("invalid").get(), TWCoinTypeAeternity)); + ASSERT_FALSE(TWAnyAddressIsValid(STRING("behave@wallet").get(), TWCoinTypeAeternity)); + ASSERT_FALSE(TWAnyAddressIsValid(STRING("a").get(), TWCoinTypeAeternity)); +} + +} // namespace TW::Aeternity::tests diff --git a/tests/chains/Aeternity/TWAnySignerTests.cpp b/tests/chains/Aeternity/TWAnySignerTests.cpp new file mode 100644 index 00000000000..75be60551f3 --- /dev/null +++ b/tests/chains/Aeternity/TWAnySignerTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "uint256.h" +#include "proto/Aeternity.pb.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::Aeternity::tests { + +TEST(TWAnySignerAeternity, Sign) { + auto privateKey = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + + Proto::SigningInput input; + input.set_from_address("ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); + input.set_to_address("ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"); + auto amount = store(uint256_t(10)); + input.set_amount(amount.data(), amount.size()); + auto fee = store(uint256_t(20000000000000)); + input.set_fee(fee.data(), fee.size()); + input.set_payload("Hello World"); + input.set_ttl(82757); + input.set_nonce(49); + input.set_private_key(privateKey.data(), privateKey.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeAeternity); + + ASSERT_EQ(output.encoded(), "tx_+KkLAfhCuEDZ2XDV5OuHv1iuLn66sFLBUwnzp1K8JW1Zz+fEgmuEh6HEvNu0R112M3IYkVzvTSnT0pJ3TWhVOumgJ+IWwW8HuGH4XwwBoQHuk6T2b40WuBm7m+uf/M383BQS6H/uajJMKpmh4OZxSKEBHxOjsIvwAUAGYqaLadh194A87EwIZH9u1dhMeJe9UKMKhhIwnOVAAIMBQ0Uxi0hlbGxvIFdvcmxkDZqNSg=="); +} + +} // namespace TW::Aeternity::tests diff --git a/tests/chains/Aeternity/TWCoinTypeTests.cpp b/tests/chains/Aeternity/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..a5ea4fed267 --- /dev/null +++ b/tests/chains/Aeternity/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWAeternityCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeAeternity)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeAeternity, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeAeternity, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeAeternity)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeAeternity)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeAeternity), 18); + ASSERT_EQ(TWBlockchainAeternity, TWCoinTypeBlockchain(TWCoinTypeAeternity)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeAeternity)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeAeternity)); + assertStringsEqual(symbol, "AE"); + assertStringsEqual(txUrl, "https://explorer.aepps.com/transactions/t123"); + assertStringsEqual(accUrl, "https://explorer.aepps.com/account/transactions/a12"); + assertStringsEqual(id, "aeternity"); + assertStringsEqual(name, "Aeternity"); +} diff --git a/tests/chains/Aeternity/TransactionTests.cpp b/tests/chains/Aeternity/TransactionTests.cpp new file mode 100644 index 00000000000..f09aa5a9de1 --- /dev/null +++ b/tests/chains/Aeternity/TransactionTests.cpp @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "TestUtilities.h" +#include +#include + +namespace TW::Aeternity::tests { + +TEST(AeternityTransaction, EncodeRlp) { + std::string sender_id = "ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi"; + std::string recipient_id = "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"; + uint64_t amount = 10; + uint64_t fee = 2e13; + std::string payload = "Hello World"; + uint64_t ttl = 82757; + uint64_t nonce = 49; + + auto tx = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); + auto encodedTx = tx.encode(); + auto encodedTxHex = TW::hex(encodedTx); + + ASSERT_EQ(encodedTxHex, "f85f0c01a101cea7ade470c9f99d9d4e400880a86f1d49bb444b62f11a9ebb64bbcfeb73fef3a1011f13a3b08bf001400662a68b69d875f7803cec4c08647f6ed5d84c7897bd50a30a8612309ce5400083014345318b48656c6c6f20576f726c64"); +} + +TEST(AeternityTransaction, EncodeRlpWithZeroAmount) { + std::string sender_id = "ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi"; + std::string recipient_id = "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"; + uint64_t amount = 0; + uint64_t fee = 2e13; + std::string payload = "Hello World"; + uint64_t ttl = 82757; + uint64_t nonce = 49; + + auto tx = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); + auto encodedTx = tx.encode(); + auto encodedTxHex = TW::hex(encodedTx); + + ASSERT_EQ(encodedTxHex, "f85f0c01a101cea7ade470c9f99d9d4e400880a86f1d49bb444b62f11a9ebb64bbcfeb73fef3a1011f13a3b08bf001400662a68b69d875f7803cec4c08647f6ed5d84c7897bd50a3008612309ce5400083014345318b48656c6c6f20576f726c64"); +} + +TEST(AeternityTransaction, EncodeRlpWithZeroTtl) { + std::string sender_id = "ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi"; + std::string recipient_id = "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v"; + uint64_t amount = 10; + uint64_t fee = 2e13; + std::string payload = "Hello World"; + uint64_t ttl = 0; + uint64_t nonce = 49; + + auto tx = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); + auto encodedTx = tx.encode(); + auto encodedTxHex = TW::hex(encodedTx); + + ASSERT_EQ(encodedTxHex, "f85c0c01a101cea7ade470c9f99d9d4e400880a86f1d49bb444b62f11a9ebb64bbcfeb73fef3a1011f13a3b08bf001400662a68b69d875f7803cec4c08647f6ed5d84c7897bd50a30a8612309ce5400000318b48656c6c6f20576f726c64"); +} + +} // namespace TW::Aeternity::tests diff --git a/tests/chains/Aion/AddressTests.cpp b/tests/chains/Aion/AddressTests.cpp new file mode 100644 index 00000000000..a2ad39be5ac --- /dev/null +++ b/tests/chains/Aion/AddressTests.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Aion/Address.h" +#include "Coin.h" +#include "HexCoding.h" + +#include + +#include "TestUtilities.h" + +using namespace TW; + +namespace TW::Aion::tests { + +TEST(AionAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("01a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7"), TWPublicKeyTypeED25519); + auto address = Address(publicKey); + ASSERT_EQ(address.string(), "0xa0d2312facea71b740679c926d040c9056a65a4bfa2ddd18ec160064f82909e7"); +} + +TEST(AionAddress, FromString) { + std::string aionAddress = "0xa0d2312facea71b740679c926d040c9056a65a4bfa2ddd18ec160064f82909e7"; + const auto address = Address(aionAddress); + ASSERT_EQ(address.string(), aionAddress); + ASSERT_ANY_THROW(Address("0xffff")); +} + +TEST(AionAddress, InvalidFromData) { + ASSERT_ANY_THROW(Address(parse_hex("0xffff"))); + auto aionAddress = parse_hex("0xa0d2312facea71b740679c926d040c9056a65a4bfa2ddd18ec160064f82909e7"); + [[maybe_unused]] auto res = Address(aionAddress); +} + +TEST(AionAddress, isValid) { + std::string validAddress = "0xa0d2312facea71b740679c926d040c9056a65a4bfa2ddd18ec160064f82909e7"; + std::string invalidAddress = "0xzzd2312facea71b740679c926d040c9056a65a4bfa2ddd18ec160064f82909e7"; + + ASSERT_TRUE(Address::isValid(validAddress)); + ASSERT_EQ(Address(parse_hex(validAddress)).string(), validAddress); + ASSERT_FALSE(Address::isValid(invalidAddress)); + EXPECT_EXCEPTION(Address(parse_hex(invalidAddress)), "Invalid address data"); +} + +} // namespace TW::Aion::tests diff --git a/tests/chains/Aion/RLPTests.cpp b/tests/chains/Aion/RLPTests.cpp new file mode 100644 index 00000000000..d18b892ef3c --- /dev/null +++ b/tests/chains/Aion/RLPTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Aion/RLP.h" +#include "HexCoding.h" +#include + +namespace TW::Aion::tests { + +using boost::multiprecision::uint128_t; + +// Function helper over `RLP::prepareLong`. +Data encodeLong(const uint128_t& l) { + EthereumRlp::Proto::EncodingInput input; + *input.mutable_item() = RLP::prepareLong(l); + return Ethereum::RLP::encode(input); +} + +TEST(AionRLP, EncodeLong) { + EXPECT_EQ(hex(encodeLong(uint128_t(1))), "01"); + EXPECT_EQ(hex(encodeLong(uint128_t(21000))), "825208"); + EXPECT_EQ(hex(encodeLong(uint128_t(1000000))), "830f4240"); + EXPECT_EQ(hex(encodeLong(uint128_t(20000000000))), "8800000004a817c800"); + EXPECT_EQ(hex(encodeLong(uint128_t(9007199254740991))), "88001fffffffffffff"); + EXPECT_EQ(hex(encodeLong(uint128_t(9007199254740990))), "88001ffffffffffffe"); + EXPECT_EQ(hex(encodeLong(uint128_t(4294967296L))), "880000000100000000"); + EXPECT_EQ(hex(encodeLong(uint128_t(4295000060L))), "880000000100007ffc"); + EXPECT_EQ(hex(encodeLong(uint128_t(72057594037927935L))), "8800ffffffffffffff"); +} + +} // namespace TW::Aion::tests diff --git a/tests/chains/Aion/SignerTests.cpp b/tests/chains/Aion/SignerTests.cpp new file mode 100644 index 00000000000..84927f7dcee --- /dev/null +++ b/tests/chains/Aion/SignerTests.cpp @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Aion/Signer.h" +#include "Aion/Transaction.h" +#include "HexCoding.h" + +#include + +namespace TW::Aion::tests { + +TEST(AionSigner, Sign) { + auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); + auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, {}); + + auto privateKey = PrivateKey(parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9")); + Signer::sign(privateKey, transaction); + + EXPECT_EQ(hex(transaction.signature), "a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); + + // Raw transaction + EXPECT_EQ(hex(transaction.encode()), "f89b09a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108085242019b04d8252088800000004a817c80001b860a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); +} + +TEST(AionSigner, SignWithData) { + auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); + auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, parse_hex("41494f4e0000")); + + auto privateKey = PrivateKey(parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9")); + Signer::sign(privateKey, transaction); + + EXPECT_EQ(hex(transaction.signature), "a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a736fc2642c2d62900204779aa274dba3b8712eff7a8464aa78ea52b09ece20679fe3f5edf94c84a7e0c5f93213be891bc279af927086f455167f5bc73d3046c0d"); + + // Raw transaction + EXPECT_EQ(hex(transaction.encode()), "f8a109a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108641494f4e000085242019b04d8252088800000004a817c80001b860a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a736fc2642c2d62900204779aa274dba3b8712eff7a8464aa78ea52b09ece20679fe3f5edf94c84a7e0c5f93213be891bc279af927086f455167f5bc73d3046c0d"); +} + +} // namespace TW::Aion::tests diff --git a/tests/chains/Aion/TWAnySignerTests.cpp b/tests/chains/Aion/TWAnySignerTests.cpp new file mode 100644 index 00000000000..79a3a21d7f2 --- /dev/null +++ b/tests/chains/Aion/TWAnySignerTests.cpp @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "uint256.h" +#include "proto/Aion.pb.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::Aion::tests { + +TEST(TWAnySignerAion, Sign) { + auto privateKey = parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9"); + + Proto::SigningInput input; + input.set_to_address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); + auto amount = store(uint256_t(10000)); + input.set_amount(amount.data(), amount.size()); + auto gasPrice = store(uint256_t(20000000000)); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + auto gasLimit = store(uint256_t(21000)); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + auto nonce = store(uint256_t(9)); + input.set_nonce(nonce.data(), nonce.size()); + input.set_timestamp(155157377101); + input.set_private_key(privateKey.data(), privateKey.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeAion); + + ASSERT_EQ(hex(output.encoded()), "f89b09a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108085242019b04d8252088800000004a817c80001b860a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); +} + +} // namespace TW::Aion::tests diff --git a/tests/chains/Aion/TWCoinTypeTests.cpp b/tests/chains/Aion/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..39175f51569 --- /dev/null +++ b/tests/chains/Aion/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWAionCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeAion)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeAion, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeAion, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeAion)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeAion)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeAion), 18); + ASSERT_EQ(TWBlockchainAion, TWCoinTypeBlockchain(TWCoinTypeAion)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeAion)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeAion)); + assertStringsEqual(symbol, "AION"); + assertStringsEqual(txUrl, "https://mainnet.aion.network/#/transaction/t123"); + assertStringsEqual(accUrl, "https://mainnet.aion.network/#/account/a12"); + assertStringsEqual(id, "aion"); + assertStringsEqual(name, "Aion"); +} diff --git a/tests/chains/Aion/TransactionCompilerTests.cpp b/tests/chains/Aion/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..e10e739bec2 --- /dev/null +++ b/tests/chains/Aion/TransactionCompilerTests.cpp @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base64.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Aion.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Aion::tests { + +TEST(AionCompiler, CompileWithSignatures) { + /// Step 1: Prepare transaction input (protobuf) + auto privateKey = parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9"); + auto key = PrivateKey(privateKey); + auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); + + Proto::SigningInput input; + input.set_to_address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); + auto amount = store(uint256_t(10000)); + input.set_amount(amount.data(), amount.size()); + auto gasPrice = store(uint256_t(20000000000)); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + auto gasLimit = store(uint256_t(21000)); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + auto nonce = store(uint256_t(9)); + input.set_nonce(nonce.data(), nonce.size()); + input.set_timestamp(155157377101); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto txInputData = data(input.SerializeAsString()); + auto preImageHashes = TransactionCompiler::preImageHashes(TWCoinTypeAion, txInputData); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = data(preSigningOutput.data_hash()); + EXPECT_EQ(hex(preImageHash), "d4423fe7d233b85c1bf5b1120ec03842e572fb25f3755f7a20bc83addc8c4d85"); + + // Simulate signature, normally obtained from signature server + const auto signature = key.sign(preImageHash, TWCurveED25519); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(TWCoinTypeAion, txInputData, {signature}, {publicKey.bytes}); + + const auto ExpectedTx = + "f89b09a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108085242019b04d8252088800000004a817c80001b860a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"; + { + Aion::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + // double check + { + Aion::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeAion); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + TWCoinTypeAion, txInputData, {signature, signature}, {publicKey.bytes}); + Aion::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + TWCoinTypeAion, txInputData, {}, {}); + Aion::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +} // namespace TW::Aion::tests diff --git a/tests/chains/Aion/TransactionTests.cpp b/tests/chains/Aion/TransactionTests.cpp new file mode 100644 index 00000000000..0e2e73e3ec1 --- /dev/null +++ b/tests/chains/Aion/TransactionTests.cpp @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Aion/Transaction.h" +#include "HexCoding.h" +#include + +namespace TW::Aion::tests { + +TEST(AionTransaction, Encode) { + auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); + auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, {}); + ASSERT_EQ(hex(transaction.encode()), "f83909a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108085242019b04d8252088800000004a817c80001"); +} + +TEST(AionTransaction, EncodeWithSignature) { + auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); + auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, {}); + transaction.signature = parse_hex("a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); + ASSERT_EQ(hex(transaction.encode()), "f89b09a0a082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e8227108085242019b04d8252088800000004a817c80001b860a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); +} + +} // namespace TW::Aion::tests diff --git a/tests/chains/Algorand/AddressTests.cpp b/tests/chains/Algorand/AddressTests.cpp new file mode 100644 index 00000000000..993b0e3247c --- /dev/null +++ b/tests/chains/Algorand/AddressTests.cpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Algorand/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include + +using namespace TW; + +namespace TW::Algorand::tests { + +TEST(AlgorandAddress, Validation) { + // empty address + ASSERT_FALSE(Address::isValid("")); + // invalid checksum + ASSERT_FALSE(Address::isValid("JBCQYJ2FREG667NAN7BFKH4RFIKPT7CYDQJNW3SNN5Z7F7ILFLKQ346TS3")); + // wrong length + ASSERT_FALSE(Address::isValid("JBCQYJ2FREG667NAN7BFKH4RFIKPT7CYDQJNW3SNN5Z7F7ILFLKQ346TSU ")); + // Stellar address + ASSERT_FALSE(Address::isValid("GABQHYQOY22KHGTCTAK24AWAUE4TXERF4O4JBSXELNM7IL5CTPUWM3SC")); + // invalid base32 + ASSERT_FALSE(Address::isValid("0000000000000000000000000000000000000000000000000000000000")); + + ASSERT_TRUE(Address::isValid("HXIWBVQGOO6ZWE5NYJO22XMYRUGZ6TGNX2K2EERPT3ZIWPHE5CLJGB2GEA")); +} + +TEST(AlgorandAddress, FromPrivateKey) { + auto privateKey = PrivateKey(parse_hex("526d96fffdbfe787b2f00586298538f9a019e97f6587964dc61aae9ad1d7fa23")); + auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519)); + ASSERT_EQ(address.string(), "JBCQYJ2FREG667NAN7BFKH4RFIKPT7CYDQJNW3SNN5Z7F7ILFLKQ346TSU"); +} + +TEST(AlgorandAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("c2b423afa8b0095e5ae105668b91b2132db4dadbf38acfc64908d3476a00191f"), TWPublicKeyTypeED25519); + auto address = Address(publicKey); + ASSERT_EQ(address.string(), "YK2CHL5IWAEV4WXBAVTIXENSCMW3JWW36OFM7RSJBDJUO2QADEP5QYVO5I"); + + auto privateKey2 = PrivateKey(parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f")); + auto publicKey2 = privateKey2.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + EXPECT_ANY_THROW(new Address(publicKey2)); +} + +TEST(AlgorandAddress, FromString) { + auto address = Address("PITDOF57RHOVLT37KM7DCXDCETLDL3OA5CBAN7LQ44Z36LGFC27IJ2IQ64"); + ASSERT_EQ(address.string(), "PITDOF57RHOVLT37KM7DCXDCETLDL3OA5CBAN7LQ44Z36LGFC27IJ2IQ64"); + + EXPECT_ANY_THROW(Address("")); +} + +} // namespace TW::Algorand::tests diff --git a/tests/chains/Algorand/SignerTests.cpp b/tests/chains/Algorand/SignerTests.cpp new file mode 100644 index 00000000000..1eef1ada080 --- /dev/null +++ b/tests/chains/Algorand/SignerTests.cpp @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Algorand/Address.h" +#include "Algorand/BinaryCoding.h" +#include "Algorand/Signer.h" +#include "Base64.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include + +using namespace TW; + +namespace TW::Algorand::tests { + +TEST(AlgorandSigner, EncodeNumbers) { + auto tests = { + std::make_tuple(100ull, "64"), + std::make_tuple(200ull, "ccc8"), + std::make_tuple(55536ull, "cdd8f0"), + std::make_tuple(3294967296ull, "cec4653600"), + std::make_tuple(14294967296ull, "cf00000003540be400"), + }; + + for (auto& test : tests) { + Data data; + encodeNumber(std::get<0>(test), data); + ASSERT_EQ(hex(data), std::get<1>(test)); + } +} + +TEST(AlgorandSigner, EncodeStrings) { + auto tests = { + std::make_tuple("algo", "a4616c676f"), + std::make_tuple("It's like JSON. but fast and small.", "d92349742773206c696b65204a534f4e2e20627574206661737420616e6420736d616c6c2e"), + std::make_tuple( + "MessagePack is an efficient binary serialization format. It lets you exchange data among multiple languages like JSON. But it's faster and smaller. Small integers are encoded into a single byte, and typical short strings require only one extra byte in addition to the strings themselves.", + "da011f4d6573736167655061636b20697320616e20656666696369656e742062696e6172792073657269616c697a6174696f6e20666f726d61742e204974206c65747320796f752065786368616e6765206461746120616d6f6e67206d756c7469706c65206c616e677561676573206c696b65204a534f4e2e2042757420697427732066617374657220616e6420736d616c6c65722e20536d616c6c20696e7465676572732061726520656e636f64656420696e746f20612073696e676c6520627974652c20616e64207479706963616c2073686f727420737472696e67732072657175697265206f6e6c79206f6e65206578747261206279746520696e206164646974696f6e20746f2074686520737472696e6773207468656d73656c7665732e")}; + + for (auto& test : tests) { + Data data; + Algorand::encodeString(std::get<0>(test), data); + ASSERT_EQ(hex(data), std::get<1>(test)); + } +} + +TEST(AlgorandSigner, EncodeBytes) { + auto rawtx = "010000000001029294c2b3bd4d25483c4c12432df01a856a38cc0cb48da1a7dd590b7d893392a90000000000ffffffffded892ea55bf1c6ccc495d3493767d7c24497f612b9edc9ab8d30eb671ea76750000000000ffffffff021027000000000000160014b96bacd6f729ef8ac1dd30d159433c0917ba8d3db00f00000000000016001476cd9d430de6db162fc3db509920255ff6d2bdb002483045022100eb8675ff6775e9c399dddba9f178002b745872e541617d690cbce7c933adb87602205de8074c173696de65d4c644a84ea1337c9e9928c7052fddcf9d99e35815e2f20121032858d3a5f9825408ea3959800c5daf22e7a91e459ef168df45071266501d28e102473044022025f1cf362a9c09bd351769f1918ab9f0a6c3f6c4682f29fdbfc08354554ea37b02203f62345b3da4d7a29f58c7c741682be4108a0fb2013980332cc3e081aad7423f01210237d83670da2d3947a58752dab95d59b592c78f2e734d1c14dbf75b29bbe4116100000000"; + Data data; + encodeBytes(parse_hex(rawtx), data); + ASSERT_EQ(hex(data), "c50173010000000001029294c2b3bd4d25483c4c12432df01a856a38cc0cb48da1a7dd590b7d893392a90000000000ffffffffded892ea55bf1c6ccc495d3493767d7c24497f612b9edc9ab8d30eb671ea76750000000000ffffffff021027000000000000160014b96bacd6f729ef8ac1dd30d159433c0917ba8d3db00f00000000000016001476cd9d430de6db162fc3db509920255ff6d2bdb002483045022100eb8675ff6775e9c399dddba9f178002b745872e541617d690cbce7c933adb87602205de8074c173696de65d4c644a84ea1337c9e9928c7052fddcf9d99e35815e2f20121032858d3a5f9825408ea3959800c5daf22e7a91e459ef168df45071266501d28e102473044022025f1cf362a9c09bd351769f1918ab9f0a6c3f6c4682f29fdbfc08354554ea37b02203f62345b3da4d7a29f58c7c741682be4108a0fb2013980332cc3e081aad7423f01210237d83670da2d3947a58752dab95d59b592c78f2e734d1c14dbf75b29bbe4116100000000"); +} + +TEST(AlgorandSigner, Sign) { + auto key = PrivateKey(parse_hex("c9d3cc16fecabe2747eab86b81528c6ed8b65efc1d6906d86aabc27187a1fe7c")); + auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); + auto from = Address(publicKey); + auto to = Address("UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM"); + Data note; + std::string genesisId = "mainnet-v1.0"; + auto genesisHash = Base64::decode("wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="); + auto transaction = Transfer( + /* from */ from, + /* to */ to, + /* fee */ 488931, + /* amount */ 847, + /* first round */ 51, + /* last round */ 61, + /* note */ note, + /* type */ "pay", + /* genesis id*/ genesisId, + /* genesis hash*/ genesisHash); + + auto serialized = transaction.serialize(); + auto signature = Signer::sign(key, transaction); + auto result = transaction.BaseTransaction::serialize(signature); + + ASSERT_EQ(hex(serialized), "89a3616d74cd034fa3666565ce000775e3a2667633a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c763da3726376c420a089aa6922e3b998fadff6cd4808ddf9e021e4944e389ea3d5c638786689197ea3736e64c42074b000b6368551a6066d713e2866002e8dab34b69ede09a72e85a39bbb1f7928a474797065a3706179"); + ASSERT_EQ(hex(signature), "de73363dbdeda0682adca06f6268a16a6ec47253c94d5692dc1c49a84a05847812cf66d7c4cf07c7e2f50f143ec365d405e30b35117b264a994626054d2af604"); + ASSERT_EQ(hex(result), "82a3736967c440de73363dbdeda0682adca06f6268a16a6ec47253c94d5692dc1c49a84a05847812cf66d7c4cf07c7e2f50f143ec365d405e30b35117b264a994626054d2af604a374786e89a3616d74cd034fa3666565ce000775e3a2667633a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c763da3726376c420a089aa6922e3b998fadff6cd4808ddf9e021e4944e389ea3d5c638786689197ea3736e64c42074b000b6368551a6066d713e2866002e8dab34b69ede09a72e85a39bbb1f7928a474797065a3706179"); +} + +TEST(AlgorandSigner, SignAssetNFTTransfer) { + // Successfully broadcasted: https://app.dappflow.org/explorer/transaction/FFLUH4QKZHG744RIQ2AZNWZUSIIH262KZ4MEWSY4RXMWN5NMOOJA + auto key = PrivateKey(parse_hex("dc6051ffc7b3ec601bde432f6dea34d40fe3855e4181afa0f0524c42194a6da7")); + auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); + auto from = Address(publicKey); + auto to = Address("362T7CSXNLIOBX6J3H2SCPS4LPYFNV6DDWE6G64ZEUJ6SY5OJIR6SB5CVE"); + Data note = Base64::decode("VFdUIFRPIFRIRSBNT09O"); + std::string genesisId = "mainnet-v1.0"; + auto genesisHash = Base64::decode("wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="); + auto transaction = AssetTransfer( + /* from */ from, + /* to */ to, + /* fee */ 1000, + /* amount */ 1, + /* asset id */ 989643841, + /* first round */ 27963950, + /* last round */ 27964950, + /* note */ note, + /* type */ "axfer", + /* genesis id*/ genesisId, + /* genesis hash*/ genesisHash); + + auto serialized = transaction.serialize(); + auto signature = Signer::sign(key, transaction); + auto result = transaction.BaseTransaction::serialize(signature); + + EXPECT_EQ(hex(serialized), "8ba461616d7401a461726376c420dfb53f8a576ad0e0dfc9d9f5213e5c5bf056d7c31d89e37b992513e963ae4a23a3666565cd03e8a26676ce01aab22ea367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce01aab616a46e6f7465c40f54575420544f20544845204d4f4f4ea3736e64c420ca40799dacdb564d1096611d9da6ca7a6a4916f6d681383860725aedafe91617a474797065a56178666572a478616964ce3afcc441"); + EXPECT_EQ(Base64::encode(signature), "nXQsDH1ilG3DIo2VQm5tdYKXe9o599ygdqikmROpZiNXAvQeK3avJqgjM5o+iByCdq6uOxlbveDyVmL9nZxxBg=="); + EXPECT_EQ(hex(result), "82a3736967c4409d742c0c7d62946dc3228d95426e6d7582977bda39f7dca076a8a49913a966235702f41e2b76af26a823339a3e881c8276aeae3b195bbde0f25662fd9d9c7106a374786e8ba461616d7401a461726376c420dfb53f8a576ad0e0dfc9d9f5213e5c5bf056d7c31d89e37b992513e963ae4a23a3666565cd03e8a26676ce01aab22ea367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce01aab616a46e6f7465c40f54575420544f20544845204d4f4f4ea3736e64c420ca40799dacdb564d1096611d9da6ca7a6a4916f6d681383860725aedafe91617a474797065a56178666572a478616964ce3afcc441"); +} + +TEST(AlgorandSigner, SignAsset) { + // https://explorer.bitquery.io/algorand_testnet/tx/NJ62HYO2LC222AVLIN2GW5LKIWKLGC7NZLIQ3DUL2RDVRYO2UW7A + auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04")); + auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); + auto from = Address(publicKey); + auto to = Address("GJIWJSX2EU5RC32LKTDDXWLA2YICBHKE35RV2ZPASXZYKWUWXFLKNFSS4U"); + Data note; + std::string genesisId = "testnet-v1.0"; + auto genesisHash = Base64::decode("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="); + auto transaction = AssetTransfer( + /* from */ from, + /* to */ to, + /* fee */ 2340, + /* amount */ 1000000, + /* asset id */ 13379146, + /* first round */ 15775683, + /* last round */ 15776683, + /* note */ note, + /* type */ "axfer", + /* genesis id*/ genesisId, + /* genesis hash*/ genesisHash); + + auto serialized = transaction.serialize(); + auto signature = Signer::sign(key, transaction); + auto result = transaction.BaseTransaction::serialize(signature); + + ASSERT_EQ(hex(serialized), "8aa461616d74ce000f4240a461726376c420325164cafa253b116f4b54c63bd960d610209d44df635d65e095f3855a96b956a3666565cd0924a26676ce00f0b7c3a367656eac746573746e65742d76312e30a26768c4204863b518a4b3c84ec810f22d4f1081cb0f71f059a7ac20dec62f7f70e5093a22a26c76ce00f0bbaba3736e64c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a474797065a56178666572a478616964ce00cc264a"); + ASSERT_EQ(hex(signature), "412720eff99a17280a437bdb8eeba7404b855d6433fffd5dde7f7966c1f9ae531a1af39e18b8a58b4a6c6acb709cca92f8a18c36d8328be9520c915311027005"); + ASSERT_EQ(hex(result), "82a3736967c440412720eff99a17280a437bdb8eeba7404b855d6433fffd5dde7f7966c1f9ae531a1af39e18b8a58b4a6c6acb709cca92f8a18c36d8328be9520c915311027005a374786e8aa461616d74ce000f4240a461726376c420325164cafa253b116f4b54c63bd960d610209d44df635d65e095f3855a96b956a3666565cd0924a26676ce00f0b7c3a367656eac746573746e65742d76312e30a26768c4204863b518a4b3c84ec810f22d4f1081cb0f71f059a7ac20dec62f7f70e5093a22a26c76ce00f0bbaba3736e64c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a474797065a56178666572a478616964ce00cc264a"); +} + +TEST(AlgorandSigner, SignAssetWithNote) { + auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04")); + auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); + auto from = Address(publicKey); + auto to = Address("GJIWJSX2EU5RC32LKTDDXWLA2YICBHKE35RV2ZPASXZYKWUWXFLKNFSS4U"); + Data note = data("note"); + std::string genesisId = "testnet-v1.0"; + auto genesisHash = Base64::decode("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="); + auto transaction = AssetTransfer( + /* from */ from, + /* to */ to, + /* fee */ 2340, + /* amount */ 1000000, + /* asset id */ 13379146, + /* first round */ 15775683, + /* last round */ 15776683, + /* note */ note, + /* type */ "axfer", + /* genesis id*/ genesisId, + /* genesis hash*/ genesisHash); + + auto serialized = transaction.serialize(); + + ASSERT_EQ(hex(serialized), "8ba461616d74ce000f4240a461726376c420325164cafa253b116f4b54c63bd960d610209d44df635d65e095f3855a96b956a3666565cd0924a26676ce00f0b7c3a367656eac746573746e65742d76312e30a26768c4204863b518a4b3c84ec810f22d4f1081cb0f71f059a7ac20dec62f7f70e5093a22a26c76ce00f0bbaba46e6f7465c4046e6f7465a3736e64c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a474797065a56178666572a478616964ce00cc264a"); +} + +TEST(AlgorandSigner, SignAssetOptIn) { + // https://explorer.bitquery.io/algorand_testnet/tx/47LE2QS4B5N6IFHXOUN2MJUTCOQCHNY6AB3AJYECK4IM2VYKJDKQ + auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04")); + auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); + auto address = Address(publicKey); + Data note; + std::string genesisId = "testnet-v1.0"; + auto genesisHash = Base64::decode("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="); + auto transaction = OptInAssetTransaction( + /* from */ address, + /* fee */ 2340, + /* asset id */ 13379146, + /* first round */ 15775553, + /* last round */ 15776553, + /* note */ note, + /* type */ "axfer", + /* genesis id*/ genesisId, + /* genesis hash*/ genesisHash); + + auto serialized = transaction.serialize(); + auto signature = Signer::sign(key, transaction); + auto result = transaction.BaseTransaction::serialize(signature); + + ASSERT_EQ(hex(serialized), "89a461726376c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a3666565cd0924a26676ce00f0b741a367656eac746573746e65742d76312e30a26768c4204863b518a4b3c84ec810f22d4f1081cb0f71f059a7ac20dec62f7f70e5093a22a26c76ce00f0bb29a3736e64c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a474797065a56178666572a478616964ce00cc264a"); + ASSERT_EQ(hex(signature), "f3a29d9a40271c00b542b38ab2ccb4967015ae6609368d4b8eb2f5e2b5348577cf9e0f62b0777ccb2d8d9b943b15c24c0cf1db312cb01a3c198d9d9c6c5bb00b"); + ASSERT_EQ(hex(result), "82a3736967c440f3a29d9a40271c00b542b38ab2ccb4967015ae6609368d4b8eb2f5e2b5348577cf9e0f62b0777ccb2d8d9b943b15c24c0cf1db312cb01a3c198d9d9c6c5bb00ba374786e89a461726376c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a3666565cd0924a26676ce00f0b741a367656eac746573746e65742d76312e30a26768c4204863b518a4b3c84ec810f22d4f1081cb0f71f059a7ac20dec62f7f70e5093a22a26c76ce00f0bb29a3736e64c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a474797065a56178666572a478616964ce00cc264a"); +} + +TEST(AlgorandSigner, ProtoSignerOptIn) { + // https://explorer.bitquery.io/algorand_testnet/tx/47LE2QS4B5N6IFHXOUN2MJUTCOQCHNY6AB3AJYECK4IM2VYKJDKQ + auto optIn = new Proto::AssetOptIn(); + optIn->set_asset_id(13379146); + + auto privateKey = parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04"); + + auto input = Proto::SigningInput(); + auto genesisHash = Base64::decode("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="); + std::string str(genesisHash.begin(), genesisHash.end()); + input.set_allocated_asset_opt_in(optIn); + input.set_genesis_hash(str); + input.set_genesis_id("testnet-v1.0"); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_first_round(15775553); + input.set_last_round(15776553); + input.set_fee(2340); + + auto result = Signer::sign(input); + auto encoded = result.encoded(); + + ASSERT_EQ(hex(encoded), "82a3736967c440f3a29d9a40271c00b542b38ab2ccb4967015ae6609368d4b8eb2f5e2b5348577cf9e0f62b0777ccb2d8d9b943b15c24c0cf1db312cb01a3c198d9d9c6c5bb00ba374786e89a461726376c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a3666565cd0924a26676ce00f0b741a367656eac746573746e65742d76312e30a26768c4204863b518a4b3c84ec810f22d4f1081cb0f71f059a7ac20dec62f7f70e5093a22a26c76ce00f0bb29a3736e64c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a474797065a56178666572a478616964ce00cc264a"); +} + +TEST(AlgorandSigner, ProtoSignerAssetTransaction) { + // https://explorer.bitquery.io/algorand_testnet/tx/NJ62HYO2LC222AVLIN2GW5LKIWKLGC7NZLIQ3DUL2RDVRYO2UW7A + auto transaction = new Proto::AssetTransfer(); + transaction->set_asset_id(13379146); + transaction->set_amount(1000000); + transaction->set_to_address("GJIWJSX2EU5RC32LKTDDXWLA2YICBHKE35RV2ZPASXZYKWUWXFLKNFSS4U"); + + auto privateKey = parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04"); + + auto input = Proto::SigningInput(); + auto genesisHash = Base64::decode("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="); + std::string str(genesisHash.begin(), genesisHash.end()); + input.set_allocated_asset_transfer(transaction); + input.set_genesis_hash(str); + input.set_genesis_id("testnet-v1.0"); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_first_round(15775683); + input.set_last_round(15776683); + input.set_fee(2340); + + auto result = Signer::sign(input); + auto encoded = result.encoded(); + + ASSERT_EQ(hex(encoded), "82a3736967c440412720eff99a17280a437bdb8eeba7404b855d6433fffd5dde7f7966c1f9ae531a1af39e18b8a58b4a6c6acb709cca92f8a18c36d8328be9520c915311027005a374786e8aa461616d74ce000f4240a461726376c420325164cafa253b116f4b54c63bd960d610209d44df635d65e095f3855a96b956a3666565cd0924a26676ce00f0b7c3a367656eac746573746e65742d76312e30a26768c4204863b518a4b3c84ec810f22d4f1081cb0f71f059a7ac20dec62f7f70e5093a22a26c76ce00f0bbaba3736e64c42082872d60c338cb928006070e02ec0942addcb79e7fbd01c76458aea526899bd3a474797065a56178666572a478616964ce00cc264a"); +} + +} // namespace TW::Algorand::tests diff --git a/tests/chains/Algorand/TWAnySignerTests.cpp b/tests/chains/Algorand/TWAnySignerTests.cpp new file mode 100644 index 00000000000..7d82693add4 --- /dev/null +++ b/tests/chains/Algorand/TWAnySignerTests.cpp @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base64.h" +#include "HexCoding.h" +#include "proto/Algorand.pb.h" +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::Algorand::tests { + +TEST(TWAnySignerAlgorand, SignAssetNFTTransfer) { + // Successfully broadcasted: https://app.dappflow.org/explorer/transaction/FFLUH4QKZHG744RIQ2AZNWZUSIIH262KZ4MEWSY4RXMWN5NMOOJA + auto privateKey = parse_hex("dc6051ffc7b3ec601bde432f6dea34d40fe3855e4181afa0f0524c42194a6da7"); + Data note = Base64::decode("VFdUIFRPIFRIRSBNT09O"); + auto genesisHash = Base64::decode("wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="); + + Proto::SigningInput input; + auto& transaction = *input.mutable_asset_transfer(); + transaction.set_to_address("362T7CSXNLIOBX6J3H2SCPS4LPYFNV6DDWE6G64ZEUJ6SY5OJIR6SB5CVE"); + transaction.set_amount(1ull); + transaction.set_asset_id(989643841ull); + input.set_first_round(27963950ull); + input.set_last_round(27964950ull); + input.set_fee(1000ull); + input.set_genesis_id("mainnet-v1.0"); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_note(note.data(), note.size()); + input.set_private_key(privateKey.data(), privateKey.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeAlgorand); + + ASSERT_EQ(hex(output.encoded()), "82a3736967c4409d742c0c7d62946dc3228d95426e6d7582977bda39f7dca076a8a49913a966235702f41e2b76af26a823339a3e881c8276aeae3b195bbde0f25662fd9d9c7106a374786e8ba461616d7401a461726376c420dfb53f8a576ad0e0dfc9d9f5213e5c5bf056d7c31d89e37b992513e963ae4a23a3666565cd03e8a26676ce01aab22ea367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce01aab616a46e6f7465c40f54575420544f20544845204d4f4f4ea3736e64c420ca40799dacdb564d1096611d9da6ca7a6a4916f6d681383860725aedafe91617a474797065a56178666572a478616964ce3afcc441"); + ASSERT_EQ(output.signature(), "nXQsDH1ilG3DIo2VQm5tdYKXe9o599ygdqikmROpZiNXAvQeK3avJqgjM5o+iByCdq6uOxlbveDyVmL9nZxxBg=="); +} + +TEST(TWAnySignerAlgorand, Sign) { + auto privateKey = parse_hex("d5b43d706ef0cb641081d45a2ec213b5d8281f439f2425d1af54e2afdaabf55b"); + auto note = parse_hex("68656c6c6f"); + auto genesisHash = Base64::decode("wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="); + + Proto::SigningInput input; + auto& transaction = *input.mutable_transfer(); + transaction.set_to_address("CRLADAHJZEW2GFY2UPEHENLOGCUOU74WYSTUXQLVLJUJFHEUZOHYZNWYR4"); + transaction.set_amount(1000000000000ull); + input.set_first_round(1937767ull); + input.set_last_round(1938767ull); + input.set_fee(263000ull); + input.set_genesis_id("mainnet-v1.0"); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_note(note.data(), note.size()); + input.set_private_key(privateKey.data(), privateKey.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeAlgorand); + + ASSERT_EQ(hex(output.encoded()), "82a3736967c440baa00062adcdcb5875e4435cdc6885d26bfe5308ab17983c0fda790b7103051fcb111554e5badfc0ac7edf7e1223a434342a9eeed5cdb047690827325051560ba374786e8aa3616d74cf000000e8d4a51000a3666565ce00040358a26676ce001d9167a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce001d954fa46e6f7465c40568656c6c6fa3726376c42014560180e9c92da3171aa3c872356e30a8ea7f96c4a74bc1755a68929c94cb8fa3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179"); +} + +TEST(TWAnySignerAlgorand, SignJSON) { + auto json = STRING(R"({"genesisId":"mainnet-v1.0","genesisHash":"wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=","note":"aGVsbG8=","firstRound":"1937767","lastRound":"1938767","fee":"263000","transfer":{"toAddress":"CRLADAHJZEW2GFY2UPEHENLOGCUOU74WYSTUXQLVLJUJFHEUZOHYZNWYR4","amount":"1000000000000"}})"); + auto key = DATA("d5b43d706ef0cb641081d45a2ec213b5d8281f439f2425d1af54e2afdaabf55b"); + + auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeAlgorand)); + + ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeAlgorand)); + assertStringsEqual(result, "82a3736967c440baa00062adcdcb5875e4435cdc6885d26bfe5308ab17983c0fda790b7103051fcb111554e5badfc0ac7edf7e1223a434342a9eeed5cdb047690827325051560ba374786e8aa3616d74cf000000e8d4a51000a3666565ce00040358a26676ce001d9167a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce001d954fa46e6f7465c40568656c6c6fa3726376c42014560180e9c92da3171aa3c872356e30a8ea7f96c4a74bc1755a68929c94cb8fa3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179"); +} + +} // namespace TW::Algorand::tests diff --git a/tests/chains/Algorand/TWCoinTypeTests.cpp b/tests/chains/Algorand/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..0f7caba2bb3 --- /dev/null +++ b/tests/chains/Algorand/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWAlgorandCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeAlgorand)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("CR7POXFTYDLC7TV3IXHA7AZKWABUJC52BACLHJQNXAKZJGRPQY3A")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeAlgorand, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("J4AEINCSSLDA7LNBNWM4ZXFCTLTOZT5LG3F5BLMFPJYGFWVCMU37EZI2AM")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeAlgorand, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeAlgorand)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeAlgorand)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeAlgorand), 6); + ASSERT_EQ(TWBlockchainAlgorand, TWCoinTypeBlockchain(TWCoinTypeAlgorand)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeAlgorand)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeAlgorand)); + assertStringsEqual(symbol, "ALGO"); + assertStringsEqual(txUrl, "https://app.dappflow.org/explorer/transaction/CR7POXFTYDLC7TV3IXHA7AZKWABUJC52BACLHJQNXAKZJGRPQY3A"); + assertStringsEqual(accUrl, "https://app.dappflow.org/explorer/account/J4AEINCSSLDA7LNBNWM4ZXFCTLTOZT5LG3F5BLMFPJYGFWVCMU37EZI2AM"); + assertStringsEqual(id, "algorand"); + assertStringsEqual(name, "Algorand"); +} diff --git a/tests/chains/Algorand/TransactionCompilerTests.cpp b/tests/chains/Algorand/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..e630a7205a3 --- /dev/null +++ b/tests/chains/Algorand/TransactionCompilerTests.cpp @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base64.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Algorand.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Algorand::tests { + +TEST(AlgorandCompiler, CompileWithSignatures) { + /// Step 1: Prepare transaction input (protobuf) + auto privateKey = parse_hex("d5b43d706ef0cb641081d45a2ec213b5d8281f439f2425d1af54e2afdaabf55b"); + auto key = PrivateKey(privateKey); + auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); + auto note = parse_hex("68656c6c6f"); + auto genesisHash = Base64::decode("wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="); + + auto input = Algorand::Proto::SigningInput(); + auto& transaction = *input.mutable_transfer(); + transaction.set_to_address("CRLADAHJZEW2GFY2UPEHENLOGCUOU74WYSTUXQLVLJUJFHEUZOHYZNWYR4"); + transaction.set_amount(1000000000000ull); + input.set_first_round(1937767ull); + input.set_last_round(1938767ull); + input.set_fee(263000ull); + input.set_genesis_id("mainnet-v1.0"); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_note(note.data(), note.size()); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + + auto txInputData = data(input.SerializeAsString()); + auto preImageHashes = TransactionCompiler::preImageHashes(TWCoinTypeAlgorand, txInputData); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = data(preSigningOutput.data()); + EXPECT_EQ(hex(preImage), "54588aa3616d74cf000000e8d4a51000a3666565ce00040358a26676ce001d9167a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce001d954fa46e6f7465c40568656c6c6fa3726376c42014560180e9c92da3171aa3c872356e30a8ea7f96c4a74bc1755a68929c94cb8fa3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179"); + + // Simulate signature, normally obtained from signature server + const auto signature = key.sign(preImage, TWCurveED25519); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(TWCoinTypeAlgorand, txInputData, {signature}, {publicKey.bytes}); + + const auto ExpectedTx = + "82a3736967c440baa00062adcdcb5875e4435cdc6885d26bfe5308ab17983c0fda790b7103051fcb111554e5badfc0ac7edf7e1223a434342a9eeed5cdb047690827325051560ba374786e8aa3616d74cf000000e8d4a51000a3666565ce00040358a26676ce001d9167a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce001d954fa46e6f7465c40568656c6c6fa3726376c42014560180e9c92da3171aa3c872356e30a8ea7f96c4a74bc1755a68929c94cb8fa3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179"; + { + Algorand::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + // double check + { + Algorand::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeAlgorand); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + TWCoinTypeAlgorand, txInputData, {signature, signature}, {publicKey.bytes}); + Algorand::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + TWCoinTypeAlgorand, txInputData, {}, {}); + Algorand::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +} // namespace TW::Algorand::tests diff --git a/tests/chains/Aptos/AddressTests.cpp b/tests/chains/Aptos/AddressTests.cpp new file mode 100644 index 00000000000..b94e4222c86 --- /dev/null +++ b/tests/chains/Aptos/AddressTests.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// Author: Clement Doumergue +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#include "HexCoding.h" +#include "Aptos/Entry.h" +#include "PublicKey.h" +#include "PrivateKey.h" +#include +#include + +namespace TW::Aptos::tests { + +TEST(AptosAddress, Valid) { + Entry entry; + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "0x1", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "0x0", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "19aadeca9388e009d136245b9a67423f3eee242b03142849eb4f81a4a409e59c", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "0x777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175", std::monostate{})); +} + +TEST(AptosAddress, Invalid) { + Entry entry; + ASSERT_FALSE(entry.validateAddress(TWCoinTypeAptos, "", std::monostate{})); + ASSERT_FALSE(entry.validateAddress(TWCoinTypeAptos, "Seff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", std::monostate{})); + ASSERT_FALSE(entry.validateAddress(TWCoinTypeAptos, "eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175bb", std::monostate{})); + ASSERT_FALSE(entry.validateAddress(TWCoinTypeAptos, "0xSeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", std::monostate{})); + +} + +TEST(AptosAddress, FromPrivateKey) { + auto privateKey = PrivateKey(parse_hex("088baa019f081d6eab8dff5c447f9ce2f83c1babf3d03686299eaf6a1e89156e")); + auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + Entry entry; + auto address = entry.deriveAddress(TWCoinTypeAptos, pubkey, TWDerivationDefault, std::monostate{}); + ASSERT_EQ(address, "0xe9c4d0b6fe32a5cc8ebd1e9ad5b54a0276a57f2d081dcb5e30342319963626c3"); +} + +TEST(AptosAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("ad0e293a56c9fc648d1872a00521d97e6b65724519a2676c2c47cb95d131cf5a"), TWPublicKeyTypeED25519); + Entry entry; + auto address = entry.deriveAddress(TWCoinTypeAptos, publicKey, TWDerivationDefault, std::monostate{}); + ASSERT_EQ(address, "0xe9c4d0b6fe32a5cc8ebd1e9ad5b54a0276a57f2d081dcb5e30342319963626c3"); +} + +} // namespace TW::Aptos::tests diff --git a/tests/chains/Aptos/CompilerTests.cpp b/tests/chains/Aptos/CompilerTests.cpp new file mode 100644 index 00000000000..ebe0ccc49ae --- /dev/null +++ b/tests/chains/Aptos/CompilerTests.cpp @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "proto/Aptos.pb.h" +#include "proto/TransactionCompiler.pb.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TestUtilities.h" +#include "TransactionCompiler.h" + +#include +#include + +namespace TW::Aptos::tests { + +TEST(AptosCompiler, StandardTransaction) { + // Set up a signing input. + + Proto::SigningInput input; + input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + input.set_sequence_number(99); + auto& tf = *input.mutable_transfer(); + tf.set_to("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + tf.set_amount(1000); + input.set_max_gas_amount(3296766); + input.set_gas_unit_price(100); + input.set_expiration_timestamp_secs(3664390082); + input.set_chain_id(33); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + // Pre-hash the transaction. + + auto preImageHashesData = TransactionCompiler::preImageHashes(TWCoinTypeAptos, inputStrData); + TxCompiler::Proto::PreSigningOutput preSigningOutput; + preSigningOutput.ParseFromArray(preImageHashesData.data(), static_cast(preImageHashesData.size())); + auto actualDataToSign = data(preSigningOutput.data()); + + EXPECT_EQ(preSigningOutput.error(), Common::Proto::OK); + EXPECT_EQ(hex(actualDataToSign), "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021"); + + // Sign the pre-hash data. + + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; + auto signature = privateKey.sign(actualDataToSign, TWCurveED25519); + EXPECT_EQ(hex(signature), "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); + + // Compile the transaction. + + auto outputData = TransactionCompiler::compileWithSignatures(TWCoinTypeAptos, inputStrData, {signature}, {publicKey}); + Proto::SigningOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::OK); + ASSERT_EQ(hex(output.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021"); + ASSERT_EQ(hex(output.authenticator().signature()), "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); + ASSERT_EQ(hex(output.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); + nlohmann::json expectedJson = R"( + { + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], + "function": "0x1::aptos_account::transfer", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "99", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", + "type": "ed25519_signature" + } + } + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(output.json()); + assertJSONEqual(expectedJson, parsedJson); +} + +TEST(AptosCompiler, BlindTransactionJson) { + // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x7efd69e7f9462774b932ce500ab51c0d0dcc004cf272e09f8ffd5804c2a84e33?network=mainnet + + auto payloadJson = R"( + { + "function": "0x16fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c::AnimeSwapPoolV1::swap_exact_coins_for_coins_3_pair_entry", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin", + "0x881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f4::coin::MOJO", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC" + ], + "arguments": [ + "1000000", + "49329" + ], + "type": "entry_function_payload" + })"_json; + Proto::SigningInput input; + input.set_sequence_number(42); + input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + input.set_gas_unit_price(100); + input.set_max_gas_amount(100011); + input.set_expiration_timestamp_secs(3664390082); + input.set_any_encoded(payloadJson.dump()); + input.set_chain_id(1); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + // Pre-hash the transaction. + + auto preImageHashesData = TransactionCompiler::preImageHashes(TWCoinTypeAptos, inputStrData); + TxCompiler::Proto::PreSigningOutput preSigningOutput; + preSigningOutput.ParseFromArray(preImageHashesData.data(), static_cast(preImageHashesData.size())); + auto actualDataToSign = data(preSigningOutput.data()); + + EXPECT_EQ(preSigningOutput.error(), Common::Proto::OK); + EXPECT_EQ(hex(actualDataToSign), "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada0000000001"); + + // Sign the pre-hash data. + + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; + auto signature = privateKey.sign(actualDataToSign, TWCurveED25519); + EXPECT_EQ(hex(signature), "42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b"); + + // Compile the transaction. + + auto outputData = TransactionCompiler::compileWithSignatures(TWCoinTypeAptos, inputStrData, {signature}, {publicKey}); + Proto::SigningOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::OK); + ASSERT_EQ(hex(output.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada0000000001"); + ASSERT_EQ(hex(output.authenticator().signature()), "42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b"); + ASSERT_EQ(hex(output.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c4042cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b"); + nlohmann::json expectedJson = R"( +{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "100011", + "payload": { + "function": "0x16fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c::AnimeSwapPoolV1::swap_exact_coins_for_coins_3_pair_entry", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin", + "0x881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f4::coin::MOJO", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC" + ], + "arguments": [ + "1000000", + "49329" + ], + "type": "entry_function_payload" + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "42", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b", + "type": "ed25519_signature" + } +} + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(output.json()); + assertJSONEqual(expectedJson, parsedJson); +} + +} diff --git a/tests/chains/Aptos/TWAnySignerTests.cpp b/tests/chains/Aptos/TWAnySignerTests.cpp new file mode 100644 index 00000000000..dce9c2f3d72 --- /dev/null +++ b/tests/chains/Aptos/TWAnySignerTests.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "proto/Aptos.pb.h" +#include +#include +#include "TestUtilities.h" + +#include + +namespace TW::Aptos::tests { + +TEST(TWAnySignerAptos, TxSign) { + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467?network=devnet + Proto::SigningInput input; + input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + input.set_sequence_number(99); + auto& tf = *input.mutable_transfer(); + tf.set_to("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + tf.set_amount(1000); + input.set_max_gas_amount(3296766); + input.set_gas_unit_price(100); + input.set_expiration_timestamp_secs(3664390082); + input.set_chain_id(33); + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeAptos); + ASSERT_EQ(hex(output.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021"); + ASSERT_EQ(hex(output.authenticator().signature()), "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); + ASSERT_EQ(hex(output.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); + nlohmann::json expectedJson = R"( + { + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], + "function": "0x1::aptos_account::transfer", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "99", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", + "type": "ed25519_signature" + } + } + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(output.json()); + assertJSONEqual(expectedJson, parsedJson); +} + +} // namespace TW::Aptos::tests diff --git a/tests/chains/Aptos/TWAptosAddressTests.cpp b/tests/chains/Aptos/TWAptosAddressTests.cpp new file mode 100644 index 00000000000..4dea755b651 --- /dev/null +++ b/tests/chains/Aptos/TWAptosAddressTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "TestUtilities.h" +#include +#include + +#include + +using namespace TW; + +namespace TW::Aptos::tests { + +TEST(TWAptosAddress, HDWallet) { + auto mnemonic = + "shoot island position soft burden budget tooth cruel issue economy destroy above"; + auto passphrase = ""; + + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(STRING(mnemonic).get(), STRING(passphrase).get())); + + auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypeAptos, WRAPS(TWCoinTypeDerivationPath(TWCoinTypeAptos)).get())); + + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey.get())); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeAptos)); + auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + + assertStringsEqual(addressStr, "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); +} + +} // namespace TW::Aptos::tests diff --git a/tests/chains/Aptos/TWCoinTypeTests.cpp b/tests/chains/Aptos/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..6d4f14ce651 --- /dev/null +++ b/tests/chains/Aptos/TWCoinTypeTests.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWAptosCoinType, TWCoinType) { + const auto coin = TWCoinTypeAptos; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xedc88058e27f6c065fd6607e262cb2a83a65f74301df90c61923014c59f9d465")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x60ad80e8cdadb81399e8a738014bc9ec865cef842f7c2cf7d84fbf7e40d065")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "aptos"); + assertStringsEqual(name, "Aptos"); + assertStringsEqual(symbol, "APT"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 8); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainAptos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(txUrl, "https://explorer.aptoslabs.com/txn/0xedc88058e27f6c065fd6607e262cb2a83a65f74301df90c61923014c59f9d465"); + assertStringsEqual(accUrl, "https://explorer.aptoslabs.com/account/0x60ad80e8cdadb81399e8a738014bc9ec865cef842f7c2cf7d84fbf7e40d065"); +} diff --git a/tests/chains/Arbitrum/TWCoinTypeTests.cpp b/tests/chains/Arbitrum/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..0b8eb85a5e9 --- /dev/null +++ b/tests/chains/Arbitrum/TWCoinTypeTests.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWArbitrumCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeArbitrum)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xa1e319be22c08155e5904aa211fb87df5f4ade48de79c6578b1cf3dfda9e498c")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeArbitrum, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xecf9ffa7f51e1194f89c25ad8c484f6bfd04e1ac")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeArbitrum, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeArbitrum)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeArbitrum)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeArbitrum), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeArbitrum)); + + assertStringsEqual(symbol, "ETH"); + assertStringsEqual(txUrl, "https://arbiscan.io/tx/0xa1e319be22c08155e5904aa211fb87df5f4ade48de79c6578b1cf3dfda9e498c"); + assertStringsEqual(accUrl, "https://arbiscan.io/address/0xecf9ffa7f51e1194f89c25ad8c484f6bfd04e1ac"); + assertStringsEqual(id, "arbitrum"); + assertStringsEqual(name, "Arbitrum"); +} diff --git a/tests/chains/ArbitrumNova/TWCoinTypeTests.cpp b/tests/chains/ArbitrumNova/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..15820936837 --- /dev/null +++ b/tests/chains/ArbitrumNova/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWArbitrumNovaCoinType, TWCoinType) { + const auto coin = TWCoinTypeArbitrumNova; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xfdfee13848c2d21813c82c53afc9925f31770564c3117477960a81055702c1be")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x0d0707963952f2fba59dd06f2b425ace40b492fe")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "arbitrumnova"); + assertStringsEqual(name, "Arbitrum Nova"); + assertStringsEqual(symbol, "ETH"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "42170"); + assertStringsEqual(txUrl, "https://nova.arbiscan.io/tx/0xfdfee13848c2d21813c82c53afc9925f31770564c3117477960a81055702c1be"); + assertStringsEqual(accUrl, "https://nova.arbiscan.io/address/0x0d0707963952f2fba59dd06f2b425ace40b492fe"); +} diff --git a/tests/chains/Aurora/TWCoinTypeTests.cpp b/tests/chains/Aurora/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..42b7334b872 --- /dev/null +++ b/tests/chains/Aurora/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWAuroraCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeAurora)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x99deebdb70f8027037abb3d3d0f3c7523daee857d85e9056d2671593ff2f2f28")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeAurora, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x8707cdE20dd43E3dB1F74c28fcd509ef38B0bA51")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeAurora, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeAurora)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeAurora)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeAurora), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeAurora)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeAurora)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeAurora)); + assertStringsEqual(symbol, "ETH"); + assertStringsEqual(txUrl, "https://aurorascan.dev/tx/0x99deebdb70f8027037abb3d3d0f3c7523daee857d85e9056d2671593ff2f2f28"); + assertStringsEqual(accUrl, "https://aurorascan.dev/address/0x8707cdE20dd43E3dB1F74c28fcd509ef38B0bA51"); + assertStringsEqual(id, "aurora"); + assertStringsEqual(name, "Aurora"); +} diff --git a/tests/chains/Avalanche/TWCoinTypeTests.cpp b/tests/chains/Avalanche/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..84ab72079c7 --- /dev/null +++ b/tests/chains/Avalanche/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWAvalancheCoinType, TWCoinTypeCChain) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeAvalancheCChain)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x9243890b844219accefd8798271052f5a056453ec18984a56e81c92921330d54")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeAvalancheCChain, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xa664325f36Ec33E66323fe2620AF3f2294b2Ef3A")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeAvalancheCChain, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeAvalancheCChain)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeAvalancheCChain)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeAvalancheCChain), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeAvalancheCChain)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeAvalancheCChain)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeAvalancheCChain)); + assertStringsEqual(symbol, "AVAX"); + assertStringsEqual(txUrl, "https://snowtrace.io/tx/0x9243890b844219accefd8798271052f5a056453ec18984a56e81c92921330d54"); + assertStringsEqual(accUrl, "https://snowtrace.io/address/0xa664325f36Ec33E66323fe2620AF3f2294b2Ef3A"); + assertStringsEqual(id, "avalanchec"); + assertStringsEqual(name, "Avalanche C-Chain"); +} diff --git a/tests/chains/Base/TWCoinTypeTests.cpp b/tests/chains/Base/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..993248ee996 --- /dev/null +++ b/tests/chains/Base/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWBaseCoinType, TWCoinType) { + const auto coin = TWCoinTypeBase; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x4acb15506b7696a2dfac4258f3f86392b4b2b717a3f316a8aa78509b2c3b6ab4")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xb8ff877ed78ba520ece21b1de7843a8a57ca47cb")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "base"); + assertStringsEqual(name, "Base"); + assertStringsEqual(symbol, "ETH"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "8453"); + assertStringsEqual(txUrl, "https://basescan.org/tx/0x4acb15506b7696a2dfac4258f3f86392b4b2b717a3f316a8aa78509b2c3b6ab4"); + assertStringsEqual(accUrl, "https://basescan.org/address/0xb8ff877ed78ba520ece21b1de7843a8a57ca47cb"); +} diff --git a/tests/chains/Binance/AddressTests.cpp b/tests/chains/Binance/AddressTests.cpp new file mode 100644 index 00000000000..56f812afbd4 --- /dev/null +++ b/tests/chains/Binance/AddressTests.cpp @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Binance/Address.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PublicKey.h" + +#include +#include "TestUtilities.h" + +using namespace TW; +using namespace TW::Binance; + +TEST(BinanceAddress, AddressToData) { + // mainnet + auto data = TW::addressToData(TWCoinTypeBinance, "bnb1h8xf9htasu9aclra954dnnve8fgcda4ae7qfa8"); + EXPECT_EQ(hex(data), "b9cc92dd7d870bdc7c7d2d2ad9cd993a5186f6bd"); + + // testnet + data = TW::addressToData(TWCoinTypeTBinance, "tbnb1x4hxmtdf7pwea9dghq73dufh3qspm8gp5qht9c"); + EXPECT_EQ(hex(data), "356e6dada9f05d9e95a8b83d16f13788201d9d01"); + + EXPECT_EQ(Address(data, TWCoinTypeTBinance).string(), "tbnb1x4hxmtdf7pwea9dghq73dufh3qspm8gp5qht9c"); + + // invalid address + data = TW::addressToData(TWCoinTypeTBinance, "tbnb1x4hxmtdf7pwea9dghq73dufh3qspm8gp"); + EXPECT_EQ(hex(data), ""); +} + +TEST(BinanceAddress, DeriveAddress) { + // mainnet + auto pubkey = parse_hex("02bf9a5e2b514492326e7ba9a5161b6e47df7a4aa970dd2d13c398bec89608d8d0"); + auto address = TW::deriveAddress(TWCoinTypeBinance, PublicKey(pubkey, TW::publicKeyType(TWCoinTypeBinance))); + EXPECT_EQ(address, "bnb1h8xf9htasu9aclra954dnnve8fgcda4ae7qfa8"); + + // testnet + address = TW::deriveAddress(TWCoinTypeTBinance, PublicKey(pubkey, TW::publicKeyType(TWCoinTypeTBinance))); + EXPECT_EQ(address, "tbnb1h8xf9htasu9aclra954dnnve8fgcda4ahtfdak"); +} diff --git a/tests/chains/Binance/TWAnySignerTests.cpp b/tests/chains/Binance/TWAnySignerTests.cpp new file mode 100644 index 00000000000..630daa83189 --- /dev/null +++ b/tests/chains/Binance/TWAnySignerTests.cpp @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Binance/Address.h" +#include "proto/Binance.pb.h" +#include +#include "Coin.h" + +#include "TestUtilities.h" +#include +#include + +namespace TW::Binance { + +Proto::SigningOutput SignTest() { + auto input = Proto::SigningInput(); + input.set_chain_id("Binance-Chain-Tigris"); + input.set_account_number(0); + input.set_sequence(0); + input.set_source(0); + + auto key = parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"); + input.set_private_key(key.data(), key.size()); + + auto& order = *input.mutable_send_order(); + + Address fromAddress; + EXPECT_TRUE(Address::decode("bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", fromAddress)); + EXPECT_EQ(hex(fromAddress.getKeyHash()), "40c2979694bbc961023d1d27be6fc4d21a9febe6"); + auto fromKeyhash = fromAddress.getKeyHash(); + Address toAddress; + EXPECT_TRUE(Address::decode("bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5", toAddress)); + EXPECT_EQ(hex(toAddress.getKeyHash()), "bffe47abfaede50419c577f1074fee6dd1535cd1"); + auto toKeyhash = toAddress.getKeyHash(); + + { + auto inputOrder = order.add_inputs(); + inputOrder->set_address(fromKeyhash.data(), fromKeyhash.size()); + auto inputCoin = inputOrder->add_coins(); + inputCoin->set_denom("BNB"); + inputCoin->set_amount(1); + } + + { + auto output = order.add_outputs(); + output->set_address(toKeyhash.data(), toKeyhash.size()); + auto outputCoin = output->add_coins(); + outputCoin->set_denom("BNB"); + outputCoin->set_amount(1); + } + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeBinance); + return output; +} + +TEST(TWAnySignerBinance, Sign) { + Proto::SigningOutput output = SignTest(); + ASSERT_EQ(hex(output.encoded()), "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212409073e581e1ea4fdf11242fe30a732f96d20799c638354bcf7a242161ac015b9321fbbed93e85b0ef9b5de58fba74dff54ecb1e379ef26e1023be8996003f4899"); +} + +TEST(TWAnySignerBinance, SignJSON) { + auto json = STRING(R"({"chainId":"Binance-Chain-Tigris","accountNumber":"13186","source":"2","memo":"Testing","sendOrder":{"inputs":[{"address":"EuZU7e+eUIuDNzaph9Bp2lqJrts=","coins":[{"denom":"BNB","amount":"1345227"}]}],"outputs":[{"address":"M7vzB7mBRvE9IGk8+UbC13pMryg=","coins":[{"denom":"BNB","amount":"1345227"}]}]}})"); + auto key = DATA("f947b3554a1c2fa70e1caa9de53fbda353348d1e856c593848f3a29737d31f11"); + auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeBinance)); + + ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeBinance)); + assertStringsEqual(result, "ca01f0625dee0a4a2a2c87fa0a210a1412e654edef9e508b833736a987d069da5a89aedb12090a03424e4210cb8d5212210a1433bbf307b98146f13d20693cf946c2d77a4caf2812090a03424e4210cb8d52126d0a26eb5ae9872102e58176f271a9796b4288908be85094a2ac978e25535fd59a37b58626e3a84d9e1240015b4beb3d6ef366a7a92fd283f66b8f0d8cdb6b152a9189146b27f84f507f047e248517cf691a36ebc2b7f3b7f64e27585ce1c40f1928d119c31af428efcf3e1882671a0754657374696e672002"); +} + +TEST(TWAnySignerBinance, MultithreadedSigning) { + auto f = [](int n) { + for (int i = 0; i < n; i++) { + Proto::SigningOutput output = SignTest(); + ASSERT_EQ(hex(output.encoded()), "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212409073e581e1ea4fdf11242fe30a732f96d20799c638354bcf7a242161ac015b9321fbbed93e85b0ef9b5de58fba74dff54ecb1e379ef26e1023be8996003f4899"); + } + }; + + // Ensure multiple threads cause no asserts + std::thread th1(f, 1000); + std::thread th2(f, 1000); + std::thread th3(f, 1000); + + th1.join(); + th2.join(); + th3.join(); +} + +} // namespace TW::Binance diff --git a/tests/chains/Binance/TWCoinTypeTests.cpp b/tests/chains/Binance/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..c7f09c9a16c --- /dev/null +++ b/tests/chains/Binance/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWBinanceCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBinance)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("A93625C9F9ABEA1A8E31585B30BBB16C34FAE0D172EB5B6B2F834AF077BF06BB")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBinance, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("bnb1u7jm0cll5h3224y0tapwn6gf6pr49ytewx4gsz")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBinance, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBinance)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBinance)); + const auto chainId = WRAPS(TWCoinTypeChainId(TWCoinTypeBinance)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBinance), 8); + ASSERT_EQ(TWBlockchainBinance, TWCoinTypeBlockchain(TWCoinTypeBinance)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeBinance)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBinance)); + assertStringsEqual(symbol, "BNB"); + assertStringsEqual(txUrl, "https://explorer.binance.org/tx/A93625C9F9ABEA1A8E31585B30BBB16C34FAE0D172EB5B6B2F834AF077BF06BB"); + assertStringsEqual(accUrl, "https://explorer.binance.org/address/bnb1u7jm0cll5h3224y0tapwn6gf6pr49ytewx4gsz"); + assertStringsEqual(id, "binance"); + assertStringsEqual(name, "BNB Beacon Chain"); + assertStringsEqual(chainId, "Binance-Chain-Tigris"); +} diff --git a/tests/chains/Binance/TWWalletConnectSigning.cpp b/tests/chains/Binance/TWWalletConnectSigning.cpp new file mode 100644 index 00000000000..7ee7e7956f4 --- /dev/null +++ b/tests/chains/Binance/TWWalletConnectSigning.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/Binance.pb.h" +#include "proto/WalletConnect.pb.h" +#include "Coin.h" +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Binance { + +TEST(TWWalletConnectSign, SendOrder) { + auto private_key = parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"); + const auto payload = R"({"signerAddress":"bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2","signDoc":{"account_number":"19","chain_id":"chain-bnb","memo":"","data":null,"msgs":[{"inputs":[{"address":"bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2","coins":[{"amount":1001000000,"denom":"BNB"}]}],"outputs":[{"address":"bnb13zeh6hs97d5eu2s5qerguhv8ewwue6u4ywa6yf","coins":[{"amount":1001000000,"denom":"BNB"}]}]}],"sequence":"23","source":"1"}})"; + + WalletConnect::Proto::ParseRequestInput parsingInput; + parsingInput.set_method(WalletConnect::Proto::Method::CosmosSignAmino); + parsingInput.set_payload(payload); + + const auto parsinginputData = parsingInput.SerializeAsString(); + const auto parsingInputDataPtr = WRAPD(TWDataCreateWithBytes(reinterpret_cast(parsinginputData.c_str()), parsinginputData.size())); + + const auto outputDataPtr = WRAPD(TWWalletConnectRequestParse(TWCoinTypeBinance, parsingInputDataPtr.get())); + + WalletConnect::Proto::ParseRequestOutput parsingOutput; + parsingOutput.ParseFromArray( + TWDataBytes(outputDataPtr.get()), + static_cast(TWDataSize(outputDataPtr.get())) + ); + + EXPECT_EQ(parsingOutput.error(), Common::Proto::SigningError::OK); + + // Step 2: Set missing fields. + ASSERT_TRUE(parsingOutput.has_binance()); + Proto::SigningInput signingInput = parsingOutput.binance(); + + signingInput.set_private_key(private_key.data(), private_key.size()); + + Proto::SigningOutput output; + ANY_SIGN(signingInput, TWCoinTypeBinance); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + EXPECT_EQ(hex(output.signature()), "3c24c784c6bbf99d54ffabb153edcb6d3c4a774936df5c72a5d32897256f8e062f320fb4753302fb0a96f08c475974d20edfd1a27bbeeda73587f58ddc958975"); + EXPECT_EQ(output.signature_json(), R"({"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Amo1kgCI2Yw4iMpoxT38k/RWRgJgbLuH8P5e5TPbOOUC"},"signature":"PCTHhMa7+Z1U/6uxU+3LbTxKd0k231xypdMolyVvjgYvMg+0dTMC+wqW8IxHWXTSDt/Ronu+7ac1h/WN3JWJdQ=="})"); +} + +} // namespace TW::Binance diff --git a/tests/chains/Binance/TransactionCompilerTests.cpp b/tests/chains/Binance/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..3c8a1d5a171 --- /dev/null +++ b/tests/chains/Binance/TransactionCompilerTests.cpp @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "uint256.h" + +#include "proto/Binance.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(BinanceCompiler, CompileWithSignatures) { + /// Step 1: Prepare transaction input (protobuf) + const auto coin = TWCoinTypeBinance; + // bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2 + const auto fromAddressData = parse_hex("40c2979694bbc961023d1d27be6fc4d21a9febe6"); + // bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5 + const auto toAddressData = parse_hex("bffe47abfaede50419c577f1074fee6dd1535cd1"); + + Binance::Proto::SigningInput txInput; + + txInput.set_chain_id("Binance-Chain-Nile"); + auto& sendOrder = *txInput.mutable_send_order(); + + auto& input1 = *sendOrder.add_inputs(); + input1.set_address(fromAddressData.data(), fromAddressData.size()); + auto& input1Coin = *input1.add_coins(); + input1Coin.set_amount(1); + input1Coin.set_denom("BNB"); + + auto& output1 = *sendOrder.add_outputs(); + output1.set_address(toAddressData.data(), toAddressData.size()); + auto& output1Coin = *output1.add_coins(); + output1Coin.set_amount(1); + output1Coin.set_denom("BNB"); + + auto txInputData = data(txInput.SerializeAsString()); + + /// Step 2: Obtain preimage hash + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = data(preSigningOutput.data_hash()); + EXPECT_EQ(hex(preImageHash), "3f3fece9059e714d303a9a1496ddade8f2c38fa78fc4cc2e505c5dbb0ea678d1"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = + parse_hex("026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = + parse_hex("1b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5" + "366bb8b041781a6771248550d04094c3d7a504f9e8310679"); + + // Verify signature (pubkey & hash & signature) + { EXPECT_TRUE(publicKey.verify(signature, preImageHash)); } + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKeyData}); + + const auto ExpectedTx = + "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001" + "121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35" + "920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c" + "253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a5" + "04f9e8310679"; + { + Binance::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Binance::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); + auto key = parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"); + input.set_private_key(key.data(), key.size()); + + Binance::Proto::SigningOutput output; + ANY_SIGN(input, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, {publicKey.bytes}); + Binance::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Binance::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); + } +} diff --git a/tests/chains/BinanceSmartChain/SignerTests.cpp b/tests/chains/BinanceSmartChain/SignerTests.cpp new file mode 100644 index 00000000000..c9196d6b53b --- /dev/null +++ b/tests/chains/BinanceSmartChain/SignerTests.cpp @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "Ethereum/Address.h" +#include "Ethereum/ABI/Function.h" +#include "proto/Ethereum.pb.h" +#include "HexCoding.h" +#include "uint256.h" +#include "TestUtilities.h" +#include "PrivateKey.h" + +#include + +namespace TW::Binance { + +TEST(BinanceSmartChain, SignNativeTransfer) { + // https://explorer.binance.org/smart-testnet/tx/0x6da28164f7b3bc255d749c3ae562e2a742be54c12bf1858b014cc2fe5700684e + + Ethereum::Proto::SigningInput input; + + auto chainId = store(uint256_t(97)); + auto nonce = store(uint256_t(0)); + auto gasPrice = store(uint256_t(20000000000)); + auto gasLimit = store(uint256_t(21000)); + auto toAddress = "0x31BE00EB1fc8e14A696DBC72f746ec3e95f49683"; + auto amount = store(uint256_t(10000000000000000)); // 0.01 + // addr: 0xB9F5771C27664bF2282D98E09D7F50cEc7cB01a7 mnemonic: isolate dismiss ... cruel note + auto privateKey = parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904"); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_to_address(toAddress); + input.set_private_key(privateKey.data(), privateKey.size()); + auto& transfer = *input.mutable_transaction()->mutable_transfer(); + transfer.set_amount(amount.data(), amount.size()); + + Ethereum::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeSmartChain); + + ASSERT_EQ(hex(output.encoded()), "f86c808504a817c8008252089431be00eb1fc8e14a696dbc72f746ec3e95f49683872386f26fc100008081e5a057806b486844c5d0b7b5ce34b289f4e8776aa1fe24a3311cef5053995c51050ca07697aa0695de27da817625df0e7e4c64b0ab22d9df30aec92299a7b380be8db7"); +} + +TEST(BinanceSmartChain, SignTokenTransfer) { + auto toAddress = "0x31BE00EB1fc8e14A696DBC72f746ec3e95f49683"; + auto funcData = Ethereum::ABI::Function::encodeFunctionCall("transfer", Ethereum::ABI::BaseParams{ + std::make_shared(toAddress), + std::make_shared(uint256_t(10000000000000000)) + }).value(); + EXPECT_EQ(hex(funcData), "a9059cbb00000000000000000000000031be00eb1fc8e14a696dbc72f746ec3e95f49683000000000000000000000000000000000000000000000000002386f26fc10000"); + + auto input = Ethereum::Proto::SigningInput(); + auto chainId = store(uint256_t(97)); + auto nonce = store(uint256_t(30)); + auto gasPrice = store(uint256_t(20000000000)); + auto gasLimit = store(uint256_t(1000000)); + auto tokenContractAddress = "0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee"; + auto dummyAmount = store(uint256_t(0)); + // addr: 0xB9F5771C27664bF2282D98E09D7F50cEc7cB01a7 mnemonic: isolate dismiss ... cruel note + auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_to_address(tokenContractAddress); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); + transfer.set_data(funcData.data(), funcData.size()); + + const std::string expected = "f8ab1e8504a817c800830f424094ed24fc36d5ee211ea25a80239fb8c4cfd80f12ee80b844a9059cbb00000000000000000000000031be00eb1fc8e14a696dbc72f746ec3e95f49683000000000000000000000000000000000000000000000000002386f26fc1000081e6a0aa9d5e9a947e96f728fe5d3e6467000cd31a693c00270c33ec64b4abddc29516a00bf1d5646139b2bcca1ad64e6e79f45b7d1255de603b5a3765cbd9544ae148d0"; + + Ethereum::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeSmartChain); + + EXPECT_EQ(hex(output.encoded()), expected); +} + +} // namespace TW::Binance diff --git a/tests/chains/BinanceSmartChain/TWAnyAddressTests.cpp b/tests/chains/BinanceSmartChain/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..139e6a8368b --- /dev/null +++ b/tests/chains/BinanceSmartChain/TWAnyAddressTests.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWBinanceSmartChain, Address) { + + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06").get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false)); + auto string = "0xf3d468DBb386aaD46E92FF222adDdf872C8CC064"; + + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeSmartChain)); + auto expected = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING(string).get(), TWCoinTypeSmartChain)); + + auto addressString = WRAPS(TWAnyAddressDescription(address.get())); + auto expectedString = WRAPS(TWAnyAddressDescription(expected.get())); + + assertStringsEqual(addressString, string); + assertStringsEqual(expectedString, string); +} diff --git a/tests/chains/BinanceSmartChain/TWCoinTypeTests.cpp b/tests/chains/BinanceSmartChain/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..7dc7a6cb102 --- /dev/null +++ b/tests/chains/BinanceSmartChain/TWCoinTypeTests.cpp @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWBinanceSmartChainCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeSmartChain)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeSmartChain, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x35552c16704d214347f29Fa77f77DA6d75d7C752")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeSmartChain, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeSmartChain)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeSmartChain)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeSmartChain), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeSmartChain)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeSmartChain)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeSmartChain)); + ASSERT_EQ(20000714, TWCoinTypeSmartChain); + assertStringsEqual(symbol, "BNB"); + assertStringsEqual(txUrl, "https://bscscan.com/tx/0xb9ae2e808fe8e57171f303ad8f6e3fd17d949b0bfc7b4db6e8e30a71cc517d7e"); + assertStringsEqual(accUrl, "https://bscscan.com/address/0x35552c16704d214347f29Fa77f77DA6d75d7C752"); + assertStringsEqual(id, "smartchain"); + assertStringsEqual(name, "BNB Smart Chain"); +} + +TEST(TWBinanceSmartChainLegacyCoinType, TWCoinType) { + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeSmartChainLegacy)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeSmartChainLegacy)); + + ASSERT_EQ(10000714, TWCoinTypeSmartChainLegacy); + assertStringsEqual(id, "bsc"); + assertStringsEqual(name, "Smart Chain Legacy"); +} diff --git a/tests/Bitcoin/BitcoinAddressTests.cpp b/tests/chains/Bitcoin/BitcoinAddressTests.cpp similarity index 86% rename from tests/Bitcoin/BitcoinAddressTests.cpp rename to tests/chains/Bitcoin/BitcoinAddressTests.cpp index 6739fb4961e..fa8507663df 100644 --- a/tests/Bitcoin/BitcoinAddressTests.cpp +++ b/tests/chains/Bitcoin/BitcoinAddressTests.cpp @@ -1,20 +1,19 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Bitcoin/Address.h" -#include -#include "PublicKey.h" #include "Bitcoin/Script.h" #include "HexCoding.h" +#include "PublicKey.h" +#include -#include #include +#include using namespace TW; -using namespace TW::Bitcoin; + +namespace TW::Bitcoin::tests { const char* TestPubKey1 = "039d645d2ce630c2a9a6dbe0cbd0a8fcb7b70241cb8a48424f25593290af2494b9"; const char* TestP2phkAddr1 = "12dNaXQtN5Asn2YFwT1cvciCrJa525fAe4"; @@ -50,20 +49,21 @@ TEST(BitcoinAddress, P2SH_CreateFromString) { TEST(BitcoinAddress, P2WPKH_Nested_P2SH) { // P2SH address cannot be created directly from pubkey, script is built const auto publicKey = PublicKey(parse_hex(TestPubKey1), TWPublicKeyTypeSECP256k1); - + const auto pubKeyHash = publicKey.hash({}); EXPECT_EQ(hex(pubKeyHash), "11d91ce1cc681f95583da3f4a6841c174be950c7"); - + const auto script = Script::buildPayToV0WitnessProgram(pubKeyHash); - EXPECT_EQ(hex(script.bytes), "0014" "11d91ce1cc681f95583da3f4a6841c174be950c7"); - + EXPECT_EQ(hex(script.bytes), "0014" + "11d91ce1cc681f95583da3f4a6841c174be950c7"); + const auto scriptHash = Hash::sha256ripemd(script.bytes.data(), script.bytes.size()); EXPECT_EQ(hex(scriptHash), "ee1e69460b59027d9df0a79ca2c92aa382a25fb7"); - + Data addressData = {TWCoinTypeP2shPrefix(TWCoinTypeBitcoin)}; TW::append(addressData, scriptHash); EXPECT_EQ(hex(addressData), TestP2shData1); - + const auto address = Address(addressData); EXPECT_EQ(address.string(), TestP2shAddr1); EXPECT_EQ(hex(address.bytes), TestP2shData1); @@ -74,3 +74,5 @@ TEST(BitcoinAddress, P2SH_CreateFromData) { EXPECT_EQ(address.string(), TestP2shAddr1); EXPECT_EQ(hex(address.bytes), TestP2shData1); } + +} // namespace TW::Bitcoin::tests diff --git a/tests/chains/Bitcoin/BitcoinOrdinalNftData.h b/tests/chains/Bitcoin/BitcoinOrdinalNftData.h new file mode 100644 index 00000000000..3923de9d360 --- /dev/null +++ b/tests/chains/Bitcoin/BitcoinOrdinalNftData.h @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +std::string nftInscriptionImageData = "\ +89504e470d0a1a0a0000000d4948445200000360000002be0803000000f3\ +0f8d7d000000d8504c54450000003070bf3070af3173bd3078b73870b730\ +70b73575ba3075ba3075b53070ba3078bb3474bb3474b73074bb3074b733\ +76b93076b93373bc3373b93073b93376bc3275ba3075ba3572ba3272ba33\ +75bc3276b83474bb3274bb3274b83276bd3276bb3474bd3474bb3276b934\ +74bb3474b93274bb3274b93476bb3276bb3375ba3275ba3373bc3273ba32\ +77bc3375bc3375ba3373bc3374ba3174b93376bb3375bc3375bb3375b933\ +75bb3374bb3376bb3475bb3375bb3375ba3374bb3276bc3276ba3375bc32\ +75bc3375bb3275bb3374bc3374bb3375bb7edf10e10000004774524e5300\ +10101f20202030303030404040404050505050505f606060606f70707070\ +7f7f7f7f80808080808f8f909090909f9f9f9fa0a0afafafb0bfbfcfcfcf\ +cfcfdfdfdfdfefefefef6a89059600001c294944415478daeddd6b7bd3d6\ +ba2ee0d8350eb00c81ec5533cbac69d6da99383334dbe510904b66129383\ +feff3fda17506888751892255b52eefb634b1259d2a3f1ead5d0f0d61600\ +000000000000000000000000000000000000000000000000000000000000\ +000000000000000000000000000000000000000000000000000000000000\ +000000000000000000000000000000000000000000000000000000000000\ +000000000000000000000000000000000084eaef3c9b4e274f87f604546e\ +27ba8abf3a19db1b50a96114dfb01031a8d0cf57f18f0e7a760a54e4b778\ +c9c2ad1854a27f1c27586cdb3350c1edd7224eb667dfc0aa9e5fc569666e\ +c460b5f2f0df71063762b08afb8b389b3211ca978771aec82006a5fcf870\ +39cd8587ce5066f8ba8ac3cc0c6250d0cec738d8c53fed2f2820bb79a89d\ +08ab185fc545a91321b03a8ce21216ea44c8372c152f2fb1400d375fb722\ +b6630f427abcf6aee2d544220675c54bc420adb5318b2be25e0c6ec72b8a\ +2bb4186bdac3dfb5e1495cb5d903fb15bed48657711d4ec67d3b973b6eb8\ +574fbafe1ac6343cb8cba5e1248a6bb69031eeead8557bba648c3b3a74ed\ +1c9cc56b74f54e5b913b13aebde82a5ebf9399af8da0ebe17a7ab09170fd\ +1db2b1ee3dddbce57a3a79b77a5978383e5fb95c8c0e9eba29a33ba3d683\ +6793d94925e3d6f9686b6bf0a69ab12c3a98ec2819696baa86c39d67cfa6\ +b377671556846fbe3e385e7d10fbdb59f4ee60fa6c6767e899340d8dd2b3\ +835974d3c9d9592db759974fbefdc98a06b1a5b09d9dfdf041a2d94429c9\ +66ed44ebea4f1cde1c63aa1cc4b29fa1ed2923d9dce8b5b6787dbefbbaa9\ +a641ccbb2f3448eec2f1955587fbcbb748bbeb1ac4e203479a8de46b5d4f\ +b4e683c4bfbf7fbda6bf3f73ac59bfe16233d5e106ea446318eb77bc9eea\ +f0d7ac6d189cac27618f1c6ed66cbca99baf5b9bb1965bb185e3cd9a9dae\ +bb35bfd18819c258af876be82d0c4207d3fa231639e2acd5a431f15a4bc4\ +2e7cdd3a6bf57ec3f75eeb8e98b75ce8cc2d58f1787d89d8bcce80fde290\ +b34ef5bdf13f2f3d37a9cee762024617027679385a65ab06b5558a0246fb\ +03369facfe5ad6e88d802160950f5e3787b1530143c07e48d7eb51955b37\ +383c1730da2caa345d35ac22bf5d6dc6048c7606ecf27054d77a18db93ea\ +6ac5270e39ed0bd87c7f54ef660ec6c7972623721703f6e970773d4b398d\ +267301e34e05ecf470cddfdd359acc2f058c3b10b04faf27a3cd2c42b8bd\ +7b583e6502c65a9598ec7b393fdc54b66ea66c7a7c2a6034dddb2283d6e9\ +ebe978bb496be76eef4e0ee7a702467b03f6e9d3fcf8f574321edd6bee87\ +e88f46e3e9e1f1fcf4536ee9e875159a12b07fddbb77af7d1fa87fefde76\ +c6f4949f1c721a12b0f64e7a1030044cc0b8038ed2cfc51702062b9aa69f\ +8bd3d67ea86b0143c0ea93d14574c411300143c0040c5609d82b01831565\ +7cf7c31f020602b66490b174b6238e80d51730df5f848009187721601f04\ +0c56f43863259bb67ea68cef3cfbd31147c07c2604cc6782554ec63f050c\ +3404040c015ba72e764611b03604ec0f479ca604ec42c060551d9c183b11\ +3004ac3e9d7cc70d011330b8ad830bc40818cdd1c125ce8e040c01ab4f27\ +175345c0040c6e3bedde57fdbc17301a23ea5ec03af891e862c09e0818a8\ +a7040c1d81353af3059708d84602e6cb5558b30e3e95f5ed45344707e715\ +59391b011330ee84eebd3cd513309aa37bafff5ad8977604ec8380c18a76\ +3bb7c65917977aa4b51edfa580591691757bd8b9824ac070c7e2ae923b1f\ +b08bce05ccaa6dac5bf79e1a4d058c06b94b01b3e60d6bd7b9a9b11695a2\ +493af77247c60b382f1c6e1a14b076be9e68cd1b9aa4732fd877709511ba\ +19b05f5c31a0be5b967606ec4cc0d013d848c0ac18c0da75aeab9df160af\ +e770b36e19cf655f752d608e366bd7b5a97b0301a325016be5e4f3875e68\ +a649baf64ab3179a69cb09b9e8d8e7f1be258dba676965c0bc6f89a6408d\ +265e07a3513af6dcc8eb60344bc75e087b2b60344ac7de57f1b60acdd2b1\ +af41f7b60aad39237f71bd80fa6e5ada389dde17c8d22c1d9b4e1f7b5b85\ +46e9d63a82be1d8c86e9d674fa879d5ba998ee06ac8593f71e9b4c8f53b2\ +3ebbe6fad22cddfafa8789b9beb4a72dd0bec988befa81a6e95463db5444\ +9aa6538f66232bd3d330a75d9a2b652a224df3be4b177debfae2b6c50d25\ +7748971a6f5645a471ba3495e3b1995234cd6e87a672ec5a1591a6e9d2fc\ +d8899952b4e9bea56d53398e4ce4a071e2ee3c697e6f22078d73d69d87b3\ +9135a568d359d9b627cdd79e33d3386fbbf31d7cb1256fd019a8cd43df1f\ +4bf38c3bf3f0c873661a68b733a7a5c7603450771e841d593080e6e975a6\ +35f0be3bed1a3ae4ac2b4f8f4ebdcf4cbbcecb76cd7f883d06a381de76a4\ +4fffd063309a68da913efd63af5bd244e38ef4e927de06a36da5559b56b2\ +38f2188c26caead3b7693e7da44b4f235d77a3bd7dad4b4f239d76a28dd8\ +d3a5a799de76a23bf0d8a28834d3a4136dc489b9f434d36e27aefdef3511\ +69a64127da88a7e6d2d350d75d6870c79a883454d4818bff634d449aeaa8\ +03fd8189053968aa7107ba1c334d449a2a6b36625bdeb93cd544a4a9b226\ +41bcf211604519ab0644edef71f87a66362c63b2d4453b3a0453af33d35c\ +93d6f7b8233d0edad9e568c7c237b11e070d76ddf29bb0c7adbf42d06951\ +cb6fc2a6e671d064472d3f4123f33868b2dd763f46ca7a0a6645299ca177\ +fbfa40f79db57a3ae2cc6366da7b13d6fcd7a916195b3f7070d9bcac09f5\ +8d6fd43f760b468b6fc21adf879bb905a3e94e5b5c232edc82d1e69bb086\ +d7888f634fc168ba517bcfd299898834df756ba7f35d5b508ae6cb78272c\ +fed8e40dcf6a807a178c569ca74d9e8f98350ff1dc71a521321bf50d6e73\ +0c634d7ada206a679b63167b558536c85a37a0b96d8ecc014c85484b6ac4\ +c6be76993980cd1c555a5223367408cb1cc0f4106992710b87b0990a91d6\ +d488d7ad1bc2b207b0b1634a931c659eaec3b60d605e05a35932e72336f1\ +59d8fdcc0df6c596b4a9cdd1c0373f162ddb5eeeb8cc4761f1a2697d8edf\ +622d0ebad3e668da63a5ec0e871607cdf332fb9cfd6793b6b59f5d206a71\ +d0c0212cfb9cbd68522731bb836816074df4266ecb6d58f60d98018c461a\ +e59cb68de9d5ff1c1bc068a1a81d27eefd2b03185d1cc2e2bd56e4cb0046\ +3befc21a91b0dc7c19c068aac175dcf4d161e72a3680d1562ff3cedef8dd\ +667b893fe76ee0b9018cc6eae50e61f16293cfc37ecbddbcf8574791e69a\ +e49fc18b8d4da4ed1fe76f9d5988345a947f0e6faad5717f11b06ddb0e21\ +edee73c4717cb28932f179c086e970d0fe3ec7e73271edd3d5872143ab0e\ +07dd2812e378b6de41ecf955d0568d1c3e3a5124c6f1628defaf840d5f71\ +fc2f478fe69b849dcd6b6bd8f7f7023748079156f83df0848e0fd611b19d\ +45e0d65cba01a3157a27a109abbfd9713f0add16eb04d0b1dbb02f11dba9\ +b53afc77f086b801a33db6c3cfeb1afb89fdbdabf0cd78e3a8d11ee378f3\ +117bb228b00dffe93b68b4c87e91842d6a983cb51315d9024f98e972c22a\ +ef760c0bc54bbee87ac22a8d5891de867c713712164715dd8a15ea6dc817\ +77266195743b0ac74bbeb833095b3d62e3452c5fdc1593c2095bada158ac\ +75f8c55c7f9ef61a5d178f58e96ec7fde2f18a0f1d23da6c705efca42fb7\ +6a47d1d6a1f951742261c725cefbe2b762c57b1b9fe7cf7bc192bbd8ea28\ +1eb1e2bd8dcfd3a3b437e8c48d588932b1d01bcf257a1b6ebfe85099f8a6\ +4c0042bb1dc35999dfae3ca44326d775d589a56ebe74e7e9da2056aa8a8b\ +f7f296b2df59941abe2c904de706b1f3eaebc461b9d8ce75377027f6edfb\ +5886155787e74f1c0b3a697c5eae4eacb23a8c0fdd7dd159a59e8925d689\ +fde372d5e17d070175626e3ff179a9eaf0d2d26ca813f307b172cd8dcb7d\ +d5212296ffc2f35ea951f058ef903b5227febe4ab363e763a99b2f333770\ +2b96ff7511a5de4a89cfc58b3b16b179a941ac546fdec40ddc8ad5466f03\ +11ab8f07cbdc5dfb7547ccb443743bea8b97de06225657bcb40ea1744351\ +eb1036d6edd03a84fa2236132ff8a14edcd7db8016743bf43620d1fdf32a\ +6ebeec47a8eb56ccbc0dc8aa137f77f3057546ecbd275fd0bc3a517508b5\ +d589aa43088fd8b9ea106a54e8eb22ac67034507b1379e2cc3e69b1d9a1b\ +50db2066f882fa0631c317d4368819bea0be41ccf005b50d62862fa844e2\ +33b163c3175434889d9bba0135dab79e28ace94eecc4175542e5111b1f9f\ +7efaf4e9f850730300000000000000000000000000000000000000000000\ +000000000000000000000000000000000000000000000000000000000000\ +00000000000000000068bff16c49effbfffc9fe5ff698f41016fe3253f7d\ +ff9fd1f2ffb4c740c040c040c0046c15b3aa0ceedcae1b26ec8583b01f7d\ +b2f483e3b01f7cbef483ff2b608d1657e5d19ddb75bdebd2bb61f9bcbd08\ +fb9bcb7f7226600276778aab781a34f695dd7fbb35ec770113b0861a25ec\ +8628e407c7093ff82aa8a05ffab173f7600276a76ac49f4a55888135620d\ +15a28009585dfefbd992b5d488c3b23b30a1427cd2fa80f5968fc28e8075\ +22602b9f3d256bc471e20e0ca81167657b234d0ed860f94ffc216002965a\ +23f64a55884159a9a3421430016b6cc0126bc417e52ac4803d584b852860\ +02d6dc808dcbd488e3943d987bf7564b85286002d6dc8095aa11a3943db8\ +c8fb6b8b3a2a440113b0e6062c312c2fca5588b9bbf0f1f24f8c054cc03a\ +1db01235e23875174e8b5688010d150113b036072ca946bce895aa107393\ +b95c217ed8123001eb74c012e3f24bb90a316716484d15a2800958930356\ +b8461c67ecc3e9062a4401abd728d5ff4938030ed3ff79ff6e06ac708d78\ +9a11b0680315a2806dcaa068f17327039658233e2a5721660e4975558802\ +d6ad800dd734d2f587c3dc3f55c9d933890b9d2093cc80bd2854210e56fb\ +f8b5062cfcef0b5858c03e1fd21ffdb083fb3b93d9c9591cc7f3bf767fd6\ +3fcefdd5c3f403fbec60767276f56d83cfa2e860b2d3af3160bdb8508d78\ +9a19b0a84885384fd8530f6e7ffc77d3a70fd619b0efc7f9b3ab93e820ef\ +cf0b5860c01e67eca6fe24baba795a14dca7d3c0226c78e3cfdc9a24f16e\ +b25353c08ad588c39c5651af7c85387c363b4bfea557efc6c3b5042cf900\ +2c664f05acce80ed1c5fddbaeed611b0fe5e9473f2465f4fb383e89b8493\ +21baa5648df8aa5c85985123e654883b7b27d9bff8ddd3ba03d69fa41f80\ +abd952c2bfefe2840d5fdc3a0a0702961eb061b45cd8541fb0fede55c8b3\ +84689cf5a0372e35a2f58abc7a729ab781652ac47e5ebabe9eb5e33a0396\ +7b00a25b254491a33017b0d4803dbf8aeb0f5860bcbe9c66412763a192b1\ +408d38ccfd83bdc215e2f020f4c32f86b505ec79c036fc388a09581501eb\ +1f27eeac8a03b6b388eb52b68ff8aa5c8598ba87532bc4e1acc8c7d9ab27\ +603b1f8bff7901ab2060c38ff11a02f63c8e371ab00235e269ee1f8c8a55\ +88e143f75727c31a02167e006e8ca102b67ac0fa8b780d01fb2dde70c0c2\ +6bc4fc0a31a5c39f52210e3f16fe40cb65e2aa01eb17b9a95d6c0b587501\ +3b8ed710b0c2f95a541eb049e8b4c2e57f781db68b8f922bc4de79bc7ac2\ +560cd8b0607dfeb3805515b0ab780d012b9caf595479c0926ac4455085f8\ +e751d88e48ab10c72506e545afca800d0bdffffe2c6015052c5e43c08685\ +cfaf41f5010bad119737f6c528a8467c98da432c3184dd5e6760a5800d4b\ +f4971e09587b02b6287e76d510b0c01a719250e75d8724f328f529739921\ +ecd65254ab04ac4cbee28ba180b52560c5cfaf411d01eb85b503972bc4a4\ +ecfc115221fef9ed7f9519c27e1c245709585426dff1494fc05a12b01203\ +581d014bfc9d3fe55788e3a4c5812f422ac4ef53aa4665cef06945012bdb\ +c03d10b076042cf96f5ebe9eec6edffb6c7bb43b3d3ebdf57375042ca946\ +7c1152216e85d488e9156272043e7d9acf4f3f850e61e5039676037c7a38\ +feb2ff479337e7a9b76102d682801d25fdfed1d2839addefc779be554fc0\ +826ac4d3c4f3e6287f16484685786b08bb3c9eecdefbf67fb677d3ceef1f\ +c25f3e6089f5c3e5e1f6cd6d1f9fa73c4e17b016042c6162c4bf927fc5e8\ +cdf7dedbd1fc9b84b7fde7b784eeaca4d4f6022ac4901af1617640a2bf4f\ +eda56b4bcaf9fd43f84b072c7102c77c10b409e3adbff77142486f1d05b3\ +e9730336ff7665bd77afc28005e7ebf3dfdc3fbffd6d7515be0fff32bf46\ +7c995ce7f5726bc4495685f83da1cb23f7d7b1f5f7c4e3d15b3d60891dc4\ +5f93f6fc49e66342ef83ad1cb0cbfd7eeeaf2e13b0e54b7bf6f73dee3fa9\ +2d6083fc1a719152f8bccdab114fb32ac4bf3e464abcbe7cea9c1ab16cc0\ +923a1cff480ef971ea733c01ab2060fbfd805f5d2660cb7fb2d83a4b55ae\ +e8925b233e4c3bc7f26ac48456c28f091cc5e7a3ac4d4b1ac3fe583d608b\ +b0f1eb4bc24e32ae3e02b65ac0920e7e6d01fb636301cbad118fd2eabcbc\ +1a31a142bcb5ccc5ef39cbcb649768250336ce9d2272f380677c46015b29\ +60e783ad3506eccf8d052cb7465ca4b6c6a2ece754a7050be1840e4fe6e8\ +5a326051d8a14e8de32b01ab22609783ad7506ecfb5cedb5072ce997dd7c\ +dcf430fd2e649c35bee4578801aeb306c172011b66dd5685ec9e85805511\ +b0f1568d014bdac27f6e2a602fb377d7517a2730bb46ccaf10f31d656d5a\ +b9808d836629670d613f09d8ea017bbf5567c0121f562e0e1e6c24603935\ +e222e3e169668db87a859878bff462c580bd2f7007967c15792160ab07ec\ +51bd014b5d073075add1fa0296b4317f5fd41f668dede38c64565121263d\ +aa9eae18b045ce1cfd25efd38eb880950fd89f5bf506ec28e3d1f655c692\ +beb504ec65d605e628eb59712fe3141f075fb58a1dca57ab05ac97fdf03b\ +c124ede410b0f2017b5173c0f2a7929f640d66d5066c94f5b8699139bd2e\ +a3468c8a5588fd074f9f3d9bde7694b5bf4b052ce1684fb31da73ded13b0\ +f2017b5073c002df863a3978daaf3f60893562488598d4c888d22bc4b47b\ +9de1647612bec6d48a012bf5a267f25f11b0d201bbd8aa3b6093e0a3194d\ +8675072ca3463cca2ea87aa9cfa9422bc49d8382ebb7ad18b06905017b20\ +602b066c5e7bc00abd7b727bf1e6aa03965423be0aa91093b6e445810ab1\ +3f39297c76af18b0a30a02f648c0560cd887fa033628f4cafcadaf20a8fa\ +dbe5526bc48779cf07d36ac4a00af149998531560cd8db0a02f68b80ad18\ +b069fd01dbda2e94b08b719d014bad118ff25a6e6935624085d83f2e7576\ +0b98800505ace018f6c3f2e855072cb546ccab10536bc4fc0a7158725d7e\ +0113b0b0806d0dde944d58e55f407c9d5c23e656886935627e8558365f4d\ +08d813016b48c08e720aa571a141ec9ff505ec28b946ccad10d36ac4dc0a\ +b174be9a10304d8ea604ec7dee9d48a1883daa2d604935e234a4424cda96\ +5f022ac459bca18069d3b72d6059ef229f063c0c1abd09ce58545bc0926a\ +c445488598d42089f22bc4151ef7ae18b0490501f3a0799d01cb7a553270\ +16f1f6f8f8bcd010567dc0926ac407b390697bcbbbe4a2975b21a61488a7\ +afa7d3f14d93ca03b6bbfcdfffefb8a02d015b63c02e0afde6d409af83db\ +2b8d665dbdab0f58628d18522126d68851ce5e4a1cc0e6a37eb192bc54c0\ +06e1134f4b9c6702b662c07a71d6410d6c1d64d8de9dbe9e5fa63f0dab2d\ +604935e2554885985823e6558809ff2079f99bea0396f04123016b4cc092\ +8abef40be0a270c0be8d6687f3ccdbeb1a0216348568103af8a575b6532f\ +5329cb62d410b0f7a97b55c01a10b0b338fc02388e4b06eccb8748ec2ffe\ +525bc04262320fde27d91562caf7caae27609390495c02b6a98025dd260d\ +0b0c60455e3adc4fddb01a0296542306a6e065c05cca9c87d3e75b6b0bd8\ +60b5832260f506ec6d1c7c01fc2d5e31600927c9abfa027654b2420c1afc\ +9ee4fca90feb0b58d2ff59f4caecb15eb16736021612b0497068eec72b07\ +6c9c7676bd2fd26aa9ac469c17a89b333badef8377f86e0d017b19172e12\ +9f6f07de902f046cc580259e868b8422f1fe555ec0a271de07495d00f86d\ +75bde60235e2b8f4e0f72170640e18c3570f58efba60c2faffefaf2f8f0d\ +d8633d015b2d608947272161cf73a73b8de378b193fd4126691b7654a0d5\ +525d8d38283df88df346b03fc2db442b072cf9839ea4dd496f0d3fa65c43\ +9346eea980ad16b0b43791f77ef8473b51e07cc245e6287692364e8d736f\ +736aa811e7e507bf5ede8d6cf2d3fac419c1ab076c90b88d2987a2bf7795\ +5aa52454ea1743015b2d606973d916b3bf1681eaefec4501b39d1e7fffb9\ +d4a730bfa5fef4c3a4fb9c9bf7093b653acfd7252bc4dcc1ef43fe3f4fba\ +3cf43fc6b5046cebf7383462dfe295dc0839ca1b099f8f05ac68c07a1927\ +d2d5d9d959de0dffb780cd12b2f9e3e53be985dfcc8dd81b7e8f57a99b81\ +b7252bc4dcc16f9c5ff9250c10c38f714d01eb5da75e259fded88e0793e8\ +e68df4492fa0057323a6fdff8e2a28dcef5cc08a7d53726ac06ecf373f39\ +98ecfc7d6cfb3b9328732848de88ab683a9d1ebcbb2a7933302a5b21e60d\ +7ebd8003b494b09d455c57c09293f17d2746b3d96c169de53742522eb657\ +ef0ea6d3d9d7703e10b0a2011b5512b0e4f7a1cecea2283a39bbca1d0af2\ +9fed7e2cf15ce7ba64859853237e08cbe3cd857dfacfa29a5e57f9ea4da9\ +63372b7eb19d0a58d180ad38843d4a1cc0c29c0715aae56727bc2d320c85\ +5f7596a399727d880e9eedececec3c3b88aef2df28582960bdf35207602f\ +f0863ce85d0b01ab65087b94318015e876e7a7bcf8372d647eb20fe51b24\ +83a02314aa9280155d71282561bdeb3aae73773d60694da802011b5e97f9\ +d179a1945f146f73649d2fe3d2835fd2cddbf1a603b6b55dea10dcfe16b7\ +fc523d12b0c2012b595fdc08d86fa50ac441b142b5c4dc8eb7252bc4ccc0\ +27457370bde980954cd83f0a0f613d012b7c6bba7dbd62c0ca2ca97479bf\ +e0295ae2da392a5b21669d6983228f1397ae2ad77505ac5495f88fe29f63\ +2a60c5774ae86dd87f0e13035666c997cb27f90f9352cfb6d56bc471e9c1\ +2fa5bd1fd8c71b9fd516b0ad41d185f12fff51e273440256e2aab31b3486\ +cdfbd3c480bd2c9eaff3fb4bdbb05fc3b5f37de9426754309abda0b3fb70\ +abc68005ecc2bc6310f2391e0958895333a4be384c5a85efcbee1e15ad4e\ +e6497556dee951a2453c2e5921660c7e6913407a018d8e375bf506ac5099\ +78dc2f77a578256065aefdb9eb5e5ffeba951ab0adadf1bc4869f26bf236\ +4c2abf76a6c524604e5d547802c87e40be6a0ed8d6d6243062e74fca7695\ +2f7a0256aab8ca5e94f7eb98334d3fe907a10b8e5eeef7cb8da39725a69a\ +46a55b61e3e2d1ccd9fc2f9795ba03b635d83f5fe918e49e09ff190858c9\ +bb97f41d3bff6b11b269e6a8b21b90b193cc439b756c67fd12fb6e5cb242\ +4c1dfc06652f52b3af3f597bc002ca89f92467570ef62fd3a3a9c9b1427b\ +20f1d05cbeffbec6df34af6c1b4d3396418ce7fba3fc4d382e7ec92d1a93\ +71e9c16f9edb2e4adefcc36fc15c47c03eafe1355fe5187cfe05a7a5a2d9\ +4abd84d58e0785fef9fdf03fd6df3dbc1991cbd3c39b0bd4de0fd990edcf\ +8bfade8ad9e5e9ebc9a85f6a13e24fc79351e99db79bb45674d05dc420e9\ +27ef17dffccb1f4ee9a5edf9fb2df0ffcadad0ff4a5df13ab50f3ab97d14\ +3e1d1fee86076430fe7169e64faf3b99ae8de86f8fbe1cc2d1bd157ec7bd\ +d1d75f32de1ddd2b7e64faa36f9bd0caa3fa7df3b7fb9b3e92bb7f6d48a9\ +63f9d741dc5de54c00000000000000000000000000000000000000000000\ +000000000000000000000000000000000000000000000000000000000000\ +000000000000000000000000000000000000000000000000000000000000\ +00000000000000000000000000000000000000000000008066fbffddd184\ +8d4adc88950000000049454e44ae426082"; + +std::string nftInscriptionRawHex = "\ +020000000001011771decbce2766b39d8fe66f4dc11737b3146c71f8cc6a\ +e1397384c5e508e7f10000000000ffffffff012202000000000000160014\ +e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d0340cc1e7b0b5fa18b28\ +dce702e4e8ed2e91069d682b8daa3a773774bfc7d0e6f737d403016a9016\ +b58a92592ad0b41682e6209167444eb56605532b28e9be922d3afdda1d00\ +63036f7264010109696d6167652f706e67004d080289504e470d0a1a0a00\ +00000d4948445200000360000002be0803000000f30f8d7d000000d8504c\ +54450000003070bf3070af3173bd3078b73870b73070b73575ba3075ba30\ +75b53070ba3078bb3474bb3474b73074bb3074b73376b93076b93373bc33\ +73b93073b93376bc3275ba3075ba3572ba3272ba3375bc3276b83474bb32\ +74bb3274b83276bd3276bb3474bd3474bb3276b93474bb3474b93274bb32\ +74b93476bb3276bb3375ba3275ba3373bc3273ba3277bc3375bc3375ba33\ +73bc3374ba3174b93376bb3375bc3375bb3375b93375bb3374bb3376bb34\ +75bb3375bb3375ba3374bb3276bc3276ba3375bc3275bc3375bb3275bb33\ +74bc3374bb3375bb7edf10e10000004774524e530010101f202020303030\ +30404040404050505050505f606060606f707070707f7f7f7f8080808080\ +8f8f909090909f9f9f9fa0a0afafafb0bfbfcfcfcfcfcfdfdfdfdfefefef\ +ef6a89059600001c294944415478daeddd6b7bd3d6ba2ee0d8350eb00c81\ +ec5533cbac69d6da99383334dbe510904b66129383feff3fda1750688875\ +1892255b52eefb634b1259d2a3f1ead5d0f0d61600000000000000000000\ +000000000000000000000000000000000000000000000000000000000000\ +000000000000000000000000000000000000000000000000000000000000\ +000000000000000000000000000000000000000000000000000000000000\ +004d08020000000000000084eaef3c9b4e274f87f604546e27ba8abf3a19\ +db1b50a96114dfb01031a8d0cf57f18f0e7a760a54e4b778c9c2ad1854a2\ +7f1c27586cdb3350c1edd7224eb667dfc0aa9e5fc569666ec460b5f2f0df\ +71063762b08afb8b389b3211ca978771aec82006a5fcf87039cd8587ce50\ +66f8ba8ac3cc0c6250d0cec738d8c53fed2f2820bb79a89d08ab185fc545\ +a91321b03a8ce21216ea44c8372c152f2fb1400d375fb722b6630f427abc\ +f6aee2d544220675c54bc420adb5318b2be25e0c6ec72b8a2bb4186bdac3\ +dfb5e1495cb5d903fb15bed48657711d4ec67d3b973b6eb8574fbafe1ac6\ +343cb8cba5e1248a6bb69031eeead8557bba648c3b3a74ed1c9cc56b74f5\ +4e5b913b13aebde82a5ebf9399af8da0ebe17a7ab09170fd1db2b1ee3ddd\ +bce57a3a79b77a5978383e5fb95c8c0e9eba29a33ba3d6836793d94925e3\ +d6f9686b6bf0a69ab12c3a98ec2819696baa86c39d67cfa6b37767155684\ +6fbe3e385e7d10fbdb59f4ee60fa6c6767e899340d8dd2b3835974d3c9d9\ +592db759974fbefdc98a06b1a5b09d9dfdf041a2d94429c966ed44ebea4f\ +1cde1c63aa1cc4b29fa1ed2923d9dce8b5b6787dbefbbaa9a641ccbb2f34\ +48eec2f1955587fbcbb748bbeb1ac4e203479a8de46b5d4fb4e683c4bfbf\ +7fbda6bf3f73ac59bfe16233d5e106ea446318eb77bc9eeaf0d7ac6d189c\ +ac27618f1c6ed66cbca99baf5b9b4d0802b1965bb185e3cd9a9daebb35bf\ +d18819c258af876be82d0c4207d3fa231639e2acd5a431f15a4bc42e7cdd\ +3a6bf57ec3f75eeb8e98b75ce8cc2d58f1787d89d8bcce80fde290b34ef5\ +bdf13f2f3d37a9cee762024617027679385a65ab06b5558a0246fb03369f\ +acfe5ad6e88d802160950f5e3787b1530143c07e48d7eb51955b37383c17\ +30da2caa345d35ac22bf5d6dc6048c7606ecf27054d77a18db93ea6ac527\ +0e39ed0bd87c7f54ef660ec6c7972623721703f6e970773d4b398d267301\ +e34e05ecf470cddfdd359acc2f058c3b10b04faf27a3cd2c42b8bd7b583e\ +6502c65a9598ec7b393fdc54b66ea66c7a7c2a6034dddb2283d6e9ebe978\ +bb496be76eef4e0ee7a702467b03f6e9d3fcf8f574321edd6bee87e88f46\ +e3e9e1f1fcf4536ee9e875159a12b07fddbb77af7d1fa87fefde76c6f494\ +9f1c721a12b0f64e7a1030044cc0b8038ed2cfc51702062b9aa69f8bd3d6\ +7ea86b0143c0ea93d14574c411300143c0040c5609d82b018315657cf7c3\ +1f020602b66490b174b6238e80d51730df5f848009187721601f040c56f4\ +3863259bb67ea68cef3cfbd31147c07c2604cc6782554ec63f050c340404\ +0c015ba72e764611b03604ec0f479ca604ec42c060551d9c183b113004ac\ +3e9d7cc70d011330b8ad830bc40818cdd1c125ce8e040c01ab4f27175345\ +c0040c6e3bedde57fdbc17301a23ea5ec03af891e862c09e0818a84d0802\ +a7040c1d81353af3059708d84602e6cb5558b30e3e95f5ed45344707e715\ +59391b011330ee84eebd3cd513309aa37bafff5ad8977604ec8380c18a76\ +3bb7c65917977aa4b51edfa580591691757bd8b9824ac070c7e2ae923b1f\ +b08bce05ccaa6dac5bf79e1a4d058c06b94b01b3e60d6bd7b9a9b11695a2\ +493af77247c60b382f1c6e1a14b076be9e68cd1b9aa4732fd877709511ba\ +19b05f5c31a0be5b967606ec4cc0d013d848c0ac18c0da75aeab9df160af\ +e770b36e19cf655f752d608e366bd7b5a97b0301a325016be5e4f3875e68\ +a649baf64ab3179a69cb09b9e8d8e7f1be258dba676965c0bc6f89a6408d\ +265e07a3513af6dcc8eb60344bc75e087b2b60344ac7de57f1b60acdd2b1\ +af41f7b60aad39237f71bd80fa6e5ada389dde17c8d22c1d9b4e1f7b5b85\ +46e9d63a82be1d8c86e9d674fa879d5ba998ee06ac8593f71e9b4c8f53b2\ +3ebbe6fad22cddfafa8789b9beb4a72dd0bec988befa81a6e95463db5444\ +9aa6538f66232bd3d330a75d9a2b652a224df3be4b177debfae2b6c50d25\ +7748971a6f5645a471ba3495e3b1995234cd6e87a672ec5a1591a6e9d2fc\ +d8899952b4e9bea56d53398e4ce4a071e2ee3c697e6f22078d73d69d87b3\ +9135a568d359d9b627cdd79e33d3386fbbf31d7cb1256fd019a8cd43df1f\ +4bf38c3bf3f0c873661a68b733a7a5c7603450771e841d593080e6e975a6\ +35f0be3bed1a3ae4ac2b4d08024f8f4ebdcf4cbbcecb76cd7f883d06a381\ +de76a44fffd063309a68da913efd63af5bd244e38ef4e927de06a36da555\ +9b56b238f2188c26caead3b7693e7da44b4f235d77a3bd7dad4b4f239d76\ +a28dd8d3a5a799de76a23bf0d8a28834d3a4136dc489b9f434d36e27aefd\ +ef351169a64127da88a7e6d2d350d75d6870c79a883454d4818bff634d44\ +9aeaa803fd8189053968aa7107ba1c334d449a2a6b36625bdeb93cd544a4\ +a9b22641bcf211604519ab0644edef71f87a66362c63b2d4453b3a0453af\ +33d35c93d6f7b8233d0edad9e568c7c237b11e070d76ddf29bb0c7adbf42\ +d06951cb6fc2a6e671d064472d3f4123f33868b2dd763f46ca7a0a664529\ +9ca177fbfa40f79db57a3ae2cc6366da7b13d6fcd7a916195b3f7070d9bc\ +ac09f58d6fd43f760b468b6fc21adf879bb905a3e94e5b5c232edc82d1e6\ +9bb086d7888f634fc168ba517bcfd299898834df756ba7f35d5b508ae6cb\ +78272cfed8e40dcf6a807a178c569ca74d9e8f98350ff1dc71a521321bf5\ +0d6e730c634d7ada206a679b63167b558536c85a37a0b96d8ecc014c8548\ +4b6ac4c6be76993980cd1c555a5223367408cb1cc0f4106992710b87b099\ +0a91d6d488d7ad1bc2b207b0b1634a931c659eaec3b60d605e05a35932e7\ +2336f159d8fdcc0df6c596b4a9cdd1c0373f162ddb5eeeb8cc4761f1a269\ +7d8edf622d0ebad3e668da63a5ec0e871607cdf332fb9c4d0802fd6793b6\ +b59f5d206a71d0c0212cfb9cbd68522731bb836816074df4266ecb6d58f6\ +0d98018c461ae59cb68de9d5ff1c1bc068a1a81d27eefd2b03185d1cc2e2\ +bd56e4cb00463befc21a91b0dc7c19c068aac175dcf4d161e72a3680d156\ +2ff3cedef8dd667b893fe76ee0b9018cc6eae50e61f16293cfc37ecbddbc\ +f8574791e69ae49fc18b8d4da4ed1fe76f9d5988345a947f0e6faad5717f\ +11b06ddb0e21edee73c4717cb28932f179c086e970d0fe3ec7e73271edd3\ +d5872143ab0e07dd2812e378b6de41ecf955d0568d1c3e3a5124c6f1628d\ +efaf840d5f71fc2f478fe69b849dcd6b6bd8f7f7023748079156f83df084\ +8e0fd611b19d45e0d65cba01a3157a27a109abbfd9713f0add16eb04d0b1\ +dbb02f11dba9b53afc77f086b801a33db6c3cfeb1afb89fdbdabf0cd78e3\ +a8d11ee378f3117bb228b00dffe93b68b4c87e91842d6a983cb51315d902\ +4f98e972c22aef760c0bc54bbee87ac22a8d5891de867c713712164715dd\ +8a15ea6dc81777266195743b0ac74bbeb833095b3d62e3452c5fdc1593c2\ +095bada158ac75f8c55c7f9ef61a5d178f58e96ec7fde2f18a0f1d23da6c\ +705efca42fb76a47d1d6a1f951742261c725cefbe2b762c57b1b9fe7cf7b\ +c192bbd8ea281eb1e2bd8dcfd3a3b437e8c48d588932b1d01bcf257a1b6e\ +bfe85099f8a64c0042bb1dc35999dfae3ca44326d775d589a56ebe74e7e9\ +da2056aa8a8b4d0802f7f296b2df59941abe2c904de706b1f3eaebc461b9\ +d8ce75377027f6edfb5886155787e74f1c0b3a697c5eae4eacb23a8c0fdd\ +7dd159a59e8925d689fde372d5e17d070175626e3ff179a9eaf0d2d26ca8\ +13f307b172cd8dcb7dd5212296ffc2f35ea951f058ef903b5227febe4ab3\ +63e763a99b2f3337702b96ff7511a5de4a89cfc58b3b16b179a941ac546f\ +dec40ddc8ad5466f0311ab8f07cbdc5dfb7547ccb443743bea8b97de0622\ +5657bcb40ea1744351eb1036d6edd03a84fa2236132ff8a14edcd7db8016\ +743bf43620d1fdf32a6ebeec47a8eb56ccbc0dc8aa137f77f3057546ecbd\ +275fd0bc3a517508b5d589aa43088fd8b9ea106a54e8eb22ac67034507b1\ +379e2cc3e69b1d9a1b50db2066f882fa0631c317d4368819bea0be41ccf0\ +05b50d62862fa844e233b163c3175434889d9bba0135dab79e28ace94eec\ +c4175542e5111b1f9f7efaf4e9f850730300000000000000000000000000\ +000000000000000000000000000000000000000000000000000000000000\ +00000000000000000000000000000000000068bff16c49effbfffc9fe5ff\ +698f41016fe3253f7dff9fd1f2ffb4c740c040c040c0046c15b3aa0ceedc\ +ae1b26ec8583b01f7db2f483e3b01f7cbef483ff2b608d1657e5d19ddb75\ +bdebd2bb61f9bcbd08fb9bcb7f7226600276778aab781a34f695dd7fbb35\ +ec770113b0861a25ec8628e407c7093ff82aa84d0802a05ffab173f76002\ +76a76ac49f4a55888135620d15a28009585dfefbd992b5d488c3b23b30a1\ +427cd2fa80f5968fc28e807522602b9f3d256bc471e20e0ca81167657b23\ +4d0ed860f94ffc216002965a23f64a55884159a9a3421430016b6cc0126b\ +c417e52ac4803d584b85286002d6dc808dcbd488e3943d987bf7564b8528\ +6002d6dc8095aa11a3943db8c8fb6b8b3a2a440113b0e6062c312c2fca55\ +88b9bbf0f1f24f8c054cc03a1db01235e23875174e8b5688010d150113b0\ +36072ca946bce895aa107393b95c217ed8123001eb74c012e3f24bb90a31\ +6716484d15a2800958930356b8461c67ecc3e9062a4401abd728d5ff4938\ +030ed3ff79ff6e06ac708d789a11b0680315a2806dcaa068f17327039658\ +233e2a5721660e4975558802d6ad800dd734d2f587c3dc3f55c9d933890b\ +9d2093cc80bd2854210e56fbf8b5062cfcef0b5858c03e1fd21ffdb083fb\ +3b93d9c9591cc7f3bf767fd63fcefdd5c3f403fbec60767276f56d83cfa2\ +e860b2d3af3160bdb8508d789a19b0a84885384fd8530f6e7ffc77d3a70f\ +d619b0efc7f9b3ab93e820efcf0b5860c01e67eca6fe24baba795a14dca7\ +d3c0226c78e3cfdc9a24f16eb25353c08ad588c39c5651af7c85387c363b\ +4bfea557efc6c3b5042cf9002c664f05acce80ed1c5fddbaeed611b0fe5e\ +9473f2465f4fb383e89b849321baa5648df8aa5c85985123e654883b7b27\ +d9bf4d0802f8ddd3ba03d69fa41f80abd952c2bfefe2840d5fdc3a0a0702\ +961eb061b45cd8541fb0fede55c8b384689cf5a0372e35a2f58abc7a729a\ +b781652ac47e5ebabe9eb5e33a03967b00a25b254491a33017b0d4803dbf\ +8aeb0f5860bcbe9c66412763a192b1408d38ccfd83bdc215e2f020f4c32f\ +86b505ec79c036fc388a09581501eb1f27eeac8a03b6b388eb52b68ff8aa\ +5c8598ba87532bc4e1acc8c7d9ab27603b1f8bff7901ab2060c38ff11a02\ +f63c8e371ab00235e269ee1f8c8a5588e143f75727c31a02167e006e8ca1\ +02b67ac0fa8b780d01fb2dde70c0c26bc4fc0a31a5c39f52210e3f16fe40\ +cb65e2aa01eb17b9a95d6c0b5875013b8ed710b0c2f95a541eb049e8b4c2\ +e57f781db68b8f922bc4de79bc7ac2560cd8b0607dfeb3805515b0ab780d\ +012b9caf595479c0926ac4455085f8e751d88e48ab10c72506e545afca80\ +0d0bdffffe2c6015052c5e43c08685cfaf41f5010bad119737f6c528a846\ +7c98da432c3184dd5e6760a5800d4bf4971e09587b02b6287e76d510b0c0\ +1a719250e75d8724f328f529739921ecd65254ab04ac4cbee28ba180b525\ +60c5cfaf411d01eb85b503972bc4a4ecfc115221fef9ed7f9519c27e1c24\ +5709585426dff1494fc05a12b01203581d014bfc9d3fe55788e3a4c5812f\ +422ac4ef53aa4665cef06945012bdbc03d10b076042cf96f5ebe9eec6edf\ +fb6c7bb43b3d3ebdf57375042ca9464d08027c1152216e85d488e9156272\ +043e7d9acf4f3f850e61e5039676037c7a38feb2ff479337e7a9b76102d6\ +82801d25fdfed1d2839addefc779be554fc0826ac4d3c4f3e6287f164846\ +85786b08bb3c9eecdefbf67fb677d3ceef1fc25f3e6089f5c3e5e1f6cd6d\ +1f9fa73c4e17b016042c6162c4bf927fc5e8cdf7dedbd1fc9b84b7fde7b7\ +84eeaca4d4f6022ac4901af1617640a2bf4feda56b4bcaf9fd43f84b072c\ +7102c77c10b409e3adbff77142486f1d05b3e9730336ff7665bd77afc280\ +05e7ebf3dfdc3fbffd6d7515be0fff32bf467c995ce7f5726bc4495685f8\ +3da1cb23f7d7b1f5f7c4e3d15b3d60891dc45f93f6fc49e66342ef83ad1c\ +b0cbfd7eeeaf2e13b0e54b7bf6f73dee3fa92d6083fc1a719152f8bccdab\ +114fb32ac4bf3e464abcbe7cea9c1ab16cc0923a1cff480ef971ea733c01\ +ab2060fbfd805f5d2660cb7fb2d83a4b55aee8925b233e4c3bc7f26ac484\ +56c28f091cc5e7a3ac4d4b1ac3fe583d608bb0f1eb4bc24e32ae3e02b65a\ +c0920e7e6d01fb636301cbad118fd2eabcbc1a31a142bcb5ccc5ef39cbcb\ +649768250336ce9d2272f380677c46015b2960e783ad3506eccf8d052cb7\ +465ca4b6c6a2ece754a7050be1840e4fe6e85a326051d8a14e8de32b01ab\ +22609783ad7506ecfb5cedb5072ce997dd7cdcf430fd2e649c35bee45788\ +01aeb306c172011b66dd5685ec9e85805511b0f1568d014bdac27f6e4d08\ +022a602fb377d7517a2730bb46ccaf10f31d656d5ab9808d836629670d61\ +3f09d8ea017bbf5567c0121f562e0e1e6c24603935e222e3e169668db87a\ +859878bff462c580bd2f7007967c15792160ab07ec51bd014b5d073075ad\ +d1fa0296b4317f5fd41f668dede38c64565121263daa9eae18b045ce1cfd\ +25efd38eb880950fd89f5bf506ec28e3d1f655c692beb504ec65d605e628\ +eb59712fe3141f075fb58a1dca57ab05ac97fdf03bc124ede410b0f2017b\ +5173c0f2a7929f640d66d5066c94f5b8699139bd2ea3468c8a5588fd074f\ +9f3d9bde7694b5bf4b052ce1684fb31da73ded13b0f2017b5073c002df86\ +3a3978daaf3f60893562488598d4c888d22bc4b47b9de1647612bec6d48a\ +012bf5a267f25f11b0d201bbd8aa3b6093e0a3194d8675072ca3463cca2e\ +a87aa9cfa9422bc49d8382ebb7ad18b06905017b20602b066c5e7bc00abd\ +7b727bf1e6aa03965423be0aa91093b6e445810ab13f39297c76af18b0a3\ +0a02f648c0560cd887fa033628f4cafcadaf20a8fadbe5526bc48779cf07\ +d36ac4a00af149998531560cd8db0a02f68b80ad18b069fd01dbda2e94b0\ +8b719d014bad118ff25a6e6935624085d83f2e75760b98800505ace018f6\ +c3f2e855072cb546ccab10536bc4fc0a7158725d7e0113b0b0806d0dde94\ +4d58e55f407c9d5c23e656886935627e8558365f4d08d813016b48c08e72\ +0aa571a141ec9ff505ec284d0802b946ccad10d36ac4dc0ab174be9a1030\ +4d8ea604ec7dee9d48a1883daa2d604935e234a4424cda965f022ac459bc\ +a18069d3b72d6059ef229f063c0c1abd09ce58545bc0926ac445488598d4\ +2089f22bc4151ef7ae18b0490501f3a0799d01cb7a55327016f1f6f8f8bc\ +d010567dc0926ac407b390697bcbbbe4a2975b21a61488a7afa7d3f14d93\ +ca03b6bbfcdfffefb8a02d015b63c02e0afde6d409af83db2b8d665dbdab\ +0f58628d18522126d68851ce5e4a1cc0e6a37eb192bc54c006e1134f4b9c\ +6702b662c07a71d6410d6c1d64d8de9dbe9e5fa63f0dab2d604935e25548\ +85985823e6558809ff2079f99bea0396f04123016b4cc0928abef40be0a2\ +70c0be8d6687f3ccdbeb1a0216348568103af8a575b6532f5329cb62d410\ +b0f7a97b55c01a10b0b338fc02388e4b06eccb8748ec2ffe525bc0426232\ +0fde27d91562caf7caae27609390495c02b6a98025dd260d0b0c60455e3a\ +dc4fddb01a0296542306a6e065c05cca9c87d3e75b6b0bd860b5832260f5\ +06ec6d1c7c01fc2d5e31600927c9abfa027654b2420c1afc9ee4fca90feb\ +0b58d2ff59f4caecb15eb16736021612b0497068eec72b076c9c7676bd2f\ +d26aa9ac469c17a89b333badef8377f86e0d017b19172e129f6f07de902f\ +046cc580259e868b8422f1fe555ec0a271de07495d00f86d75bde60235e2\ +b8f4e0f72170640e18c3570f58efba60c2faffefaf2f8f0d4d0802d8633d\ +015b2d608947272161cf73a73b8de378b193fd4126691b7654a0d5525d8d\ +38283df88df346b03fc2db442b072cf9839ea4dd496f0d3fa65c439346ee\ +a980ad16b0b43791f77ef8473b51e07cc245e6287692364e8d736f736aa8\ +11e7e507bf5ede8d6cf2d3fac419c1ab076c90b88d2987a2bf77955aa524\ +54ea1743015b2d606973d916b3bf1681eaefec4501b39d1e7fffb9d4a730\ +bfa5fef4c3a4fb9c9bf7093b653acfd7252bc4dcc1ef43fe3f4fba3cf43f\ +c6b5046cebf7383462dfe295dc0839ca1b099f8f05ac68c07a1927d2d5d9\ +d959de0dffb780cd12b2f9e3e53be985dfcc8dd81b7e8f57a99b81b7252b\ +c4dcc16f9c5ff9250c10c38f714d01eb5da75e259fded88e0793e8e68df4\ +492fa0057323a6fdff8e2a28dcef5cc08a7d53726ac06ecf373f3998ecfc\ +7d6cfb3b9328732848de88ab683a9d1ebcbb2a7933302a5b21e60d7ebd80\ +03b494b09d455c57c09293f17d2746b3d96c169de53742522eb657ef0ea6\ +d3d9d7703e10b0a2011b5512b0e4f7a1cecea2283a39bbca1d0af29fed7e\ +2cf15ce7ba64859853237e08cbe3cd857dfacfa29a5e57f9ea4da963372b\ +7eb19d0a58d180ad38843d4a1cc0c29c0715aae56727bc2d320c855f7596\ +a399727d880e9eedececec3c3b88aef2df28582960bdf35207602ff0863c\ +e85d0b01ab65087b94318015e876e7a7bcf8372d647eb20fe51b2483a023\ +14aa9280155d714d0802282561bdeb3aae73773d60694da802011b5e97f9\ +d179a1945f146f73649d2fe3d2835fd2cddbf1a603b6b55dea10dcfe16b7\ +fc523d12b0c2012b595fdc08d86fa50ac441b142b5c4dc8eb7252bc4ccc0\ +27457370bde980954cd83f0a0f613d012b7c6bba7dbd62c0ca2ca97479bf\ +e0295ae2da392a5b21669d6983228f1397ae2ad77505ac5495f88fe29f63\ +2a60c5774ae86dd87f0e13035666c997cb27f90f9352cfb6d56bc471e9c1\ +2fa5bd1fd8c71b9fd516b0ad41d185f12fff51e273440256e2aab31b3486\ +cdfbd3c480bd2c9eaff3fb4bdbb05fc3b5f37de9426754309abda0b3fb70\ +abc68005ecc2bc6310f2391e0958895333a4be384c5a85efcbee1e15ad4e\ +e6497556dee951a2453c2e5921660c7e6913407a018d8e375bf506ac5099\ +78dc2f77a578256065aefdb9eb5e5ffeba951ab0adadf1bc4869f26bf236\ +4c2abf76a6c524604e5d547802c87e40be6a0ed8d6d6243062e74fca7695\ +2f7a0256aab8ca5e94f7eb98334d3fe907a10b8e5eeef7cb8da39725a69a\ +46a55b61e3e2d1ccd9fc2f9795ba03b635d83f5fe918e49e09ff190858c9\ +bb97f41d3bff6b11b269e6a8b21b90b193cc439b756c67fd12fb6e5cb242\ +4c1dfc06652f52b3af3f597bc002ca89f92467570ef62fd3a3a9c9b1427b\ +20f1d05cbeffbec6df34af6c1b4d3396418ce7fba3fc4d382e7ec92d1a93\ +71e9c16f9edb2e4adefcc36fc15c47c03eafe1354d29015fe5187cfe05a7\ +a5a2d94abd84d58e0785fef9fdf03fd6df3dbc1991cbd3c39b0bd4de0fd9\ +90edcf8bfade8ad9e5e9ebc9a85f6a13e24fc79351e99db79bb45674d05d\ +c420e927ef17dffccb1f4ee9a5edf9fb2df0ffcadad0ff4a5df13ab50f3a\ +b97d143e1d1fee86076430fe7169e64faf3b99ae8de86f8fbe1cc2d1bd15\ +7ec7bdd1d75f32de1ddd2b7e64faa36f9bd0caa3fa7df3b7fb9b3e92bb7f\ +6d48a963f9d741dc5de54c00000000000000000000000000000000000000\ +000000000000000000000000000000000000000000000000000000000000\ +000000000000000000000000000000000000000000000000000000000000\ +00000000000000000000000000000000000000000000000000008066fbff\ +ddd1848d4adc88950000000049454e44ae4260826821c00f209b6ada5edb\ +42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"; diff --git a/tests/Bitcoin/BitcoinScriptTests.cpp b/tests/chains/Bitcoin/BitcoinScriptTests.cpp similarity index 84% rename from tests/Bitcoin/BitcoinScriptTests.cpp rename to tests/chains/Bitcoin/BitcoinScriptTests.cpp index c9a7adc36df..f4a6ed9fbbe 100644 --- a/tests/Bitcoin/BitcoinScriptTests.cpp +++ b/tests/chains/Bitcoin/BitcoinScriptTests.cpp @@ -1,18 +1,16 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Bitcoin/Script.h" #include "Bitcoin/SignatureBuilder.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "HexCoding.h" #include using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin::tests { const Script PayToScriptHash(parse_hex("a914" "4733f37cf4db86fbc2efed2500b4f4e49f312023" "87")); const Script PayToWitnessScriptHash(parse_hex("0020" "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db")); @@ -61,7 +59,7 @@ TEST(BitcoinScript, PayToScriptHash) { EXPECT_EQ(hex(script.bytes), hex(PayToScriptHash.bytes)); EXPECT_EQ(PayToScriptHash.isPayToScriptHash(), true); - EXPECT_EQ(PayToScriptHash.bytes.size(), 23); + EXPECT_EQ(PayToScriptHash.bytes.size(), 23ul); EXPECT_EQ(PayToWitnessScriptHash.isPayToScriptHash(), false); EXPECT_EQ(PayToWitnessPublicKeyHash.isPayToScriptHash(), false); @@ -80,12 +78,28 @@ TEST(BitcoinScript, PayToScriptHash) { EXPECT_EQ(PayToPublicKeyHash.matchPayToScriptHash(res), false); } +TEST(BitcoinScript, PayToScriptHashReplay) { + const Script script = Script::buildPayToScriptHashReplay( + parse_hex("2cda89c2f217517108d55ffdf3d90e111d450be9"), + parse_hex("f1bf729948789aef5801fd91a3bf4e014ffcf4fcbd4f685de2b6990100000000"), 1190791); + EXPECT_EQ(hex(script.bytes), "a9142cda89c2f217517108d55ffdf3d90e111d450be98720f1bf729948789aef5801fd91a3bf4e014ffcf4fcbd4f685de2b699010000000003872b12b4"); + + const Script script2 = Script::lockScriptForAddress( + "zsiZvKaCW9bSVt7Fy6C79CE5rFR6SEiJxAw", TWCoinTypeZen, + parse_hex("f1bf729948789aef5801fd91a3bf4e014ffcf4fcbd4f685de2b6990100000000"), 1190791); + EXPECT_EQ(script.bytes, script2.bytes); + + Data res; + EXPECT_EQ(script.matchPayToScriptHashReplay(res), true); + EXPECT_EQ(hex(res), "2cda89c2f217517108d55ffdf3d90e111d450be9"); +} + TEST(BitcoinScript, PayToWitnessScriptHash) { const Script script = Script::buildPayToWitnessScriptHash(parse_hex("ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db")); EXPECT_EQ(hex(script.bytes), hex(PayToWitnessScriptHash.bytes)); EXPECT_EQ(PayToWitnessScriptHash.isPayToWitnessScriptHash(), true); - EXPECT_EQ(PayToWitnessScriptHash.bytes.size(), 34); + EXPECT_EQ(PayToWitnessScriptHash.bytes.size(), 34ul); EXPECT_EQ(PayToScriptHash.isPayToWitnessScriptHash(), false); EXPECT_EQ(PayToWitnessPublicKeyHash.isPayToWitnessScriptHash(), false); @@ -109,7 +123,7 @@ TEST(BitcoinScript, PayToWitnessPublicKeyHash) { EXPECT_EQ(hex(script.bytes), hex(PayToWitnessPublicKeyHash.bytes)); EXPECT_EQ(PayToWitnessPublicKeyHash.isPayToWitnessPublicKeyHash(), true); - EXPECT_EQ(PayToWitnessPublicKeyHash.bytes.size(), 22); + EXPECT_EQ(PayToWitnessPublicKeyHash.bytes.size(), 22ul); EXPECT_EQ(PayToScriptHash.isPayToWitnessPublicKeyHash(), false); EXPECT_EQ(PayToWitnessScriptHash.isPayToWitnessPublicKeyHash(), false); @@ -143,6 +157,11 @@ TEST(BitcoinScript, EncodeNumber) { EXPECT_EQ(Script::encodeNumber(1), OP_1); EXPECT_EQ(Script::encodeNumber(3), OP_3); EXPECT_EQ(Script::encodeNumber(9), OP_9); + + EXPECT_EQ(hex(Script::encodeNumber(int64_t(0))), "00"); + EXPECT_EQ(hex(Script::encodeNumber(int64_t(1))), "51"); + EXPECT_EQ(hex(Script::encodeNumber(int64_t(10000000))), "80969800"); + EXPECT_EQ(hex(Script::encodeNumber(int64_t(-10000000))), "80969880"); } TEST(BitcoinScript, DecodeNumber) { @@ -160,21 +179,21 @@ TEST(BitcoinScript, GetScriptOp) { { size_t index = 0; uint8_t opcode; Data operand; EXPECT_EQ(Script(parse_hex("4f")).getScriptOp(index, opcode, operand), true); - EXPECT_EQ(index, 1); + EXPECT_EQ(index, 1ul); EXPECT_EQ(opcode, 0x4f); EXPECT_EQ(hex(operand), ""); } { size_t index = 0; uint8_t opcode; Data operand; EXPECT_EQ(Script(parse_hex("05" "0102030405")).getScriptOp(index, opcode, operand), true); - EXPECT_EQ(index, 6); + EXPECT_EQ(index, 6ul); EXPECT_EQ(opcode, 0x05); EXPECT_EQ(hex(operand), "0102030405"); } { // OP_PUSHDATA1 size_t index = 0; uint8_t opcode; Data operand; EXPECT_EQ(Script(parse_hex("4c" "05" "0102030405")).getScriptOp(index, opcode, operand), true); - EXPECT_EQ(index, 7); + EXPECT_EQ(index, 7ul); EXPECT_EQ(opcode, 0x4c); EXPECT_EQ(hex(operand), "0102030405"); } @@ -189,7 +208,7 @@ TEST(BitcoinScript, GetScriptOp) { { // OP_PUSHDATA2 size_t index = 0; uint8_t opcode; Data operand; EXPECT_EQ(Script(parse_hex("4d" "0500" "0102030405")).getScriptOp(index, opcode, operand), true); - EXPECT_EQ(index, 8); + EXPECT_EQ(index, 8ul); EXPECT_EQ(opcode, 0x4d); EXPECT_EQ(hex(operand), "0102030405"); } @@ -204,7 +223,7 @@ TEST(BitcoinScript, GetScriptOp) { { // OP_PUSHDATA4 size_t index = 0; uint8_t opcode; Data operand; EXPECT_EQ(Script(parse_hex("4e" "05000000" "0102030405")).getScriptOp(index, opcode, operand), true); - EXPECT_EQ(index, 10); + EXPECT_EQ(index, 10ul); EXPECT_EQ(opcode, 0x4e); EXPECT_EQ(hex(operand), "0102030405"); } @@ -239,7 +258,7 @@ TEST(BitcoinScript, MatchMultiSig) { // valid one key EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "51" "ae")).matchMultisig(keys, required), true); EXPECT_EQ(required, 1); - ASSERT_EQ(keys.size(), 1); + ASSERT_EQ(keys.size(), 1ul); EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); EXPECT_EQ(Script(parse_hex("51" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "21" "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" "51" "ae")).matchMultisig(keys, required), false); @@ -248,7 +267,7 @@ TEST(BitcoinScript, MatchMultiSig) { // valid two keys EXPECT_EQ(Script(parse_hex("52" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "21" "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" "52" "ae")).matchMultisig(keys, required), true); EXPECT_EQ(required, 2); - ASSERT_EQ(keys.size(), 2); + ASSERT_EQ(keys.size(), 2ul); EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); EXPECT_EQ(hex(keys[1]), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); @@ -261,7 +280,7 @@ TEST(BitcoinScript, MatchMultiSig) { // valid one key, OP_PUSHDATA1 EXPECT_EQ(Script(parse_hex("514c" "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "51" "ae")).matchMultisig(keys, required), true); EXPECT_EQ(required, 1); - ASSERT_EQ(keys.size(), 1); + ASSERT_EQ(keys.size(), 1ul); EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); // OP_PUSHDATA2 @@ -273,7 +292,7 @@ TEST(BitcoinScript, MatchMultiSig) { // valid one key, OP_PUSHDATA2 EXPECT_EQ(Script(parse_hex("514d" "2100" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "51" "ae")).matchMultisig(keys, required), true); EXPECT_EQ(required, 1); - ASSERT_EQ(keys.size(), 1); + ASSERT_EQ(keys.size(), 1ul); EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); // OP_PUSHDATA4 @@ -286,7 +305,7 @@ TEST(BitcoinScript, MatchMultiSig) { // valid one key, OP_PUSHDATA2 EXPECT_EQ(Script(parse_hex("514e" "21000000" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "51" "ae")).matchMultisig(keys, required), true); EXPECT_EQ(required, 1); - ASSERT_EQ(keys.size(), 1); + ASSERT_EQ(keys.size(), 1ul); EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); // valid three keys, mixed @@ -296,7 +315,7 @@ TEST(BitcoinScript, MatchMultiSig) { "4e" "21000000" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "53" "ae")).matchMultisig(keys, required), true); EXPECT_EQ(required, 3); - ASSERT_EQ(keys.size(), 3); + ASSERT_EQ(keys.size(), 3ul); EXPECT_EQ(hex(keys[0]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); EXPECT_EQ(hex(keys[1]), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); EXPECT_EQ(hex(keys[2]), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); @@ -306,19 +325,54 @@ TEST(BitcoinScript, OpReturn) { { Data data = parse_hex("00010203"); Script script = Script::buildOpReturnScript(data); + EXPECT_EQ(script.bytes.size(), 2 + data.size()); EXPECT_EQ(hex(script.bytes), "6a0400010203"); } { Data data = parse_hex("535741503a54484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a"); Script script = Script::buildOpReturnScript(data); + EXPECT_EQ(script.bytes.size(), 2 + data.size()); EXPECT_EQ(hex(script.bytes), "6a3b535741503a54484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a"); } { - // too long, truncated - Data data = Data(70); + Data data = Data(69); + data.push_back(0xab); + Script script = Script::buildOpReturnScript(data); + EXPECT_EQ(script.bytes.size(), 2 + data.size()); + EXPECT_EQ(hex(script.bytes), "6a46000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ab"); + } + { + Data data = Data(74); + data.push_back(0xab); Script script = Script::buildOpReturnScript(data); - EXPECT_EQ(hex(script.bytes), "6a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + EXPECT_EQ(script.bytes.size(), 2 + data.size()); + EXPECT_EQ(hex(script.bytes), + "6a4b" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ab"); } + { + // >75 bytes, with OP_PUSHDATA1 + Data data = Data(79); + data.push_back(0xab); + Script script = Script::buildOpReturnScript(data); + EXPECT_EQ(script.bytes.size(), 3 + data.size()); + EXPECT_EQ(hex(script.bytes), + "6a4c50" + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ab"); + } + { + // >80 bytes, fails + EXPECT_EQ(hex(Script::buildOpReturnScript(Data(81)).bytes), ""); + EXPECT_EQ(hex(Script::buildOpReturnScript(Data(255)).bytes), ""); + } +} + +TEST(BitcoinScript, OpReturnTooLong) { + // too long, truncated + Data data = Data(89); + data.push_back(0xab); + Script script = Script::buildOpReturnScript(data); + EXPECT_EQ(hex(script.bytes), ""); } TEST(BitcoinTransactionSigner, PushAllEmpty) { @@ -365,4 +419,5 @@ TEST(BitcoinTransactionSigner, PushAllEmpty) { Data res = SignatureBuilder::pushAll(input); EXPECT_EQ(hex(res), hex(expected)); } -} \ No newline at end of file +} +} // namespace TW::Bitcoin::tests diff --git a/tests/chains/Bitcoin/FeeCalculatorTests.cpp b/tests/chains/Bitcoin/FeeCalculatorTests.cpp new file mode 100644 index 00000000000..aa2f973c4bd --- /dev/null +++ b/tests/chains/Bitcoin/FeeCalculatorTests.cpp @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bitcoin/FeeCalculator.h" + +#include + +namespace TW::Bitcoin { + +TEST(BitcoinFeeCalculator, ConstantFeeCalculator) { + const auto feeCalculator = ConstantFeeCalculator(33); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 33); + EXPECT_EQ(feeCalculator.calculate(0, 2, 1), 33); + EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 33); + EXPECT_EQ(feeCalculator.calculateSingleInput(10), 0); +} + +TEST(BitcoinFeeCalculator, LinearFeeCalculator) { + const auto feeCalculator = LinearFeeCalculator(10, 20, 50); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 100); + EXPECT_EQ(feeCalculator.calculate(1, 1, 1), 80); + EXPECT_EQ(feeCalculator.calculate(0, 2, 1), 90); + EXPECT_EQ(feeCalculator.calculate(1, 0, 1), 60); + EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 50); + EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 1000); + EXPECT_EQ(feeCalculator.calculateSingleInput(10), 100); +} + +TEST(BitcoinFeeCalculator, BitcoinCalculate) { + const FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 174); + EXPECT_EQ(feeCalculator.calculate(1, 1, 1), 143); + EXPECT_EQ(feeCalculator.calculate(0, 2, 1), 72); + EXPECT_EQ(feeCalculator.calculate(1, 0, 1), 112); + EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 10); + EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 1740); +} + +TEST(BitcoinFeeCalculator, SegwitCalculate) { + const FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 174); + EXPECT_EQ(feeCalculator.calculate(1, 1, 1), 143); + EXPECT_EQ(feeCalculator.calculate(0, 2, 1), 72); + EXPECT_EQ(feeCalculator.calculate(1, 0, 1), 112); + EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 10); + EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 1740); +} + +TEST(BitcoinFeeCalculator, BitcoinCalculateNoDustFilter) { + const FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin, true); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 174); + EXPECT_EQ(feeCalculator.calculate(1, 1, 1), 143); + EXPECT_EQ(feeCalculator.calculate(0, 2, 1), 72); + EXPECT_EQ(feeCalculator.calculate(1, 0, 1), 112); + EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 10); + EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 1740); + EXPECT_EQ(feeCalculator.calculateSingleInput(1), 0); +} + +TEST(BitcoinFeeCalculator, DefaultCalculate) { + DefaultFeeCalculator defaultFeeCalculator; + EXPECT_EQ(defaultFeeCalculator.calculate(1, 2, 1), 226); + EXPECT_EQ(defaultFeeCalculator.calculate(1, 1, 1), 192); + EXPECT_EQ(defaultFeeCalculator.calculate(0, 2, 1), 78); + EXPECT_EQ(defaultFeeCalculator.calculate(1, 0, 1), 158); + EXPECT_EQ(defaultFeeCalculator.calculate(0, 0, 1), 10); + EXPECT_EQ(defaultFeeCalculator.calculate(1, 2, 10), 2260); + EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(1), 148); + EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(2), 296); + EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(10), 1480); +} + +TEST(BitcoinFeeCalculator, DefaultCalculateNoDustFilter) { + DefaultFeeCalculator defaultFeeCalculator(true); + EXPECT_EQ(defaultFeeCalculator.calculate(1, 2, 1), 226); + EXPECT_EQ(defaultFeeCalculator.calculate(1, 1, 1), 192); + EXPECT_EQ(defaultFeeCalculator.calculate(0, 2, 1), 78); + EXPECT_EQ(defaultFeeCalculator.calculate(1, 0, 1), 158); + EXPECT_EQ(defaultFeeCalculator.calculate(0, 0, 1), 10); + EXPECT_EQ(defaultFeeCalculator.calculate(1, 2, 10), 2260); + EXPECT_EQ(defaultFeeCalculator.calculateSingleInput(1), 0); +} + +TEST(BitcoinFeeCalculator, DecredCalculate) { + const FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeDecred); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 254); + EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 12); + EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 2540); + EXPECT_EQ(feeCalculator.calculateSingleInput(1), 166); +} + +TEST(BitcoinFeeCalculator, DecredCalculateNoDustFilter) { + const FeeCalculator& feeCalculator = getFeeCalculator(TWCoinTypeDecred, true); + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 254); + EXPECT_EQ(feeCalculator.calculate(0, 0, 1), 12); + EXPECT_EQ(feeCalculator.calculate(1, 2, 10), 2540); + EXPECT_EQ(feeCalculator.calculateSingleInput(1), 0); +} + +} // namespace TW::Bitcoin diff --git a/tests/Bitcoin/InputSelectorTests.cpp b/tests/chains/Bitcoin/InputSelectorTests.cpp similarity index 81% rename from tests/Bitcoin/InputSelectorTests.cpp rename to tests/chains/Bitcoin/InputSelectorTests.cpp index 4fb8355ad0c..eef037af45a 100644 --- a/tests/Bitcoin/InputSelectorTests.cpp +++ b/tests/chains/Bitcoin/InputSelectorTests.cpp @@ -1,23 +1,15 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include "TxComparisonHelper.h" -#include "Bitcoin/OutPoint.h" -#include "Bitcoin/Script.h" #include "Bitcoin/InputSelector.h" -#include "proto/Bitcoin.pb.h" #include -#include - -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { TEST(BitcoinInputSelector, SelectUnspents1) { auto utxos = buildTestUTXOs({4000, 2000, 6000, 1000, 11000, 12000}); @@ -133,7 +125,9 @@ TEST(BitcoinInputSelector, SelectOneInsufficientEqual) { auto selector = InputSelector(utxos); auto selected = selector.select(100'000, 1); - EXPECT_TRUE(verifySelectedUTXOs(selected, {})); + // `InputSelector` returns the entire list of UTXOs even if they are not enough. + // That's because `InputSelector` has a rough segwit fee estimation algorithm, and the UTXOs can actually be enough. + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); } TEST(BitcoinInputSelector, SelectOneInsufficientHigher) { @@ -142,14 +136,28 @@ TEST(BitcoinInputSelector, SelectOneInsufficientHigher) { auto selector = InputSelector(utxos); auto selected = selector.select(99'900, 1); - EXPECT_TRUE(verifySelectedUTXOs(selected, {})); + // `InputSelector` returns the entire list of UTXOs even if they are not enough. + // That's because `InputSelector` has a rough segwit fee estimation algorithm, and the UTXOs can actually be enough. + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); +} + +TEST(BitcoinInputSelector, SelectOneInsufficientHigherFilterDust) { + auto utxos = buildTestUTXOs({22, 100'000, 40}); + + auto selector = InputSelector(utxos); + auto selected = selector.select(99'900, 1); + + // `InputSelector` returns the entire list of UTXOs even if they are not enough. + // That's because `InputSelector` has a rough segwit fee estimation algorithm, and the UTXOs can actually be enough. + // However, the list of result UTXOs does not include dust inputs. + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); } TEST(BitcoinInputSelector, SelectOneFitsExactly) { auto utxos = buildTestUTXOs({100'000}); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = InputSelector(utxos, feeCalculator); + auto selector = InputSelector(utxos); auto expectedFee = 174; auto selected = selector.select(100'000 - expectedFee, 1); @@ -158,10 +166,11 @@ TEST(BitcoinInputSelector, SelectOneFitsExactly) { EXPECT_EQ(feeCalculator.calculate(1, 2, 1), expectedFee); EXPECT_EQ(feeCalculator.calculate(1, 1, 1), 143); - // 1 sat more and does not fit any more + // 1 sat more and does not fit any more. + // However, `InputSelector` returns the entire list of UTXOs even if they are not enough. + // That's because `InputSelector` has a rough segwit fee estimation algorithm, and the UTXOs can actually be enough. selected = selector.select(100'000 - expectedFee + 1, 1); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); } TEST(BitcoinInputSelector, SelectOneFitsExactlyHighfee) { @@ -169,7 +178,7 @@ TEST(BitcoinInputSelector, SelectOneFitsExactlyHighfee) { const auto byteFee = 10; auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = InputSelector(utxos, feeCalculator); + auto selector = InputSelector(utxos); auto expectedFee = 1740; auto selected = selector.select(100'000 - expectedFee, byteFee); @@ -178,22 +187,23 @@ TEST(BitcoinInputSelector, SelectOneFitsExactlyHighfee) { EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), expectedFee); EXPECT_EQ(feeCalculator.calculate(1, 1, byteFee), 1430); - // 1 sat more and does not fit any more + // 1 sat more and does not fit any more. + // However, `InputSelector` returns the entire list of UTXOs even if they are not enough. + // That's because `InputSelector` has a rough segwit fee estimation algorithm, and the UTXOs can actually be enough. selected = selector.select(100'000 - expectedFee + 1, byteFee); - - EXPECT_TRUE(verifySelectedUTXOs(selected, {})); + EXPECT_TRUE(verifySelectedUTXOs(selected, {100'000})); } TEST(BitcoinInputSelector, SelectThreeNoDust) { auto utxos = buildTestUTXOs({100'000, 70'000, 75'000}); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = InputSelector(utxos, feeCalculator); + auto selector = InputSelector(utxos); auto selected = selector.select(100'000 - 174 - 10, 1); // 100'000 would fit with dust; instead two UTXOs are selected not to leave dust EXPECT_TRUE(verifySelectedUTXOs(selected, {75'000, 100'000})); - + EXPECT_EQ(feeCalculator.calculate(1, 2, 1), 174); const auto dustLimit = 102; @@ -282,7 +292,7 @@ TEST(BitcoinInputSelector, SelectTenThreeExact) { auto utxos = buildTestUTXOs({1'000, 2'000, 100'000, 3'000, 4'000, 5'000, 125'000, 6'000, 150'000, 7'000}); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = InputSelector(utxos, feeCalculator); + auto selector = InputSelector(utxos); const auto dustLimit = 102; auto selected = selector.select(375'000 - 376 - dustLimit, 1); @@ -300,7 +310,7 @@ TEST(BitcoinInputSelector, SelectMaxAmountOne) { auto utxos = buildTestUTXOs({10189534}); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = InputSelector(utxos, feeCalculator); + auto selector = InputSelector(utxos); auto selected = selector.selectMaxAmount(1); EXPECT_TRUE(verifySelectedUTXOs(selected, {10189534})); @@ -312,7 +322,7 @@ TEST(BitcoinInputSelector, SelectAllAvail) { auto utxos = buildTestUTXOs({10189534}); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = InputSelector(utxos, feeCalculator); + auto selector = InputSelector(utxos); auto selected = selector.select(10189534 - 226, 1); EXPECT_TRUE(verifySelectedUTXOs(selected, {10189534})); @@ -324,7 +334,7 @@ TEST(BitcoinInputSelector, SelectMaxAmount5of5) { auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = InputSelector(utxos, feeCalculator); + auto selector = InputSelector(utxos); auto byteFee = 1; auto selected = selector.selectMaxAmount(byteFee); @@ -338,7 +348,7 @@ TEST(BitcoinInputSelector, SelectMaxAmount4of5) { auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = InputSelector(utxos, feeCalculator); + auto selector = InputSelector(utxos); auto byteFee = 4; auto selected = selector.selectMaxAmount(byteFee); @@ -352,7 +362,7 @@ TEST(BitcoinInputSelector, SelectMaxAmount1of5) { auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = InputSelector(utxos, feeCalculator); + auto selector = InputSelector(utxos); auto byteFee = 8; auto selected = selector.selectMaxAmount(byteFee); @@ -366,7 +376,7 @@ TEST(BitcoinInputSelector, SelectMaxAmountNone) { auto utxos = buildTestUTXOs({400, 500, 600, 800, 1000}); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = InputSelector(utxos, feeCalculator); + auto selector = InputSelector(utxos); auto byteFee = 10; auto selected = selector.selectMaxAmount(byteFee); @@ -378,8 +388,7 @@ TEST(BitcoinInputSelector, SelectMaxAmountNone) { TEST(BitcoinInputSelector, SelectMaxAmountNoUTXOs) { auto utxos = buildTestUTXOs({}); - auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); - auto selector = InputSelector(utxos, feeCalculator); + auto selector = InputSelector(utxos); auto selected = selector.selectMaxAmount(1); EXPECT_TRUE(verifySelectedUTXOs(selected, {})); @@ -388,7 +397,7 @@ TEST(BitcoinInputSelector, SelectMaxAmountNoUTXOs) { TEST(BitcoinInputSelector, SelectZcashUnspents) { auto utxos = buildTestUTXOs({100000, 2592, 73774}); - auto selector = InputSelector(utxos, getFeeCalculator(TWCoinTypeZcash)); + auto selector = InputSelector(utxos, getFeeCalculator(TWCoinTypeZcash), std::make_shared(TWCoinTypeZcash)); auto selected = selector.select(10000, 1); EXPECT_TRUE(verifySelectedUTXOs(selected, {73774})); @@ -397,7 +406,7 @@ TEST(BitcoinInputSelector, SelectZcashUnspents) { TEST(BitcoinInputSelector, SelectGroestlUnspents) { auto utxos = buildTestUTXOs({499971976}); - auto selector = InputSelector(utxos, getFeeCalculator(TWCoinTypeZcash)); + auto selector = InputSelector(utxos, getFeeCalculator(TWCoinTypeZcash), std::make_shared(TWCoinTypeZcash)); auto selected = selector.select(499951976, 1, 1); EXPECT_TRUE(verifySelectedUTXOs(selected, {499971976})); @@ -406,7 +415,7 @@ TEST(BitcoinInputSelector, SelectGroestlUnspents) { TEST(BitcoinInputSelector, SelectZcashMaxAmount) { auto utxos = buildTestUTXOs({100000, 2592, 73774}); - auto selector = InputSelector(utxos, getFeeCalculator(TWCoinTypeZcash)); + auto selector = InputSelector(utxos, getFeeCalculator(TWCoinTypeZcash), std::make_shared(TWCoinTypeZcash)); auto selected = selector.selectMaxAmount(1); EXPECT_TRUE(verifySelectedUTXOs(selected, {100000, 2592, 73774})); @@ -415,10 +424,12 @@ TEST(BitcoinInputSelector, SelectZcashMaxAmount) { TEST(BitcoinInputSelector, SelectZcashMaxUnspents2) { auto utxos = buildTestUTXOs({100000, 2592, 73774}); - auto selector = InputSelector(utxos, getFeeCalculator(TWCoinTypeZcash)); + auto selector = InputSelector(utxos, getFeeCalculator(TWCoinTypeZcash), std::make_shared(TWCoinTypeZcash)); auto selected = selector.select(176366 - 6, 1); - EXPECT_TRUE(verifySelectedUTXOs(selected, {})); + // `InputSelector` returns the entire list of UTXOs even if they are not enough. + // That's because `InputSelector` has a rough segwit fee estimation algorithm, and the UTXOs can actually be enough. + EXPECT_TRUE(verifySelectedUTXOs(selected, {2592, 73774, 100000})); } TEST(BitcoinInputSelector, ManyUtxos_900) { @@ -432,7 +443,7 @@ TEST(BitcoinInputSelector, ManyUtxos_900) { valueSum += val; } const uint64_t requestedAmount = valueSum / 8; - EXPECT_EQ(requestedAmount, 5'068'125); + EXPECT_EQ(requestedAmount, 5'068'125ul); auto utxos = buildTestUTXOs(values); auto selector = InputSelector(utxos); @@ -446,8 +457,8 @@ TEST(BitcoinInputSelector, ManyUtxos_900) { subset.push_back(val); subsetSum += val; } - EXPECT_EQ(subset.size(), 59); - EXPECT_EQ(subsetSum, 5'138'900); + EXPECT_EQ(subset.size(), 59ul); + EXPECT_EQ(subsetSum, 5'138'900ul); EXPECT_TRUE(verifySelectedUTXOs(selected, subset)); } @@ -462,7 +473,7 @@ TEST(BitcoinInputSelector, ManyUtxos_5000_simple) { valueSum += val; } const uint64_t requestedAmount = valueSum / 20; - EXPECT_EQ(requestedAmount, 62'512'500); + EXPECT_EQ(requestedAmount, 62'512'500ul); auto utxos = buildTestUTXOs(values); auto selector = InputSelector(utxos); @@ -471,13 +482,13 @@ TEST(BitcoinInputSelector, ManyUtxos_5000_simple) { // expected result: 1205 utxos, with the smaller amounts (except the very small dust ones) std::vector subset; uint64_t subsetSum = 0; - for (int i = 10; i < 1205+10; ++i) { + for (int i = 10; i < 1205 + 10; ++i) { const uint64_t val = (i + 1) * 100; subset.push_back(val); subsetSum += val; } - EXPECT_EQ(subset.size(), 1205); - EXPECT_EQ(subsetSum, 73'866'500); + EXPECT_EQ(subset.size(), 1205ul); + EXPECT_EQ(subsetSum, 73'866'500ul); EXPECT_TRUE(verifySelectedUTXOs(selected, subset)); } @@ -497,12 +508,14 @@ TEST(BitcoinInputSelector, ManyUtxos_MaxAmount_5000) { // expected result: 4990 utxos (none of which is dust) std::vector subset; uint64_t subsetSum = 0; - for (int i = 10; i < 4990+10; ++i) { + for (int i = 10; i < 4990 + 10; ++i) { const uint64_t val = (i + 1) * 100; subset.push_back(val); subsetSum += val; } - EXPECT_EQ(subset.size(), 4990); - EXPECT_EQ(subsetSum, 1'250'244'500); + EXPECT_EQ(subset.size(), 4990ul); + EXPECT_EQ(subsetSum, 1'250'244'500ul); EXPECT_TRUE(verifySelectedUTXOs(selected, subset)); } + +} // namespace TW::Bitcoin diff --git a/tests/chains/Bitcoin/MessageSignerTests.cpp b/tests/chains/Bitcoin/MessageSignerTests.cpp new file mode 100644 index 00000000000..d55cded3107 --- /dev/null +++ b/tests/chains/Bitcoin/MessageSignerTests.cpp @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bitcoin/MessageSigner.h" +#include +#include "Bitcoin/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "Base64.h" +#include "Coin.h" +#include "Data.h" +#include "TestUtilities.h" + +#include +#include + +#include +#include + +namespace TW::Bitcoin::MessageSignerTests { + +const auto gPrivateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + +TEST(BitcoinMessageSigner, VerifyMessage) { + EXPECT_TRUE(MessageSigner::verifyMessage( + "1B8Qea79tsxmn4dTiKKRVvsJpHwL2fMQnr", + "test signature", + "H+3L5IbSVcejp4S2VwLXCxLEMQAWDvKbE8lQyq0ocdvyM1aoEudkzN/S/qLI3vnNOFY6V13BXWSFrPr3OjGa5Dk=" + )); + EXPECT_TRUE(MessageSigner::verifyMessage( + "1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN", + "This is an example of a signed message.", + "G39Qf0XrZHICWbz3r5gOkcgTRw3vM4leGjiR3refr/K1OezcKmmXaLn4zc8ji2rjbBUIMrIhH/jc5Z2qEEz7qVk=" + )); + EXPECT_TRUE(MessageSigner::verifyMessage( + "1H8X4u6CVZRTLLNbUQTKAnc5vCkqWMpwfF", + "compressed key", + "IKUI9v2xbHogJe8HKXI2M5KEhMKaW6fjNxtyEy27Mf+3/e1ht4jZoc85e4F8stPsxt4Xcg8Yr42S28O6L/Qx9fE=" + )); + EXPECT_TRUE(MessageSigner::verifyMessage( + "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", + "test signature", + "ILH5K7JQLaRGaKGXXH5mYM6FIIy9IWyY4JUPI+PHYY4WaupxUbg+zy0bhBCrDuehy9x4WidwjkRR1GSLnWvOXBo=" + )); + EXPECT_TRUE(MessageSigner::verifyMessage( + "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", + "another text", + "H7vrF2C+TlFiHyegAw3QLv6SK0myuEEXUOgfx0+Qio1YVDuSa6p/OHpoQVlUt3F8QJdbdZN9M1h/fYEAnEz16V0=" + )); + EXPECT_TRUE(MessageSigner::verifyMessage( + "1E4T9JZ3mq6cdgiRJEWzHqDXb9t322fE6d", + "test signature", + "HLH5K7JQLaRGaKGXXH5mYM6FIIy9IWyY4JUPI+PHYY4WaupxUbg+zy0bhBCrDuehy9x4WidwjkRR1GSLnWvOXBo=" + )); +} + +TEST(BitcoinMessageSigner, SignAndVerify) { + const auto pubKey = gPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(pubKey.bytes), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); + const auto address = Address(pubKey, TW::p2pkhPrefix(TWCoinTypeBitcoin)).string(); + EXPECT_EQ(address, "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X"); + + { + const auto msg = "test signature"; + const auto signature = MessageSigner::signMessage(gPrivateKey, address, msg); + EXPECT_EQ(signature, "ILH5K7JQLaRGaKGXXH5mYM6FIIy9IWyY4JUPI+PHYY4WaupxUbg+zy0bhBCrDuehy9x4WidwjkRR1GSLnWvOXBo="); + + EXPECT_TRUE(MessageSigner::verifyMessage(address, msg, signature)); + } + { + const auto msg = "another text"; + const auto signature = MessageSigner::signMessage(gPrivateKey, address, msg); + EXPECT_EQ(signature, "H7vrF2C+TlFiHyegAw3QLv6SK0myuEEXUOgfx0+Qio1YVDuSa6p/OHpoQVlUt3F8QJdbdZN9M1h/fYEAnEz16V0="); + + EXPECT_TRUE(MessageSigner::verifyMessage(address, msg, signature)); + } + + // uncompressed + const auto pubKeyUncomp = gPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + const auto keyHash = pubKeyUncomp.hash(Data{TW::p2pkhPrefix(TWCoinTypeBitcoin)}, Hash::HasherSha256ripemd); + const auto addressUncomp = Address(keyHash).string(); + EXPECT_EQ(addressUncomp, "1E4T9JZ3mq6cdgiRJEWzHqDXb9t322fE6d"); + { + const auto msg = "test signature"; + const auto signature = MessageSigner::signMessage(gPrivateKey, addressUncomp, msg, false); + EXPECT_EQ(signature, "HLH5K7JQLaRGaKGXXH5mYM6FIIy9IWyY4JUPI+PHYY4WaupxUbg+zy0bhBCrDuehy9x4WidwjkRR1GSLnWvOXBo="); + + EXPECT_TRUE(MessageSigner::verifyMessage(addressUncomp, msg, signature)); + } +} + +TEST(BitcoinMessageSigner, SignNegative) { + const auto address = Address(gPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1), TW::p2pkhPrefix(TWCoinTypeBitcoin)).string(); + EXPECT_EQ(address, "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X"); + const auto msg = "test signature"; + // Use invalid address + EXPECT_EXCEPTION(MessageSigner::signMessage(gPrivateKey, "__THIS_IS_NOT_A_VALID_ADDRESS__", msg), "Address is not valid (legacy) address"); + // Use invalid address, not legacy + EXPECT_EXCEPTION(MessageSigner::signMessage(gPrivateKey, "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8", msg), "Address is not valid (legacy) address"); + // Use valid, but not matching key + EXPECT_EXCEPTION(MessageSigner::signMessage(gPrivateKey, "1B8Qea79tsxmn4dTiKKRVvsJpHwL2fMQnr", msg), "Address does not match key"); +} + +TEST(BitcoinMessageSigner, VerifyNegative) { + const auto sig = parse_hex("1fedcbe486d255c7a3a784b65702d70b12c43100160ef29b13c950caad2871dbf23356a812e764ccdfd2fea2c8def9cd38563a575dc15d6485acfaf73a319ae439"); + // Baseline positive + EXPECT_TRUE(MessageSigner::verifyMessage( + "1B8Qea79tsxmn4dTiKKRVvsJpHwL2fMQnr", + "test signature", + sig + )); + + // Provide non-matching address + EXPECT_FALSE(MessageSigner::verifyMessage( + "1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN", + "test signature", + sig + )); + // Signature too short + EXPECT_EXCEPTION(MessageSigner::verifyMessage( + "1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN", + "test signature", + parse_hex("1fedcbe486d255c7a3a784b65702d70b12c43100160ef29b13c950caad2871dbf23356a812e764ccdfd2fea2c8def9cd38563a575dc15d6485acfaf73a319ae4") + ), "signature too short"); + // Invalid address + EXPECT_EXCEPTION(MessageSigner::verifyMessage( + "__THIS_IS_NOT_A_VALID_ADDRESS__", + "test signature", + parse_hex("1fedcbe486d255c7a3a784b65702d70b12c43100160ef29b13c950caad2871dbf23356a812e764ccdfd2fea2c8def9cd38563a575dc15d6485acfaf73a319ae4") + ), "Input address invalid"); + EXPECT_EXCEPTION(MessageSigner::verifyMessage( + "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8", + "test signature", + parse_hex("1fedcbe486d255c7a3a784b65702d70b12c43100160ef29b13c950caad2871dbf23356a812e764ccdfd2fea2c8def9cd38563a575dc15d6485acfaf73a319ae4") + ), "Input address invalid"); +} + +TEST(BitcoinMessageSigner, MessageToHash) { + EXPECT_EQ(hex(MessageSigner::messageToHash("Hello, world!")), "02d6c0643e40b0db549cbbd7eb47dcab71a59d7017199ebde6b272f28fbbf95f"); + EXPECT_EQ(hex(MessageSigner::messageToHash("test signature")), "8e81cc5bca9862d8b7f22be1f7cb762b49121cf4e1611c27906a041f9a9eb21f"); +} + +TEST(TWBitcoinMessageSigner, VerifyMessage) { + EXPECT_TRUE(TWBitcoinMessageSignerVerifyMessage( + STRING("1B8Qea79tsxmn4dTiKKRVvsJpHwL2fMQnr").get(), + STRING("test signature").get(), + STRING("H+3L5IbSVcejp4S2VwLXCxLEMQAWDvKbE8lQyq0ocdvyM1aoEudkzN/S/qLI3vnNOFY6V13BXWSFrPr3OjGa5Dk=").get() + )); +} + +TEST(TWBitcoinMessageSigner, SignAndVerify) { + const auto privKeyData = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto address = STRING("19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X"); + const auto message = STRING("test signature"); + + const auto signature = WRAPS(TWBitcoinMessageSignerSignMessage(privateKey.get(), address.get(), message.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature.get())), "ILH5K7JQLaRGaKGXXH5mYM6FIIy9IWyY4JUPI+PHYY4WaupxUbg+zy0bhBCrDuehy9x4WidwjkRR1GSLnWvOXBo="); + + EXPECT_TRUE(TWBitcoinMessageSignerVerifyMessage(address.get(), message.get(), signature.get())); +} + +} // namespace TW::Bitcoin diff --git a/tests/Bitcoin/SegwitAddressTests.cpp b/tests/chains/Bitcoin/SegwitAddressTests.cpp similarity index 76% rename from tests/Bitcoin/SegwitAddressTests.cpp rename to tests/chains/Bitcoin/SegwitAddressTests.cpp index 7fadb980c0c..62c7fe7fde5 100644 --- a/tests/Bitcoin/SegwitAddressTests.cpp +++ b/tests/chains/Bitcoin/SegwitAddressTests.cpp @@ -1,11 +1,10 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Bech32.h" #include "Bitcoin/SegwitAddress.h" +#include "HDWallet.h" #include "HexCoding.h" #include @@ -14,7 +13,7 @@ #include using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin::tests { static const std::string valid_checksum[] = { "A12UEL5L", @@ -128,7 +127,7 @@ bool case_insensitive_equal(const std::string& s1, const std::string& s2) { } TEST(SegwitAddress, ValidChecksum) { - for (auto i = 0; i < sizeof(valid_checksum) / sizeof(valid_checksum[0]); ++i) { + for (auto i = 0ul; i < sizeof(valid_checksum) / sizeof(valid_checksum[0]); ++i) { auto dec = Bech32::decode(valid_checksum[i]); ASSERT_FALSE(std::get<0>(dec).empty()); @@ -140,7 +139,7 @@ TEST(SegwitAddress, ValidChecksum) { } TEST(SegwitAddress, InvalidChecksum) { - for (auto i = 0; i < sizeof(invalid_checksum) / sizeof(invalid_checksum[0]); ++i) { + for (auto i = 0ul; i < sizeof(invalid_checksum) / sizeof(invalid_checksum[0]); ++i) { auto dec = Bech32::decode(invalid_checksum[i]); EXPECT_TRUE(std::get<0>(dec).empty() && std::get<1>(dec).empty()); } @@ -151,7 +150,7 @@ TEST(SegwitAddress, ValidAddress) { auto dec = SegwitAddress::decode(td.address); EXPECT_TRUE(std::get<2>(dec)) << "Valid address could not be decoded " << td.address; EXPECT_TRUE(std::get<0>(dec).witnessProgram.size() > 0) << "Empty decoded address data for " << td.address; - EXPECT_EQ(std::get<1>(dec).length(), 2); // hrp + EXPECT_EQ(std::get<1>(dec).length(), 2ul); // hrp // recode std::string recode = std::get<0>(dec).string(); @@ -164,14 +163,14 @@ TEST(SegwitAddress, ValidAddress) { } TEST(SegwitAddress, InvalidAddress) { - for (auto i = 0; i < invalid_address.size(); ++i) { + for (auto i = 0ul; i < invalid_address.size(); ++i) { auto dec = SegwitAddress::decode(invalid_address[i]); EXPECT_FALSE(std::get<2>(dec)) << "Invalid address reported as valid: " << invalid_address[i]; } } TEST(SegwitAddress, InvalidAddressEncoding) { - for (auto i = 0; i < sizeof(invalid_address_enc) / sizeof(invalid_address_enc[0]); ++i) { + for (auto i = 0ul; i < sizeof(invalid_address_enc) / sizeof(invalid_address_enc[0]); ++i) { auto address = SegwitAddress(invalid_address_enc[i].hrp, invalid_address_enc[i].version, Data(invalid_address_enc[i].program_length, 0)); std::string code = address.string(); EXPECT_TRUE(code.empty()); @@ -207,3 +206,46 @@ TEST(SegwitAddress, Equals) { ASSERT_TRUE(addr1 == addr1); ASSERT_FALSE(addr1 == addr2); } + +TEST(SegwitAddress, TestnetAddress) { + const auto mnemonic1 = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + const auto passphrase = ""; + const auto coin = TWCoinTypeBitcoin; + HDWallet wallet = HDWallet(mnemonic1, passphrase); + + // default + { + const auto privKey = wallet.getKey(coin, TWDerivationDefault); + const auto pubKey = privKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(pubKey.bytes), "02df9ef2a7a5552765178b181e1e1afdefc7849985c7dfe9647706dd4fa40df6ac"); + EXPECT_EQ(SegwitAddress(pubKey, "bc").string(), "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"); + } + + // testnet: different derivation path and hrp + { + const auto privKey = wallet.getKey(coin, TW::DerivationPath("m/84'/1'/0'/0/0")); + const auto pubKey = privKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(pubKey.bytes), "03eb1a535b59f03894b99319f850c784cf4164f4de07620695c5cf0dc5c1ab2a54"); + EXPECT_EQ(SegwitAddress::createTestnetFromPublicKey(pubKey).string(), "tb1qq8p994ak933c39d2jaj8n4sg598tnkhnyk5sg5"); + // alternative with custom hrp + EXPECT_EQ(SegwitAddress(pubKey, "tb").string(), "tb1qq8p994ak933c39d2jaj8n4sg598tnkhnyk5sg5"); + } + + EXPECT_TRUE(SegwitAddress::isValid("tb1qq8p994ak933c39d2jaj8n4sg598tnkhnyk5sg5")); +} + +TEST(SegwitAddress, SegwitDerivationHDWallet) { + const auto mnemonic1 = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + const auto passphrase = ""; + const auto coin = TWCoinTypeBitcoin; + HDWallet wallet = HDWallet(mnemonic1, passphrase); + + // addresses with different derivations + EXPECT_EQ(wallet.deriveAddress(coin), "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"); + EXPECT_EQ(wallet.deriveAddress(coin, TWDerivationDefault), "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"); + EXPECT_EQ(wallet.deriveAddress(coin, TWDerivationBitcoinSegwit), "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"); + EXPECT_EQ(wallet.deriveAddress(coin, TWDerivationBitcoinLegacy), "1GVb4mfQrvymPLz7zeZ3LnQ8sFv3NedZXe"); + EXPECT_EQ(wallet.deriveAddress(coin, TWDerivationBitcoinTestnet), "tb1qq8p994ak933c39d2jaj8n4sg598tnkhnyk5sg5"); +} + +} // namespace TW::Bitcoin::tests diff --git a/tests/Bitcoin/TWBitcoinAddressTests.cpp b/tests/chains/Bitcoin/TWBitcoinAddressTests.cpp similarity index 91% rename from tests/Bitcoin/TWBitcoinAddressTests.cpp rename to tests/chains/Bitcoin/TWBitcoinAddressTests.cpp index c2e81b6d33a..60c6cb7d16e 100644 --- a/tests/Bitcoin/TWBitcoinAddressTests.cpp +++ b/tests/chains/Bitcoin/TWBitcoinAddressTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/Bitcoin/TWBitcoinPsbtTests.cpp b/tests/chains/Bitcoin/TWBitcoinPsbtTests.cpp new file mode 100644 index 00000000000..616c9913f2a --- /dev/null +++ b/tests/chains/Bitcoin/TWBitcoinPsbtTests.cpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/BitcoinV2.pb.h" +#include "PrivateKey.h" +#include "TestUtilities.h" + +#include "TrustWalletCore/TWBitcoinPsbt.h" + +#include + +namespace TW::Bitcoin::PsbtTests { + +const auto gPrivateKey = PrivateKey(parse_hex("f00ffbe44c5c2838c13d2778854ac66b75e04eb6054f0241989e223223ad5e55")); +const auto gPsbt = parse_hex("70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000"); + +TEST(TWBitcoinPsbt, SignThorSwap) { + BitcoinV2::Proto::PsbtSigningInput input; + input.set_psbt(gPsbt.data(), gPsbt.size()); + input.add_private_keys(gPrivateKey.bytes.data(), gPrivateKey.bytes.size()); + + const auto inputData = data(input.SerializeAsString()); + const auto inputPtr = WRAPD(TWDataCreateWithBytes(inputData.data(), inputData.size())); + + const auto outputPtr = WRAPD(TWBitcoinPsbtSign(inputPtr.get(), TWCoinTypeBitcoin)); + + BitcoinV2::Proto::PsbtSigningOutput output; + output.ParseFromArray( + TWDataBytes(outputPtr.get()), + static_cast(TWDataSize(outputPtr.get())) + ); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + EXPECT_EQ(hex(output.psbt()), "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d01086c02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000"); + EXPECT_EQ(hex(output.encoded()), "02000000000101147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000"); +} + +TEST(TWBitcoinPsbt, PlanThorSwap) { + const auto publicKey = gPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + + BitcoinV2::Proto::PsbtSigningInput input; + input.set_psbt(gPsbt.data(), gPsbt.size()); + input.add_public_keys(publicKey.bytes.data(), publicKey.bytes.size()); + + const auto inputData = data(input.SerializeAsString()); + const auto inputPtr = WRAPD(TWDataCreateWithBytes(inputData.data(), inputData.size())); + + const auto planPtr = WRAPD(TWBitcoinPsbtPlan(inputPtr.get(), TWCoinTypeBitcoin)); + + BitcoinV2::Proto::TransactionPlan plan; + plan.ParseFromArray( + TWDataBytes(planPtr.get()), + static_cast(TWDataSize(planPtr.get())) + ); + + EXPECT_EQ(plan.error(), Common::Proto::SigningError::OK); + EXPECT_EQ(plan.send_amount(), 66'406); + EXPECT_EQ(plan.fee_estimate(), 1'736); +} + +} diff --git a/tests/Bitcoin/TWBitcoinScriptTests.cpp b/tests/chains/Bitcoin/TWBitcoinScriptTests.cpp similarity index 91% rename from tests/Bitcoin/TWBitcoinScriptTests.cpp rename to tests/chains/Bitcoin/TWBitcoinScriptTests.cpp index 204ee624b90..1851e7ba9ba 100644 --- a/tests/Bitcoin/TWBitcoinScriptTests.cpp +++ b/tests/chains/Bitcoin/TWBitcoinScriptTests.cpp @@ -1,37 +1,43 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include #include +namespace TW::Bitcoin::TWScriptTests { + +// clang-format off const auto PayToScriptHash = WRAP(TWBitcoinScript, TWBitcoinScriptCreateWithData(DATA("a914" "4733f37cf4db86fbc2efed2500b4f4e49f312023" "87").get())); const auto PayToWitnessScriptHash = WRAP(TWBitcoinScript, TWBitcoinScriptCreateWithData(DATA("0020" "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db").get())); const auto PayToWitnessPublicKeyHash = WRAP(TWBitcoinScript, TWBitcoinScriptCreateWithData(DATA("0014" "79091972186c449eb1ded22b78e40d009bdf0089").get())); const auto PayToPublicKeySecp256k1 = WRAP(TWBitcoinScript, TWBitcoinScriptCreateWithData(DATA("21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ac").get())); const auto PayToPublicKeyHash = WRAP(TWBitcoinScript, TWBitcoinScriptCreateWithData(DATA("76a914" "79091972186c449eb1ded22b78e40d009bdf0089" "88ac").get())); +// clang-format on TEST(TWBitcoinScript, Create) { auto data = DATA("a9144733f37cf4db86fbc2efed2500b4f4e49f31202387"); + { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptCreate()); + ASSERT_TRUE(script.get() != nullptr); + } { auto script = WRAP(TWBitcoinScript, TWBitcoinScriptCreateWithData(data.get())); ASSERT_TRUE(script.get() != nullptr); - ASSERT_EQ(TWBitcoinScriptSize(script.get()), 23); + ASSERT_EQ(TWBitcoinScriptSize(script.get()), 23ul); } { auto script = WRAP(TWBitcoinScript, TWBitcoinScriptCreateWithBytes(TWDataBytes(data.get()), TWDataSize(data.get()))); ASSERT_TRUE(script.get() != nullptr); - ASSERT_EQ(TWBitcoinScriptSize(script.get()), 23); + ASSERT_EQ(TWBitcoinScriptSize(script.get()), 23ul); auto scriptCopy = WRAP(TWBitcoinScript, TWBitcoinScriptCreateCopy(script.get())); ASSERT_TRUE(scriptCopy.get() != nullptr); - ASSERT_EQ(TWBitcoinScriptSize(scriptCopy.get()), 23); + ASSERT_EQ(TWBitcoinScriptSize(scriptCopy.get()), 23ul); } } @@ -117,7 +123,9 @@ TEST(TWBitcoinScript, BuildPayToPublicKey) { const auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildPayToPublicKey(pubkey.get())); ASSERT_TRUE(script.get() != nullptr); const auto hex = WRAPS(TWStringCreateWithHexData(WRAPD(TWBitcoinScriptData(script.get())).get())); - ASSERT_STREQ(TWStringUTF8Bytes(hex.get()), "21" "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ac"); + ASSERT_STREQ(TWStringUTF8Bytes(hex.get()), "21" + "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" + "ac"); } TEST(TWBitcoinScript, BuildPayToWitnessPubkeyHash) { @@ -125,7 +133,8 @@ TEST(TWBitcoinScript, BuildPayToWitnessPubkeyHash) { const auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildPayToWitnessPubkeyHash(hash.get())); ASSERT_TRUE(script.get() != nullptr); const auto hex = WRAPS(TWStringCreateWithHexData(WRAPD(TWBitcoinScriptData(script.get())).get())); - ASSERT_STREQ(TWStringUTF8Bytes(hex.get()), "0014" "79091972186c449eb1ded22b78e40d009bdf0089"); + ASSERT_STREQ(TWStringUTF8Bytes(hex.get()), "0014" + "79091972186c449eb1ded22b78e40d009bdf0089"); } TEST(TWBitcoinScript, BuildPayToWitnessScriptHash) { @@ -133,7 +142,8 @@ TEST(TWBitcoinScript, BuildPayToWitnessScriptHash) { const auto script = WRAP(TWBitcoinScript, TWBitcoinScriptBuildPayToWitnessScriptHash(hash.get())); ASSERT_TRUE(script.get() != nullptr); const auto hex = WRAPS(TWStringCreateWithHexData(WRAPD(TWBitcoinScriptData(script.get())).get())); - ASSERT_STREQ(TWStringUTF8Bytes(hex.get()), "0020" "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db"); + ASSERT_STREQ(TWStringUTF8Bytes(hex.get()), "0020" + "ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db"); } TEST(TWBitcoinScript, ScriptHash) { @@ -202,11 +212,11 @@ TEST(TWBitcoinScript, LockScriptForP2WSHAddress) { } TEST(TWBitcoinScript, LockScriptForCashAddress) { - auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("bitcoincash:pzclklsyx9f068hd00a0vene45akeyrg7vv0053uqf").get(), TWCoinTypeBitcoin)); + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("bitcoincash:pzclklsyx9f068hd00a0vene45akeyrg7vv0053uqf").get(), TWCoinTypeBitcoinCash)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "a914b1fb7e043152fd1eed7bfaf66679ad3b6c9068f387"); - auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("bitcoincash:qpk05r5kcd8uuzwqunn8rlx5xvuvzjqju5rch3tc0u").get(), TWCoinTypeBitcoin)); + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("bitcoincash:qpk05r5kcd8uuzwqunn8rlx5xvuvzjqju5rch3tc0u").get(), TWCoinTypeBitcoinCash)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "76a9146cfa0e96c34fce09c0e4e671fcd43338c14812e588ac"); } @@ -233,3 +243,5 @@ TEST(TWBitcoinSigHashType, IsNone) { EXPECT_FALSE(TWBitcoinSigHashTypeIsNone(TWBitcoinSigHashTypeAll)); EXPECT_FALSE(TWBitcoinSigHashTypeIsNone(TWBitcoinSigHashTypeFork)); } + +} // namespace TW::Bitcoin::TWScriptTests diff --git a/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp b/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp new file mode 100644 index 00000000000..3150758df09 --- /dev/null +++ b/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp @@ -0,0 +1,2383 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base58.h" +#include "Bitcoin/Address.h" +#include "Bitcoin/OutPoint.h" +#include "Bitcoin/Script.h" +#include "Bitcoin/SegwitAddress.h" +#include "Bitcoin/SigHashType.h" +#include "Bitcoin/Transaction.h" +#include "Bitcoin/TransactionBuilder.h" +#include "Bitcoin/TransactionSigner.h" +#include "BitcoinOrdinalNftData.h" +#include "Hash.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "TxComparisonHelper.h" +#include "proto/Bitcoin.pb.h" +#include "TestUtilities.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace TW::Bitcoin { + +// clang-format off +SigningInput buildInputP2PKH(bool omitKey = false) { + auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); + auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); + + // Setup input + SigningInput input; + input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); + input.amount = 335'790'000; + input.byteFee = 1; + input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; + input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; + input.coinType = TWCoinTypeBitcoin; + + auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); + auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash0 = Hash::ripemd(Hash::sha256(pubKey0.bytes)); + assert(hex(utxoPubkeyHash0) == "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); + if (!omitKey) { + input.privateKeys.push_back(utxoKey0); + } + + auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + auto pubKey1 = utxoKey1.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash1 = Hash::ripemd(Hash::sha256(pubKey1.bytes)); + assert(hex(utxoPubkeyHash1) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + if (!omitKey) { + input.privateKeys.push_back(utxoKey1); + } + + auto utxo0Script = Script::buildPayToPublicKeyHash(utxoPubkeyHash0); + Data scriptHash; + utxo0Script.matchPayToPublicKeyHash(scriptHash); + assert(hex(scriptHash) == "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); + + UTXO utxo0; + utxo0.script = utxo0Script; + utxo0.amount = 625'000'000; + utxo0.outPoint = OutPoint(hash0, 0, UINT32_MAX); + input.utxos.push_back(utxo0); + + UTXO utxo1; + utxo1.script = Script(parse_hex("0014" + "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + utxo1.amount = 600'000'000; + utxo1.outPoint = OutPoint(hash1, 1, UINT32_MAX); + input.utxos.push_back(utxo1); + + return input; +} + +TEST(BitcoinSigning, SpendMinimumAmountP2WPKH) { + auto myPrivateKey = PrivateKey(parse_hex("9ea2172511ed73ae0096be8e593c3b75631700edaf729f1abbae607314a20e35")); + + auto myPublicKey = myPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(myPublicKey.bytes)); + auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); + + // Both two UTXOs came from the same transaction. + auto utxoHash = parse_hex("e8b3c2d0d5851cef139d87dfb5794db8897ce90ce1b6961526f61923baf5b5a3"); + std::reverse(utxoHash.begin(), utxoHash.end()); + + auto segwitDustAmount = 294; + + // Setup input + SigningInput input; + input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); + input.amount = 546; + input.useMaxAmount = false; + input.useMaxUtxo = true; + input.byteFee = 27; + input.toAddress = "bc1qvrt7ukvhvmdny0a3j9k8l8jasx92lrqm30t2u2"; + input.changeAddress = "bc1qvrt7ukvhvmdny0a3j9k8l8jasx92lrqm30t2u2"; + input.coinType = TWCoinTypeBitcoin; + input.dustCalculator = std::make_shared(segwitDustAmount); + + input.privateKeys.push_back(myPrivateKey); + input.scripts[hex(utxoPubkeyHash)] = redeemScript; + + UTXO utxo0; + utxo0.script = redeemScript; + utxo0.amount = segwitDustAmount; + utxo0.outPoint = OutPoint(utxoHash, 0, UINT32_MAX); + input.utxos.push_back(utxo0); + + UTXO utxo1; + utxo1.script = redeemScript; + utxo1.amount = 16776; + utxo1.outPoint = OutPoint(utxoHash, 1, UINT32_MAX); + input.utxos.push_back(utxo1); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {294, 16776}, 546, 5643)); + EXPECT_EQ(plan.change, 10881); + } + + // Signs + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + const auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + + EXPECT_EQ( + hex(serialized), + "01000000000102a3b5f5ba2319f6261596b6e10ce97c89b84d79b5df879d13ef1c85d5d0c2b3e80000000000ffffffffa3b5f5ba2319f6261596b6e10ce97c89b84d79b5df879d13ef1c85d5d0c2b3e80100000000ffffffff02220200000000000016001460d7ee599766db323fb1916c7f9e5d818aaf8c1b812a00000000000016001460d7ee599766db323fb1916c7f9e5d818aaf8c1b02483045022100d7e4d267e94547bd365736229219a85b21f79cf896a65baa444e339215b4b36f022078c0dee3d1d603f77855fee8f23291fe180b50afaa2c9ae9f724b7418d76da75012103a11506993946e20ea82686b157bf08f944759f43d91af8d84650ee73a482431c02483045022100c10cdbe21cedab3b4e7db9422f69c7074764711d552a63545104d71c905b138802204999f3ecb5fdadfd8669a8c14f04643c59bb3e98aaf52c52f829a0f6ef5d6abb012103a11506993946e20ea82686b157bf08f944759f43d91af8d84650ee73a482431c00000000" + ); +} + +TEST(BitcoinSigning, ExtraOutputs) { + auto privateKey = parse_hex("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); + auto ownAddress = "1MhdctqCwYMn2DT4mshpwvYtfF98wBojXS"; + auto toAddress = "1PRuxNSZwUXym6A31kmrArdT2BGJiTna19"; + auto utxoAmount = 10000; + auto toAmount = 2000; + int byteFee = 6; + + auto signingInput = Proto::SigningInput(); + signingInput.set_hash_type(TWBitcoinSigHashTypeAll); + signingInput.set_amount(toAmount); + signingInput.set_byte_fee(byteFee); + signingInput.set_to_address(toAddress); + signingInput.set_change_address(ownAddress); + signingInput.add_private_key(privateKey.data(), privateKey.size()); + + auto utxoScript = Script::lockScriptForAddress(ownAddress, TWCoinTypeBitcoin); + auto& utxo = *signingInput.add_utxo(); + // The UTXO doesn't belong to the `ownAddress`, it's used just for the test purposes. + auto utxoHash = parse_hex("d15d38de9a619809b575532a235d23947c4ff7d219d3feb6c5b6105d23360f4e"); + std::reverse(utxoHash.begin(), utxoHash.end()); + utxo.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); + utxo.mutable_out_point()->set_index(0); + utxo.mutable_out_point()->set_sequence(4294967290); + utxo.set_amount(utxoAmount); + utxo.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + + auto& output1 = *signingInput.add_extra_outputs(); + output1.set_to_address("bc1qkm0awulcn94gmtjwzwkvnpflc3ytt7a6cjentn"); + output1.set_amount(2000UL); + + auto& output2 = *signingInput.add_extra_outputs(); + output2.set_to_address("bc1pqa49cxxdqyr49nwe2379tq4xsc4e8qe8mdxyjx3mprnftcde0v4s3lnhzq"); + output2.set_amount(2000UL); + + Proto::TransactionPlan plan; + ANY_PLAN(signingInput, plan, TWCoinTypeBitcoin); + + EXPECT_EQ(plan.amount(), 2000L); + EXPECT_EQ(plan.change(), 2200L); + EXPECT_EQ(plan.fee(), 1800L); + + *signingInput.mutable_plan() = plan; + + Proto::SigningOutput output; + ANY_SIGN(signingInput, TWCoinTypeBitcoin); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(output.transaction().inputs_size(), 1); + // Expected outputs: `amount`, `change`, `extra_output[0]`, `extra_output[1]` + EXPECT_EQ(output.transaction().outputs_size(), 4); + EXPECT_EQ(hex(output.encoded()), "01000000014e0f36235d10b6c5b6fed319d2f74f7c94235d232a5375b50998619ade385dd1000000006b483045022100e044cce5c2cf141f725bb88dafc74d7db8679826838f1dd4ba35fa57a159454202204aed2c624dc53b6f98adbc818689af4d99c6d9cdb0377979a74982b0624d6e9e0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cbfaffffff04d0070000000000001976a914f608f4635f9072c4f92715e5a6c35c058a9d6fe988ac98080000000000001976a914e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d88acd007000000000000160014b6dfd773f8996a8dae4e13acc9853fc448b5fbbad007000000000000225120076a5c18cd010752cdd9547c5582a6862b938327db4c491a3b08e695e1b97b2b00000000"); +} + +/// It would be enough to use the only `utxo0` to send `toAmount` and pay the fee. +/// But since the `extraOutputs` are present, `utxo0` is not enough to generate the transaction. +/// Here, `utxoAmount - toAmount - extraOutputsAmount - fee = -1799` +/// Please note that the `toAmount` shouldn't be reduced if `extraOutputs` are set. +TEST(BitcoinSigning, ExtraOutputsExceedAvailableAmount) { + auto privateKey = parse_hex("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); + auto ownAddress = "1MhdctqCwYMn2DT4mshpwvYtfF98wBojXS"; + auto toAddress = "1PRuxNSZwUXym6A31kmrArdT2BGJiTna19"; + auto utxoAmount = 10000; + auto toAmount = 5999; + int byteFee = 6; + + auto signingInput = Proto::SigningInput(); + signingInput.set_hash_type(TWBitcoinSigHashTypeAll); + signingInput.set_amount(toAmount); + signingInput.set_byte_fee(byteFee); + signingInput.set_to_address(toAddress); + signingInput.set_change_address(ownAddress); + signingInput.add_private_key(privateKey.data(), privateKey.size()); + + auto utxoScript = Script::lockScriptForAddress(ownAddress, TWCoinTypeBitcoin); + auto& utxo = *signingInput.add_utxo(); + // The UTXO doesn't belong to the `ownAddress`, it's used just for the test purposes. + auto utxoHash = parse_hex("d15d38de9a619809b575532a235d23947c4ff7d219d3feb6c5b6105d23360f4e"); + std::reverse(utxoHash.begin(), utxoHash.end()); + utxo.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); + utxo.mutable_out_point()->set_index(0); + utxo.mutable_out_point()->set_sequence(4294967290); + utxo.set_amount(utxoAmount); + utxo.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + + auto& output1 = *signingInput.add_extra_outputs(); + output1.set_to_address("bc1qkm0awulcn94gmtjwzwkvnpflc3ytt7a6cjentn"); + output1.set_amount(2000UL); + + auto& output2 = *signingInput.add_extra_outputs(); + output2.set_to_address("bc1pqa49cxxdqyr49nwe2379tq4xsc4e8qe8mdxyjx3mprnftcde0v4s3lnhzq"); + output2.set_amount(2000UL); + + Proto::TransactionPlan plan; + ANY_PLAN(signingInput, plan, TWCoinTypeBitcoin); + + EXPECT_EQ(plan.error(), Common::Proto::Error_not_enough_utxos); +} + +/// It would be enough to use the only `utxo0` to send `toAmount` and pay the fee. +/// But since the `extraOutputs` are present, the transaction builder needs to select one extra UTXO (e.g. `utxo1`). +TEST(BitcoinSigning, ExtraOutputsRequireExtraInputs) { + auto privateKey = parse_hex("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); + auto ownAddress = "1MhdctqCwYMn2DT4mshpwvYtfF98wBojXS"; + auto toAddress = "1PRuxNSZwUXym6A31kmrArdT2BGJiTna19"; + auto utxo0Amount = 10000; + auto utxo1Amount = 3000; + auto toAmount = 5999; + int byteFee = 6; + + auto signingInput = Proto::SigningInput(); + signingInput.set_hash_type(TWBitcoinSigHashTypeAll); + signingInput.set_amount(toAmount); + signingInput.set_byte_fee(byteFee); + signingInput.set_to_address(toAddress); + signingInput.set_change_address(ownAddress); + signingInput.add_private_key(privateKey.data(), privateKey.size()); + // Dust threshold will be 612 (102 * 6) if otherwise is not set. + // So to fix the test, we should set the 313 dust threshold for the change output to be included. + signingInput.set_fixed_dust_threshold(313); + + auto utxoScript = Script::lockScriptForAddress(ownAddress, TWCoinTypeBitcoin); + auto& utxo0 = *signingInput.add_utxo(); + // The UTXO doesn't belong to the `ownAddress`, it's used just for the test purposes. + auto utxoHash = parse_hex("d15d38de9a619809b575532a235d23947c4ff7d219d3feb6c5b6105d23360f4e"); + std::reverse(utxoHash.begin(), utxoHash.end()); + utxo0.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); + utxo0.mutable_out_point()->set_index(0); + utxo0.mutable_out_point()->set_sequence(4294967290); + utxo0.set_amount(utxo0Amount); + utxo0.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + + auto& utxo1 = *signingInput.add_utxo(); + std::reverse(utxoHash.begin(), utxoHash.end()); + utxo1.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); + utxo1.mutable_out_point()->set_index(1); + utxo1.mutable_out_point()->set_sequence(4294967290); + utxo1.set_amount(utxo1Amount); + utxo1.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + + auto& output1 = *signingInput.add_extra_outputs(); + output1.set_to_address("bc1qkm0awulcn94gmtjwzwkvnpflc3ytt7a6cjentn"); + output1.set_amount(2000UL); + + auto& output2 = *signingInput.add_extra_outputs(); + output2.set_to_address("bc1pqa49cxxdqyr49nwe2379tq4xsc4e8qe8mdxyjx3mprnftcde0v4s3lnhzq"); + output2.set_amount(2000UL); + + Proto::TransactionPlan plan; + ANY_PLAN(signingInput, plan, TWCoinTypeBitcoin); + + EXPECT_EQ(plan.error(), Common::Proto::OK); + EXPECT_EQ(plan.amount(), toAmount); + EXPECT_EQ(plan.fee(), 2688L); + EXPECT_EQ(plan.change(), 313L); + + Proto::SigningOutput output; + ANY_SIGN(signingInput, TWCoinTypeBitcoin); + + EXPECT_EQ(output.transaction().inputs_size(), 2); + // Expected outputs: `amount`, `change`, `extra_output[0]`, `extra_output[1]` + EXPECT_EQ(output.transaction().outputs_size(), 4); + EXPECT_EQ(hex(output.encoded()), "0100000002d15d38de9a619809b575532a235d23947c4ff7d219d3feb6c5b6105d23360f4e010000006b483045022100d104fd6b122a22b4104ec7898e355e8fe2e5fea2c838e828f748fa1e2ac3af4f022068dd9448c55f70d19cf04c2d1e7627029270e4cfd0721d5fac817a3b0c230d900121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cbfaffffff4e0f36235d10b6c5b6fed319d2f74f7c94235d232a5375b50998619ade385dd1000000006b4830450221009faff5b9ce33df0d56f068cae5c82b589698a79b86b51a6ca2c6784f7b761157022043849144348cea8526f6e78cf235e51edf6c72bf47a08234231a9df936d0746f0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cbfaffffff046f170000000000001976a914f608f4635f9072c4f92715e5a6c35c058a9d6fe988ac39010000000000001976a914e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d88acd007000000000000160014b6dfd773f8996a8dae4e13acc9853fc448b5fbbad007000000000000225120076a5c18cd010752cdd9547c5582a6862b938327db4c491a3b08e695e1b97b2b00000000"); +} + +/// This test only checks if the transaction output will have an expected value. +/// It doesn't check correctness of the encoded representation. +/// Issue: https://github.com/trustwallet/wallet-core/issues/3273 +TEST(BitcoinSigning, SignMaxAmount) { + const auto privateKey = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + const auto ownAddress = "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv"; + + const auto revUtxoHash0 = + parse_hex("07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8"); + const auto inPubKey0 = + parse_hex("024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382"); + const auto inPubKeyHash0 = parse_hex("bd92088bb7e82d611a9b94fbb74a0908152b784f"); + const auto utxoScript0 = parse_hex("0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); + + const auto initialAmount = 10'189'533; + const auto availableAmount = 10'189'534; + const auto fee = 110; + const auto amountWithoutFee = availableAmount - fee; + // There shouldn't be any change + const auto change = 0; + + Proto::SigningInput signingInput; + signingInput.set_coin_type(TWCoinTypeBitcoin); + signingInput.set_hash_type(TWBitcoinSigHashTypeAll); + signingInput.set_amount(initialAmount); + signingInput.set_byte_fee(1); + signingInput.set_to_address("bc1q2dsdlq3343vk29runkgv4yc292hmq53jedfjmp"); + signingInput.set_change_address(ownAddress); + signingInput.set_use_max_amount(true); + + *signingInput.add_private_key() = std::string(privateKey.begin(), privateKey.end()); + + // Add UTXO + auto utxo = signingInput.add_utxo(); + utxo->set_script(utxoScript0.data(), utxoScript0.size()); + utxo->set_amount(availableAmount); + utxo->mutable_out_point()->set_hash( + std::string(revUtxoHash0.begin(), revUtxoHash0.end())); + utxo->mutable_out_point()->set_index(0); + utxo->mutable_out_point()->set_sequence(UINT32_MAX); + + // Plan + Proto::TransactionPlan plan; + ANY_PLAN(signingInput, plan, TWCoinTypeBitcoin); + // Plan is checked, assume it is accepted + EXPECT_EQ(plan.amount(), amountWithoutFee); + EXPECT_EQ(plan.available_amount(), availableAmount); + EXPECT_EQ(plan.fee(), fee); + EXPECT_EQ(plan.change(), change); + + *signingInput.mutable_plan() = plan; + + Proto::SigningOutput output; + ANY_SIGN(signingInput, TWCoinTypeBitcoin); + + const auto& output0 = output.transaction().outputs().at(0); + EXPECT_EQ(output0.value(), amountWithoutFee); +} + +// Tests the BitcoinV2 API through the legacy `SigningInput`. +TEST(BitcoinSigning, SignBRC20TransferCommitV2) { + auto privateKey = parse_hex("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); + auto fullAmount = 26400; + auto minerFee = 3000; + auto brcInscribeAmount = 7000; + auto forFeeAmount = fullAmount - brcInscribeAmount - minerFee; + auto txId = parse_hex("089098890d2653567b9e8df2d1fbe5c3c8bf1910ca7184e301db0ad3b495c88e"); + + PrivateKey key(privateKey); + auto pubKey = key.getPublicKey(TWPublicKeyTypeSECP256k1); + + TW::BitcoinV2::Proto::SigningInput signing; + signing.set_version(BitcoinV2::Proto::TransactionVersion::V2); + signing.add_private_keys(key.bytes.data(), key.bytes.size()); + signing.set_input_selector(BitcoinV2::Proto::InputSelector::UseAll); + signing.set_dangerous_use_fixed_schnorr_rng(true); + signing.set_fixed_dust_threshold(546); + + auto& chainInfo = *signing.mutable_chain_info(); + chainInfo.set_p2pkh_prefix(0); + chainInfo.set_p2sh_prefix(5); + + auto& in = *signing.add_inputs(); + auto& inOutPoint = *in.mutable_out_point(); + inOutPoint.set_hash(txId.data(), txId.size()); + inOutPoint.set_vout(1); + in.set_value(fullAmount); + in.mutable_script_builder()->mutable_p2wpkh()->set_pubkey(pubKey.bytes.data(), pubKey.bytes.size()); + in.set_sighash_type(TWBitcoinSigHashTypeAll); + + auto& out = *signing.add_outputs(); + out.set_value(brcInscribeAmount); + auto& brc20 = *out.mutable_builder()->mutable_brc20_inscribe(); + brc20.set_ticker("oadf"); + brc20.set_transfer_amount("20"); + brc20.set_inscribe_to(pubKey.bytes.data(), pubKey.bytes.size()); + + auto& changeOut = *signing.add_outputs(); + changeOut.set_value(forFeeAmount); + changeOut.mutable_builder()->mutable_p2wpkh()->set_pubkey(pubKey.bytes.data(), pubKey.bytes.size()); + + Proto::SigningInput legacy; + *legacy.mutable_signing_v2() = signing; + + Proto::SigningOutput output; + ANY_SIGN(legacy, TWCoinTypeBitcoin); + + EXPECT_EQ(output.error(), Common::Proto::OK); + ASSERT_TRUE(output.has_signing_result_v2()); + EXPECT_EQ(output.signing_result_v2().error(), Common::Proto::SigningError::OK) + << output.signing_result_v2().error_message(); + EXPECT_EQ(hex(output.signing_result_v2().encoded()), "02000000000101089098890d2653567b9e8df2d1fbe5c3c8bf1910ca7184e301db0ad3b495c88e0100000000ffffffff02581b000000000000225120e8b706a97732e705e22ae7710703e7f589ed13c636324461afa443016134cc051040000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d02483045022100a44aa28446a9a886b378a4a65e32ad9a3108870bd725dc6105160bed4f317097022069e9de36422e4ce2e42b39884aa5f626f8f94194d1013007d5a1ea9220a06dce0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"); + EXPECT_EQ(hex(output.signing_result_v2().txid()), "797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1"); + + // Successfully broadcasted: https://www.blockchain.com/explorer/transactions/btc/797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1 +} + +TEST(BitcoinSigning, SignPlanTransactionWithDustAmount) { + const auto privateKey = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + const auto ownAddress = "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv"; + + const auto revUtxoHash0 = + parse_hex("07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8"); + const auto utxoScript0 = parse_hex("0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); + + const auto dustAmount = 546; + // Use an amount less than dust. + const auto sendAmount = 545; + const auto availableAmount = 10'189'534; + + Proto::SigningInput signingInput; + signingInput.set_coin_type(TWCoinTypeBitcoin); + signingInput.set_hash_type(TWBitcoinSigHashTypeAll); + signingInput.set_amount(sendAmount); + signingInput.set_byte_fee(1); + signingInput.set_to_address("bc1q2dsdlq3343vk29runkgv4yc292hmq53jedfjmp"); + signingInput.set_change_address(ownAddress); + signingInput.set_fixed_dust_threshold(dustAmount); + + *signingInput.add_private_key() = std::string(privateKey.begin(), privateKey.end()); + + // Add UTXO + auto utxo = signingInput.add_utxo(); + utxo->set_script(utxoScript0.data(), utxoScript0.size()); + utxo->set_amount(availableAmount); + utxo->mutable_out_point()->set_hash( + std::string(revUtxoHash0.begin(), revUtxoHash0.end())); + utxo->mutable_out_point()->set_index(0); + utxo->mutable_out_point()->set_sequence(UINT32_MAX); + + Proto::TransactionPlan plan; + ANY_PLAN(signingInput, plan, TWCoinTypeBitcoin); + EXPECT_EQ(plan.error(), Common::Proto::Error_dust_amount_requested); + + // `AnySigner.sign` should return the same error. + Proto::SigningOutput output; + ANY_SIGN(signingInput, TWCoinTypeBitcoin); + EXPECT_EQ(output.error(), Common::Proto::Error_dust_amount_requested); +} + +// If the change amount is less than "dust", there should not be a change output. +TEST(BitcoinSigning, SignPlanTransactionNoChange) { + const auto myPrivateKey = PrivateKey(parse_hex("9ea2172511ed73ae0096be8e593c3b75631700edaf729f1abbae607314a20e35")); + auto myPublicKey = myPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(myPublicKey.bytes)); + auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); + + const auto ownAddress = "bc1qvrt7ukvhvmdny0a3j9k8l8jasx92lrqm30t2u2"; + + auto utxoHash0 = + parse_hex("b33082a5fad105c1d9712e8d503971fe4d84713065bd323fd1019636ed940e8d"); + std::reverse(utxoHash0.begin(), utxoHash0.end()); + auto utxoAmount0 = 30269; + auto utxoOutputIndex0 = 1; + + auto utxoHash1 = + parse_hex("1f62c18bfc5f8293a2b7b061587c427bf830fb224289f9a806e6ad48de6a4c7d"); + std::reverse(utxoHash1.begin(), utxoHash1.end()); + auto utxoAmount1 = 4863; + auto utxoOutputIndex1 = 1; + + auto utxoHash2 = + parse_hex("71c3343dfca5f1914e1bfc04153517d73650cb9c931e8511d24d1f5290120f6f"); + std::reverse(utxoHash2.begin(), utxoHash2.end()); + // This UTXO will be filtered out as less than dust threshold. + auto utxoAmount2 = 300; + auto utxoOutputIndex2 = 0; + + const auto dustAmount = 546; + // Change amount is too low (less than dust), so we just waste it as the transaction fee. + const auto dustChange = 200; + const auto sendAmount = 28235 - dustChange; + + Proto::SigningInput signingInput; + signingInput.set_coin_type(TWCoinTypeBitcoin); + signingInput.set_hash_type(TWBitcoinSigHashTypeAll); + signingInput.set_byte_fee(33); + signingInput.set_amount(sendAmount); + signingInput.set_to_address("bc1q2dsdlq3343vk29runkgv4yc292hmq53jedfjmp"); + signingInput.set_change_address(ownAddress); + signingInput.set_fixed_dust_threshold(dustAmount); + + signingInput.add_private_key(myPrivateKey.bytes.data(), myPrivateKey.bytes.size()); + + // Add UTXO 0 + auto utxo0 = signingInput.add_utxo(); + utxo0->set_script(redeemScript.bytes.data(), redeemScript.bytes.size()); + utxo0->set_amount(utxoAmount0); + utxo0->mutable_out_point()->set_hash( + std::string(utxoHash0.begin(), utxoHash0.end())); + utxo0->mutable_out_point()->set_index(utxoOutputIndex0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + // Add UTXO 1 + auto utxo1 = signingInput.add_utxo(); + utxo1->set_script(redeemScript.bytes.data(), redeemScript.bytes.size()); + utxo1->set_amount(utxoAmount1); + utxo1->mutable_out_point()->set_hash( + std::string(utxoHash1.begin(), utxoHash1.end())); + utxo1->mutable_out_point()->set_index(utxoOutputIndex1); + utxo1->mutable_out_point()->set_sequence(UINT32_MAX); + + // Add UTXO 2 + auto utxo2 = signingInput.add_utxo(); + utxo2->set_script(redeemScript.bytes.data(), redeemScript.bytes.size()); + utxo2->set_amount(utxoAmount2); + utxo2->mutable_out_point()->set_hash( + std::string(utxoHash2.begin(), utxoHash2.end())); + utxo2->mutable_out_point()->set_index(utxoOutputIndex2); + utxo2->mutable_out_point()->set_sequence(UINT32_MAX); + + Proto::TransactionPlan plan; + ANY_PLAN(signingInput, plan, TWCoinTypeBitcoin); + EXPECT_EQ(plan.error(), Common::Proto::OK); + + auto fee = 6897 + dustChange; + // UTXO-2 with 300 satoshis should be filtered out as less than dust. + EXPECT_TRUE(verifyPlan(plan, {4863, 30269}, sendAmount, fee)); + + Proto::SigningOutput output; + ANY_SIGN(signingInput, TWCoinTypeBitcoin); + EXPECT_EQ(output.error(), Common::Proto::OK); + // Successfully broadcasted: https://mempool.space/tx/5d6bf53576a54be4d92cd8abf58d28ecc9ea7956eaf970d24d6bfcb9fcfe9855 + EXPECT_EQ(hex(output.encoded()), "010000000001027d4c6ade48ade606a8f9894222fb30f87b427c5861b0b7a293825ffc8bc1621f0100000000ffffffff8d0e94ed369601d13f32bd653071844dfe7139508d2e71d9c105d1faa58230b30100000000ffffffff01836d0000000000001600145360df8231ac5965147c9d90ca930a2aafb0523202483045022100f95f9ac5d39f4b47dcd8c86daaaeac86374258d9960f922333ba0d5fdaa15b7e0220761794672dc9fbd71398d608f72f5d21a0f6c1306c6b700ad0d82f747c221062012103a11506993946e20ea82686b157bf08f944759f43d91af8d84650ee73a482431c02483045022100eb6ba0dcc64af61b2186b7efdab1ff03784d585ee03437f9a53875e93429db080220015a268d308436d3564b83ceaed90bc7272ca164016298ea855d1936568002a7012103a11506993946e20ea82686b157bf08f944759f43d91af8d84650ee73a482431c00000000"); +} + +// Not enough funds to send requested amount after UTXO dust filtering. +TEST(BitcoinSigning, SignPlanTransactionNotSufficientAfterDustFiltering) { + const auto myPrivateKey = PrivateKey(parse_hex("9ea2172511ed73ae0096be8e593c3b75631700edaf729f1abbae607314a20e35")); + auto myPublicKey = myPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(myPublicKey.bytes)); + auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); + + const auto ownAddress = "bc1qvrt7ukvhvmdny0a3j9k8l8jasx92lrqm30t2u2"; + + auto utxoHash0 = + parse_hex("b33082a5fad105c1d9712e8d503971fe4d84713065bd323fd1019636ed940e8d"); + std::reverse(utxoHash0.begin(), utxoHash0.end()); + auto utxoAmount0 = 30269; + auto utxoOutputIndex0 = 1; + + auto utxoHash1 = + parse_hex("1f62c18bfc5f8293a2b7b061587c427bf830fb224289f9a806e6ad48de6a4c7d"); + std::reverse(utxoHash1.begin(), utxoHash1.end()); + auto utxoAmount1 = 545; + auto utxoOutputIndex1 = 1; + + const auto utxoScript0 = parse_hex("0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); + + const auto dustAmount = 546; + const auto sendAmount = 25620; + + Proto::SigningInput signingInput; + signingInput.set_coin_type(TWCoinTypeBitcoin); + signingInput.set_hash_type(TWBitcoinSigHashTypeAll); + signingInput.set_byte_fee(33); + signingInput.set_amount(sendAmount); + signingInput.set_to_address("bc1q2dsdlq3343vk29runkgv4yc292hmq53jedfjmp"); + signingInput.set_change_address(ownAddress); + signingInput.set_fixed_dust_threshold(dustAmount); + + signingInput.add_private_key(myPrivateKey.bytes.data(), myPrivateKey.bytes.size()); + + // Add UTXO 0 + auto utxo0 = signingInput.add_utxo(); + utxo0->set_script(redeemScript.bytes.data(), redeemScript.bytes.size()); + utxo0->set_amount(utxoAmount0); + utxo0->mutable_out_point()->set_hash( + std::string(utxoHash0.begin(), utxoHash0.end())); + utxo0->mutable_out_point()->set_index(utxoOutputIndex0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + // Add UTXO 1 + auto utxo1 = signingInput.add_utxo(); + utxo1->set_script(redeemScript.bytes.data(), redeemScript.bytes.size()); + utxo1->set_amount(utxoAmount1); + utxo1->mutable_out_point()->set_hash( + std::string(utxoHash1.begin(), utxoHash1.end())); + utxo1->mutable_out_point()->set_index(utxoOutputIndex1); + utxo1->mutable_out_point()->set_sequence(UINT32_MAX); + + // sum() + + Proto::TransactionPlan plan; + ANY_PLAN(signingInput, plan, TWCoinTypeBitcoin); + EXPECT_EQ(plan.error(), Common::Proto::Error_not_enough_utxos); + + // `AnySigner.sign` should return the same error. + Proto::SigningOutput output; + ANY_SIGN(signingInput, TWCoinTypeBitcoin); + EXPECT_EQ(output.error(), Common::Proto::Error_not_enough_utxos); +} + +// Deposit 0.0001 BTC from bc1q2sphzvc2uqmxqte2w9dd4gzy4sy9vvfv0me9ke to 0xa8491D40d4F71A752cA41DA0516AEd80c33a1B56 on ZETA mainnet. +// https://www.zetachain.com/docs/developers/omnichain/bitcoin/#example-1-deposit-btc-into-an-account-in-zevm +TEST(BitcoinSigning, SignDepositBtcToZetaChain) { + const auto myPrivateKey = PrivateKey(parse_hex("428d66be0b5a620f126a00fa67637222ce3dc9badfe5c605189520760810cfac")); + auto myPublicKey = myPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(myPublicKey.bytes)); + auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); + + const auto ownAddress = "bc1q2sphzvc2uqmxqte2w9dd4gzy4sy9vvfv0me9ke"; + const auto ownZetaEvmAddress = parse_hex("a8491D40d4F71A752cA41DA0516AEd80c33a1B56"); + // https://www.zetachain.com/docs/reference/glossary/#tss + const auto zetaTssAddress = "bc1qm24wp577nk8aacckv8np465z3dvmu7ry45el6y"; + + auto utxoHash0 = + parse_hex("17a6adb5db1e33c87467a58aa31cddbb3800052315015cf3cf1c2b0119310e20"); + std::reverse(utxoHash0.begin(), utxoHash0.end()); + auto utxoAmount0 = 20000; + auto utxoOutputIndex0 = 0; + + const auto sendAmount = 10000; + const auto dustAmount = 546; + + Proto::SigningInput signingInput; + signingInput.set_coin_type(TWCoinTypeBitcoin); + signingInput.set_hash_type(TWBitcoinSigHashTypeAll); + signingInput.set_byte_fee(15); + signingInput.set_amount(sendAmount); + signingInput.set_to_address(zetaTssAddress); + signingInput.set_change_address(ownAddress); + signingInput.set_fixed_dust_threshold(dustAmount); + signingInput.set_output_op_return(ownZetaEvmAddress.data(), ownZetaEvmAddress.size()); + // OP_RETURN must be the second output before the change. + signingInput.mutable_output_op_return_index()->set_index(1); + + signingInput.add_private_key(myPrivateKey.bytes.data(), myPrivateKey.bytes.size()); + + // Add UTXO 0 + auto utxo0 = signingInput.add_utxo(); + utxo0->set_script(redeemScript.bytes.data(), redeemScript.bytes.size()); + utxo0->set_amount(utxoAmount0); + utxo0->mutable_out_point()->set_hash( + std::string(utxoHash0.begin(), utxoHash0.end())); + utxo0->mutable_out_point()->set_index(utxoOutputIndex0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + Proto::SigningOutput output; + ANY_SIGN(signingInput, TWCoinTypeBitcoin); + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + + EXPECT_EQ(output.transaction().outputs_size(), 3); + // P2WPKH to the TSS address. + EXPECT_EQ(output.transaction().outputs(0).value(), sendAmount); + // OP_RETURN + EXPECT_EQ(output.transaction().outputs(1).value(), 0); + // Transaction fee. + EXPECT_EQ(output.transaction().outputs(2).value(), 7420); + + // Successfully broadcasted: + // https://mempool.space/tx/2b871b6c1112ad0a777f6db1f7a7709154c4d9af8e771ba4eca148915f830e9d + // https://explorer.zetachain.com/cc/tx/0x269e319478f8849247abb28b33a7b8e0a849dab4551bab328bf58bf67b02a807 + const auto expectedTx = "01000000000101200e3119012b1ccff35c011523050038bbdd1ca38aa56774c8331edbb5ada6170000000000ffffffff031027000000000000160014daaae0d3de9d8fdee31661e61aea828b59be78640000000000000000166a14a8491d40d4f71a752ca41da0516aed80c33a1b56fc1c000000000000160014540371330ae036602f2a715adaa044ac0856312c02483045022100e29731f7474f9103c6df3434c8c62a540a21ad0e10e23df343b1e81e4b26110602202d37fb4fee5341a41f9e4e65ba2d3e0d2309425ea9806d94eb268efe6f21007001210369cdaf80b4a5fdad91e9face90e848225512884ec2e3ed572ca11dc68e75054700000000"; + + EXPECT_EQ(hex(output.encoded()), expectedTx); + + Proto::TransactionPlan plan; + ANY_PLAN(signingInput, plan, TWCoinTypeBitcoin); + EXPECT_EQ(plan.error(), Common::Proto::SigningError::OK); + EXPECT_TRUE(plan.has_output_op_return_index()); + EXPECT_EQ(plan.output_op_return_index().index(), 1); + + *signingInput.mutable_plan() = plan; + ANY_SIGN(signingInput, TWCoinTypeBitcoin); + // The result has to be the same as signing without transaction planning. + EXPECT_EQ(hex(output.encoded()), expectedTx); +} + +TEST(BitcoinSigning, SignP2PKH) { + auto input = buildInputP2PKH(); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 226)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{228, 225, 226})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "01" // inputs + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "6a" "47304402202819d70d4bec472113a1392cadc0860a7a1b34ea0869abb4bdce3290c3aba086022023eff75f410ad19cdbe6c6a017362bd554ce5fb906c13534ddc306be117ad30a012103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432" "ffffffff" + "02" // outputs + "b0bf031400000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "aefd3c1100000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + "00000000" // nLockTime + ); +} + +TEST(BitcoinSigning, SignP2PKH_NegativeMissingKey) { + auto input = buildInputP2PKH(true); + + { + // test plan (but do not reuse plan result). Plan works even with missing keys. + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 226)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Common::Proto::Error_missing_private_key); +} + +TEST(BitcoinSigning, EncodeP2WPKH) { + auto unsignedTx = Transaction(1, 0x11); + + auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); + auto outpoint0 = TW::Bitcoin::OutPoint(hash0, 0); + unsignedTx.inputs.emplace_back(outpoint0, Script(), 0xffffffee); + + auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); + auto outpoint1 = TW::Bitcoin::OutPoint(hash1, 1); + unsignedTx.inputs.emplace_back(outpoint1, Script(), UINT32_MAX); + + auto outScript0 = Script(parse_hex("76a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac")); + unsignedTx.outputs.emplace_back(112340000, outScript0); + + auto outScript1 = Script(parse_hex("76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac")); + unsignedTx.outputs.emplace_back(223450000, outScript1); + + Data unsignedData; + unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); + ASSERT_EQ(unsignedData.size(), 164ul); + ASSERT_EQ(hex(unsignedData), + "01000000" // version + "0001" // marker & flag + "02" // inputs + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "00" "" "eeffffff" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "" "ffffffff" + "02" // outputs + "202cb20600000000" "19" "76a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac" + "9093510d00000000" "19" "76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac" + // witness + "00" + "00" + "11000000" // nLockTime + ); +} + +TEST(BitcoinSigning, SignP2WPKH_Bip143) { + // https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#native-p2wpkh + + SigningInput input; + input.hashType = TWBitcoinSigHashTypeAll; + const auto amount = 112340000; // 0x06B22C20 + input.amount = amount; + input.byteFee = 20; // not relevant + input.toAddress = "1Cu32FVupVCgHkMMRJdYJugxwo2Aprgk7H"; + input.changeAddress = "16TZ8J6Q5iZKBWizWzFAYnrsaox5Z5aBRV"; + + const auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); + const auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); + const auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(pubKey0.bytes), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); + + const auto utxo0Script = Script::buildPayToPublicKey(pubKey0.bytes); + Data key2; + utxo0Script.matchPayToPublicKey(key2); + EXPECT_EQ(hex(key2), hex(pubKey0.bytes)); + input.privateKeys.push_back(utxoKey0); + + const auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); + const auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + const auto pubKey1 = utxoKey1.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(pubKey1.bytes), "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357"); + const auto utxoPubkeyHash1 = Hash::ripemd(Hash::sha256(pubKey1.bytes)); + EXPECT_EQ(hex(utxoPubkeyHash1), "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + input.privateKeys.push_back(utxoKey1); + input.lockTime = 0x11; + + UTXO utxo0; + utxo0.script = utxo0Script; + utxo0.amount = 1000000; // note: this amount is not specified in the test + utxo0.outPoint = OutPoint(hash0, 0, 0xffffffee); + input.utxos.push_back(utxo0); + + UTXO utxo1; + auto utxo1Script = Script::buildPayToV0WitnessProgram(utxoPubkeyHash1); + utxo1.script = utxo1Script; + utxo1.amount = 600000000; // 0x23C34600 0046c323 + utxo1.outPoint = OutPoint(hash1, 1, UINT32_MAX); + input.utxos.push_back(utxo1); + + // Set plan to force both UTXOs and exact output amounts + TransactionPlan plan; + plan.amount = amount; + plan.availableAmount = 600000000 + 1000000; + plan.fee = 265210000; // very large, the amounts specified (in1, out0, out1) are not consistent/realistic + plan.change = 223450000; // 0x0d519390 + plan.branchId = {0}; + plan.utxos.push_back(utxo0); + plan.utxos.push_back(utxo1); + input.plan = plan; + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + const auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{343, 233, 261})); + // expected in one string for easy comparison/copy: + ASSERT_EQ(hex(serialized), "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000494830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac000247304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000"); + // expected in structured format: + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "02" // inputs + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "4830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01" "eeffffff" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "" "ffffffff" + "02" // outputs + "202cb20600000000" "19" "76a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac" + "9093510d00000000" "19" "76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac" + // witness + "00" + "02" + "47" "304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee01" + "21" "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" + "11000000" // nLockTime + ); +} + +SigningInput buildInputP2WPKH(int64_t amount, TWBitcoinSigHashType hashType, int64_t utxo0Amount, int64_t utxo1Amount, bool useMaxAmount = false) { + auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); + auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); + + // Setup input + SigningInput input; + input.hashType = hashType; + input.amount = amount; + input.useMaxAmount = useMaxAmount; + input.byteFee = 1; + input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; + input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; + input.coinType = TWCoinTypeBitcoin; + + auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); + auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash0 = Hash::ripemd(Hash::sha256(pubKey0.bytes)); + assert(hex(utxoPubkeyHash0) == "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); + input.privateKeys.push_back(utxoKey0); + + auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + auto pubKey1 = utxoKey1.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash1 = Hash::ripemd(Hash::sha256(pubKey1.bytes)); + assert(hex(utxoPubkeyHash1) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + input.privateKeys.push_back(utxoKey1); + + auto scriptPub1 = Script(parse_hex("0014" + "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + Data scriptHash; + scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); + auto scriptHashHex = hex(scriptHash); + assert(scriptHashHex == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + + auto redeemScript = Script::buildPayToPublicKeyHash(parse_hex("1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + input.scripts[scriptHashHex] = redeemScript; + + UTXO utxo0; + utxo0.script = Script(parse_hex("2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac")); + utxo0.amount = utxo0Amount; + utxo0.outPoint = OutPoint(hash0, 0, UINT32_MAX); + input.utxos.push_back(utxo0); + + UTXO utxo1; + utxo1.script = Script(parse_hex("0014" + "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + utxo1.amount = utxo1Amount; + utxo1.outPoint = OutPoint(hash1, 1, UINT32_MAX); + input.utxos.push_back(utxo1); + + return input; +} + +TEST(BitcoinSigning, SignP2WPKH) { + auto input = buildInputP2WPKH(335'790'000, TWBitcoinSigHashTypeAll, 625'000'000, 600'000'000); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 192)); + } + + // Signs + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{195, 192, 193})); + EXPECT_EQ(serialized.size(), 192ul); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "01" // inputs + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "483045022100c327babdd370f0fc5b24cf920736446bf7d9c5660e4a5f7df432386fd652fe280220269c4fc3690c1c248e50c8bf2435c20b4ef00f308b403575f4437f862a91c53a01" "ffffffff" + "02" // outputs + "b0bf031400000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "d0fd3c1100000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + "00000000" // nLockTime + ); + + { + // Non-segwit encoded, for comparison + Data serialized_; + signedTx.encode(serialized_, Transaction::SegwitFormatMode::NonSegwit); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{195, 192, 193})); + EXPECT_EQ(serialized_.size(), 192ul); + ASSERT_EQ(hex(serialized_), // printed using prettyPrintTransaction + "01000000" // version + "01" // inputs + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "483045022100c327babdd370f0fc5b24cf920736446bf7d9c5660e4a5f7df432386fd652fe280220269c4fc3690c1c248e50c8bf2435c20b4ef00f308b403575f4437f862a91c53a01" "ffffffff" + "02" // outputs + "b0bf031400000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "d0fd3c1100000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + "00000000" // nLockTime + ); + } +} + +TEST(BitcoinSigning, SignP2WPKH_HashSingle_TwoInput) { + auto input = buildInputP2WPKH(335'790'000, TWBitcoinSigHashTypeSingle, 210'000'000, 210'000'000); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {210'000'000, 210'000'000}, 335'790'000, 261)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{343, 233, 261})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "02" // inputs + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "483045022100fd8591c3611a07b55f509ec850534c7a9c49713c9b8fa0e844ea06c2e65e19d702205e3806676192e790bc93dd4c28e937c4bf97b15f189158ba1a30d7ecff5ee75503" "ffffffff" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "" "ffffffff" + "02" // outputs + "b0bf031400000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "4bf0040500000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + // witness + "00" + "02" "47" "30440220096d20c7e92f991c2bf38dc28118feb34019ae74ec1c17179b28cb041de7517402204594f46a911f24bdc7109ca192e6860ebf2f3a0087579b3c128d5ce0cd5ed46803" "21" "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" + "00000000" // nLockTime + ); +} + +TEST(BitcoinSigning, SignP2WPKH_HashAnyoneCanPay_TwoInput) { + auto input = buildInputP2WPKH(335'790'000, TWBitcoinSigHashTypeAnyoneCanPay, 210'000'000, 210'000'000); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {210'000'000, 210'000'000}, 335'790'000, 261)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{344, 233, 261})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "02" // inputs + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "483045022100e21fb2f1cfd59bdb3703fd45db38fd680d0c06e5d0be86fb7dc233c07ee7ab2f02207367220a73e43df4352a6831f6f31d8dc172c83c9f613a9caf679f0f15621c5e80" "ffffffff" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "" "ffffffff" + "02" // outputs + "b0bf031400000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "4bf0040500000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + // witness + "00" + "02" "48" "304502210095f9cc913d2f0892b953f2380112533e8930b67c53e00a7bbd7a01d547156adc022026efe3a684aa7432a00a919dbf81b63e635fb92d3149453e95b4a7ccea59f7c480" "21" "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" + "00000000" // nLockTime + ); +} + +TEST(BitcoinSigning, SignP2WPKH_MaxAmount) { + auto input = buildInputP2WPKH(1'000, TWBitcoinSigHashTypeAll, 625'000'000, 600'000'000, true); + input.amount = 1'224'999'773; + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {625'000'000, 600'000'000}, 1'224'999'773, 227)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{310, 199, 227})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "02" // inputs + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" "00000000" "49" "483045022100a8b3c1619e985923994e80efdc0be0eac12f2419e11ce5e4286a0a5ac27c775d02205d6feee85ffe19ae0835cba1562beb3beb172107cd02ac4caf24a8be3749811f01" "ffffffff" + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" "01000000" "00" "" "ffffffff" + "01" // outputs + "5d03044900000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + // witness + "00" + "02" "48" "3045022100db1199de92f6fb638a0ba706d13ec686bb01138a254dec2c397616cd74bad30e02200d7286d6d2d4e00d145955bf3d3b848b03c0d1eef8899e4645687a3035d7def401" "21" "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" + "00000000" // nLockTime + ); +} + +TEST(BitcoinSigning, EncodeP2WSH) { + auto unsignedTx = Transaction(1); + + auto outpoint0 = OutPoint(parse_hex("0001000000000000000000000000000000000000000000000000000000000000"), 0); + unsignedTx.inputs.emplace_back(outpoint0, Script(), UINT32_MAX); + + auto outScript0 = Script(parse_hex("76a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac")); + unsignedTx.outputs.emplace_back(1000, outScript0); + + Data unsignedData; + unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::NonSegwit); + ASSERT_EQ(hex(unsignedData), + "01000000" // version + "01" // inputs + "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "" "ffffffff" + "01" // outputs + "e803000000000000" "19" "76a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac" + "00000000" // nLockTime + ); +} + +SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType, bool omitScript = false, bool omitKeys = false) { + SigningInput input; + input.hashType = hashType; + input.amount = 1000; + input.byteFee = 1; + input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; + input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; + // Set the very low fixed Dust threshold just to fix the tests. + // Actually, transactions in these tests have change=79 and change=52 that will lead to Dust error when broadcasting it. + input.dustCalculator = std::make_shared(50); + + if (!omitKeys) { + auto utxoKey0 = PrivateKey(parse_hex("ed00a0841cd53aedf89b0c616742d1d2a930f8ae2b0fb514765a17bb62c7521a")); + input.privateKeys.push_back(utxoKey0); + + auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + input.privateKeys.push_back(utxoKey1); + } + + if (!omitScript) { + auto redeemScript = Script(parse_hex("2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac")); + auto scriptHash = "593128f9f90e38b706c18623151e37d2da05c229"; + input.scripts[scriptHash] = redeemScript; + } + + UTXO utxo0; + auto p2wsh = Script::buildPayToWitnessScriptHash(parse_hex("ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db")); + utxo0.script = p2wsh; + utxo0.amount = 1226; + auto hash0 = parse_hex("0001000000000000000000000000000000000000000000000000000000000000"); + utxo0.outPoint = OutPoint(hash0, 0, UINT32_MAX); + input.utxos.push_back(utxo0); + + return input; +} + +TEST(BitcoinSigning, SignP2WSH) { + // Setup input + const auto input = buildInputP2WSH(hashTypeForCoin(TWCoinTypeBitcoin)); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 147)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{231, 119, 147})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "" "ffffffff" + "02" // outputs + "e803000000000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "4f00000000000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + // witness + "02" "48" "30450221009eefc1befe96158f82b74e6804f1f713768c6172636ca11fcc975c316ea86f75022057914c48bc24f717498b851a47a2926f96242e3943ebdf08d5a97a499efc8b9001" "23" "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" + "00000000" // nLockTime + ); +} + +TEST(BitcoinSigning, SignP2WSH_HashNone) { + // Setup input + const auto input = buildInputP2WSH(TWBitcoinSigHashTypeNone); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 147)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{231, 119, 147})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "" "ffffffff" + "02" // outputs + "e803000000000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "4f00000000000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + // witness + "02" "48" "3045022100caa585732cfc50226a90834a306d23d5d2ab1e94af2c66136a637e3d9bad3688022069028750908e53a663bb1f434fd655bcc0cf8d394c6fa1fd5a4983790135722e02" "23" "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" + "00000000" // nLockTime + ); +} + +TEST(BitcoinSigning, SignP2WSH_HashSingle) { + // Setup input + const auto input = buildInputP2WSH(TWBitcoinSigHashTypeSingle); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 147)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{230, 119, 147})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "" "ffffffff" + "02" // outputs + "e803000000000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "4f00000000000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + // witness + "02" "47" "304402201ba80b2c48fe82915297dc9782ae2141e40263001fafd21b02c04a092503f01e0220666d6c63475c6c52abd09371c200ac319bcf4a7c72eb3782e95790f5c847f0b903" "23" "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" + "00000000" // nLockTime + ); +} + +TEST(BitcoinSigning, SignP2WSH_HashAnyoneCanPay) { + // Setup input + const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAnyoneCanPay); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 147)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(serialized.size(), 231ul); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{231, 119, 147})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "0001000000000000000000000000000000000000000000000000000000000000" "00000000" "00" "" "ffffffff" + "02" // outputs + "e803000000000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "4f00000000000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + // witness + "02" "48" "3045022100d14699fc9b7337768bcd1430098d279cfaf05f6abfa75dd542da2dc038ae1700022063f0751c08796c086ac23b39c25f4320f432092e0c11bec46af0723cc4f55a3980" "23" "2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac" + "00000000" // nLockTime + ); +} + +TEST(BitcoinSigning, SignP2WSH_NegativeMissingScript) { + const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll, true); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 174)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Common::Proto::Error_script_redeem); +} + +TEST(BitcoinSigning, SignP2WSH_NegativeMissingKeys) { + const auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll, false, true); + + { + // test plan (but do not reuse plan result). Plan works even with missing keys. + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'226}, 1'000, 147)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Common::Proto::Error_missing_private_key); +} + +TEST(BitcoinSigning, SignP2WSH_NegativePlanWithError) { + // Setup input + auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll); + input.plan = TransactionBuilder::plan(input); + input.plan->error = Common::Proto::Error_missing_input_utxos; + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Common::Proto::Error_missing_input_utxos); +} + +TEST(BitcoinSigning, SignP2WSH_NegativeNoUTXOs) { + // Setup input + auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll); + input.utxos.clear(); + ASSERT_FALSE(input.plan.has_value()); + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Common::Proto::Error_missing_input_utxos); +} + +TEST(BitcoinSigning, SignP2WSH_NegativePlanWithNoUTXOs) { + // Setup input + auto input = buildInputP2WSH(TWBitcoinSigHashTypeAll); + input.plan = TransactionBuilder::plan(input); + input.plan->utxos.clear(); + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Common::Proto::Error_missing_input_utxos); +} + +TEST(BitcoinSigning, EncodeP2SH_P2WPKH) { + auto unsignedTx = Transaction(1, 0x492); + + auto outpoint0 = OutPoint(parse_hex("db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477"), 1); + unsignedTx.inputs.emplace_back(outpoint0, Script(), 0xfffffffe); + + auto outScript0 = Script(parse_hex("76a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac")); + unsignedTx.outputs.emplace_back(199'996'600, outScript0); + + auto outScript1 = Script(parse_hex("76a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac")); + unsignedTx.outputs.emplace_back(800'000'000, outScript1); + + Data unsignedData; + unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::NonSegwit); + ASSERT_EQ(hex(unsignedData), + "01000000" // version + "01" // inputs + "db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477" "01000000" "00" "" "feffffff" + "02" // outputs + "b8b4eb0b00000000" "19" "76a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac" + "0008af2f00000000" "19" "76a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac" + "92040000" // nLockTime + ); +} + +SigningInput buildInputP2SH_P2WPKH(bool omitScript = false, bool omitKeys = false, bool invalidOutputScript = false, bool invalidRedeemScript = false) { + // Setup input + SigningInput input; + input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); + input.amount = 200'000'000; + input.byteFee = 1; + input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; + input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; + input.coinType = TWCoinTypeBitcoin; + + auto utxoKey0 = PrivateKey(parse_hex("eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf")); + auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); + auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey0.bytes)); + assert(hex(utxoPubkeyHash) == "79091972186c449eb1ded22b78e40d009bdf0089"); + if (!omitKeys) { + input.privateKeys.push_back(utxoKey0); + } + + if (!omitScript && !invalidRedeemScript) { + auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); + auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); + assert(hex(scriptHash) == "4733f37cf4db86fbc2efed2500b4f4e49f312023"); + input.scripts[hex(scriptHash)] = redeemScript; + } else if (invalidRedeemScript) { + auto redeemScript = Script(parse_hex("FAFBFCFDFE")); + auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); + input.scripts[hex(scriptHash)] = redeemScript; + } + + UTXO utxo0; + auto utxo0Script = Script(parse_hex("a9144733f37cf4db86fbc2efed2500b4f4e49f31202387")); + if (invalidOutputScript) { + utxo0Script = Script(parse_hex("FFFEFDFCFB")); + } + utxo0.script = utxo0Script; + utxo0.amount = 1'000'000'000; + auto hash0 = parse_hex("db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477"); + utxo0.outPoint = OutPoint(hash0, 1, UINT32_MAX); + input.utxos.push_back(utxo0); + + return input; +} + +TEST(BitcoinSigning, SignP2SH_P2WPKH) { + auto input = buildInputP2SH_P2WPKH(); + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'000'000'000}, 200'000'000, 170)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{251, 142, 170})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477" "01000000" "17" "16001479091972186c449eb1ded22b78e40d009bdf0089" "ffffffff" + "02" // outputs + "00c2eb0b00000000" "19" "76a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "5607af2f00000000" "19" "76a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + // witness + "02" "47" "3044022062b408cc7f92c8add622f3297b8992d68403849c6421ef58274ed6fc077102f30220250696eacc0aad022f55882d742dda7178bea780c03705bf9cdbee9f812f785301" "21" "03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873" + "00000000" // nLockTime + ); +} + +TEST(BitcoinSigning, SignP2SH_P2WPKH_NegativeOmitScript) { + auto input = buildInputP2SH_P2WPKH(true, false); + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'000'000'000}, 200'000'000, 174)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Common::Proto::Error_script_redeem); +} + +TEST(BitcoinSigning, SignP2SH_P2WPKH_NegativeInvalidOutputScript) { + auto input = buildInputP2SH_P2WPKH(false, false, true); + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'000'000'000}, 200'000'000, 174)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Common::Proto::Error_script_output); +} + +TEST(BitcoinSigning, SignP2SH_P2WPKH_NegativeInvalidRedeemScript) { + auto input = buildInputP2SH_P2WPKH(false, false, false, true); + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'000'000'000}, 200'000'000, 174)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Common::Proto::Error_script_redeem); +} + +TEST(BitcoinSigning, SignP2SH_P2WPKH_NegativeOmitKeys) { + auto input = buildInputP2SH_P2WPKH(false, true); + { + // test plan (but do not reuse plan result). Plan works even with missing keys. + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'000'000'000}, 200'000'000, 170)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Common::Proto::Error_missing_private_key); +} + +TEST(BitcoinSigning, EncodeP2SH_P2WSH) { + auto unsignedTx = Transaction(1); + + auto hash0 = parse_hex("36641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e"); + auto outpoint0 = OutPoint(hash0, 1); + unsignedTx.inputs.emplace_back(outpoint0, Script(), 0xffffffff); + + auto outScript0 = Script(parse_hex("76a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688ac")); + unsignedTx.outputs.emplace_back(0x0000000035a4e900, outScript0); + + auto outScript1 = Script(parse_hex("76a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac")); + unsignedTx.outputs.emplace_back(0x00000000052f83c0, outScript1); + + Data unsignedData; + unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::NonSegwit); + ASSERT_EQ(hex(unsignedData), + "01000000" // version + "01" // inputs + "36641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e" "01000000" "00" "" "ffffffff" + "02" // outputs + "00e9a43500000000" "19" "76a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688ac" + "c0832f0500000000" "19" "76a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac" + "00000000" // nLockTime + ); +} + +TEST(BitcoinSigning, SignP2SH_P2WSH) { + // Setup signing input + SigningInput input; + input.amount = 900000000; + input.hashType = (TWBitcoinSigHashType)0; + input.toAddress = "16AQVuBMt818u2HBcbxztAZTT2VTDKupPS"; + input.changeAddress = "1Bd1VA2bnLjoBk4ook3H19tZWETk8s6Ym5"; + + auto key0 = parse_hex("730fff80e1413068a05b57d6a58261f07551163369787f349438ea38ca80fac6"); + input.privateKeys.push_back(PrivateKey(key0)); + auto key1 = parse_hex("11fa3d25a17cbc22b29c44a484ba552b5a53149d106d3d853e22fdd05a2d8bb3"); + input.privateKeys.push_back(PrivateKey(key1)); + auto key2 = parse_hex("77bf4141a87d55bdd7f3cd0bdccf6e9e642935fec45f2f30047be7b799120661"); + input.privateKeys.push_back(PrivateKey(key2)); + auto key3 = parse_hex("14af36970f5025ea3e8b5542c0f8ebe7763e674838d08808896b63c3351ffe49"); + input.privateKeys.push_back(PrivateKey(key3)); + auto key4 = parse_hex("fe9a95c19eef81dde2b95c1284ef39be497d128e2aa46916fb02d552485e0323"); + input.privateKeys.push_back(PrivateKey(key4)); + auto key5 = parse_hex("428a7aee9f0c2af0cd19af3cf1c78149951ea528726989b2e83e4778d2c3f890"); + input.privateKeys.push_back(PrivateKey(key5)); + + auto redeemScript = Script::buildPayToWitnessScriptHash(parse_hex("a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54")); + auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); + input.scripts[hex(scriptHash)] = redeemScript; + + auto witnessScript = Script(parse_hex( + "56" + "210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3" + "2103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b" + "21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a" + "21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4" + "2103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16" + "2102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b" + "56ae" + )); + auto witnessScriptHash = Hash::ripemd(Hash::sha256(witnessScript.bytes)); + input.scripts[hex(witnessScriptHash)] = witnessScript; + + auto utxo0Script = Script(parse_hex("a9149993a429037b5d912407a71c252019287b8d27a587")); + UTXO utxo; + utxo.outPoint = OutPoint(parse_hex("36641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e"), 1, UINT32_MAX); + utxo.script = utxo0Script; + utxo.amount = 987654321; + input.utxos.push_back(utxo); + + TransactionPlan plan; + plan.amount = input.amount; + plan.availableAmount = input.utxos[0].amount; + plan.change = 87000000; + plan.fee = plan.availableAmount - plan.amount - plan.change; + plan.utxos = input.utxos; + input.plan = plan; + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + auto expected = + "01000000" // version + "0001" // marker & flag + "01" // inputs + "36641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e" "01000000" "23" "220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54" "ffffffff" + "02" // outputs + "00e9a43500000000" "19" "76a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688ac" + "c0832f0500000000" "19" "76a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac" + // witness + "08" + "00" "" + "47" "304402201992f5426ae0bab04cf206d7640b7e00410297bfe5487637f6c2427ee8496be002204ad4e64dc2d269f593cc4820db1fc1e8dc34774f602945115ce485940e05c64200" + "47" "304402201e412363fa554b994528fd44149f3985b18bb901289ef6b71105b27c7d0e336c0220595e4a1e67154337757562ed5869127533e3e5084c3c2e128518f5f0b85b721800" + "47" "3044022003b0a20ccf545b3f12c5ade10db8717e97b44da2e800387adfd82c95caf529d902206aee3a2395530d52f476d0ddd9d20ba062820ae6f4e1be4921c3630395743ad900" + "48" "3045022100ed7a0eeaf72b84351bceac474b0c0510f67065b1b334f77e6843ed102f968afe022004d97d0cfc4bf5651e46487d6f87bd4af6aef894459f9778f2293b0b2c5b7bc700" + "48" "3045022100934a0c364820588154aed2d519cbcc61969d837b91960f4abbf0e374f03aa39d022036b5c58b754bd44cb5c7d34806c89d9778ea1a1c900618a841e9fbfbe805ff9b00" + "47" "3044022044e3b59b06931d46f857c82fa1d53d89b116a40a581527eac35c5eb5b7f0785302207d0f8b5d063ffc6749fb4e133db7916162b540c70dee40ec0b21e142d8843b3a00" + "cf" "56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae" + "00000000" // nLockTime + ; + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{800, 154, 316})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), expected); +} + +TEST(BitcoinSigning, Sign_NegativeNoUtxos) { + auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); + auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); + + // Setup input + SigningInput input; + input.hashType = TWBitcoinSigHashTypeAll; + input.amount = 335'790'000; + input.byteFee = 1; + input.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"; + input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; + + auto scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + Data scriptHash; + scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); + auto scriptHashHex = hex(scriptHash); + ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + + auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); + input.scripts[scriptHashHex] = redeemScript; + + { + // plan returns empty, as there are 0 utxos + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {}, 0, 0, Common::Proto::Error_missing_input_utxos)); + } + + // Invoke Sign nonetheless + auto result = TransactionSigner::sign(input); + + // Fails as there are 0 utxos + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Common::Proto::Error_missing_input_utxos); +} + +TEST(BitcoinSigning, Sign_NegativeInvalidAddress) { + auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); + auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); + + // Setup input + SigningInput input; + input.hashType = TWBitcoinSigHashTypeAll; + input.amount = 335'790'000; + input.byteFee = 1; + input.toAddress = "THIS-IS-NOT-A-BITCOIN-ADDRESS"; + input.changeAddress = "THIS-IS-NOT-A-BITCOIN-ADDRESS-EITHER"; + + auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); + input.privateKeys.push_back(utxoKey0); + + auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + input.privateKeys.push_back(utxoKey1); + + auto scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + Data scriptHash; + scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); + auto scriptHashHex = hex(scriptHash); + ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + + auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); + input.scripts[scriptHashHex] = redeemScript; + + UTXO utxo0; + auto utxo0Script = Script(parse_hex("2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac")); + utxo0.script = utxo0Script; + utxo0.amount = 625'000'000; + utxo0.outPoint = OutPoint(hash0, 0, UINT32_MAX); + input.utxos.push_back(utxo0); + + UTXO utxo1; + auto utxo1Script = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + utxo1.script = utxo1Script; + utxo1.amount = 600'000'000; + utxo1.outPoint = OutPoint(hash1, 1, UINT32_MAX); + input.utxos.push_back(utxo1); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(std::move(input)); + EXPECT_TRUE(verifyPlan(plan, {625'000'000}, 335'790'000, 174)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Common::Proto::Error_invalid_address); +} + +TEST(BitcoinSigning, Plan_10input_MaxAmount) { + auto ownAddress = "bc1q0yy3juscd3zfavw76g4h3eqdqzda7qyf58rj4m"; + auto ownPrivateKey = "eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf"; + + SigningInput input; + + for (int i = 0; i < 10; ++i) { + auto utxoScript = Script::lockScriptForAddress(ownAddress, TWCoinTypeBitcoin); + Data keyHash; + EXPECT_TRUE(utxoScript.matchPayToWitnessPublicKeyHash(keyHash)); + EXPECT_EQ(hex(keyHash), "79091972186c449eb1ded22b78e40d009bdf0089"); + + auto redeemScript = Script::buildPayToPublicKeyHash(keyHash); + input.scripts[std::string(keyHash.begin(), keyHash.end())] = redeemScript; + + UTXO utxo; + utxo.script = utxoScript; + utxo.amount = 1'000'000 + i * 10'000; + auto hash = parse_hex("a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407"); + std::reverse(hash.begin(), hash.end()); + utxo.outPoint = OutPoint(hash, 0, UINT32_MAX); + input.utxos.push_back(utxo); + } + + input.coinType = TWCoinTypeBitcoin; + input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); + input.useMaxAmount = true; + input.amount = 2'000'000; + input.byteFee = 1; + input.toAddress = "bc1qauwlpmzamwlf9tah6z4w0t8sunh6pnyyjgk0ne"; + input.changeAddress = ownAddress; + + // Plan. + // Estimated size: witness size: 10 * (1 + 1 + 72 + 1 + 33) + 2 = 1082; base 451; raw 451 + 1082 = 1533; vsize 451 + 1082/4 --> 722 + // Actual size: witness size: 1078; base 451; raw 451 + 1078 = 1529; vsize 451 + 1078/4 --> 721 + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {1'000'000, 1'010'000, 1'020'000, 1'030'000, 1'040'000, 1'050'000, 1'060'000, 1'070'000, 1'080'000, 1'090'000}, 10'449'278, 722)); + + // Extend input with keys, reuse plan, Sign + auto privKey = PrivateKey(parse_hex(ownPrivateKey)); + input.privateKeys.push_back(privKey); + input.plan = plan; + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{1529, 451, 721})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + + ASSERT_EQ(serialized.size(), 1529ul); +} + +TEST(BitcoinSigning, Sign_LitecoinReal_a85f) { + auto coin = TWCoinTypeLitecoin; + auto ownAddress = "ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00"; + auto ownPrivateKey = "b820f41f96c8b7442f3260acd23b3897e1450b8c7c6580136a3c2d3a14e34674"; + + // Setup input + SigningInput input; + input.coinType = coin; + input.hashType = hashTypeForCoin(coin); + input.amount = 3'899'774; + input.useMaxAmount = true; + input.byteFee = 1; + input.toAddress = "ltc1q0dvup9kzplv6yulzgzzxkge8d35axkq4n45hum"; + input.changeAddress = ownAddress; + + auto privKey = PrivateKey(parse_hex(ownPrivateKey)); + input.privateKeys.push_back(privKey); + + auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); + Data keyHash0; + EXPECT_TRUE(utxo0Script.matchPayToWitnessPublicKeyHash(keyHash0)); + EXPECT_EQ(hex(keyHash0), "5c74be45eb45a3459050667529022d9df8a1ecff"); + + auto redeemScript = Script::buildPayToPublicKeyHash(keyHash0); + input.scripts[std::string(keyHash0.begin(), keyHash0.end())] = redeemScript; + + UTXO utxo0; + utxo0.script = utxo0Script; + utxo0.amount = 3'900'000; + auto hash0 = parse_hex("7051cd18189401a844abf0f9c67e791315c4c154393870453f8ad98a818efdb5"); + std::reverse(hash0.begin(), hash0.end()); + utxo0.outPoint = OutPoint(hash0, 9, UINT32_MAX - 1); + input.utxos.push_back(utxo0); + + // set plan, to match real tx + TransactionPlan plan; + plan.availableAmount = 3'900'000; + plan.amount = 3'899'774; + plan.fee = 226; + plan.change = 0; + plan.utxos.push_back(input.utxos[0]); + input.plan = plan; + EXPECT_TRUE(verifyPlan(input.plan.value(), {3'900'000}, 3'899'774, 226)); + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + + // https://blockchair.com/litecoin/transaction/a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407 + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "b5fd8e818ad98a3f4570383954c1c41513797ec6f9f0ab44a801941818cd5170" "09000000" "00" "" "feffffff" + "01" // outputs + "7e813b0000000000" "16" "00147b59c096c20fd9a273e240846b23276c69d35815" + // witness + "02" + "47" "3044022029153096af176f9cca0ba9b827e947689a8bb8d11dda570c880f9108bc590b3002202410c78b666722ade1ef4547ad85a128ddcbd4695c40f942457bea3d043b9bb301" + "21" "036739829f2cfec79cfe6aaf1c22ecb7d4867dfd8ab4deb7121b36a00ab646caed" + "00000000" // nLockTime + ); +} + +TEST(BitcoinSigning, PlanAndSign_LitecoinReal_8435) { + auto coin = TWCoinTypeLitecoin; + auto ownAddress = "ltc1q0dvup9kzplv6yulzgzzxkge8d35axkq4n45hum"; + auto ownPrivateKey = "690b34763f34e0226ad2a4d47098269322e0402f847c97166e8f39959fcaff5a"; + + // Setup input for Plan + SigningInput input; + input.coinType = coin; + input.hashType = hashTypeForCoin(coin); + input.amount = 1'200'000; + input.useMaxAmount = false; + input.byteFee = 1; + input.toAddress = "ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00"; + input.changeAddress = ownAddress; + + auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); + Data keyHash0; + EXPECT_TRUE(utxo0Script.matchPayToWitnessPublicKeyHash(keyHash0)); + EXPECT_EQ(hex(keyHash0), "7b59c096c20fd9a273e240846b23276c69d35815"); + + auto redeemScript = Script::buildPayToPublicKeyHash(keyHash0); + input.scripts[std::string(keyHash0.begin(), keyHash0.end())] = redeemScript; + + UTXO utxo0; + utxo0.script = utxo0Script; + utxo0.amount = 3'899'774; + auto hash0 = parse_hex("a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407"); + std::reverse(hash0.begin(), hash0.end()); + utxo0.outPoint = OutPoint(hash0, 0, UINT32_MAX); + input.utxos.push_back(utxo0); + + // Plan + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {3'899'774}, 1'200'000, 141)); + + // Extend input with keys and plan, for Sign + auto privKey = PrivateKey(parse_hex(ownPrivateKey)); + input.privateKeys.push_back(privKey); + input.plan = plan; + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{222, 113, 141})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + + // https://blockchair.com/litecoin/transaction/8435d205614ee70066060734adf03af4194d0c3bc66dd01bb124ab7fd25e2ef8 + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8" "00000000" "00" "" "ffffffff" + "02" // outputs + "804f120000000000" "16" "00145c74be45eb45a3459050667529022d9df8a1ecff" + "7131290000000000" "16" "00147b59c096c20fd9a273e240846b23276c69d35815" + // witness + "02" + "47" "304402204139b82927dd80445f27a5d2c29fa4881dbd2911714452a4a706145bc43cc4bf022016fbdf4b09bc5a9c43e79edb1c1061759779a20c35535082bdc469a61ed0771f01" + "21" "02499e327a05cc8bb4b3c34c8347ecfcb152517c9927c092fa273be5379fde3226" + "00000000" // nLockTime + ); +} + +TEST(BitcoinSigning, Sign_ManyUtxos_400) { + auto ownAddress = "bc1q0yy3juscd3zfavw76g4h3eqdqzda7qyf58rj4m"; + auto ownPrivateKey = "eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf"; + + // Setup input + SigningInput input; + + const auto n = 400; + uint64_t utxoSum = 0; + for (int i = 0; i < n; ++i) { + auto utxoScript = Script::lockScriptForAddress(ownAddress, TWCoinTypeBitcoin); + Data keyHash; + EXPECT_TRUE(utxoScript.matchPayToWitnessPublicKeyHash(keyHash)); + EXPECT_EQ(hex(keyHash), "79091972186c449eb1ded22b78e40d009bdf0089"); + + auto redeemScript = Script::buildPayToPublicKeyHash(keyHash); + input.scripts[std::string(keyHash.begin(), keyHash.end())] = redeemScript; + + UTXO utxo; + utxo.script = utxoScript; + utxo.amount = 1000 + (i + 1) * 10; + auto hash = parse_hex("a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407"); + std::reverse(hash.begin(), hash.end()); + utxo.outPoint = OutPoint(hash, 0, UINT32_MAX); + input.utxos.push_back(utxo); + utxoSum += utxo.amount; + } + EXPECT_EQ(utxoSum, 1'202'000ul); + + input.coinType = TWCoinTypeBitcoin; + input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); + input.useMaxAmount = false; + input.amount = 300'000; + input.byteFee = 1; + input.toAddress = "bc1qauwlpmzamwlf9tah6z4w0t8sunh6pnyyjgk0ne"; + input.changeAddress = ownAddress; + + // Plan + auto plan = TransactionBuilder::plan(input); + + // expected result: 66 utxos, with the largest amounts + std::vector subset; + uint64_t subsetSum = 0; + for (int i = n - 66; i < n; ++i) { + const uint64_t val = 1000 + (i + 1) * 10; + subset.push_back(val); + subsetSum += val; + } + EXPECT_EQ(subset.size(), 66ul); + EXPECT_EQ(subsetSum, 308'550ul); + EXPECT_TRUE(verifyPlan(plan, subset, 300'000, 4'561)); + + // Extend input with keys, reuse plan, Sign + auto privKey = PrivateKey(parse_hex(ownPrivateKey)); + input.privateKeys.push_back(privKey); + input.plan = plan; + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + + EXPECT_EQ(serialized.size(), 9871ul); +} + +TEST(BitcoinSigning, Sign_ManyUtxos_2000) { + auto ownAddress = "bc1q0yy3juscd3zfavw76g4h3eqdqzda7qyf58rj4m"; + auto ownPrivateKey = "eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf"; + + // Setup input + SigningInput input; + + const auto n = 2000; + uint64_t utxoSum = 0; + for (int i = 0; i < n; ++i) { + auto utxoScript = Script::lockScriptForAddress(ownAddress, TWCoinTypeBitcoin); + Data keyHash; + EXPECT_TRUE(utxoScript.matchPayToWitnessPublicKeyHash(keyHash)); + EXPECT_EQ(hex(keyHash), "79091972186c449eb1ded22b78e40d009bdf0089"); + + auto redeemScript = Script::buildPayToPublicKeyHash(keyHash); + input.scripts[std::string(keyHash.begin(), keyHash.end())] = redeemScript; + + UTXO utxo; + utxo.script = utxoScript; + utxo.amount = 1000 + (i + 1) * 10; + auto hash = parse_hex("a85fd6a9a7f2f54cacb57e83dfd408e51c0a5fc82885e3fa06be8692962bc407"); + std::reverse(hash.begin(), hash.end()); + utxo.outPoint = OutPoint(hash, 0, UINT32_MAX); + input.utxos.push_back(utxo); + utxoSum += utxo.amount; + } + EXPECT_EQ(utxoSum, 22'010'000ul); + + input.coinType = TWCoinTypeBitcoin; + input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); + input.useMaxAmount = false; + input.amount = 2'000'000; + input.byteFee = 1; + input.toAddress = "bc1qauwlpmzamwlf9tah6z4w0t8sunh6pnyyjgk0ne"; + input.changeAddress = ownAddress; + + // Plan + auto plan = TransactionBuilder::plan(input); + + // expected result: 601 utxos (smaller ones) + std::vector subset; + uint64_t subsetSum = 0; + for (int i = 0; i < 601; ++i) { + const uint64_t val = 1000 + (i + 1) * 10; + subset.push_back(val); + subsetSum += val; + } + EXPECT_EQ(subset.size(), 601ul); + EXPECT_EQ(subsetSum, 2'410'010ul); + EXPECT_TRUE(verifyPlan(plan, subset, 2'000'000, 40'943)); + + // Extend input with keys, reuse plan, Sign + auto privKey = PrivateKey(parse_hex(ownPrivateKey)); + input.privateKeys.push_back(privKey); + input.plan = plan; + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + + EXPECT_EQ(serialized.size(), 89'339ul); +} + +TEST(BitcoinSigning, EncodeThreeOutput) { + auto coin = TWCoinTypeLitecoin; + auto ownAddress = "ltc1qt36tu30tgk35tyzsve6jjq3dnhu2rm8l8v5q00"; + auto ownPrivateKey = "b820f41f96c8b7442f3260acd23b3897e1450b8c7c6580136a3c2d3a14e34674"; + auto toAddress0 = "ltc1qgknskahmm6svn42e33gum5wc4dz44wt9vc76q4"; + auto toAddress1 = "ltc1qulgtqdgxyd9nxnn5yxft6jykskz0ffl30nu32z"; + auto utxo0Amount = 3'851'829; + auto toAmount0 = 1'000'000; + auto toAmount1 = 2'000'000; + + auto unsignedTx = Transaction(1); + + auto hash0 = parse_hex("bbe736ada63c4678025dff0ff24d5f38970a3e4d7a2f77808689ed68004f55fe"); + std::reverse(hash0.begin(), hash0.end()); + auto outpoint0 = TW::Bitcoin::OutPoint(hash0, 0); + unsignedTx.inputs.emplace_back(outpoint0, Script(), UINT32_MAX); + + auto lockingScript0 = Script::lockScriptForAddress(toAddress0, coin); + unsignedTx.outputs.emplace_back(toAmount0, lockingScript0); + auto lockingScript1 = Script::lockScriptForAddress(toAddress1, coin); + unsignedTx.outputs.emplace_back(toAmount1, lockingScript1); + // change + auto lockingScript2 = Script::lockScriptForAddress(ownAddress, coin); + unsignedTx.outputs.emplace_back(utxo0Amount - toAmount0 - toAmount1 - 172, lockingScript2); + + Data unsignedData; + unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); + EXPECT_EQ(unsignedData.size(), 147ul); + EXPECT_EQ(hex(unsignedData), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "fe554f0068ed898680772f7a4d3e0a97385f4df20fff5d0278463ca6ad36e7bb" "00000000" "00" "" "ffffffff" + "03" // outputs + "40420f0000000000" "16" "001445a70b76fbdea0c9d5598c51cdd1d8ab455ab965" + "80841e0000000000" "16" "0014e7d0b03506234b334e742192bd48968584f4a7f1" + "c9fe0c0000000000" "16" "00145c74be45eb45a3459050667529022d9df8a1ecff" + // witness + "00" + "00000000" // nLockTime + ); + + // add signature + + auto privkey = PrivateKey(parse_hex(ownPrivateKey)); + auto pubkey = PrivateKey(privkey).getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(pubkey.bytes), "036739829f2cfec79cfe6aaf1c22ecb7d4867dfd8ab4deb7121b36a00ab646caed"); + + auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); // buildPayToV0WitnessProgram() + Data keyHashIn0; + EXPECT_TRUE(utxo0Script.matchPayToWitnessPublicKeyHash(keyHashIn0)); + EXPECT_EQ(hex(keyHashIn0), "5c74be45eb45a3459050667529022d9df8a1ecff"); + + auto redeemScript0 = Script::buildPayToPublicKeyHash(keyHashIn0); + EXPECT_EQ(hex(redeemScript0.bytes), "76a9145c74be45eb45a3459050667529022d9df8a1ecff88ac"); + + auto hashType = TWBitcoinSigHashType::TWBitcoinSigHashTypeAll; + Data sighash = unsignedTx.getSignatureHash(redeemScript0, unsignedTx.inputs[0].previousOutput.index, + hashType, utxo0Amount, static_cast(unsignedTx._version)); + auto sig = privkey.signAsDER(sighash); + ASSERT_FALSE(sig.empty()); + sig.push_back(hashType); + EXPECT_EQ(hex(sig), "30450221008d88197a37ffcb51ecacc7e826aa588cb1068a107a82373c4b54ec42318a395c02204abbf5408504614d8f943d67e7873506c575e85a5e1bd92a02cd345e5192a82701"); + + // add witness stack + unsignedTx.inputs[0].scriptWitness.push_back(sig); + unsignedTx.inputs[0].scriptWitness.push_back(pubkey.bytes); + + unsignedData.clear(); + unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); + EXPECT_EQ(unsignedData.size(), 254ul); + // https://blockchair.com/litecoin/transaction/9e3fe98565a904d2da5ec1b3ba9d2b3376dfc074f43d113ce1caac01bf51b34c + EXPECT_EQ(hex(unsignedData), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "fe554f0068ed898680772f7a4d3e0a97385f4df20fff5d0278463ca6ad36e7bb" "00000000" "00" "" "ffffffff" + "03" // outputs + "40420f0000000000" "16" "001445a70b76fbdea0c9d5598c51cdd1d8ab455ab965" + "80841e0000000000" "16" "0014e7d0b03506234b334e742192bd48968584f4a7f1" + "c9fe0c0000000000" "16" "00145c74be45eb45a3459050667529022d9df8a1ecff" + // witness + "02" + "48" "30450221008d88197a37ffcb51ecacc7e826aa588cb1068a107a82373c4b54ec42318a395c02204abbf5408504614d8f943d67e7873506c575e85a5e1bd92a02cd345e5192a82701" + "21" "036739829f2cfec79cfe6aaf1c22ecb7d4867dfd8ab4deb7121b36a00ab646caed" + "00000000" // nLockTime + ); +} + +TEST(BitcoinSigning, RedeemExtendedPubkeyUTXO) { + auto wif = "L4BeKzm3AHDUMkxLRVKTSVxkp6Hz9FcMQPh18YCKU1uioXfovzwP"; + auto decoded = Base58::decodeCheck(wif); + auto key = PrivateKey(Data(decoded.begin() + 1, decoded.begin() + 33)); + auto pubkey = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + auto hash = Hash::sha256ripemd(pubkey.bytes.data(), pubkey.bytes.size()); + + Data data; + append(data, 0x00); + append(data, hash); + auto address = Bitcoin::Address(data); + auto addressString = address.string(); + + EXPECT_EQ(addressString, "1PAmpW5igXUJnuuzRa5yTcsWHwBamZG7Y2"); + + // Setup input for Plan + SigningInput input; + input.coinType = TWCoinTypeBitcoin; + input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); + input.amount = 26972; + input.useMaxAmount = true; + input.byteFee = 1; + input.toAddress = addressString; + + auto utxo0Script = Script::lockScriptForAddress(addressString, TWCoinTypeBitcoin); + + UTXO utxo0; + utxo0.script = utxo0Script; + utxo0.amount = 16874; + auto hash0 = parse_hex("6ae3f1d245521b0ea7627231d27d613d58c237d6bf97a1471341a3532e31906c"); + std::reverse(hash0.begin(), hash0.end()); + utxo0.outPoint = OutPoint(hash0, 0, UINT32_MAX); + input.utxos.push_back(utxo0); + + UTXO utxo1; + utxo1.script = utxo0Script; + utxo1.amount = 10098; + auto hash1 = parse_hex("fd1ea8178228e825d4106df0acb61a4fb14a8f04f30cd7c1f39c665c9427bf13"); + std::reverse(hash1.begin(), hash1.end()); + utxo1.outPoint = OutPoint(hash1, 0, UINT32_MAX); + input.utxos.push_back(utxo1); + + input.privateKeys.push_back(key); + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data encoded; + signedTx.encode(encoded); + EXPECT_EQ(encoded.size(), 402ul); +} + +TEST(BitcoinSigning, SignP2TR_5df51e) { + const auto privateKey = "13fcaabaf9e71ffaf915e242ec58a743d55f102cf836968e5bd4881135e0c52c"; + const auto ownAddress = "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"; + const auto toAddress = "bc1ptmsk7c2yut2xah4pgflpygh2s7fh0cpfkrza9cjj29awapv53mrslgd5cf"; // Taproot + const auto coin = TWCoinTypeBitcoin; + + // Setup input + SigningInput input; + input.hashType = hashTypeForCoin(coin); + input.amount = 1100; + input.useMaxAmount = false; + input.byteFee = 1; + input.toAddress = toAddress; + input.changeAddress = ownAddress; + input.coinType = coin; + + auto utxoKey0 = PrivateKey(parse_hex(privateKey)); + auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(pubKey0.bytes), "021e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee"); + EXPECT_EQ(SegwitAddress(pubKey0, "bc").string(), ownAddress); + auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey0.bytes)); + EXPECT_EQ(hex(utxoPubkeyHash), "0cb9f5c6b62c03249367bc20a90dd2425e6926af"); + input.privateKeys.push_back(utxoKey0); + + auto redeemScript = Script::lockScriptForAddress(input.toAddress, coin); + EXPECT_EQ(hex(redeemScript.bytes), "51205ee16f6144e2d46edea1427e1222ea879377e029b0c5d2e252517aee85948ec7"); + auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); + EXPECT_EQ(hex(scriptHash), "e0a5001e7b394a1a6b2978cdcab272241280bf46"); + input.scripts[hex(scriptHash)] = redeemScript; + + UTXO utxo0; + auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); + EXPECT_EQ(hex(utxo0Script.bytes), "00140cb9f5c6b62c03249367bc20a90dd2425e6926af"); + utxo0.script = utxo0Script; + utxo0.amount = 49429; + auto hash0 = parse_hex("c24bd72e3eaea797bd5c879480a0db90980297bc7085efda97df2bf7d31413fb"); + std::reverse(hash0.begin(), hash0.end()); + utxo0.outPoint = OutPoint(hash0, 1, UINT32_MAX); + input.utxos.push_back(utxo0); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {49429}, 1100, 153)); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{234, 125, 153})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + // https://mempool.space/tx/5df51e13bfeb79f386e1e17237f06d1b5c87c5bfcaa907c0c1cfe51cd7ca446d + EXPECT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "fb1314d3f72bdf97daef8570bc97029890dba08094875cbd97a7ae3e2ed74bc2" "01000000" "00" "" "ffffffff" + "02" // outputs + "4c04000000000000" "22" "51205ee16f6144e2d46edea1427e1222ea879377e029b0c5d2e252517aee85948ec7" + "30bc000000000000" "16" "00140cb9f5c6b62c03249367bc20a90dd2425e6926af" + // witness + "02" + "47" "3044022021cea91157fdab33226e38ee7c1a686538fc323f5e28feb35775cf82ba8c62210220723743b150cea8ead877d8b8d059499779a5df69f9bdc755c9f968c56cfb528f01" + "21" "021e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee" + "00000000" // nLockTime + ); +} + +TEST(BitcoinSigning, Build_OpReturn_THORChainSwap_eb4c) { + auto coin = TWCoinTypeBitcoin; + auto ownAddress = "bc1q7s0a2l4aguksehx8hf93hs9yggl6njxds6m02g"; + auto toAddress = "bc1qxu5a8gtnjxw3xwdlmr2gl9d76h9fysu3zl656e"; + auto utxoAmount = 342101; + auto toAmount = 300000; + int fee = 36888; + + auto unsignedTx = Transaction(2, 0); + + auto hash0 = parse_hex("30b82960291a39de3664ec4c844a815e3e680e29b4d3a919e450f0c119cf4e35"); + std::reverse(hash0.begin(), hash0.end()); + auto outpoint0 = TW::Bitcoin::OutPoint(hash0, 1); + unsignedTx.inputs.emplace_back(outpoint0, Script(), UINT32_MAX); + + auto lockingScriptTo = Script::lockScriptForAddress(toAddress, coin); + unsignedTx.outputs.push_back(TransactionOutput(toAmount, lockingScriptTo)); + // change + auto lockingScriptChange = Script::lockScriptForAddress(ownAddress, coin); + unsignedTx.outputs.push_back(TransactionOutput(utxoAmount - toAmount - fee, lockingScriptChange)); + // memo OP_RETURN + Data memo = data("SWAP:THOR.RUNE:thor1tpercamkkxec0q0jk6ltdnlqvsw29guap8wmcl:"); + auto lockingScriptOpReturn = Script::buildOpReturnScript(memo); + EXPECT_EQ(hex(lockingScriptOpReturn.bytes), "6a3b535741503a54484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a"); + unsignedTx.outputs.push_back(TransactionOutput(0, lockingScriptOpReturn)); + + Data unsignedData; + unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); + EXPECT_EQ(unsignedData.size(), 186ul); + EXPECT_EQ(hex(unsignedData), // printed using prettyPrintTransaction + "02000000" // version + "0001" // marker & flag + "01" // inputs + "354ecf19c1f050e419a9d3b4290e683e5e814a844cec6436de391a296029b830" "01000000" "00" "" "ffffffff" + "03" // outputs + "e093040000000000" "16" "00143729d3a173919d1339bfd8d48f95bed5ca924391" + "5d14000000000000" "16" "0014f41fd57ebd472d0cdcc7ba4b1bc0a4423fa9c8cd" + "0000000000000000" "3d" "6a3b535741503a54484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a" + // witness + "00" + "00000000" // nLockTime + ); + + // add signature + auto pubkey = parse_hex("0206121b83ebfddbb1997b50cb87b968190857269333e21e295142c8b88af9312a"); + auto sig = parse_hex("3045022100876eba8f9324d3fbb00b9dad9a34a8166dd75127d4facda63484c19703e9c178022052495a6229cc465d5f0fcf3cde3b22a0f861e762d0bb10acde26a57598bfe7e701"); + + // add witness stack + unsignedTx.inputs[0].scriptWitness.push_back(sig); + unsignedTx.inputs[0].scriptWitness.push_back(pubkey); + + unsignedData.clear(); + unsignedTx.encode(unsignedData, Transaction::SegwitFormatMode::Segwit); + EXPECT_EQ(unsignedData.size(), 293ul); + // https://blockchair.com/bitcoin/transaction/eb4c1b064bfaf593d7cc6a5c73b75f932ffefe12a0478acf5a7e3145476683fc + EXPECT_EQ(hex(unsignedData), + "02000000000101354ecf19c1f050e419a9d3b4290e683e5e814a844cec6436de391a296029b8300100000000ffffffff03e0930400000000001600143729d3a1" + "73919d1339bfd8d48f95bed5ca9243915d14000000000000160014f41fd57ebd472d0cdcc7ba4b1bc0a4423fa9c8cd00000000000000003d6a3b535741503a54" + "484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a02483045022100876eba8f" + "9324d3fbb00b9dad9a34a8166dd75127d4facda63484c19703e9c178022052495a6229cc465d5f0fcf3cde3b22a0f861e762d0bb10acde26a57598bfe7e70121" + "0206121b83ebfddbb1997b50cb87b968190857269333e21e295142c8b88af9312a00000000" + ); +} + +TEST(BitcoinSigning, Sign_OpReturn_THORChainSwap) { + PrivateKey privateKey = PrivateKey(parse_hex("6bd4096fa6f08bd3af2b437244ba0ca2d35045c5233b8d6796df37e61e974de5")); + PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto ownAddress = SegwitAddress(publicKey, "bc"); + auto ownAddressString = ownAddress.string(); + EXPECT_EQ(ownAddressString, "bc1q2gzg42w98ytatvmsgxfc8vrg6l24c25pydup9u"); + auto toAddress = "bc1qxu5a8gtnjxw3xwdlmr2gl9d76h9fysu3zl656e"; + auto utxoAmount = 342101; + auto toAmount = 300000; + int byteFee = 126; + Data memo = data("SWAP:THOR.RUNE:thor1tpercamkkxec0q0jk6ltdnlqvsw29guap8wmcl:"); + + SigningInput input; + input.coinType = TWCoinTypeBitcoin; + input.hashType = hashTypeForCoin(TWCoinTypeBitcoin); + input.amount = toAmount; + input.byteFee = byteFee; + input.toAddress = toAddress; + input.changeAddress = ownAddressString; + + input.privateKeys.push_back(privateKey); + input.outputOpReturn = memo; + + UTXO utxo; + auto utxoHash = parse_hex("30b82960291a39de3664ec4c844a815e3e680e29b4d3a919e450f0c119cf4e35"); + std::reverse(utxoHash.begin(), utxoHash.end()); + utxo.outPoint = OutPoint(utxoHash, 1, UINT32_MAX); + utxo.amount = utxoAmount; + + auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(publicKey.bytes)); + EXPECT_EQ(hex(utxoPubkeyHash), "52048aa9c53917d5b370419383b068d7d55c2a81"); + auto utxoScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); + EXPECT_EQ(hex(utxoScript.bytes), "001452048aa9c53917d5b370419383b068d7d55c2a81"); + utxo.script = utxoScript; + input.utxos.push_back(utxo); + + { + // test plan (but do not reuse plan result) + auto plan = TransactionBuilder::plan(input); + EXPECT_TRUE(verifyPlan(plan, {342101}, 300000, 26586)); + EXPECT_EQ(plan.outputOpReturn.size(), 59ul); + } + + // Sign + auto result = TransactionSigner::sign(input); + + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + EXPECT_EQ(getEncodedTxSize(signedTx), (EncodedTxSize{293, 183, 211})); + EXPECT_TRUE(validateEstimatedSize(signedTx, -1, 1)); + ASSERT_EQ(hex(serialized), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "354ecf19c1f050e419a9d3b4290e683e5e814a844cec6436de391a296029b830" "01000000" "00" "" "ffffffff" + "03" // outputs + "e093040000000000" "16" "00143729d3a173919d1339bfd8d48f95bed5ca924391" + "9b3c000000000000" "16" "001452048aa9c53917d5b370419383b068d7d55c2a81" + "0000000000000000" "3d" "6a3b535741503a54484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a" + // witness + "02" + "48" "3045022100ff6c0aaef512aa52f3036161bfbcef39046ac89eb9617fa461a0c9c43fe45eb3022055d208d3f81736e72e3ad8ef761dc79ac5dd3dc00721174bc69db416a74960e301" + "21" "02c2e5c8b4927812fb37444a7862466ad23978a4ac626f8eaf93e1d1a60d6abb80" + "00000000" // nLockTime + ); +} +// clang-format on +} // namespace TW::Bitcoin diff --git a/tests/chains/Bitcoin/TWBitcoinTransactionTests.cpp b/tests/chains/Bitcoin/TWBitcoinTransactionTests.cpp new file mode 100644 index 00000000000..712595ce63e --- /dev/null +++ b/tests/chains/Bitcoin/TWBitcoinTransactionTests.cpp @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bitcoin/Transaction.h" +#include "HexCoding.h" + +#include + +namespace TW::Bitcoin { + +TEST(BitcoinTransaction, Encode) { + auto transaction = Transaction(2, 0); + + auto po0 = OutPoint(parse_hex("5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f"), 0); + transaction.inputs.emplace_back(po0, Script(), 4294967295); + + auto po1 = OutPoint(parse_hex("bf829c6bcf84579331337659d31f89dfd138f7f7785802d5501c92333145ca7c"), 18); + transaction.inputs.emplace_back(po1, Script(), 4294967295); + + auto po2 = OutPoint(parse_hex("22a6f904655d53ae2ff70e701a0bbd90aa3975c0f40bfc6cc996a9049e31cdfc"), 1); + transaction.inputs.emplace_back(po2, Script(), 4294967295); + + auto oscript0 = Script(parse_hex("76a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac")); + transaction.outputs.emplace_back(18000000, oscript0); + + auto oscript1 = Script(parse_hex("0x76a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac")); + transaction.outputs.emplace_back(400000000, oscript1); + + Data unsignedData; + transaction.encode(unsignedData, Transaction::SegwitFormatMode::NonSegwit); + ASSERT_EQ(unsignedData.size(), 201ul); + ASSERT_EQ(hex(unsignedData), + "02000000035897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f0000000000ffffffffbf829c6bcf84579331337659d31f89dfd138f7f7785802d5501c92333145ca7c1200000000ffffffff22a6f904655d53ae2ff70e701a0bbd90aa3975c0f40bfc6cc996a9049e31cdfc0100000000ffffffff0280a81201000000001976a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac0084d717000000001976a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac00000000"); +} + +} // namespace TW::Bitcoin diff --git a/tests/chains/Bitcoin/TWCoinTypeTests.cpp b/tests/chains/Bitcoin/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..6b5d718b593 --- /dev/null +++ b/tests/chains/Bitcoin/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWBitcoinCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBitcoin)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0607f62530b68cfcc91c57a1702841dd399a899d0eecda8e31ecca3f52f01df2")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBitcoin, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("17A16QmavnUfCW11DAApiJxp7ARnxN5pGX")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBitcoin, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBitcoin)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBitcoin)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBitcoin), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeBitcoin)); + ASSERT_EQ(0x5, TWCoinTypeP2shPrefix(TWCoinTypeBitcoin)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBitcoin)); + assertStringsEqual(symbol, "BTC"); + assertStringsEqual(txUrl, "https://mempool.space/tx/0607f62530b68cfcc91c57a1702841dd399a899d0eecda8e31ecca3f52f01df2"); + assertStringsEqual(accUrl, "https://mempool.space/address/17A16QmavnUfCW11DAApiJxp7ARnxN5pGX"); + assertStringsEqual(id, "bitcoin"); + assertStringsEqual(name, "Bitcoin"); +} diff --git a/tests/chains/Bitcoin/TWSegwitAddressTests.cpp b/tests/chains/Bitcoin/TWSegwitAddressTests.cpp new file mode 100644 index 00000000000..4adf19e4250 --- /dev/null +++ b/tests/chains/Bitcoin/TWSegwitAddressTests.cpp @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include "Data.h" +#include "HexCoding.h" +#include +#include + +#include + +const char* address1 = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"; +const char* address2 = "bc1qr583w2swedy2acd7rung055k8t3n7udp7vyzyg"; +const char* address3Taproot = "bc1ptmsk7c2yut2xah4pgflpygh2s7fh0cpfkrza9cjj29awapv53mrslgd5cf"; + +TEST(TWSegwitAddress, CreateAndDelete) { + { + TWSegwitAddress* addr = TWSegwitAddressCreateWithString(STRING(address1).get()); + EXPECT_TRUE(addr != nullptr); + TWSegwitAddressDelete(addr); + } + { + auto pkData = DATA("0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"); + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(pkData.get(), TWPublicKeyTypeSECP256k1)); + TWSegwitAddress* addr = TWSegwitAddressCreateWithPublicKey(TWHRPBitcoin, publicKey.get()); + EXPECT_TRUE(addr != nullptr); + TWSegwitAddressDelete(addr); + } +} + +TEST(TWSegwitAddress, PublicKeyToAddress) { + auto pkData = DATA("0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"); + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(pkData.get(), TWPublicKeyTypeSECP256k1)); + + auto address = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPBitcoin, publicKey.get())); + auto string = WRAPS(TWSegwitAddressDescription(address.get())); + + ASSERT_STREQ(address1, TWStringUTF8Bytes(string.get())); + + auto str = STRING(address1); + auto addr = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithString(string.get())); + ASSERT_TRUE(TWSegwitAddressEqual(address.get(), addr.get())); +} + +TEST(TWSegwitAddress, InitWithAddress) { + auto string = STRING(address1); + ASSERT_TRUE(TWSegwitAddressIsValidString(string.get())); + + auto address = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithString(string.get())); + auto description = WRAPS(TWSegwitAddressDescription(address.get())); + + ASSERT_TRUE(address.get() != nullptr); + ASSERT_STREQ(address1, TWStringUTF8Bytes(description.get())); + + ASSERT_EQ(0, TWSegwitAddressWitnessVersion(address.get())); + + ASSERT_EQ(TWHRPBitcoin, TWSegwitAddressHRP(address.get())); + + auto witness = WRAPD(TWSegwitAddressWitnessProgram(address.get())); + ASSERT_EQ(TW::hex(TW::data(TWDataBytes(witness.get()), TWDataSize(witness.get()))), "751e76e8199196d454941c45d1b3a323f1433bd6"); +} + +TEST(TWSegwitAddress, TaprootString) { + const auto string = STRING(address3Taproot); + const auto address = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithString(string.get())); + ASSERT_TRUE(address.get() != nullptr); + + const auto description = WRAPS(TWSegwitAddressDescription(address.get())); + ASSERT_STREQ(address3Taproot, TWStringUTF8Bytes(description.get())); + + ASSERT_EQ(1, TWSegwitAddressWitnessVersion(address.get())); // taproot has segwit version 1 + + ASSERT_EQ(TWHRPBitcoin, TWSegwitAddressHRP(address.get())); +} + +TEST(TWSegwitAddress, InvalidAddress) { + std::vector> strings = { + STRING("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5"), + STRING("bc1rw5uspcuh"), + STRING("bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90"), + STRING("BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P"), + STRING("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7"), + STRING("bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du"), + STRING("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv"), + STRING("bc1gmk9yu"), + }; + for (auto& string : strings) { + ASSERT_TRUE(TWSegwitAddressCreateWithString(string.get()) == nullptr) << "Invalid address '" << TWStringUTF8Bytes(string.get()) << "' reported as valid."; + } +} diff --git a/tests/chains/Bitcoin/TransactionCompilerTests.cpp b/tests/chains/Bitcoin/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..f28813fd594 --- /dev/null +++ b/tests/chains/Bitcoin/TransactionCompilerTests.cpp @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include + +#include "Bitcoin/Script.h" +#include "Bitcoin/SegwitAddress.h" +#include "Bitcoin/TransactionInput.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" + +#include "TxComparisonHelper.h" +#include "TestUtilities.h" +#include + +using namespace TW; + +const static uint64_t ONE_BTC = 100'000'000ll; + +TEST(BitcoinCompiler, CompileWithSignatures) { + // Test external signining with a Bircoin transaction with 3 input UTXOs, all used, but only + // using 2 public keys. Three signatures are neeeded. This illustrates that order of + // UTXOs/hashes is not always the same. + + const auto revUtxoHash0 = + parse_hex("07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8"); + const auto revUtxoHash1 = + parse_hex("d6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e"); + const auto revUtxoHash2 = + parse_hex("6021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d"); + const auto inPubKey0 = + parse_hex("024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382"); + const auto inPubKey1 = + parse_hex("0217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc"); + const auto inPubKeyHash0 = parse_hex("bd92088bb7e82d611a9b94fbb74a0908152b784f"); + const auto inPubKeyHash1 = parse_hex("6641abedacf9483b793afe1718689cc9420bbb1c"); + + // Input UTXO infos + struct UtxoInfo { + Data revUtxoHash; + Data publicKey; + long amount; + int index; + }; + std::vector utxoInfos = { + // first + UtxoInfo{revUtxoHash0, inPubKey0, 600'000, 0}, + // second UTXO, with same pubkey + UtxoInfo{revUtxoHash1, inPubKey0, 500'000, 1}, + // third UTXO, with different pubkey + UtxoInfo{revUtxoHash2, inPubKey1, 400'000, 0}, + }; + + // Signature infos, indexed by pubkeyhash+hash + struct SignatureInfo { + Data signature; + Data publicKey; + }; + std::map signatureInfos = { + {hex(inPubKeyHash0) + "+" + + "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7", + { + parse_hex("304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b349022" + "00a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a40"), + inPubKey0, + }}, + {hex(inPubKeyHash1) + "+" + + "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6", + { + parse_hex("3044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe11582022" + "0646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e4"), + inPubKey1, + }}, + {hex(inPubKeyHash0) + "+" + + "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101", + { + parse_hex("30440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b1022" + "07e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc"), + inPubKey0, + }}, + }; + + const auto coin = TWCoinTypeBitcoin; + const auto ownAddress = "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv"; + + // Setup input for Plan + Bitcoin::Proto::SigningInput signingInput; + signingInput.set_coin_type(coin); + signingInput.set_hash_type(TWBitcoinSigHashTypeAll); + signingInput.set_amount(1'200'000); + signingInput.set_use_max_amount(false); + signingInput.set_byte_fee(1); + signingInput.set_to_address("bc1q2dsdlq3343vk29runkgv4yc292hmq53jedfjmp"); + signingInput.set_change_address(ownAddress); + + // process UTXOs + int count = 0; + for (auto& u : utxoInfos) { + const auto publicKey = PublicKey(u.publicKey, TWPublicKeyTypeSECP256k1); + const auto address = Bitcoin::SegwitAddress(publicKey, "bc"); + if (count == 0) + EXPECT_EQ(address.string(), ownAddress); + if (count == 1) + EXPECT_EQ(address.string(), ownAddress); + if (count == 2) + EXPECT_EQ(address.string(), "bc1qveq6hmdvl9yrk7f6lct3s6yue9pqhwcuxedggg"); + + const auto utxoScript = Bitcoin::Script::lockScriptForAddress(address.string(), coin); + if (count == 0) + EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); + if (count == 1) + EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); + if (count == 2) + EXPECT_EQ(hex(utxoScript.bytes), "00146641abedacf9483b793afe1718689cc9420bbb1c"); + + Data keyHash; + EXPECT_TRUE(utxoScript.matchPayToWitnessPublicKeyHash(keyHash)); + if (count == 0) + EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); + if (count == 1) + EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); + if (count == 2) + EXPECT_EQ(hex(keyHash), hex(inPubKeyHash1)); + + const auto redeemScript = Bitcoin::Script::buildPayToPublicKeyHash(keyHash); + if (count == 0) + EXPECT_EQ(hex(redeemScript.bytes), + "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac"); + if (count == 1) + EXPECT_EQ(hex(redeemScript.bytes), + "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac"); + if (count == 2) + EXPECT_EQ(hex(redeemScript.bytes), + "76a9146641abedacf9483b793afe1718689cc9420bbb1c88ac"); + (*signingInput.mutable_scripts())[hex(keyHash)] = + std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + + auto utxo = signingInput.add_utxo(); + utxo->set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + utxo->set_amount(u.amount); + utxo->mutable_out_point()->set_hash( + std::string(u.revUtxoHash.begin(), u.revUtxoHash.end())); + utxo->mutable_out_point()->set_index(u.index); + utxo->mutable_out_point()->set_sequence(UINT32_MAX); + + ++count; + } + EXPECT_EQ(count, 3); + EXPECT_EQ(signingInput.utxo_size(), 3); + + // Plan + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(signingInput, plan, coin); + + // Plan is checked, assume it is accepted + EXPECT_EQ(plan.amount(), 1'200'000); + EXPECT_EQ(plan.fee(), 277); + EXPECT_EQ(plan.change(), 299'723); + ASSERT_EQ(plan.utxos_size(), 3); + // Note that UTXOs happen to be in reverse order compared to the input + EXPECT_EQ(hex(plan.utxos(0).out_point().hash()), hex(revUtxoHash2)); + EXPECT_EQ(hex(plan.utxos(1).out_point().hash()), hex(revUtxoHash1)); + EXPECT_EQ(hex(plan.utxos(2).out_point().hash()), hex(revUtxoHash0)); + + // Extend input with accepted plan + *signingInput.mutable_plan() = plan; + + // Serialize input + const auto txInputData = data(signingInput.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + /// Step 2: Obtain preimage hashes + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + TW::Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), (int)preImageHashes.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].data_hash()), "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6"); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[1].data_hash()), "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7"); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[2].data_hash()), "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101"); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].public_key_hash()), hex(inPubKeyHash1)); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[1].public_key_hash()), hex(inPubKeyHash0)); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[2].public_key_hash()), hex(inPubKeyHash0)); + + // Simulate signatures, normally obtained from signature server. + std::vector signatureVec; + std::vector pubkeyVec; + for (const auto& h : preSigningOutput.hash_public_keys()) { + const auto& preImageHash = h.data_hash(); + const auto& pubkeyhash = h.public_key_hash(); + + const std::string key = hex(pubkeyhash) + "+" + hex(preImageHash); + const auto sigInfoFind = signatureInfos.find(key); + ASSERT_TRUE(sigInfoFind != signatureInfos.end()); + const auto& sigInfo = std::get<1>(*sigInfoFind); + const auto& publicKeyData = sigInfo.publicKey; + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = sigInfo.signature; + + signatureVec.push_back(signature); + pubkeyVec.push_back(publicKeyData); + + EXPECT_TRUE(publicKey.verifyAsDER(signature, TW::Data(preImageHash.begin(), preImageHash.end()))); + } + + /// Step 3: Compile transaction info + auto compileWithSignatures = + TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, pubkeyVec); + + const auto ExpectedTx = + "010000000001036021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d0000000000ff" + "ffffffd6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e0100000000ffffffff07" + "c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa80000000000ffffffff02804f1200" + "000000001600145360df8231ac5965147c9d90ca930a2aafb05232cb92040000000000160014bd92088bb7e82d" + "611a9b94fbb74a0908152b784f02473044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e379" + "66414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e40121021714" + "2f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc0247304402201857bc6e6e48b46046" + "a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efd" + "bc9c6784aa2571ff4f6f2a400121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb49" + "3382024730440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f" + "7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc0121024bc2a31265153f07e70e0bab08" + "724e6b85e217f8cd628ceb62974247bb49338200000000"; + { + EXPECT_EQ(compileWithSignatures.size(), 786ul); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(compileWithSignatures.data(), (int)compileWithSignatures.size())); + + EXPECT_EQ(output.encoded().size(), 518ul); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Bitcoin::Proto::SigningInput input; + ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); + + // 2 private keys are needed (despite >2 UTXOs) + auto key0 = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + auto key1 = parse_hex("7878787878787878787878787878787878787878787878787878787878787878"); + EXPECT_EQ(hex(PrivateKey(key0).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + hex(inPubKey0)); + EXPECT_EQ(hex(PrivateKey(key1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + hex(inPubKey1)); + *input.add_private_key() = std::string(key0.begin(), key0.end()); + *input.add_private_key() = std::string(key1.begin(), key1.end()); + + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(input, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: not enough signatures + const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, {signatureVec[0]}, pubkeyVec); + EXPECT_GT(outputData.size(), 1ul); + + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + { // Negative: invalid public key + const auto publicKeyBlake = + parse_hex("b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"); + EXPECT_EXCEPTION( + TransactionCompiler::compileWithSignatures( + coin, txInputData, signatureVec, {pubkeyVec[0], pubkeyVec[1], publicKeyBlake}), + "Invalid public key"); + } + { // Negative: wrong signature (formally valid) + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, + {parse_hex("415502201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b349022" + "00a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f3b51"), + signatureVec[1], signatureVec[2]}, + pubkeyVec); + EXPECT_EQ(outputData.size(), 2ul); + + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signing); + } +} + +TEST(BitcoinCompiler, CompileWithSignaturesV2) { + Bitcoin::Proto::SigningInput inputLegacy; + auto& input = *inputLegacy.mutable_signing_v2(); + + const PrivateKey alicePrivateKey(parse_hex("56429688a1a6b00b90ccd22a0de0a376b6569d8684022ae92229a28478bfb657")); + const auto alicePublicKey = alicePrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto bobPublicKey = parse_hex("037ed9a436e11ec4947ac4b7823787e24ba73180f1edd2857bff19c9f4d62b65bf"); + + auto txid0 = parse_hex("1e1cdc48aa990d7e154a161d5b5f1cad737742e97d2712ab188027bb42e6e47b"); + std::reverse(txid0.begin(), txid0.end()); + + // Step 1: Prepare transaction input (protobuf) + + auto& utxo0 = *input.add_inputs(); + utxo0.mutable_out_point()->set_hash(txid0.data(), txid0.size()); + utxo0.mutable_out_point()->set_vout(0); + utxo0.set_value(ONE_BTC * 50); + utxo0.set_sighash_type(TWBitcoinSigHashTypeAll); + // Set the Alice public key as the owner of the P2PKH input. + utxo0.mutable_script_builder()->mutable_p2pkh()->set_pubkey(alicePublicKey.bytes.data(), alicePublicKey.bytes.size()); + + auto& out0 = *input.add_outputs(); + out0.set_value(ONE_BTC * 50 - 1'000'000); + // Set the Bob public key as the receiver of the P2PKH output. + out0.mutable_builder()->mutable_p2pkh()->set_pubkey(bobPublicKey.data(), bobPublicKey.size()); + + input.set_version(BitcoinV2::Proto::TransactionVersion::V2); + input.add_public_keys(alicePublicKey.bytes.data(), alicePublicKey.bytes.size()); + input.set_input_selector(BitcoinV2::Proto::InputSelector::UseAll); + input.mutable_chain_info()->set_p2pkh_prefix(0); + input.mutable_chain_info()->set_p2sh_prefix(5); + input.set_fixed_dust_threshold(546); + + const auto inputLegacyData = data(inputLegacy.SerializeAsString()); + + // Step 2: Obtain preimage hashes + + const auto preOutputData = TransactionCompiler::preImageHashes(TWCoinTypeBitcoin, inputLegacyData); + Bitcoin::Proto::PreSigningOutput preOutput; + preOutput.ParseFromArray(preOutputData.data(), static_cast(preOutputData.size())); + + EXPECT_EQ(preOutput.error(), Common::Proto::SigningError::OK); + EXPECT_TRUE(preOutput.has_pre_signing_result_v2()); + EXPECT_EQ(preOutput.pre_signing_result_v2().error(), Common::Proto::SigningError::OK); + + const auto& sighashes = preOutput.pre_signing_result_v2().sighashes(); + EXPECT_EQ(sighashes.size(), 1); + const auto& sighash0 = sighashes.Get(0); + EXPECT_EQ(data(sighash0.public_key()), alicePublicKey.bytes); + EXPECT_EQ(hex(sighash0.sighash()), "6a0e072da66b141fdb448323d54765cafcaf084a06d2fa13c8aed0c694e50d18"); + + // Step 3: Simulate signature, normally obtained from signature server + + const auto sig0 = alicePrivateKey.sign(data(sighash0.sighash()), TWCurveSECP256k1); + EXPECT_EQ(hex(sig0), "78eda020d4b86fcb3af78ef919912e6d79b81164dbbb0b0b96da6ac58a2de4b11a5fd8d48734d5a02371c4b5ee551a69dca3842edbf577d863cf8ae9fdbbd45900"); + + // Step 4: Compile transaction info + + const std::vector signatures { sig0 }; + const std::vector publicKeys { alicePublicKey.bytes }; + const auto outputData = TransactionCompiler::compileWithSignatures(TWCoinTypeBitcoin, inputLegacyData, signatures, publicKeys); + Bitcoin::Proto::SigningOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + EXPECT_TRUE(output.has_signing_result_v2()); + const auto& outputV2 = output.signing_result_v2(); + EXPECT_EQ(outputV2.error(), Common::Proto::SigningError::OK); + EXPECT_EQ( + hex(outputV2.encoded()), + "02000000017be4e642bb278018ab12277de9427773ad1c5f5b1d164a157e0d99aa48dc1c1e000000006a473044022078eda020d4b86fcb3af78ef919912e6d79b81164dbbb0b0b96da6ac58a2de4b102201a5fd8d48734d5a02371c4b5ee551a69dca3842edbf577d863cf8ae9fdbbd4590121036666dd712e05a487916384bfcd5973eb53e8038eccbbf97f7eed775b87389536ffffffff01c0aff629010000001976a9145eaaa4f458f9158f86afcba08dd7448d27045e3d88ac00000000" + ); + EXPECT_EQ(hex(outputV2.txid()), "c19f410bf1d70864220e93bca20f836aaaf8cdde84a46692616e9f4480d54885"); +} diff --git a/tests/Bitcoin/TransactionPlanTests.cpp b/tests/chains/Bitcoin/TransactionPlanTests.cpp similarity index 88% rename from tests/Bitcoin/TransactionPlanTests.cpp rename to tests/chains/Bitcoin/TransactionPlanTests.cpp index cf4d6b37671..357d4784979 100644 --- a/tests/Bitcoin/TransactionPlanTests.cpp +++ b/tests/chains/Bitcoin/TransactionPlanTests.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "TxComparisonHelper.h" #include "Bitcoin/OutPoint.h" @@ -16,9 +14,7 @@ #include -using namespace TW; -using namespace TW::Bitcoin; - +namespace TW::Bitcoin { TEST(TransactionPlan, OneTypical) { auto utxos = buildTestUTXOs({100'000}); @@ -63,17 +59,29 @@ TEST(TransactionPlan, OneInsufficientLower100) { EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0, Common::Proto::Error_not_enough_utxos)); } -TEST(TransactionPlan, OneInsufficientLower170) { +TEST(TransactionPlan, OneInsufficient146) { // requested is only slightly lower than avail, not enough for fee, cannot be satisfied auto utxos = buildTestUTXOs({100'000}); - auto sigingInput = buildSigningInput(100'000 - 170, 1, utxos); + auto sigingInput = buildSigningInput(100'000 - 146, 1, utxos); auto txPlan = TransactionBuilder::plan(sigingInput); EXPECT_TRUE(verifyPlan(txPlan, {}, 0, 0, Common::Proto::Error_not_enough_utxos)); } -TEST(TransactionPlan, OneInsufficientLower300) { +TEST(TransactionPlan, OneSufficientLower170) { + // requested is only slightly lower than avail, not enough for fee, cannot be satisfied + auto utxos = buildTestUTXOs({100'000}); + auto sigingInput = buildSigningInput(100'000 - 170, 1, utxos); + + auto txPlan = TransactionBuilder::plan(sigingInput); + + auto dustChange = 23; + auto actualFee = 147 + dustChange; + EXPECT_TRUE(verifyPlan(txPlan, {100'000}, 100'000 - 170, actualFee)); +} + +TEST(TransactionPlan, OneSufficientLower300) { auto utxos = buildTestUTXOs({100'000}); auto sigingInput = buildSigningInput(100'000 - 300, 1, utxos); @@ -96,7 +104,9 @@ TEST(TransactionPlan, OneMoreRequested) { TEST(TransactionPlan, OneFitsExactly) { auto utxos = buildTestUTXOs({100'000}); auto byteFee = 1; - auto expectedFee = 147; + auto dustChange = 27; + // Change amount is too low (less than dust), so we just waste it as the transaction fee. + auto expectedFee = 147 + dustChange; auto sigingInput = buildSigningInput(100'000 - 174, byteFee, utxos); auto txPlan = TransactionBuilder::plan(sigingInput); @@ -110,7 +120,9 @@ TEST(TransactionPlan, OneFitsExactly) { TEST(TransactionPlan, OneFitsExactlyHighFee) { auto utxos = buildTestUTXOs({100'000}); auto byteFee = 10; - auto expectedFee = 1470; + auto dustChange = 270; + // Change amount is too low (less than dust), so we just waste it as the transaction fee. + auto expectedFee = 1470 + dustChange; auto sigingInput = buildSigningInput(100'000 - 1740, byteFee, utxos); auto txPlan = TransactionBuilder::plan(sigingInput); @@ -196,7 +208,7 @@ TEST(TransactionPlan, ThreeNoDust) { } TEST(TransactionPlan, TenThree) { - auto utxos = buildTestUTXOs({1'000, 2'000, 100'000, 3'000, 4'000, 5,000, 125'000, 6'000, 150'000, 7'000}); + auto utxos = buildTestUTXOs({1'000, 2'000, 100'000, 3'000, 4'000, 5, 000, 125'000, 6'000, 150'000, 7'000}); auto sigingInput = buildSigningInput(300'000, 1, utxos); auto txPlan = TransactionBuilder::plan(sigingInput); @@ -243,6 +255,26 @@ TEST(TransactionPlan, SelectionSuboptimal_ExtraSmallUtxo) { EXPECT_EQ(firstUtxo, 500); } +TEST(TransactionPlan, SelectionSuboptimal_ExtraSmallUtxoFixedDust) { + // Solution found 4-in-2-out {500, 600, 800, 1000} avail 2900 txamount 1390 fee 702 change 628 + // Better solution: 3-in-2-out {600, 800, 1000} avail 2400 txamount 1390 fee 566 change 444 + // Previously, with with higher fee estimation used in UTXO selection, solution found was 5-in-2-out {400, 500, 600, 800, 1000} avail 3300 txamount 1390 fee 838 change 1072 + auto utxos = buildTestUTXOs({400, 500, 600, 800, 1'000}); + auto byteFee = 2; + auto signingInput = buildSigningInput(1'390, byteFee, utxos); + signingInput.dustCalculator = std::make_shared(450); + + // UTXOs smaller than singleInputFee are not included + auto txPlan = TransactionBuilder::plan(signingInput); + + auto expectedFee = 702; + EXPECT_TRUE(verifyPlan(txPlan, {500, 600, 800, 1'000}, 1'390, expectedFee)); + auto change = 2'900 - 1'390 - expectedFee; + auto firstUtxo = txPlan.utxos[0].amount; + EXPECT_EQ(change, 808); + EXPECT_EQ(firstUtxo, 500); +} + TEST(TransactionPlan, Selection_Satisfied5) { // 5-input case, with a 5-input solution. // Previously, with with higher fee estimation used in UTXO selection, no solution would be found. @@ -263,7 +295,7 @@ TEST(TransactionPlan, Inputs5_33Req19NoDustFee2) { // UTXOs smaller than singleInputFee are not included auto txPlan = TransactionBuilder::plan(sigingInput); - auto expectedFee = 283*byteFee; + auto expectedFee = 283 * byteFee; EXPECT_TRUE(verifyPlan(txPlan, {6'000, 8'000, 10'000}, 19'000, expectedFee)); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); @@ -278,7 +310,7 @@ TEST(TransactionPlan, Inputs5_33Req19Dust1Fee5) { // UTXOs smaller than singleInputFee are not included auto txPlan = TransactionBuilder::plan(sigingInput); - auto expectedFee = 283*byteFee; + auto expectedFee = 283 * byteFee; EXPECT_TRUE(verifyPlan(txPlan, {6'000, 8'000, 10'000}, 19'000, expectedFee)); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); @@ -293,7 +325,7 @@ TEST(TransactionPlan, Inputs5_33Req19Dust1Fee9) { // UTXOs smaller than singleInputFee are not included auto txPlan = TransactionBuilder::plan(sigingInput); - auto expectedFee = 283*byteFee; + auto expectedFee = 283 * byteFee; EXPECT_TRUE(verifyPlan(txPlan, {6'000, 8'000, 10'000}, 19'000, expectedFee)); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); @@ -319,7 +351,7 @@ TEST(TransactionPlan, Inputs5_33Req13Fee20) { // UTXOs smaller than singleInputFee are not included auto txPlan = TransactionBuilder::plan(sigingInput); - auto expectedFee = 283*byteFee; + auto expectedFee = 283 * byteFee; EXPECT_TRUE(verifyPlan(txPlan, {6'000, 8'000, 10'000}, 13'000, expectedFee)); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); @@ -488,15 +520,15 @@ TEST(TransactionPlan, MaxAmountDustAllFee10) { } TEST(TransactionPlan, One_MaxAmount_FeeMoreThanAvailable) { - auto utxos = buildTestUTXOs({170}); + auto utxos = buildTestUTXOs({340}); auto byteFee = 1; auto expectedFee = 113; - auto sigingInput = buildSigningInput(300, byteFee, utxos, true); + auto sigingInput = buildSigningInput(340, byteFee, utxos, true); auto txPlan = TransactionBuilder::plan(sigingInput); // Fee is reduced to availableAmount - EXPECT_TRUE(verifyPlan(txPlan, {170}, 170 - expectedFee, expectedFee)); + EXPECT_TRUE(verifyPlan(txPlan, {340}, 340 - expectedFee, expectedFee)); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); EXPECT_EQ(feeCalculator.calculate(1, 1, byteFee), 143); @@ -532,7 +564,7 @@ TEST(TransactionPlan, ManyUtxosNonmax_400) { valueSum += val; } const uint64_t requestedAmount = valueSum / 8; - EXPECT_EQ(requestedAmount, 1'002'500); + EXPECT_EQ(requestedAmount, 1'002'500ul); auto utxos = buildTestUTXOs(values); auto sigingInput = buildSigningInput(requestedAmount, byteFee, utxos, false, TWCoinTypeBitcoin); @@ -547,8 +579,8 @@ TEST(TransactionPlan, ManyUtxosNonmax_400) { subset.push_back(val); subsetSum += val; } - EXPECT_EQ(subset.size(), 27); - EXPECT_EQ(subsetSum, 1'044'900); + EXPECT_EQ(subset.size(), 27ul); + EXPECT_EQ(subsetSum, 1'044'900ul); EXPECT_TRUE(verifyPlan(txPlan, subset, requestedAmount, 19'150)); } @@ -563,7 +595,7 @@ TEST(TransactionPlan, ManyUtxosNonmax_5000_simple) { valueSum += val; } const uint64_t requestedAmount = valueSum / 20; - EXPECT_EQ(requestedAmount, 62'512'500); + EXPECT_EQ(requestedAmount, 62'512'500ul); // Use Ravencoin, because of faster non-segwit estimation, and one of the original issues was with this coin. auto utxos = buildTestUTXOs(values); @@ -579,8 +611,8 @@ TEST(TransactionPlan, ManyUtxosNonmax_5000_simple) { subset.push_back(val); subsetSum += val; } - EXPECT_EQ(subset.size(), 1220); - EXPECT_EQ(subsetSum, 76'189'000); + EXPECT_EQ(subset.size(), 1220ul); + EXPECT_EQ(subsetSum, 76'189'000ul); EXPECT_TRUE(verifyPlan(txPlan, subset, requestedAmount, 1'806'380)); } @@ -612,10 +644,10 @@ TEST(TransactionPlan, ManyUtxosMax_400) { filteredValueSum += val; } } - EXPECT_EQ(valueSum, 8'020'000); - EXPECT_EQ(dustLimit, 1480); - EXPECT_EQ(filteredValues.size(), 386); - EXPECT_EQ(filteredValueSum, 80'09'500); + EXPECT_EQ(valueSum, 8'020'000ul); + EXPECT_EQ(dustLimit, 1480ul); + EXPECT_EQ(filteredValues.size(), 386ul); + EXPECT_EQ(filteredValueSum, 80'09'500ul); EXPECT_TRUE(verifyPlan(txPlan, filteredValues, 7'437'780, 571'720)); } @@ -647,10 +679,10 @@ TEST(TransactionPlan, ManyUtxosMax_5000_simple) { filteredValueSum += val; } } - EXPECT_EQ(valueSum, 1'250'250'000); - EXPECT_EQ(dustLimit, 1500); - EXPECT_EQ(filteredValues.size(), 3000); - EXPECT_EQ(filteredValueSum, 454'350'000); + EXPECT_EQ(valueSum, 1'250'250'000ul); + EXPECT_EQ(dustLimit, 1500ul); + EXPECT_EQ(filteredValues.size(), 3000ul); + EXPECT_EQ(filteredValueSum, 454'350'000ul); EXPECT_TRUE(verifyPlan(txPlan, filteredValues, 449'909'560, 4'440'440)); } @@ -681,10 +713,13 @@ TEST(TransactionPlan, OpReturn) { auto txPlan = TransactionBuilder::plan(signingInput); EXPECT_TRUE(verifyPlan(txPlan, {342101}, 300000, 205 * byteFee)); - EXPECT_EQ(txPlan.outputOpReturn.size(), 59); + EXPECT_EQ(txPlan.outputOpReturn.size(), 59ul); EXPECT_EQ(hex(txPlan.outputOpReturn), "535741503a54484f522e52554e453a74686f72317470657263616d6b6b7865633071306a6b366c74646e6c7176737732396775617038776d636c3a"); + EXPECT_FALSE(txPlan.outputOpReturnIndex.has_value()); auto& feeCalculator = getFeeCalculator(TWCoinTypeBitcoin); EXPECT_EQ(feeCalculator.calculate(1, 2, byteFee), 174 * byteFee); EXPECT_EQ(feeCalculator.calculate(1, 3, byteFee), 205 * byteFee); } + +} // namespace TW::Bitcoin diff --git a/tests/Bitcoin/TxComparisonHelper.cpp b/tests/chains/Bitcoin/TxComparisonHelper.cpp similarity index 87% rename from tests/Bitcoin/TxComparisonHelper.cpp rename to tests/chains/Bitcoin/TxComparisonHelper.cpp index e84e87c4998..f674bcaaff7 100644 --- a/tests/Bitcoin/TxComparisonHelper.cpp +++ b/tests/chains/Bitcoin/TxComparisonHelper.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "TxComparisonHelper.h" @@ -16,12 +14,10 @@ #include "HexCoding.h" #include "BinaryCoding.h" -#include #include #include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { auto emptyTxOutPoint = OutPoint(parse_hex("1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"), 0); @@ -30,14 +26,15 @@ UTXO buildTestUTXO(int64_t amount) { utxo.amount = amount; utxo.outPoint = emptyTxOutPoint; utxo.outPoint.sequence = UINT32_MAX; - utxo.script = Script(parse_hex("0014" "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + utxo.script = Script(parse_hex("0014" + "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); return utxo; } UTXOs buildTestUTXOs(const std::vector& amounts) { UTXOs utxos; - for (auto it = amounts.begin(); it != amounts.end(); it++) { - utxos.push_back(buildTestUTXO(*it)); + for (long long amount : amounts) { + utxos.push_back(buildTestUTXO(amount)); } return utxos; } @@ -48,7 +45,8 @@ SigningInput buildSigningInput(Amount amount, int byteFee, const UTXOs& utxos, b input.byteFee = byteFee; input.useMaxAmount = useMaxAmount; input.coinType = coin; - + input.dustCalculator = std::make_shared(coin); + if (!omitPrivateKey) { auto utxoKey = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); auto pubKey = utxoKey.getPublicKey(TWPublicKeyTypeSECP256k1); @@ -65,7 +63,7 @@ SigningInput buildSigningInput(Amount amount, int byteFee, const UTXOs& utxos, b int64_t sumUTXOs(const UTXOs& utxos) { int64_t s = 0u; - for (auto& utxo: utxos) { + for (auto& utxo : utxos) { s += utxo.amount; } return s; @@ -78,7 +76,7 @@ bool verifySelectedUTXOs(const UTXOs& selected, const std::vector& expe std::cerr << "Wrong number of selected UTXOs, " << selected.size() << " vs. " << expectedAmounts.size() << std::endl; } int errorCount = 0; - for (auto i = 0; i < selected.size() && i < expectedAmounts.size(); ++i) { + for (auto i = 0ul; i < selected.size() && i < expectedAmounts.size(); ++i) { if (expectedAmounts[i] != selected[i].amount) { ret = false; ++errorCount; @@ -104,8 +102,8 @@ bool verifyPlan(const TransactionPlan& plan, const std::vector& utxoAmo std::cerr << "Mismatch in fee, act " << plan.fee << ", exp " << fee << std::endl; } int64_t sumExpectedUTXOs = 0; - for (auto i = 0; i < utxoAmounts.size(); ++i) { - sumExpectedUTXOs += utxoAmounts[i]; + for (long long utxoAmount : utxoAmounts) { + sumExpectedUTXOs += utxoAmount; } if (plan.availableAmount != sumExpectedUTXOs) { ret = false; @@ -140,7 +138,7 @@ bool operator==(const EncodedTxSize& s1, const EncodedTxSize& s2) { } EncodedTxSize getEncodedTxSize(const Transaction& tx) { - EncodedTxSize size; + EncodedTxSize size{}; { // full segwit size Data data; tx.encode(data, Transaction::SegwitFormatMode::Segwit); @@ -156,13 +154,13 @@ EncodedTxSize getEncodedTxSize(const Transaction& tx) { Data data; tx.encodeWitness(data); witnessSize = data.size(); - assert(size.segwit - size.nonSegwit == 2 + witnessSize); + assert(size.segwit - size.nonSegwit == 2ul + witnessSize); } // compute virtual size: 3/4 of (smaller) non-segwit + 1/4 of segwit size - uint64_t sum = size.nonSegwit * 3 + size.segwit; + uint64_t sum = size.nonSegwit * 3 + size.segwit; size.virtualBytes = sum / 4 + (sum % 4 != 0); // alternative computation: (smaller) non-segwit + 1/4 of the diff (witness-only) - uint64_t vSize2 = size.nonSegwit + (witnessSize + 2)/ 4 + ((witnessSize + 2) % 4 != 0); + uint64_t vSize2 = size.nonSegwit + (witnessSize + 2) / 4 + ((witnessSize + 2) % 4 != 0); assert(size.virtualBytes == vSize2); return size; } @@ -196,7 +194,7 @@ void prettyPrintScript(const Script& script) { void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat) { Data data; - encode32LE(tx.version, data); + encode32LE(tx._version, data); std::cout << " \"" << hex(data) << "\" // version\n"; if (useWitnessFormat) { @@ -207,7 +205,7 @@ void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat) { data.clear(); encodeVarInt(tx.inputs.size(), data); std::cout << " \"" << hex(data) << "\" // inputs\n"; - for (auto& input: tx.inputs) { + for (auto& input : tx.inputs) { auto& outpoint = reinterpret_cast(input.previousOutput); std::cout << " \"" << hex(outpoint.hash) << "\""; data.clear(); @@ -223,7 +221,7 @@ void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat) { data.clear(); encodeVarInt(tx.outputs.size(), data); std::cout << " \"" << hex(data) << "\" // outputs\n"; - for (auto& output: tx.outputs) { + for (auto& output : tx.outputs) { data.clear(); encode64LE(output.value, data); std::cout << " \"" << hex(data) << "\""; @@ -233,11 +231,11 @@ void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat) { if (useWitnessFormat) { std::cout << " // witness\n"; - for (auto& input: tx.inputs) { + for (auto& input : tx.inputs) { data.clear(); encodeVarInt(input.scriptWitness.size(), data); std::cout << " \"" << hex(data) << "\"\n"; - for (auto& item: input.scriptWitness) { + for (auto& item : input.scriptWitness) { data.clear(); encodeVarInt(item.size(), data); std::cout << " \"" << hex(data) << "\""; @@ -251,3 +249,5 @@ void prettyPrintTransaction(const Transaction& tx, bool useWitnessFormat) { std::cout << " \"" << hex(data) << "\" // nLockTime\n"; std::cout << "\n"; } + +} // namespace TW::Bitcoin diff --git a/tests/chains/Bitcoin/TxComparisonHelper.h b/tests/chains/Bitcoin/TxComparisonHelper.h new file mode 100644 index 00000000000..b969b14595f --- /dev/null +++ b/tests/chains/Bitcoin/TxComparisonHelper.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "Bitcoin/Amount.h" +#include "Bitcoin/SigningInput.h" +#include "Bitcoin/Transaction.h" +#include "Bitcoin/TransactionPlan.h" +#include + +#include +#include +#include + +namespace TW::Bitcoin { + +/// Build a dummy UTXO with the given amount +UTXO buildTestUTXO(int64_t amount); + +/// Build a set of dummy UTXO with the given amounts +UTXOs buildTestUTXOs(const std::vector& amounts); + +SigningInput buildSigningInput(Amount amount, int byteFee, const UTXOs& utxos, + bool useMaxAmount = false, enum TWCoinType coin = TWCoinTypeBitcoin, bool omitPrivateKey = false); + +/// Compare a set of selected UTXOs to the expected set of amounts. +/// Returns false on mismatch, and error is printed (stderr). +bool verifySelectedUTXOs(const UTXOs& selected, const std::vector& expectedAmounts); + +/// Compare a transaction plan against expected values (UTXO amounts, amount, fee, change is implicit). +/// Returns false on mismatch, and error is printed (stderr). +bool verifyPlan(const TransactionPlan& plan, const std::vector& utxoAmounts, int64_t outputAmount, int64_t fee, Common::Proto::SigningError error = Common::Proto::OK); + +int64_t sumUTXOs(const UTXOs& utxos); + +struct EncodedTxSize { + uint64_t segwit; + uint64_t nonSegwit; + uint64_t virtualBytes; +}; +bool operator==(const EncodedTxSize& s1, const EncodedTxSize& s2); + +/// Return the encoded size of the transaction, virtual and non-segwit, etc. +EncodedTxSize getEncodedTxSize(const TW::Bitcoin::Transaction& tx); + +/// Validate the previously estimated transaction size (if available) with the actual transaction size. +/// Uses segwit byte size (virtual size). Tolerance is estiamte-smaller and estimate-larger, like -1 and 20. +/// Returns false on mismatch, and error is printed (stderr). +bool validateEstimatedSize(const TW::Bitcoin::Transaction& tx, int smallerTolerance = -1, int biggerTolerance = 20); + +/// Print out a transaction in a nice format, as structured hex strings. +void prettyPrintTransaction(const TW::Bitcoin::Transaction& tx, bool useWitnessFormat = true); + +} // namespace TW::Bitcoin diff --git a/tests/BitcoinCash/TWBitcoinCashTests.cpp b/tests/chains/BitcoinCash/TWBitcoinCashTests.cpp similarity index 95% rename from tests/BitcoinCash/TWBitcoinCashTests.cpp rename to tests/chains/BitcoinCash/TWBitcoinCashTests.cpp index 3109f631b20..734aa7d541c 100644 --- a/tests/BitcoinCash/TWBitcoinCashTests.cpp +++ b/tests/chains/BitcoinCash/TWBitcoinCashTests.cpp @@ -1,14 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Bitcoin/Address.h" #include "Bitcoin/SigHashType.h" #include "HexCoding.h" #include "proto/Bitcoin.pb.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include @@ -23,8 +21,10 @@ #include using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin::tests { + +// clang-format off TEST(BitcoinCash, Address) { EXPECT_TRUE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeBitcoinCash)); EXPECT_TRUE(TWAnyAddressIsValid(STRING("bitcoincash:pqx578nanz2h2estzmkr53zqdg6qt8xyqvwhn6qeyc").get(), TWCoinTypeBitcoinCash)); @@ -34,7 +34,7 @@ TEST(BitcoinCash, ValidAddress) { auto string = STRING("bitcoincash:qqa2qx0d8tegw32xk8u75ws055en4x3h2u0e6k46y4"); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeBitcoinCash)); ASSERT_NE(address.get(), nullptr); - + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(string.get(), TWCoinTypeBitcoinCash)); ASSERT_FALSE(TWBitcoinScriptSize(script.get()) == 0); } @@ -151,7 +151,7 @@ TEST(BitcoinCash, SignTransaction) { EXPECT_EQ(output.transaction().outputs_size(), 2); EXPECT_EQ(output.transaction().outputs(0).value(), amount); EXPECT_EQ(output.transaction().outputs(1).value(), 4325); - EXPECT_EQ(output.encoded().length(), 226); + EXPECT_EQ(output.encoded().length(), 226ul); ASSERT_EQ(hex(output.encoded()), "01000000" "01" @@ -161,3 +161,6 @@ TEST(BitcoinCash, SignTransaction) { "e510000000000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" "00000000"); } +// clang-format on + +} // namespace TW::Bitcoin::tests diff --git a/tests/chains/BitcoinCash/TWCoinTypeTests.cpp b/tests/chains/BitcoinCash/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..e86daa42bb1 --- /dev/null +++ b/tests/chains/BitcoinCash/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWBitcoinCashCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBitcoinCash)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBitcoinCash, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBitcoinCash, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBitcoinCash)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBitcoinCash)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBitcoinCash), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeBitcoinCash)); + ASSERT_EQ(0x5, TWCoinTypeP2shPrefix(TWCoinTypeBitcoinCash)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBitcoinCash)); + assertStringsEqual(symbol, "BCH"); + assertStringsEqual(txUrl, "https://blockchair.com/bitcoin-cash/transaction/t123"); + assertStringsEqual(accUrl, "https://blockchair.com/bitcoin-cash/address/a12"); + assertStringsEqual(id, "bitcoincash"); + assertStringsEqual(name, "Bitcoin Cash"); +} diff --git a/tests/chains/BitcoinDiamond/AddressTests.cpp b/tests/chains/BitcoinDiamond/AddressTests.cpp new file mode 100644 index 00000000000..a99b7ce46ea --- /dev/null +++ b/tests/chains/BitcoinDiamond/AddressTests.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "Coin.h" +#include "HexCoding.h" +#include "HDWallet.h" +#include "Bitcoin/Address.h" +#include "PublicKey.h" +#include "PrivateKey.h" +#include +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(BitcoinDiamondAddress, Valid) { + ASSERT_TRUE(Address::isValid(std::string("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"))); + ASSERT_TRUE(Address::isValid(std::string("3PaCtJcD7sxpCf3JNPLpMNnHnwJHoBeucF"))); + ASSERT_TRUE(Address::isValid(std::string("395mRH5aV3DLHPXC4Md9cPdpiquLtTqRSX"))); +} + +TEST(BitcoinDiamondAddress, FromPublicKey) { + const auto publicKey = PublicKey(parse_hex("02485a209514cc896f8ed736e205bc4c35bd5299ef3f9e84054475336b964c02a3"), TWPublicKeyTypeSECP256k1); + const auto address = Address(publicKey, TWCoinTypeP2pkhPrefix(TWCoinTypeBitcoinDiamond)); + EXPECT_EQ(address.string(), "1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + + auto wallet = HDWallet("shoot island position soft burden budget tooth cruel issue economy destroy above", ""); + auto addr = wallet.deriveAddress(TWCoinTypeBitcoinDiamond); + EXPECT_EQ(addr, "1KaRW9xPPtCTZ9FdaTHduCPck4YvSeEWNn"); + + addr = wallet.deriveAddress(TWCoinTypeBitcoinDiamond, TWDerivationBitcoinSegwit); + EXPECT_EQ(addr, "bcd1q7jh5qukuy9fc2pjm89xnyvx5dtfyvru9evw30x"); +} + +TEST(BitcoinDiamondAddress, FromString) { + auto address = Address("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + ASSERT_EQ(address.string(), "1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + + address = Address("3PaCtJcD7sxpCf3JNPLpMNnHnwJHoBeucF"); + ASSERT_EQ(address.string(), "3PaCtJcD7sxpCf3JNPLpMNnHnwJHoBeucF"); +} + +TEST(BitcoinDiamondAddress, AddressData) { + const auto publicKey = PublicKey(parse_hex("02485a209514cc896f8ed736e205bc4c35bd5299ef3f9e84054475336b964c02a3"), TWPublicKeyTypeSECP256k1); + auto address = TW::deriveAddress(TWCoinTypeBitcoinDiamond, publicKey, TWDerivationBitcoinSegwit); + + auto data = TW::addressToData(TWCoinTypeBitcoinDiamond, "1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + ASSERT_EQ(hex(data), "a48da46386ce52cccad178de900c71f06130c310"); + ASSERT_EQ(data, TW::addressToData(TWCoinTypeBitcoinDiamond, address)); + + data = TW::addressToData(TWCoinTypeBitcoinDiamond, "1G15VvshDxwFTnahZZECJfFwEkq9fP79"); // invalid address + ASSERT_EQ(data.size(), 0ul); +} diff --git a/tests/chains/BitcoinDiamond/SignerTests.cpp b/tests/chains/BitcoinDiamond/SignerTests.cpp new file mode 100644 index 00000000000..2b0a239d726 --- /dev/null +++ b/tests/chains/BitcoinDiamond/SignerTests.cpp @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" +#include "BitcoinDiamond/Signer.h" +#include "BitcoinDiamond/TransactionBuilder.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "proto/Bitcoin.pb.h" + +#include +#include + +using namespace TW; +namespace TW::BitcoinDiamond::tests { + +TEST(BitcoinDiamondSigner, Sign) { + const int64_t amount = 17615; + const int64_t fee = 10000; + const std::string toAddress = "1HevQVTSEc8cEpDm65UpfCdj5erd4xxBhx"; + + auto toScript = Bitcoin::Script::lockScriptForAddress(toAddress, TWCoinTypeBitcoinDiamond); + ASSERT_EQ(hex(toScript.bytes), "76a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac"); + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + input.set_coin_type(TWCoinTypeBitcoinDiamond); + + auto txHash0 = parse_hex("1d4653041a1915b3a52d47aeaa119c8f79ed7634a93692a6e811173067464f03"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(27615); + + auto utxoKey0 = + PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")); + auto utxoAddr0 = TW::deriveAddress(TWCoinTypeBitcoinDiamond, utxoKey0); + ASSERT_EQ(utxoAddr0, "1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeBitcoinDiamond); + ASSERT_EQ(hex(script0.bytes), "76a914a48da46386ce52cccad178de900c71f06130c31088ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 0; + plan.preBlockHash = + parse_hex("4bfa9d92e1e99e72597e1e9162bdaaab624f1bb6faa83b2f46c6777caf8d6699"); + std::reverse(plan.preBlockHash.begin(), plan.preBlockHash.end()); + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = BitcoinDiamond::Signer::sign(input); + + ASSERT_EQ( + hex(result.encoded()), + "0c00000099668daf7c77c6462f3ba8fab61b4f62abaabd62911e7e59729ee9e1929dfa4b01034f4667301711e8" + "a69236a93476ed798f9c11aaae472da5b315191a0453461d000000006a473044022078e0d3a9e1eb270ab02c15" + "f8fcf1d3bfc95a324839690b7de4f011a4266132ff02204679e8103c4d3f0bb5192a5f53cc273732fd0e8392ab" + "3b00dc708fd24d0160b3012102485a209514cc896f8ed736e205bc4c35bd5299ef3f9e84054475336b964c02a3" + "ffffffff01cf440000000000001976a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac00000000"); +} + +TEST(BitcoinDiamondSigner, SignSegwit) { + const int64_t amount = 17615; + const int64_t fee = 10000; + const std::string toAddress = "1HevQVTSEc8cEpDm65UpfCdj5erd4xxBhx"; + + auto toScript = Bitcoin::Script::lockScriptForAddress(toAddress, TWCoinTypeBitcoinDiamond); + ASSERT_EQ(hex(toScript.bytes), "76a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac"); + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + input.set_coin_type(TWCoinTypeBitcoinDiamond); + + auto txHash0 = parse_hex("1d4653041a1915b3a52d47aeaa119c8f79ed7634a93692a6e811173067464f03"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(27615); + + auto utxoKey0 = + PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")); + auto utxoAddr0 = + TW::deriveAddress(TWCoinTypeBitcoinDiamond, utxoKey0, TWDerivationBitcoinSegwit); + ASSERT_EQ(utxoAddr0, "bcd1q5jx6gcuxeefvejk30r0fqrr37psnpscslrrd0y"); + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeBitcoinDiamond); + ASSERT_EQ(hex(script0.bytes), "0014a48da46386ce52cccad178de900c71f06130c310"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 0; + plan.preBlockHash = + parse_hex("4bfa9d92e1e99e72597e1e9162bdaaab624f1bb6faa83b2f46c6777caf8d6699"); + std::reverse(plan.preBlockHash.begin(), plan.preBlockHash.end()); + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = BitcoinDiamond::Signer::sign(input); + + ASSERT_EQ(hex(result.encoded()), + "0c00000099668daf7c77c6462f3ba8fab61b4f62abaabd62911e7e59729ee9e1929dfa4b000101034f46" + "67301711e8a69236a93476ed798f9c11aaae472da5b315191a0453461d0000000000ffffffff01cf4400" + "00000000001976a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac02483045022100e9cd497e" + "a0156dbe72bd0e052869c8feb9bea182907023bcc447b98655a5e1080220767e85892df6e4c952bd62b1" + "8c76317f623c5e4f1a8bf4cef6b5e1193e17cf6e012102485a209514cc896f8ed736e205bc4c35bd5299" + "ef3f9e84054475336b964c02a300000000"); +} + +TEST(BitcoinDiamondSigner, SignAnyoneCanPay) { + const int64_t amount = 17615; + const int64_t fee = 10000; + const std::string toAddress = "1HevQVTSEc8cEpDm65UpfCdj5erd4xxBhx"; + + auto toScript = Bitcoin::Script::lockScriptForAddress(toAddress, TWCoinTypeBitcoinDiamond); + ASSERT_EQ(hex(toScript.bytes), "76a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac"); + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAnyoneCanPay | TWBitcoinSigHashTypeSingle); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + input.set_coin_type(TWCoinTypeBitcoinDiamond); + + auto txHash0 = parse_hex("1d4653041a1915b3a52d47aeaa119c8f79ed7634a93692a6e811173067464f03"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(27615); + + auto utxoKey0 = + PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")); + auto utxoAddr0 = + TW::deriveAddress(TWCoinTypeBitcoinDiamond, utxoKey0, TWDerivationBitcoinSegwit); + ASSERT_EQ(utxoAddr0, "bcd1q5jx6gcuxeefvejk30r0fqrr37psnpscslrrd0y"); + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeBitcoinDiamond); + ASSERT_EQ(hex(script0.bytes), "0014a48da46386ce52cccad178de900c71f06130c310"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 0; + plan.preBlockHash = + parse_hex("4bfa9d92e1e99e72597e1e9162bdaaab624f1bb6faa83b2f46c6777caf8d6699"); + std::reverse(plan.preBlockHash.begin(), plan.preBlockHash.end()); + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = BitcoinDiamond::Signer::sign(input); + + ASSERT_EQ(hex(result.encoded()), + "0c00000099668daf7c77c6462f3ba8fab61b4f62abaabd62911e7e59729ee9e1929dfa4b000101034f46" + "67301711e8a69236a93476ed798f9c11aaae472da5b315191a0453461d0000000000ffffffff01cf4400" + "00000000001976a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac02483045022100d659b1e0" + "8f30e133658eca9d97158723b49658bbe28930e361fa274bd11a0b090220587436eaaf3b9397d14af18f" + "a8b4c77a7d7d51bc4733a2821bf03865704966d7832102485a209514cc896f8ed736e205bc4c35bd5299" + "ef3f9e84054475336b964c02a300000000"); +} + +TEST(BitcoinDiamondSigner, SignWithError) { + const int64_t amount = 17615; + const std::string toAddress = "1HevQVTSEc8cEpDm65UpfCdj5erd4xxBhx"; + + auto toScript = Bitcoin::Script::lockScriptForAddress(toAddress, TWCoinTypeBitcoinDiamond); + ASSERT_EQ(hex(toScript.bytes), "76a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac"); + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + input.set_coin_type(TWCoinTypeBitcoinDiamond); + + auto inputData = data(input.SerializeAsString()); + + // PreImageHash + auto preData = TransactionCompiler::preImageHashes(TWCoinTypeBitcoinDiamond, inputData); + + TW::Bitcoin::Proto::PreSigningOutput output; + ASSERT_TRUE(output.ParseFromArray(preData.data(), (int)preData.size())); + + ASSERT_NE(output.error(), Common::Proto::OK); + + // Sign + auto result = BitcoinDiamond::Signer::sign(input); + ASSERT_NE(result.error(), Common::Proto::OK); +} + +} // namespace TW::BitcoinDiamond::tests diff --git a/tests/chains/BitcoinDiamond/TWAnyAddressTests.cpp b/tests/chains/BitcoinDiamond/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..43ae566f74e --- /dev/null +++ b/tests/chains/BitcoinDiamond/TWAnyAddressTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWBitcoinDiamond, Address) { + auto string = STRING("3CDf39adX4mc1AnvDzYHjw2NhxKswAPV3y"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeBitcoinDiamond)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "737cb7c194ec6502be59ed985d66b8bfe8b2b986"); + + string = STRING("1DH9cvKqGgzCvwoap45Nh75qV62wqje9pJ"); + addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeBitcoinDiamond)); + string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "86af5c1d5e754fc8906ec3c5d26e0135e1cb7c85"); + + // segwit addrerss + string = STRING("bcd1q35t4g0ezx9cl5qzac4tl377n9492uztqjwf5wh"); + addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeBitcoinDiamond)); + string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "8d17543f223171fa005dc557f8fbd32d4aae0960"); +} diff --git a/tests/chains/BitcoinDiamond/TWAnySignerTests.cpp b/tests/chains/BitcoinDiamond/TWAnySignerTests.cpp new file mode 100644 index 00000000000..f1ab62557f2 --- /dev/null +++ b/tests/chains/BitcoinDiamond/TWAnySignerTests.cpp @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "proto/Bitcoin.pb.h" +#include "proto/Common.pb.h" + +#include +#include +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + + +TEST(TWAnySignerBitcoinDiamond, Sign) { + const int64_t amount = 17615; + const int64_t fee = 10000; + const std::string toAddress = "1HevQVTSEc8cEpDm65UpfCdj5erd4xxBhx"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); + input.set_coin_type(TWCoinTypeBitcoinDiamond); + + auto txHash0 = parse_hex("1d4653041a1915b3a52d47aeaa119c8f79ed7634a93692a6e811173067464f03"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(27615); + + auto script0 = parse_hex("76a914a48da46386ce52cccad178de900c71f06130c31088ac"); + utxo0->set_script(script0.data(), (int)script0.size()); + + auto utxoKey0 = PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + Bitcoin::Proto::TransactionPlan plan; + { + ANY_PLAN(input, plan, TWCoinTypeBitcoinDiamond); + + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(0); + auto preBlockHash = parse_hex("4bfa9d92e1e99e72597e1e9162bdaaab624f1bb6faa83b2f46c6777caf8d6699"); + std::reverse(preBlockHash.begin(), preBlockHash.end()); + plan.set_preblockhash(preBlockHash.data(), (int)preBlockHash.size()); + } + + *input.mutable_plan() = plan; + + Bitcoin::Proto::SigningOutput output; + { + ANY_SIGN(input, TWCoinTypeBitcoinDiamond); + ASSERT_EQ(output.error(), Common::Proto::OK); + } + // Sign + ASSERT_EQ(hex(output.encoded()), "0c00000099668daf7c77c6462f3ba8fab61b4f62abaabd62911e7e59729ee9e1929dfa4b01034f4667301711e8a69236a93476ed798f9c11aaae472da5b315191a0453461d000000006a473044022078e0d3a9e1eb270ab02c15f8fcf1d3bfc95a324839690b7de4f011a4266132ff02204679e8103c4d3f0bb5192a5f53cc273732fd0e8392ab3b00dc708fd24d0160b3012102485a209514cc896f8ed736e205bc4c35bd5299ef3f9e84054475336b964c02a3ffffffff01cf440000000000001976a914b6adfbbf15c8f6fa53f1edb37054dce5c7c145c688ac00000000"); +} diff --git a/tests/chains/BitcoinDiamond/TWCoinTypeTests.cpp b/tests/chains/BitcoinDiamond/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..7e29ed4578e --- /dev/null +++ b/tests/chains/BitcoinDiamond/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWBitcoinDiamondCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBitcoinDiamond)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("ec564fe8993ba77f3f5c8b7f6ebb4cbc08e564a54612d6f4584cd1017cf723d4")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBitcoinDiamond, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("1HNTyntGXNhy4WxNzWfffPqp7LHb8bGJ9R")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBitcoinDiamond, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBitcoinDiamond)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBitcoinDiamond)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBitcoinDiamond), 7); + ASSERT_EQ(TWBlockchainBitcoinDiamond, TWCoinTypeBlockchain(TWCoinTypeBitcoinDiamond)); + ASSERT_EQ(0x5, TWCoinTypeP2shPrefix(TWCoinTypeBitcoinDiamond)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBitcoinDiamond)); + assertStringsEqual(symbol, "BCD"); + assertStringsEqual(txUrl, "http://explorer.btcd.io/#/tx?tx=ec564fe8993ba77f3f5c8b7f6ebb4cbc08e564a54612d6f4584cd1017cf723d4"); + assertStringsEqual(accUrl, "http://explorer.btcd.io/#/address?address=1HNTyntGXNhy4WxNzWfffPqp7LHb8bGJ9R"); + assertStringsEqual(id, "bitcoindiamond"); + assertStringsEqual(name, "Bitcoin Diamond"); +} diff --git a/tests/chains/BitcoinDiamond/TWSegwitAddressTests.cpp b/tests/chains/BitcoinDiamond/TWSegwitAddressTests.cpp new file mode 100644 index 00000000000..bf09d3f8d0f --- /dev/null +++ b/tests/chains/BitcoinDiamond/TWSegwitAddressTests.cpp @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// + +#include "TestUtilities.h" +#include "Bitcoin/SegwitAddress.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "HexCoding.h" +#include +#include + +using namespace TW; + +TEST(TWBitcoinDiamondSegwitAddress, Valid) { + ASSERT_TRUE(Bitcoin::SegwitAddress::isValid("bcd1q35t4g0ezx9cl5qzac4tl377n9492uztqjwf5wh")); + ASSERT_FALSE(Bitcoin::SegwitAddress::isValid("bcd1q35t4g0ezx9cl5qzac4tl377n9492uztqjwf5ac")); +} + +/// Initializes a Bech32 address with a human-readable part, a witness +/// version, and a witness program. +TEST(TWBitcoinDiamondSegwitAddress, WitnessProgramToAddress) { + auto address = Bitcoin::SegwitAddress("bcd", 0, parse_hex("8d17543f223171fa005dc557f8fbd32d4aae0960")); + + ASSERT_TRUE(Bitcoin::SegwitAddress::isValid(address.string())); + ASSERT_EQ(address.string(), "bcd1q35t4g0ezx9cl5qzac4tl377n9492uztqjwf5wh"); +} + +/// Initializes a Bech32 address with a public key and a HRP prefix. +TEST(TWBitcoinDiamondSegwitAddress, PubkeyToAddress) { + const auto publicKey = PublicKey(parse_hex("032a9ccb9cc6fd461df091b0f711730daa4292f9226aec918ac19381ac2af5e9ee"), TWPublicKeyTypeSECP256k1); + + /// construct with public key + auto address = Bitcoin::SegwitAddress(publicKey, "bcd"); + + ASSERT_TRUE(Bitcoin::SegwitAddress::isValid(address.string())); + ASSERT_EQ(address.string(), "bcd1q35t4g0ezx9cl5qzac4tl377n9492uztqjwf5wh"); +} + +/// Decodes a SegWit address. +TEST(TWBitcoinDiamondSegwitAddress, Decode) { + auto result = Bitcoin::SegwitAddress::decode("bcd1q35t4g0ezx9cl5qzac4tl377n9492uztqjwf5wh"); + + ASSERT_TRUE(std::get<2>(result)); + ASSERT_EQ(std::get<0>(result).string(), "bcd1q35t4g0ezx9cl5qzac4tl377n9492uztqjwf5wh"); + ASSERT_EQ(std::get<1>(result), "bcd"); +} diff --git a/tests/chains/BitcoinDiamond/TransactionCompilerTests.cpp b/tests/chains/BitcoinDiamond/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..744c64deb7a --- /dev/null +++ b/tests/chains/BitcoinDiamond/TransactionCompilerTests.cpp @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include + +#include "Bitcoin/Script.h" +#include "Bitcoin/SegwitAddress.h" +#include "Bitcoin/TransactionInput.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(BitcoinDiamondCompiler, CompileWithSignatures) { + // tx on mainnet + // http://explorer.btcd.io/#/tx?tx=6f8db2317c0940ff97c461e5e9b89692c6c1fded15fb30ae8b9cc2429ce43f66 + + const auto coin = TWCoinTypeBitcoinDiamond; + const int64_t amount = 196007725; + const int64_t fee = 1014; + const std::string toAddress = "39mKL9gxk29f2RiofywHYRDmgXPv1ur8uC"; + + auto toScript = Bitcoin::Script::lockScriptForAddress(toAddress, coin); + ASSERT_EQ(hex(toScript.bytes), "a914589133651fd11901381ecb4d3beef58bc28ba2e787"); + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("15ehpdrZqfZ5rj2e4T4hZKMi3kA8qdSyQu"); + input.set_coin_type(coin); + + auto txHash0 = parse_hex("6ce528c1192a9be648dd8c960695a15454c4c77b5a1dd5c8a5a208e6ae7e0ca8"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(501008739); + + auto utxoAddr0 = "15ehpdrZqfZ5rj2e4T4hZKMi3kA8qdSyQu"; + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, coin); + ASSERT_EQ(hex(script0.bytes), "76a9143301f83977102415e34cccd5ca15136a3dba87d588ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + EXPECT_EQ(input.utxo_size(), 1); + + // Plan + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(input, plan, coin); + + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(305000000); + auto preBlockHash = parse_hex("840980d100724999ea20e8b14ddd5ea5e37e2beacb9157a17fe87d0854bc7e6f"); + std::reverse(preBlockHash.begin(), preBlockHash.end()); + plan.set_preblockhash(preBlockHash.data(), (int)preBlockHash.size()); + + // Extend input with accepted plan + *input.mutable_plan() = plan; + + // Serialize input + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT((int)txInputData.size(), 0); + + /// Step 2: Obtain preimage hashes + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + TW::Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), (int)preImageHashes.size())); + + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].data_hash()), + "13565ac8d1d5a8a721417e0391cd13ea1a212b51b9d6bba093babaa203ed9d74"); + + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].public_key_hash()), "3301f83977102415e34cccd5ca15136a3dba87d5"); + + auto publicKeyHex = "02f65e76c2a7c239bd6c8b18dc10b71d463b96c0b0d827c97345e6bbe8ee8f2ddc"; + auto publicKey = PublicKey(parse_hex(publicKeyHex), TWPublicKeyTypeSECP256k1); + auto preImageHash = preSigningOutput.hash_public_keys()[0].data_hash(); + auto signature = parse_hex("3045022100e2c048cdf844c77275ac92cc27cfc357155d42d9a82d5d22f62247dce7681467022052c57d744a2ea91970b14e8863efdbcb3fb91f6448c027c25a8e86b752acb5ce"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE( + publicKey.verifyAsDER(signature, TW::Data(preImageHash.begin(), preImageHash.end()))); + + // Simulate signatures, normally obtained from signature server. + std::vector signatureVec; + std::vector pubkeyVec; + signatureVec.push_back(signature); + pubkeyVec.push_back(publicKey.bytes); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, pubkeyVec); + + const auto ExpectedTx = + "0c0000006f7ebc54087de87fa15791cbea2b7ee3a55edd4db1e820ea99497200d180098401a80c7eaee608a2a5c8d51d5a7bc7c45454a19506968cdd48e69b2a19c128e56c000000006b483045022100e2c048cdf844c77275ac92cc27cfc357155d42d9a82d5d22f62247dce7681467022052c57d744a2ea91970b14e8863efdbcb3fb91f6448c027c25a8e86b752acb5ce012102f65e76c2a7c239bd6c8b18dc10b71d463b96c0b0d827c97345e6bbe8ee8f2ddcffffffff022dd7ae0b0000000017a914589133651fd11901381ecb4d3beef58bc28ba2e78740ee2d12000000001976a9143301f83977102415e34cccd5ca15136a3dba87d588ac00000000"; + { + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: not enough signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, pubkeyVec); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + { // Negative: invalid public key + const auto publicKeyBlake = + parse_hex("b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"); + EXPECT_EXCEPTION( + TransactionCompiler::compileWithSignatures( + coin, txInputData, signatureVec, {publicKeyBlake}), + "Invalid public key"); + } + { // Negative: wrong signature (formally valid) + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, + {parse_hex("415502201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b349022" + "00a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f3b51")}, + pubkeyVec); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signing); + } +} diff --git a/tests/BitcoinGold/TWAddressTests.cpp b/tests/chains/BitcoinGold/TWAddressTests.cpp similarity index 76% rename from tests/BitcoinGold/TWAddressTests.cpp rename to tests/chains/BitcoinGold/TWAddressTests.cpp index 77c192461bb..ec7372aee34 100644 --- a/tests/BitcoinGold/TWAddressTests.cpp +++ b/tests/chains/BitcoinGold/TWAddressTests.cpp @@ -1,11 +1,9 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "Bitcoin/Address.h" #include "PrivateKey.h" #include "PublicKey.h" diff --git a/tests/BitcoinGold/TWBitcoinGoldTests.cpp b/tests/chains/BitcoinGold/TWBitcoinGoldTests.cpp similarity index 92% rename from tests/BitcoinGold/TWBitcoinGoldTests.cpp rename to tests/chains/BitcoinGold/TWBitcoinGoldTests.cpp index 1621692b37b..dddb5e8a13f 100644 --- a/tests/BitcoinGold/TWBitcoinGoldTests.cpp +++ b/tests/chains/BitcoinGold/TWBitcoinGoldTests.cpp @@ -1,30 +1,30 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include -#include #include +#include #include #include -#include "Bitcoin/SegwitAddress.h" -#include "proto/Bitcoin.pb.h" #include "Bitcoin/OutPoint.h" #include "Bitcoin/Script.h" +#include "Bitcoin/SegwitAddress.h" +#include "Bitcoin/SigHashType.h" #include "Bitcoin/Transaction.h" #include "Bitcoin/TransactionBuilder.h" #include "Bitcoin/TransactionSigner.h" -#include "Bitcoin/SigHashType.h" #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "proto/Bitcoin.pb.h" +#include "TestUtilities.h" using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin::tests { + +// clang-format off TEST(TWBitcoinGoldScript, LockScriptTest) { auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("btg1q6572ulr0kmywle8a30lvagm9xsg9k9n5cmzfdj").get(), TWCoinTypeBitcoinGold)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); @@ -79,12 +79,12 @@ TEST(TWBitcoinGoldTxGeneration, TxGeneration) { auto utxoKey0 = parse_hex("cbe13a79b82ec7f8871b336a64fd8d531f598e7c9022e29c67e824cfd54af57f"); input.add_private_key(utxoKey0.data(), utxoKey0.size()); input.set_lock_time(0x00098971); - + auto scriptPub1 = Script(parse_hex("0014db746a75d9aae8995d135b1e19a04d7765242a8f")); auto scriptHash = std::vector(); scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); - auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); + auto scriptHashHex = hex(scriptHash); auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); @@ -94,7 +94,7 @@ TEST(TWBitcoinGoldTxGeneration, TxGeneration) { auto utxo0Script = parse_hex("0014d53cae7c6fb6c8efe4fd8bfecea36534105b1674"); utxo0->set_script(utxo0Script.data(), utxo0Script.size()); utxo0->set_amount(10000); - + auto hash0 = parse_hex("5727794fa2b94aa22a226e206130524201ede9b50e032526e713c848493a890f"); utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); utxo0->mutable_out_point()->set_index(0); @@ -122,4 +122,6 @@ TEST(TWBitcoinGoldTxGeneration, TxGeneration) { "71890900" // nLockTime ); } - +// clang-format on + +} // namespace TW::Bitcoin::tests diff --git a/tests/chains/BitcoinGold/TWCoinTypeTests.cpp b/tests/chains/BitcoinGold/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..c58fa8c23de --- /dev/null +++ b/tests/chains/BitcoinGold/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWBitcoinGoldCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBitcoinGold)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("2f807d7734de35d2236a1b3d8704eb12954f5f82ea66987949b10e94d9999b23")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBitcoinGold, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("GJjz2Du9BoJQ3CPcoyVTHUJZSj62i1693U")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBitcoinGold, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBitcoinGold)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBitcoinGold)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBitcoinGold), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeBitcoinGold)); + ASSERT_EQ(0x17, TWCoinTypeP2shPrefix(TWCoinTypeBitcoinGold)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBitcoinGold)); + assertStringsEqual(symbol, "BTG"); + assertStringsEqual(txUrl, "https://explorer.bitcoingold.org/insight/tx/2f807d7734de35d2236a1b3d8704eb12954f5f82ea66987949b10e94d9999b23"); + assertStringsEqual(accUrl, "https://explorer.bitcoingold.org/insight/address/GJjz2Du9BoJQ3CPcoyVTHUJZSj62i1693U"); + assertStringsEqual(id, "bitcoingold"); + assertStringsEqual(name, "Bitcoin Gold"); +} diff --git a/tests/chains/BitcoinGold/TWSegwitAddressTests.cpp b/tests/chains/BitcoinGold/TWSegwitAddressTests.cpp new file mode 100644 index 00000000000..e4d6be2f679 --- /dev/null +++ b/tests/chains/BitcoinGold/TWSegwitAddressTests.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// + +#include "TestUtilities.h" +#include "Bitcoin/SegwitAddress.h" +#include "Coin.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "HexCoding.h" +#include +#include + +using namespace TW; + +TEST(TWBitcoinGoldSegwitAddress, Valid) { + ASSERT_TRUE(Bitcoin::SegwitAddress::isValid("btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk")); + ASSERT_FALSE(Bitcoin::SegwitAddress::isValid("btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sl")); +} + +/// Initializes a Bech32 address with a human-readable part, a witness +/// version, and a witness program. +TEST(TWBitcoinGoldSegwitAddress, WitnessProgramToAddress) { + auto address = Bitcoin::SegwitAddress("btg", 0, parse_hex("5e6132a9ad21f7423081441ab4ae229501f6c8a8")); + + ASSERT_TRUE(Bitcoin::SegwitAddress::isValid(address.string())); + ASSERT_EQ(address.string(), "btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); +} + +/// Get address data from a Bech32 address +TEST(TWBitcoinGoldSegwitAddress, addressToData) { + auto data = TW::addressToData(TWCoinTypeBitcoinGold, "btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); + ASSERT_EQ(hex(data), "5e6132a9ad21f7423081441ab4ae229501f6c8a8"); +} + +/// Initializes a Bech32 address with a public key and a HRP prefix. +TEST(TWBitcoinGoldSegwitAddress, PubkeyToAddress) { + const auto publicKey = PublicKey(parse_hex("02f74712b5d765a73b52a14c1e113f2ef3f9502d09d5987ee40f53828cfe68b9a6"), TWPublicKeyTypeSECP256k1); + + /// construct with public key + auto address = Bitcoin::SegwitAddress(publicKey, "btg"); + + ASSERT_TRUE(Bitcoin::SegwitAddress::isValid(address.string())); + ASSERT_EQ(address.string(), "btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); +} + +/// Decodes a SegWit address. +TEST(TWBitcoinGoldSegwitAddress, Decode) { + auto result = Bitcoin::SegwitAddress::decode("btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); + + ASSERT_TRUE(std::get<2>(result)); + ASSERT_EQ(std::get<0>(result).string(), "btg1qtesn92ddy8m5yvypgsdtft3zj5qldj9g2u52sk"); + ASSERT_EQ(std::get<1>(result), "btg"); +} diff --git a/tests/BitcoinGold/TWSignerTests.cpp b/tests/chains/BitcoinGold/TWSignerTests.cpp similarity index 87% rename from tests/BitcoinGold/TWSignerTests.cpp rename to tests/chains/BitcoinGold/TWSignerTests.cpp index b568ea08916..02d28a81c4a 100644 --- a/tests/BitcoinGold/TWSignerTests.cpp +++ b/tests/chains/BitcoinGold/TWSignerTests.cpp @@ -1,28 +1,24 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include #include #include -#include "Bitcoin/SegwitAddress.h" -#include "proto/Bitcoin.pb.h" #include "Bitcoin/OutPoint.h" #include "Bitcoin/Script.h" +#include "Bitcoin/SigHashType.h" #include "Bitcoin/Transaction.h" #include "Bitcoin/TransactionBuilder.h" #include "Bitcoin/TransactionSigner.h" -#include "Bitcoin/SigHashType.h" #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "proto/Bitcoin.pb.h" #include "../Bitcoin/TxComparisonHelper.h" +#include "TestUtilities.h" -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { TEST(TWBitcoinGoldSigner, SignTransaction) { const int64_t amount = 10000; @@ -39,11 +35,10 @@ TEST(TWBitcoinGoldSigner, SignTransaction) { auto utxoKey0 = parse_hex("cbe13a79b82ec7f8871b336a64fd8d531f598e7c9022e29c67e824cfd54af57f"); input.add_private_key(utxoKey0.data(), utxoKey0.size()); - auto scriptPub1 = Script(parse_hex("0014db746a75d9aae8995d135b1e19a04d7765242a8f")); auto scriptHash = std::vector(); scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); - auto scriptHashHex = hex(scriptHash.begin(), scriptHash.end()); + auto scriptHashHex = hex(scriptHash); auto redeemScript = Script::buildPayToPublicKeyHash(scriptHash); auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); @@ -53,14 +48,13 @@ TEST(TWBitcoinGoldSigner, SignTransaction) { auto utxo0Script = parse_hex("0014d53cae7c6fb6c8efe4fd8bfecea36534105b1674"); utxo0->set_script(utxo0Script.data(), utxo0Script.size()); utxo0->set_amount(99000); - + auto hash0 = parse_hex("1d4653041a1915b3a52d47aeaa119c8f79ed7634a93692a6e811173067464f03"); utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); utxo0->mutable_out_point()->set_index(1); utxo0->mutable_out_point()->set_sequence(0xfffffffd); input.set_lock_time(0x00098971); - Proto::TransactionPlan plan; { // try plan first @@ -83,7 +77,8 @@ TEST(TWBitcoinGoldSigner, SignTransaction) { Data serialized; signedTx.encode(serialized); // BitcoinGold Mainnet: https://btg2.trezor.io/tx/db26faec66d070045df0da56140349beb5a12bd14bca12b162fded8f84d18afa - EXPECT_EQ(serialized.size(), 222); + EXPECT_EQ(serialized.size(), 222ul); + // clang-format off ASSERT_EQ(hex(serialized), "01000000" "0001" @@ -96,6 +91,8 @@ TEST(TWBitcoinGoldSigner, SignTransaction) { "4730440220325c56363b17e1b1329efeb400c0933a3d9adfb304f29889b3ef01084aef19e302202a69d9be9ef668b5a5517fbfa42e1fc26b3f8b582c721bd1eabd721322bc2b6c41" "2103e00b5dec8078d526fba090247bd92db6b67a4dd1953b788cea9b52de9471b8cf" "71890900" - ); + ); + // clang-format on } - + +} // namespace TW::Bitcoin diff --git a/tests/chains/Blast/TWCoinTypeTests.cpp b/tests/chains/Blast/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..6136c26626d --- /dev/null +++ b/tests/chains/Blast/TWCoinTypeTests.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +TEST(TWBlastCoinType, TWCoinType) { + const auto coin = TWCoinTypeBlast; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x511fc00e8329343b9e953bf1f75e9b0a7b3cc2eb3a8f049d5be41adf4fbd6cac")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x0d11f2f0ff55c4fcfc3ff86bdc8e78ffa7df99fd")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "blast"); + assertStringsEqual(name, "Blast"); + assertStringsEqual(symbol, "ETH"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0); + assertStringsEqual(txUrl, "https://blastscan.io/tx/0x511fc00e8329343b9e953bf1f75e9b0a7b3cc2eb3a8f049d5be41adf4fbd6cac"); + assertStringsEqual(accUrl, "https://blastscan.io/address/0x0d11f2f0ff55c4fcfc3ff86bdc8e78ffa7df99fd"); +} diff --git a/tests/chains/Boba/TWCoinTypeTests.cpp b/tests/chains/Boba/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..a5e5201bf27 --- /dev/null +++ b/tests/chains/Boba/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWBobaCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBoba)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x31533707c3feb3b10f7deeea387ff8893f229253e65ca6b14d2400bf95b5d103")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBoba, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x4F96F50eDB37a19216d87693E5dB241e31bD3735")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBoba, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBoba)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBoba)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBoba), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeBoba)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeBoba)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBoba)); + assertStringsEqual(symbol, "BOBAETH"); + assertStringsEqual(txUrl, "https://blockexplorer.boba.network/tx/0x31533707c3feb3b10f7deeea387ff8893f229253e65ca6b14d2400bf95b5d103"); + assertStringsEqual(accUrl, "https://blockexplorer.boba.network/address/0x4F96F50eDB37a19216d87693E5dB241e31bD3735"); + assertStringsEqual(id, "boba"); + assertStringsEqual(name, "Boba"); +} diff --git a/tests/chains/BounceBit/TWCoinTypeTests.cpp b/tests/chains/BounceBit/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..7dbc072273e --- /dev/null +++ b/tests/chains/BounceBit/TWCoinTypeTests.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +TEST(TWBounceBitCoinType, TWCoinType) { + const auto coin = TWCoinTypeBounceBit; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x52558f4143d058d942e3b73414090266ae3ffce1fe8c25fe86896e2c8e5ef932")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xf4aa7349a9ccca4609943955b5ddc7bd9278c223")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "bouncebit"); + assertStringsEqual(name, "BounceBit"); + assertStringsEqual(symbol, "BB"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0); + assertStringsEqual(txUrl, "https://bbscan.io/tx/0x52558f4143d058d942e3b73414090266ae3ffce1fe8c25fe86896e2c8e5ef932"); + assertStringsEqual(accUrl, "https://bbscan.io/address/0xf4aa7349a9ccca4609943955b5ddc7bd9278c223"); +} diff --git a/tests/chains/Callisto/TWCoinTypeTests.cpp b/tests/chains/Callisto/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..752e2349656 --- /dev/null +++ b/tests/chains/Callisto/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWCallistoCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeCallisto)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeCallisto, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeCallisto, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeCallisto)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeCallisto)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeCallisto), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeCallisto)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeCallisto)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeCallisto)); + assertStringsEqual(symbol, "CLO"); + assertStringsEqual(txUrl, "https://explorer.callisto.network/tx/t123"); + assertStringsEqual(accUrl, "https://explorer.callisto.network/addr/a12"); + assertStringsEqual(id, "callisto"); + assertStringsEqual(name, "Callisto"); +} diff --git a/tests/chains/Cardano/AddressTests.cpp b/tests/chains/Cardano/AddressTests.cpp new file mode 100644 index 00000000000..c42ea1b5402 --- /dev/null +++ b/tests/chains/Cardano/AddressTests.cpp @@ -0,0 +1,561 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Cardano/AddressV3.h" + +#include "Coin.h" +#include "HDWallet.h" +#include "HexCoding.h" +#include "PrivateKey.h" + +#include + +using namespace TW; +using namespace std; + +namespace TW::Cardano::tests { + +const auto dummyKey = parse_hex("1111111111111111111111111111111111111111111111111111111111111111"); + +TEST(CardanoAddress, V3NetworkIdKind) { + EXPECT_EQ(AddressV3::firstByte(AddressV3::Network_Test, AddressV3::Kind_Base), 0); + EXPECT_EQ(AddressV3::firstByte(AddressV3::Network_Production, AddressV3::Kind_Base), 1); + EXPECT_EQ(AddressV3::firstByte(AddressV3::NetworkId(2), AddressV3::Kind(3)), 50); + + EXPECT_EQ(AddressV3::networkIdFromFirstByte(0), AddressV3::Network_Test); + EXPECT_EQ(AddressV3::networkIdFromFirstByte(1), AddressV3::Network_Production); + EXPECT_EQ(AddressV3::networkIdFromFirstByte(50), AddressV3::NetworkId(2)); + + EXPECT_EQ(AddressV3::kindFromFirstByte(0), AddressV3::Kind_Base); + EXPECT_EQ(AddressV3::kindFromFirstByte(1), AddressV3::Kind_Base); + EXPECT_EQ(AddressV3::kindFromFirstByte(50), AddressV3::Kind(3)); +} + +TEST(CardanoAddress, Validation) { + // valid V3 address + ASSERT_TRUE(AddressV3::isValidLegacy("addr1v9wa6entm75duchtu50mu6u6hkagdgqzaevt0cwryaw3pnca870vt")); + ASSERT_TRUE(AddressV3::isValidLegacy("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23")); + + ASSERT_TRUE(AddressV3::isValid("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23")); + ASSERT_TRUE(AddressV3::isValid("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5")); + ASSERT_TRUE(AddressV3::isValid("addr1vyuca7esanpgs4ke0um3ft6f4yaeuz3ftpfqx9nxpct2uyqu7dvlp")); // enterprise + ASSERT_TRUE(AddressV3::isValid("stake1uy9ggsc9qls4pu9qvyyacwnmr9tt0gzcdt5s0zj4au8qkqc65geks")); // reward + ASSERT_TRUE(AddressV3::isValid("addr1sxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qmxapsy")); // kind 8 + + // valid V2 address + ASSERT_TRUE(AddressV3::isValidLegacy("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx")); + ASSERT_TRUE(AddressV3::isValidLegacy("Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W")); + + ASSERT_FALSE(AddressV3::isValid("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx")); + + // valid V1 address + ASSERT_TRUE(AddressV3::isValidLegacy("DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ")); + ASSERT_TRUE(AddressV3::isValidLegacy("DdzFFzCqrht7HGoJ87gznLktJGywK1LbAJT2sbd4txmgS7FcYLMQFhawb18ojS9Hx55mrbsHPr7PTraKh14TSQbGBPJHbDZ9QVh6Z6Di")); + + // invalid V3, invalid network + ASSERT_FALSE(AddressV3::isValidLegacy("addr1sna05l45z33zpkm8z44q8f0h57wxvm0c86e34wlmua7gtcrdgrdrzy8ny3walyfjanhe33nsyuh088qr5gepqaen6jsa9r94xvvd7fh6jc3e6x")); + // invalid V3, invalid prefix + ASSERT_FALSE(AddressV3::isValidLegacy("prefix1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35q3hm7lv")); + // invalid V3, length + ASSERT_FALSE(AddressV3::isValidLegacy("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32xsmpqws7")); + // invalid checksum V3 + ASSERT_FALSE(AddressV3::isValidLegacy("PREFIX1qvqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqxuzx4s")); + // invalid checksum V2 + ASSERT_FALSE(AddressV3::isValidLegacy("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvm")); + // invalid V2 address + ASSERT_FALSE(AddressV2::isValid("73Fig6QU8N")); + // random + ASSERT_FALSE(AddressV3::isValidLegacy("hasoiusaodiuhsaijnnsajnsaiussai")); + // empty + ASSERT_FALSE(AddressV3::isValidLegacy("")); + ASSERT_FALSE(AddressV3::isValidLegacy("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk2")); +} + +TEST(CardanoAddress, FromStringV2) { + { + auto address = AddressV3("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(address.kind, AddressV3::Kind_Base); + EXPECT_EQ(hex(address.data()), "01" "8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034e" "cc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + } + { + auto address = AddressV3("addr1qxteqxsgxrs4he9d28lh70qu7qfz7saj6dmxwsqyle2yp3xvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35quehtx3"); + EXPECT_EQ(address.kind, AddressV3::Kind_Base); + EXPECT_EQ(hex(address.data()), "01" "97901a0830e15be4ad51ff7f3c1cf0122f43b2d376674004fe5440c4" "cc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + } + { + auto address = AddressV3("addr1q8sfzcwce0fqll3symd7f0amayxqq68nxt2u8pgen9y00tkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35q40ytea"); + EXPECT_EQ(address.kind, AddressV3::Kind_Base); + EXPECT_EQ(hex(address.data()), "01" "e09161d8cbd20ffe3026dbe4bfbbe90c0068f332d5c385199948f7ae" "cc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + } + { + auto address = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); + ASSERT_EQ(address.string(), "Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); + } + { + auto address = AddressV3("DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ"); + ASSERT_EQ(address.string(), "DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ"); + } + { + EXPECT_ANY_THROW(new AddressV3("")); + } +} + +TEST(CardanoAddress, FromStringV3_Base) { + { + auto address = AddressV3("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(address.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(address.string("addr"), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(AddressV3::Network_Production, address.networkId); + EXPECT_EQ(AddressV3::Kind_Base, address.kind); + EXPECT_EQ("8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468", hex(address.bytes)); + } +} + +TEST(CardanoAddress, FromStringV3_Enterprise) { + { + auto address = AddressV3("addr1vyuca7esanpgs4ke0um3ft6f4yaeuz3ftpfqx9nxpct2uyqu7dvlp"); + EXPECT_EQ(address.string(), "addr1vyuca7esanpgs4ke0um3ft6f4yaeuz3ftpfqx9nxpct2uyqu7dvlp"); + EXPECT_EQ(AddressV3::Network_Production, address.networkId); + EXPECT_EQ(AddressV3::Kind_Enterprise, address.kind); + EXPECT_EQ("398efb30ecc28856d97f3714af49a93b9e0a2958520316660e16ae10", hex(address.bytes)); + } +} + +TEST(CardanoAddress, FromStringV3_Reward) { + { + auto address = AddressV3("stake1uy9ggsc9qls4pu9qvyyacwnmr9tt0gzcdt5s0zj4au8qkqc65geks"); + EXPECT_EQ(address.string(), "stake1uy9ggsc9qls4pu9qvyyacwnmr9tt0gzcdt5s0zj4au8qkqc65geks"); + EXPECT_EQ(AddressV3::Network_Production, address.networkId); + EXPECT_EQ(AddressV3::Kind_Reward, address.kind); + EXPECT_EQ("0a84430507e150f0a06109dc3a7b1956b7a0586ae9078a55ef0e0b03", hex(address.bytes)); + } +} + +TEST(CardanoAddress, MnemonicToAddressV3) { + { + // Test from cardano-crypto.js; Test wallet + const auto mnemonic = "cost dash dress stove morning robust group affair stomach vacant route volume yellow salute laugh"; + const auto coin = TWCoinTypeCardano; + const auto derivPath = derivationPath(coin); + + const auto wallet = HDWallet(mnemonic, ""); + + // check entropy + EXPECT_EQ("30a6f50aeb58ff7699b822d63e0ef27aeff17d9f", hex(wallet.getEntropy())); + + { + PrivateKey masterPrivKey = wallet.getMasterKey(TWCurve::TWCurveED25519ExtendedCardano); + PrivateKey masterPrivKeyExt = wallet.getMasterKeyExtension(TWCurve::TWCurveED25519ExtendedCardano); + // the two together matches first half of keypair + ASSERT_EQ("a018cd746e128a0be0782b228c275473205445c33b9000a33dd5668b430b5744", hex(masterPrivKey.bytes)); + ASSERT_EQ("26877cfe435fddda02409b839b7386f3738f10a30b95a225f4b720ee71d2505b", hex(masterPrivKeyExt.bytes)); + + PublicKey masterPublicKey = masterPrivKey.getPublicKey(TWPublicKeyTypeED25519); + ASSERT_EQ("3aecb95953edd0b16db20366097ddedcb3512fe36193473c5fca2af774d44739", hex(masterPublicKey.bytes)); + } + { + string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); + EXPECT_EQ("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq", addr); + } + { + auto addressData = addressToData(TWCoinType::TWCoinTypeCardano, "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23"); + EXPECT_EQ(hex(addressData), "01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b"); + } + { + const auto privateKey = wallet.getKey(coin, derivPath); + EXPECT_EQ(hex(privateKey.bytes), "e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276"); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + + const auto address = AddressV3(publicKey); + EXPECT_EQ(address.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(address.networkId, AddressV3::Network_Production); + EXPECT_EQ(address.kind, AddressV3::Kind_Base); + EXPECT_EQ(hex(address.bytes), "8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034e" + "cc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + } + { + PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/0")); + EXPECT_EQ("e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b5744", hex(privateKey.key())); + EXPECT_EQ("37aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f31", hex(privateKey.extension())); + EXPECT_EQ("10f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fa", hex(privateKey.chainCode())); + EXPECT_EQ("e0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744", hex(privateKey.secondKey())); + EXPECT_EQ("424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5", hex(privateKey.secondExtension())); + EXPECT_EQ("bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276", hex(privateKey.secondChainCode())); + PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + EXPECT_EQ("fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26faf4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276", hex(publicKey.bytes)); + string addr = AddressV3(publicKey).string(); + EXPECT_EQ("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq", addr); + } + { + PrivateKey privKey0 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/44'/1815'/0'/0/0")); + PublicKey pubKey0 = privKey0.getPublicKey(TWPublicKeyTypeED25519Cardano); + auto addr0 = AddressV2(pubKey0); + EXPECT_EQ("Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W", addr0.string()); + } + { + PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/44'/1815'/0'/0/1")); + PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Cardano); + auto addr1 = AddressV2(pubKey1); + EXPECT_EQ("Ae2tdPwUPEZ7dnds6ZyhQdmgkrDFFPSDh8jG9RAhswcXt1bRauNw5jczjpV", addr1.string()); + } + { + PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/44'/1815'/0'/0/2")); + PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Cardano); + auto addr1 = AddressV2(pubKey1); + EXPECT_EQ("Ae2tdPwUPEZ8LAVy21zj4BF97iWxKCmPv12W6a18zLX3V7rZDFFVgqUBkKw", addr1.string()); + } + { + PrivateKey privKey0 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/0")); + PublicKey pubKey0 = privKey0.getPublicKey(TWPublicKeyTypeED25519Cardano); + auto addr0 = AddressV3(pubKey0); + EXPECT_EQ("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq", addr0.string()); + } + { + PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/1")); + PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Cardano); + auto addr1 = AddressV3(pubKey1); + EXPECT_EQ("addr1q9068st87h22h3l6w6t5evnlm067rag94llqya2hkjrsd3wvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qpmxzjt", addr1.string()); + } + { + PrivateKey privKey1 = wallet.getKey(TWCoinTypeCardano, DerivationPath("m/1852'/1815'/0'/0/2")); + PublicKey pubKey1 = privKey1.getPublicKey(TWPublicKeyTypeED25519Cardano); + auto addr1 = AddressV3(pubKey1); + EXPECT_EQ("addr1qxteqxsgxrs4he9d28lh70qu7qfz7saj6dmxwsqyle2yp3xvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35quehtx3", addr1.string()); + } + } + { + auto mnemonicPlay1 = "youth away raise north opinion slice dash bus soldier dizzy bitter increase saddle live champion"; + auto wallet = HDWallet(mnemonicPlay1, ""); + string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); + EXPECT_EQ("addr1q83nm9ntq3eaz8dya49txxtle6nn8geq4gmyylrzhzs7v0qjdwm6zuahwwds6c7mj8t6a09rup6m2cnh6zvzddnafp2slmcu95", addr); + } + { + auto mnemonicPlay2 = "return custom two home gain guilt kangaroo supply market current curtain tomorrow heavy blue robot"; + auto wallet = HDWallet(mnemonicPlay2, ""); + string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); + EXPECT_EQ("addr1qywxuqm7dx0yvqnn2yllye9urz5f2e4fgwanluzh008r22e53hart525dxgjcl0xzm0kes4n5tan8f5pz7ej0tkzgyrqtfmlal", addr); + } + { + auto mnemonicALDemo = "civil void tool perfect avocado sweet immense fluid arrow aerobic boil flash"; + auto wallet = HDWallet(mnemonicALDemo, ""); + string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); + EXPECT_EQ("addr1q94zzrtl32tjp8j96auatnhxd2y35fnk6wuxqvqm9364vp9spdkjdsmyfhvfagjzh4uzp9zs6p5djw89jac2g0ujs2eqsuy7pu", addr); + } + { + // V2 Tested against AdaLite + auto mnemonicPlay1 = "youth away raise north opinion slice dash bus soldier dizzy bitter increase saddle live champion"; + auto wallet = HDWallet(mnemonicPlay1, ""); + PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); + PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + string addr = AddressV2(publicKey).string(); + EXPECT_EQ("Ae2tdPwUPEZJYT9g1JgQWtLveUHavyRxQGi6hVzoQjct7yyCLGgk3pCyx7h", addr); + } + { + // V2 Tested against AdaLite + auto mnemonicPlay2 = "return custom two home gain guilt kangaroo supply market current curtain tomorrow heavy blue robot"; + auto wallet = HDWallet(mnemonicPlay2, ""); + PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); + PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + string addr = AddressV2(publicKey).string(); + EXPECT_EQ("Ae2tdPwUPEZLtJx7LA2XZ3zzwonH9x9ieX3dMzaTBD3TfXuKczjMSjTecr1", addr); + } + { + // V2 AdaLite Demo phrase, 12-word. AdaLite uses V1 for it, in V2 it produces different addresses. + // In AdaLite V1 addr0 is DdzFFzCqrht7HGoJ87gznLktJGywK1LbAJT2sbd4txmgS7FcYLMQFhawb18ojS9Hx55mrbsHPr7PTraKh14TSQbGBPJHbDZ9QVh6Z6Di + auto mnemonicALDemo = "civil void tool perfect avocado sweet immense fluid arrow aerobic boil flash"; + auto wallet = HDWallet(mnemonicALDemo, ""); + PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); + PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + string addr = AddressV2(publicKey).string(); + EXPECT_EQ("Ae2tdPwUPEZJbLcD8iLgN7hVGvq66WdR4zocntRekSP97Ds3MvCfmEDjJYu", addr); + } +} + +TEST(CardanoAddress, KeyHashV2) { + auto xpub = parse_hex("e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf69869272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"); + auto hash = AddressV2::keyHash(xpub); + ASSERT_EQ("a1eda96a9952a56c983d9f49117f935af325e8a6c9d38496e945faa8", hex(hash)); +} + +TEST(CardanoAddress, FromDataV3_Base) { + auto address0 = AddressV3("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(address0.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(hex(address0.data()), "018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + { + auto address = AddressV3(parse_hex("018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468")); + EXPECT_EQ(address.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(address.kind, AddressV3::Kind_Base); + EXPECT_EQ(address.networkId, AddressV3::Network_Production); + EXPECT_EQ(hex(address.bytes), "8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + } + { + auto address = AddressV3::createBase(AddressV3::Network_Production, + PublicKey(parse_hex("fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da31"), TWPublicKeyTypeED25519), + PublicKey(parse_hex("f4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4"), TWPublicKeyTypeED25519)); + EXPECT_EQ(address.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + } + { + EXPECT_ANY_THROW(AddressV3::createBase(AddressV3::Network_Production, + PublicKey(parse_hex("039997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be"), TWPublicKeyTypeSECP256k1), + PublicKey(parse_hex("f4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4"), TWPublicKeyTypeED25519))); + } + { + EXPECT_ANY_THROW(AddressV3::createBase(AddressV3::Network_Production, + PublicKey(parse_hex("fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da31"), TWPublicKeyTypeED25519), + PublicKey(parse_hex("039997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be"), TWPublicKeyTypeSECP256k1))); + } + { + EXPECT_ANY_THROW(AddressV3::createBase(AddressV3::Network_Production, + parse_hex("039997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be"), + parse_hex("f4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4"))); + } + { + EXPECT_ANY_THROW(AddressV3::createBase(AddressV3::Network_Production, + parse_hex("fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da31"), + parse_hex("039997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be"))); + } +} + +TEST(CardanoAddress, FromPrivateKeyV3) { + { + // from cardano-crypto.js test + auto privateKey = PrivateKey( + parse_hex("d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48"), + parse_hex("d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c"), + parse_hex("69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"), + dummyKey, dummyKey, dummyKey); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + EXPECT_ANY_THROW(new AddressV3(publicKey)); + } +} + +TEST(CardanoAddress, FromDataV3_Enterprise) { + auto address = AddressV3(parse_hex("61398efb30ecc28856d97f3714af49a93b9e0a2958520316660e16ae10")); + EXPECT_EQ(address.string(), "addr1vyuca7esanpgs4ke0um3ft6f4yaeuz3ftpfqx9nxpct2uyqu7dvlp"); + EXPECT_EQ(address.kind, AddressV3::Kind_Enterprise); + EXPECT_EQ(address.networkId, AddressV3::Network_Production); + EXPECT_EQ(hex(address.bytes), "398efb30ecc28856d97f3714af49a93b9e0a2958520316660e16ae10"); +} + +TEST(CardanoAddress, FromDataV3_Reward) { + auto address = AddressV3(parse_hex("e10a84430507e150f0a06109dc3a7b1956b7a0586ae9078a55ef0e0b03")); + EXPECT_EQ(address.string(), "stake1uy9ggsc9qls4pu9qvyyacwnmr9tt0gzcdt5s0zj4au8qkqc65geks"); + EXPECT_EQ(address.kind, AddressV3::Kind_Reward); + EXPECT_EQ(address.networkId, AddressV3::Network_Production); + EXPECT_EQ(hex(address.bytes), "0a84430507e150f0a06109dc3a7b1956b7a0586ae9078a55ef0e0b03"); +} + +TEST(CardanoAddress, FromDataV3_Invalid) { + { // base, invalid length + auto address = AddressV3(parse_hex("018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a34")); + EXPECT_EQ(address.string(), "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32xsmpqws7"); + EXPECT_EQ(address.kind, AddressV3::Kind_Base); + EXPECT_EQ(address.networkId, AddressV3::Network_Production); + EXPECT_EQ(hex(address.bytes), "8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a34"); + } + { // kind = 8 + auto address = AddressV3(parse_hex("818d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468")); + EXPECT_EQ(address.string(), "addr1sxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qmxapsy"); + EXPECT_EQ(address.kind, static_cast(8)); + EXPECT_EQ(address.networkId, AddressV3::Network_Production); + EXPECT_EQ(hex(address.bytes), "8d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + } +} + +TEST(CardanoAddress, FromPublicKeyV2) { + { + // caradano-crypto.js test + auto publicKey = PublicKey(parse_hex( + "e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf69869272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" + "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" // dummy second + ), TWPublicKeyTypeED25519Cardano); + auto address = AddressV2(publicKey); + ASSERT_EQ(address.string(), "Ae2tdPwUPEZCxt4UV1Uj2AMMRvg5pYPypqZowVptz3GYpK4pkcvn3EjkuNH"); + } + { + // Adalite test account addr0 + auto publicKey = PublicKey(parse_hex( + "57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" + "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" // dummy second + ), TWPublicKeyTypeED25519Cardano); + auto address = AddressV2(publicKey); + ASSERT_EQ(address.string(), "Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W"); + } + { + // Adalite test account addr1 + auto publicKey = PublicKey(parse_hex( + "25af99056d600f7956312406bdd1cd791975bb1ae91c9d034fc65f326195fcdb247ee97ec351c0820dd12de4ca500232f73a35fe6f86778745bcd57f34d1048d" + "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" // dummy second + ), TWPublicKeyTypeED25519Cardano); + auto address = AddressV2(publicKey); + ASSERT_EQ(address.string(), "Ae2tdPwUPEZ7dnds6ZyhQdmgkrDFFPSDh8jG9RAhswcXt1bRauNw5jczjpV"); + } + { + // Play1 addr0 + auto publicKey = PublicKey(parse_hex( + "7cee0f30b9d536a786547dd77b35679b6830e945ffde768eb4f2a061b9dba016e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2" + "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" // dummy second + ), TWPublicKeyTypeED25519Cardano); + auto address = AddressV2(publicKey); + ASSERT_EQ(address.string(), "Ae2tdPwUPEZJYT9g1JgQWtLveUHavyRxQGi6hVzoQjct7yyCLGgk3pCyx7h"); + } +} + +TEST(CardanoAddress, FromPrivateKeyV2) { + { + // mnemonic Test, addr0 + auto privateKey = PrivateKey( + parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), + parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), + parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), + dummyKey, dummyKey, dummyKey + ); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + ASSERT_EQ(hex(publicKey.bytes), + "57fd54be7b38bb8952782c2f59aa276928a4dcbb66c8c62ce44f9d623ecd5a03" + "bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" + "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" + "1111111111111111111111111111111111111111111111111111111111111111"); + auto address = AddressV2(publicKey); + ASSERT_EQ(address.string(), "Ae2tdPwUPEZ6RUCnjGHFqi59k5WZLiv3HoCCNGCW8SYc5H9srdTzn1bec4W"); + } + { + // mnemonic Play1, addr0 + auto privateKey = PrivateKey( + parse_hex("a089c9423100960440ccd5b7adbd202d1ab1993a7bb30fc88b287d94016df247"), + parse_hex("da86a87f08fb15de1431a6c0ccd5ebf51c3bee81f7eaf714801bbbe4d903154a"), + parse_hex("e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2"), + dummyKey, dummyKey, dummyKey + ); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + ASSERT_EQ(hex(publicKey.bytes), + "7cee0f30b9d536a786547dd77b35679b6830e945ffde768eb4f2a061b9dba016" + "e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2" + "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" + "1111111111111111111111111111111111111111111111111111111111111111"); + auto address = AddressV2(publicKey); + ASSERT_EQ(address.string(), "Ae2tdPwUPEZJYT9g1JgQWtLveUHavyRxQGi6hVzoQjct7yyCLGgk3pCyx7h"); + } + { + // from cardano-crypto.js test + auto privateKey = PrivateKey( + parse_hex("d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48"), + parse_hex("d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c"), + parse_hex("69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"), + dummyKey, dummyKey, dummyKey + ); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + ASSERT_EQ(hex(publicKey.bytes), + "e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf698" + "69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" + "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" + "1111111111111111111111111111111111111111111111111111111111111111"); + auto address = AddressV2(publicKey); + ASSERT_EQ(address.string(), "Ae2tdPwUPEZCxt4UV1Uj2AMMRvg5pYPypqZowVptz3GYpK4pkcvn3EjkuNH"); + } + { + // from cardano-crypto.js test + auto privateKey = PrivateKey( + parse_hex("d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48"), + parse_hex("d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c"), + parse_hex("69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"), + dummyKey, dummyKey, dummyKey); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + EXPECT_ANY_THROW(new AddressV2(publicKey)); + } +} + +TEST(CardanoAddress, PrivateKeyExtended) { + // check extended key lengths, private key 2x3x32 bytes, public key 2x64 bytes + auto privateKeyExt = PrivateKey( + parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), + parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), + parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), + dummyKey, dummyKey, dummyKey + ); + auto publicKeyExt = privateKeyExt.getPublicKey(TWPublicKeyTypeED25519Cardano); + ASSERT_EQ(128ul, publicKeyExt.bytes.size()); + + // Non-extended: both are 32 bytes. + auto privateKeyNonext = PrivateKey( + parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744")); + auto publicKeyNonext = privateKeyNonext.getPublicKey(TWPublicKeyTypeED25519); + ASSERT_EQ(32ul, publicKeyNonext.bytes.size()); +} + +TEST(CardanoAddress, FromStringNegativeInvalidString) { + try { + auto address = AddressV3("__INVALID_ADDRESS__"); + } catch (...) { + return; + } + FAIL() << "Expected exception!"; +} + +TEST(CardanoAddress, FromStringNegativeBadChecksumV2) { + try { + auto address = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvm"); + } catch (...) { + return; + } + FAIL() << "Expected exception!"; +} + +TEST(CardanoAddress, CopyConstructorLegacy) { + AddressV3 address1 = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); + EXPECT_TRUE(address1.legacyAddressV2.has_value()); + AddressV3 address2 = AddressV3(address1); + EXPECT_TRUE(address2.legacyAddressV2.has_value()); + EXPECT_TRUE(*(address2.legacyAddressV2) == *(address1.legacyAddressV2)); + // if it was not a deep copy, double freeing would occur +} + +TEST(CardanoAddress, AssignmentOperatorLegacy) { + AddressV3 addr1leg = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); + EXPECT_TRUE(addr1leg.legacyAddressV2.has_value()); + AddressV3 addr2nonleg = AddressV3("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_FALSE(addr2nonleg.legacyAddressV2.has_value()); + AddressV3 addr3leg = AddressV3("Ae2tdPwUPEZ18ZjTLnLVr9CEvUEUX4eW1LBHbxxxJgxdAYHrDeSCSbCxrvx"); + EXPECT_TRUE(addr3leg.legacyAddressV2.has_value()); + + AddressV3 address = addr1leg; + EXPECT_TRUE(address.legacyAddressV2.has_value()); + EXPECT_TRUE(*address.legacyAddressV2 == *addr1leg.legacyAddressV2); + address = addr2nonleg; + EXPECT_FALSE(address.legacyAddressV2.has_value()); + address = addr3leg; + EXPECT_TRUE(address.legacyAddressV2.has_value()); + EXPECT_TRUE(*address.legacyAddressV2 == *addr3leg.legacyAddressV2); +} + +TEST(CardanoAddress, StakingKey) { + { + auto address = AddressV3("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23"); + EXPECT_EQ(hex(address.data()), "01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b"); + EXPECT_EQ(address.getStakingAddress(), "stake1u80jysjtdzqt88jt4jx93h5lumfr67d273r4vwyasfa2pxcwxllmx"); + EXPECT_EQ(hex(AddressV3(address.getStakingAddress()).data()), "e1df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b"); + EXPECT_EQ(hex(AddressV3(address.getStakingAddress()).bytes), "df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b"); + } + { + auto address = AddressV3("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + EXPECT_EQ(hex(address.data()), "018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + EXPECT_EQ(address.getStakingAddress(), "stake1u8xxf0e93w8rxr8sehvlmvp7zz6wftqg7hdplhkxyg4rg6qwgxzhc"); + EXPECT_EQ(hex(AddressV3(address.getStakingAddress()).data()), "e1cc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + EXPECT_EQ(hex(AddressV3(address.getStakingAddress()).bytes), "cc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468"); + } + { + auto address = AddressV3("addr1q8lcljuzfg8yvpuv94x02sytmwd8jsalzf6u0j8muhq69wng9ejcvpyczmw0zx7wguq2dml4xdl2wj3k7uexsfnxep2q9ja352"); + EXPECT_EQ(hex(address.data()), "01ff8fcb824a0e46078c2d4cf5408bdb9a7943bf1275c7c8fbe5c1a2ba682e6586049816dcf11bce4700a6eff5337ea74a36f732682666c854"); + EXPECT_EQ(address.getStakingAddress(), "stake1u95zuevxqjvpdh83r08ywq9xal6nxl48fgm0wvngyenvs4qh0hqf9"); + EXPECT_EQ(hex(AddressV3(address.getStakingAddress()).data()), "e1682e6586049816dcf11bce4700a6eff5337ea74a36f732682666c854"); + EXPECT_EQ(hex(AddressV3(address.getStakingAddress()).bytes), "682e6586049816dcf11bce4700a6eff5337ea74a36f732682666c854"); + } + { // negative case: cannot get staking address from non-base address + auto address = AddressV3("stake1u95zuevxqjvpdh83r08ywq9xal6nxl48fgm0wvngyenvs4qh0hqf9"); + EXPECT_EQ(hex(address.data()), "e1682e6586049816dcf11bce4700a6eff5337ea74a36f732682666c854"); + EXPECT_EQ(address.getStakingAddress(), ""); + } +} + +} // namespace TW::Cardano::tests diff --git a/tests/chains/Cardano/SigningTests.cpp b/tests/chains/Cardano/SigningTests.cpp new file mode 100644 index 00000000000..ccc689580eb --- /dev/null +++ b/tests/chains/Cardano/SigningTests.cpp @@ -0,0 +1,998 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Cardano/AddressV3.h" +#include "Cardano/Signer.h" +#include "proto/Cardano.pb.h" +#include + +#include "Cbor.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "uint256.h" +#include "TestUtilities.h" +#include + +#include +#include + +using namespace TW; +using namespace std; + +namespace TW::Cardano::SigningTests { + +const auto privateKeyTest1 = "089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e"; +const auto ownAddress1 = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23"; +const auto sundaeTokenPolicy = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77"; + +TEST(CardanoSigning, SelectInputs) { + const auto inputs = std::vector({ + TxInput{{parse_hex("0001"), 0}, "ad01", 700, {}}, + TxInput{{parse_hex("0002"), 1}, "ad02", 900, {}}, + TxInput{{parse_hex("0003"), 2}, "ad03", 300, {}}, + TxInput{{parse_hex("0004"), 3}, "ad04", 600, {}}, + }); + + { // 2 + const auto s1 = Signer::selectInputsWithTokens(inputs, 1500, {}); + ASSERT_EQ(s1.size(), 2ul); + EXPECT_EQ(s1[0].amount, 900ul); + EXPECT_EQ(s1[1].amount, 700ul); + } + { // all + const auto s1 = Signer::selectInputsWithTokens(inputs, 10000, {}); + ASSERT_EQ(s1.size(), 4ul); + EXPECT_EQ(s1[0].amount, 900ul); + EXPECT_EQ(s1[1].amount, 700ul); + EXPECT_EQ(s1[2].amount, 600ul); + EXPECT_EQ(s1[3].amount, 300ul); + } + { // 3 + const auto s1 = Signer::selectInputsWithTokens(inputs, 2000, {}); + ASSERT_EQ(s1.size(), 3ul); + } + { // 1 + const auto s1 = Signer::selectInputsWithTokens(inputs, 500, {}); + ASSERT_EQ(s1.size(), 1ul); + } + { // at least 0 is returned + const auto s1 = Signer::selectInputsWithTokens(inputs, 0, {}); + ASSERT_EQ(s1.size(), 1ul); + } +} + +Proto::SigningInput createSampleInput(uint64_t amount, int utxoCount = 10, + const std::string& alternateToAddress = "", bool omitPrivateKey = false) { + const std::string toAddress = (alternateToAddress.length() > 0) ? alternateToAddress : "addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"; + + Proto::SigningInput input; + if (utxoCount >= 1) { + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(1); + utxo1->set_address(ownAddress1); + utxo1->set_amount(1500000); + } + if (utxoCount >= 2) { + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0"); + utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); + utxo2->mutable_out_point()->set_output_index(0); + utxo2->set_address(ownAddress1); + utxo2->set_amount(6500000); + } + + if (!omitPrivateKey) { + const auto privateKeyData = parse_hex(privateKeyTest1); + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + } + input.mutable_transfer_message()->set_to_address(toAddress); + input.mutable_transfer_message()->set_change_address(ownAddress1); + input.mutable_transfer_message()->set_amount(amount); + input.mutable_transfer_message()->set_use_max_amount(false); + input.set_ttl(53333333); + return input; +} + +/// Successfully broadcasted: +/// https://cardanoscan.io/transaction/87ca43a36b09c0b140f0ef2b71fbdcfcf1fdc88f7aa378b861e8eed3e8974628 +TEST(CardanoSigning, SendNft) { + const auto fromAddressPrivKey = "d09831a668db6b36ffb747600cb1cd3e3d34f36e1e6feefc11b5f988719b7557a7029ab80d3e6fe4180ad07a59ddf742ea9730f3c4145df6365fa4ae2ee49c3392e19444caf461567727b7fefec40a3763bdb6ce5e0e8c05f5e340355a8fef4528dfe7502cfbda49e38f5a0021962d52dc3dee82834a23abb6750981799b75577d1ed9af9853707f0ef74264274e71b2f12e86e3c91314b6efa75ef750d9711b84cedd742ab873ef2f9566ad20b3fc702232c6d2f5d83ff425019234037d1e58"; + const auto fromAddress = "addr1qy5eme9r6frr0m6q2qpncg282jtrhq5lg09uxy2j0545hj8rv7v2ntdxuv6p4s3eq4lqzg39lewgvt6fk5kmpa0zppesufzjud"; + const auto toAddress = "addr1qy9wjfn6nd8kak6dd8z53u7t5wt9f4lx0umll40px5hnq05avwcsq5r3ytdp36wttzv4558jaq8lvhgqhe3y8nuf5xrquju7z4"; + const auto nftPolicyId = "219820e6cb04316f41a337fea356480f412e7acc147d28f175f21b5e"; + const auto nftAssetName = "coolcatssociety4567"; + const auto nftTokenAmount = 1ul; + // 1.20249 ADA. Amount locked by the NFT. + const auto nftInputAmount = 1202490ul; + const auto ttl = 89130965ul; + + Proto::SigningInput input; + + // Set the first utxo (NFT token and locked ADA). + + auto* utxo1 = input.add_utxos(); + // NFT unspent output. + const auto txHash1 = parse_hex("aba499ec2f23529e70bb256ceaffcc6274a882cf02f29e5670c75ee980d7c2b8"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(0); + utxo1->set_address(fromAddress); + utxo1->set_amount(nftInputAmount); + + auto* token1 = utxo1->add_token_amount(); + token1->set_policy_id(nftPolicyId); + token1->set_asset_name(nftAssetName); + const auto tokenAmount1 = store(uint256_t(nftTokenAmount)); + token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); + + // Set additional utxos to pay fee. + + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("ee414d635b3bc67831907354d274a31174664777c57c21ae923b9459e5644840"); + utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); + utxo2->mutable_out_point()->set_output_index(0); + utxo2->set_address(fromAddress); + utxo2->set_amount(1000000); + + auto* utxo3 = input.add_utxos(); + const auto txHash3 = parse_hex("6a7221dcc28353ed69b733391ffeb984a34c1e72293af111d59f9ddfa8639167"); + utxo3->mutable_out_point()->set_tx_hash(txHash3.data(), txHash3.size()); + utxo3->mutable_out_point()->set_output_index(0); + utxo3->set_address(fromAddress); + utxo3->set_amount(2000000); + + PrivateKey privKey(parse_hex(fromAddressPrivKey)); + input.add_private_key(privKey.bytes.data(), privKey.bytes.size()); + + // Set an output info. + + input.mutable_transfer_message()->set_to_address(toAddress); + input.mutable_transfer_message()->set_change_address(fromAddress); + input.mutable_transfer_message()->set_amount(nftInputAmount); + + auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + toToken->set_policy_id(nftPolicyId); + toToken->set_asset_name(nftAssetName); + const auto toTokenAmount = store(uint256_t(nftTokenAmount)); + toToken->set_amount(toTokenAmount.data(), toTokenAmount.size()); + input.set_ttl(ttl); + + { // check min ADA amount + // The byte cost at the moment when the transaction was constructed. + // See `ProtocolParams::coinsPerUtxoByte`: + // https://input-output-hk.github.io/cardano-graphql/ + const auto coinsPerUtxoByte = STRING("4310"); + + const auto bundleProtoData = data(input.transfer_message().token_amount().SerializeAsString()); + const auto toAddressPtr = STRING(toAddress); + + const auto minAdaAmount = WRAPS(TWCardanoOutputMinAdaAmount(toAddressPtr.get(), &bundleProtoData, coinsPerUtxoByte.get())); + assertStringsEqual(minAdaAmount, std::to_string(nftInputAmount).c_str()); + EXPECT_EQ(input.transfer_message().amount(), nftInputAmount); + } + + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + const auto output = signer.sign(); + + const auto txid = hex(data(output.tx_id())); + EXPECT_EQ(txid, "87ca43a36b09c0b140f0ef2b71fbdcfcf1fdc88f7aa378b861e8eed3e8974628"); + + EXPECT_EQ(plan.availableAmount, nftInputAmount + 1000000ul + 2000000ul); + EXPECT_EQ(plan.amount, nftInputAmount); + EXPECT_EQ(plan.fee, 176539ul); + EXPECT_EQ(plan.change, 1000000ul + 2000000ul - 176539ul); + EXPECT_EQ(plan.utxos.size(), 3ul); + EXPECT_EQ(plan.availableTokens.size(), nftTokenAmount); + EXPECT_EQ(plan.availableTokens.getAmount("219820e6cb04316f41a337fea356480f412e7acc147d28f175f21b5e_coolcatssociety4567"), nftTokenAmount); + EXPECT_EQ(plan.outputTokens.size(), 1ul); + EXPECT_EQ(plan.outputTokens.getAmount("219820e6cb04316f41a337fea356480f412e7acc147d28f175f21b5e_coolcatssociety4567"), nftTokenAmount); + EXPECT_EQ(plan.changeTokens.size(), 0ul); + + const auto txHex = hex(data(output.encoded())); + EXPECT_EQ(txHex, "83a400838258206a7221dcc28353ed69b733391ffeb984a34c1e72293af111d59f9ddfa863916700825820aba499ec2f23529e70bb256ceaffcc6274a882cf02f29e5670c75ee980d7c2b800825820ee414d635b3bc67831907354d274a31174664777c57c21ae923b9459e5644840000182825839010ae9267a9b4f6edb4d69c548f3cba39654d7e67f37ffd5e1352f303e9d63b100507122da18e9cb58995a50f2e80ff65d00be6243cf89a186821a0012593aa1581c219820e6cb04316f41a337fea356480f412e7acc147d28f175f21b5ea153636f6f6c63617473736f6369657479343536370182583901299de4a3d24637ef4050033c214754963b829f43cbc311527d2b4bc8e36798a9ada6e3341ac239057e012225fe5c862f49b52db0f5e208731a002b1525021a0002b19b031a055007d5a1008182582088bd26e8656fa7dead846c3373588f0192da5bfb90bf5d3fb877decfb3b3fd085840da8656aca0dacc57d4c2d957fc7dff03908f6dcf60c48f1e40b3006e2fd0cfacfa4c24fa02e35a310572526586d4ce0d30bf660ba274c8efd507848cbe177d09f6"); +} + +TEST(CardanoSigning, Plan) { + auto input = createSampleInput(7000000); + + { + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.availableAmount, 8000000ul); + EXPECT_EQ(plan.amount, 7000000ul); + EXPECT_EQ(plan.fee, 170196ul); + EXPECT_EQ(plan.change, 829804ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + EXPECT_EQ(plan.error, Common::Proto::OK); + } + { // very small target amount + input.mutable_transfer_message()->set_amount(1); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.utxos.size(), 1ul); + EXPECT_EQ(plan.availableAmount, 6500000ul); + EXPECT_EQ(plan.amount, 1ul); + EXPECT_EQ(plan.fee, 168435ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } + { // small target amount + input.mutable_transfer_message()->set_amount(2000000); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.utxos.size(), 1ul); + EXPECT_EQ(plan.availableAmount, 6500000ul); + EXPECT_EQ(plan.amount, 2000000ul); + EXPECT_EQ(plan.fee, 168611ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } + { // small target amount requested, but max amount + input.mutable_transfer_message()->set_amount(2000000); + input.mutable_transfer_message()->set_use_max_amount(true); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.availableAmount, 8000000ul); + EXPECT_EQ(plan.amount, 7832667ul); + EXPECT_EQ(plan.fee, 167333ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } +} + +TEST(CardanoSigning, ExtraOutputPlan) { + auto input = createSampleInput(2000000, 10, "addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5", true); + // two output + Proto::TxOutput txOutput; + txOutput.set_address("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); + txOutput.set_amount(2000000); + *input.add_extra_outputs() = txOutput; + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.utxos.size(), 1ul); + EXPECT_EQ(plan.availableAmount, 6500000ul); + EXPECT_EQ(plan.amount, 2000000ul); + EXPECT_EQ(plan.fee, 171474ul); + uint64_t extraAmountSum = 0; + for (auto& output: plan.extraOutputs) { + extraAmountSum = extraAmountSum + output.amount; + } + EXPECT_EQ(plan.amount + plan.change + plan.fee + extraAmountSum, plan.availableAmount); + + { + // also test proto fromProto / fromProto + Proto::TxOutput txOutputProto; + txOutputProto.set_address("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); + txOutputProto.set_amount(2000000); + + auto* token = txOutputProto.add_token_amount(); + token->set_policy_id(sundaeTokenPolicy); + token->set_asset_name_hex("43554259"); + const auto tokenAmount = store(uint256_t(3000000)); + token->set_amount(tokenAmount.data(), tokenAmount.size()); + + const auto txOutput1 = TxOutput::fromProto(txOutputProto); + EXPECT_EQ(txOutput1.amount, 2000000ul); + const auto toAddress = AddressV3(txOutput1.address); + EXPECT_EQ(toAddress.string(), "addr1v9jxgu33wyunycmdddnh5a3edq6x2dt3xakkuun6wd6hsar8v9uhvee5w9erw7fnvauhswfhw44k673nv3n8sdmj89n82denweckuv34xvmnw6m9xeerq7rt8ymh5aesxaj8zu3e0y6k67tcd3nkzervxfenqer8ddjn27jkkrj"); + EXPECT_EQ(txOutput1.tokenBundle.getByPolicyId(sundaeTokenPolicy)[0].amount, 3000000); + EXPECT_EQ(txOutput1.tokenBundle.getByPolicyId(sundaeTokenPolicy)[0].assetName, "CUBY"); + EXPECT_EQ(txOutput1.tokenBundle.getByPolicyId(sundaeTokenPolicy)[0].policyId, sundaeTokenPolicy); + } + { + // also test proto toProto / toProto + const auto toAddress = AddressV3("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); + std::vector tokenAmount; + tokenAmount.emplace_back(sundaeTokenPolicy, "CUBY", 3000000); + const Proto::TxOutput txOutputProto = TxOutput(toAddress.data(), 2000000, TokenBundle(tokenAmount)).toProto(); + EXPECT_EQ(txOutputProto.amount(), 2000000ul); + EXPECT_EQ(txOutputProto.address(), "addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); + const auto token = txOutputProto.token_amount(0); + EXPECT_EQ(token.policy_id(), sundaeTokenPolicy); + EXPECT_EQ(token.asset_name(), "CUBY"); + EXPECT_EQ(token.asset_name_hex(), "43554259"); + const auto amount = store(uint256_t(3000000)); + EXPECT_EQ(data(token.amount()), amount); + } + + { + // also test proto toProto / fromProto + const Proto::TransactionPlan planProto = Signer::plan(input); + const auto plan2 = TransactionPlan::fromProto(planProto); + EXPECT_EQ(plan2.amount, 2000000ul); + EXPECT_EQ(plan2.change, 2328526ul); + } +} + +TEST(CardanoSigning, ErrorDoPlan) { + { + // Common::Proto::Error_missing_input_utxos + auto input = createSampleInput(2000000, 0, "addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5", true); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.error, Common::Proto::Error_missing_input_utxos); + } + { + // Common::Proto::Error_low_balance + auto input = createSampleInput(9000000, 1, "addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5", true); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.error, Common::Proto::Error_low_balance); + + } +} + +TEST(CardanoSigning, PlanForceFee) { + auto requestedAmount = 6500000ul; + auto availableAmount = 8000000ul; + auto input = createSampleInput(requestedAmount); + + { + auto fee = 170147ul; + input.mutable_transfer_message()->set_force_fee(fee); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.availableAmount, availableAmount); + EXPECT_EQ(plan.amount, requestedAmount); + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, availableAmount - requestedAmount - fee); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + EXPECT_EQ(plan.error, Common::Proto::OK); + } + { // tiny fee + auto fee = 100ul; + input.mutable_transfer_message()->set_force_fee(fee); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.availableAmount, availableAmount); + EXPECT_EQ(plan.amount, requestedAmount); + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, availableAmount - requestedAmount - fee); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } + { // large fee + auto fee = 1200000ul; + input.mutable_transfer_message()->set_force_fee(fee); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.availableAmount, availableAmount); + EXPECT_EQ(plan.amount, requestedAmount); + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, availableAmount - requestedAmount - fee); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } + { // very large fee, larger than possible, truncated + auto fee = 3000000ul; + input.mutable_transfer_message()->set_force_fee(fee); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.availableAmount, availableAmount); + EXPECT_EQ(plan.amount, requestedAmount); + EXPECT_EQ(plan.fee, 1500000ul); + EXPECT_EQ(plan.change, 0ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } + { // force fee and max amount: fee is used, amount is max, change 0 + auto fee = 160000ul; + input.mutable_transfer_message()->set_force_fee(fee); + input.mutable_transfer_message()->set_use_max_amount(true); + auto signer = Signer(input); + const auto plan = signer.doPlan(); + EXPECT_EQ(plan.availableAmount, availableAmount); + EXPECT_EQ(plan.amount, 7840000ul); + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, 0ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + } +} + +TEST(CardanoSigning, PlanMissingPrivateKey) { + auto input = createSampleInput(7000000, 10, "", true); + + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.availableAmount, 8000000ul); + EXPECT_EQ(plan.amount, 7000000ul); + EXPECT_EQ(plan.fee, 170196ul); + EXPECT_EQ(plan.change, 829804ul); + EXPECT_EQ(plan.amount + plan.change + plan.fee, plan.availableAmount); + EXPECT_EQ(plan.error, Common::Proto::OK); +} + +TEST(CardanoSigning, SignTransfer1) { + const auto input = createSampleInput(7000000); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a006acfc082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a000ca96c021a000298d4031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058407cf591599852b5f5e007fdc241062405c47e519266c0d884b0767c1d4f5eacce00db035998e53ed10ca4ba5ce4aac8693798089717ce6cf4415f345cc764200ef6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "9b5b15e133cd73ccaa85307d2986aebc846505118a2eb4e6111e6b4b67d1f389"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0\", 0], [h\"f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767\", 1]], 1: [[h\"01558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5\", 7000000], [h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 829804]], 2: 170196, 3: 53333333}, {0: [[h\"6d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290\", h\"7cf591599852b5f5e007fdc241062405c47e519266c0d884b0767c1d4f5eacce00db035998e53ed10ca4ba5ce4aac8693798089717ce6cf4415f345cc764200e\"]]}, null]"); + EXPECT_EQ(decode.getArrayElements().size(), 3ul); + } +} + +TEST(CardanoSigning, PlanAndSignTransfer1) { + uint amount = 6000000; + auto input = createSampleInput(amount); + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 8000000ul); + EXPECT_EQ(plan.amount, amount); + EXPECT_EQ(plan.fee, 170196ul); + EXPECT_EQ(plan.change, 8000000 - amount - 170196); + ASSERT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.utxos[0].amount, 6500000ul); + EXPECT_EQ(plan.utxos[1].amount, 1500000ul); + + // perform sign with default plan + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a005b8d8082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a001bebac021a000298d4031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058404abc749ffaffcf2f87970e4f1983c5e44b352ee1515b60017fc65e581d42b3a6ed146d5eb35d04a770460b0541a25afd5aedfd027fdaded82686f43454196a0cf6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "3852f809245d7000ad0c5ccb1357e5d333b0dd25158924581e4c7049ec68c564"); + } + + // set different plan, with one input only + input.mutable_plan()->set_amount(amount); + input.mutable_plan()->set_available_amount(6500000); + input.mutable_plan()->set_fee(165489); + input.mutable_plan()->set_change(17191988); + *(input.mutable_plan()->add_utxos()) = input.utxos(0); + input.mutable_plan()->set_error(Common::Proto::OK); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40081825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a005b8d8082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a01065434021a00028671031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058408311a058035d75545a47b844fea401aa9c23e99fe7bc8136b554396eef135d4cd93062c5df38e613185c21bb1c98b881d1e0fd1024d3539b163c8e14d1a6e40df6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "e319c0bfc99cdb79d64f00b7e8fb8bfbf29fa70554c84f101e92b7dfed172448"); +} + +TEST(CardanoSigning, PlanAndSignMaxAmount) { + auto input = createSampleInput(7000000); + input.mutable_transfer_message()->set_use_max_amount(true); + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 8000000ul); + EXPECT_EQ(plan.amount, 8000000 - 167333ul); + EXPECT_EQ(plan.fee, 167333ul); + EXPECT_EQ(plan.change, 0ul); + ASSERT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.utxos[0].amount, 1500000ul); + EXPECT_EQ(plan.utxos[1].amount, 6500000ul); + } + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000018182583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a0077845b021a00028da5031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058403e64473e08adc863953c0e9f820b658dda0b8a423d6172fdccff73fcd5559956c9df8ed93ff67405331d368a0c11fd18c69781046384946582e1555e9e8ec70bf6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "ca0f1e12f20c95011da7d686d206a1eb98df94accd74c4df4ef403c5ce836057"); +} + +TEST(CardanoSigning, SignNegative) { + { // plan with error + auto input = createSampleInput(7000000); + const auto error = Common::Proto::Error_invalid_memo; + input.mutable_plan()->set_error(error); + auto signer = Signer(input); + const auto output = signer.sign(); + EXPECT_EQ(output.error(), error); + } + { // zero requested amount + auto input = createSampleInput(0); + auto signer = Signer(input); + const auto output = signer.sign(); + EXPECT_EQ(output.error(), Common::Proto::Error_zero_amount_requested); + } + { // no utxo + auto input = createSampleInput(7000000, 0); + auto signer = Signer(input); + const auto output = signer.sign(); + EXPECT_EQ(output.error(), Common::Proto::Error_missing_input_utxos); + } + { // low balance + auto input = createSampleInput(7000000000); + auto signer = Signer(input); + const auto output = signer.sign(); + EXPECT_EQ(output.error(), Common::Proto::Error_low_balance); + } + { // missing private key + auto input = createSampleInput(7000000, 10, "", true); + auto signer = Signer(input); + const auto output = signer.sign(); + EXPECT_EQ(output.error(), Common::Proto::Error_missing_private_key); + } +} + +TEST(CardanoSigning, SignTransfer_0db1ea) { + const auto amount = 1100000ul; + + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("81b935447bb994567f041d181b628a0afbcd747d0199c9ff4cd895686bbee8c6"); + utxo1->mutable_out_point()->set_tx_hash(std::string(txHash1.begin(), txHash1.end())); + utxo1->mutable_out_point()->set_output_index(0); + utxo1->set_address(ownAddress1); + utxo1->set_amount(1000000); + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("3a9068a273cc2af59b45593b78973841d972d01802abe992c55dbeecdffc561b"); + utxo2->mutable_out_point()->set_tx_hash(std::string(txHash2.begin(), txHash2.end())); + utxo2->mutable_out_point()->set_output_index(0); + utxo2->set_address(ownAddress1); + utxo2->set_amount(1800000); + + const auto privateKeyData1 = parse_hex(privateKeyTest1); + input.add_private_key(privateKeyData1.data(), privateKeyData1.size()); + input.mutable_transfer_message()->set_to_address("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + input.mutable_transfer_message()->set_change_address(ownAddress1); + input.mutable_transfer_message()->set_amount(amount); + auto fee = 170147ul; + input.mutable_transfer_message()->set_use_max_amount(false); + input.mutable_transfer_message()->set_force_fee(fee); // use force fee feature here + input.set_ttl(54675589); + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 2800000ul); + EXPECT_EQ(plan.amount, amount); + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, 2800000ul - amount - fee); + EXPECT_EQ(plan.utxos.size(), 2ul); + } + + // set plan with specific fee, to match the real transaction + input.mutable_plan()->set_amount(amount); + input.mutable_plan()->set_available_amount(2800000); + input.mutable_plan()->set_fee(fee); + input.mutable_plan()->set_change(2800000 - amount - fee); + *(input.mutable_plan()->add_utxos()) = input.utxos(0); + *(input.mutable_plan()->add_utxos()) = input.utxos(1); + input.mutable_plan()->set_error(Common::Proto::OK); + + auto signer = Signer(input); + const auto output = signer.sign(); + + // https://cardanoscan.io/transaction/0db1ea8c5c5828bbd027fcef3da02a63b86899db670ad7bb0630cefbe35944fa + // curl -d '{"txHash":"0db1ea..44fa","txBody":"83a400..06f6"}' -H "Content-Type: application/json" https:///api/txs/submit + EXPECT_EQ(output.error(), Common::Proto::OK); + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a4008282582081b935447bb994567f041d181b628a0afbcd747d0199c9ff4cd895686bbee8c6008258203a9068a273cc2af59b45593b78973841d972d01802abe992c55dbeecdffc561b000182825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a34681a0010c8e082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a001757fd021a000298a3031a03424885a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058406300b52aaff1e26067a3e0a48ae26f4f068765f46f934fabeab872c1d25535fc94893ec72feacd787f0174fbabd8933727d9a2b319b406e7a855843b0c051806f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "0db1ea8c5c5828bbd027fcef3da02a63b86899db670ad7bb0630cefbe35944fa"); +} + +/// Successfully broadcasted: +/// https://cardanoscan.io/transaction/0203ce2c91f59f169a26e9ef91254639d2b7911afac9c7c0ae64539f88ba46a5 +TEST(CardanoSigning, SignTransferFromLegacy) { + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("8316e5007d61fb90652cabb41141972a38b5bc60954d602cf843476aa3f67f63"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(0); + utxo1->set_address("Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8"); + utxo1->set_amount(2500000); + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("e29392c59c903fefb905730587d22cae8bda30bd8d9aeec3eca082ae77675946"); + utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); + utxo2->mutable_out_point()->set_output_index(0); + utxo2->set_address("Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8"); + utxo2->set_amount(1700000); + + const auto privateKeyData = parse_hex("98f266d1aac660179bc2f456033941238ee6b2beb8ed0f9f34c9902816781f5a9903d1d395d6ab887b65ea5e344ef09b449507c21a75f0ce8c59d0ed1c6764eba7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f4e890ca4eb6bec44bf751b5a843174534af64d6ad1f44e0613db78a7018781f5aa151d2997f52059466b715d8eefab30a78b874ae6ef4931fa58bb21ef8ce2423d46f19d0fbf75afb0b9a24e31d533f4fd74cee3b56e162568e8defe37123afc4"); + { + const auto privKey = PrivateKey(privateKeyData); + const auto pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto addr = AddressV2(pubKey); + EXPECT_EQ(addr.string(), "Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8"); + } + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + input.mutable_transfer_message()->set_to_address("addr1q90uh2eawrdc9vaemftgd50l28yrh9lqxtjjh4z6dnn0u7ggasexxdyyk9f05atygnjlccsjsggtc87hhqjna32fpv5qeq96ls"); + input.mutable_transfer_message()->set_change_address("addr1qx55ymlqemndq8gluv40v58pu76a2tp4mzjnyx8n6zrp2vtzrs43a0057y0edkn8lh9su8vh5lnhs4npv6l9tuvncv8swc7t08"); + input.mutable_transfer_message()->set_amount(3000000); + input.mutable_transfer_message()->set_use_max_amount(false); + input.set_ttl(190000000); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(hex(output.encoded()), "83a400828258208316e5007d61fb90652cabb41141972a38b5bc60954d602cf843476aa3f67f6300825820e29392c59c903fefb905730587d22cae8bda30bd8d9aeec3eca082ae77675946000182825839015fcbab3d70db82b3b9da5686d1ff51c83b97e032e52bd45a6ce6fe7908ec32633484b152fa756444e5fc62128210bc1fd7b8253ec5490b281a002dc6c082583901a9426fe0cee6d01d1fe32af650e1e7b5d52c35d8a53218f3d0861531621c2b1ebdf4f11f96da67fdcb0e1d97a7e778566166be55f193c30f1a000f9ec1021a0002b0bf031a0b532b80a20081825820d163c8c4f0be7c22cd3a1152abb013c855ea614b92201497a568c5d93ceeb41e58406a23ab9267867fbf021c1cb2232bc83d2cdd663d651d22d59b6cddbca5cb106d4db99da50672f69a2309ca8a329a3f9576438afe4538b013de4591a6dfcd4d090281845820d163c8c4f0be7c22cd3a1152abb013c855ea614b92201497a568c5d93ceeb41e58406a23ab9267867fbf021c1cb2232bc83d2cdd663d651d22d59b6cddbca5cb106d4db99da50672f69a2309ca8a329a3f9576438afe4538b013de4591a6dfcd4d095820a7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f441a0f6"); + EXPECT_EQ(hex(data(output.tx_id())), "0203ce2c91f59f169a26e9ef91254639d2b7911afac9c7c0ae64539f88ba46a5"); +} + +TEST(CardanoSigning, SignTransferToLegacy) { + const auto toAddressLegacy = "DdzFFzCqrhssmYoG5Eca1bKZFdGS8d6iag1mU4wbLeYcSPVvBNF2wRG8yhjzQqErbg63N6KJA4DHqha113tjKDpGEwS5x1dT2KfLSbSJ"; + EXPECT_FALSE(AddressV3::isValid(toAddressLegacy)); // not V3 + EXPECT_TRUE(AddressV3::isValidLegacy(toAddressLegacy)); + + const auto input = createSampleInput(7000000, 10, toAddressLegacy); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(hex(output.encoded()), "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282584c82d818584283581c6aebd89cf88271c3ee76339930d8956b03f018b2f4871522f88eb8f9a101581e581c692a37dae3bc63dfc3e1463f12011f26655ab1d1e0f4ed4b8fc63708001ad8a9555b1a006acfc082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a000ca627021a00029c19031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840db9becdc733f4c08c0e7abc29b5cc6469f9339d32f565df8bf77455439ae1f949facc9b831754e74d3fbb42e99647eedd6c28de1461d18c315485f5d24b5b90af6"); + EXPECT_EQ(hex(data(output.tx_id())), "f9b713e9987ec1377ac223f50d63c7a5e155915302de43f40d7b2627accabf69"); +} + +TEST(CardanoSigning, SignTransferToInvalid) { + const auto input = createSampleInput(7000000, 10, "__INVALID_ADDRESS__"); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_address); + EXPECT_EQ(hex(output.encoded()), ""); +} + +TEST(CardanoSigning, SignTransferToken) { + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(1); + utxo1->set_address(ownAddress1); + utxo1->set_amount(8051373); + // some token, to be preserved + auto* token3 = utxo1->add_token_amount(); + token3->set_policy_id(sundaeTokenPolicy); + token3->set_asset_name("CUBY"); + const auto tokenAmount3 = store(uint256_t(3000000)); + token3->set_amount(tokenAmount3.data(), tokenAmount3.size()); + + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"); + utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); + utxo2->mutable_out_point()->set_output_index(2); + utxo2->set_address(ownAddress1); + utxo2->set_amount(2000000); + // some SUNDAE token, to be transferred + auto* token1 = utxo2->add_token_amount(); + token1->set_policy_id(sundaeTokenPolicy); + token1->set_asset_name_hex("53554e444145"); + const auto tokenAmount1 = store(uint256_t(80996569)); + token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); + // some other token, to be preserved + auto* token2 = utxo2->add_token_amount(); + token2->set_policy_id(sundaeTokenPolicy); + token2->set_asset_name("CUBY"); + // This should be ignored! + token2->set_asset_name_hex("00"); + const auto tokenAmount2 = store(uint256_t(2000000)); + token2->set_amount(tokenAmount2.data(), tokenAmount2.size()); + + const auto privateKeyData = parse_hex(privateKeyTest1); + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + input.mutable_transfer_message()->set_to_address("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); + input.mutable_transfer_message()->set_change_address("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + input.mutable_transfer_message()->set_amount(1500000); + auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + toToken->set_policy_id(sundaeTokenPolicy); + toToken->set_asset_name_hex("53554e444145"); + const auto toTokenAmount = store(uint256_t(20000000)); + toToken->set_amount(toTokenAmount.data(), toTokenAmount.size()); + input.mutable_transfer_message()->set_use_max_amount(false); + input.set_ttl(53333333); + + { // check min ADA amount, set it + const auto bundleProtoData = data(input.transfer_message().token_amount().SerializeAsString()); + const auto minAdaAmount = TWCardanoMinAdaAmount(&bundleProtoData); + EXPECT_EQ(minAdaAmount, 1444443ul); + input.mutable_transfer_message()->set_amount(minAdaAmount); + } + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 10051373ul); + EXPECT_EQ(plan.amount, 1444443ul); + EXPECT_EQ(plan.fee, 174601ul); + EXPECT_EQ(plan.change, 8432329ul); + EXPECT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.availableTokens.size(), 2ul); + EXPECT_EQ(plan.availableTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_CUBY"), 5000000); + EXPECT_EQ(plan.availableTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 80996569); + EXPECT_EQ(plan.outputTokens.size(), 1ul); + EXPECT_EQ(plan.outputTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_CUBY"), 0); + EXPECT_EQ(plan.outputTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 20000000); + EXPECT_EQ(plan.changeTokens.size(), 2ul); + EXPECT_EQ(plan.changeTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_CUBY"), 5000000); + EXPECT_EQ(plan.changeTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 60996569); + } + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76702018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5821a00160a5ba1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a01312d00825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468821a0080aac9a1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a244435542591a004c4b404653554e4441451a03a2bbd9021a0002aa09031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840d90dcfbd190cbe59c42094e59eeb49b3de9d80a85b786cc311f932c5c9302d1c8c6c577b22aa70ff7955c139c700ea918f8cb425c3ba43a27980e1d238e4e908f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "201c537693b005b64a0f0528e366ec67a84be0119ed4363b547f141f2a7770c2"); + + { + // also test proto toProto / fromProto + const Proto::TransactionPlan planProto = Signer::plan(input); + const auto plan2 = TransactionPlan::fromProto(planProto); + EXPECT_EQ(plan2.amount, 1444443ul); + EXPECT_EQ(plan2.change, 8432329ul); + } +} + +TEST(CardanoSigning, SignTransferToken_1dd248) { + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("f2d2b11c8c07c5c646f5b5af20fddf2f0a174743c6a1b13cca27e28a6ca34710"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(0); + utxo1->set_address(ownAddress1); + utxo1->set_amount(1500000); + // some token + auto* token3 = utxo1->add_token_amount(); + token3->set_policy_id(sundaeTokenPolicy); + token3->set_asset_name_hex("53554e444145"); + const auto tokenAmount3 = store(uint256_t(20000000)); + token3->set_amount(tokenAmount3.data(), tokenAmount3.size()); + + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("6975fcf7bbca745c85f50777f956219868fd9cad14ba496fed1371252e8df60f"); + utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); + utxo2->mutable_out_point()->set_output_index(0); + utxo2->set_address(ownAddress1); + utxo2->set_amount(10258890); + + const auto privateKeyData = parse_hex(privateKeyTest1); + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + input.mutable_transfer_message()->set_to_address("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); // Test + input.mutable_transfer_message()->set_change_address(ownAddress1); + input.mutable_transfer_message()->set_amount(1600000); + auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + toToken->set_policy_id(sundaeTokenPolicy); + toToken->set_asset_name_hex("53554e444145"); + const auto toTokenAmount = store(uint256_t(11000000)); + toToken->set_amount(toTokenAmount.data(), toTokenAmount.size()); + input.mutable_transfer_message()->set_use_max_amount(false); + input.set_ttl(61232158); + + { // check min ADA amount + const auto bundleProtoData = data(input.transfer_message().token_amount().SerializeAsString()); + EXPECT_EQ(TWCardanoMinAdaAmount(&bundleProtoData), 1444443ul); + EXPECT_GT(input.transfer_message().amount(), TWCardanoMinAdaAmount(&bundleProtoData)); + } + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 11758890ul); + EXPECT_EQ(plan.amount, 11758890 - 9984729 - 174161ul); + EXPECT_EQ(plan.fee, 174161ul); + EXPECT_EQ(plan.change, 9984729ul); + EXPECT_EQ(plan.utxos.size(), 2ul); + EXPECT_EQ(plan.availableTokens.size(), 1ul); + EXPECT_EQ(plan.availableTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 20000000); + EXPECT_EQ(plan.outputTokens.size(), 1ul); + EXPECT_EQ(plan.outputTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 11000000); + EXPECT_EQ(plan.changeTokens.size(), 1ul); + EXPECT_EQ(plan.changeTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 9000000); + } + + // set plan with specific fee, to match the real transaction + input.mutable_plan()->set_available_amount(11758890); + input.mutable_plan()->set_amount(1600000); + input.mutable_plan()->set_fee(174102); + input.mutable_plan()->set_change(9984788); + *(input.mutable_plan()->add_available_tokens()) = input.utxos(0).token_amount(0); + *(input.mutable_plan()->add_output_tokens()) = input.utxos(0).token_amount(0); + input.mutable_plan()->mutable_output_tokens(0)->set_amount(toTokenAmount.data(), toTokenAmount.size()); + *(input.mutable_plan()->add_change_tokens()) = input.utxos(0).token_amount(0); + const auto changeTokenAmount = store(uint256_t(9000000)); + input.mutable_plan()->mutable_change_tokens(0)->set_amount(changeTokenAmount.data(), changeTokenAmount.size()); + *(input.mutable_plan()->add_utxos()) = input.utxos(1); + *(input.mutable_plan()->add_utxos()) = input.utxos(0); + input.mutable_plan()->set_error(Common::Proto::OK); + + auto signer = Signer(input); + const auto output = signer.sign(); + + // https://cardanoscan.io/transaction/1dd24872d93d3b5091b98e19b9f920cd0c4369e4c5ca178e898152c52f00c162 + // curl -d '{"txHash":"1dd248..c162","txBody":"83a400..08f6"}' -H "Content-Type: application/json" https:///api/txs/submit + EXPECT_EQ(output.error(), Common::Proto::OK); + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a400828258206975fcf7bbca745c85f50777f956219868fd9cad14ba496fed1371252e8df60f00825820f2d2b11c8c07c5c646f5b5af20fddf2f0a174743c6a1b13cca27e28a6ca34710000182825839018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cdd9fdb03e10b4e4ac08f5da1fdec6222a3468821a00186a00a1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a00a7d8c082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b821a00985b14a1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a00895440021a0002a816031a03a6541ea100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840c8cdee32bfd584f55cf334b4ec6f734635144736d48f882e647a7a6283f230bc5a67d4dd66a9e523e0c29c812ed1e3589febbcf96547a1fc6d061a7ccfb81308f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "1dd24872d93d3b5091b98e19b9f920cd0c4369e4c5ca178e898152c52f00c162"); +} + +TEST(CardanoSigning, SignTransferTokenMaxAmount_620b71) { + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("46964521ad00d9b3f3d41f77c07e1b3093848048dbdf2d95cf900e15cdac0d7f"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(0); + utxo1->set_address(ownAddress1); + utxo1->set_amount(2170871); + // some token + auto* token1 = utxo1->add_token_amount(); + token1->set_policy_id(sundaeTokenPolicy); + token1->set_asset_name_hex("53554e444145"); + const auto tokenAmount1 = store(uint256_t(20000000)); + token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); + + const auto privateKeyData = parse_hex(privateKeyTest1); + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + input.mutable_transfer_message()->set_to_address("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5"); + input.mutable_transfer_message()->set_change_address(ownAddress1); + input.mutable_transfer_message()->set_amount(666); // doesn't matter, max is used + auto* toToken = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + toToken->set_policy_id(sundaeTokenPolicy); + toToken->set_asset_name_hex("53554e444145"); + const auto toTokenAmount = store(uint256_t(666)); // doesn't matter, max is used + input.mutable_transfer_message()->set_use_max_amount(true); + input.set_ttl(61085916); + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + EXPECT_EQ(plan.availableAmount, 2170871ul); + EXPECT_EQ(plan.amount, 2170871 - 167730ul); + EXPECT_EQ(plan.fee, 167730ul); + EXPECT_EQ(plan.change, 0ul); + EXPECT_EQ(plan.utxos.size(), 1ul); + EXPECT_EQ(plan.availableTokens.size(), 1ul); + EXPECT_EQ(plan.availableTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 20000000); + EXPECT_EQ(plan.outputTokens.size(), 1ul); + EXPECT_EQ(plan.outputTokens.getAmount("9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77_SUNDAE"), 20000000); + EXPECT_EQ(plan.changeTokens.size(), 0ul); + } + + // set plan with specific fee, to match the real transaction + input.mutable_plan()->set_available_amount(2170871); + input.mutable_plan()->set_amount(1998526); + input.mutable_plan()->set_fee(172345); + input.mutable_plan()->set_change(0); + *(input.mutable_plan()->add_available_tokens()) = input.utxos(0).token_amount(0); + *(input.mutable_plan()->add_output_tokens()) = input.utxos(0).token_amount(0); + *(input.mutable_plan()->add_utxos()) = input.utxos(0); + input.mutable_plan()->set_error(Common::Proto::OK); + + auto signer = Signer(input); + const auto output = signer.sign(); + + // https://cardanoscan.io/transaction/620b719338efb419b0e1417bfbe01fc94a62d5669a4b8cbbf4e32ecc1ca3b872 + // curl -d '{"txHash":"620b71..b872","txBody":"83a400..08f6"}' -H "Content-Type: application/json" https:///api/txs/submit + EXPECT_EQ(output.error(), Common::Proto::OK); + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a4008182582046964521ad00d9b3f3d41f77c07e1b3093848048dbdf2d95cf900e15cdac0d7f00018182583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5821a001e7ebea1581c9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77a14653554e4441451a01312d00021a0002a139031a03a418dca100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840e1d1565cd747b20b0f10a92f068f3d5faebdee92b4b4a4b96ce14736d975e17d1446f7f51e64997a0bb38e0151dc738468161d574d6cfcd8040e4455ff46bc08f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "620b719338efb419b0e1417bfbe01fc94a62d5669a4b8cbbf4e32ecc1ca3b872"); +} + +TEST(CardanoSigning, SignTransferTwoTokens) { + auto input = createSampleInput(7000000); + input.mutable_transfer_message()->set_amount(1500000); + auto* token1 = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + token1->set_policy_id(sundaeTokenPolicy); + token1->set_asset_name_hex("53554e444145"); + const auto tokenAmount1 = store(uint256_t(40000000)); + token1->set_amount(tokenAmount1.data(), tokenAmount1.size()); + auto* token2 = input.mutable_transfer_message()->mutable_token_amount()->add_token(); + token2->set_policy_id(sundaeTokenPolicy); + token2->set_asset_name_hex("43554259"); + const auto tokenAmount2 = store(uint256_t(2000000)); + token2->set_amount(tokenAmount2.data(), tokenAmount2.size()); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_requested_token_amount); + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(output.encoded()), ""); +} + +TEST(CardanoSigning, SignMessageWithKey) { + // test case from cardano-crypto.js + + const auto privateKey = PrivateKey(parse_hex( + "d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48" + "d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c" + "69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" + "1111111111111111111111111111111111111111111111111111111111111111" + "1111111111111111111111111111111111111111111111111111111111111111" + "1111111111111111111111111111111111111111111111111111111111111111")); + + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + EXPECT_EQ(hex(publicKey.bytes), + "e6f04522f875c1563682ca876ddb04c2e2e3ae718e3ff9f11c03dd9f9dccf698" + "69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" + "857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3" + "1111111111111111111111111111111111111111111111111111111111111111"); + + const auto sampleMessageStr = "Hello world"; + const auto sampleMessage = data(sampleMessageStr); + + const auto signature = privateKey.sign(sampleMessage, TWCurveED25519ExtendedCardano); + + const auto sampleRightSignature = "1096ddcfb2ad21a4c0d861ef3fabe18841e8de88105b0d8e36430d7992c588634ead4100c32b2800b31b65e014d54a8238bdda63118d829bf0bcf1b631e86f0e"; + EXPECT_EQ(hex(signature), sampleRightSignature); +} + +TEST(CardanoSigning, AnySignTransfer1) { + const auto input = createSampleInput(7000000); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeCardano); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a40082825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701018282583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a006acfc082583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a000ca96c021a000298d4031a032dcd55a100818258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058407cf591599852b5f5e007fdc241062405c47e519266c0d884b0767c1d4f5eacce00db035998e53ed10ca4ba5ce4aac8693798089717ce6cf4415f345cc764200ef6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "9b5b15e133cd73ccaa85307d2986aebc846505118a2eb4e6111e6b4b67d1f389"); +} + +TEST(CardanoSigning, AnyPlan1) { + const auto input = createSampleInput(7000000); + + Proto::TransactionPlan plan; + ANY_PLAN(input, plan, TWCoinTypeCardano); + + EXPECT_EQ(plan.error(), Common::Proto::OK); + EXPECT_EQ(plan.amount(), 7000000ul); + EXPECT_EQ(plan.available_amount(), 8000000ul); + EXPECT_EQ(plan.fee(), 170196ul); + EXPECT_EQ(plan.change(), 829804ul); + ASSERT_EQ(plan.utxos_size(), 2); + EXPECT_EQ(plan.utxos(0).amount(), 6500000ul); + EXPECT_EQ(plan.utxos(1).amount(), 1500000ul); + + EXPECT_EQ(hex(plan.SerializeAsString()), "0880a4e80310c09fab0318d4b10a20ecd2324292010a220a20554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af01267616464723171383034336d356865656179646e76746d6d6b7975686536717635686176766873663064323671336a7967737370786c796670796b3679716b77307968747976747230666c656b6a3834753634617a38326375666d716e36357a6473796c7a6b323318a0dd8c034293010a240a20f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76710011267616464723171383034336d356865656179646e76746d6d6b7975686536717635686176766873663064323671336a7967737370786c796670796b3679716b77307968747976747230666c656b6a3834753634617a38326375666d716e36357a6473796c7a6b323318e0c65b"); + + { + // also test fromProto + const auto plan2 = TransactionPlan::fromProto(plan); + EXPECT_EQ(plan2.amount, plan.amount()); + EXPECT_EQ(plan2.change, plan.change()); + } +} + +} // namespace TW::Cardano::tests diff --git a/tests/chains/Cardano/StakingTests.cpp b/tests/chains/Cardano/StakingTests.cpp new file mode 100644 index 00000000000..45e9e7d13f9 --- /dev/null +++ b/tests/chains/Cardano/StakingTests.cpp @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Cardano/AddressV3.h" +#include "Cardano/Signer.h" +#include "proto/Cardano.pb.h" +#include + +#include "Cbor.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "uint256.h" +#include "TestUtilities.h" +#include + +#include +#include + +using namespace TW; +using namespace std; + +namespace TW::Cardano::StakingTests { + +const auto privateKeyTest1 = "089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e"; +const auto ownAddress1 = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23"; +const auto stakingAddress1 = "stake1u80jysjtdzqt88jt4jx93h5lumfr67d273r4vwyasfa2pxcwxllmx"; +const auto poolIdNufi = "7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6"; + +TEST(CardanoStaking, RegisterStakingKey) { + const auto privateKeyData = parse_hex(privateKeyTest1); + const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto ownAddress = AddressV3(publicKey).string(); + EXPECT_EQ(ownAddress, ownAddress1); + const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); + EXPECT_EQ(stakingAddress, stakingAddress1); + const auto poolId = parse_hex(poolIdNufi); + + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(1); + utxo1->set_address(ownAddress); + utxo1->set_amount(10000000ul); + + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + + input.mutable_transfer_message()->set_to_address(ownAddress); + input.mutable_transfer_message()->set_change_address(ownAddress); + input.mutable_transfer_message()->set_amount(5000000ul); // not relevant if we use MaxAmount + input.mutable_transfer_message()->set_use_max_amount(true); + input.set_ttl(69986091ul); + + // Register staking key, 2 ADA deposit + input.mutable_register_staking_key()->set_staking_address(stakingAddress); + input.mutable_register_staking_key()->set_deposit_amount(2000000ul); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a50081825820cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca01018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a007772fa021a00029f06031a042be72b048182008200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09ba100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840d08ed71da87d0928090edd9e226496ab109f2eee7926ac2ce51e7abe89a4f513c4afe2b85b71595e862e7f6fc992d14d2416a6e53a1961da7d26d3cf3f823400825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e06932584079ed55400cebc70c56ca87ba09009dfc298c64768f90a9139bf2e7f134250927c614ee846253fac33e652f1b50373d349fdfe13c207968c2a10991824fe2a10ef6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "6a206fe4df76e12499b4fd9722f33429f4d93f8a996f9f523fa6c02a8301386b"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca\", 1]], 1: [[h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 7828218]], 2: 171782, 3: 69986091, 4: [[0, [0, h\"df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\"]]]}, {0: [[h\"6d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290\", h\"d08ed71da87d0928090edd9e226496ab109f2eee7926ac2ce51e7abe89a4f513c4afe2b85b71595e862e7f6fc992d14d2416a6e53a1961da7d26d3cf3f823400\"], [h\"e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e06932\", h\"79ed55400cebc70c56ca87ba09009dfc298c64768f90a9139bf2e7f134250927c614ee846253fac33e652f1b50373d349fdfe13c207968c2a10991824fe2a10e\"]]}, null]"); + EXPECT_EQ(decode.getArrayElements().size(), 3ul); + } +} + +TEST(CardanoStaking, DeregisterStakingKey) { + const auto privateKeyData = parse_hex(privateKeyTest1); + const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto ownAddress = AddressV3(publicKey).string(); + EXPECT_EQ(ownAddress, ownAddress1); + const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); + EXPECT_EQ(stakingAddress, stakingAddress1); + const auto poolId = parse_hex(poolIdNufi); + + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(1); + utxo1->set_address(ownAddress); + utxo1->set_amount(10000000ul); + + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + + input.mutable_transfer_message()->set_to_address(ownAddress); + input.mutable_transfer_message()->set_change_address(ownAddress); + input.mutable_transfer_message()->set_amount(5000000ul); // not relevant if we use MaxAmount + input.mutable_transfer_message()->set_use_max_amount(true); + input.set_ttl(69986091ul); + + // Deregister staking key, get back 2 ADA deposit + input.mutable_deregister_staking_key()->set_staking_address(stakingAddress); + input.mutable_deregister_staking_key()->set_undeposit_amount(2000000ul); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a50081825820cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca01018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a00b47bfa021a00029f06031a042be72b048182018200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09ba100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290584056619a7d6192b6f68c31a43e927c893161fd994d5c1bcc16f3710cf5e5e652e01f118d55f0110e9de34edc050d509748bea637db5c34f4fe342ae262ccb5520d825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e069325840d23680fdd8aa63e10efccc550eb726743b653008952f9d731d076d1df8106b0401823ebb195127b211389f1bc2c3f6ededbcec04bc8f0de93607a2409421e006f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "1caae2456e5471cc77e73410da475fb0a23874c18c1ea55f9267c59767caef0a"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca\", 1]], 1: [[h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 11828218]], 2: 171782, 3: 69986091, 4: [[1, [0, h\"df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\"]]]}, {0: [[h\"6d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290\", h\"56619a7d6192b6f68c31a43e927c893161fd994d5c1bcc16f3710cf5e5e652e01f118d55f0110e9de34edc050d509748bea637db5c34f4fe342ae262ccb5520d\"], [h\"e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e06932\", h\"d23680fdd8aa63e10efccc550eb726743b653008952f9d731d076d1df8106b0401823ebb195127b211389f1bc2c3f6ededbcec04bc8f0de93607a2409421e006\"]]}, null]"); + EXPECT_EQ(decode.getArrayElements().size(), 3ul); + } +} + +TEST(CardanoStaking, Redelegate) { + const auto privateKeyData = parse_hex(privateKeyTest1); + const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto ownAddress = AddressV3(publicKey).string(); + EXPECT_EQ(ownAddress, ownAddress1); + const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); + EXPECT_EQ(stakingAddress, stakingAddress1); + const auto poolId = parse_hex(poolIdNufi); + + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(1); + utxo1->set_address(ownAddress); + utxo1->set_amount(10000000ul); + + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + + input.mutable_transfer_message()->set_to_address(ownAddress); + input.mutable_transfer_message()->set_change_address(ownAddress); + input.mutable_transfer_message()->set_amount(5000000ul); // not relevant if we use MaxAmount + input.mutable_transfer_message()->set_use_max_amount(true); + input.set_ttl(69986091ul); + + // Delegate, no deposit + input.mutable_delegate()->set_staking_address(stakingAddress); + input.mutable_delegate()->set_pool_id(poolId.data(), poolId.size()); + input.mutable_delegate()->set_deposit_amount(0ul); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a50081825820cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca01018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a0095f251021a0002a42f031a042be72b048183028200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b581c7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6a100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840fb48f3ddbfc2d4ca231a0581c5b456019aa4215ed5a2447ba89a4860569f9e7296afd3a0a81506882d8bda33683e623e6d8033786275369f7e247d866e017c06825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e069325840e26f696a6cd1c34101623568c9efe3796ff5855ada0e2e0cf557c7fc2148f6b2af176aff40a1f9c13fb29d9636c49f774d4a967c71f052f865cfaf0d02d5bb05f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "985f613fb8b86dad35f075599099776e50fc2a6aa74ee4b37c14fd9f2c0f0891"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"cba84549f07f2128410c0a22731f2c57f2a617746e8edc61b295cd8792638dca\", 1]], 1: [[h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 9826897]], 2: 173103, 3: 69986091, 4: [[2, [0, h\"df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\"], h\"7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6\"]]}, {0: [[h\"6d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290\", h\"fb48f3ddbfc2d4ca231a0581c5b456019aa4215ed5a2447ba89a4860569f9e7296afd3a0a81506882d8bda33683e623e6d8033786275369f7e247d866e017c06\"], [h\"e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e06932\", h\"e26f696a6cd1c34101623568c9efe3796ff5855ada0e2e0cf557c7fc2148f6b2af176aff40a1f9c13fb29d9636c49f774d4a967c71f052f865cfaf0d02d5bb05\"]]}, null]"); + EXPECT_EQ(decode.getArrayElements().size(), 3ul); + } +} + +TEST(CardanoStaking, RegisterAndDelegate_similar53339b) { + const auto privateKeyData = parse_hex(privateKeyTest1); + const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto ownAddress = AddressV3(publicKey).string(); + EXPECT_EQ(ownAddress, ownAddress1); + const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); + EXPECT_EQ(stakingAddress, stakingAddress1); + const auto poolId = parse_hex(poolIdNufi); + + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(0); + utxo1->set_address(ownAddress); + utxo1->set_amount(4000000ul); + auto* utxo2 = input.add_utxos(); + const auto txHash2 = parse_hex("9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e"); + utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size()); + utxo2->mutable_out_point()->set_output_index(1); + utxo2->set_address(ownAddress); + utxo2->set_amount(26651312ul); + + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + + input.mutable_transfer_message()->set_to_address(ownAddress); + input.mutable_transfer_message()->set_change_address(ownAddress); + input.mutable_transfer_message()->set_amount(4000000ul); // not relevant if we use MaxAmount + input.mutable_transfer_message()->set_use_max_amount(true); + input.set_ttl(69885081ul); + + // Register staking key, 2 ADA desposit + input.mutable_register_staking_key()->set_staking_address(stakingAddress); + input.mutable_register_staking_key()->set_deposit_amount(2000000ul); + + // Delegate + input.mutable_delegate()->set_staking_address(stakingAddress); + input.mutable_delegate()->set_pool_id(poolId.data(), poolId.size()); + input.mutable_delegate()->set_deposit_amount(0ul); + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + const auto amount = 28475125ul; + const auto availAmount = 30651312ul; + EXPECT_EQ(plan.availableAmount, availAmount); + EXPECT_EQ(plan.amount, amount); + const auto fee = 176187ul; + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, availAmount - 2000000ul - amount - fee); + EXPECT_EQ(plan.change, 0ul); + + // perform sign with default plan + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a500828258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e008258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e01018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a01b27ef5021a0002b03b031a042a5c99048282008200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b83028200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b581c7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6a100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840677c901704be027d9a1734e8aa06f0700009476fa252baaae0de280331746a320a61456d842d948ea5c0e204fc36f3bd04c88ca7ee3d657d5a38014243c37c07825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e0693258401fa21bdc62b85ca217bf08cbacdeba2fadaf33dc09ee3af9cc25b40f24822a1a42cfbc03585cc31a370ef75aaec4d25db6edcf329e40a4e725ec8718c94f220af6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "96a781fd6481b6a7fd3926da110265e8c44b53947b81daa84da5b148825d02aa"); + } + + // set different plan, with exact fee + const auto amount = 28467322ul; + input.mutable_plan()->set_amount(amount); + input.mutable_plan()->set_available_amount(28651312ul); + input.mutable_plan()->set_fee(183990); + input.mutable_plan()->set_change(0); + *(input.mutable_plan()->add_utxos()) = input.utxos(0); + *(input.mutable_plan()->add_utxos()) = input.utxos(1); + input.mutable_plan()->set_error(Common::Proto::OK); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + // Similar to (but with different key): https://cardanoscan.io/transaction/53339b758009a0896a87e9569cadcdb5a095ffe0c100cc7483d72e817e81b60b + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a500828258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e008258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e01018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a01b2607a021a0002ceb6031a042a5c99048282008200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b83028200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b581c7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6a100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058405d8b21c993aec7a7bdf0c832e5688920b64b665e1e36a2e6040d6dd8ad195e7774df3c1377047737d8b676fa4115e38fbf6ef854904db6d9c8ee3e26e8561408825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e06932584088a3f6387693f9077d11a6e245e024b791074bcaa26c034e687d67f3324b6f90a437d33d0343e11c7dee1a28270c223e02080e452fe97cdc93d26c720ab6b805f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "23e1d1bc27f6de57e323d232d44c909fb41ee2ebfff28b82ca8cae6947866ea7"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e\", 0], [h\"9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e\", 1]], 1: [[h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 28467322]], 2: 183990, 3: 69885081, 4: [[0, [0, h\"df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\"]], [2, [0, h\"df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\"], h\"7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6\"]]}, {0: [[h\"6d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290\", h\"5d8b21c993aec7a7bdf0c832e5688920b64b665e1e36a2e6040d6dd8ad195e7774df3c1377047737d8b676fa4115e38fbf6ef854904db6d9c8ee3e26e8561408\"], [h\"e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e06932\", h\"88a3f6387693f9077d11a6e245e024b791074bcaa26c034e687d67f3324b6f90a437d33d0343e11c7dee1a28270c223e02080e452fe97cdc93d26c720ab6b805\"]]}, null]"); + EXPECT_EQ(decode.getArrayElements().size(), 3ul); + } +} + +TEST(CardanoStaking, Withdraw_similarf48098) { + const auto privateKeyData = parse_hex(privateKeyTest1); + const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto ownAddress = AddressV3(publicKey).string(); + EXPECT_EQ(ownAddress, ownAddress1); + const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); + EXPECT_EQ(stakingAddress, stakingAddress1); + + Proto::SigningInput input; + auto* utxo1 = input.add_utxos(); + const auto txHash1 = parse_hex("7dfd2c579794314b1f84efc9db932a098e440ccefb874945591f1d4e85a9152a"); + utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_output_index(0); + utxo1->set_address(ownAddress); + utxo1->set_amount(6305913ul); + + input.add_private_key(privateKeyData.data(), privateKeyData.size()); + + input.mutable_transfer_message()->set_to_address(ownAddress); + input.mutable_transfer_message()->set_change_address(ownAddress); + input.mutable_transfer_message()->set_amount(6000000ul); // not relevant if we use MaxAmount + input.mutable_transfer_message()->set_use_max_amount(true); + input.set_ttl(71678326ul); + + // Withdraw available amount + const auto withdrawAmount = 3468ul; + input.mutable_withdraw()->set_staking_address(stakingAddress); + input.mutable_withdraw()->set_withdraw_amount(withdrawAmount); + + { + // run plan and check result + auto signer = Signer(input); + const auto plan = signer.doPlan(); + + const auto amount = 6137599ul; + const auto availAmount = 6305913ul; + EXPECT_EQ(plan.availableAmount, availAmount); + EXPECT_EQ(plan.amount, amount); + const auto fee = 171782ul; + EXPECT_EQ(plan.fee, fee); + EXPECT_EQ(plan.change, availAmount + withdrawAmount - amount - fee); + EXPECT_EQ(plan.change, 0ul); + + // perform sign with default plan + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a500818258207dfd2c579794314b1f84efc9db932a098e440ccefb874945591f1d4e85a9152a00018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a005da6ff021a00029f06031a0445b97605a1581de1df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b190d8ca100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058401ebaca2876fd17122404912a2558a98109cdf0f990a938d2917fa2c3b8c4e55e18a2cbabfa82fff03fa0d7ab8b88ca01ed18e42af3bfc4cda7f423a3aa30c00b825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e069325840777f04fa8f083fe562aecf78898aaaaac36e2cc6ca962f6ffb01e84a421cae1860496db79b2c5fb2879524c3d5121060b9ea1e693336230c6e5338e14c4c3303f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "6dcf3956232953fc25b8355fb1ded1e912b5802090fd21434d789087d6329683"); + } + + // set different plan, with exact fee + const auto amount = 6137599ul; + input.mutable_plan()->set_amount(amount); + input.mutable_plan()->set_available_amount(6305913ul); + input.mutable_plan()->set_fee(171782ul); + input.mutable_plan()->set_change(0); + *(input.mutable_plan()->add_utxos()) = input.utxos(0); + input.mutable_plan()->set_error(Common::Proto::OK); + + auto signer = Signer(input); + const auto output = signer.sign(); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + // Similar to (but with different key): https://cardanoscan.io/transaction/f480985662886e419a22673d31944455ab8891a80940bf392a37d9288ea9cf01?tab=withdrawals + const auto encoded = data(output.encoded()); + EXPECT_EQ(hex(encoded), "83a500818258207dfd2c579794314b1f84efc9db932a098e440ccefb874945591f1d4e85a9152a00018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a005da6ff021a00029f06031a0445b97605a1581de1df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b190d8ca100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df29058401ebaca2876fd17122404912a2558a98109cdf0f990a938d2917fa2c3b8c4e55e18a2cbabfa82fff03fa0d7ab8b88ca01ed18e42af3bfc4cda7f423a3aa30c00b825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e069325840777f04fa8f083fe562aecf78898aaaaac36e2cc6ca962f6ffb01e84a421cae1860496db79b2c5fb2879524c3d5121060b9ea1e693336230c6e5338e14c4c3303f6"); + const auto txid = data(output.tx_id()); + EXPECT_EQ(hex(txid), "6dcf3956232953fc25b8355fb1ded1e912b5802090fd21434d789087d6329683"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.dumpToString(), "[{0: [[h\"7dfd2c579794314b1f84efc9db932a098e440ccefb874945591f1d4e85a9152a\", 0]], 1: [[h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 6137599]], 2: 171782, 3: 71678326, 5: {h\"e1df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\": 3468}}, {0: [[h\"6d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df290\", h\"1ebaca2876fd17122404912a2558a98109cdf0f990a938d2917fa2c3b8c4e55e18a2cbabfa82fff03fa0d7ab8b88ca01ed18e42af3bfc4cda7f423a3aa30c00b\"], [h\"e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e06932\", h\"777f04fa8f083fe562aecf78898aaaaac36e2cc6ca962f6ffb01e84a421cae1860496db79b2c5fb2879524c3d5121060b9ea1e693336230c6e5338e14c4c3303\"]]}, null]"); + + EXPECT_EQ(decode.getArrayElements().size(), 3ul); + } +} + +} // namespace TW::Cardano::tests diff --git a/tests/chains/Cardano/TWCardanoAddressTests.cpp b/tests/chains/Cardano/TWCardanoAddressTests.cpp new file mode 100644 index 00000000000..d18caf17f49 --- /dev/null +++ b/tests/chains/Cardano/TWCardanoAddressTests.cpp @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include +#include +#include +#include +#include +#include +#include "TestUtilities.h" +#include "PrivateKey.h" + +#include + +TEST(TWCardano, AddressFromPublicKey) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA( + "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" + "639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a" + ).get())); + ASSERT_NE(nullptr, privateKey.get()); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519Cardano(privateKey.get())); + ASSERT_NE(nullptr, publicKey.get()); + ASSERT_EQ(128ul, publicKey.get()->impl.bytes.size()); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeCardano)); + auto addressString = WRAPS(TWAnyAddressDescription(address.get())); + assertStringsEqual(addressString, "addr1qx4z6twzknkkux0hhp0kq6hvdfutczp56g56y5em8r8mgvxalp7nkkk25vuspleke2zltaetmlwrfxv7t049cq9jmwjswmfw6t"); + + auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING("addr1qx4z6twzknkkux0hhp0kq6hvdfutczp56g56y5em8r8mgvxalp7nkkk25vuspleke2zltaetmlwrfxv7t049cq9jmwjswmfw6t").get(), TWCoinTypeCardano)); + ASSERT_NE(nullptr, address2.get()); + auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); + assertStringsEqual(address2String, "addr1qx4z6twzknkkux0hhp0kq6hvdfutczp56g56y5em8r8mgvxalp7nkkk25vuspleke2zltaetmlwrfxv7t049cq9jmwjswmfw6t"); + + ASSERT_TRUE(TWAnyAddressEqual(address.get(), address2.get())); +} + +TEST(TWCardano, AddressFromWallet) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( + STRING("cost dash dress stove morning robust group affair stomach vacant route volume yellow salute laugh").get(), + STRING("").get() + )); + auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeCardano)); + auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); + EXPECT_EQ(TWDataSize(privateKeyData.get()), 192ul); + + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519Cardano(privateKey.get())); + auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); + EXPECT_EQ(TWDataSize(publicKeyData.get()), 128ul); + assertHexEqual(publicKeyData, "fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26faf4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276"); + + auto address = WRAPS(TWCoinTypeDeriveAddress(TWCoinTypeCardano, privateKey.get())); + assertStringsEqual(address, "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); +} + +TEST(TWCardano, GetStakingKey) { + { + auto stakingAddress = WRAPS(TWCardanoGetStakingAddress(STRING("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23").get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(stakingAddress.get())), "stake1u80jysjtdzqt88jt4jx93h5lumfr67d273r4vwyasfa2pxcwxllmx"); + } + { // negative case: cannot get staking address from non-base address + auto stakingAddress = WRAPS(TWCardanoGetStakingAddress(STRING("stake1u95zuevxqjvpdh83r08ywq9xal6nxl48fgm0wvngyenvs4qh0hqf9").get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(stakingAddress.get())), ""); + } + { // negative case: cannot get staking address from invalid address, should not throw + auto stakingAddress = WRAPS(TWCardanoGetStakingAddress(STRING("__THIS_IS_NOT_A_VALID_CARDANO_ADDRESS__").get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(stakingAddress.get())), ""); + } +} diff --git a/tests/chains/Cardano/TWCoinTypeTests.cpp b/tests/chains/Cardano/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..72ba864892d --- /dev/null +++ b/tests/chains/Cardano/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWCardanoCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeCardano)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("b7a6c5cadab0f64bdc89c77ee4a351463aba5c33f2cef6bbd6542a74a90a3af3")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeCardano, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("DdzFFzCqrhstpwKc8WMvPwwBb5oabcTW9zc5ykA37wJR4tYQucvsR9dXb2kEGNXkFJz2PtrpzfRiZkx8R1iNo8NYqdsukVmv7EAybFwC")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeCardano, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeCardano)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeCardano)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeCardano), 6); + ASSERT_EQ(TWBlockchainCardano, TWCoinTypeBlockchain(TWCoinTypeCardano)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeCardano)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeCardano)); + assertStringsEqual(symbol, "ADA"); + assertStringsEqual(txUrl, "https://cardanoscan.io/transaction/b7a6c5cadab0f64bdc89c77ee4a351463aba5c33f2cef6bbd6542a74a90a3af3"); + assertStringsEqual(accUrl, "https://cardanoscan.io/address/DdzFFzCqrhstpwKc8WMvPwwBb5oabcTW9zc5ykA37wJR4tYQucvsR9dXb2kEGNXkFJz2PtrpzfRiZkx8R1iNo8NYqdsukVmv7EAybFwC"); + assertStringsEqual(id, "cardano"); + assertStringsEqual(name, "Cardano"); +} diff --git a/tests/chains/Cardano/TransactionCompilerTests.cpp b/tests/chains/Cardano/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..5106f34af35 --- /dev/null +++ b/tests/chains/Cardano/TransactionCompilerTests.cpp @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "uint256.h" + +#include "proto/Cardano.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(CardanoCompiler, CompileWithSignaturesAndPubKeyType) { + /// Prepare transaction input (protobuf) + const auto coin = TWCoinTypeCardano; + auto input = Cardano::Proto::SigningInput(); + input.mutable_transfer_message()->set_to_address("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq"); + input.mutable_transfer_message()->set_change_address("addr1v8mv75d2evhr4kt048cx7m3f97x363ajadha8xv8dp96nuggpv8rn"); + input.mutable_transfer_message()->set_amount(1850000); + + auto* utxo1 = input.add_utxos(); + utxo1->set_address("addr1v8mv75d2evhr4kt048cx7m3f97x363ajadha8xv8dp96nuggpv8rn"); + utxo1->set_amount(1000000); + auto txHash = parse_hex("d87f6e99c8d3a0fb22b1ea4de477f5a6d1f0e419450c2a194304371cada0ebb9"); + utxo1->mutable_out_point()->set_tx_hash(txHash.data(), txHash.size()); + utxo1->mutable_out_point()->set_output_index(0); + + auto* utxo2 = input.add_utxos(); + utxo2->set_address("addr1v8mv75d2evhr4kt048cx7m3f97x363ajadha8xv8dp96nuggpv8rn"); + utxo2->set_amount(4040957); + utxo2->mutable_out_point()->set_tx_hash(txHash.data(), txHash.size()); + utxo2->mutable_out_point()->set_output_index(1); + + auto* tokenBundle1 = utxo2->add_token_amount(); + tokenBundle1->set_policy_id("122d15a15dc753d2b3ca9ee46c1c6ca41dda38d735942d9d259c785b"); + tokenBundle1->set_asset_name_hex("5454546f6b656e2d31"); + const auto tokenAmount1 = store(uint256_t(3000000)); + tokenBundle1->set_amount(tokenAmount1.data(), tokenAmount1.size()); + + auto* tokenBundle2 = utxo2->add_token_amount(); + tokenBundle2->set_policy_id("122d15a15dc753d2b3ca9ee46c1c6ca41dda38d735942d9d259c785b"); + tokenBundle2->set_asset_name("TTToken-2"); + const auto tokenAmount2 = store(uint256_t(3000000)); + tokenBundle2->set_amount(tokenAmount2.data(), tokenAmount2.size()); + + auto* tokenBundle3 = utxo2->add_token_amount(); + tokenBundle3->set_policy_id("122d15a15dc753d2b3ca9ee46c1c6ca41dda38d735942d9d259c785b"); + tokenBundle3->set_asset_name_hex("5454546f6b656e2d33"); + const auto tokenAmount3 = store(uint256_t(5000000)); + tokenBundle3->set_amount(tokenAmount3.data(), tokenAmount3.size()); + + auto inputData = data(input.SerializeAsString()); + const auto preImageHash = TransactionCompiler::preImageHashes(coin, inputData); + + auto preOut = TxCompiler::Proto::PreSigningOutput(); + preOut.ParseFromArray(preImageHash.data(), (int)preImageHash.size()); + EXPECT_EQ(hex(preOut.data_hash()), "3e5a7c1d1afbc7e3ca783daba1beb12010fc4ecc748722558697509212c9f186"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = parse_hex("17c55d712152ccabf28215fe2d008d615f94796e098a97f1aa43d986ac3cb946"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeED25519); + const auto sig = parse_hex("1096ddcfb2ad21a4c0d861ef3fabe18841e8de88105b0d8e36430d7992c588634ead4100c32b2800b31b65e014d54a8238bdda63118d829bf0bcf1b631e86f0e"); + + /// Compile transaction info + const auto expectedTx = + "83a40082825820d87f6e99c8d3a0fb22b1ea4de477f5a6d1f0e419450c2a194304371cada0ebb901" + "825820d87f6e99c8d3a0fb22b1ea4de477f5a6d1f0e419450c2a194304371cada0ebb90001828258" + "39018d98bea0414243dc84070f96265577e7e6cf702d62e871016885034ecc64bf258b8e330cf0cd" + "d9fdb03e10b4e4ac08f5da1fdec6222a34681a001c3a9082581d61f6cf51aacb2e3ad96fa9f06f6e" + "292f8d1d47b2eb6fd39987684ba9f1821a002e0feea1581c122d15a15dc753d2b3ca9ee46c1c6ca4" + "1dda38d735942d9d259c785ba3495454546f6b656e2d311a002dc6c0495454546f6b656e2d321a00" + "2dc6c0495454546f6b656e2d331a004c4b40021a0002a0bf0300a1008182582017c55d712152ccab" + "f28215fe2d008d615f94796e098a97f1aa43d986ac3cb94658401096ddcfb2ad21a4c0d861ef3fab" + "e18841e8de88105b0d8e36430d7992c588634ead4100c32b2800b31b65e014d54a8238bdda63118d" + "829bf0bcf1b631e86f0ef6"; + const Data outData = TransactionCompiler::compileWithSignaturesAndPubKeyType(coin, inputData, {sig}, {publicKeyData}, TWPublicKeyTypeED25519); + + { + auto output = Cardano::Proto::SigningOutput(); + output.ParseFromArray(outData.data(), (int)outData.size()); + + EXPECT_EQ(hex(output.encoded()), expectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + const Data outputData = TransactionCompiler::compileWithSignaturesAndPubKeyType( + coin, inputData, {sig, sig}, {publicKeyData}, TWPublicKeyTypeED25519); + Cardano::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + const Data outputData = TransactionCompiler::compileWithSignaturesAndPubKeyType( + coin, inputData, {}, {}, TWPublicKeyTypeED25519); + Cardano::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/Cardano/TransactionTests.cpp b/tests/chains/Cardano/TransactionTests.cpp new file mode 100644 index 00000000000..8f9a64a038c --- /dev/null +++ b/tests/chains/Cardano/TransactionTests.cpp @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Cardano/Transaction.h" +#include "Cardano/AddressV3.h" +#include + +#include "HexCoding.h" +#include "Cbor.h" +#include "TestUtilities.h" +#include "Numeric.h" + +#include + + +namespace TW::Cardano { + +Transaction createTx() { + Transaction tx; + tx.inputs.emplace_back(parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767"), 1); + tx.inputs.emplace_back(parse_hex("554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0"), 0); + tx.outputs.emplace_back( + AddressV3("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23").data(), + 2000000); + tx.outputs.emplace_back( + AddressV3("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5").data(), + 16749189); + tx.fee = 165555; + tx.ttl = 53333345; + return tx; +} + +TEST(CardanoTransaction, Encode) { + const Transaction tx = createTx(); + + const auto encoded = tx.encode(); + EXPECT_EQ(hex(encoded), "a40082825820f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e76701825820554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af000018282583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a001e848082583901558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd51a00ff9285021a000286b3031a032dcd61"); + + { + const auto decode = Cbor::Decode(encoded); + ASSERT_TRUE(decode.isValid()); + EXPECT_EQ(decode.getMapElements().size(), 4ul); + EXPECT_EQ(decode.dumpToString(), "{0: [[h\"f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767\", 1], [h\"554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0\", 0]], 1: [[h\"01df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b\", 2000000], [h\"01558dd902616f5cd01edcc62870cb4748c45403f1228218bee5b628b526f0ca9e7a2c04d548fbd6ce86f358be139fe680652536437d1d6fd5\", 16749189]], 2: 165555, 3: 53333345}"); + } +} + +TEST(CardanoTransaction, GetId) { + const Transaction tx = createTx(); + + const auto txid = tx.getId(); + EXPECT_EQ(hex(txid), "cc262713a3e15a0fa245b062f33ffc6c2aa5a64c3ae7bfa793414069914e1bbf"); +} + +TEST(CardanoTransaction, minAdaAmount) { + const auto policyId = "9a9693a9a37912a5097918f97918d15240c92ab729a0b7c4aa144d77"; + + { // ADA only + const auto tb = TokenBundle(); + EXPECT_EQ(tb.minAdaAmount(), 1000000ul); + } + + { // 1 policyId, 1 6-char asset name + const auto tb = TokenBundle({TokenAmount(policyId, "TOKEN1", 0)}); + EXPECT_EQ(tb.minAdaAmount(), 1444443ul); + } + { // 2 policyId, 2 4-char asset names + auto tb = TokenBundle(); + tb.add(TokenAmount("012345678901234567890POLICY1", "TOK1", 20)); + tb.add(TokenAmount("012345678901234567890POLICY2", "TOK2", 20)); + EXPECT_EQ(tb.minAdaAmount(), 1629628ul); + } + { // 10 policyId, 10 6-char asset names + auto tb = TokenBundle(); + for (auto i = 0; i < 10; ++i) { + std::string policyId1 = + "012345678901234567890123456" + std::to_string(i); + std::string name = "ASSET" + std::to_string(i); + tb.add(TokenAmount(policyId1, name, 0)); + } + EXPECT_EQ(tb.minAdaAmount(), 3370367ul); + } + + EXPECT_EQ(TokenBundle::minAdaAmountHelper(0, 0, 0), 1000000ul); // ADA only + EXPECT_EQ(TokenBundle::minAdaAmountHelper(1, 0, 0), 1370369ul); // 1 policyId, no asset name + EXPECT_EQ(TokenBundle::minAdaAmountHelper(1, 1, 1), 1444443ul); // 1 policyId, 1 1-char asset name + EXPECT_EQ(TokenBundle::minAdaAmountHelper(1, 1, 6), 1444443ul); // 1 policyId, 1 6-char asset name + EXPECT_EQ(TokenBundle::minAdaAmountHelper(1, 1, 32), 1555554ul); // 1 policyId, 1 32-char asset name + EXPECT_EQ(TokenBundle::minAdaAmountHelper(1, 110, 110 * 32), 23777754ul); // 1 policyId, 110 32-char asset name + EXPECT_EQ(TokenBundle::minAdaAmountHelper(2, 2, 8), 1629628ul); // 2 policyId, 2 4-char asset names + EXPECT_EQ(TokenBundle::minAdaAmountHelper(3, 5, 20), 1999998ul); // 3 policyId, 5 4-char asset names + EXPECT_EQ(TokenBundle::minAdaAmountHelper(10, 10, 10 * 6), 3370367ul); // 10 policyId, 10 6-char asset names + EXPECT_EQ(TokenBundle::minAdaAmountHelper(60, 60, 60 * 32), 21222201ul); // 60 policyId, 60 32-char asset names +} + +TEST(CardanoTransaction, getPolicyIDs) { + const auto policyId1 = "012345678901234567890POLICY1"; + const auto policyId2 = "012345678901234567890POLICY2"; + const auto tb = TokenBundle({ + TokenAmount(policyId1, "TOK1", 10), + TokenAmount(policyId2, "TOK2", 20), + TokenAmount(policyId2, "TOK3", 30), // duplicate policyId + }); + ASSERT_EQ(tb.getPolicyIds().size(), 2ul); + EXPECT_TRUE(tb.getPolicyIds().contains(policyId1)); + EXPECT_TRUE(tb.getPolicyIds().contains(policyId2)); + + EXPECT_EQ(tb.getByPolicyId(policyId1).size(), 1ul); + EXPECT_EQ(tb.getByPolicyId(policyId2).size(), 2ul); +} + +TEST(TWCardanoTransaction, minAdaAmount) { + { // ADA-only + const auto bundleProto = TokenBundle().toProto(); + const auto bundleProtoData = data(bundleProto.SerializeAsString()); + EXPECT_EQ(TWCardanoMinAdaAmount(&bundleProtoData), 1000000ul); + } + { // 2 policyId, 2 4-char asset names + auto bundle = TokenBundle(); + bundle.add(TokenAmount("012345678901234567890POLICY1", "TOK1", 20)); + bundle.add(TokenAmount("012345678901234567890POLICY2", "TOK2", 20)); + const auto bundleProto = bundle.toProto(); + const auto bundleProtoData = data(bundleProto.SerializeAsString()); + EXPECT_EQ(TWCardanoMinAdaAmount(&bundleProtoData), 1629628ul); + } +} + +TEST(TWCardanoTransaction, outputMinAdaAmount) { + // For an actual value see `ProtocolParams::coinsPerUtxoByte`: + // https://input-output-hk.github.io/cardano-graphql/ + const auto coinsPerUtxoByte = STRING("4310"); + const auto toAddress = STRING("addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23"); + const auto toLegacy = STRING("Ae2tdPwUPEZ4YjgvykNpoFeYUxoyhNj2kg8KfKWN2FizsSpLUPv68MpTVDo"); + + { // ADA-only + const auto bundleProto = TokenBundle().toProto(); + const auto bundleProtoData = data(bundleProto.SerializeAsString()); + const auto actual = WRAPS(TWCardanoOutputMinAdaAmount(toAddress.get(), &bundleProtoData, coinsPerUtxoByte.get())); + assertStringsEqual(actual, "969750"); + } + { // ADA-only (to legacy address) + const auto bundleProto = TokenBundle().toProto(); + const auto bundleProtoData = data(bundleProto.SerializeAsString()); + const auto actual = WRAPS(TWCardanoOutputMinAdaAmount(toLegacy.get(), &bundleProtoData, coinsPerUtxoByte.get())); + assertStringsEqual(actual, "909410"); + } + { // 1 NFT + auto bundle = TokenBundle(); + bundle.add(TokenAmount("219820e6cb04316f41a337fea356480f412e7acc147d28f175f21b5e", "coolcatssociety4567", 1)); + const auto bundleProto = bundle.toProto(); + const auto bundleProtoData = data(bundleProto.SerializeAsString()); + const auto actual = WRAPS(TWCardanoOutputMinAdaAmount(toAddress.get(), &bundleProtoData, coinsPerUtxoByte.get())); + assertStringsEqual(actual, "1202490"); + } + { // 2 policyId, 2 4-char asset names + auto bundle = TokenBundle(); + bundle.add(TokenAmount("8fef2d34078659493ce161a6c7fba4b56afefa8535296a5743f69587", "AADA", 20)); + bundle.add(TokenAmount("6ac8ef33b510ec004fe11585f7c5a9f0c07f0c23428ab4f29c1d7d10", "MELD", 20)); + const auto bundleProto = bundle.toProto(); + const auto bundleProtoData = data(bundleProto.SerializeAsString()); + const auto actual = WRAPS(TWCardanoOutputMinAdaAmount(toAddress.get(), &bundleProtoData, coinsPerUtxoByte.get())); + assertStringsEqual(actual, "1297310"); + } + { // Invalid token bundle + Data invalidTokenBundleData {1, 2, 3, 4, 5}; + const auto actual = TWCardanoOutputMinAdaAmount(toAddress.get(), &invalidTokenBundleData, coinsPerUtxoByte.get()); + EXPECT_EQ(actual, nullptr); + } + { // Invalid address + const auto invalidAddress = STRING("foobar"); + const auto bundleProto = TokenBundle().toProto(); + const auto bundleProtoData = data(bundleProto.SerializeAsString()); + const auto actual = TWCardanoOutputMinAdaAmount(invalidAddress.get(), &bundleProtoData, coinsPerUtxoByte.get()); + EXPECT_EQ(actual, nullptr); + } + { // Invalid coinsPerUtxoByte + const auto invalidCoinsPerUtxoByte = STRING("foobar"); + const auto bundleProto = TokenBundle().toProto(); + const auto bundleProtoData = data(bundleProto.SerializeAsString()); + const auto actual = TWCardanoOutputMinAdaAmount(toAddress.get(), &bundleProtoData, invalidCoinsPerUtxoByte.get()); + EXPECT_EQ(actual, nullptr); + } + { // Invalid coinsPerUtxoByte (multiply overflow) + auto invalidCoinsPerUtxoByteStr = std::to_string(std::numeric_limits::max()); + const auto invalidCoinsPerUtxoByte = STRING(invalidCoinsPerUtxoByteStr.c_str()); + const auto bundleProto = TokenBundle().toProto(); + const auto bundleProtoData = data(bundleProto.SerializeAsString()); + const auto actual = TWCardanoOutputMinAdaAmount(toAddress.get(), &bundleProtoData, invalidCoinsPerUtxoByte.get()); + EXPECT_EQ(actual, nullptr); + } +} + +} // namespace TW::Cardano diff --git a/tests/chains/Celo/TWCoinTypeTests.cpp b/tests/chains/Celo/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..6ac1b830db8 --- /dev/null +++ b/tests/chains/Celo/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWCeloCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeCelo)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xaf41ee58afe633dc7b179c15693cca40fe0372c1d7c70351620105d59d326693")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeCelo, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xFBFf95e2Fa7e4Ff3aeA34eFB05fB60F9968a6aaD")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeCelo, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeCelo)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeCelo)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeCelo), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeCelo)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeCelo)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeCelo)); + assertStringsEqual(symbol, "CELO"); + assertStringsEqual(txUrl, "https://explorer.celo.org/tx/0xaf41ee58afe633dc7b179c15693cca40fe0372c1d7c70351620105d59d326693"); + assertStringsEqual(accUrl, "https://explorer.celo.org/address/0xFBFf95e2Fa7e4Ff3aeA34eFB05fB60F9968a6aaD"); + assertStringsEqual(id, "celo"); + assertStringsEqual(name, "Celo"); +} diff --git a/tests/chains/ConfluxeSpace/TWCoinTypeTests.cpp b/tests/chains/ConfluxeSpace/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..438632f7ad0 --- /dev/null +++ b/tests/chains/ConfluxeSpace/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +TEST(TWConfluxeSpaceCoinType, TWCoinType) { + const auto coin = TWCoinTypeConfluxeSpace; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x920efefb3213b2d6a3b84149cb50b61a813d085443a20e1b0eab74120e41ff72")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x337a087daef75c72871de592fbbba570829a936a")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "cfxevm"); + assertStringsEqual(name, "Conflux eSpace"); + assertStringsEqual(symbol, "CFX"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "1030"); + assertStringsEqual(txUrl, "https://evm.confluxscan.net/tx/0x920efefb3213b2d6a3b84149cb50b61a813d085443a20e1b0eab74120e41ff72"); + assertStringsEqual(accUrl, "https://evm.confluxscan.net/address/0x337a087daef75c72871de592fbbba570829a936a"); +} diff --git a/tests/chains/Cosmos/AddressTests.cpp b/tests/chains/Cosmos/AddressTests.cpp new file mode 100644 index 00000000000..97034a9b590 --- /dev/null +++ b/tests/chains/Cosmos/AddressTests.cpp @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "Cosmos/Address.h" + +#include +#include + +namespace TW::Cosmos { + +TEST(CosmosAddressAddressToData, Invalid) { + ASSERT_TRUE(addressToData(TWCoinTypeCosmos, "fake").empty()); +} + +TEST(CosmosAddress, Valid) { + ASSERT_TRUE(Address::isValid(TWCoinTypeBinance, "bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2")); +} + +TEST(CosmosAddress, Invalid) { + ASSERT_FALSE(Address::isValid(TWCoinTypeBinance, "bnb1grpf0955h0ykzq3ar6nmum7y6gdfl6lxfn46h2")); +} + +TEST(CosmosAddress, Cosmos_FromPublicKey) { + auto privateKey = PrivateKey(parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005")); + auto publicKeyData = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + ASSERT_EQ(hex(publicKeyData.bytes), "0257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc5"); + + auto publicKey = PublicKey(publicKeyData); + auto address = Address("cosmos", publicKey); + ASSERT_EQ(address.string(), "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); + ASSERT_EQ(hex(address.getKeyHash()), "bc2da90c84049370d1b7c528bc164bc588833f21"); +} + +TEST(CosmosAddress, Cosmos_FromString) { + Address addr; + ASSERT_TRUE(Address::decode("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", addr)); + ASSERT_EQ(addr.string(), "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); +} + +TEST(CosmosAddress, Cosmos_Valid) { + ASSERT_TRUE(Address::isValid(TWCoinTypeCosmos, "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")); + ASSERT_TRUE(Address::isValid(TWCoinTypeCosmos, "cosmospub1addwnpepqftjsmkr7d7nx4tmhw4qqze8w39vjq364xt8etn45xqarlu3l2wu2n7pgrq")); + ASSERT_TRUE(Address::isValid(TWCoinTypeCosmos, "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08")); + ASSERT_TRUE(Address::isValid(TWCoinTypeCosmos, "cosmosvalconspub1zcjduepqjnnwe2jsywv0kfc97pz04zkm7tc9k2437cde2my3y5js9t7cw9mstfg3sa")); +} + +TEST(CosmosAddress, Cosmos_Invalid) { + ASSERT_FALSE(Address::isValid(TWCoinTypeCosmos, "cosmos1xsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02")); + ASSERT_FALSE(Address::isValid(TWCoinTypeCosmos, "cosmospub1xddwnpepqftjsmkr7d7nx4tmhw4qqze8w39vjq364xt8etn45xqarlu3l2wu2n7pgrq")); + ASSERT_FALSE(Address::isValid(TWCoinTypeCosmos, "cosmosvaloper1xxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08")); + ASSERT_FALSE(Address::isValid(TWCoinTypeCosmos, "cosmosvalconspub1xcjduepqjnnwe2jsywv0kfc97pz04zkm7tc9k2437cde2my3y5js9t7cw9mstfg3sa")); +} + +TEST(CosmosAddress, ThorFromPublicKey) { + auto privateKey = PrivateKey(parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e")); + auto publicKeyData = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + ASSERT_EQ(hex(publicKeyData.bytes), "03ed997e396cf4292f5fce5a42bba41599ccd5d96e313154a7c9ea7049de317c77"); + + auto publicKey = PublicKey(publicKeyData); + auto address = Address("thor", publicKey); + ASSERT_EQ(address.string(), "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r"); + ASSERT_EQ(hex(address.getKeyHash()), "1522e767db6eb19708b0038029bfbd607bc9bd0e"); +} + +TEST(CosmosAddress, ThorValid) { + ASSERT_TRUE(Address::isValid(TWCoinTypeTHORChain, "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r")); + ASSERT_FALSE(Address::isValid(TWCoinTypeTHORChain, "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2s")); +} + +} // namespace TW::Cosmos diff --git a/tests/chains/Cosmos/Agoric/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Agoric/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..1cb7062bcb7 --- /dev/null +++ b/tests/chains/Cosmos/Agoric/TWAnyAddressTests.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gAgoricAddr = "agoric1wu32wujsvz290sasq308fswznx77nk9k0cxqwg"; +static const std::string gAgoricHrp = "agoric"; + +TEST(TWAgoricAnyAddress, AllAgoricAddressTests) { + CosmosAddressParameters parameters{.hrp = gAgoricHrp, + .coinType = TWCoinTypeAgoric, + .address = gAgoricAddr, + .privKey = "9457d0a4b7bdfe23528af07603af0f7d0ac0c510526da7721abefdc3948461f6", + .publicKey = "03602731bc2f787eec358c1ba8ddb8e7c7720f56a0406b8d16e20c93b822953960"}; + TestCosmosAddressParameters(parameters); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Agoric/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Agoric/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..05d975709ae --- /dev/null +++ b/tests/chains/Cosmos/Agoric/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWAgoricCoinType, TWCoinType) { + const auto coin = TWCoinTypeAgoric; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("576224B1A0F3D56BA23C5350C2A0E3CEA86F40005B828793E5ACBC2F4813152E")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("agoric1cqvwa8jr6pmt45jndanc8lqmdsxjkhw0yertc0")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "agoric"); + assertStringsEqual(name, "Agoric"); + assertStringsEqual(symbol, "BLD"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "agoric-3"); + assertStringsEqual(txUrl, "https://atomscan.com/agoric/transactions/576224B1A0F3D56BA23C5350C2A0E3CEA86F40005B828793E5ACBC2F4813152E"); + assertStringsEqual(accUrl, "https://atomscan.com/agoric/accounts/agoric1cqvwa8jr6pmt45jndanc8lqmdsxjkhw0yertc0"); +} diff --git a/tests/chains/Cosmos/Akash/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Akash/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..621a43dd782 --- /dev/null +++ b/tests/chains/Cosmos/Akash/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gAkashAddr = "akash1mry47pkga5tdswtluy0m8teslpalkdq0n6af90"; +static const std::string gAkashHrp = "akash"; + +TEST(TWAkashAnyAddress, AllAkashAddressTests) { + CosmosAddressParameters parameters{.hrp = gAkashHrp, .coinType = TWCoinTypeAkash, .address = gAkashAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Akash/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Akash/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..e13668774da --- /dev/null +++ b/tests/chains/Cosmos/Akash/TWCoinTypeTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + TEST(TWAkashCoinType, TWCoinType) { + const auto coin = TWCoinTypeAkash; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("C0083856344425908D5333D4325E3E0DE9D697BA568C6D99C34303819F615D25")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("akash1f4nskxfw8ufhwnajh7xwt0wmdtxm02vwta6krg")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "akash"); + assertStringsEqual(name, "Akash"); + assertStringsEqual(symbol, "AKT"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "akashnet-2"); + assertStringsEqual(txUrl, "https://www.mintscan.io/akash/txs/C0083856344425908D5333D4325E3E0DE9D697BA568C6D99C34303819F615D25"); + assertStringsEqual(accUrl, "https://www.mintscan.io/akash/account/akash1f4nskxfw8ufhwnajh7xwt0wmdtxm02vwta6krg"); + } +} diff --git a/tests/chains/Cosmos/Axelar/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Axelar/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..b1e92806669 --- /dev/null +++ b/tests/chains/Cosmos/Axelar/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gAxelarAddr = "axelar1mry47pkga5tdswtluy0m8teslpalkdq060xxh5"; +static const std::string gAxelarHrp = "axelar"; + +TEST(TWAxelarAnyAddress, AllAxelarAddressTests) { + CosmosAddressParameters parameters{.hrp = gAxelarHrp, .coinType = TWCoinTypeAxelar, .address = gAxelarAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Axelar/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Axelar/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..69c7f823c5f --- /dev/null +++ b/tests/chains/Cosmos/Axelar/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWAxelarCoinType, TWCoinType) { + const auto coin = TWCoinTypeAxelar; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("838F31D023B273E6A8282085202A4CCEDE1693D2503ACCD557B37C9DAB33A79C")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("axelar1mry47pkga5tdswtluy0m8teslpalkdq060xxh5")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "axelar"); + assertStringsEqual(name, "Axelar"); + assertStringsEqual(symbol, "AXL"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "axelar-dojo-1"); + assertStringsEqual(txUrl, "https://www.mintscan.io/axelar/txs/838F31D023B273E6A8282085202A4CCEDE1693D2503ACCD557B37C9DAB33A79C"); + assertStringsEqual(accUrl, "https://www.mintscan.io/axelar/account/axelar1mry47pkga5tdswtluy0m8teslpalkdq060xxh5"); +} diff --git a/tests/chains/Cosmos/BandChain/TWCoinTypeTests.cpp b/tests/chains/Cosmos/BandChain/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..d2a7096dd94 --- /dev/null +++ b/tests/chains/Cosmos/BandChain/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWBandChainCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBandChain)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("74AF38C2183B06EB6274DA4AAC0D2334E6E283643D436852F5E088AEA2CD0B17")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBandChain, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("band16gpgu994g2gdrzvwp9047le3pcq9wz6mcgtd4w")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBandChain, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBandChain)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBandChain)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBandChain), 6); + ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeBandChain)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeBandChain)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBandChain)); + assertStringsEqual(symbol, "BAND"); + assertStringsEqual(txUrl, "https://www.mintscan.io/band/tx/74AF38C2183B06EB6274DA4AAC0D2334E6E283643D436852F5E088AEA2CD0B17"); + assertStringsEqual(accUrl, "https://www.mintscan.io/band/account/band16gpgu994g2gdrzvwp9047le3pcq9wz6mcgtd4w"); + assertStringsEqual(id, "band"); + assertStringsEqual(name, "BandChain"); +} diff --git a/tests/chains/Cosmos/Bluzelle/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Bluzelle/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..6a569a48677 --- /dev/null +++ b/tests/chains/Cosmos/Bluzelle/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWCoinTypeBluzelle, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeBluzelle)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("AC026E0EC6E33A77D5EA6B9CEF9810699BC2AD8C5582E007E7857457C6D3B819")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeBluzelle, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("bluzelle1q9cryfal7u3jvnq6er5ufety20xtzw6ycx2te9")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeBluzelle, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeBluzelle)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeBluzelle)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeBluzelle), 6); + ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeBluzelle)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeBluzelle)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeBluzelle)); + assertStringsEqual(symbol, "BLZ"); + assertStringsEqual(txUrl, "https://bigdipper.net.bluzelle.com/transactions/AC026E0EC6E33A77D5EA6B9CEF9810699BC2AD8C5582E007E7857457C6D3B819"); + assertStringsEqual(accUrl, "https://bigdipper.net.bluzelle.com/account/bluzelle1q9cryfal7u3jvnq6er5ufety20xtzw6ycx2te9"); + assertStringsEqual(id, "bluzelle"); + assertStringsEqual(name, "Bluzelle"); +} diff --git a/tests/chains/Cosmos/Comdex/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Comdex/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..6ef956ac13e --- /dev/null +++ b/tests/chains/Cosmos/Comdex/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gComdexAddr = "comdex1mry47pkga5tdswtluy0m8teslpalkdq0ewjv9z"; +static const std::string gComdexHrp = "comdex"; + +TEST(TWComdexAnyAddress, AllComdexAddressTests) { + CosmosAddressParameters parameters{.hrp = gComdexHrp, .coinType = TWCoinTypeComdex, .address = gComdexAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Comdex/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Comdex/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..1f598515c34 --- /dev/null +++ b/tests/chains/Cosmos/Comdex/TWCoinTypeTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + TEST(TWComdexCoinType, TWCoinType) { + const auto coin = TWCoinTypeComdex; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("04C790D09A40EE958DBDD385B679B5EB60C10F9BC1389CC8F896DC9193A5ED6C")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("comdex1jz7av7cq45gh5hhrugtak7lkps2ga5v0u64nz6")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "comdex"); + assertStringsEqual(name, "Comdex"); + assertStringsEqual(symbol, "CMDX"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "comdex-1"); + assertStringsEqual(txUrl, "https://www.mintscan.io/comdex/txs/04C790D09A40EE958DBDD385B679B5EB60C10F9BC1389CC8F896DC9193A5ED6C"); + assertStringsEqual(accUrl, "https://www.mintscan.io/comdex/account/comdex1jz7av7cq45gh5hhrugtak7lkps2ga5v0u64nz6"); + } +} diff --git a/tests/chains/Cosmos/Coreum/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Coreum/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..50a619ba0db --- /dev/null +++ b/tests/chains/Cosmos/Coreum/TWAnyAddressTests.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gCoreumAddr = "core1a5nvz6smgsph9gephguyhn30fmzrpaxrvvdjun"; +static const std::string gCoreumHrp = "core"; + +TEST(TWCoreumAnyAddress, AllCoreumAddressTests) { + CosmosAddressParameters parameters{.hrp = gCoreumHrp, + .coinType = TWCoinTypeCoreum, + .address = gCoreumAddr, + .privKey = "56e5e45bf33a779527ec670b5336f6bc78efbe0e3bf1f004e7250673a82a3431", + .publicKey = "0345d8d927b955c3cd468d12b5bc634c7919ee4777e578439af6314cf04b2ff114"}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Coreum/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Coreum/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..a588861349e --- /dev/null +++ b/tests/chains/Cosmos/Coreum/TWCoinTypeTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + TEST(TWCoreumCoinType, TWCoinType) { + const auto coin = TWCoinTypeCoreum; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("32A4AE2AE6AAE31E75EDDADE0AB9F1499ABD5AD8D3F261ADEF2805CD46FF74E7")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("core1zmwdnfpwuymwn0fkwnj2aaje34npd5sqgjxq9v")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "coreum"); + assertStringsEqual(name, "Coreum"); + assertStringsEqual(symbol, "CORE"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "coreum-mainnet-1"); + assertStringsEqual(txUrl, "https://www.mintscan.io/coreum/txs/32A4AE2AE6AAE31E75EDDADE0AB9F1499ABD5AD8D3F261ADEF2805CD46FF74E7"); + assertStringsEqual(accUrl, "https://www.mintscan.io/coreum/account/core1zmwdnfpwuymwn0fkwnj2aaje34npd5sqgjxq9v"); + } +} diff --git a/tests/chains/Cosmos/CosmosTestHelpers.h b/tests/chains/Cosmos/CosmosTestHelpers.h new file mode 100644 index 00000000000..50e81c4459f --- /dev/null +++ b/tests/chains/Cosmos/CosmosTestHelpers.h @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include +#include +#include "HexCoding.h" +#include "Hash.h" +#include "PublicKey.h" +#include "Bech32Address.h" +#include "Cosmos/Address.h" + +#include "TestUtilities.h" +#include + +namespace TW::Cosmos::tests { + +struct CosmosAddressParameters { + const std::string hrp{}; + TWCoinType coinType; + const std::string address; + bool standaloneChain{true}; + TWPublicKeyType publicKeyType{TWPublicKeyTypeSECP256k1}; + const std::string privKey{"a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433"}; + const std::string publicKey{"02cbfdb5e472893322294e60cf0883d43df431e1089d29ecb447a9e6d55045aae5"}; +}; + +} + +namespace TW::Cosmos::tests::internal { + static inline void isValidAddress(const CosmosAddressParameters& addressParameters) { + auto address_utf8 = STRING(addressParameters.address.c_str()); + auto hrp_utf8 = STRING(addressParameters.hrp.c_str()); + EXPECT_TRUE(TWAnyAddressIsValidBech32(address_utf8.get(), TWCoinTypeCosmos, hrp_utf8.get())); + EXPECT_TRUE(TWAnyAddressIsValid(address_utf8.get(), addressParameters.coinType)); + EXPECT_FALSE(TWAnyAddressIsValidBech32(address_utf8.get(), TWCoinTypeBitcoin, hrp_utf8.get())); + if (addressParameters.coinType != TWCoinTypeCosmos) { + EXPECT_FALSE(TWAnyAddressIsValid(address_utf8.get(), TWCoinTypeCosmos)); + } + EXPECT_FALSE(TWAnyAddressIsValid(address_utf8.get(), TWCoinTypeBitcoin)); + } + + static inline void testCreateFromPubKeyWrapper(const CosmosAddressParameters& addressParameters) { + if (addressParameters.standaloneChain) { + const auto hrp_utf8 = STRING(addressParameters.hrp.c_str()); + const auto data = DATA(addressParameters.publicKey.c_str()); + const auto pubkey = TWPublicKeyCreateWithData(data.get(), TWPublicKeyTypeSECP256k1); + const auto twAddress = TWAnyAddressCreateBech32WithPublicKey(pubkey, TWCoinTypeCosmos, hrp_utf8.get()); + auto twData = TWAnyAddressData(twAddress); + auto hexData = hex(*reinterpret_cast(twData)); + ASSERT_EQ(hex(Bech32Address(addressParameters.hrp, TW::Hash::HasherSha256ripemd, pubkey->impl).getKeyHash()), hexData); + auto address = TWAnyAddressDescription(twAddress); + EXPECT_EQ(addressParameters.address, *reinterpret_cast(address)); + TWStringDelete(address); + TWAnyAddressDelete(twAddress); + TWDataDelete(twData); + TWPublicKeyDelete(pubkey); + } + + // With coin type + { + auto publicKey = PublicKey(parse_hex(addressParameters.publicKey), addressParameters.publicKeyType); + auto address = Address(addressParameters.coinType, publicKey); + } + } + + static inline void testCreateFromPrivKey(const CosmosAddressParameters& addressParameters) { + auto privateKey = PrivateKey(parse_hex(addressParameters.privKey)); + auto address = Address(addressParameters.coinType, privateKey.getPublicKey(addressParameters.publicKeyType)); + ASSERT_EQ(address.string(), addressParameters.address); + } + + static inline void testCreateFromString(const CosmosAddressParameters& addressParameters) { + // BECH32 + if (addressParameters.standaloneChain) { + const auto address = STRING(addressParameters.address.c_str()); + const auto hrp = STRING(addressParameters.hrp.c_str()); + const auto anyAddr = TWAnyAddressCreateBech32(address.get(), TWCoinTypeCosmos, hrp.get()); + const auto addrDescription = TWAnyAddressDescription(anyAddr); + ASSERT_TRUE(TWAnyAddressIsValidBech32(addrDescription, TWCoinTypeCosmos, hrp.get())); + TWStringDelete(addrDescription); + TWAnyAddressDelete(anyAddr); + } + + // With Coin Type + { + const auto address = STRING(addressParameters.address.c_str()); + const auto anyAddr = TWAnyAddressCreateWithString(address.get(), addressParameters.coinType); + const auto addrDescription = TWAnyAddressDescription(anyAddr); + ASSERT_TRUE(TWAnyAddressIsValid(addrDescription, addressParameters.coinType)); + TWStringDelete(addrDescription); + TWAnyAddressDelete(anyAddr); + } + } +} + +namespace TW::Cosmos::tests { + static inline void TestCosmosAddressParameters(const CosmosAddressParameters& addressParameters) { + internal::isValidAddress(addressParameters); + internal::testCreateFromPubKeyWrapper(addressParameters); + internal::testCreateFromPrivKey(addressParameters); + internal::testCreateFromString(addressParameters); + } +} diff --git a/tests/chains/Cosmos/Crescent/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Crescent/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..f53619dd664 --- /dev/null +++ b/tests/chains/Cosmos/Crescent/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gCrescentAddr = "cre1mry47pkga5tdswtluy0m8teslpalkdq06frtfc"; +static const std::string gCrescentHrp = "cre"; + +TEST(TWCrescentAnyAddress, AllCrescentAddressTests) { + CosmosAddressParameters parameters{.hrp = gCrescentHrp, .coinType = TWCoinTypeCrescent, .address = gCrescentAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Crescent/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Crescent/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..7b8586d04ec --- /dev/null +++ b/tests/chains/Cosmos/Crescent/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWCrescentCoinType, TWCoinType) { + const auto coin = TWCoinTypeCrescent; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0C46F4B65706FB5A1FB3A7C32543CF7836DA33EB88295573F66F1886A264E852")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("cre1ekj60f38vatr9fxy4p2t04mwedpc3mc6v38d6n")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "crescent"); + assertStringsEqual(name, "Crescent"); + assertStringsEqual(symbol, "CRE"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "crescent-1"); + assertStringsEqual(txUrl, "https://www.mintscan.io/crescent/txs/0C46F4B65706FB5A1FB3A7C32543CF7836DA33EB88295573F66F1886A264E852"); + assertStringsEqual(accUrl, "https://www.mintscan.io/crescent/account/cre1ekj60f38vatr9fxy4p2t04mwedpc3mc6v38d6n"); +} diff --git a/tests/chains/Cosmos/CryptoOrg/SignerTests.cpp b/tests/chains/Cosmos/CryptoOrg/SignerTests.cpp new file mode 100644 index 00000000000..c81ffb931af --- /dev/null +++ b/tests/chains/Cosmos/CryptoOrg/SignerTests.cpp @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "proto/Cosmos.pb.h" +#include "Cosmos/Address.h" +#include "HexCoding.h" +#include "PublicKey.h" +#include "TestUtilities.h" +#include "TrustWalletCore/TWAnySigner.h" + +#include +#include + +namespace TW::Cosmos::tests { + +TEST(CryptoorgSigner, SignTx_DDCCE4) { + auto input = Cosmos::Proto::SigningInput(); + input.set_account_number(125798); + input.set_sequence(0); + input.set_chain_id("crypto-org-chain-mainnet-1"); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address("cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0"); + message.set_to_address("cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus"); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("basecro"); + amountOfTx->set_amount("100000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("basecro"); + amountOfFee->set_amount("5000"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + + assertJSONEqual(json, R"( + { + "accountNumber": "125798", + "chainId": "crypto-org-chain-mainnet-1", + "fee": { + "amounts": [ + { + "denom": "basecro", + "amount": "5000" + } + ], + "gas": "200000" + }, + "messages": [ + { + "sendCoinsMessage": { + "fromAddress": "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0", + "toAddress": "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus", + "amounts": [ + { + "denom": "basecro", + "amount": "100000000" + } + ] + } + } + ] + } + )"); + + auto privateKey = parse_hex("200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCryptoOrg); + + assertJSONEqual(output.json(), R"( + { + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "5000", + "denom": "basecro" + } + ], + "gas": "200000" + }, + "memo": "", + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "amount": [ + { + "amount": "100000000", + "denom": "basecro" + } + ], + "from_address": "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0", + "to_address": "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A4gxsGFiPn6L5Z2IjHEISkXI0IkwfL9exV3GLB171Wvj" + }, + "signature": "5+5rSFFg0FE9cTklQWQHNktBDJsz7UCnMSgF0t0+gYcrIhEWUyTtibXaHZQbKAAaciJ1BkHXYREjU55VswByVg==" + } + ] + } + } + )"); + EXPECT_EQ(hex(output.signature()), "e7ee6b485160d0513d713925416407364b410c9b33ed40a7312805d2dd3e81872b2211165324ed89b5da1d941b28001a7222750641d7611123539e55b3007256"); + + /// https://crypto.org/explorer/tx/DDCCE4052040B05914CADEFE78C0A75BE363AE39504E7EF6B2EDB8A9072AD44B + /// curl -H 'Content-Type: application/json' --data-binary '{"mode":"block","tx":{"fee": ... }}' https://mainnet.crypto.org:1317/txs +} + +TEST(CryptoorgSigner, SignJson) { + auto inputJson = R"({"accountNumber":"125798","chainId":"crypto-org-chain-mainnet-1","fee":{"amounts":[{"denom":"basecro","amount":"5000"}],"gas":"200000"},"messages":[{"sendCoinsMessage":{"fromAddress":"cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0","toAddress":"cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus","amounts":[{"denom":"basecro","amount":"100000000"}]}}]})"; + auto privateKey = parse_hex("200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d"); + + auto outputJson = TW::anySignJSON(TWCoinTypeCryptoOrg, inputJson, privateKey); + + assertJSONEqual(outputJson, R"( + { + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "5000", + "denom": "basecro" + } + ], + "gas": "200000" + }, + "memo": "", + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "amount": [ + { + "amount": "100000000", + "denom": "basecro" + } + ], + "from_address": "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0", + "to_address": "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A4gxsGFiPn6L5Z2IjHEISkXI0IkwfL9exV3GLB171Wvj" + }, + "signature": "5+5rSFFg0FE9cTklQWQHNktBDJsz7UCnMSgF0t0+gYcrIhEWUyTtibXaHZQbKAAaciJ1BkHXYREjU55VswByVg==" + } + ] + } + } + )"); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/CryptoOrg/TWAnyAddressTests.cpp b/tests/chains/Cosmos/CryptoOrg/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..cea33e461fd --- /dev/null +++ b/tests/chains/Cosmos/CryptoOrg/TWAnyAddressTests.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gCryptoorgAddr = "cro1tcfsr7m7d6jk6fpyety373m8c39ea2f8dmp830"; +static const std::string gCryptoorgHrp = "cro"; + +TEST(TWCryptoorgAnyAddress, AllCryptoorgAddressTests) { + CosmosAddressParameters parameters{.hrp = gCryptoorgHrp, + .coinType = TWCoinTypeCryptoOrg, + .address = gCryptoorgAddr, + .privKey = "5469c1a88e67d6d490e647ac8d82d54c4a17b8f00d272b3b30fac2253339aa28", + .publicKey = "025824f188c340235910b15e5e35aea11cfc28eabfa7756da5585c08f74db437ef"}; + TestCosmosAddressParameters(parameters); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/CryptoOrg/TWAnySignerTests.cpp b/tests/chains/Cosmos/CryptoOrg/TWAnySignerTests.cpp new file mode 100644 index 00000000000..c39786cac39 --- /dev/null +++ b/tests/chains/Cosmos/CryptoOrg/TWAnySignerTests.cpp @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "proto/Cosmos.pb.h" +#include "HexCoding.h" +#include "Base64.h" +#include "Data.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + + +const auto Address1 = "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0"; +const auto Address2 = "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus"; +const auto PrivateKey1 = "200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d"; + +TEST(TWAnySignerCryptoorg, SignTx_Proto_BCB213) { + auto input = Cosmos::Proto::SigningInput(); + input.set_signing_mode(Cosmos::Proto::Protobuf); + input.set_account_number(125798); + input.set_sequence(2); + input.set_chain_id("crypto-org-chain-mainnet-1"); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(Address1); + message.set_to_address(Address2); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("basecro"); + amountOfTx->set_amount("50000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("basecro"); + amountOfFee->set_amount("5000"); + + auto privateKey = parse_hex(PrivateKey1); + input.set_private_key(privateKey.data(), privateKey.size()); + + Cosmos::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeCryptoOrg); + + // https://crypto.org/explorer/tx/BCB213B0A121F0CF11BECCF52475F1C8328D6070F3CFDA9E14C42E6DB30E847E + // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "CpABC...F0SI=", "mode": "BROADCAST_MODE_BLOCK"}' https://mainnet.crypto.org:1317/cosmos/tx/v1beta1/txs + assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CpABCo0BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm0KKmNybzFjdHd0Y3dwZ2tza3k5ODhkaHRoNmpzbHh2ZXVtZ3UwZDQ1emdmMBIqY3JvMXhwYWh5NmM3d2xkeGFjdjZsZDk5aDQzNW1odmZuc3VwMjR2Y3VzGhMKB2Jhc2Vjcm8SCDUwMDAwMDAwEmkKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQOIMbBhYj5+i+WdiIxxCEpFyNCJMHy/XsVdxiwde9Vr4xIECgIIARgCEhUKDwoHYmFzZWNybxIENTAwMBDAmgwaQAcxK9xk6r69gmz+1UWaCnYxNuXPXZdp59YcqKPJE5d6fp+IICTBOwd2rs8MiApcf8kNSrbZ6oECxcGQAdxF0SI=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "07312bdc64eabebd826cfed5459a0a763136e5cf5d9769e7d61ca8a3c913977a7e9f882024c13b0776aecf0c880a5c7fc90d4ab6d9ea8102c5c19001dc45d122"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error_message(), ""); +} + +TEST(TWAnySignerCryptoorg, SignTx_Json_DDCCE4) { + auto input = Cosmos::Proto::SigningInput(); + input.set_signing_mode(Cosmos::Proto::JSON); // obsolete + input.set_account_number(125798); + input.set_sequence(0); + input.set_chain_id("crypto-org-chain-mainnet-1"); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(Address1); + message.set_to_address(Address2); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("basecro"); + amountOfTx->set_amount("100000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("basecro"); + amountOfFee->set_amount("5000"); + + auto privateKey = parse_hex(PrivateKey1); + input.set_private_key(privateKey.data(), privateKey.size()); + + Cosmos::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeCryptoOrg); + + assertJSONEqual(output.json(), R"( + { + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "5000", + "denom": "basecro" + } + ], + "gas": "200000" + }, + "memo": "", + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "amount": [ + { + "amount": "100000000", + "denom": "basecro" + } + ], + "from_address": "cro1ctwtcwpgksky988dhth6jslxveumgu0d45zgf0", + "to_address": "cro1xpahy6c7wldxacv6ld99h435mhvfnsup24vcus" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A4gxsGFiPn6L5Z2IjHEISkXI0IkwfL9exV3GLB171Wvj" + }, + "signature": "5+5rSFFg0FE9cTklQWQHNktBDJsz7UCnMSgF0t0+gYcrIhEWUyTtibXaHZQbKAAaciJ1BkHXYREjU55VswByVg==" + } + ] + } + } + )"); + EXPECT_EQ(hex(output.signature()), "e7ee6b485160d0513d713925416407364b410c9b33ed40a7312805d2dd3e81872b2211165324ed89b5da1d941b28001a7222750641d7611123539e55b3007256"); + EXPECT_EQ(output.serialized(), ""); + EXPECT_EQ(output.error_message(), ""); + + /// https://crypto.org/explorer/tx/DDCCE4052040B05914CADEFE78C0A75BE363AE39504E7EF6B2EDB8A9072AD44B + /// curl -H 'Content-Type: application/json' --data-binary '{"mode":"block","tx":{"fee": ... }}' https://mainnet.crypto.org:1317/txs +} diff --git a/tests/chains/Cosmos/CryptoOrg/TWCoinTypeTests.cpp b/tests/chains/Cosmos/CryptoOrg/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..6d301f20534 --- /dev/null +++ b/tests/chains/Cosmos/CryptoOrg/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWCryptoorgCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeCryptoOrg)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("D87D2EB46B21466886EE149C1DEA3AE6C2E019C7D8C24FA1533A95439DDCE1E2")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeCryptoOrg, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("cro10wrflcdc4pys9vvgqm98yg7yv5ltj7d3xehent")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeCryptoOrg, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeCryptoOrg)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeCryptoOrg)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeCryptoOrg), 8); + ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeCryptoOrg)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeCryptoOrg)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeCryptoOrg)); + assertStringsEqual(symbol, "CRO"); + assertStringsEqual(txUrl, "https://crypto.org/explorer/tx/D87D2EB46B21466886EE149C1DEA3AE6C2E019C7D8C24FA1533A95439DDCE1E2"); + assertStringsEqual(accUrl, "https://crypto.org/explorer/account/cro10wrflcdc4pys9vvgqm98yg7yv5ltj7d3xehent"); + assertStringsEqual(id, "cryptoorg"); + assertStringsEqual(name, "Crypto.org"); +} diff --git a/tests/chains/Cosmos/FetchAI/TWAnyAddressTests.cpp b/tests/chains/Cosmos/FetchAI/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..1a0f8862c88 --- /dev/null +++ b/tests/chains/Cosmos/FetchAI/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gFetchAIAddr = "fetch1mry47pkga5tdswtluy0m8teslpalkdq0due27z"; +static const std::string gFetchAIHrp = "fetch"; + +TEST(TWFetchAIAnyAddress, AllFetchAIAddressTests) { + CosmosAddressParameters parameters{.hrp = gFetchAIHrp, .coinType = TWCoinTypeFetchAI, .address = gFetchAIAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/FetchAI/TWCoinTypeTests.cpp b/tests/chains/Cosmos/FetchAI/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..2653dc3ab4d --- /dev/null +++ b/tests/chains/Cosmos/FetchAI/TWCoinTypeTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + TEST(TWFetchAICoinType, TWCoinType) { + const auto coin = TWCoinTypeFetchAI; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("7EB4F6C26809BA047F81CEFD0889775AC8522B7B8AF559B436083BE7039C5EA6")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("fetch1t3qet68dr0qkmrjtq89lrx837qa2t05265qy6s")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "fetchai"); + assertStringsEqual(name, "Fetch AI"); + assertStringsEqual(symbol, "FET"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "fetchhub-4"); + assertStringsEqual(txUrl, "https://www.mintscan.io/fetchai/txs/7EB4F6C26809BA047F81CEFD0889775AC8522B7B8AF559B436083BE7039C5EA6"); + assertStringsEqual(accUrl, "https://www.mintscan.io/fetchai/account/fetch1t3qet68dr0qkmrjtq89lrx837qa2t05265qy6s"); + } +} diff --git a/tests/chains/Cosmos/Juno/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Juno/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..0256957b57f --- /dev/null +++ b/tests/chains/Cosmos/Juno/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gJunoAddr = "juno1mry47pkga5tdswtluy0m8teslpalkdq0gnn4mf"; +static const std::string gJunoHrp = "juno"; + +TEST(TWJunoAnyAddress, AllJunoAddressTests) { + CosmosAddressParameters parameters{.hrp = gJunoHrp, .coinType = TWCoinTypeJuno, .address = gJunoAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Juno/TWAnySignerTests.cpp b/tests/chains/Cosmos/Juno/TWAnySignerTests.cpp new file mode 100644 index 00000000000..3fcb6facd0d --- /dev/null +++ b/tests/chains/Cosmos/Juno/TWAnySignerTests.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Cosmos/Address.h" +#include "HexCoding.h" +#include "proto/Cosmos.pb.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::Cosmos::tests { + +TEST(TWAnySignerJuno, Sign) { + auto privateKey = parse_hex("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433"); + Proto::SigningInput input; + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(376606); + input.set_chain_id("juno-1"); + input.set_memo(""); + input.set_sequence(0); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address("juno1mry47pkga5tdswtluy0m8teslpalkdq0gnn4mf"); + message.set_to_address("juno1mry47pkga5tdswtluy0m8teslpalkdq0gnn4mf"); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("ujuno"); + amountOfTx->set_amount("10000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(80000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("ujuno"); + amountOfFee->set_amount("1000"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeJuno); + + EXPECT_EQ(output.error(), Common::Proto::OK); + + // https://www.mintscan.io/juno/txs/3DCE6AAF19657BCF11D44FD6BE124D57B44E04CA34851DE0ECCE619F70ECC46F + auto expectedJson = R"( + { + "mode": "BROADCAST_MODE_BLOCK", + "tx_bytes": "Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKK2p1bm8xbXJ5NDdwa2dhNXRkc3d0bHV5MG04dGVzbHBhbGtkcTBnbm40bWYSK2p1bm8xbXJ5NDdwa2dhNXRkc3d0bHV5MG04dGVzbHBhbGtkcTBnbm40bWYaDgoFdWp1bm8SBTEwMDAwEmUKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLL/bXkcokzIilOYM8Ig9Q99DHhCJ0p7LRHqebVUEWq5RIECgIIARITCg0KBXVqdW5vEgQxMDAwEIDxBBpABrA2SUNtur1XqAIzNjM4UYtFylKARkfMd2YJUi11qqMkX0rZfmHrELL+QqjERn0o3vsR231fmPGJe4P0Isjwjw==" + })"; + assertJSONEqual(output.serialized(), expectedJson); + EXPECT_EQ(hex(output.signature()), "06b03649436dbabd57a80233363338518b45ca52804647cc776609522d75aaa3245f4ad97e61eb10b2fe42a8c4467d28defb11db7d5f98f1897b83f422c8f08f"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error_message(), ""); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Juno/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Juno/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..e6683195b92 --- /dev/null +++ b/tests/chains/Cosmos/Juno/TWCoinTypeTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + TEST(TWJunoCoinType, TWCoinType) { + const auto coin = TWCoinTypeJuno; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("3DCE6AAF19657BCF11D44FD6BE124D57B44E04CA34851DE0ECCE619F70ECC46F")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("juno1mry47pkga5tdswtluy0m8teslpalkdq0gnn4mf")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "juno"); + assertStringsEqual(name, "Juno"); + assertStringsEqual(symbol, "JUNO"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "juno-1"); + assertStringsEqual(txUrl, "https://www.mintscan.io/juno/txs/3DCE6AAF19657BCF11D44FD6BE124D57B44E04CA34851DE0ECCE619F70ECC46F"); + assertStringsEqual(accUrl, "https://www.mintscan.io/juno/account/juno1mry47pkga5tdswtluy0m8teslpalkdq0gnn4mf"); + } +} diff --git a/tests/chains/Cosmos/Kava/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Kava/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..25b8079bad6 --- /dev/null +++ b/tests/chains/Cosmos/Kava/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWKavaCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeKava)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("2988DF83FCBFAA38179D583A96734CBD071541D6768221BB23111BC8136D5E6A")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeKava, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("kava1xd39avn2f008jmvua0eupg39zsp2xn3wf802vn")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeKava, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeKava)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeKava)); + const auto chainId = WRAPS(TWCoinTypeChainId(TWCoinTypeKava)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeKava), 6); + ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeKava)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeKava)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeKava)); + assertStringsEqual(chainId, "kava_2222-10"); + assertStringsEqual(symbol, "KAVA"); + assertStringsEqual(txUrl, "https://mintscan.io/kava/txs/2988DF83FCBFAA38179D583A96734CBD071541D6768221BB23111BC8136D5E6A"); + assertStringsEqual(accUrl, "https://mintscan.io/kava/account/kava1xd39avn2f008jmvua0eupg39zsp2xn3wf802vn"); + assertStringsEqual(id, "kava"); + assertStringsEqual(name, "Kava"); +} diff --git a/tests/chains/Cosmos/Kujira/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Kujira/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..803e8c6e2cf --- /dev/null +++ b/tests/chains/Cosmos/Kujira/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gKujiraAddr = "kujira1mry47pkga5tdswtluy0m8teslpalkdq00fjk3l"; +static const std::string gKujiraHrp = "kujira"; + +TEST(TWKujiraAnyAddress, AllKujiraAddressTests) { + CosmosAddressParameters parameters{.hrp = gKujiraHrp, .coinType = TWCoinTypeKujira, .address = gKujiraAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Kujira/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Kujira/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..61583919905 --- /dev/null +++ b/tests/chains/Cosmos/Kujira/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWKujiraCoinType, TWCoinType) { + const auto coin = TWCoinTypeKujira; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("2F5D1B1E0041A86B0590AAD2ED028693E93137A3EA1E614D59FE9B02261BC235")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("kujira13c90ger64wc26s8399rvazceqy273u3n84kgyp")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "kujira"); + assertStringsEqual(name, "Kujira"); + assertStringsEqual(symbol, "KUJI"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "kaiyo-1"); + assertStringsEqual(txUrl, "https://www.mintscan.io/kujira/txs/2F5D1B1E0041A86B0590AAD2ED028693E93137A3EA1E614D59FE9B02261BC235"); + assertStringsEqual(accUrl, "https://www.mintscan.io/kujira/account/kujira13c90ger64wc26s8399rvazceqy273u3n84kgyp"); +} diff --git a/tests/chains/Cosmos/Mars/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Mars/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..16fd4812c92 --- /dev/null +++ b/tests/chains/Cosmos/Mars/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gMarsAddr = "mars1mry47pkga5tdswtluy0m8teslpalkdq0rufhfw"; +static const std::string gMarsHrp = "mars"; + +TEST(TWMarsAnyAddress, AllMarsAddressTests) { + CosmosAddressParameters parameters{.hrp = gMarsHrp, .coinType = TWCoinTypeMars, .address = gMarsAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Mars/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Mars/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..4a21d8a6b4d --- /dev/null +++ b/tests/chains/Cosmos/Mars/TWCoinTypeTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + TEST(TWMarsCoinType, TWCoinType) { + const auto coin = TWCoinTypeMars; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("C12120760C71189A678739E0F1FD4EFAF2C29EA660B57A359AC728F89FAA7528")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("mars1nnjy6nct405pzfaqjm3dsyw0pf0kyw72vhw4pr")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "mars"); + assertStringsEqual(name, "Mars Hub"); + assertStringsEqual(symbol, "MARS"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "mars-1"); + assertStringsEqual(txUrl, "https://www.mintscan.io/mars-protocol/txs/C12120760C71189A678739E0F1FD4EFAF2C29EA660B57A359AC728F89FAA7528"); + assertStringsEqual(accUrl, "https://www.mintscan.io/mars-protocol/account/mars1nnjy6nct405pzfaqjm3dsyw0pf0kyw72vhw4pr"); + } +} diff --git a/tests/chains/Cosmos/NativeCanto/TWAnyAddressTests.cpp b/tests/chains/Cosmos/NativeCanto/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..cfc4a2f6421 --- /dev/null +++ b/tests/chains/Cosmos/NativeCanto/TWAnyAddressTests.cpp @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gNativeCantoAddr = "canto14py36sx57ud82t9yrks9z6hdsrpn5x6kl5l2ap"; +static const std::string gNativeCantoHrp = "canto"; + +TEST(TWNativeCantoAnyAddress, AllNativeCantoAddressTests) { + CosmosAddressParameters parameters{.hrp = gNativeCantoHrp, + .coinType = TWCoinTypeNativeCanto, + .address = gNativeCantoAddr, + .standaloneChain = false, + .publicKeyType = TWPublicKeyTypeSECP256k1Extended, + .privKey = "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed", + .publicKey = "04868e7e1634417db2adfd9fe38205bfa0fea01898a7fd30565d13f7056a37c065211845f6e553524c2c1611af9712ac02b7a3b439c9f0cfcadfd81a2c86cc0ab8", + }; + TestCosmosAddressParameters(parameters); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/NativeCanto/TWCoinTypeTests.cpp b/tests/chains/Cosmos/NativeCanto/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..cc282cd0e1f --- /dev/null +++ b/tests/chains/Cosmos/NativeCanto/TWCoinTypeTests.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +namespace TW::NativeCanto::tests { + +TEST(TWCantoCoinType, TWCoinTypeNativeCanto) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNativeCanto)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("A16C211C83AD1E684DE46F694FAAC17D8465C864BD7385A81EC062CDE0638811")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNativeCanto, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("canto17xpfvakm2amg962yls6f84z3kell8c5ljcjw34")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNativeCanto, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNativeCanto)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNativeCanto)); + const auto chainId = WRAPS(TWCoinTypeChainId(TWCoinTypeNativeCanto)); + + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNativeCanto), 18); + ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeNativeCanto)); + + assertStringsEqual(symbol, "CANTO"); + assertStringsEqual(txUrl, "https://mintscan.io/canto/txs/A16C211C83AD1E684DE46F694FAAC17D8465C864BD7385A81EC062CDE0638811"); + assertStringsEqual(accUrl, "https://mintscan.io/canto/account/canto17xpfvakm2amg962yls6f84z3kell8c5ljcjw34"); + assertStringsEqual(id, "nativecanto"); + assertStringsEqual(name, "NativeCanto"); + assertStringsEqual(chainId, "canto_7700-1"); +} + +} // namespace TW::NativeCanto::tests diff --git a/tests/chains/Cosmos/NativeEvmos/TWAnyAddressTests.cpp b/tests/chains/Cosmos/NativeEvmos/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..3dbb62b8cfd --- /dev/null +++ b/tests/chains/Cosmos/NativeEvmos/TWAnyAddressTests.cpp @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gNativeEvmosAddr = "evmos14py36sx57ud82t9yrks9z6hdsrpn5x6k0r05np"; +static const std::string gNativeEvmosHrp = "evmos"; + +TEST(TWNativeEvmosAnyAddress, AllNativeEvmosAddressTests) { + CosmosAddressParameters parameters{.hrp = gNativeEvmosHrp, + .coinType = TWCoinTypeNativeEvmos, + .address = gNativeEvmosAddr, + .standaloneChain = false, + .publicKeyType = TWPublicKeyTypeSECP256k1Extended, + .privKey = "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed", + .publicKey = "04868e7e1634417db2adfd9fe38205bfa0fea01898a7fd30565d13f7056a37c065211845f6e553524c2c1611af9712ac02b7a3b439c9f0cfcadfd81a2c86cc0ab8", + }; + TestCosmosAddressParameters(parameters); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/NativeEvmos/TWCoinTypeTests.cpp b/tests/chains/Cosmos/NativeEvmos/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..052ffcf1038 --- /dev/null +++ b/tests/chains/Cosmos/NativeEvmos/TWCoinTypeTests.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +namespace TW::NativeEvmos::tests { + +TEST(TWEvmosCoinType, TWCoinTypeNativeEvmos) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNativeEvmos)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("A16C211C83AD1E684DE46F694FAAC17D8465C864BD7385A81EC062CDE0638811")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNativeEvmos, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNativeEvmos, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNativeEvmos)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNativeEvmos)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNativeEvmos), 18); + ASSERT_EQ(TWBlockchainNativeEvmos, TWCoinTypeBlockchain(TWCoinTypeNativeEvmos)); + + assertStringsEqual(symbol, "EVMOS"); + assertStringsEqual(txUrl, "https://mintscan.io/evmos/txs/A16C211C83AD1E684DE46F694FAAC17D8465C864BD7385A81EC062CDE0638811"); + assertStringsEqual(accUrl, "https://mintscan.io/evmos/account/evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34"); + assertStringsEqual(id, "nativeevmos"); + assertStringsEqual(name, "Native Evmos"); +} + +} // namespace TW::NativeEvmos::tests diff --git a/tests/chains/Cosmos/NativeInjective/SignerTests.cpp b/tests/chains/Cosmos/NativeInjective/SignerTests.cpp new file mode 100644 index 00000000000..4b1ce6b5ec2 --- /dev/null +++ b/tests/chains/Cosmos/NativeInjective/SignerTests.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Base64.h" +#include "proto/Cosmos.pb.h" +#include "Cosmos/Address.h" +#include "TestUtilities.h" +#include "TrustWalletCore/TWAnySigner.h" + +#include + +namespace TW::Cosmos::nativeInjective::tests { + +TEST(NativeInjectiveSigner, Sign) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(17396); + input.set_chain_id("injective-1"); + input.set_sequence(1); + + Address fromAddress; + Address toAddress; + EXPECT_TRUE(Address::decode("inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a", fromAddress)); + EXPECT_TRUE(Address::decode("inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd", toAddress)); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("inj"); + amountOfTx->set_amount("10000000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(110000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("inj"); + amountOfFee->set_amount("100000000000000"); + + auto privateKey = parse_hex("9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeNativeInjective); + + // https://www.mintscan.io/injective/txs/135DD2C4A1910E4334A9C0F15125DA992E724EBF23FEB9638FCB71218BB064A5 + assertJSONEqual(output.serialized(), "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Co8BCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKKmluajEzdTZnN3ZxZ3cwNzRtZ21mMnplMmNhZHp2a3o5c25sd2NydHE4YRIqaW5qMXhtcGtteHI0YXMwMGVtMjN0YzJ6Z211eXkyZ3I0aDN3Z2NsNnZkGhIKA2luahILMTAwMDAwMDAwMDASngEKfgp0Ci0vaW5qZWN0aXZlLmNyeXB0by52MWJldGExLmV0aHNlY3AyNTZrMS5QdWJLZXkSQwpBBFoMa4O4vZgn5QcnDK20mbfjqQlSRvaiITKB94PYd8mLJWdCdBsGOfMXdo/k9MJ2JmDCESKDp2hdgVUH3uMikXMSBAoCCAEYARIcChYKA2luahIPMTAwMDAwMDAwMDAwMDAwELDbBhpAx2vkplmzeK7n3puCFGPWhLd0l/ZC/CYkGl+stH+3S3hiCvIe7uwwMpUlNaSwvT8HwF1kNUp+Sx2m0Uo1x5xcFw==\"}"); +} + +} diff --git a/tests/chains/Cosmos/NativeInjective/TWAnyAddressTests.cpp b/tests/chains/Cosmos/NativeInjective/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..9dd4818c87c --- /dev/null +++ b/tests/chains/Cosmos/NativeInjective/TWAnyAddressTests.cpp @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gNativeInjectiveAddr = "inj14py36sx57ud82t9yrks9z6hdsrpn5x6k8tf7m3"; +static const std::string gNativeInjectiveHrp = "inj"; + +TEST(TWNativeInjectiveAnyAddress, AllNativeInjectiveAddressTests) { + CosmosAddressParameters parameters{.hrp = gNativeInjectiveHrp, + .coinType = TWCoinTypeNativeInjective, + .address = gNativeInjectiveAddr, + .standaloneChain = false, + .publicKeyType = TWPublicKeyTypeSECP256k1Extended, + .privKey = "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed", + .publicKey = "04868e7e1634417db2adfd9fe38205bfa0fea01898a7fd30565d13f7056a37c065211845f6e553524c2c1611af9712ac02b7a3b439c9f0cfcadfd81a2c86cc0ab8", + }; + TestCosmosAddressParameters(parameters); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/NativeInjective/TWCoinTypeTests.cpp b/tests/chains/Cosmos/NativeInjective/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..6a120226d4f --- /dev/null +++ b/tests/chains/Cosmos/NativeInjective/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWNativeInjectiveCoinType, TWCoinType) { + const auto coin = TWCoinTypeNativeInjective; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("C5F6A4FF9DF1AE9FF543D2CEBD8E3E9B04290B2445F9D91D7707EDBF4B7EE16B")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "nativeinjective"); + assertStringsEqual(name, "Native Injective"); + assertStringsEqual(symbol, "INJ"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainNativeInjective); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "injective-1"); + assertStringsEqual(txUrl, "https://www.mintscan.io/injective/txs/C5F6A4FF9DF1AE9FF543D2CEBD8E3E9B04290B2445F9D91D7707EDBF4B7EE16B"); + assertStringsEqual(accUrl, "https://www.mintscan.io/injective/account/inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd"); +} diff --git a/tests/chains/Cosmos/NativeInjective/TransactionCompilerTests.cpp b/tests/chains/Cosmos/NativeInjective/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..2bf1a12785a --- /dev/null +++ b/tests/chains/Cosmos/NativeInjective/TransactionCompilerTests.cpp @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base64.h" +#include "HexCoding.h" +#include "proto/Cosmos.pb.h" +#include "proto/TransactionCompiler.pb.h" +#include "TrustWalletCore/TWAnySigner.h" +#include "TestUtilities.h" +#include "TransactionCompiler.h" + +namespace TW::Cosmos::nativeInjective::tests { + +TEST(NativeInjectiveCompiler, CompileWithSignatures) { + // Successfully broadcasted: https://www.mintscan.io/injective/transactions/B77D61590353C4AEDEAE2BBFF9E406DCF90E8D3A1A723BF22860F1E0A2617058 + + const auto coin = TWCoinTypeNativeInjective; + TW::Cosmos::Proto::SigningInput input; + + PrivateKey privateKey = + PrivateKey(parse_hex("727513ec3c54eb6fae24f2ff756bbc4c89b82945c6538bbd173613ae3de719d3")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + /// Step 1: Prepare transaction input (protobuf) + input.set_account_number(88701); + input.set_chain_id("injective-1"); + input.set_memo(""); + input.set_sequence(0); + + PublicKey publicKey = privateKey.getPublicKey(TWCoinTypePublicKeyType(coin)); + const auto pubKeyBz = publicKey.bytes; + ASSERT_EQ(hex(pubKeyBz), "04088ac2919987d927368cb2be2ade44cd0ed3616745a9699cae264b3fc5a7c3607d99f441b8340990ee990cb3eaf560f1f0bafe600c7e94a4be8392166984f728"); + input.set_public_key(pubKeyBz.data(), pubKeyBz.size()); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address("inj1d0jkrsd09c7pule43y3ylrul43lwwcqaky8w8c"); + message.set_to_address("inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd"); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("inj"); + amountOfTx->set_amount("10000000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(110000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("inj"); + amountOfFee->set_amount("100000000000000"); + + /// Step 2: Obtain protobuf preimage hash + input.set_signing_mode(TW::Cosmos::Proto::Protobuf); + auto protoInputString = input.SerializeAsString(); + auto protoInputData = TW::Data(protoInputString.begin(), protoInputString.end()); + + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, protoInputData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + auto preImage = preSigningOutput.data(); + auto preImageHash = preSigningOutput.data_hash(); + + EXPECT_EQ( + hex(preImage), + "0a8f010a8c010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e64126c0a2a696e6a3164306a6b7273643039633770756c6534337933796c72756c34336c77776371616b7938773863122a696e6a31786d706b6d78723461733030656d32337463327a676d7579793267723468337767636c3676641a120a03696e6a120b3130303030303030303030129c010a7c0a740a2d2f696e6a6563746976652e63727970746f2e763162657461312e657468736563703235366b312e5075624b657912430a4104088ac2919987d927368cb2be2ade44cd0ed3616745a9699cae264b3fc5a7c3607d99f441b8340990ee990cb3eaf560f1f0bafe600c7e94a4be8392166984f72812040a020801121c0a160a03696e6a120f31303030303030303030303030303010b0db061a0b696e6a6563746976652d3120fdb405"); + EXPECT_EQ(hex(preImageHash), + "57dc10c3d1893ff16e1f5a47fa4b2e96f37b9c57d98a42d88c971debb4947ec9"); + + + auto expectedTx = R"({"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"Co8BCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKKmluajFkMGprcnNkMDljN3B1bGU0M3kzeWxydWw0M2x3d2NxYWt5OHc4YxIqaW5qMXhtcGtteHI0YXMwMGVtMjN0YzJ6Z211eXkyZ3I0aDN3Z2NsNnZkGhIKA2luahILMTAwMDAwMDAwMDASnAEKfAp0Ci0vaW5qZWN0aXZlLmNyeXB0by52MWJldGExLmV0aHNlY3AyNTZrMS5QdWJLZXkSQwpBBAiKwpGZh9knNoyyvireRM0O02FnRalpnK4mSz/Fp8NgfZn0Qbg0CZDumQyz6vVg8fC6/mAMfpSkvoOSFmmE9ygSBAoCCAESHAoWCgNpbmoSDzEwMDAwMDAwMDAwMDAwMBCw2wYaQPep7ApSEXC7VWbKlz08c6G2mxYtmc4CIFkYmZHsRAY3MzOU/xyedfrYTrEUOTlp8gmJsDbx3+0olJ6QbcAHdCE="})"; + Data signature; + + { + TW::Cosmos::Proto::SigningOutput output; + ANY_SIGN(input, coin); + assertJSONEqual( + output.serialized(), + expectedTx); + + signature = data(output.signature()); + EXPECT_EQ(hex(signature), + "f7a9ec0a521170bb5566ca973d3c73a1b69b162d99ce022059189991ec440637333394ff1c9e75fad84eb114393969f20989b036f1dfed28949e906dc0077421"); + + ASSERT_TRUE(publicKey.verify(signature, data(preImageHash.data()))); + } + + { + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, protoInputData, {signature}, {publicKey.bytes}); + Cosmos::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(output.serialized(), expectedTx); + EXPECT_EQ(hex(output.signature()), "f7a9ec0a521170bb5566ca973d3c73a1b69b162d99ce022059189991ec440637333394ff1c9e75fad84eb114393969f20989b036f1dfed28949e906dc0077421"); + } +} + +} diff --git a/tests/chains/Cosmos/Neutron/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Neutron/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..662f74bd756 --- /dev/null +++ b/tests/chains/Cosmos/Neutron/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gNeutronAddr = "neutron1mry47pkga5tdswtluy0m8teslpalkdq067evxj"; +static const std::string gNeutronHrp = "neutron"; + +TEST(TWNeutronAnyAddress, AllNeutronAddressTests) { + CosmosAddressParameters parameters{.hrp = gNeutronHrp, .coinType = TWCoinTypeNeutron, .address = gNeutronAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Neutron/TWAnySignerTests.cpp b/tests/chains/Cosmos/Neutron/TWAnySignerTests.cpp new file mode 100644 index 00000000000..bafa42a9c7d --- /dev/null +++ b/tests/chains/Cosmos/Neutron/TWAnySignerTests.cpp @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Cosmos/Address.h" +#include "HexCoding.h" +#include "proto/Cosmos.pb.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::Cosmos::tests { + +TEST(TWAnySignerNeutron, SignAirdropNeutron) { + auto privateKey = parse_hex("37f0af5bc20adb6832d39368a15492cd1e9e0cc1556d4317a5f75f9ccdf525ee"); + Proto::SigningInput input; + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(336); + input.set_chain_id("pion-1"); + input.set_memo(""); + input.set_sequence(0); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_execute_contract_generic(); + + const auto tokenContractAddress = "neutron1465d8udjudl6cd8kgdlh2s37p7q0cf9x7yveumqwqk6ng94qwnmq7n79qn"; + const auto txMessage = R"({"claim":{"address":"neutron19h42zjnls2tpmg6yylcg6nr56cjxcx35q6xt57", "proof":["404ae2093edcca979ccb6ae4a36689cebc9c2c6a2b00b106c5396b079bf6dcf5","282fee30a25a60904f54d4f74aee8fcf8dd2822799c43be733e18e15743d4ece","e10de4202fe6532329d0d463d9669f1b659920868b9ea87d6715bfd223a86a40","564b4122c6f98653153d8e09d5a5f659fa7ebea740aa6b689c94211f8a11cc4b"], "amount":"2000000"}})"; + + message.set_sender_address("neutron19h42zjnls2tpmg6yylcg6nr56cjxcx35q6xt57"); + message.set_contract_address(tokenContractAddress); + message.set_execute_msg(txMessage); + + auto& fee = *input.mutable_fee(); + fee.set_gas(666666); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("untrn"); + amountOfFee->set_amount("1000"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNeutron); + + // Successfully broadcasted: https://explorer.rs-testnet.polypore.xyz/pion-1/tx/28F25164B1E2556844C227819B1D5437960B7E91181B37460EC6792588FF7E4E + auto expectedJson = R"( + { + "mode":"BROADCAST_MODE_BLOCK", + "tx_bytes":"CpQECpEECiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QS6AMKLm5ldXRyb24xOWg0MnpqbmxzMnRwbWc2eXlsY2c2bnI1NmNqeGN4MzVxNnh0NTcSQm5ldXRyb24xNDY1ZDh1ZGp1ZGw2Y2Q4a2dkbGgyczM3cDdxMGNmOXg3eXZldW1xd3FrNm5nOTRxd25tcTduNzlxbhrxAnsiY2xhaW0iOnsiYWRkcmVzcyI6Im5ldXRyb24xOWg0MnpqbmxzMnRwbWc2eXlsY2c2bnI1NmNqeGN4MzVxNnh0NTciLCAicHJvb2YiOlsiNDA0YWUyMDkzZWRjY2E5NzljY2I2YWU0YTM2Njg5Y2ViYzljMmM2YTJiMDBiMTA2YzUzOTZiMDc5YmY2ZGNmNSIsIjI4MmZlZTMwYTI1YTYwOTA0ZjU0ZDRmNzRhZWU4ZmNmOGRkMjgyMjc5OWM0M2JlNzMzZTE4ZTE1NzQzZDRlY2UiLCJlMTBkZTQyMDJmZTY1MzIzMjlkMGQ0NjNkOTY2OWYxYjY1OTkyMDg2OGI5ZWE4N2Q2NzE1YmZkMjIzYTg2YTQwIiwiNTY0YjQxMjJjNmY5ODY1MzE1M2Q4ZTA5ZDVhNWY2NTlmYTdlYmVhNzQwYWE2YjY4OWM5NDIxMWY4YTExY2M0YiJdLCAiYW1vdW50IjoiMjAwMDAwMCJ9fRJlCk4KRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECqPwhojhpWpB3vDr8R+qyUnDkcK3BPxS35F8OrHPq5WwSBAoCCAESEwoNCgV1bnRybhIEMTAwMBCq2CgaQMIEXC8zyuuXWuIeX7dZBBzxMjmheOP1ONitBrVZdwmuQUgClmwhOdW0JwRe8CJ5NUKqtDYZjKFAPKGEWQ2veDs=" + })"; + assertJSONEqual(output.serialized(), expectedJson); + EXPECT_EQ(hex(output.signature()), "c2045c2f33caeb975ae21e5fb759041cf13239a178e3f538d8ad06b5597709ae414802966c2139d5b427045ef022793542aab436198ca1403ca184590daf783b"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error_message(), ""); +} + +TEST(TWAnySignerNeutron, SignWithdrawAirdropNeutron) { + auto privateKey = parse_hex("37f0af5bc20adb6832d39368a15492cd1e9e0cc1556d4317a5f75f9ccdf525ee"); + Proto::SigningInput input; + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(336); + input.set_chain_id("pion-1"); + input.set_memo(""); + input.set_sequence(1); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_execute_contract_generic(); + + const auto tokenContractAddress = "neutron1dv49y7afpq573yyk6zj2z4rn7gqh689plhtrf6223kqs8ee3tq9spqpuf2"; + const auto txMessage = R"({"withdraw":{"amount":"313468"}})"; + + message.set_sender_address("neutron19h42zjnls2tpmg6yylcg6nr56cjxcx35q6xt57"); + message.set_contract_address(tokenContractAddress); + message.set_execute_msg(txMessage); + + auto& fee = *input.mutable_fee(); + fee.set_gas(666666); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("untrn"); + amountOfFee->set_amount("1000"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNeutron); + + // Successfully broadcasted: https://explorer.rs-testnet.polypore.xyz/pion-1/tx/28F25164B1E2556844C227819B1D5437960B7E91181B37460EC6792588FF7E4E + auto expectedJson = R"( + { + "mode":"BROADCAST_MODE_BLOCK", + "tx_bytes":"CsIBCr8BCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QSlgEKLm5ldXRyb24xOWg0MnpqbmxzMnRwbWc2eXlsY2c2bnI1NmNqeGN4MzVxNnh0NTcSQm5ldXRyb24xZHY0OXk3YWZwcTU3M3l5azZ6ajJ6NHJuN2dxaDY4OXBsaHRyZjYyMjNrcXM4ZWUzdHE5c3BxcHVmMhogeyJ3aXRoZHJhdyI6eyJhbW91bnQiOiIzMTM0NjgifX0SZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAqj8IaI4aVqQd7w6/EfqslJw5HCtwT8Ut+RfDqxz6uVsEgQKAggBGAESEwoNCgV1bnRybhIEMTAwMBCq2CgaQN/zzFyDC2i/lvQUNJ9Y24sWlDsAx2pa+p7KPAIiya+TNrsVrgW9jq83gi8OPhS/+/47hPH8LYOR41TijWnLgDA=" + })"; + assertJSONEqual(output.serialized(), expectedJson); + EXPECT_EQ(hex(output.signature()), "dff3cc5c830b68bf96f414349f58db8b16943b00c76a5afa9eca3c0222c9af9336bb15ae05bd8eaf37822f0e3e14bffbfe3b84f1fc2d8391e354e28d69cb8030"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error_message(), ""); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Neutron/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Neutron/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..0c5a3aa3978 --- /dev/null +++ b/tests/chains/Cosmos/Neutron/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWNeutronCoinType, TWCoinType) { + const auto coin = TWCoinTypeNeutron; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("E18BA087009A05EB6A15A22FE30BA99379B909F74A74120E6F92B9882C45F0D7")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("neutron1pm4af8pcurxssdxztqw9rexx5f8zfq7uzqfmy8")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "neutron"); + assertStringsEqual(name, "Neutron"); + assertStringsEqual(symbol, "NTRN"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "neutron-1"); + assertStringsEqual(txUrl, "https://www.mintscan.io/neutron/txs/E18BA087009A05EB6A15A22FE30BA99379B909F74A74120E6F92B9882C45F0D7"); + assertStringsEqual(accUrl, "https://www.mintscan.io/neutron/account/neutron1pm4af8pcurxssdxztqw9rexx5f8zfq7uzqfmy8"); +} diff --git a/tests/chains/Cosmos/Noble/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Noble/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..4be942ba178 --- /dev/null +++ b/tests/chains/Cosmos/Noble/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gNobleAddr = "noble1mry47pkga5tdswtluy0m8teslpalkdq0kz9xym"; +static const std::string gNobleHrp = "noble"; + +TEST(TWNobleAnyAddress, AllNobleAddressTests) { + CosmosAddressParameters parameters{.hrp = gNobleHrp, .coinType = TWCoinTypeNoble, .address = gNobleAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Noble/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Noble/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..f63dbdff7ee --- /dev/null +++ b/tests/chains/Cosmos/Noble/TWCoinTypeTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + TEST(TWNobleCoinType, TWCoinType) { + const auto coin = TWCoinTypeNoble; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("EA231079975A058FEC28EF372B445763918C098DE033E868E2E035F3F98C59C7")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("noble1y2egevq0nyzm7w6a9kpxkw86eqytcvxpwsp6d9")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "noble"); + assertStringsEqual(name, "Noble"); + assertStringsEqual(symbol, "USDC"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "noble-1"); + assertStringsEqual(txUrl, "https://www.mintscan.io/noble/txs/EA231079975A058FEC28EF372B445763918C098DE033E868E2E035F3F98C59C7"); + assertStringsEqual(accUrl, "https://www.mintscan.io/noble/account/noble1y2egevq0nyzm7w6a9kpxkw86eqytcvxpwsp6d9"); + } +} diff --git a/tests/chains/Cosmos/Osmosis/SignerTests.cpp b/tests/chains/Cosmos/Osmosis/SignerTests.cpp new file mode 100644 index 00000000000..91935d871e5 --- /dev/null +++ b/tests/chains/Cosmos/Osmosis/SignerTests.cpp @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Cosmos/Address.h" +#include "HexCoding.h" +#include "PublicKey.h" +#include "proto/Cosmos.pb.h" +#include "TrustWalletCore/TWAnySigner.h" +#include "TestUtilities.h" + +#include + +namespace TW::Cosmos::tests { + +TEST(OsmosisSigner, SignTransfer_81B4) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(124703); + input.set_chain_id("osmosis-1"); + input.set_memo(""); + input.set_sequence(0); + + Address fromAddress; + Address toAddress; + EXPECT_TRUE(Address::decode("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5", fromAddress)); + EXPECT_TRUE(Address::decode("osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn", toAddress)); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("uosmo"); + amountOfTx->set_amount("99800"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uosmo"); + amountOfFee->set_amount("200"); + + auto privateKey = parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeOsmosis); + + // https://www.mintscan.io/osmosis/txs/81B4F01BDE72AF7FF4536E5D7E66EB218E9FC9ACAA7C5EB5DB237DD0595D5F5F + // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Co0B...rYVj", "mode": "BROADCAST_MODE_BLOCK"}' https://lcd-osmosis.keplr.app/cosmos/tx/v1beta1/txs + + assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKK29zbW8xbWt5Njljbjhla3R3eTA4NDV2ZWM5dXBzZHBoa3R4dDBlbjk3ZjUSK29zbW8xOHMwaGRuc2xsZ2NjbHdldTlheW13NG5na3RyMmswcmt2bjdqbW4aDgoFdW9zbW8SBTk5ODAwEmQKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLs71zkN6MCxn+VRo3ksx826RH0Z9fmpStBweE+HVY2SRIECgIIARISCgwKBXVvc21vEgMyMDAQwJoMGkAMY//Md5GRUR4lVZhk558hFS3kii9QZYoYKfg4+ac/xgNeyoiEweVDhcmEvlH1orVwjLUOnYs4ly2a/yIurYVj\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "0c63ffcc779191511e25559864e79f21152de48a2f50658a1829f838f9a73fc6035eca8884c1e54385c984be51f5a2b5708cb50e9d8b38972d9aff222ead8563"); + EXPECT_EQ(output.error_message(), ""); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Osmosis/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Osmosis/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..7979390c7a3 --- /dev/null +++ b/tests/chains/Cosmos/Osmosis/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gOsmosisAddr = "osmo1mry47pkga5tdswtluy0m8teslpalkdq0k6r728"; +static const std::string gOsmosisHrp = "osmo"; + +TEST(TWOsmosisAnyAddress, AllOsmosisAddressTests) { + CosmosAddressParameters parameters{.hrp = gOsmosisHrp, .coinType = TWCoinTypeOsmosis, .address = gOsmosisAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Osmosis/TWAnySignerTests.cpp b/tests/chains/Cosmos/Osmosis/TWAnySignerTests.cpp new file mode 100644 index 00000000000..41dbaaf789c --- /dev/null +++ b/tests/chains/Cosmos/Osmosis/TWAnySignerTests.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Cosmos/Address.h" +#include "HexCoding.h" +#include "proto/Cosmos.pb.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::Cosmos::tests { + +TEST(TWAnySignerOsmosis, Sign) { + auto privateKey = parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"); + Proto::SigningInput input; + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(124703); + input.set_chain_id("osmosis-1"); + input.set_memo(""); + input.set_sequence(0); + input.set_private_key(privateKey.data(), privateKey.size()); + + Address fromAddress; + Address toAddress; + EXPECT_TRUE(Address::decode("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5", fromAddress)); + EXPECT_TRUE(Address::decode("osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn", toAddress)); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("uosmo"); + amountOfTx->set_amount("99800"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uosmo"); + amountOfFee->set_amount("200"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeOsmosis); + + // https://www.mintscan.io/osmosis/txs/81B4F01BDE72AF7FF4536E5D7E66EB218E9FC9ACAA7C5EB5DB237DD0595D5F5F + assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKK29zbW8xbWt5Njljbjhla3R3eTA4NDV2ZWM5dXBzZHBoa3R4dDBlbjk3ZjUSK29zbW8xOHMwaGRuc2xsZ2NjbHdldTlheW13NG5na3RyMmswcmt2bjdqbW4aDgoFdW9zbW8SBTk5ODAwEmQKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLs71zkN6MCxn+VRo3ksx826RH0Z9fmpStBweE+HVY2SRIECgIIARISCgwKBXVvc21vEgMyMDAQwJoMGkAMY//Md5GRUR4lVZhk558hFS3kii9QZYoYKfg4+ac/xgNeyoiEweVDhcmEvlH1orVwjLUOnYs4ly2a/yIurYVj\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "0c63ffcc779191511e25559864e79f21152de48a2f50658a1829f838f9a73fc6035eca8884c1e54385c984be51f5a2b5708cb50e9d8b38972d9aff222ead8563"); + EXPECT_EQ(output.json(), ""); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Osmosis/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Osmosis/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..ca043ca6fec --- /dev/null +++ b/tests/chains/Cosmos/Osmosis/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWOsmosisCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeOsmosis)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("5A6E50A6F2927E4B8C87BB094D5FBF15F1287429A09111806FC44B3CD86CACA8")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeOsmosis, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeOsmosis, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeOsmosis)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeOsmosis)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeOsmosis), 6); + ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeOsmosis)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeOsmosis)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeOsmosis)); + assertStringsEqual(symbol, "OSMO"); + assertStringsEqual(txUrl, "https://mintscan.io/osmosis/txs/5A6E50A6F2927E4B8C87BB094D5FBF15F1287429A09111806FC44B3CD86CACA8"); + assertStringsEqual(accUrl, "https://mintscan.io/osmosis/account/osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5"); + assertStringsEqual(id, "osmosis"); + assertStringsEqual(name, "Osmosis"); +} diff --git a/tests/chains/Cosmos/Persistence/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Persistence/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..3219bf90f55 --- /dev/null +++ b/tests/chains/Cosmos/Persistence/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gPersistenceAddr = "persistence1mry47pkga5tdswtluy0m8teslpalkdq0sdkaj3"; +static const std::string gPersistenceHrp = "persistence"; + +TEST(TWPersistenceAnyAddress, AllPersistenceAddressTests) { + CosmosAddressParameters parameters{.hrp = gPersistenceHrp, .coinType = TWCoinTypePersistence, .address = gPersistenceAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Persistence/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Persistence/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..0669a3af94d --- /dev/null +++ b/tests/chains/Cosmos/Persistence/TWCoinTypeTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + TEST(TWPersistenceCoinType, TWCoinType) { + const auto coin = TWCoinTypePersistence; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("BBD9DEE03A8D7538D8E7398217467F4A2B5690D15773E8A6442E6AEEEFA21E64")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("persistence10ys69560pqr6zmqam80g8s0smtjw6p3ugzmy3u")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "persistence"); + assertStringsEqual(name, "Persistence"); + assertStringsEqual(symbol, "XPRT"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "core-1"); + assertStringsEqual(txUrl, "https://www.mintscan.io/persistence/txs/BBD9DEE03A8D7538D8E7398217467F4A2B5690D15773E8A6442E6AEEEFA21E64"); + assertStringsEqual(accUrl, "https://www.mintscan.io/persistence/account/persistence10ys69560pqr6zmqam80g8s0smtjw6p3ugzmy3u"); + } +} diff --git a/tests/Cosmos/Protobuf/.gitignore b/tests/chains/Cosmos/Protobuf/.gitignore similarity index 100% rename from tests/Cosmos/Protobuf/.gitignore rename to tests/chains/Cosmos/Protobuf/.gitignore diff --git a/tests/Cosmos/Protobuf/Article.proto b/tests/chains/Cosmos/Protobuf/Article.proto similarity index 100% rename from tests/Cosmos/Protobuf/Article.proto rename to tests/chains/Cosmos/Protobuf/Article.proto diff --git a/tests/chains/Cosmos/Quasar/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Quasar/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..99b9eddc9eb --- /dev/null +++ b/tests/chains/Cosmos/Quasar/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gQuasarAddr = "quasar1mry47pkga5tdswtluy0m8teslpalkdq0sz2n3s"; +static const std::string gQuasarHrp = "quasar"; + +TEST(TWQuasarAnyAddress, AllQuasarAddressTests) { + CosmosAddressParameters parameters{.hrp = gQuasarHrp, .coinType = TWCoinTypeQuasar, .address = gQuasarAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Quasar/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Quasar/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..724a95988da --- /dev/null +++ b/tests/chains/Cosmos/Quasar/TWCoinTypeTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + TEST(TWQuasarCoinType, TWCoinType) { + const auto coin = TWCoinTypeQuasar; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("2898B89C98FE1E8CF1E05A37E4EE5EE5ED83FD957B0CAEE53DE39FC82BF1A033")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("quasar1cqu6w425slheul3jsmyt6q0ec0rs0w0ugkst3k")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "quasar"); + assertStringsEqual(name, "Quasar"); + assertStringsEqual(symbol, "QSR"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "quasar-1"); + assertStringsEqual(txUrl, "https://www.mintscan.io/quasar/txs/2898B89C98FE1E8CF1E05A37E4EE5EE5ED83FD957B0CAEE53DE39FC82BF1A033"); + assertStringsEqual(accUrl, "https://www.mintscan.io/quasar/account/quasar1cqu6w425slheul3jsmyt6q0ec0rs0w0ugkst3k"); + } +} diff --git a/tests/chains/Cosmos/Secret/SignerTests.cpp b/tests/chains/Cosmos/Secret/SignerTests.cpp new file mode 100644 index 00000000000..515419fd331 --- /dev/null +++ b/tests/chains/Cosmos/Secret/SignerTests.cpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Cosmos/Address.h" +#include "TrustWalletCore/TWAnySigner.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "proto/Cosmos.pb.h" +#include "PublicKey.h" +#include "TestUtilities.h" + +#include + +namespace TW::Cosmos::tests { + +TEST(SecretSigner, Sign) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(265538); + input.set_chain_id("secret-4"); + input.set_memo(""); + input.set_sequence(1); + + Address fromAddress; + Address toAddress; + EXPECT_TRUE(Address::decode("secret18mdrja40gfuftt5yx6tgj0fn5lurplezyp894y", fromAddress)); + EXPECT_TRUE(Address::decode("secret1rnq6hjfnalxeef87rmdeya3nu9dhpc7k9pujs3", toAddress)); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("uscrt"); + amountOfTx->set_amount("100000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(25000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uscrt"); + amountOfFee->set_amount("2500"); + + auto privateKey = parse_hex("87201512d132ef7a1e57f9e24905fbc24300bd73f676b5716182be5f3e39dada"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeSecret); + + // https://www.mintscan.io/secret/txs/01F4BD2458BF966F287533775C8D67BBC7CA7214CAEB1752D270A90223E9E82F + // curl -H 'Content-Type: application/json' --data-binary "{\"tx_bytes\":\"CpIB...c4o=\",\"mode\":\"BROADCAST_MODE_BLOCK\"}" https://scrt-lcd.blockpane.com/cosmos/tx/v1beta1/txs + + assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLXNlY3JldDE4bWRyamE0MGdmdWZ0dDV5eDZ0Z2owZm41bHVycGxlenlwODk0eRItc2VjcmV0MXJucTZoamZuYWx4ZWVmODdybWRleWEzbnU5ZGhwYzdrOXB1anMzGg8KBXVzY3J0EgYxMDAwMDASZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAkZqxdKMtPq2w0kGDGwWGejTAed0H7azPMHtrCX0XYZGEgQKAggBGAESEwoNCgV1c2NydBIEMjUwMBCowwEaQOcHd2gHpa5WKZ/5RRerEtrHlyDlojIEzUGhC9xMFgs7UQMWy+kTTN+NRf7zQ8rx3cPkIKeZhv0u1KRc8uRCc4o=\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "e707776807a5ae56299ff94517ab12dac79720e5a23204cd41a10bdc4c160b3b510316cbe9134cdf8d45fef343caf1ddc3e420a79986fd2ed4a45cf2e442738a"); + EXPECT_EQ(output.error_message(), ""); + EXPECT_EQ(output.json(), ""); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Secret/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Secret/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..945f5192010 --- /dev/null +++ b/tests/chains/Cosmos/Secret/TWAnyAddressTests.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gSecretAddr = "secret1l0cjpuwu09hwu4wdds7pljn83346x2c90d8h0l"; +static const std::string gSecretHrp = "secret"; + +TEST(TWSecretAnyAddress, AllSecretAddressTests) { + CosmosAddressParameters parameters{.hrp = gSecretHrp, + .coinType = TWCoinTypeSecret, + .address = gSecretAddr, + .privKey = "a054c9a67d81ada560ab6fda3310ebf5971e163ff2291ee736ca64b6a5af1ada", + .publicKey = "03967d2c6263c2d74d9c2fac3a024e2892a94497b64edb294ffab4042851f00b90"}; + TestCosmosAddressParameters(parameters); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Secret/TWAnySignerTests.cpp b/tests/chains/Cosmos/Secret/TWAnySignerTests.cpp new file mode 100644 index 00000000000..95a0f4712b0 --- /dev/null +++ b/tests/chains/Cosmos/Secret/TWAnySignerTests.cpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Cosmos/Address.h" +#include "HexCoding.h" +#include "proto/Cosmos.pb.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::Cosmos::tests { + +TEST(TWAnySignerSecret, Sign) { + auto privateKey = parse_hex("87201512d132ef7a1e57f9e24905fbc24300bd73f676b5716182be5f3e39dada"); + Proto::SigningInput input; + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(265538); + input.set_chain_id("secret-4"); + input.set_memo(""); + input.set_sequence(1); + input.set_private_key(privateKey.data(), privateKey.size()); + + Address fromAddress; + Address toAddress; + EXPECT_TRUE(Address::decode("secret18mdrja40gfuftt5yx6tgj0fn5lurplezyp894y", fromAddress)); + EXPECT_TRUE(Address::decode("secret1rnq6hjfnalxeef87rmdeya3nu9dhpc7k9pujs3", toAddress)); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("uscrt"); + amountOfTx->set_amount("100000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(25000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uscrt"); + amountOfFee->set_amount("2500"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeSecret); + + // https://www.mintscan.io/secret/txs/01F4BD2458BF966F287533775C8D67BBC7CA7214CAEB1752D270A90223E9E82F + assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLXNlY3JldDE4bWRyamE0MGdmdWZ0dDV5eDZ0Z2owZm41bHVycGxlenlwODk0eRItc2VjcmV0MXJucTZoamZuYWx4ZWVmODdybWRleWEzbnU5ZGhwYzdrOXB1anMzGg8KBXVzY3J0EgYxMDAwMDASZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAkZqxdKMtPq2w0kGDGwWGejTAed0H7azPMHtrCX0XYZGEgQKAggBGAESEwoNCgV1c2NydBIEMjUwMBCowwEaQOcHd2gHpa5WKZ/5RRerEtrHlyDlojIEzUGhC9xMFgs7UQMWy+kTTN+NRf7zQ8rx3cPkIKeZhv0u1KRc8uRCc4o=\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "e707776807a5ae56299ff94517ab12dac79720e5a23204cd41a10bdc4c160b3b510316cbe9134cdf8d45fef343caf1ddc3e420a79986fd2ed4a45cf2e442738a"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error_message(), ""); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Secret/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Secret/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..aa77bd0ac21 --- /dev/null +++ b/tests/chains/Cosmos/Secret/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWSecretCoinType, TWCoinType) { + const auto coin = TWCoinTypeSecret; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("026B4886B1D9CE836A99755DDE99D4F8A7748D27B1CE9D298A763B1CFFF62C00")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("secret167m3s89ddurjpyr82vsluvvj0t8ylzn95trrqy")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "secret"); + assertStringsEqual(name, "Secret"); + assertStringsEqual(symbol, "SCRT"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "secret-4"); + assertStringsEqual(txUrl, "https://mintscan.io/secret/txs/026B4886B1D9CE836A99755DDE99D4F8A7748D27B1CE9D298A763B1CFFF62C00"); + assertStringsEqual(accUrl, "https://mintscan.io/secret/account/secret167m3s89ddurjpyr82vsluvvj0t8ylzn95trrqy"); +} diff --git a/tests/chains/Cosmos/Sei/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Sei/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..45746b25326 --- /dev/null +++ b/tests/chains/Cosmos/Sei/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gSeiAddr = "sei1mry47pkga5tdswtluy0m8teslpalkdq0ndpc65"; +static const std::string gSeiHrp = "sei"; + +TEST(TWSeiAnyAddress, AllSeiAddressTests) { + CosmosAddressParameters parameters{.hrp = gSeiHrp, .coinType = TWCoinTypeSei, .address = gSeiAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Sei/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Sei/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..88a856d389a --- /dev/null +++ b/tests/chains/Cosmos/Sei/TWCoinTypeTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + TEST(TWSeiCoinType, TWCoinType) { + const auto coin = TWCoinTypeSei; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("4A2114EE45317439690F3BEA9C8B6CFA11D42CF978F9487754902D372EEB488C")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("sei155hqv2rsypqzq0zpjn72frsxx4l6tcmplw63m2")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "sei"); + assertStringsEqual(name, "Sei"); + assertStringsEqual(symbol, "SEI"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "pacific-1"); + assertStringsEqual(txUrl, "https://www.mintscan.io/sei/txs/4A2114EE45317439690F3BEA9C8B6CFA11D42CF978F9487754902D372EEB488C"); + assertStringsEqual(accUrl, "https://www.mintscan.io/sei/account/sei155hqv2rsypqzq0zpjn72frsxx4l6tcmplw63m2"); + } +} diff --git a/tests/chains/Cosmos/SignerTests.cpp b/tests/chains/Cosmos/SignerTests.cpp new file mode 100644 index 00000000000..6e5570004d4 --- /dev/null +++ b/tests/chains/Cosmos/SignerTests.cpp @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "Base64.h" +#include "proto/Cosmos.pb.h" +#include "Cosmos/Address.h" +#include "TrustWalletCore/TWAnySigner.h" +#include "TestUtilities.h" + +#include +#include + +namespace TW::Cosmos::tests { + +TEST(CosmosSigner, SignTxProtobuf) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(8); + + auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); + auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("muon"); + amountOfTx->set_amount("1"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("200"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + + EXPECT_EQ(R"({"signingMode":"Protobuf","accountNumber":"1037","chainId":"gaia-13003","fee":{"amounts":[{"denom":"muon","amount":"200"}],"gas":"200000"},"sequence":"8","messages":[{"sendCoinsMessage":{"fromAddress":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","toAddress":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573","amounts":[{"denom":"muon","amount":"1"}]}}]})", json); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + + assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47"); +} + +TEST(CosmosSigner, SignProtobuf_ErrorMissingMessage) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(8); + + auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); + auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("200"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + + // TODO +// EXPECT_EQ(output.error_message(), "Error: No message found"); + EXPECT_EQ(output.serialized(), ""); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(hex(output.signature()), ""); +} + +TEST(CosmosSigner, SignTxJson) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::JSON); // obsolete + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(8); + + auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); + auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("muon"); + amountOfTx->set_amount("1"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("200"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + + EXPECT_EQ(R"({"accountNumber":"1037","chainId":"gaia-13003","fee":{"amounts":[{"denom":"muon","amount":"200"}],"gas":"200000"},"sequence":"8","messages":[{"sendCoinsMessage":{"fromAddress":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","toAddress":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573","amounts":[{"denom":"muon","amount":"1"}]}}]})", json); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + + // the sample tx on testnet + // https://hubble.figment.network/chains/gaia-13003/blocks/142933/transactions/3A9206598C3D2E75A5EC074FD33EA53EB18EC729357F0965971C1C51F812AEA3?format=json + EXPECT_EQ(R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); + + EXPECT_EQ(hex(output.signature()), "fc3ef899d206c88077fec42f21ba0b4df4bd3fd115fdf606ae01d9136fef363f57e9e33a7b9ec6ddab658cd07e3c0067470de94e4e75b979a1085a29f0efd926"); +} + +TEST(CosmosSigner, SignTxJsonWithRawJSONMsg) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::JSON); // obsolete + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(8); + + auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); + auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_raw_json_message(); + message.set_type("test"); + message.set_value("{\"test\":\"hello\"}"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("200"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + + EXPECT_EQ(json, "{\"accountNumber\":\"1037\",\"chainId\":\"gaia-13003\",\"fee\":{\"amounts\":[{\"denom\":\"muon\",\"amount\":\"200\"}],\"gas\":\"200000\"},\"sequence\":\"8\",\"messages\":[{\"rawJsonMessage\":{\"type\":\"test\",\"value\":\"{\\\"test\\\":\\\"hello\\\"}\"}}]}"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + + EXPECT_EQ(output.json(), "{\"mode\":\"block\",\"tx\":{\"fee\":{\"amount\":[{\"amount\":\"200\",\"denom\":\"muon\"}],\"gas\":\"200000\"},\"memo\":\"\",\"msg\":[{\"type\":\"test\",\"value\":{\"test\":\"hello\"}}],\"signatures\":[{\"pub_key\":{\"type\":\"tendermint/PubKeySecp256k1\",\"value\":\"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F\"},\"signature\":\"qhxxCOMiVhP7e7Mx+98HUZI0t5DNOFXwzIqNQz+fT6hDKR/ebW0uocsYnE5CiBNEalmBcs5gSIJegNkHhgyEmA==\"}]}}"); + + EXPECT_EQ(hex(output.signature()), "aa1c7108e3225613fb7bb331fbdf07519234b790cd3855f0cc8a8d433f9f4fa843291fde6d6d2ea1cb189c4e428813446a598172ce6048825e80d907860c8498"); +} + +TEST(CosmosSigner, SignTxJson_WithMode) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::JSON); // obsolete + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(8); + input.set_mode(Proto::BroadcastMode::ASYNC); + + auto fromAddress = Address("cosmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); + auto toAddress = Address("cosmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("muon"); + amountOfTx->set_amount("1"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("200"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + { + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + EXPECT_EQ(R"({"mode":"async","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); + EXPECT_EQ(output.error_message(), ""); + } + input.set_mode(Proto::BroadcastMode::SYNC); + { + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + EXPECT_EQ(R"({"mode":"sync","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02","to_address":"cosmos1zt50azupanqlfam5afhv3hexwyutnukeh4c573"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"/D74mdIGyIB3/sQvIboLTfS9P9EV/fYGrgHZE2/vNj9X6eM6e57G3atljNB+PABnRw3pTk51uXmhCFop8O/ZJg=="}]}})", output.json()); + EXPECT_EQ(output.error_message(), ""); + } +} + +TEST(CosmosSigner, SignIbcTransferProtobuf_817101) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(546179); + input.set_chain_id("cosmoshub-4"); + input.set_sequence(2); + + Address fromAddress; + EXPECT_TRUE(Address::decode("cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx", fromAddress)); + Address toAddress; + EXPECT_TRUE(Address::decode("osmo18s0hdnsllgcclweu9aymw4ngktr2k0rkvn7jmn", toAddress)); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_transfer_tokens_message(); + message.set_source_port("transfer"); + message.set_source_channel("channel-141"); + message.set_sender(fromAddress.string()); + message.set_receiver(toAddress.string()); + message.mutable_token()->set_denom("uatom"); + message.mutable_token()->set_amount("100000"); // 0.1 ATOM + message.mutable_timeout_height()->set_revision_number(1); + message.mutable_timeout_height()->set_revision_height(8800000); + + auto& fee = *input.mutable_fee(); + fee.set_gas(500000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uatom"); + amountOfFee->set_amount("12500"); + + auto privateKey = parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"); + EXPECT_EQ(Cosmos::Address(TWCoinTypeCosmos, PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + + // real-world tx: https://www.mintscan.io/cosmos/txs/817101F3D96314AD028733248B28BAFAD535024D7D2C8875D3FE31DC159F096B + // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "Cr4BCr...1yKOU=", "mode": "BROADCAST_MODE_BLOCK"}' https://api.cosmos.network/cosmos/tx/v1beta1/txs + // also similar TX: BCDAC36B605576C8182C2829C808B30A69CAD4959D5ED1E6FF9984ABF280D603 + assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"Cr4BCrsBCikvaWJjLmFwcGxpY2F0aW9ucy50cmFuc2Zlci52MS5Nc2dUcmFuc2ZlchKNAQoIdHJhbnNmZXISC2NoYW5uZWwtMTQxGg8KBXVhdG9tEgYxMDAwMDAiLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseCorb3NtbzE4czBoZG5zbGxnY2Nsd2V1OWF5bXc0bmdrdHIyazBya3ZuN2ptbjIHCAEQgI6ZBBJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEC7O9c5DejAsZ/lUaN5LMfNukR9GfX5qUrQcHhPh1WNkkSBAoCCAEYAhIUCg4KBXVhdG9tEgUxMjUwMBCgwh4aQK0HIWdFMk+C6Gi1KG/vELe1ffcc1aEWUIqz2t/ZhwqNNHxUUSp27wteiugHEMVTEIOBhs84t2gIcT/nD/1yKOU=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "ad07216745324f82e868b5286fef10b7b57df71cd5a116508ab3dadfd9870a8d347c54512a76ef0b5e8ae80710c55310838186cf38b76808713fe70ffd7228e5"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error_message(), ""); +} + +TEST(CosmosSigner, SignDirect1) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_sign_direct_message(); + const auto bodyBytes = parse_hex("0a89010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412690a2d636f736d6f733168736b366a727979716a6668703564686335357463396a74636b796778306570683664643032122d636f736d6f73317a743530617a7570616e716c66616d356166687633686578777975746e756b656834633537331a090a046d756f6e120131"); + message.set_body_bytes(bodyBytes.data(), bodyBytes.size()); + const auto authInfoBytes = parse_hex("0a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc512040a020801180812110a0b0a046d756f6e120332303010c09a0c"); + message.set_auth_info_bytes(authInfoBytes.data(), authInfoBytes.size()); + + { + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + EXPECT_EQ(R"({"signingMode":"Protobuf","accountNumber":"1037","chainId":"gaia-13003","messages":[{"signDirectMessage":{"bodyBytes":"CokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATE=","authInfoBytes":"ClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYCBIRCgsKBG11b24SAzIwMBDAmgw="}}]})", json); + } + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + + assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error_message(), ""); +} + +TEST(CosmosSigner, SignDirect_0a90010a) { + // MsgSend: + // from: cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6 + // to: cosmos1qypqxpq9qcrsszg2pvxq6rs0zqg3yyc5lzv7xu + // amount: 1234567 ucosm + const auto bodyBytes = parse_hex("0a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d120731323334353637"); + + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1); + input.set_chain_id("cosmoshub-4"); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_sign_direct_message(); + message.set_body_bytes(bodyBytes.data(), bodyBytes.size()); + const auto authInfoBytes = parse_hex("0a0a0a0012040a020801180112130a0d0a0575636f736d12043230303010c09a0c"); + message.set_auth_info_bytes(authInfoBytes.data(), authInfoBytes.size()); + + { + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + EXPECT_EQ(R"({"signingMode":"Protobuf","accountNumber":"1","chainId":"cosmoshub-4","messages":[{"signDirectMessage":{"bodyBytes":"CpABChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnAKLWNvc21vczFwa3B0cmU3ZmRrbDZnZnJ6bGVzamp2aHhobGMzcjRnbW1rOHJzNhItY29zbW9zMXF5cHF4cHE5cWNyc3N6ZzJwdnhxNnJzMHpxZzN5eWM1bHp2N3h1GhAKBXVjb3NtEgcxMjM0NTY3","authInfoBytes":"CgoKABIECgIIARgBEhMKDQoFdWNvc20SBDIwMDAQwJoM"}}]})", json); + } + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + + assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CpMBCpABChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnAKLWNvc21vczFwa3B0cmU3ZmRrbDZnZnJ6bGVzamp2aHhobGMzcjRnbW1rOHJzNhItY29zbW9zMXF5cHF4cHE5cWNyc3N6ZzJwdnhxNnJzMHpxZzN5eWM1bHp2N3h1GhAKBXVjb3NtEgcxMjM0NTY3EiEKCgoAEgQKAggBGAESEwoNCgV1Y29zbRIEMjAwMBDAmgwaQEgXmSAlm4M5bz+OX1GtvvZ3fBV2wrZrp4A/Imd55KM7ASivB/siYJegmYiOKzQ82uwoEmFalNnG2BrHHDwDR2Y=\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "48179920259b83396f3f8e5f51adbef6777c1576c2b66ba7803f226779e4a33b0128af07fb226097a099888e2b343cdaec2812615a94d9c6d81ac71c3c034766"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error_message(), ""); +} + +TEST(CosmosSigner, MsgVote) { + // Successfully broadcasted https://www.mintscan.io/cosmos/txs/2EFA054B842B1641B131137B13360F95164C6C1D51BB4A4AC6DE8F75F504AA4C + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1366160); + input.set_chain_id("cosmoshub-4"); + input.set_memo(""); + input.set_sequence(0); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_msg_vote(); + message.set_voter("cosmos1mry47pkga5tdswtluy0m8teslpalkdq07pswu4"); + message.set_proposal_id(77); + message.set_option(TW::Cosmos::Proto::Message_VoteOption_YES); + + auto& fee = *input.mutable_fee(); + fee.set_gas(97681); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uatom"); + amountOfFee->set_amount("2418"); + + auto privateKey = parse_hex("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + + auto expected = R"( + {"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"ClQKUgobL2Nvc21vcy5nb3YudjFiZXRhMS5Nc2dWb3RlEjMITRItY29zbW9zMW1yeTQ3cGtnYTV0ZHN3dGx1eTBtOHRlc2xwYWxrZHEwN3Bzd3U0GAESZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAsv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarlEgQKAggBEhMKDQoFdWF0b20SBDI0MTgQkfsFGkA+Nb3NULc38quGC1x+8ZXry4w9mMX3IA7wUjFboTv7kVOwPlleIc8UqIsjVvKTUFnUuW8dlGQzNR1KkvbvZ1NA"})"; + assertJSONEqual(output.serialized(), expected); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Sommelier/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Sommelier/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..2775fcb56ab --- /dev/null +++ b/tests/chains/Cosmos/Sommelier/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gSommelierAddr = "somm1mry47pkga5tdswtluy0m8teslpalkdq0jalzdl"; +static const std::string gSommelierHrp = "somm"; + +TEST(TWSommelierAnyAddress, AllSommelierAddressTests) { + CosmosAddressParameters parameters{.hrp = gSommelierHrp, .coinType = TWCoinTypeSommelier, .address = gSommelierAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Sommelier/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Sommelier/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..f60eb5df59d --- /dev/null +++ b/tests/chains/Cosmos/Sommelier/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWSommelierCoinType, TWCoinType) { + const auto coin = TWCoinTypeSommelier; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("E73A9E5E534777DDADF7F69A5CB41972894B862D1763FA4081FE913D8D3A5E80")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("somm10d5wmqvezwtj20u5hg3wuvwucce2nhsy0tzqgn")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "sommelier"); + assertStringsEqual(name, "Sommelier"); + assertStringsEqual(symbol, "SOMM"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "sommelier-3"); + assertStringsEqual(txUrl, "https://www.mintscan.io/sommelier/txs/E73A9E5E534777DDADF7F69A5CB41972894B862D1763FA4081FE913D8D3A5E80"); + assertStringsEqual(accUrl, "https://www.mintscan.io/sommelier/account/somm10d5wmqvezwtj20u5hg3wuvwucce2nhsy0tzqgn"); +} diff --git a/tests/chains/Cosmos/StakingTests.cpp b/tests/chains/Cosmos/StakingTests.cpp new file mode 100644 index 00000000000..ae5be9dd728 --- /dev/null +++ b/tests/chains/Cosmos/StakingTests.cpp @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base64.h" +#include "Coin.h" +#include "Cosmos/Address.h" +#include "HexCoding.h" +#include "proto/Cosmos.pb.h" +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + +TEST(CosmosStaking, CompoundingAuthz) { + // Successfully broadcasted https://www.mintscan.io/cosmos/txs/C4629BC7C88690518D8F448E7A8D239C9D63975B11F8E1CE2F95CC2ADA3CCF67 + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1290826); + input.set_chain_id("cosmoshub-4"); + input.set_memo(""); + input.set_sequence(5); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_auth_grant(); + message.set_granter("cosmos13k0q0l7lg2kr32kvt7ly236ppldy8v9dzwh3gd"); + message.set_grantee("cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf"); + auto& grant_stake = *message.mutable_grant_stake(); + grant_stake.mutable_allow_list()->add_address("cosmosvaloper1gjtvly9lel6zskvwtvlg5vhwpu9c9waw7sxzwx"); + grant_stake.set_authorization_type(TW::Cosmos::Proto::Message_AuthorizationType_DELEGATE); + message.set_expiration(1692309600); + + auto& fee = *input.mutable_fee(); + fee.set_gas(96681); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uatom"); + amountOfFee->set_amount("2418"); + + auto privateKey = parse_hex("c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + // Please note the signature has been updated according to the serialization of the `StakeAuthorization` message. + // Previous: CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjoSNgo0Y29zbW9zdmFsb3BlcjFnanR2bHk5bGVsNnpza3Z3dHZsZzV2aHdwdTljOXdhdzdzeHp3eCABEgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQIFyfuijGKf87Hz61ZqxasfLI1PZnNge4RDq/tRyB/tZI6p80iGRqHecoV6+84EQkc9GTlNRQOSlApRCsivT9XI= + auto expected = R"( + { + "mode":"BROADCAST_MODE_BLOCK", + "tx_bytes":"CvgBCvUBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQS0gEKLWNvc21vczEzazBxMGw3bGcya3IzMmt2dDdseTIzNnBwbGR5OHY5ZHp3aDNnZBItY29zbW9zMWZzN2x1MjhoeDVtOWFrbTdycDBjMjQyMmNuOHIyZjdndXJ1amhmGnIKaAoqL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuU3Rha2VBdXRob3JpemF0aW9uEjogARI2CjRjb3Ntb3N2YWxvcGVyMWdqdHZseTlsZWw2enNrdnd0dmxnNXZod3B1OWM5d2F3N3N4end4EgYI4LD6pgYSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAUSEwoNCgV1YXRvbRIEMjQxOBCp8wUaQEAN1nIfDawlHnep2bNEm14w+g7tYybJJT3htcGVS6s9D7va3ed1OUEIk9LZoc3G//VenJ+KLw26SRVBaRukgVI=" + })"; + assertJSONEqual(output.serialized(), expected); +} + +TEST(CosmosStaking, RevokeCompoundingAuthz) { + // Successfully broadcasted: https://www.mintscan.io/cosmos/txs/E3218F634BB6A1BE256545EBE38275D5B02D41E88F504A43F97CD9CD2B624D44 + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1290826); + input.set_chain_id("cosmoshub-4"); + input.set_memo(""); + input.set_sequence(4); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_auth_revoke(); + message.set_grantee("cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf"); + message.set_granter("cosmos13k0q0l7lg2kr32kvt7ly236ppldy8v9dzwh3gd"); + message.set_msg_type_url("/cosmos.staking.v1beta1.MsgDelegate"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(87735); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uatom"); + amountOfFee->set_amount("2194"); + + auto privateKey = parse_hex("c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + auto expected = R"( + { + "mode":"BROADCAST_MODE_BLOCK", + "tx_bytes":"CqoBCqcBCh8vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnUmV2b2tlEoMBCi1jb3Ntb3MxM2swcTBsN2xnMmtyMzJrdnQ3bHkyMzZwcGxkeTh2OWR6d2gzZ2QSLWNvc21vczFmczdsdTI4aHg1bTlha203cnAwYzI0MjJjbjhyMmY3Z3VydWpoZhojL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuTXNnRGVsZWdhdGUSZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/fcQw1hCVUx904t+kCXTiiziaLIY8lyssu1ENfzaN1KEgQKAggBGAQSEwoNCgV1YXRvbRIEMjE5NBC3rQUaQI7K+W7MMBoD6FbFZxRBqs9VTjErztjWTy57+fvrLaTCIZ+eBs7CuaKqfUZdSN8otjubSHVTQID3k9DpPAX0yDo=" + })"; + assertJSONEqual(output.serialized(), expected); +} + +TEST(CosmosStaking, Staking) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(7); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_stake_message(); + message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); + message.set_validator_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); + auto& amountOfTx = *message.mutable_amount(); + amountOfTx.set_denom("muon"); + amountOfTx.set_amount("10"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(101721); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("1018"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + + assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CpsBCpgBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJxCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3AaCgoEbXVvbhICMTASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpA8O9Jm/kL6Za2I3poDs5vpMowYJgNvYCJBRU/vxAjs0lNZYsq40qpTbwOTbORjJA5UjQ6auc40v6uCFT4q4z+uA==\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "f0ef499bf90be996b6237a680ece6fa4ca3060980dbd808905153fbf1023b3494d658b2ae34aa94dbc0e4db3918c903952343a6ae738d2feae0854f8ab8cfeb8"); + EXPECT_EQ(output.error_message(), ""); + + { // Json-serialization, for coverage (to be removed later) + input.set_signing_mode(Proto::JSON); + ANY_SIGN(input, TWCoinTypeCosmos); + + ASSERT_EQ(hex(output.signature()), "c08bdf6c2b0b4428f37975e85d329f1cb19745b000994a743b5df81d57d573aa5f755349befcc848c1d1507818723b1288594bc91df685e89aff22e0303b4861"); + EXPECT_EQ(output.error_message(), ""); + EXPECT_EQ(hex(output.serialized()), ""); + } +} + +TEST(CosmosStaking, Unstaking) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(7); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_unstake_message(); + message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); + message.set_validator_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); + auto& amountOfTx = *message.mutable_amount(); + amountOfTx.set_denom("muon"); + amountOfTx.set_amount("10"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(101721); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("1018"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + + assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"Cp0BCpoBCiUvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dVbmRlbGVnYXRlEnEKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBoKCgRtdW9uEgIxMBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYBxISCgwKBG11b24SBDEwMTgQ2ZoGGkBhlxHFnjBERxLtjLbMCKXcrDctaSZ9djtWCa3ely1bpV6m+6aAFjpr8aEZH+q2AtjJSEdgpQRJxP+9/gQsRTnZ\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "619711c59e30444712ed8cb6cc08a5dcac372d69267d763b5609adde972d5ba55ea6fba680163a6bf1a1191feab602d8c9484760a50449c4ffbdfe042c4539d9"); + EXPECT_EQ(output.error_message(), ""); + + { // Json-serialization, for coverage (to be removed later) + input.set_signing_mode(Proto::JSON); + ANY_SIGN(input, TWCoinTypeCosmos); + ASSERT_EQ(hex(output.signature()), "8f85a9515a211881daebfb346c2beeca3ab5c2d406a9b3ad402cfddaa3d08e2b13378e13cfef8ecf1d6500fe85d0ce3e793034dd77aba90f216427807cbff79f"); + EXPECT_EQ(output.error_message(), ""); + EXPECT_EQ(hex(output.serialized()), ""); + } +} + +TEST(CosmosStaking, Restaking) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(7); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_restake_message(); + message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); + message.set_validator_dst_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); + message.set_validator_src_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); + + auto& amountOfTx = *message.mutable_amount(); + amountOfTx.set_denom("muon"); + amountOfTx.set_amount("10"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(101721); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("1018"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + + assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CtIBCs8BCiovY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dCZWdpblJlZGVsZWdhdGUSoAEKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBotY29zbW9zMWhzazZqcnl5cWpmaHA1ZGhjNTV0YzlqdGNreWd4MGVwaDZkZDAyIgoKBG11b24SAjEwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQJXKG7D830zVXu7qgALJ3RKyQI6qZZ8rnWhgdH/kfqdxRIECgIIARgHEhIKDAoEbXVvbhIEMTAxOBDZmgYaQJ52qO5xdtBkNUeFeWrnqUXkngyHFKCXnOPPClyVI0HrULdp5jbwGra2RujEOn4BrbFCb3JFnpc2o1iuLXbKQxg=\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "9e76a8ee7176d064354785796ae7a945e49e0c8714a0979ce3cf0a5c952341eb50b769e636f01ab6b646e8c43a7e01adb1426f72459e9736a358ae2d76ca4318"); + EXPECT_EQ(output.error_message(), ""); + + { // Json-serialization, for coverage (to be removed later) + input.set_signing_mode(Proto::JSON); + ANY_SIGN(input, TWCoinTypeCosmos); + ASSERT_EQ(hex(output.signature()), "e64d3761bd25a28befcda80c0a0e208d024fdb0a2b89955170e65a5c5d454aba2ce81d57e01f0c126de5a59c2b58124c109560c9803d65a17a14b548dd6c50db"); + EXPECT_EQ(output.error_message(), ""); + EXPECT_EQ(hex(output.serialized()), ""); + } +} + +TEST(CosmosStaking, Withdraw) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(7); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_withdraw_stake_reward_message(); + message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); + message.set_validator_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(101721); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("1018"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + + assertJSONEqual(output.serialized(), "{\"tx_bytes\":\"CqMBCqABCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmUKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhI0Y29zbW9zdmFsb3BlcjF6a3VwcjgzaHJ6a24zdXA1ZWxrdHpjcTN0dWZ0OG54c213ZHFncBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYBxISCgwKBG11b24SBDEwMTgQ2ZoGGkBW1Cd+0pNfMPEVXQtqG1VIijDjZP2UOiDlvUF478axnxlF8PaOAsY0S5OdUE3Wz7+nu8YVmrLZQS/8mlqLaK05\",\"mode\":\"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "56d4277ed2935f30f1155d0b6a1b55488a30e364fd943a20e5bd4178efc6b19f1945f0f68e02c6344b939d504dd6cfbfa7bbc6159ab2d9412ffc9a5a8b68ad39"); + EXPECT_EQ(output.error_message(), ""); + + { // Json-serialization, for coverage (to be removed later) + input.set_signing_mode(Proto::JSON); + ANY_SIGN(input, TWCoinTypeCosmos); + ASSERT_EQ(hex(output.signature()), "546f0d67356f6af94cfb5ab22b974e499c33123f2c2c292f4f0e64878e0e728f4643105fd771550beb3f2371f08880aaa38fa8f2334c103a779f1d82d2db98d6"); + EXPECT_EQ(output.error_message(), ""); + EXPECT_EQ(hex(output.serialized()), ""); + } +} + +TEST(CosmosStaking, SetWithdrawAddress) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("gaia-13003"); + input.set_memo(""); + input.set_sequence(7); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_set_withdraw_address_message(); + message.set_delegator_address("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); + message.set_withdraw_address("cosmosvaloper1zkupr83hrzkn3up5elktzcq3tuft8nxsmwdqgp"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(101721); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("1018"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeCosmos); + + assertJSONEqual(output.serialized(), R"({"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"Cp4BCpsBCjIvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1NldFdpdGhkcmF3QWRkcmVzcxJlCi1jb3Ntb3MxaHNrNmpyeXlxamZocDVkaGM1NXRjOWp0Y2t5Z3gwZXBoNmRkMDISNGNvc21vc3ZhbG9wZXIxemt1cHI4M2hyemtuM3VwNWVsa3R6Y3EzdHVmdDhueHNtd2RxZ3ASZgpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAcSEgoMCgRtdW9uEgQxMDE4ENmaBhpAkm2TJLw4FcIwN5bkqVaGbmAgkTSHeYD8sUkIyJHLa89cPvThkFO/lKlxBMl2UAMs06hL6cYcl4Px+B6rpFdBpA=="})"); + EXPECT_EQ(hex(output.signature()), "926d9324bc3815c2303796e4a956866e60209134877980fcb14908c891cb6bcf5c3ef4e19053bf94a97104c97650032cd3a84be9c61c9783f1f81eaba45741a4"); + EXPECT_EQ(output.error_message(), ""); + + { // Json-serialization, for coverage (to be removed later) + input.set_signing_mode(Proto::JSON); + ANY_SIGN(input, TWCoinTypeCosmos); + ASSERT_EQ(hex(output.signature()), "22cfbcec33d06ed42623264049d11d6fb86566103d5621a23b1444022eb1aace3a0790a1c46b48c0218689616daf97f99ae72c3589966205de45b57194fbada2"); + EXPECT_EQ(output.error_message(), ""); + EXPECT_EQ(hex(output.serialized()), ""); + } +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Stargaze/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Stargaze/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..0fb293d3483 --- /dev/null +++ b/tests/chains/Cosmos/Stargaze/TWAnyAddressTests.cpp @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "TestUtilities.h" +#include +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gStarsAddr = "stars1mry47pkga5tdswtluy0m8teslpalkdq02a8nhy"; +static const std::string gStarsHrp = "stars"; + +TEST(TWStargazeAnyAddress, AllStargazeAddressTests) { + CosmosAddressParameters parameters{.hrp = gStarsHrp, .coinType = TWCoinTypeStargaze, .address = gStarsAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Stargaze/TWAnySignerTests.cpp b/tests/chains/Cosmos/Stargaze/TWAnySignerTests.cpp new file mode 100644 index 00000000000..c6e1faec3e1 --- /dev/null +++ b/tests/chains/Cosmos/Stargaze/TWAnySignerTests.cpp @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Cosmos/Address.h" +#include "HexCoding.h" +#include "proto/Cosmos.pb.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::Cosmos::tests { + +TEST(TWAnySignerStargaze, SignNftTransferCW721) { + auto privateKey = parse_hex("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433"); + Proto::SigningInput input; + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(188393); + input.set_chain_id("stargaze-1"); + input.set_memo(""); + input.set_sequence(5); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_execute_contract_generic(); + + const auto tokenContractAddress = "stars14gmjlyfz5mpv5d8zrksn0tjhz2wwvdc4yk06754alfasq9qen7fsknry42"; + const auto txMessage = R"({"transfer_nft": {"recipient": "stars1kd5q7qejlqz94kpmd9pvr4v2gzgnca3lvt6xnp","token_id": "1209"}})"; + + message.set_sender_address("stars1mry47pkga5tdswtluy0m8teslpalkdq02a8nhy"); + message.set_contract_address(tokenContractAddress); + message.set_execute_msg(txMessage); + + auto& fee = *input.mutable_fee(); + fee.set_gas(666666); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("ustars"); + amountOfFee->set_amount("1000"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeStargaze); + + // https://www.mintscan.io/stargaze/txs/300836A5BF9002CF38EE34A8C56E8E7E6854FA64F1DEB3AE108F381A48150F7C + auto expectedJson = R"( + { + "mode":"BROADCAST_MODE_BLOCK", + "tx_bytes":"CoACCv0BCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QS1AEKLHN0YXJzMW1yeTQ3cGtnYTV0ZHN3dGx1eTBtOHRlc2xwYWxrZHEwMmE4bmh5EkBzdGFyczE0Z21qbHlmejVtcHY1ZDh6cmtzbjB0amh6Mnd3dmRjNHlrMDY3NTRhbGZhc3E5cWVuN2Zza25yeTQyGmJ7InRyYW5zZmVyX25mdCI6IHsicmVjaXBpZW50IjogInN0YXJzMWtkNXE3cWVqbHF6OTRrcG1kOXB2cjR2Mmd6Z25jYTNsdnQ2eG5wIiwidG9rZW5faWQiOiAiMTIwOSJ9fRJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECy/215HKJMyIpTmDPCIPUPfQx4QidKey0R6nm1VBFquUSBAoCCAEYBRIUCg4KBnVzdGFycxIEMTAwMBCq2CgaQMx+l2sdM5DAPbDyY1p173MLnjGyNWIcRmaFiVNphLuTV3tjhwPbsXEA0hyRxyWS3vN0/xUF/JEsO9wRspj2aJ4=" + })"; + assertJSONEqual(output.serialized(), expectedJson); + EXPECT_EQ(hex(output.signature()), "cc7e976b1d3390c03db0f2635a75ef730b9e31b235621c46668589536984bb93577b638703dbb17100d21c91c72592def374ff1505fc912c3bdc11b298f6689e"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error_message(), ""); +} + +TEST(TWAnySignerStargaze, Sign) { + auto privateKey = parse_hex("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433"); + Proto::SigningInput input; + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(188393); + input.set_chain_id("stargaze-1"); + input.set_memo(""); + input.set_sequence(0); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address("stars1mry47pkga5tdswtluy0m8teslpalkdq02a8nhy"); + message.set_to_address("stars1mry47pkga5tdswtluy0m8teslpalkdq02a8nhy"); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("ustars"); + amountOfTx->set_amount("10000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(80000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("ustars"); + amountOfFee->set_amount("1000"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeStargaze); + + // https://www.mintscan.io/stargaze/txs/98D5E36CA7080DDB286FE924A5A9976ABD4EBE49C92A09D322F29AD30DE4BE4D + auto expectedJson = R"( + { + "mode":"BROADCAST_MODE_BLOCK", + "tx_bytes":"CpABCo0BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm0KLHN0YXJzMW1yeTQ3cGtnYTV0ZHN3dGx1eTBtOHRlc2xwYWxrZHEwMmE4bmh5EixzdGFyczFtcnk0N3BrZ2E1dGRzd3RsdXkwbTh0ZXNscGFsa2RxMDJhOG5oeRoPCgZ1c3RhcnMSBTEwMDAwEmYKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQLL/bXkcokzIilOYM8Ig9Q99DHhCJ0p7LRHqebVUEWq5RIECgIIARIUCg4KBnVzdGFycxIEMTAwMBCA8QQaQHAkntxzC1oH7Yde4+KEmnB+K3XbJIYw0q6MqMPEY65YAwBDNDOdaTu/rpehus/20MvBfbAEZiw9+whzXLpkQ5A=" + })"; + assertJSONEqual(output.serialized(), expectedJson); + EXPECT_EQ(hex(output.signature()), "70249edc730b5a07ed875ee3e2849a707e2b75db248630d2ae8ca8c3c463ae5803004334339d693bbfae97a1bacff6d0cbc17db004662c3dfb08735cba644390"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error_message(), ""); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Stride/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Stride/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..9a2035d95c7 --- /dev/null +++ b/tests/chains/Cosmos/Stride/TWAnyAddressTests.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" +#include "TestUtilities.h" +#include + +namespace TW::Cosmos::tests { + +static const std::string gStrideAddr = "stride1mry47pkga5tdswtluy0m8teslpalkdq0a2sjge"; +static const std::string gStrideHrp = "stride"; + +TEST(TWStrideAnyAddress, AllStrideAddressTests) { + CosmosAddressParameters parameters{.hrp = gStrideHrp, .coinType = TWCoinTypeStride, .address = gStrideAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Stride/TWAnySignerTests.cpp b/tests/chains/Cosmos/Stride/TWAnySignerTests.cpp new file mode 100644 index 00000000000..b076cb54de4 --- /dev/null +++ b/tests/chains/Cosmos/Stride/TWAnySignerTests.cpp @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Cosmos/Address.h" +#include "HexCoding.h" +#include "proto/Cosmos.pb.h" +#include "Base64.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::Cosmos::tests { + +TEST(TWAnySignerStride, SignLiquidStaking) { + auto privateKey = parse_hex("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433"); + Proto::SigningInput input; + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(136412); + input.set_chain_id("stride-1"); + input.set_memo(""); + input.set_sequence(0); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_msg_stride_liquid_staking_stake(); + message.set_creator("stride1mry47pkga5tdswtluy0m8teslpalkdq0a2sjge"); + message.set_amount("100000"); + message.set_host_denom("uatom"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(500000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("ustrd"); + amountOfFee->set_amount("0"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeStride); + + // Successfully broadcasted: https://www.mintscan.io/stride/txs/48E51A2571D99453C4581B30CECA2A1156C0D1EBACCD3619729B5A35AD67CC94?height=3485243 + auto expectedJson = R"( + { + "mode":"BROADCAST_MODE_BLOCK", + "tx_bytes":"CmMKYQofL3N0cmlkZS5zdGFrZWliYy5Nc2dMaXF1aWRTdGFrZRI+Ci1zdHJpZGUxbXJ5NDdwa2dhNXRkc3d0bHV5MG04dGVzbHBhbGtkcTBhMnNqZ2USBjEwMDAwMBoFdWF0b20SYgpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAsv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarlEgQKAggBEhAKCgoFdXN0cmQSATAQoMIeGkCDaZHV5/Z3CAQC5DXkaHmF6OKUiS5XKDsl3ZnBaaVuJjlSWV2vA7MPwGbC17P6jbVJt58ZLcxIWFt76UO3y1ix" + })"; + assertJSONEqual(output.serialized(), expectedJson); + EXPECT_EQ(hex(output.signature()), "836991d5e7f677080402e435e4687985e8e294892e57283b25dd99c169a56e263952595daf03b30fc066c2d7b3fa8db549b79f192dcc48585b7be943b7cb58b1"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error_message(), ""); +} + +TEST(TWAnySignerStride, SignLiquidStakingRedeem) { + auto privateKey = parse_hex("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433"); + Proto::SigningInput input; + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(136412); + input.set_chain_id("stride-1"); + input.set_memo(""); + input.set_sequence(1); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_msg_stride_liquid_staking_redeem(); + message.set_creator("stride1mry47pkga5tdswtluy0m8teslpalkdq0a2sjge"); + message.set_amount("40000"); + message.set_receiver("cosmos1mry47pkga5tdswtluy0m8teslpalkdq07pswu4"); + message.set_host_zone("cosmoshub-4"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(1000000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("ustrd"); + amountOfFee->set_amount("0"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeStride); + + // Successfully broadcasted: https://www.mintscan.io/stride/txs/B3D3A92A2FFB92A480A4B547A4303E6932204972A965D687DB4FB6B4E16B2C42?height=3485343 + auto expectedJson = R"( + { + "mode":"BROADCAST_MODE_BLOCK", + "tx_bytes":"CpgBCpUBCh8vc3RyaWRlLnN0YWtlaWJjLk1zZ1JlZGVlbVN0YWtlEnIKLXN0cmlkZTFtcnk0N3BrZ2E1dGRzd3RsdXkwbTh0ZXNscGFsa2RxMGEyc2pnZRIFNDAwMDAaC2Nvc21vc2h1Yi00Ii1jb3Ntb3MxbXJ5NDdwa2dhNXRkc3d0bHV5MG04dGVzbHBhbGtkcTA3cHN3dTQSZApQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAsv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarlEgQKAggBGAESEAoKCgV1c3RyZBIBMBDAhD0aQKf84TYoPqwnXw22r0dok2fYplUFu003TlIfpoT+wqTZF1lHPC+RTAoJob6x50CnfvGlgJFBEQYPD+Ccv659VVA=" + })"; + assertJSONEqual(output.serialized(), expectedJson); + EXPECT_EQ(TW::Base64::encode(data(output.signature())), "p/zhNig+rCdfDbavR2iTZ9imVQW7TTdOUh+mhP7CpNkXWUc8L5FMCgmhvrHnQKd+8aWAkUERBg8P4Jy/rn1VUA=="); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error_message(), ""); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Stride/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Stride/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..6003f28894a --- /dev/null +++ b/tests/chains/Cosmos/Stride/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWStrideCoinType, TWCoinType) { + const auto coin = TWCoinTypeStride; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("FB67755B3A00D4BCC11F607867B9C767CF24BCB749C718579D1EC794226087C8")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("stride1c44mngg9pjjeqrr07sle7ntuggrajnt4lsf9jl")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "stride"); + assertStringsEqual(name, "Stride"); + assertStringsEqual(symbol, "STRD"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "stride-1"); + assertStringsEqual(txUrl, "https://www.mintscan.io/stride/txs/FB67755B3A00D4BCC11F607867B9C767CF24BCB749C718579D1EC794226087C8"); + assertStringsEqual(accUrl, "https://www.mintscan.io/stride/account/stride1c44mngg9pjjeqrr07sle7ntuggrajnt4lsf9jl"); +} diff --git a/tests/chains/Cosmos/THORChain/SignerTests.cpp b/tests/chains/Cosmos/THORChain/SignerTests.cpp new file mode 100644 index 00000000000..be2e0a334b3 --- /dev/null +++ b/tests/chains/Cosmos/THORChain/SignerTests.cpp @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "proto/Cosmos.pb.h" +#include "Coin.h" +#include "HexCoding.h" +#include "Bech32Address.h" +#include "TestUtilities.h" +#include "TrustWalletCore/TWAnySigner.h" +#include "TrustWalletCore/TWCoinType.h" + +#include +#include + +using namespace TW; + + +TEST(THORChainSigner, SignTx_Protobuf_7E480F) { + auto input = Cosmos::Proto::SigningInput(); + input.set_signing_mode(Cosmos::Proto::Protobuf); + input.set_chain_id("thorchain-mainnet-v1"); + input.set_account_number(593); + input.set_sequence(21); + input.set_memo(""); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_thorchain_send_message(); + Bech32Address fromAddress("thor"); + EXPECT_TRUE(Bech32Address::decode("thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", fromAddress, "thor")); + Bech32Address toAddress("thor"); + EXPECT_TRUE(Bech32Address::decode("thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn", toAddress, "thor")); + message.set_from_address(std::string(fromAddress.getKeyHash().begin(), fromAddress.getKeyHash().end())); + message.set_to_address(std::string(toAddress.getKeyHash().begin(), toAddress.getKeyHash().end())); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("rune"); + amountOfTx->set_amount("38000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(2500000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("rune"); + amountOfFee->set_amount("200"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + + assertJSONEqual(json, R"( + { + "accountNumber": "593", + "chainId": "thorchain-mainnet-v1", + "fee": { + "amounts": [ + { + "amount": "200", + "denom": "rune" + } + ], + "gas": "2500000" + }, + "messages": [ + { + "thorchainSendMessage": { + "amounts": [ + { + "amount": "38000000", + "denom": "rune" + } + ], + "fromAddress": "FSLnZ9tusZcIsAOAKb+9YHvJvQ4=", + "toAddress": "yoZFn7AFUcffQlQMXnhpGSyDOts=" + } + } + ], + "sequence": "21", + "signingMode": "Protobuf" + } + )"); + + auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTHORChain); + + // https://viewblock.io/thorchain/tx/7E480FA163F6C6AFA17593F214C7BBC218F69AE3BC72366E39042AF381BFE105 + // curl -H 'Content-Type: application/json' --data-binary '{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"ClIKUAoO..89g="}' https:///cosmos/tx/v1beta1/txs + assertJSONEqual(output.serialized(), R"( + { + "mode": "BROADCAST_MODE_BLOCK", + "tx_bytes": "ClIKUAoOL3R5cGVzLk1zZ1NlbmQSPgoUFSLnZ9tusZcIsAOAKb+9YHvJvQ4SFMqGRZ+wBVHH30JUDF54aRksgzrbGhAKBHJ1bmUSCDM4MDAwMDAwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQPtmX45bPQpL1/OWkK7pBWZzNXZbjExVKfJ6nBJ3jF8dxIECgIIARgVEhIKCwoEcnVuZRIDMjAwEKDLmAEaQKZtS3ATa26OOGvqdKm14ZbHeNfkPtIajXi5MkZ5XaX2SWOeX+YnCPZ9TxF9Jj5cVIo71m55xq4hVL3yDbRe89g=" + } + )"); + EXPECT_EQ(hex(output.signature()), "a66d4b70136b6e8e386bea74a9b5e196c778d7e43ed21a8d78b93246795da5f649639e5fe62708f67d4f117d263e5c548a3bd66e79c6ae2154bdf20db45ef3d8"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error_message(), ""); +} + +TEST(THORChainSigner, SignTx_MsgDeposit) { + auto input = Cosmos::Proto::SigningInput(); + input.set_signing_mode(Cosmos::Proto::Protobuf); + input.set_chain_id("thorchain-mainnet-v1"); + input.set_account_number(75247); + input.set_sequence(7); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_thorchain_deposit_message(); + + message.set_memo("=:DOGE.DOGE:DNhRF1h8J4ZnB1bxp9kaqhVLYetkx1nSJ5::tr:0"); + Bech32Address signerAddress("thor"); + EXPECT_TRUE(Bech32Address::decode("thor14j5lwl8ulexrqp5x39kmkctv2937694z3jn2dz", signerAddress, "thor")); + message.set_signer(std::string(signerAddress.getKeyHash().begin(), signerAddress.getKeyHash().end())); + + auto& coins = *message.add_coins(); + coins.set_amount("150000000"); + coins.set_decimals(0); + + auto& asset = *coins.mutable_asset(); + asset.set_chain("THOR"); + asset.set_symbol("RUNE"); + asset.set_ticker("RUNE"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(50000000); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + + assertJSONEqual(json, R"( + { + "accountNumber": "75247", + "chainId": "thorchain-mainnet-v1", + "fee": { + "gas": "50000000" + }, + "messages": [ + { + "thorchainDepositMessage": { + "coins": [ + { + "amount": "150000000", + "asset": { + "chain": "THOR", + "symbol": "RUNE", + "ticker": "RUNE" + } + } + ], + "memo": "=:DOGE.DOGE:DNhRF1h8J4ZnB1bxp9kaqhVLYetkx1nSJ5::tr:0", + "signer": "rKn3fPz+TDAGholtu2FsUWPtFqI=" + } + } + ], + "sequence": "7", + "signingMode": "Protobuf" + } + )"); + + auto privateKey = parse_hex("2659e41d54ebd449d68b9d58510d8eeeb837ee00d6ecc760b7a731238d8c3113"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTHORChain); + + // https://viewblock.io/thorchain/tx/0162213E7F9D85965B1C57FA3BF9603C655B542F358318303A7B00661AE42510 + // curl -H 'Content-Type: application/json' --data-binary '{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CoUBCoIB..hiw="}' https:///cosmos/tx/v1beta1/txs + assertJSONEqual(output.serialized(), R"( + { + "mode": "BROADCAST_MODE_BLOCK", + "tx_bytes": "CoUBCoIBChEvdHlwZXMuTXNnRGVwb3NpdBJtCh8KEgoEVEhPUhIEUlVORRoEUlVORRIJMTUwMDAwMDAwEjQ9OkRPR0UuRE9HRTpETmhSRjFoOEo0Wm5CMWJ4cDlrYXFoVkxZZXRreDFuU0o1Ojp0cjowGhSsqfd8/P5MMAaGiW27YWxRY+0WohJZClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDuZVDlIFW3DtSEBa6aUBJ0DrQHlQ+2g7lIt5ekAM25SkSBAoCCAEYBxIFEIDh6xcaQAxKMZMKbM8gdLwn23GDXfbwyCkgqWzFMFlnrqFm0u54F8T32wmsoJQAdoLIyOskYmi7nb1rhryfabeeULwRhiw=" + } + )"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error_message(), ""); +} + +TEST(THORChainSigner, SignTx_Json_Deprecated) { + auto input = Cosmos::Proto::SigningInput(); + input.set_memo("memo1234"); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address("thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r"); + message.set_to_address("thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("rune"); + amountOfTx->set_amount("50000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(2000000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("rune"); + amountOfFee->set_amount("200"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + + assertJSONEqual(json, R"( + { + "fee": { + "amounts": [ + { + "denom": "rune", + "amount": "200" + } + ], + "gas": "2000000" + }, + "memo": "memo1234", + "messages": [ + { + "sendCoinsMessage": { + "fromAddress": "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", + "toAddress": "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn", + "amounts": [ + { + "denom": "rune", + "amount": "50000000" + } + ] + } + } + ] + } + )"); + + auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTHORChain); + + assertJSONEqual(output.json(), R"( + { + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "200", + "denom": "rune" + } + ], + "gas": "2000000" + }, + "memo": "memo1234", + "msg": [ + { + "type": "thorchain/MsgSend", + "value": { + "amount": [ + { + "amount": "50000000", + "denom": "rune" + } + ], + "from_address": "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", + "to_address": "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3" + }, + "signature": "12AaNC0v51Rhz8rBf7V7rpI6oksREWrjzba3RK1v1NNlqZq62sG0aXWvStp9zZXe07Pp2FviFBAx+uqWsO30NQ==" + } + ] + } + } + )"); + EXPECT_EQ(hex(output.signature()), "d7601a342d2fe75461cfcac17fb57bae923aa24b11116ae3cdb6b744ad6fd4d365a99abadac1b46975af4ada7dcd95ded3b3e9d85be2141031faea96b0edf435"); +} + +TEST(THORChainSigner, SignJson) { + auto inputJson = R"({"fee":{"amounts":[{"denom":"rune","amount":"200"}],"gas":"2000000"},"memo":"memo1234","messages":[{"sendCoinsMessage":{"fromAddress":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","toAddress":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn","amounts":[{"denom":"rune","amount":"50000000"}]}}]})"; + auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); + + auto outputJson = TW::anySignJSON(TWCoinTypeTHORChain, inputJson, privateKey); + + EXPECT_EQ(R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"rune"}],"gas":"2000000"},"memo":"memo1234","msg":[{"type":"thorchain/MsgSend","value":{"amount":[{"amount":"50000000","denom":"rune"}],"from_address":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","to_address":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"12AaNC0v51Rhz8rBf7V7rpI6oksREWrjzba3RK1v1NNlqZq62sG0aXWvStp9zZXe07Pp2FviFBAx+uqWsO30NQ=="}]}})", outputJson); +} diff --git a/tests/chains/Cosmos/THORChain/SwapTests.cpp b/tests/chains/Cosmos/THORChain/SwapTests.cpp new file mode 100644 index 00000000000..44b11f6d686 --- /dev/null +++ b/tests/chains/Cosmos/THORChain/SwapTests.cpp @@ -0,0 +1,1293 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Binance/Address.h" +#include "Bitcoin/Script.h" +#include "Bitcoin/SegwitAddress.h" +#include "Ethereum/ABI/Function.h" +#include "Ethereum/Address.h" +#include "THORChain/Swap.h" +#include "proto/Binance.pb.h" +#include "proto/Bitcoin.pb.h" +#include "proto/Cosmos.pb.h" +#include "proto/Ethereum.pb.h" +#include "proto/THORChainSwap.pb.h" + +#include "Coin.h" +#include "HexCoding.h" +#include "TestUtilities.h" +#include "uint256.h" +#include +#include + +#include + +namespace TW::THORChainSwap { + +// Addresses for wallet 'isolate dismiss fury ... note' +const auto Address1Btc = "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"; +const auto Address1Eth = "0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7"; +const auto Address1Bnb = "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx"; +const auto Address1Thor = "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r"; +const Data TestKey1Btc = parse_hex("13fcaabaf9e71ffaf915e242ec58a743d55f102cf836968e5bd4881135e0c52c"); +const Data TestKey1Eth = parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904"); +const Data TestKey1Bnb = parse_hex("bcf8b072560dda05122c99390def2c385ec400e1a93df0657a85cf6b57a715da"); +const auto VaultBtc = "bc1q6m9u2qsu8mh8y7v8rr2ywavtj8g5arzlyhcej7"; +const auto VaultEth = "0x1091c4De6a3cF09CdA00AbDAeD42c7c3B69C83EC"; +const auto VaultBnb = "bnb1n9esxuw8ca7ts8l6w66kdh800s09msvul6vlse"; +const auto RouterEth = "0x42A5Ed456650a09Dc10EBc6361A7480fDd61f27B"; + +TEST(THORChainSwap, OverflowFixEth) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::ETH)); + fromAsset.set_symbol("ETH"); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BTC)); + toAsset.set_symbol("BTC"); + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(Address1Eth) + .toAddress(Address1Btc) + .vault(VaultEth) + .fromAmount("1234000000000000000000") + .toAmountLimit("5285656144") + .build(); + ASSERT_EQ(errorCode, 0); +} + +TEST(THORChainSwap, SwapBtcEth) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::BTC)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::ETH)); + toAsset.set_symbol("ETH"); + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(Address1Btc) + .toAddress(Address1Eth) + .vault(VaultBtc) + .fromAmount("1000000") + .toAmountLimit("140000000000000000") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "080110c0843d1801222a62633171366d397532717375386d68387937763872723279776176746a38673561727a6c796863656a372a2a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070386a473d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a313430303030303030303030303030303030"); + + auto tx = Bitcoin::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.amount(), 1000000); + EXPECT_EQ(tx.to_address(), VaultBtc); + EXPECT_EQ(tx.change_address(), Address1Btc); + EXPECT_EQ(tx.output_op_return(), "=:ETH.ETH:0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7:140000000000000000"); + EXPECT_EQ(tx.coin_type(), 0ul); + EXPECT_EQ(tx.private_key_size(), 0); + EXPECT_FALSE(tx.has_plan()); + + // set few fields before signing + tx.set_byte_fee(20); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + tx.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); + auto& utxo = *tx.add_utxo(); + Data utxoHash = parse_hex("1234000000000000000000000000000000000000000000000000000000005678"); + utxo.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); + utxo.mutable_out_point()->set_index(0); + utxo.mutable_out_point()->set_sequence(UINT32_MAX); + auto utxoScript = Bitcoin::Script::lockScriptForAddress(Address1Btc, TWCoinTypeBitcoin); + utxo.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + utxo.set_amount(50000000); + tx.set_use_max_amount(false); + + // sign and encode resulting input + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeBitcoin); + EXPECT_EQ(output.error(), 0); + EXPECT_EQ(hex(output.encoded()), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "1234000000000000000000000000000000000000000000000000000000005678" "00000000" "00" "" "ffffffff" + "03" // outputs + "40420f0000000000" "16" "0014d6cbc5021c3eee72798718d447758b91d14e8c5f" + "d49ceb0200000000" "16" "00140cb9f5c6b62c03249367bc20a90dd2425e6926af" + "0000000000000000" "49" "6a473d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a313430303030303030303030303030303030" + // witness + "02" + "48" "3045022100a67f84cbde5affbb46ffff2b33c1453ff2de70ef990fc974175d9a609e5a87ed0220589c57d958208f866c9477c7d6c9075dea4c58622debb02eab85032b8b6d373001" + "21" "021e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee" + "00000000" // nLockTime + ); +} + +TEST(THORChainSwap, SwapDogeBusd) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::DOGE)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BNB)); + toAsset.set_symbol("BNB"); + toAsset.set_token_id("BUSD-BD1"); + + auto vaultDoge = "DExct9oTfqr7pfnbP2hkCHP1Z2eUDgqXya"; + auto fromAddressDoge = "DKftkYCtCyYxQy2TRAuAzQXoyKDdYsEBnw"; + auto toAddressBnb = "bnb1s4kallxngpyspzm6nrezkml9rgyw6kxpw4fhr2"; + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(fromAddressDoge) + .toAddress(toAddressBnb) + .vault(vaultDoge) + .fromAmount("10000000000") + .toAmountLimit("789627468") + .affFeeAddress("t") + .affFeeRate("0") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "08011080c8afa025180122224445786374396f546671723770666e625032686b434850315a3265554467715879612a22444b66746b5943744379597851793254524175417a51586f794b4464597345426e7750036a473d3a424e422e425553442d4244313a626e623173346b616c6c786e67707973707a6d366e72657a6b6d6c3972677977366b78707734666872323a3738393632373436383a743a30"); + + auto tx = Bitcoin::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.amount(), 10000000000); + EXPECT_EQ(tx.to_address(), vaultDoge); + EXPECT_EQ(tx.change_address(), fromAddressDoge); + EXPECT_EQ(tx.output_op_return(), "=:BNB.BUSD-BD1:bnb1s4kallxngpyspzm6nrezkml9rgyw6kxpw4fhr2:789627468:t:0"); + EXPECT_EQ(tx.coin_type(), TWCoinTypeDogecoin); + EXPECT_EQ(tx.private_key_size(), 0); + EXPECT_FALSE(tx.has_plan()); + + // + auto dogeKey = parse_hex("3785864c91ed408ebaeae473962a471eb4d68ce998c2957e8e5f6be7a525f2d7"); + tx.add_private_key(dogeKey.data(), dogeKey.size()); + tx.set_byte_fee(1000); + auto& utxo = *tx.add_utxo(); + Data previousUTXOHash = parse_hex("9989c36afdd1755a679226875425b368816031186c0f1b4a363ab2ef6d0a2fe8"); + std::reverse(previousUTXOHash.begin(), previousUTXOHash.end()); + utxo.mutable_out_point()->set_hash(previousUTXOHash.data(), previousUTXOHash.size()); + utxo.mutable_out_point()->set_index(1); + utxo.mutable_out_point()->set_sequence(UINT32_MAX - 3); + auto utxoScript = Bitcoin::Script::lockScriptForAddress(fromAddressDoge, TWCoinTypeDogecoin); + utxo.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + utxo.set_amount(16845776096); + tx.set_use_max_amount(false); + + // sign and encode resulting input + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeDogecoin); + EXPECT_EQ(output.error(), 0); + EXPECT_EQ(hex(output.encoded()), "0100000001e82f0a6defb23a364a1b0f6c1831608168b32554872692675a75d1fd6ac38999010000006b4830450221008660de3d3123a9e6831517265fb84c4fb2bfc4b98366dbfb4b63bc78a5812cce02201a0673af15edab604d9cd89f0e2842ccdd973e107ff9cd08dcf45d8c0b27c5dd0121039535d01e184b4a6d624e7ab007612e2558697fbed29274e6474f17e70d31ce5afcffffff0300e40b54020000001976a9146bb602e5e8eca75c7f6f25f766254658581db71688ac40490698010000001976a9149f64d0c07876a1dbce40cdce328bc7ecd8182b2288ac0000000000000000496a473d3a424e422e425553442d4244313a626e623173346b616c6c786e67707973707a6d366e72657a6b6d6c3972677977366b78707734666872323a3738393632373436383a743a3000000000"); + + // similar real transaction: + // https://viewblock.io/thorchain/tx/E7588A6A4C6B9DBA8B9AD8B0834655F9D9E5861744B5493E711623E320B981A5 + // https://dogechain.info/tx/e7588a6a4c6b9dba8b9ad8b0834655f9d9e5861744b5493e711623e320b981a5 + // https://binance.mintscan.io/txs/A5943D315BFD501DD5FC212F5A505772A20DDB154A8B5760A9897ABB8114CBDB +} + +TEST(THORChainSwap, SwapLtcBusd) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::LTC)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BNB)); + toAsset.set_symbol("BNB"); + toAsset.set_token_id("BUSD-BD1"); + + auto vaultLTC = "ltc1qmca5runvg3hygarulu34evdulcdfda7z7zquhn"; + auto fromAddressLTC = "ltc1qyu9qvkukx99r6yadxlk3t2x78a7dxe73s3r4x3"; + auto toAddressBnb = "bnb1s4kallxngpyspzm6nrezkml9rgyw6kxpw4fhr2"; + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(fromAddressLTC) + .toAddress(toAddressBnb) + .vault(vaultLTC) + .fromAmount("15000000") + .toAmountLimit("977240514") + .affFeeAddress("t") + .affFeeRate("0") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "080110c0c393071801222b6c746331716d63613572756e7667336879676172756c753334657664756c6364666461377a377a7175686e2a2b6c7463317179753971766b756b7839397236796164786c6b3374327837386137647865373373337234783350026a473d3a424e422e425553442d4244313a626e623173346b616c6c786e67707973707a6d366e72657a6b6d6c3972677977366b78707734666872323a3937373234303531343a743a30"); + + auto tx = Bitcoin::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.amount(), 15000000); + EXPECT_EQ(tx.to_address(), vaultLTC); + EXPECT_EQ(tx.change_address(), fromAddressLTC); + EXPECT_EQ(tx.output_op_return(), "=:BNB.BUSD-BD1:bnb1s4kallxngpyspzm6nrezkml9rgyw6kxpw4fhr2:977240514:t:0"); + EXPECT_EQ(tx.coin_type(), TWCoinTypeLitecoin); + EXPECT_EQ(tx.private_key_size(), 0); + EXPECT_FALSE(tx.has_plan()); + + // + auto ltcKey = parse_hex("6affb3d4e2c4f5a23b711e67ca94d0bd93550e203f5c8258df74cc62282d1494"); + tx.add_private_key(ltcKey.data(), ltcKey.size()); + tx.set_byte_fee(140); + auto& utxo = *tx.add_utxo(); + Data previousUTXOHash = parse_hex("6e71e6da1898584ccf92c362db3d7c16326f9daae6687132c69abfdb043cc749"); + std::reverse(previousUTXOHash.begin(), previousUTXOHash.end()); + utxo.mutable_out_point()->set_hash(previousUTXOHash.data(), previousUTXOHash.size()); + utxo.mutable_out_point()->set_index(0); + utxo.mutable_out_point()->set_sequence(UINT32_MAX - 3); + auto utxoScript = Bitcoin::Script::lockScriptForAddress(fromAddressLTC, TWCoinTypeLitecoin); + utxo.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + utxo.set_amount(34183600); + tx.set_use_max_amount(false); + + // sign and encode resulting input + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeLitecoin); + EXPECT_EQ(output.error(), 0); + EXPECT_EQ(hex(output.encoded()), "0100000000010149c73c04dbbf9ac6327168e6aa9d6f32167c3ddb62c392cf4c589818dae6716e0000000000fcffffff03c0e1e40000000000160014de3b41f26c446e44747cff235cb1bcfe1a96f7c2fc3d240100000000160014270a065b96314a3d13ad37ed15a8de3f7cd367d10000000000000000496a473d3a424e422e425553442d4244313a626e623173346b616c6c786e67707973707a6d366e72657a6b6d6c3972677977366b78707734666872323a3937373234303531343a743a3002483045022100fb9df5ef12c26648a50af298c5319ec52ea0287aa1405e07d817c606bb17a23502206520b087a9155a7d8c04b54b8ee3405fad9c3d22cf2c7cac06197ce555d56077012103acefb7d95b8c1da28f17400740d7e1124dbee3cfbe55646deb28198d570ea26b00000000"); + + // https://viewblock.io/thorchain/tx/FBB450335ED839C5FE3DCB9CBC0999DA6E6E52B787D1B165D3FA47E6273CCF5F + // https://blockchair.com/litecoin/transaction/fbb450335ed839c5fe3dcb9cbc0999da6e6e52b787d1b165d3fa47e6273ccf5f + // https://binance.mintscan.io/txs/7071DF040641D9C62EAA5D7AE5CDAC0C408FE64406261EC32417BD919684707C +} + +TEST(THORChainSwap, SwapBchBusd) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::BCH)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BNB)); + toAsset.set_symbol("BNB"); + toAsset.set_token_id("BUSD-BD1"); + + auto vaultBCH = "qpsfh5xvk7mgf9e6kl4e045nm6awl5hmks9x7h5ad6"; + auto fromAddressBCH = "qr50u7hy3xcr3j0w9j5nfx2gevjqgfm42ykc2hqgy4"; + auto toAddressBnb = "bnb1s4kallxngpyspzm6nrezkml9rgyw6kxpw4fhr2"; + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(fromAddressBCH) + .toAddress(toAddressBnb) + .vault(vaultBCH) + .fromAmount("10000000") + .toAmountLimit("977240514") + .affFeeAddress("t") + .affFeeRate("0") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "08411080ade2041801222a71707366683578766b376d67663965366b6c34653034356e6d3661776c35686d6b7339783768356164362a2a717235307537687933786372336a3077396a356e6678326765766a7167666d3432796b633268716779345091016a473d3a424e422e425553442d4244313a626e623173346b616c6c786e67707973707a6d366e72657a6b6d6c3972677977366b78707734666872323a3937373234303531343a743a30"); + + auto tx = Bitcoin::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.amount(), 10000000); + EXPECT_EQ(tx.to_address(), vaultBCH); + EXPECT_EQ(tx.change_address(), fromAddressBCH); + EXPECT_EQ(tx.output_op_return(), "=:BNB.BUSD-BD1:bnb1s4kallxngpyspzm6nrezkml9rgyw6kxpw4fhr2:977240514:t:0"); + EXPECT_EQ(tx.coin_type(), TWCoinTypeBitcoinCash); + EXPECT_EQ(tx.private_key_size(), 0); + EXPECT_FALSE(tx.has_plan()); + + // + auto bchKey = parse_hex("1a3b0105a08908734ed0525f4c6fadca068514cdeb732d7ebca5b0fcbe6952a7"); + tx.add_private_key(bchKey.data(), bchKey.size()); + tx.set_byte_fee(3); + auto& utxo = *tx.add_utxo(); + Data previousUTXOHash = parse_hex("651e5d3a60f8110a6cfb745005168bdfcaf21e7f2f4371873a24b5cd894564da"); + std::reverse(previousUTXOHash.begin(), previousUTXOHash.end()); + utxo.mutable_out_point()->set_hash(previousUTXOHash.data(), previousUTXOHash.size()); + utxo.mutable_out_point()->set_index(1); + utxo.mutable_out_point()->set_sequence(UINT32_MAX - 3); + auto utxoScript = Bitcoin::Script::lockScriptForAddress(fromAddressBCH, TWCoinTypeBitcoinCash); + utxo.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + utxo.set_amount(14118938); + tx.set_use_max_amount(false); + + // sign and encode resulting input + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeBitcoinCash); + EXPECT_EQ(output.error(), 0); + EXPECT_EQ(hex(output.encoded()), "0100000001da644589cdb5243a8771432f7f1ef2cadf8b16055074fb6c0a11f8603a5d1e65010000006a4730440220392fab53b86e02bef19638077fd378dd713dd6b1968d07f4507e28feb022d52a02200240bb2f2e8b8eb7673c4bc69b485e28a0d56c735d84e3f794c303c1b71759e941210393dc5157b5879cd602f25529437e01b3d4892a4b9b8d9efcaa640d842b27438efcffffff0380969800000000001976a914609bd0ccb7b684973ab7eb97d693debaefd2fbb488ac8ed63e00000000001976a914e8fe7ae489b038c9ee2ca9349948cb240427755188ac0000000000000000496a473d3a424e422e425553442d4244313a626e623173346b616c6c786e67707973707a6d366e72657a6b6d6c3972677977366b78707734666872323a3937373234303531343a743a3000000000"); + + // https://viewblock.io/thorchain/tx/B8AA6F2BFD09D7AC510BFCDA417903B2DDBEE0E9811821640D9C304B9B382B9B + // https://blockchair.com/bitcoin-cash/transaction/b8aa6f2bfd09d7ac510bfcda417903b2ddbee0e9811821640d9c304b9b382b9b + // https://binance.mintscan.io/txs/F4CD6554934E85D72269399607A7ADF8A92378C2287C164CC97CB57E8348B090 +} + +TEST(THORChainSwap, SwapBtcBnb) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::BTC)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BNB)); + toAsset.set_symbol("BNB"); + + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(Address1Btc) + .toAddress(Address1Bnb) + .vault(VaultBtc) + .fromAmount("200000") + .toAmountLimit("140000000") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "080110c09a0c1801222a62633171366d397532717375386d68387937763872723279776176746a38673561727a6c796863656a372a2a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070386a3e3d3a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a313430303030303030"); + + auto tx = Bitcoin::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.amount(), 200000); + EXPECT_EQ(tx.to_address(), VaultBtc); + EXPECT_EQ(tx.change_address(), Address1Btc); + EXPECT_EQ(tx.output_op_return(), "=:BNB.BNB:bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx:140000000"); + EXPECT_EQ(tx.coin_type(), 0ul); + EXPECT_EQ(tx.private_key_size(), 0); + EXPECT_FALSE(tx.has_plan()); + + // set few fields before signing + tx.set_byte_fee(80); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + tx.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); + auto& utxo = *tx.add_utxo(); + Data utxoHash = parse_hex("8eae5c3a4c75058d4e3facd5d72f18a40672bcd3d1f35ebf3094bd6c78da48eb"); + std::reverse(utxoHash.begin(), utxoHash.end()); + utxo.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); + utxo.mutable_out_point()->set_index(0); + utxo.mutable_out_point()->set_sequence(UINT32_MAX - 3); + auto utxoScript = Bitcoin::Script::lockScriptForAddress(Address1Btc, TWCoinTypeBitcoin); + utxo.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + utxo.set_amount(450000); + tx.set_use_max_amount(false); + + // sign and encode resulting input + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeBitcoin); + EXPECT_EQ(output.error(), 0); + EXPECT_EQ(hex(output.encoded()), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "eb48da786cbd9430bf5ef3d1d3bc7206a4182fd7d5ac3f4e8d05754c3a5cae8e" "00000000" "00" "" "fcffffff" + "03" // outputs + "400d030000000000" "16" "0014d6cbc5021c3eee72798718d447758b91d14e8c5f" + "b08d030000000000" "16" "00140cb9f5c6b62c03249367bc20a90dd2425e6926af" + "0000000000000000" "40" "6a3e3d3a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a313430303030303030" + // witness + "02" + "48" "3045022100e17d8cf207c79edfb7afa16102842b434e1f908bd9858553fd54970f1a8b4334022059583f89c3a126df0da46d92947bcbe7c265a1bb838b696c0e7ea7fc8761c2bf01210" + "21" "e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee" + "00000000" // nLockTime + ); + + // similar real transaction: + // https://blockchair.com/bitcoin/transaction/1cd9056b212b85d9d7d34d0795a746dd8691b8cd34ef56df0aa9622fbdec5f88 + // https://viewblock.io/thorchain/tx/1CD9056B212B85D9D7D34D0795A746DD8691B8CD34EF56DF0AA9622FBDEC5F88 + // https://explorer.binance.org/tx/8D78469069118E9B9546696214CCD46E63D3FA0D7E854C094D63C8F6061278B7 +} + +TEST(THORChainSwap, SwapUsdtBsc) { + auto myAddress = "0x0d6aA74992eDDaaf430eadca63B87f4C99Aef8dE"; + auto vaultAddress = "0x1f3b3c6ac151bf32409fe139a5d55f3d9444729c"; + auto routerAddress = "0xD37BbE5744D730a1d98d8DC97c42F0Ca46aD7146"; + auto usdtTokenId = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; + auto amount = 70000000; + auto expirationTime = 1775669796; + + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::ETH)); + fromAsset.set_symbol("USDT"); + fromAsset.set_token_id(usdtTokenId); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BSC)); + toAsset.set_symbol("BSC"); + toAsset.set_token_id("BNB"); + + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(myAddress) + .toAddress(myAddress) + .vault(vaultAddress) + .router(routerAddress) + .fromAmount(std::to_string(amount)) + .expirationPolicy(expirationTime) + .affFeeAddress("tr") + .affFeeRate("0") + .streamInterval("1") + .streamQuantity("0") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + + auto tx = Ethereum::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.to_address(), routerAddress); + ASSERT_TRUE(tx.transaction().has_contract_generic()); + + auto funcData = Ethereum::ABI::Function::encodeFunctionCall("depositWithExpiry", Ethereum::ABI::BaseParams{ + std::make_shared(vaultAddress), + std::make_shared(usdtTokenId), + std::make_shared(uint256_t(amount)), + std::make_shared("=:BSC.BNB:0x0d6aA74992eDDaaf430eadca63B87f4C99Aef8dE:0/1/0:tr:0"), + std::make_shared(uint256_t(expirationTime)) + }).value(); + EXPECT_EQ(hex(funcData), "44bc937b0000000000000000000000001f3b3c6ac151bf32409fe139a5d55f3d9444729c000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000042c1d8000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000069d69224000000000000000000000000000000000000000000000000000000000000003f3d3a4253432e424e423a3078306436614137343939326544446161663433306561646361363342383766344339394165663864453a302f312f303a74723a3000"); + EXPECT_EQ(hex(TW::data(tx.transaction().contract_generic().amount())), "00"); + EXPECT_EQ(hex(TW::data(tx.transaction().contract_generic().data())), hex(funcData)); +} + +TEST(THORChainSwap, SwapAtomBnb) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::ATOM)); + fromAsset.set_symbol("ATOM"); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BNB)); + toAsset.set_symbol("BNB"); + + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress("cosmos1v4e6vpehwrfez2dqepnw9g6t4fl83xzegd5ac9") + .toAddress("bnb1s4kallxngpyspzm6nrezkml9rgyw6kxpw4fhr2") + .vault("cosmos154t5ycejlr7ax3ynmed9z05yg5a27y9u6pj5hq") + .fromAmount("300000") + .toAmountLimit("819391") + .affFeeAddress("t") + .affFeeRate("0") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "08011a0b636f736d6f736875622d342a3f3d3a424e422e424e423a626e623173346b616c6c786e67707973707a6d366e72657a6b6d6c3972677977366b78707734666872323a3831393339313a743a3042710a6f0a2d636f736d6f73317634653676706568777266657a32647165706e773967367434666c3833787a65676435616339122d636f736d6f7331353474357963656a6c7237617833796e6d6564397a303579673561323779397536706a3568711a0f0a057561746f6d1206333030303030"); + + auto tx = Cosmos::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + ASSERT_EQ(tx.memo(), "=:BNB.BNB:bnb1s4kallxngpyspzm6nrezkml9rgyw6kxpw4fhr2:819391:t:0"); + + auto& fee = *tx.mutable_fee(); + fee.set_gas(200000); + auto& fee_amount = *fee.add_amounts(); + fee_amount.set_denom("uatom"); + fee_amount.set_amount("500"); + + tx.set_account_number(1483163); + tx.set_sequence(1); + + auto privKey = parse_hex("3eed3f32b8ba90e579ba46f37e7445fb4b34558306aa5bc32c525a93dff486e7"); + tx.set_private_key(privKey.data(), privKey.size()); + + Cosmos::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeCosmos); + EXPECT_EQ(output.error_message(), ""); + ASSERT_EQ(output.serialized(), "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CtMBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczF2NGU2dnBlaHdyZmV6MmRxZXBudzlnNnQ0Zmw4M3h6ZWdkNWFjORItY29zbW9zMTU0dDV5Y2VqbHI3YXgzeW5tZWQ5ejA1eWc1YTI3eTl1NnBqNWhxGg8KBXVhdG9tEgYzMDAwMDASPz06Qk5CLkJOQjpibmIxczRrYWxseG5ncHlzcHptNm5yZXprbWw5cmd5dzZreHB3NGZocjI6ODE5MzkxOnQ6MBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDmmNIYBvR9bnOloFEMOWdk9DHYIGe7naW0T19y+/k1SUSBAoCCAEYARISCgwKBXVhdG9tEgM1MDAQwJoMGkCFqUWtDu0pn1P/cnVQnIJIWF8HFJn/xkJh55Mc7ZLVPF60uXYUOg8nNkt0IQPuTFREw32/yff6lmA5w6KwPen/\"}"); + + // https://viewblock.io/thorchain/tx/07F47D71A74245538E205F24ADB4BBB799B49C3A8A8875665D249EA51662AA50 + // https://www.mintscan.io/cosmos/txs/07F47D71A74245538E205F24ADB4BBB799B49C3A8A8875665D249EA51662AA50 + // https://binance.mintscan.io/txs/2C97061737B16B234990B9B18A2BF65F7C7418FF9E39A68E634C832E4E4C59CE +} + +Data SwapTest_ethAddressStringToData(const std::string& asString) { + if (asString.empty()) { + return Data(); + } + auto address = Ethereum::Address(asString); + Data asData; + asData.resize(20); + std::copy(address.bytes.begin(), address.bytes.end(), asData.data()); + return asData; +} + +TEST(THORChainSwap, SwapErc20Rune) { + Proto::Asset fromAsset; + fromAsset.set_token_id("0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E"); + fromAsset.set_chain(static_cast(Chain::AVAX)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::THOR)); + toAsset.set_symbol("RUNE"); + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress("0xbe6523017422A983B900b614Baeac51Ef7C1d0A3") + .toAddress("thor1ad6hapypumu7su5ad9qry2d74yt9d56fssa774") + .vault("0xa56f6Cb1D66cd80150b1ea79643b4C5900D6E36E") + .router("0x8f66c4ae756bebc49ec8b81966dd8bba9f127549") + .fromAmount("1000000") + .toAmountLimit("51638857") + .expirationPolicy(1775669796) + .affFeeAddress("t") + .affFeeRate("0") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "0a010012010018012201002a0100422a30783866363663346165373536626562633439656338623831393636646438626261396631323735343952ad0232aa020a010012a40244bc937b000000000000000000000000a56f6cb1d66cd80150b1ea79643b4c5900d6e36e000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000000f424000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000069d6922400000000000000000000000000000000000000000000000000000000000000443d3a54484f522e52554e453a74686f72316164366861707970756d753773753561643971727932643734797439643536667373613737343a35313633383835373a743a3000000000000000000000000000000000000000000000000000000000"); + + auto tx = Ethereum::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.to_address(), "0x8f66c4ae756bebc49ec8b81966dd8bba9f127549"); + ASSERT_TRUE(tx.transaction().has_contract_generic()); + + auto vaultAddress = "0xa56f6Cb1D66cd80150b1ea79643b4C5900D6E36E"; + auto funcData = Ethereum::ABI::Function::encodeFunctionCall("depositWithExpiry", Ethereum::ABI::BaseParams{ + std::make_shared(vaultAddress), + std::make_shared("0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E"), + std::make_shared(uint256_t(1000000)), + std::make_shared("=:THOR.RUNE:thor1ad6hapypumu7su5ad9qry2d74yt9d56fssa774:51638857:t:0"), + std::make_shared(uint256_t(1775669796)) + }).value(); + EXPECT_EQ(hex(funcData), "44bc937b000000000000000000000000a56f6cb1d66cd80150b1ea79643b4c5900d6e36e000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000000f424000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000069d6922400000000000000000000000000000000000000000000000000000000000000443d3a54484f522e52554e453a74686f72316164366861707970756d753773753561643971727932643734797439643536667373613737343a35313633383835373a743a3000000000000000000000000000000000000000000000000000000000"); + EXPECT_EQ(hex(TW::data(tx.transaction().contract_generic().amount())), "00"); + EXPECT_EQ(hex(TW::data(tx.transaction().contract_generic().data())), hex(funcData)); + + EXPECT_EQ(hex(TW::data(tx.private_key())), ""); + + // set few fields before signing + auto chainId = store(uint256_t(43114)); + tx.set_chain_id(chainId.data(), chainId.size()); + auto nonce = store(uint256_t(6)); + tx.set_nonce(nonce.data(), nonce.size()); + auto maxInclusionFeePerGas = store(uint256_t(2000000000)); + auto maxFeePerGas = store(uint256_t(25000000000)); + tx.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); + tx.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); + auto gasLimit = store(uint256_t(108810)); + tx.set_gas_limit(gasLimit.data(), gasLimit.size()); + auto privKey = parse_hex("6649ba1d931059e7b419f97ee41c3f98b8f8054dfeb4cb57b9898bc5b9bbe318"); + tx.set_private_key(privKey.data(), privKey.size()); + + // sign and encode resulting input + Ethereum::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeAvalancheCChain); + EXPECT_EQ(hex(output.encoded()), "02f9019482a86a0684773594008505d21dba008301a90a948f66c4ae756bebc49ec8b81966dd8bba9f12754980b9012444bc937b000000000000000000000000a56f6cb1d66cd80150b1ea79643b4c5900d6e36e000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000000f424000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000069d6922400000000000000000000000000000000000000000000000000000000000000443d3a54484f522e52554e453a74686f72316164366861707970756d753773753561643971727932643734797439643536667373613737343a35313633383835373a743a3000000000000000000000000000000000000000000000000000000000c080a04a3a01941906579f1c6888771fe0621d66ee78998bfbb87219c0b5970235fc5ca03aefe4bb0c074f90798e078270c380930f4ae75366217f85535dd9be196a4244"); + // https://viewblock.io/thorchain/tx/B5E88D61157E7073995CA8729B75DAB2C1684A7B145DB711327CA4B8FF7DBDE7 + // https://snowtrace.io/tx/0xb5e88d61157e7073995ca8729b75dab2c1684a7b145db711327ca4b8ff7dbde7 + // https://thorchain.net/tx/B5E88D61157E7073995CA8729B75DAB2C1684A7B145DB711327CA4B8FF7DBDE7 +} + +TEST(THORChainSwap, SwapBscBnb) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::BSC)); + fromAsset.set_token_id("0x0000000000000000000000000000000000000000"); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BNB)); + toAsset.set_symbol("BNB"); + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress("0xf8192E9c51c070d199a8F262c12DDD1034274083") + .toAddress("bnb1tjcup6q8nere6r0pdt2ucc4g0xcrhm0jy5xql8") + .vault("0xcBE4334E4a0fC7C5Fa8083223B28a4b9F695A06C") + .router("0xb30eC53F98ff5947EDe720D32aC2da7e52A5f56b") + .fromAmount("10000000000000000") + .toAmountLimit("100000") + .expirationPolicy(1775669796) + .affFeeAddress("t") + .affFeeRate("0") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "0a01001201002201002a0100422a3078623330654335334639386666353934374544653732304433326143326461376535324135663536625293023290020a072386f26fc1000012840244bc937b000000000000000000000000cbe4334e4a0fc7c5fa8083223b28a4b9f695a06c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000069d69224000000000000000000000000000000000000000000000000000000000000003f3d3a424e422e424e423a626e6231746a6375703671386e65726536723070647432756363346730786372686d306a793578716c383a3130303030303a743a3000"); + + auto tx = Ethereum::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + + // check fields + EXPECT_EQ(tx.to_address(), "0xb30eC53F98ff5947EDe720D32aC2da7e52A5f56b"); + ASSERT_TRUE(tx.transaction().has_contract_generic()); + + EXPECT_EQ(hex(TW::data(tx.private_key())), ""); + + // set few fields before signing + auto chainId = store(uint256_t(56)); + tx.set_chain_id(chainId.data(), chainId.size()); + auto nonce = store(uint256_t(0)); + tx.set_nonce(nonce.data(), nonce.size()); + // 0,000000001 + auto gasPrice = store(uint256_t(3000000000)); + tx.set_gas_price(gasPrice.data(), gasPrice.size()); + auto gasLimit = store(uint256_t(50000)); + tx.set_gas_limit(gasLimit.data(), gasLimit.size()); + auto privKey = parse_hex("74c452b55e0da4139172bc3b32bec469cfefbcdce373edda8e33afcfbf9c0a87"); + tx.set_private_key(privKey.data(), privKey.size()); + + // sign and encode resulting input + Ethereum::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeSmartChain); + EXPECT_EQ(hex(output.encoded()), "f901718084b2d05e0082c35094b30ec53f98ff5947ede720d32ac2da7e52a5f56b872386f26fc10000b9010444bc937b000000000000000000000000cbe4334e4a0fc7c5fa8083223b28a4b9f695a06c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000069d69224000000000000000000000000000000000000000000000000000000000000003f3d3a424e422e424e423a626e6231746a6375703671386e65726536723070647432756363346730786372686d306a793578716c383a3130303030303a743a30008194a05b0032d4150a3fa3b39a047648c02cb44b3256b9c34b7780265643c33d2aa2c6a017fece0465a271b7bddf655f7ac77419fb0433f9acf64b455b9aa17183b6eb98"); + // https://viewblock.io/thorchain/tx/4292A5068BAA5619CF7A35861058915423688DF3CAE8F241453D8FCC6E0BF0A9 + // https://bscscan.com/tx/0x4292a5068baa5619cf7a35861058915423688df3cae8f241453d8fcc6e0bf0a9 + // https://explorer.bnbchain.org/tx/88A1B6F9D64F3B48CE1107979CD325E817446C5D6729EE6FC917589A6FADA79D +} + +TEST(THORChainSwap, SwapAvaxBnb) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::AVAX)); + fromAsset.set_token_id("0x0000000000000000000000000000000000000000"); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BNB)); + toAsset.set_symbol("BNB"); + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress("0xbB7cF2f05a01DB5394234FE1257D907059edFa66") + .toAddress("bnb16gk7gczst59wy8rnxrqnt3yn6f60uw6ec0w6uv") + .vault("0x3bd92906c60e5843ce01b2dc54e6dc3575b5215a") + .router("0x8f66c4ae756bebc49ec8b81966dd8bba9f127549") + .fromAmount("150000000000000000") + .toAmountLimit("297039") + .expirationPolicy(1775669796) + .affFeeAddress("t") + .affFeeRate("0") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "0a010012010018012201002a0100422a3078386636366334616537353662656263343965633862383139363664643862626139663132373534395294023291020a080214e8348c4f000012840244bc937b0000000000000000000000003bd92906c60e5843ce01b2dc54e6dc3575b5215a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000214e8348c4f000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000069d69224000000000000000000000000000000000000000000000000000000000000003f3d3a424e422e424e423a626e623136676b3767637a73743539777938726e7872716e7433796e36663630757736656330773675763a3239373033393a743a3000"); + + auto tx = Ethereum::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.to_address(), "0x8f66c4ae756bebc49ec8b81966dd8bba9f127549"); + ASSERT_TRUE(tx.transaction().has_contract_generic()); + + EXPECT_EQ(hex(TW::data(tx.private_key())), ""); + + // set few fields before signing + auto chainId = store(uint256_t(43114)); + tx.set_chain_id(chainId.data(), chainId.size()); + auto nonce = store(uint256_t(5)); + tx.set_nonce(nonce.data(), nonce.size()); + auto maxInclusionFeePerGas = store(uint256_t(2000000000)); + auto maxFeePerGas = store(uint256_t(25000000000)); + tx.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); + tx.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); + auto gasLimit = store(uint256_t(108810)); + tx.set_gas_limit(gasLimit.data(), gasLimit.size()); + auto privKey = parse_hex("09da019c250b7e2b140645df36fd839806c5ae8eecf4d8f35e8ff57cf3bd1e57"); + tx.set_private_key(privKey.data(), privKey.size()); + + // sign and encode resulting input + Ethereum::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeAvalancheCChain); + EXPECT_EQ(hex(output.encoded()), "02f9017c82a86a0584773594008505d21dba008301a90a948f66c4ae756bebc49ec8b81966dd8bba9f127549880214e8348c4f0000b9010444bc937b0000000000000000000000003bd92906c60e5843ce01b2dc54e6dc3575b5215a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000214e8348c4f000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000069d69224000000000000000000000000000000000000000000000000000000000000003f3d3a424e422e424e423a626e623136676b3767637a73743539777938726e7872716e7433796e36663630757736656330773675763a3239373033393a743a3000c080a0a794b7cd86242df0f69bfc2555adec7841ad1f3a02e478be0d63571da8d41f20a06cd214b052d2a2aee598c2d3d57a972979e4c49a447c52828657101e9ad39737"); + // https://viewblock.io/thorchain/tx/8A29B132443BF1B0A0BD3E00F8155D10FEEEC7737BDC912C4A1AFB0A52E4FD4F + // https://snowtrace.io/tx/0x8A29B132443BF1B0A0BD3E00F8155D10FEEEC7737BDC912C4A1AFB0A52E4FD4F + // https://binance.mintscan.io/txs/9D250C8BAC8205B942A597AFB345045439A55CAB8DD588B75870D4E47D751C16 +} + +TEST(THORChainSwap, SwapEthBnb) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::ETH)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BNB)); + toAsset.set_symbol("BNB"); + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(Address1Eth) + .toAddress(Address1Bnb) + .vault(VaultEth) + .fromAmount("50000000000000000") + .toAmountLimit("600003") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "0a010012010018012201002a0100422a30783130393163344465366133634630394364413030416244416544343263376333423639433833454352480a460a07b1a2bc2ec50000123b3d3a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a363030303033"); + + auto tx = Ethereum::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.to_address(), VaultEth); + ASSERT_FALSE(tx.transaction().has_contract_generic()); + + EXPECT_EQ(hex(TW::data(tx.private_key())), ""); + + // set few fields before signing + auto chainId = store(uint256_t(1)); + tx.set_chain_id(chainId.data(), chainId.size()); + auto nonce = store(uint256_t(3)); + tx.set_nonce(nonce.data(), nonce.size()); + auto gasPrice = store(uint256_t(30000000000)); + tx.set_gas_price(gasPrice.data(), gasPrice.size()); + auto gasLimit = store(uint256_t(80000)); + tx.set_gas_limit(gasLimit.data(), gasLimit.size()); + tx.set_private_key(""); + tx.set_private_key(TestKey1Eth.data(), TestKey1Eth.size()); + + // sign and encode resulting input + Ethereum::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeEthereum); + EXPECT_EQ(hex(output.encoded()), "02f8a60103808083013880941091c4de6a3cf09cda00abdaed42c7c3b69c83ec87b1a2bc2ec50000b83b3d3a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a363030303033c080a00d605807f983650fafbfdcf0c33bdf0c524c7185eae8c1501ae24892faf16b1ba03b51b0a35e4754ab21d1e48fed635d8486048df50c253ba9af4cebdb6a92a450"); +} + +TEST(THORChainSwap, SwapBnbBtc) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::BNB)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BTC)); + toAsset.set_symbol("BTC"); + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(Address1Bnb) + .toAddress(Address1Btc) + .vault(VaultBnb) + .fromAmount("10000000") + .toAmountLimit("10000000") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "2a3d3d3a4254432e4254433a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070383a313030303030303052480a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e421080ade204"); + + auto tx = Binance::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.memo(), "=:BTC.BTC:bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8:10000000"); + ASSERT_TRUE(tx.has_send_order()); + ASSERT_EQ(tx.send_order().inputs_size(), 1); + ASSERT_EQ(tx.send_order().outputs_size(), 1); + EXPECT_EQ(hex(tx.send_order().inputs(0).address()), "e42be736e933cf8b97c26f33789a3ca6f8348cd1"); + EXPECT_EQ(hex(tx.send_order().outputs(0).address()), "99730371c7c77cb81ffa76b566dcef7c1e5dc19c"); + EXPECT_EQ(hex(TW::data(tx.private_key())), ""); + + // set few fields before signing + tx.set_chain_id("Binance-Chain-Tigris"); + tx.set_private_key(TestKey1Bnb.data(), TestKey1Bnb.size()); + + // sign and encode resulting input + Binance::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeBinance); + EXPECT_EQ(hex(output.encoded()), "fd01f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e421080ade204126a0a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e19124086d43e9bdf12508a9a1415f5f970dfa5ff5930dee01d922f99779b63190735ba1d69694bda203b6678939a5c1eab0a52ed32bb67864ec7864de37b333533ae0c1a3d3d3a4254432e4254433a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070383a3130303030303030"); +} + +TEST(THORChainSwap, SwapBnbEth) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::BNB)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::ETH)); + toAsset.set_symbol("ETH"); + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(Address1Bnb) + .toAddress(Address1Eth) + .vault(VaultBnb) + .fromAmount("27000000") + .toAmountLimit("123456") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "2a3b3d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a31323334353652480a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e4210c0f9ef0c12220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e4210c0f9ef0c"); + + auto tx = Binance::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.memo(), "=:ETH.ETH:0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7:123456"); + ASSERT_TRUE(tx.has_send_order()); + ASSERT_EQ(tx.send_order().inputs_size(), 1); + ASSERT_EQ(tx.send_order().outputs_size(), 1); + EXPECT_EQ(hex(tx.send_order().inputs(0).address()), "e42be736e933cf8b97c26f33789a3ca6f8348cd1"); + EXPECT_EQ(hex(tx.send_order().outputs(0).address()), "99730371c7c77cb81ffa76b566dcef7c1e5dc19c"); + EXPECT_EQ(hex(TW::data(tx.private_key())), ""); + + // set private key and few other fields + EXPECT_EQ(TW::deriveAddress(TWCoinTypeBinance, PrivateKey(TestKey1Bnb)), Address1Bnb); + tx.set_private_key(TestKey1Bnb.data(), TestKey1Bnb.size()); + tx.set_chain_id("Binance-Chain-Tigris"); + tx.set_account_number(1902570); + tx.set_sequence(12); + // sign and encode resulting input + Binance::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeBinance); + EXPECT_EQ(hex(output.encoded()), "8102f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e4210c0f9ef0c12220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e4210c0f9ef0c12700a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e1912409ad3d44f3cc8d5dd2701b0bf3758ef674683533fb63e3e94d39728688c0279f8410395d631075dac62dee74b972c320f5a58e88ab81be6f1bb6a9564468ae1b618ea8f74200c1a3b3d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a313233343536"); + + // real transaction: + // https://explorer.binance.org/tx/F0CFDB0D9467E83B5BBF6DF92E4E2D04FE9EFF9B0A1C71D88DCEF566233DCAA2 + // https://viewblock.io/thorchain/tx/F0CFDB0D9467E83B5BBF6DF92E4E2D04FE9EFF9B0A1C71D88DCEF566233DCAA2 + // https://etherscan.io/tx/0x8e5bb7d87e17af86e649e402bc5c182ea8c32ddaca153804679de1184e0d9747 +} + +TEST(THORChainSwap, SwapBnbRune) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::BNB)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::THOR)); + toAsset.set_symbol("RUNE"); + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(Address1Bnb) + .toAddress(Address1Thor) + .vault(VaultBnb) + .fromAmount("4000000") + .toAmountLimit("121065076") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "2a413d3a54484f522e52554e453a74686f72317a3533777765376d64366365777a39737177717a6e306161767061756e3067773065786e32723a31323130363530373652480a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e42108092f40112220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e42108092f401"); + + auto tx = Binance::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.memo(), "=:THOR.RUNE:thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r:121065076"); + ASSERT_TRUE(tx.has_send_order()); + ASSERT_EQ(tx.send_order().inputs_size(), 1); + ASSERT_EQ(tx.send_order().outputs_size(), 1); + EXPECT_EQ(hex(tx.send_order().inputs(0).address()), "e42be736e933cf8b97c26f33789a3ca6f8348cd1"); + EXPECT_EQ(hex(tx.send_order().outputs(0).address()), "99730371c7c77cb81ffa76b566dcef7c1e5dc19c"); + EXPECT_EQ(hex(TW::data(tx.private_key())), ""); + + // set private key and few other fields + EXPECT_EQ(TW::deriveAddress(TWCoinTypeBinance, PrivateKey(TestKey1Bnb)), Address1Bnb); + tx.set_private_key(TestKey1Bnb.data(), TestKey1Bnb.size()); + tx.set_chain_id("Binance-Chain-Tigris"); + tx.set_account_number(1902570); + tx.set_sequence(4); + // sign and encode resulting input + Binance::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeBinance); + EXPECT_EQ(hex(output.encoded()), "8702f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e42108092f40112220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e42108092f40112700a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e191240f0bd5a0b4936ce73b1564f737a22cb7cfa3c171a3598b1fe42f6c926c516777042673f3b30148d54b591dcfcb88c2aa04bb87b4b492e8d17c72e4d263f57159018ea8f7420041a413d3a54484f522e52554e453a74686f72317a3533777765376d64366365777a39737177717a6e306161767061756e3067773065786e32723a313231303635303736"); + + // real transaction: + // https://explorer.binance.org/tx/84EE429B35945F0568097527A084532A9DE7BBAB0E6A5562E511CEEFB188DE69 + // https://viewblock.io/thorchain/tx/D582E1473FE229F02F162055833C64F49FB4FF515989A4785ED7898560A448FC +} + +TEST(THORChainSwap, SwapBusdTokenBnb) { + Proto::Asset fromAsset; + fromAsset.set_symbol("BNB"); + fromAsset.set_token_id("BUSD-BD1"); + fromAsset.set_chain(static_cast(Chain::BNB)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BNB)); + toAsset.set_symbol("BNB"); + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress("bnb1gddl87crh47wzynjx3c6pmcclzk7txlkm74x28") + .toAddress("bnb1gddl87crh47wzynjx3c6pmcclzk7txlkm74x28") + .vault("bnb17e9qd0ffrkxsy9pehx7q6hjer730pzq5z4tv82") + .fromAmount("500000000") + .toAmountLimit("719019") + .affFeeAddress("t") + .affFeeRate("0") + .build(false); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "2a42535741503a424e422e424e423a626e62316764646c38376372683437777a796e6a78336336706d63636c7a6b3774786c6b6d37347832383a3731393031393a743a3052540a280a14435bf3fb03bd7ce112723471a0ef18f8ade59bf612100a08425553442d4244311080cab5ee0112280a14f64a06bd291d8d021439b9bc0d5e591fa2f0881412100a08425553442d4244311080cab5ee01"); + + auto tx = Binance::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.memo(), "SWAP:BNB.BNB:bnb1gddl87crh47wzynjx3c6pmcclzk7txlkm74x28:719019:t:0"); + ASSERT_TRUE(tx.has_send_order()); + ASSERT_EQ(tx.send_order().inputs_size(), 1); + ASSERT_EQ(tx.send_order().outputs_size(), 1); + EXPECT_EQ(hex(tx.send_order().inputs(0).address()), "435bf3fb03bd7ce112723471a0ef18f8ade59bf6"); + EXPECT_EQ(hex(tx.send_order().outputs(0).address()), "f64a06bd291d8d021439b9bc0d5e591fa2f08814"); + EXPECT_EQ(hex(TW::data(tx.private_key())), ""); + + // set private key and few other fields + const Data privateKey = parse_hex("412c379cccf9d792238f0a8bd923604e00c2be11ea1de715945f6a849796362a"); + EXPECT_EQ(Binance::Address(PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "bnb1gddl87crh47wzynjx3c6pmcclzk7txlkm74x28"); + tx.set_private_key(privateKey.data(), privateKey.size()); + tx.set_chain_id("Binance-Chain-Tigris"); + tx.set_account_number(7320332); + tx.set_sequence(2); + + // sign and encode resulting input + Binance::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeBinance); + EXPECT_EQ(hex(output.encoded()), "9502f0625dee0a582a2c87fa0a280a14435bf3fb03bd7ce112723471a0ef18f8ade59bf612100a08425553442d4244311080cab5ee0112280a14f64a06bd291d8d021439b9bc0d5e591fa2f0881412100a08425553442d4244311080cab5ee0112710a26eb5ae98721039aa92707d6789692628099f288de219c9c9a0dd179df4e8b1b717191c75fbbfb1240fb41cf3eaaf1286de4be633682c120886b39dcc41690b583f4f08561d660a1677ebda2323e0f22c440c6fe8855d21f1153557b94066ce956363f0a82d1ab3c92188ce6be0320021a42535741503a424e422e424e423a626e62316764646c38376372683437777a796e6a78336336706d63636c7a6b3774786c6b6d37347832383a3731393031393a743a30"); + + // https://viewblock.io/thorchain/tx/1B7E472C7C8D60176FCFD83CAD7DA970EB12B45145C553CD37BD34CABE276C59 + // https://explorer.bnbchain.org/tx/1B7E472C7C8D60176FCFD83CAD7DA970EB12B45145C553CD37BD34CABE276C59 + // https://explorer.bnbchain.org/tx/79D2194584F498CA2D4C391FBD7B158FC94B670703B629CA6F46852BB24234A6 +} + +TEST(THORChainSwap, SwapBnbBnbToken) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::BNB)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BNB)); + toAsset.set_symbol("BNB"); + toAsset.set_token_id("TWT-8C2"); + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress("bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx") + .toAddress("bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx") + .vault("bnb1qefsjm654cdw94ejj8g4s49w7z8te75veslusz") + .fromAmount("10000000") + .toAmountLimit("5400000000") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "2a433d3a424e422e5457542d3843323a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a3534303030303030303052480a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a140653096f54ae1ae2d73291d15854aef08ebcfa8c120a0a03424e421080ade204"); + + auto tx = Binance::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.memo(), "=:BNB.TWT-8C2:bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx:5400000000"); + ASSERT_TRUE(tx.has_send_order()); + ASSERT_EQ(tx.send_order().inputs_size(), 1); + ASSERT_EQ(tx.send_order().outputs_size(), 1); + EXPECT_EQ(hex(tx.send_order().inputs(0).address()), "e42be736e933cf8b97c26f33789a3ca6f8348cd1"); + EXPECT_EQ(hex(tx.send_order().outputs(0).address()), "0653096f54ae1ae2d73291d15854aef08ebcfa8c"); + EXPECT_EQ(hex(TW::data(tx.private_key())), ""); + + // set private key and few other fields + const Data privateKey = parse_hex("bcf8b072560dda05122c99390def2c385ec400e1a93df0657a85cf6b57a715da"); + EXPECT_EQ(Binance::Address(PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx"); + tx.set_private_key(privateKey.data(), privateKey.size()); + tx.set_chain_id("Binance-Chain-Tigris"); + tx.set_account_number(1902570); + tx.set_sequence(18); + + // sign and encode resulting input + Binance::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeBinance); + EXPECT_EQ(hex(output.encoded()), "8902f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a140653096f54ae1ae2d73291d15854aef08ebcfa8c120a0a03424e421080ade20412700a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e191240918963970aedc528e3a9ba34f37fb544ec18e7d2caade2ebf7b8371928c93e6e0eca072313ddfda393c1340766d5fef00e6b0cb7147ef3382b6303f3a6ca01a318ea8f7420121a433d3a424e422e5457542d3843323a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a35343030303030303030"); + + // real transaction: + // curl -X GET "http://dataseed1.binance.org/broadcast_tx_sync?tx=0x8c02...3030" + // https://viewblock.io/thorchain/tx/6D1EDC9BD9BFAFEF0F88F95A164191262EA02A0413BF3D9773110AD5676E1523 + // https://explorer.binance.org/tx/6D1EDC9BD9BFAFEF0F88F95A164191262EA02A0413BF3D9773110AD5676E1523 + // https://explorer.binance.org/tx/60C54C9F253B89C36A2788AB66951045E8AC5F5729597CB6C64A13013A7A54CC +} + +TEST(THORChainSwap, SwapBtcEthWithAffFee) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::BTC)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::ETH)); + toAsset.set_symbol("ETH"); + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(Address1Btc) + .toAddress(Address1Eth) + .vault(VaultBtc) + .fromAmount("1000000") + .toAmountLimit("140000000000000000") + .affFeeAddress("thrnm") + .affFeeRate("10") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "080110c0843d1801222a62633171366d397532717375386d68387937763872723279776176746a38673561727a6c796863656a372a2a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070386a503d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a3134303030303030303030303030303030303a7468726e6d3a3130"); + + auto tx = Bitcoin::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.amount(), 1000000); + EXPECT_EQ(tx.to_address(), VaultBtc); + EXPECT_EQ(tx.change_address(), Address1Btc); + EXPECT_EQ(tx.output_op_return(), "=:ETH.ETH:0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7:140000000000000000:thrnm:10"); + EXPECT_EQ(tx.coin_type(), 0ul); + EXPECT_EQ(tx.private_key_size(), 0); + EXPECT_FALSE(tx.has_plan()); + + // set few fields before signing + tx.set_byte_fee(20); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + tx.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); + auto& utxo = *tx.add_utxo(); + Data utxoHash = parse_hex("1234000000000000000000000000000000000000000000000000000000005678"); + utxo.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); + utxo.mutable_out_point()->set_index(0); + utxo.mutable_out_point()->set_sequence(UINT32_MAX); + auto utxoScript = Bitcoin::Script::lockScriptForAddress(Address1Btc, TWCoinTypeBitcoin); + utxo.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + utxo.set_amount(50000000); + tx.set_use_max_amount(false); + + // sign and encode resulting input + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeBitcoin); + EXPECT_EQ(output.error(), 0); + EXPECT_EQ(hex(output.encoded()), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "1234000000000000000000000000000000000000000000000000000000005678" "00000000" "00" "" "ffffffff" + "03" // outputs + "40420f0000000000" "16" "0014d6cbc5021c3eee72798718d447758b91d14e8c5f" + "0c9ceb0200000000" "16" "00140cb9f5c6b62c03249367bc20a90dd2425e6926af" + "0000000000000000" "53" "6a4c503d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a3134303030303030303030303030303030303a7468726e6d3a3130" + // witness + "02" + "47" "3044022056e918d8dea9431057b7b8b7f7c990ff72d653aef296eda9a85e546537e1eaa4022050b64766ea4ce56ecd3325f184d67b20924fd4539cb40bbad916ede1cc26017f01" + "21" "021e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee" + "00000000" // nLockTime + ); +} + +TEST(THORChainSwap, SwapEthBnbWithAffFee) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::ETH)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BNB)); + toAsset.set_symbol("BNB"); + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(Address1Eth) + .toAddress(Address1Bnb) + .vault(VaultEth) + .fromAmount("50000000000000000") + .toAmountLimit("600003") + .affFeeAddress("tthor1ql2tcqyrqsgnql2tcqyj2n8kfdmt9lh0yzql2tcqy") + .affFeeRate("10") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "0a010012010018012201002a0100422a307831303931633444653661336346303943644130304162444165443432633763334236394338334543527b0a790a07b1a2bc2ec50000126e3d3a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a3630303030333a7474686f7231716c3274637179727173676e716c32746371796a326e386b66646d74396c6830797a716c32746371793a3130"); + + auto tx = Ethereum::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.to_address(), VaultEth); + ASSERT_FALSE(tx.transaction().has_contract_generic()); + + EXPECT_EQ(hex(TW::data(tx.private_key())), ""); + + // set few fields before signing + auto chainId = store(uint256_t(1)); + tx.set_chain_id(chainId.data(), chainId.size()); + auto nonce = store(uint256_t(3)); + tx.set_nonce(nonce.data(), nonce.size()); + auto gasPrice = store(uint256_t(30000000000)); + tx.set_gas_price(gasPrice.data(), gasPrice.size()); + auto gasLimit = store(uint256_t(80000)); + tx.set_gas_limit(gasLimit.data(), gasLimit.size()); + tx.set_private_key(""); + tx.set_private_key(TestKey1Eth.data(), TestKey1Eth.size()); + + // sign and encode resulting input + Ethereum::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeEthereum); + EXPECT_EQ(hex(output.encoded()), "02f8d90103808083013880941091c4de6a3cf09cda00abdaed42c7c3b69c83ec87b1a2bc2ec50000b86e3d3a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a3630303030333a7474686f7231716c3274637179727173676e716c32746371796a326e386b66646d74396c6830797a716c32746371793a3130c001a05c16871b66fd0fa8f658d6f171310bab332d09e0533d6c97329a59ddc93a9a11a05ed2be94e6dbb640e58920c8be4fa597cd5f0a918123245acb899042dd43777f"); +} + +TEST(THORChainSwap, SwapBtcNegativeMemoTooLong) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::BTC)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::ETH)); + toAsset.set_symbol("ETH"); + auto&& [out, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(Address1Btc) + .toAddress(Address1Eth) + .vault(VaultBtc) + .fromAmount("1000000") + .toAmountLimit("140000000000000000") + .affFeeAddress("affiliate_address") + .affFeeRate("10") + .extraMemo("extra_memo_very_loooooooooooooong") + .build(); + ASSERT_EQ(errorCode, 0); + ASSERT_EQ(error, ""); + EXPECT_EQ(hex(out), "080110c0843d1801222a62633171366d397532717375386d68387937763872723279776176746a38673561727a6c796863656a372a2a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070386a7e3d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a3134303030303030303030303030303030303a616666696c696174655f616464726573733a31303a65787472615f6d656d6f5f766572795f6c6f6f6f6f6f6f6f6f6f6f6f6f6f6f6e67"); + + auto tx = Bitcoin::Proto::SigningInput(); + ASSERT_TRUE(tx.ParseFromArray(out.data(), (int)out.size())); + + // check fields + EXPECT_EQ(tx.amount(), 1000000); + EXPECT_EQ(tx.to_address(), VaultBtc); + EXPECT_EQ(tx.change_address(), Address1Btc); + EXPECT_EQ(tx.output_op_return(), "=:ETH.ETH:0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7:140000000000000000:affiliate_address:10:extra_memo_very_loooooooooooooong"); + EXPECT_EQ(tx.output_op_return().length(), 126ul); + EXPECT_EQ(tx.coin_type(), 0ul); + EXPECT_EQ(tx.private_key_size(), 0); + EXPECT_FALSE(tx.has_plan()); + + // set few fields before signing + tx.set_byte_fee(20); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + tx.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); + auto& utxo = *tx.add_utxo(); + Data utxoHash = parse_hex("1234000000000000000000000000000000000000000000000000000000005678"); + utxo.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); + utxo.mutable_out_point()->set_index(0); + utxo.mutable_out_point()->set_sequence(UINT32_MAX); + auto utxoScript = Bitcoin::Script::lockScriptForAddress(Address1Btc, TWCoinTypeBitcoin); + utxo.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + utxo.set_amount(50000000); + tx.set_use_max_amount(false); + + // sign and encode resulting input + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeBitcoin); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_memo); + EXPECT_EQ(hex(output.encoded()), ""); +} + +TEST(THORChainSwap, Memo) { + Proto::Asset toAssetBTC; + toAssetBTC.set_chain(static_cast(Chain::BTC)); + toAssetBTC.set_symbol("BTC"); + auto builder = SwapBuilder::builder().to(toAssetBTC).toAddress("btc123").toAmountLimit("1234"); + EXPECT_EQ(builder.buildMemo(), "=:BTC.BTC:btc123:1234"); + EXPECT_EQ(builder.affFeeAddress("feeaddr").buildMemo(), "=:BTC.BTC:btc123:1234:feeaddr"); + EXPECT_EQ(builder.affFeeRate("10").buildMemo(), "=:BTC.BTC:btc123:1234:feeaddr:10"); + EXPECT_EQ(builder.extraMemo("extramemo").buildMemo(), "=:BTC.BTC:btc123:1234:feeaddr:10:extramemo"); + EXPECT_EQ(builder.extraMemo("").affFeeRate("0").buildMemo(), "=:BTC.BTC:btc123:1234:feeaddr:0"); + EXPECT_EQ(builder.affFeeAddress("").affFeeRate("10").buildMemo(), "=:BTC.BTC:btc123:1234::10"); + EXPECT_EQ(builder.extraMemo("extramemo").affFeeRate("").buildMemo(), "=:BTC.BTC:btc123:1234:::extramemo"); + + Proto::Asset toAssetETH; + toAssetETH.set_chain(static_cast(Chain::ETH)); + toAssetETH.set_symbol("ETH"); + builder = SwapBuilder::builder().to(toAssetETH).toAddress("0xaabbccdd").toAmountLimit("1234"); + EXPECT_EQ(builder.buildMemo(), "=:ETH.ETH:0xaabbccdd:1234"); + toAssetETH.set_token_id("0x0000000000000000000000000000000000000000"); + EXPECT_EQ(builder.to(toAssetETH).buildMemo(), "=:ETH.ETH:0xaabbccdd:1234"); + toAssetETH.set_token_id("0x4B0F1812e5Df2A09796481Ff14017e6005508003"); + EXPECT_EQ(builder.to(toAssetETH).buildMemo(), "=:ETH.0x4B0F1812e5Df2A09796481Ff14017e6005508003:0xaabbccdd:1234"); + + builder = SwapBuilder::builder().to(toAssetETH).toAddress("bnb123").toAmountLimit("1234"); + Proto::Asset toAssetBNB; + toAssetBNB.set_chain(static_cast(Chain::BNB)); + toAssetBNB.set_symbol("BNB"); + EXPECT_EQ(builder.to(toAssetBNB).buildMemo(), "=:BNB.BNB:bnb123:1234"); + toAssetBNB.set_token_id("TWT-8C2"); + EXPECT_EQ(builder.to(toAssetBNB).buildMemo(), "=:BNB.TWT-8C2:bnb123:1234"); + + // Check streaming parameters. + EXPECT_EQ(builder.streamInterval("").buildMemo(), "=:BNB.TWT-8C2:bnb123:1234/1/0"); + EXPECT_EQ(builder.streamQuantity("").buildMemo(), "=:BNB.TWT-8C2:bnb123:1234/1/0"); + EXPECT_EQ(builder.streamQuantity("30").buildMemo(), "=:BNB.TWT-8C2:bnb123:1234/1/30"); + EXPECT_EQ(builder.streamInterval("7").streamQuantity("15").buildMemo(), "=:BNB.TWT-8C2:bnb123:1234/7/15"); + + // Check the default `toAmountLimit` and streaming parameters. + builder = SwapBuilder::builder().to(toAssetETH).toAddress("bnb123"); + builder.to(toAssetBNB); + EXPECT_EQ(builder.buildMemo(), "=:BNB.TWT-8C2:bnb123:0"); + EXPECT_EQ(builder.streamQuantity("").buildMemo(), "=:BNB.TWT-8C2:bnb123:0/1/0"); +} + +TEST(THORChainSwap, WrongFromAddress) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::BNB)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::ETH)); + toAsset.set_symbol("ETH"); + { + auto&& [_, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress("DummyAddress") + .toAddress(Address1Eth) + .vault(VaultEth) + .fromAmount("1000000") + .toAmountLimit("100000") + .build(); + EXPECT_EQ(errorCode, Proto::ErrorCode::Error_Invalid_from_address); + EXPECT_EQ(error, "Invalid from address"); + } + { + auto&& [_, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(Address1Btc) + .toAddress(Address1Eth) + .vault(VaultEth) + .fromAmount("1000000") + .toAmountLimit("100000") + .build(); + EXPECT_EQ(errorCode, Proto::ErrorCode::Error_Invalid_from_address); + EXPECT_EQ(error, "Invalid from address"); + } +} + +TEST(THORChainSwap, WrongToAddress) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::BNB)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::ETH)); + toAsset.set_symbol("ETH"); + { + auto&& [_, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(Address1Bnb) + .toAddress("DummyAddress") + .vault(VaultEth) + .fromAmount("100000") + .toAmountLimit("100000") + .build(); + EXPECT_EQ(errorCode, Proto::ErrorCode::Error_Invalid_to_address); + EXPECT_EQ(error, "Invalid to address"); + } + { + auto&& [_, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(Address1Bnb) + .toAddress(Address1Btc) + .vault(VaultEth) + .fromAmount("100000") + .toAmountLimit("100000") + .build(); + EXPECT_EQ(errorCode, Proto::ErrorCode::Error_Invalid_to_address); + EXPECT_EQ(error, "Invalid to address"); + } +} + +TEST(THORChainSwap, EthInvalidVault) { + Proto::Asset fromAsset; + fromAsset.set_chain(static_cast(Chain::ETH)); + Proto::Asset toAsset; + toAsset.set_chain(static_cast(Chain::BNB)); + toAsset.set_symbol("BNB"); + { + fromAsset.set_token_id("0x53595320f158d4546677b4795cc66dff59d154db"); + auto&& [_, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(Address1Eth) + .toAddress(Address1Bnb) + .vault("_INVALID_ADDRESS_") + .router(RouterEth) + .fromAmount("50000000000000000") + .toAmountLimit("600003") + .build(); + EXPECT_EQ(errorCode, Proto::ErrorCode::Error_Invalid_vault_address); + EXPECT_EQ(error, "Invalid vault address: _INVALID_ADDRESS_"); + } + { + auto&& [_, errorCode, error] = SwapBuilder::builder() + .from(fromAsset) + .to(toAsset) + .fromAddress(Address1Eth) + .toAddress(Address1Bnb) + .vault(VaultEth) + .router("_INVALID_ADDRESS_") + .fromAmount("50000000000000000") + .toAmountLimit("600003") + .build(); + EXPECT_EQ(errorCode, Proto::ErrorCode::Error_Invalid_router_address); + EXPECT_EQ(error, "Invalid router address: _INVALID_ADDRESS_"); + } +} + +} // namespace TW::THORChainSwap diff --git a/tests/chains/Cosmos/THORChain/TWAnyAddressTests.cpp b/tests/chains/Cosmos/THORChain/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..f28a401908e --- /dev/null +++ b/tests/chains/Cosmos/THORChain/TWAnyAddressTests.cpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(THORChainAnyAddress, IsValid) { + EXPECT_TRUE(TWAnyAddressIsValid(STRING("thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r").get(), TWCoinTypeTHORChain)); + EXPECT_TRUE(TWAnyAddressIsValid(STRING("thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65").get(), TWCoinTypeTHORChain)); + EXPECT_FALSE(TWAnyAddressIsValid(STRING("cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02").get(), TWCoinTypeTHORChain)); +} + +TEST(THORChainAnyAddress, Create) { + auto string = STRING("thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeTHORChain)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "1522e767db6eb19708b0038029bfbd607bc9bd0e"); +} diff --git a/tests/chains/Cosmos/THORChain/TWAnySignerTests.cpp b/tests/chains/Cosmos/THORChain/TWAnySignerTests.cpp new file mode 100644 index 00000000000..a33c5df584e --- /dev/null +++ b/tests/chains/Cosmos/THORChain/TWAnySignerTests.cpp @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include "Cosmos/Address.h" +#include "proto/Cosmos.pb.h" +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(THORChainTWAnySigner, SignTx) { + auto privateKey = parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"); + Cosmos::Proto::SigningInput input; + input.set_account_number(593); + input.set_chain_id("thorchain"); + input.set_sequence(3); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_memo(""); + + auto fromAddress = "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r"; + auto toAddress = "thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"; + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress); + message.set_to_address(toAddress); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("rune"); + amountOfTx->set_amount("10000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("rune"); + amountOfFee->set_amount("2000000"); + + Cosmos::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeTHORChain); + + // https://viewblock.io/thorchain/tx/FD0445AFFC4ED9ACCB7B5D3ADE361DAE4596EA096340F1360F1020381EA454AF + ASSERT_EQ(output.json(), R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"2000000","denom":"rune"}],"gas":"200000"},"memo":"","msg":[{"type":"thorchain/MsgSend","value":{"amount":[{"amount":"10000000","denom":"rune"}],"from_address":"thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r","to_address":"thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+2Zfjls9CkvX85aQrukFZnM1dluMTFUp8nqcEneMXx3"},"signature":"qgpMX3WNq4DsNBnYtdmBD4ejiailK4uI/m3/YVqCSNF8AtkUOTmP48ztqCbpkWTFvw1/9S8/ivsFxOcK6AI0jA=="}]}})"); +} diff --git a/tests/chains/Cosmos/THORChain/TWCoinTypeTests.cpp b/tests/chains/Cosmos/THORChain/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..65b8c1ee908 --- /dev/null +++ b/tests/chains/Cosmos/THORChain/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWTHORChainCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTHORChain)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("ADF0899E58C177E2391F22D04E9C5E1C35BB0F75B42B363A0761687907FD9476")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTHORChain, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("thor196yf4pq80hjrmz7nnh0ar0ypqg02r0w4dq4mzu")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTHORChain, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTHORChain)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTHORChain)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTHORChain), 8); + ASSERT_EQ(TWBlockchainThorchain, TWCoinTypeBlockchain(TWCoinTypeTHORChain)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTHORChain)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTHORChain)); + assertStringsEqual(symbol, "RUNE"); + assertStringsEqual(txUrl, "https://viewblock.io/thorchain/tx/ADF0899E58C177E2391F22D04E9C5E1C35BB0F75B42B363A0761687907FD9476"); + assertStringsEqual(accUrl, "https://viewblock.io/thorchain/address/thor196yf4pq80hjrmz7nnh0ar0ypqg02r0w4dq4mzu"); + assertStringsEqual(id, "thorchain"); + assertStringsEqual(name, "THORChain"); +} diff --git a/tests/chains/Cosmos/THORChain/TWSwapTests.cpp b/tests/chains/Cosmos/THORChain/TWSwapTests.cpp new file mode 100644 index 00000000000..df999de49ad --- /dev/null +++ b/tests/chains/Cosmos/THORChain/TWSwapTests.cpp @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bitcoin/Script.h" +#include "Bitcoin/SegwitAddress.h" +#include "PrivateKey.h" +#include "proto/Binance.pb.h" +#include "proto/Ethereum.pb.h" +#include "proto/THORChainSwap.pb.h" +#include +#include +#include + +#include "HexCoding.h" +#include "uint256.h" +#include "TestUtilities.h" + +#include + +using namespace TW; + +namespace TW::THORChainSwap::tests { + +// clang-format off +const auto Address1Bnb = "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx"; +const auto Address1Btc = "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"; +const auto Address1Eth = "0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7"; +const auto VaultBnb = "bnb1n9esxuw8ca7ts8l6w66kdh800s09msvul6vlse"; +const auto VaultBtc = "bc1q6m9u2qsu8mh8y7v8rr2ywavtj8g5arzlyhcej7"; +const auto VaultEth = "0x1091c4De6a3cF09CdA00AbDAeD42c7c3B69C83EC"; +const auto RouterEth = "0x42A5Ed456650a09Dc10EBc6361A7480fDd61f27B"; +const Data TestKey1Bnb = parse_hex("bcf8b072560dda05122c99390def2c385ec400e1a93df0657a85cf6b57a715da"); +const Data TestKey1Btc = parse_hex("13fcaabaf9e71ffaf915e242ec58a743d55f102cf836968e5bd4881135e0c52c"); +const Data TestKey1Eth = parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904"); + +TEST(TWTHORChainSwap, SwapBtcToEth) { + // prepare swap input + Proto::SwapInput input; + Proto::Asset fromAsset; + fromAsset.set_chain(Proto::BTC); + *input.mutable_from_asset() = fromAsset; + input.set_from_address(Address1Btc); + Proto::Asset toAsset; + toAsset.set_chain(Proto::ETH); + toAsset.set_symbol("ETH"); + toAsset.set_token_id(""); + *input.mutable_to_asset() = toAsset; + input.set_to_address(Address1Eth); + input.set_vault_address(VaultBtc); + input.set_router_address(""); + input.set_from_amount("1000000"); + input.set_to_amount_limit("140000000000000000"); + + // serialize input + const auto inputData_ = input.SerializeAsString(); + EXPECT_EQ(hex(inputData_), "0a020801122a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070381a0708021203455448222a3078623966353737316332373636346266323238326439386530396437663530636563376362303161372a2a62633171366d397532717375386d68387937763872723279776176746a38673561727a6c796863656a373a07313030303030304212313430303030303030303030303030303030"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + + // invoke swap + const auto outputTWData_ = WRAPD(TWTHORChainSwapBuildSwap(inputTWData_.get())); + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_EQ(outputData.size(), 178ul); + // parse result in proto + Proto::SwapOutput outputProto; + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + EXPECT_EQ(outputProto.from_chain(), Proto::BTC); + EXPECT_EQ(outputProto.to_chain(), Proto::ETH); + EXPECT_EQ(outputProto.error().code(), 0); + EXPECT_EQ(outputProto.error().message(), ""); + EXPECT_TRUE(outputProto.has_bitcoin()); + Bitcoin::Proto::SigningInput txInput = outputProto.bitcoin(); + + // tx input: check some fields + EXPECT_EQ(txInput.amount(), 1000000); + EXPECT_EQ(txInput.to_address(), "bc1q6m9u2qsu8mh8y7v8rr2ywavtj8g5arzlyhcej7"); + EXPECT_EQ(txInput.change_address(), "bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"); + EXPECT_EQ(txInput.output_op_return(), "=:ETH.ETH:0xb9f5771c27664bf2282d98e09d7f50cec7cb01a7:140000000000000000"); + EXPECT_EQ(txInput.coin_type(), 0ul); + + // sign tx input for signed full tx + // set few fields before signing + txInput.set_byte_fee(20); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + txInput.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); + auto& utxo = *txInput.add_utxo(); + Data utxoHash = parse_hex("1234000000000000000000000000000000000000000000000000000000005678"); + utxo.mutable_out_point()->set_hash(utxoHash.data(), utxoHash.size()); + utxo.mutable_out_point()->set_index(0); + utxo.mutable_out_point()->set_sequence(UINT32_MAX); + auto utxoScript = Bitcoin::Script::lockScriptForAddress(Address1Btc, TWCoinTypeBitcoin); + utxo.set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); + utxo.set_amount(50000000); + txInput.set_use_max_amount(false); + + // sign and encode resulting input + { + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(txInput, TWCoinTypeBitcoin); + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(hex(output.encoded()), // printed using prettyPrintTransaction + "01000000" // version + "0001" // marker & flag + "01" // inputs + "1234000000000000000000000000000000000000000000000000000000005678" "00000000" "00" "" "ffffffff" + "03" // outputs + "40420f0000000000" "16" "0014d6cbc5021c3eee72798718d447758b91d14e8c5f" + "d49ceb0200000000" "16" "00140cb9f5c6b62c03249367bc20a90dd2425e6926af" + "0000000000000000" "49" "6a473d3a4554482e4554483a3078623966353737316332373636346266323238326439386530396437663530636563376362303161373a313430303030303030303030303030303030" + // witness + "02" + "48" "3045022100a67f84cbde5affbb46ffff2b33c1453ff2de70ef990fc974175d9a609e5a87ed0220589c57d958208f866c9477c7d6c9075dea4c58622debb02eab85032b8b6d373001" + "21" "021e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee" + "00000000" // nLockTime + ); + } +} + +TEST(TWTHORChainSwap, SwapEthBnb) { + // prepare swap input + Proto::SwapInput input; + Proto::Asset fromAsset; + fromAsset.set_chain(Proto::ETH); + *input.mutable_from_asset() = fromAsset; + input.set_from_address(Address1Eth); + Proto::Asset toAsset; + toAsset.set_chain(Proto::BNB); + toAsset.set_symbol("BNB"); + toAsset.set_token_id(""); + *input.mutable_to_asset() = toAsset; + input.set_to_address(Address1Bnb); + input.set_vault_address(VaultEth); + input.set_router_address(RouterEth); + input.set_from_amount("50000000000000000"); + input.set_to_amount_limit("600003"); + + // serialize input + const auto inputData_ = input.SerializeAsString(); + EXPECT_EQ(hex(inputData_), "0a020802122a3078623966353737316332373636346266323238326439386530396437663530636563376362303161371a0708031203424e42222a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372782a2a307831303931633444653661336346303943644130304162444165443432633763334236394338334543322a3078343241354564343536363530613039446331304542633633363141373438306644643631663237423a1135303030303030303030303030303030304206363030303033"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + + // invoke swap + const auto outputTWData_ = WRAPD(TWTHORChainSwapBuildSwap(inputTWData_.get())); + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_EQ(outputData.size(), 141ul); + // parse result in proto + Proto::SwapOutput outputProto; + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + EXPECT_EQ(outputProto.from_chain(), Proto::ETH); + EXPECT_EQ(outputProto.to_chain(), Proto::BNB); + EXPECT_EQ(outputProto.error().code(), 0); + EXPECT_EQ(outputProto.error().message(), ""); + EXPECT_TRUE(outputProto.has_ethereum()); + Ethereum::Proto::SigningInput txInput = outputProto.ethereum(); + + // sign tx input for signed full tx + // set few fields before signing + auto chainId = store(uint256_t(1)); + txInput.set_chain_id(chainId.data(), chainId.size()); + auto nonce = store(uint256_t(3)); + txInput.set_nonce(nonce.data(), nonce.size()); + auto gasPrice = store(uint256_t(30000000000)); + txInput.set_gas_price(gasPrice.data(), gasPrice.size()); + auto gasLimit = store(uint256_t(80000)); + txInput.set_gas_limit(gasLimit.data(), gasLimit.size()); + txInput.set_private_key(""); + txInput.set_private_key(TestKey1Eth.data(), TestKey1Eth.size()); + + // sign and encode resulting input + Ethereum::Proto::SigningOutput output; + ANY_SIGN(txInput, TWCoinTypeEthereum); + EXPECT_EQ(hex(output.encoded()), "02f8a60103808083013880941091c4de6a3cf09cda00abdaed42c7c3b69c83ec87b1a2bc2ec50000b83b3d3a424e422e424e423a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372783a363030303033c080a00d605807f983650fafbfdcf0c33bdf0c524c7185eae8c1501ae24892faf16b1ba03b51b0a35e4754ab21d1e48fed635d8486048df50c253ba9af4cebdb6a92a450"); +} + +TEST(TWTHORChainSwap, SwapBnbBtc) { + // prepare swap input + Proto::SwapInput input; + Proto::Asset fromAsset; + fromAsset.set_chain(Proto::BNB); + *input.mutable_from_asset() = fromAsset; + input.set_from_address(Address1Bnb); + Proto::Asset toAsset; + toAsset.set_chain(Proto::BTC); + toAsset.set_symbol("BTC"); + toAsset.set_token_id(""); + *input.mutable_to_asset() = toAsset; + input.set_to_address(Address1Btc); + input.set_vault_address(VaultBnb); + input.set_router_address(""); + input.set_from_amount("10000000"); + input.set_to_amount_limit("10000000"); + + // serialize input + const auto inputData_ = input.SerializeAsString(); + EXPECT_EQ(hex(inputData_), "0a020803122a626e62317573343777646866783038636839377a6475656833783375356d757266727833306a656372781a0708011203425443222a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070382a2a626e62316e396573787577386361377473386c367736366b64683830307330396d7376756c36766c73653a08313030303030303042083130303030303030"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + + // invoke swap + const auto outputTWData_ = WRAPD(TWTHORChainSwapBuildSwap(inputTWData_.get())); + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_EQ(outputData.size(), 146ul); + // parse result in proto + Proto::SwapOutput outputProto; + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + EXPECT_EQ(outputProto.from_chain(), Proto::BNB); + EXPECT_EQ(outputProto.to_chain(), Proto::BTC); + EXPECT_EQ(outputProto.error().code(), 0); + EXPECT_EQ(outputProto.error().message(), ""); + EXPECT_TRUE(outputProto.has_binance()); + Binance::Proto::SigningInput txInput = outputProto.binance(); + + // set few fields before signing + txInput.set_chain_id("Binance-Chain-Tigris"); + txInput.set_private_key(TestKey1Bnb.data(), TestKey1Bnb.size()); + + // sign and encode resulting input + Ethereum::Proto::SigningOutput output; + ANY_SIGN(txInput, TWCoinTypeBinance); + EXPECT_EQ(hex(output.encoded()), "fd01f0625dee0a4c2a2c87fa0a220a14e42be736e933cf8b97c26f33789a3ca6f8348cd1120a0a03424e421080ade20412220a1499730371c7c77cb81ffa76b566dcef7c1e5dc19c120a0a03424e421080ade204126a0a26eb5ae9872103ea4b4bc12dc6f36a28d2c9775e01eef44def32cc70fb54f0e4177b659dbc0e19124086d43e9bdf12508a9a1415f5f970dfa5ff5930dee01d922f99779b63190735ba1d69694bda203b6678939a5c1eab0a52ed32bb67864ec7864de37b333533ae0c1a3d3d3a4254432e4254433a62633171706a756c7433346b3973706a66796d38687373326a72776a676630786a6634307a65307070383a3130303030303030"); +} + +TEST(TWTHORChainSwap, SwapAtomBnb) { + // prepare swap input + Proto::SwapInput input; + Proto::Asset fromAsset; + fromAsset.set_chain(Proto::ATOM); + fromAsset.set_symbol("ATOM"); + *input.mutable_from_asset() = fromAsset; + input.set_from_address("cosmos1v4e6vpehwrfez2dqepnw9g6t4fl83xzegd5ac9"); + Proto::Asset toAsset; + toAsset.set_chain(Proto::BNB); + toAsset.set_symbol("BNB"); + *input.mutable_to_asset() = toAsset; + input.set_to_address("bnb1s4kallxngpyspzm6nrezkml9rgyw6kxpw4fhr2"); + input.set_vault_address("cosmos154t5ycejlr7ax3ynmed9z05yg5a27y9u6pj5hq"); + input.set_from_amount("300000"); + input.set_to_amount_limit("819391"); + input.set_affiliate_fee_address("t"); + input.set_affiliate_fee_rate_bp("0"); + + // serialize input + const auto inputData_ = input.SerializeAsString(); + EXPECT_EQ(hex(inputData_), "0a080807120441544f4d122d636f736d6f73317634653676706568777266657a32647165706e773967367434666c3833787a656764356163391a0708031203424e42222a626e623173346b616c6c786e67707973707a6d366e72657a6b6d6c3972677977366b78707734666872322a2d636f736d6f7331353474357963656a6c7237617833796e6d6564397a303579673561323779397536706a3568713a0633303030303042063831393339314a0174520130"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + + // invoke swap + const auto outputTWData_ = WRAPD(TWTHORChainSwapBuildSwap(inputTWData_.get())); + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_EQ(outputData.size(), 204ul); + // parse result in proto + Proto::SwapOutput outputProto; + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + EXPECT_EQ(outputProto.from_chain(), Proto::ATOM); + EXPECT_EQ(outputProto.to_chain(), Proto::BNB); + EXPECT_EQ(outputProto.error().code(), 0); + EXPECT_EQ(outputProto.error().message(), ""); + EXPECT_TRUE(outputProto.has_cosmos()); + Cosmos::Proto::SigningInput txInput = outputProto.cosmos(); + + ASSERT_EQ(txInput.memo(), "=:BNB.BNB:bnb1s4kallxngpyspzm6nrezkml9rgyw6kxpw4fhr2:819391:t:0"); + auto& fee = *txInput.mutable_fee(); + fee.set_gas(200000); + auto& fee_amount = *fee.add_amounts(); + fee_amount.set_denom("uatom"); + fee_amount.set_amount("500"); + + txInput.set_account_number(1483163); + txInput.set_sequence(1); + + auto privKey = parse_hex("3eed3f32b8ba90e579ba46f37e7445fb4b34558306aa5bc32c525a93dff486e7"); + txInput.set_private_key(privKey.data(), privKey.size()); + + // sign and encode resulting input + Cosmos::Proto::SigningOutput output; + ANY_SIGN(txInput, TWCoinTypeCosmos); + EXPECT_EQ(output.error_message(), ""); + ASSERT_EQ(output.serialized(), "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CtMBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczF2NGU2dnBlaHdyZmV6MmRxZXBudzlnNnQ0Zmw4M3h6ZWdkNWFjORItY29zbW9zMTU0dDV5Y2VqbHI3YXgzeW5tZWQ5ejA1eWc1YTI3eTl1NnBqNWhxGg8KBXVhdG9tEgYzMDAwMDASPz06Qk5CLkJOQjpibmIxczRrYWxseG5ncHlzcHptNm5yZXprbWw5cmd5dzZreHB3NGZocjI6ODE5MzkxOnQ6MBJmClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDmmNIYBvR9bnOloFEMOWdk9DHYIGe7naW0T19y+/k1SUSBAoCCAEYARISCgwKBXVhdG9tEgM1MDAQwJoMGkCFqUWtDu0pn1P/cnVQnIJIWF8HFJn/xkJh55Mc7ZLVPF60uXYUOg8nNkt0IQPuTFREw32/yff6lmA5w6KwPen/\"}"); + + // https://viewblock.io/thorchain/tx/07F47D71A74245538E205F24ADB4BBB799B49C3A8A8875665D249EA51662AA50 + // https://www.mintscan.io/cosmos/txs/07F47D71A74245538E205F24ADB4BBB799B49C3A8A8875665D249EA51662AA50 + // https://binance.mintscan.io/txs/2C97061737B16B234990B9B18A2BF65F7C7418FF9E39A68E634C832E4E4C59CE +} + +TEST(TWTHORChainSwap, SwapRuneDoge) { + // prepare swap input + Proto::SwapInput input; + Proto::Asset fromAsset; + fromAsset.set_chain(Proto::THOR); + fromAsset.set_symbol("RUNE"); + *input.mutable_from_asset() = fromAsset; + input.set_from_address("thor14j5lwl8ulexrqp5x39kmkctv2937694z3jn2dz"); + Proto::Asset toAsset; + toAsset.set_chain(Proto::DOGE); + toAsset.set_symbol("DOGE"); + *input.mutable_to_asset() = toAsset; + input.set_to_address("DNhRF1h8J4ZnB1bxp9kaqhVLYetkx1nSJ5"); + input.set_from_amount("150000000"); + input.set_to_amount_limit("10000000"); + input.set_affiliate_fee_address("tr"); + input.set_affiliate_fee_rate_bp("0"); + + // serialize input + const auto inputData_ = input.SerializeAsString(); + EXPECT_EQ(hex(inputData_), "0a06120452554e45122b74686f7231346a356c776c38756c6578727170357833396b6d6b637476323933373639347a336a6e32647a1a0808041204444f47452222444e6852463168384a345a6e4231627870396b617168564c5965746b78316e534a353a09313530303030303030420831303030303030304a027472520130"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + + // invoke swap + const auto outputTWData_ = WRAPD(TWTHORChainSwapBuildSwap(inputTWData_.get())); + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_EQ(outputData.size(), 153ul); + // parse result in proto + Proto::SwapOutput outputProto; + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + EXPECT_EQ(outputProto.from_chain(), Proto::THOR); + EXPECT_EQ(outputProto.to_chain(), Proto::DOGE); + EXPECT_EQ(outputProto.error().code(), 0); + EXPECT_EQ(outputProto.error().message(), ""); + EXPECT_TRUE(outputProto.has_cosmos()); + Cosmos::Proto::SigningInput txInput = outputProto.cosmos(); + + ASSERT_EQ(txInput.messages(0).thorchain_deposit_message().memo(), "=:DOGE.DOGE:DNhRF1h8J4ZnB1bxp9kaqhVLYetkx1nSJ5:10000000:tr:0"); + auto& fee = *txInput.mutable_fee(); + fee.set_gas(50000000); + + txInput.set_account_number(75247); + txInput.set_sequence(8); + + auto privKey = parse_hex("2659e41d54ebd449d68b9d58510d8eeeb837ee00d6ecc760b7a731238d8c3113"); + txInput.set_private_key(privKey.data(), privKey.size()); + + // sign and encode resulting input + Cosmos::Proto::SigningOutput output; + ANY_SIGN(txInput, TWCoinTypeCosmos); + EXPECT_EQ(output.error_message(), ""); + ASSERT_EQ(output.serialized(), "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"Co0BCooBChEvdHlwZXMuTXNnRGVwb3NpdBJ1Ch8KEgoEVEhPUhIEUlVORRoEUlVORRIJMTUwMDAwMDAwEjw9OkRPR0UuRE9HRTpETmhSRjFoOEo0Wm5CMWJ4cDlrYXFoVkxZZXRreDFuU0o1OjEwMDAwMDAwOnRyOjAaFKyp93z8/kwwBoaJbbthbFFj7RaiElkKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQO5lUOUgVbcO1IQFrppQEnQOtAeVD7aDuUi3l6QAzblKRIECgIIARgIEgUQgOHrFxpABOMrkMdq0FFNUEvjE7DDFpDW3EudV2qPhNCD4FrYtHsiBjMefdBaN8Ddp2Fucqs6OMkoXBEoW/u1msDqnvaXdA==\"}"); + + // https://viewblock.io/thorchain/tx/29C8B558051A0E0B1F44E4FFED034EDD204A7249A824DCE06C72C28D6114B5E3 + // https://dogechain.info/tx/905ce02ec3397d6d4f2cbe63ebbff2ccf8b9f16d7ea136319be5ed543cdb66f3 +} + +TEST(TWTHORChainSwap, SwapRuneBnbStreamParams) { + // prepare swap input + Proto::SwapInput input; + Proto::Asset fromAsset; + fromAsset.set_chain(Proto::THOR); + fromAsset.set_symbol("RUNE"); + *input.mutable_from_asset() = fromAsset; + input.set_from_address("thor157vzvw2chydgf8g4qu2cqhlsyhq0mydutmd0p7"); + Proto::Asset toAsset; + toAsset.set_chain(Proto::BNB); + toAsset.set_symbol("BNB"); + *input.mutable_to_asset() = toAsset; + input.set_to_address("bnb1swlv73yc6rc7z4n244gcpjknqh22m7kpjpr0mw"); + input.set_from_amount("170000000"); + // Don't set `toAmountLimit`, should be 0 by default. + auto* streamParams = input.mutable_stream_params(); + streamParams->set_interval("1"); + streamParams->set_quantity("0"); + input.set_affiliate_fee_address("tr"); + input.set_affiliate_fee_rate_bp("0"); + + // serialize input + const auto inputData_ = input.SerializeAsString(); + EXPECT_EQ(hex(inputData_), "0a06120452554e45122b74686f72313537767a7677326368796467663867347175326371686c73796871306d796475746d643070371a0708031203424e42222a626e623173776c7637337963367263377a346e3234346763706a6b6e716832326d376b706a7072306d773a093137303030303030304a0274725201306a060a0131120130"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + + // invoke swap + const auto outputTWData_ = WRAPD(TWTHORChainSwapBuildSwap(inputTWData_.get())); + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_EQ(outputData.size(), 156ul); + // parse result in proto + Proto::SwapOutput outputProto; + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + EXPECT_EQ(outputProto.from_chain(), Proto::THOR); + EXPECT_EQ(outputProto.to_chain(), Proto::BNB); + EXPECT_EQ(outputProto.error().code(), 0); + EXPECT_EQ(outputProto.error().message(), ""); + EXPECT_TRUE(outputProto.has_cosmos()); + Cosmos::Proto::SigningInput txInput = outputProto.cosmos(); + + ASSERT_EQ(txInput.messages(0).thorchain_deposit_message().memo(), "=:BNB.BNB:bnb1swlv73yc6rc7z4n244gcpjknqh22m7kpjpr0mw:0/1/0:tr:0"); + auto& fee = *txInput.mutable_fee(); + fee.set_gas(50000000); + + txInput.set_account_number(76456); + txInput.set_sequence(0); + + auto privKey = parse_hex("15f9be0e6c80949f3dbe24fd9614027869af1e41953a86fdced947b0b1f3efa7"); + txInput.set_private_key(privKey.data(), privKey.size()); + + // sign and encode resulting input + Cosmos::Proto::SigningOutput output; + ANY_SIGN(txInput, TWCoinTypeCosmos); + EXPECT_EQ(output.error_message(), ""); + ASSERT_EQ(output.serialized(), "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpABCo0BChEvdHlwZXMuTXNnRGVwb3NpdBJ4Ch8KEgoEVEhPUhIEUlVORRoEUlVORRIJMTcwMDAwMDAwEj89OkJOQi5CTkI6Ym5iMXN3bHY3M3ljNnJjN3o0bjI0NGdjcGprbnFoMjJtN2twanByMG13OjAvMS8wOnRyOjAaFKeYJjlYuRqEnRUHFYBf8CXA/ZG8ElcKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNWwhqmW30kANTyAfdGJPa9BfZlI3xkAjqLWmhynukWThIECgIIARIFEIDh6xcaQNzvOBmgAgRriO5lsEgU4o58Gxu4mA71XZNyf5XXWBo5L9HkaJiDXE/YOlWPFj7iy86vDXVR1798pmc3n5EbkQ0=\"}"); + + // https://viewblock.io/thorchain/tx/317443DD48DDEE8811D0DCCC2FCA397F8E93DA0AC9C1D5173CB42E69CD0E01B0 + // https://explorer.bnbchain.org/tx/6DE7B60C71F9FC3EEE914AAD8FE80D1A53A2EC59BE759A1C111C1B6C194740D2 +} + +TEST(TWTHORChainSwap, NegativeInvalidInput) { + const auto inputData = parse_hex("00112233"); + const auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size())); + + const auto outputTWData = WRAPD(TWTHORChainSwapBuildSwap(inputTWData.get())); + const auto outputData = data(TWDataBytes(outputTWData.get()), TWDataSize(outputTWData.get())); + EXPECT_EQ(outputData.size(), 39ul); + EXPECT_EQ(hex(outputData), "1a2508021221436f756c64206e6f7420646573657269616c697a6520696e7075742070726f746f"); + EXPECT_EQ(hex(data(std::string("Could not deserialize input proto"))), "436f756c64206e6f7420646573657269616c697a6520696e7075742070726f746f"); +} +// clang-format on + +} // namespace TW::ThorChainSwap::tests diff --git a/tests/chains/Cosmos/TWAnyAddressTests.cpp b/tests/chains/Cosmos/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..6f520edca1b --- /dev/null +++ b/tests/chains/Cosmos/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gAtomAddr = "cosmos1mry47pkga5tdswtluy0m8teslpalkdq07pswu4"; +static const std::string gAtomHrp = "cosmos"; + +TEST(TWAtomAnyAddress, AllAtomAddressTests) { + CosmosAddressParameters parameters{.hrp = gAtomHrp, .coinType = TWCoinTypeCosmos, .address = gAtomAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/TWAnySignerTests.cpp b/tests/chains/Cosmos/TWAnySignerTests.cpp new file mode 100644 index 00000000000..2ecc82de1aa --- /dev/null +++ b/tests/chains/Cosmos/TWAnySignerTests.cpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Cosmos/Address.h" +#include "HexCoding.h" +#include "proto/Cosmos.pb.h" +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + +TEST(TWAnySignerCosmos, SignTx) { + auto privateKey = parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"); + Proto::SigningInput input; + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(546179); + input.set_chain_id("cosmoshub-4"); + input.set_memo(""); + input.set_sequence(0); + input.set_private_key(privateKey.data(), privateKey.size()); + + Address fromAddress; + EXPECT_TRUE(Address::decode("cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx", fromAddress)); + Address toAddress; + EXPECT_TRUE(Address::decode("cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp", toAddress)); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("uatom"); + amountOfTx->set_amount("400000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uatom"); + amountOfFee->set_amount("1000"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeCosmos); + + // https://www.mintscan.io/cosmos/txs/85392373F54577562067030BF0D61596C91188AA5E6CA8FFE731BD0349296411 + // curl -H 'Content-Type: application/json' --data-binary '{"tx_bytes": "CpIBC...JXoCX", "mode": "BROADCAST_MODE_BLOCK"}' https://api.cosmos.network/cosmos/tx/v1beta1/txs + assertJSONEqual(output.serialized(), "{\"tx_bytes\": \"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX\", \"mode\": \"BROADCAST_MODE_BLOCK\"}"); + EXPECT_EQ(hex(output.signature()), "afbd513a776f4fdf470ef7f9675f21ae9d630fc4d635d8dbaa0dc0a716434cd07e02510765d4673dfa880825bae8e67cb367396ff6b976fc6b19a31fc95e8097"); + EXPECT_EQ(output.json(), ""); + EXPECT_EQ(output.error_message(), ""); +} + +TEST(TWAnySignerCosmos, SignJSON) { + auto json = STRING(R"({"accountNumber":"8733","chainId":"cosmoshub-2","fee":{"amounts":[{"denom":"uatom","amount":"5000"}],"gas":"200000"}, "memo":"Testing", "messages":[{"sendCoinsMessage":{"fromAddress":"cosmos1ufwv9ymhqaal6xz47n0jhzm2wf4empfqvjy575","toAddress":"cosmos135qla4294zxarqhhgxsx0sw56yssa3z0f78pm0","amounts":[{"denom":"uatom","amount":"995000"}]}}]})"); + auto key = DATA("c9b0a273831931aa4a5f8d1a570d5021dda91d3319bd3819becdaabfb7b44e3b"); + auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeCosmos)); + + ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeCosmos)); + assertStringsEqual(result, R"({"mode":"block","tx":{"fee":{"amount":[{"amount":"5000","denom":"uatom"}],"gas":"200000"},"memo":"Testing","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"995000","denom":"uatom"}],"from_address":"cosmos1ufwv9ymhqaal6xz47n0jhzm2wf4empfqvjy575","to_address":"cosmos135qla4294zxarqhhgxsx0sw56yssa3z0f78pm0"}}],"signatures":[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A6EsukEXB53GhohQVeDpxtkeH8KQIayd/Co/ApYRYkTm"},"signature":"ULEpUqNzoAnYEx2x22F3ANAiPXquAU9+mqLWoAA/ZOUGTMsdb6vryzsW6AKX2Kqj1pGNdrTcQ58Z09JPyjpgEA=="}]}})"); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/TWCoinTypeTests.cpp b/tests/chains/Cosmos/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..01cbdd3c12b --- /dev/null +++ b/tests/chains/Cosmos/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWCosmosCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeCosmos)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("541FA06FB37AC1BF61922143783DD76FECA361830F9876D0342536EE8A87A790")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeCosmos, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("cosmos1gu6y2a0ffteesyeyeesk23082c6998xyzmt9mz")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeCosmos, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeCosmos)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeCosmos)); + const auto chainId = WRAPS(TWCoinTypeChainId(TWCoinTypeCosmos)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeCosmos), 6); + ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeCosmos)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeCosmos)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeCosmos)); + assertStringsEqual(chainId, "cosmoshub-4"); + assertStringsEqual(symbol, "ATOM"); + assertStringsEqual(txUrl, "https://mintscan.io/cosmos/txs/541FA06FB37AC1BF61922143783DD76FECA361830F9876D0342536EE8A87A790"); + assertStringsEqual(accUrl, "https://mintscan.io/cosmos/account/cosmos1gu6y2a0ffteesyeyeesk23082c6998xyzmt9mz"); + assertStringsEqual(id, "cosmos"); + assertStringsEqual(name, "Cosmos Hub"); +} diff --git a/tests/chains/Cosmos/Terra/SignerTests.cpp b/tests/chains/Cosmos/Terra/SignerTests.cpp new file mode 100644 index 00000000000..0b8bb3f92a3 --- /dev/null +++ b/tests/chains/Cosmos/Terra/SignerTests.cpp @@ -0,0 +1,433 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Base64.h" +#include "proto/Cosmos.pb.h" +#include "Cosmos/Address.h" +#include "uint256.h" +#include "TestUtilities.h" +#include "TrustWalletCore/TWAnySigner.h" + +#include +#include + +namespace TW::Cosmos::tests { + +TEST(TerraClassicSigner, SignSendTx) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::JSON); + input.set_account_number(1037); + input.set_chain_id("columbus-5"); + input.set_memo(""); + input.set_sequence(2); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", toAddress)); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("luna"); + amountOfTx->set_amount("1000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("luna"); + amountOfFee->set_amount("200"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + EXPECT_EQ(json, R"({"accountNumber":"1037","chainId":"columbus-5","fee":{"amounts":[{"denom":"luna","amount":"200"}],"gas":"200000"},"sequence":"2","messages":[{"sendCoinsMessage":{"fromAddress":"terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2","toAddress":"terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf","amounts":[{"denom":"luna","amount":"1000000"}]}}]})"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); + + assertJSONEqual(output.json(), R"( + { + "mode": "block", + "tx": { + "fee": { + "amount": [ + { + "amount": "200", + "denom": "luna" + } + ], + "gas": "200000" + }, + "memo": "", + "msg": [ + { + "type": "cosmos-sdk/MsgSend", + "value": { + "amount": [ + { + "amount": "1000000", + "denom": "luna" + } + ], + "from_address": "terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2", + "to_address": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf" + } + } + ], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" + }, + "signature": "ofdIsLJzkODcQwLG89eE2g4HOaUmfKPh/08t07ehKPUqRMl4rVonzo73mkOvqtrHWjdtB+6t6R8DGudPpb6bRg==" + } + ] + } + } + )"); + EXPECT_EQ(hex(output.signature()), "a1f748b0b27390e0dc4302c6f3d784da0e0739a5267ca3e1ff4f2dd3b7a128f52a44c978ad5a27ce8ef79a43afaadac75a376d07eeade91f031ae74fa5be9b46"); + EXPECT_EQ(output.serialized(), ""); + EXPECT_EQ(output.error_message(), ""); +} + +TEST(TerraClassicSigner, SignWasmTransferTxProtobuf_9FF3F0) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(3407705); + input.set_chain_id("columbus-5"); + input.set_memo(""); + input.set_sequence(3); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); + const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; // ANC + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_terra_execute_contract_transfer_message(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + const auto amount = store(uint256_t(250000), 0); + message.set_amount(amount.data(), amount.size()); + message.set_recipient_address(toAddress.string()); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("3000"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + assertJSONEqual(json, R"( + { + "signingMode": "Protobuf", + "accountNumber": "3407705", + "chainId": "columbus-5", + "fee": { + "amounts": [ + { + "denom": "uluna", + "amount": "3000" + } + ], + "gas": "200000" + }, + "sequence": "3", + "messages": [ + { + "wasmTerraExecuteContractTransferMessage": { + "senderAddress": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", + "contractAddress": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", + "amount": "A9CQ", + "recipientAddress": "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" + } + } + ] + } + )"); + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); + + // https://finder.terra.money/mainnet/tx/9FF3F0A16879254C22EB90D8B4D6195467FE5014381FD36BD3C23CA6698FE94B + // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "CogCCo..wld8"})' https:///cosmos/tx/v1beta1/txs + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "CucBCuQBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBK5AQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Glt7InRyYW5zZmVyIjp7ImFtb3VudCI6IjI1MDAwMCIsInJlY2lwaWVudCI6InRlcnJhMWpsZ2FxeTludm4yaGY1dDJzcmE5eWN6OHM3N3duZjlsMGttZ2NwIn19EmcKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNwZjrHsPmJKW/rXOWfukpQ1+lOHOJW3/IlFFnKLNmsABIECgIIARgDEhMKDQoFdWx1bmESBDMwMDAQwJoMGkAaprIEMLPH2HmFdwFGoaipb2GIyhXt6ombz+WMnG2mORBI6gFt0M+IymYgzZz6w1SW52R922yafDnn7yXfutRw", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + EXPECT_EQ(hex(output.signature()), "1aa6b20430b3c7d87985770146a1a8a96f6188ca15edea899bcfe58c9c6da6391048ea016dd0cf88ca6620cd9cfac35496e7647ddb6c9a7c39e7ef25dfbad470"); + EXPECT_EQ(output.json(), ""); +} + +TEST(TerraClassicSigner, SignWasmTransferTxJson_078E90) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::JSON); + input.set_account_number(3407705); + input.set_chain_id("columbus-5"); + input.set_memo(""); + input.set_sequence(2); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); + const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; // ANC + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_terra_execute_contract_transfer_message(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + const auto amount = store(250000); + message.set_amount(amount.data(), amount.size()); + message.set_recipient_address(toAddress.string()); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("3000"); + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); + + // https://finder.terra.money/mainnet/tx/078E90458061611F6FD8B708882B55FF5C1FFB3FCE61322107A0A0DE39FC0F3E + // curl -H 'Content-Type: application/json' --data-binary '{"mode": "block","tx":{...}}' https:///txs + assertJSONEqual(output.json(), R"( + { + "mode": "block", + "tx": + { + "fee": {"amount":[{"amount": "3000","denom": "uluna"}],"gas": "200000"}, + "memo": "", + "msg": + [ + { + "type": "wasm/MsgExecuteContract", + "value": + { + "sender": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", + "contract": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", + "execute_msg": + { + "transfer": + { + "amount": "250000", + "recipient": "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" + } + }, + "coins": [] + } + } + ], + "signatures": + [ + { + "pub_key": + { + "type": "tendermint/PubKeySecp256k1", + "value": "A3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awA" + }, + "signature": "BjETdtbA97Wv1zvcsCV1tM+bdYKC8O3uGTk4mMRv6pBJB2y/Ds7qoS7s/zrkhYak1YChklQetHsI30XRXzGIkg==" + } + ] + } + })"); + EXPECT_EQ(hex(output.signature()), "06311376d6c0f7b5afd73bdcb02575b4cf9b758282f0edee19393898c46fea9049076cbf0eceeaa12eecff3ae48586a4d580a192541eb47b08df45d15f318892"); + EXPECT_EQ(output.serialized(), ""); +} + +TEST(TerraClassicSigner, SignWasmGeneric_EC4F85) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(3407705); + input.set_chain_id("columbus-5"); + input.set_memo(""); + input.set_sequence(7); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); + const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; // ANC + const auto txMessage = R"({"transfer": { "amount": "250000", "recipient": "terra1d7048csap4wzcv5zm7z6tdqem2agyp9647vdyj" } })"; + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_terra_execute_contract_generic(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + message.set_execute_msg(txMessage); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("3000"); + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); + + // https://finder.terra.money/mainnet/tx/EC4F8532847E4D6AF016E6F6D3F027AE7FB6FF0B533C5132B01382D83B214A6F + // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "Cu4BC...iVt"})' https:///cosmos/tx/v1beta1/txs + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "Cu4BCusBCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLAAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2GmJ7InRyYW5zZmVyIjogeyAiYW1vdW50IjogIjI1MDAwMCIsICJyZWNpcGllbnQiOiAidGVycmExZDcwNDhjc2FwNHd6Y3Y1em03ejZ0ZHFlbTJhZ3lwOTY0N3ZkeWoiIH0gfRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYBxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAkPsS7xlSng2LMc9KiD1soN5NLaDcUh8I9okPmsdJN3le1B7yxRGNB4aQfhaRl/8Z0r5vitRT0AWuxDasd8wcFw==", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + + EXPECT_EQ(hex(output.signature()), "90fb12ef19529e0d8b31cf4a883d6ca0de4d2da0dc521f08f6890f9ac74937795ed41ef2c5118d0786907e169197ff19d2be6f8ad453d005aec436ac77cc1c17"); + EXPECT_EQ(output.json(), ""); +} + +TEST(TerraClassicSigner, SignWasmGenericWithCoins_6651FC) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(3407705); + input.set_chain_id("columbus-5"); + input.set_memo(""); + input.set_sequence(9); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); + const auto tokenContractAddress = "terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s"; // ANC Market + const auto txMessage = R"({ "deposit_stable": {} })"; + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_terra_execute_contract_generic(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + message.set_execute_msg(txMessage); + + auto amount = message.add_coins(); + amount->set_denom("uusd"); + amount->set_amount("1000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(600000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("7000"); + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); + + // https://finder.terra.money/mainnet/tx/6651FCE0EE5C6D6ACB655CC49A6FD5E939FB082862854616EA0642475BCDD0C9 + // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "CrIBCq8B.....0NWg=="})' https:///cosmos/tx/v1beta1/txs + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "CrIBCq8BCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBKEAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMXNlcGZqN3MwYWVnNTk2N3V4bmZrNHRoemxlcnJza3RrcGVsbTVzGhh7ICJkZXBvc2l0X3N0YWJsZSI6IHt9IH0qDAoEdXVzZBIEMTAwMBJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYCRITCg0KBXVsdW5hEgQ3MDAwEMDPJBpAGyi7f1ioY8XV6pjFq1s86Om4++CIUnd3rLHif2iopCcYvX0mLkTlQ6NUERg8nWTYgXcj6fOTO/ptgPuAtv0NWg==", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + + EXPECT_EQ(hex(output.signature()), "1b28bb7f58a863c5d5ea98c5ab5b3ce8e9b8fbe088527777acb1e27f68a8a42718bd7d262e44e543a35411183c9d64d8817723e9f3933bfa6d80fb80b6fd0d5a"); + EXPECT_EQ(output.json(), ""); +} + +TEST(TerraClassicSigner, SignWasmSendTxProtobuf) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(3407705); + input.set_chain_id("columbus-5"); + input.set_memo(""); + input.set_sequence(4); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); + const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; // ANC + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_terra_execute_contract_send_message(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + const auto amount = store(uint256_t(250000), 0); + message.set_amount(amount.data(), amount.size()); + message.set_recipient_contract_address(toAddress.string()); + const auto msgMsg = Base64::encode(data(std::string(R"({"some_message":{}})"))); + EXPECT_EQ(msgMsg, "eyJzb21lX21lc3NhZ2UiOnt9fQ=="); + message.set_msg(msgMsg); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("3000"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + assertJSONEqual(json, R"( + { + "signingMode": "Protobuf", + "accountNumber": "3407705", + "chainId": "columbus-5", + "fee": { + "amounts": [ + { + "denom": "uluna", + "amount": "3000" + } + ], + "gas": "200000" + }, + "sequence": "4", + "messages": [ + { + "wasmTerraExecuteContractSendMessage": { + "senderAddress": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", + "contractAddress": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", + "amount": "A9CQ", + "recipientContractAddress": "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", + "msg": "eyJzb21lX21lc3NhZ2UiOnt9fQ==" + } + } + ] + } + )"); + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerra); + + // https://finder.terra.money/mainnet/tx/9FF3F0A16879254C22EB90D8B4D6195467FE5014381FD36BD3C23CA6698FE94B + // curl -H 'Content-Type: application/json' --data-binary '{"mode": "BROADCAST_MODE_BLOCK","tx_bytes": "CogCCo..wld8"})' https:///cosmos/tx/v1beta1/txs + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "CocCCoQCCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBLZAQosdGVycmExOHd1a3A4NGRxMjI3d3U0bWdoMGptNm45bmxuajZyczgycHA5d2YSLHRlcnJhMTR6NTZsMGZwMmxzZjg2enkzaHR5Mno0N2V6a2hudGh0cjl5cTc2Gnt7InNlbmQiOnsiYW1vdW50IjoiMjUwMDAwIiwiY29udHJhY3QiOiJ0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcCIsIm1zZyI6ImV5SnpiMjFsWDIxbGMzTmhaMlVpT250OWZRPT0ifX0SZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awAEgQKAggBGAQSEwoNCgV1bHVuYRIEMzAwMBDAmgwaQL6NByKeRZsyq5g6CTMdmPqiM77nOe9uLO8FjpetFgkBFiG3Le7ieZZ+4vCMhD1bcFgMwSHibFI/uPil847U/+g=", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + EXPECT_EQ(hex(output.signature()), "be8d07229e459b32ab983a09331d98faa233bee739ef6e2cef058e97ad1609011621b72deee279967ee2f08c843d5b70580cc121e26c523fb8f8a5f38ed4ffe8"); + EXPECT_EQ(output.json(), ""); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/Terra/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Terra/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..b564ac066b9 --- /dev/null +++ b/tests/chains/Cosmos/Terra/TWCoinTypeTests.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + +TEST(TWTerraCoinType, TWCoinTypeClassic) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTerra)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("D28D8AFC7CE89F2A22FA2DBF78D2C0A36E549BB830C4D9FA7459E3F723CA7182")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTerra, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("terra16t3gx5rqvz6ru37yzn3shuu20erv4ngmfr59zf")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTerra, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTerra)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTerra)); + const auto chainId = WRAPS(TWCoinTypeChainId(TWCoinTypeTerra)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTerra), 6); + ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeTerra)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTerra)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTerra)); + assertStringsEqual(chainId, "columbus-5"); + assertStringsEqual(symbol, "LUNC"); + assertStringsEqual(txUrl, "https://finder.terra.money/classic/tx/D28D8AFC7CE89F2A22FA2DBF78D2C0A36E549BB830C4D9FA7459E3F723CA7182"); + assertStringsEqual(accUrl, "https://finder.terra.money/classic/address/terra16t3gx5rqvz6ru37yzn3shuu20erv4ngmfr59zf"); + assertStringsEqual(id, "terra"); + assertStringsEqual(name, "Terra Classic"); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/TerraV2/SignerTests.cpp b/tests/chains/Cosmos/TerraV2/SignerTests.cpp new file mode 100644 index 00000000000..7e382641480 --- /dev/null +++ b/tests/chains/Cosmos/TerraV2/SignerTests.cpp @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Base64.h" +#include "proto/Cosmos.pb.h" +#include "Cosmos/Address.h" +#include "uint256.h" +#include "TrustWalletCore/TWAnySigner.h" +#include "TestUtilities.h" + +#include +#include + +namespace TW::Cosmos::tests { + +TEST(TerraSigner, SignSendTx) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(1037); + input.set_chain_id("phoenix-1"); + input.set_memo(""); + input.set_sequence(1); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("uluna"); + amountOfTx->set_amount("1000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("30000"); + + { + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + assertJSONEqual(json, R"( + { + "signingMode": "Protobuf", + "accountNumber": "1037", + "chainId": "phoenix-1", + "fee": { + "amounts": [ + { + "denom": "uluna", + "amount": "30000" + } + ], + "gas": "200000" + }, + "sequence": "1", + "messages": [ + { + "sendCoinsMessage": { + "fromAddress": "terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2", + "toAddress": "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", + "amounts": [ + { + "denom": "uluna", + "amount": "1000000" + } + ] + } + } + ] + } + )"); + } + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerraV2); + + // similar tx: https://finder.terra.money/mainnet/tx/fbbe73ad2f0db3a13911dc424f8a34370dc4b7e8b66687f536797e68ee200ece + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "CpEBCo4BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm4KLHRlcnJhMWhzazZqcnl5cWpmaHA1ZGhjNTV0YzlqdGNreWd4MGVwMzdoZGQyEix0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcBoQCgV1bHVuYRIHMTAwMDAwMBJoClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECVyhuw/N9M1V7u6oACyd0SskCOqmWfK51oYHR/5H6ncUSBAoCCAEYARIUCg4KBXVsdW5hEgUzMDAwMBDAmgwaQPh0C3rjzdixIUiyPx3FlWAxzbKILNAcSRVeQnaTl1vsI5DEfYa2oYlUBLqyilcMCcU/iaJLhex30No2ak0Zn1Q=", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + EXPECT_EQ(hex(output.signature()), "f8740b7ae3cdd8b12148b23f1dc5956031cdb2882cd01c49155e427693975bec2390c47d86b6a1895404bab28a570c09c53f89a24b85ec77d0da366a4d199f54"); + EXPECT_EQ(output.error_message(), ""); + EXPECT_EQ(output.json(), ""); +} + +TEST(TerraSigner, SignWasmTransferTx) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(3407705); + input.set_chain_id("phoenix-1"); + input.set_memo(""); + input.set_sequence(3); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); + const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_execute_contract_transfer_message(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + const auto amount = store(uint256_t(250000), 0); + message.set_amount(amount.data(), amount.size()); + message.set_recipient_address(toAddress.string()); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("3000"); + + { + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + assertJSONEqual(json, R"( + { + "signingMode": "Protobuf", + "accountNumber": "3407705", + "chainId": "phoenix-1", + "fee": { + "amounts": [ + { + "denom": "uluna", + "amount": "3000" + } + ], + "gas": "200000" + }, + "sequence": "3", + "messages": [ + { + "wasmExecuteContractTransferMessage": { + "senderAddress": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", + "contractAddress": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", + "amount": "A9CQ", + "recipientAddress": "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp" + } + } + ] + } + )"); + } + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerraV2); + + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "CuUBCuIBCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QSuQEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTE0ejU2bDBmcDJsc2Y4Nnp5M2h0eTJ6NDdlemtobnRodHI5eXE3NhpbeyJ0cmFuc2ZlciI6eyJhbW91bnQiOiIyNTAwMDAiLCJyZWNpcGllbnQiOiJ0ZXJyYTFqbGdhcXk5bnZuMmhmNXQyc3JhOXljejhzNzd3bmY5bDBrbWdjcCJ9fRJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDcGY6x7D5iSlv61zln7pKUNfpThziVt/yJRRZyizZrAASBAoCCAEYAxITCg0KBXVsdW5hEgQzMDAwEMCaDBpAiBGbQaj+jsXE6/FssD3fC77QOxpli9GqsPea+KoNyMIEgVj89Hii+oU1bAEQS4qV0SaE2V6RNy24uCcFTIRbcQ==", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + EXPECT_EQ(hex(output.signature()), "88119b41a8fe8ec5c4ebf16cb03ddf0bbed03b1a658bd1aab0f79af8aa0dc8c2048158fcf478a2fa85356c01104b8a95d12684d95e91372db8b827054c845b71"); + EXPECT_EQ(output.error_message(), ""); + EXPECT_EQ(output.json(), ""); +} + +TEST(TerraSigner, SignWasmGeneric) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(3407705); + input.set_chain_id("phoenix-1"); + input.set_memo(""); + input.set_sequence(7); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; + const auto txMessage = R"({"transfer": { "amount": "250000", "recipient": "terra1d7048csap4wzcv5zm7z6tdqem2agyp9647vdyj" } })"; + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_execute_contract_generic(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + message.set_execute_msg(txMessage); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("3000"); + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerraV2); + + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "CuwBCukBCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QSwAEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTE0ejU2bDBmcDJsc2Y4Nnp5M2h0eTJ6NDdlemtobnRodHI5eXE3NhpieyJ0cmFuc2ZlciI6IHsgImFtb3VudCI6ICIyNTAwMDAiLCAicmVjaXBpZW50IjogInRlcnJhMWQ3MDQ4Y3NhcDR3emN2NXptN3o2dGRxZW0yYWd5cDk2NDd2ZHlqIiB9IH0SZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awAEgQKAggBGAcSEwoNCgV1bHVuYRIEMzAwMBDAmgwaQGlYzOoAu/PfyCTSTisGJVW9KWwifxMbCmzy2xwqNg+ZHQkDjVRyUBl7gmbXXLzdOMqtwF1CMauJhlGwmEdzhK4=", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + + EXPECT_EQ(hex(output.signature()), "6958ccea00bbf3dfc824d24e2b062555bd296c227f131b0a6cf2db1c2a360f991d09038d547250197b8266d75cbcdd38caadc05d4231ab898651b098477384ae"); + EXPECT_EQ(output.error_message(), ""); + EXPECT_EQ(output.json(), ""); +} + +TEST(TerraSigner, SignWasmGenericWithCoins) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(3407705); + input.set_chain_id("phoenix-1"); + input.set_memo(""); + input.set_sequence(9); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + const auto tokenContractAddress = "terra1sepfj7s0aeg5967uxnfk4thzlerrsktkpelm5s"; + const auto txMessage = R"({ "deposit_stable": {} })"; + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_execute_contract_generic(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + message.set_execute_msg(txMessage); + + auto amount = message.add_coins(); + amount->set_denom("uusd"); + amount->set_amount("1000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(600000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("7000"); + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerraV2); + + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "CrABCq0BCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QShAEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTFzZXBmajdzMGFlZzU5Njd1eG5mazR0aHpsZXJyc2t0a3BlbG01cxoYeyAiZGVwb3NpdF9zdGFibGUiOiB7fSB9KgwKBHV1c2QSBDEwMDASZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA3BmOsew+Ykpb+tc5Z+6SlDX6U4c4lbf8iUUWcos2awAEgQKAggBGAkSEwoNCgV1bHVuYRIENzAwMBDAzyQaQEDA2foXegF+rslj6o8bX2HPJfn+q/6Ezbq2iAd0SFOTQqS8aAyywQkdZJRToXcaby1HOYL1WvmsMPgrFzChiY4=", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + + EXPECT_EQ(hex(output.signature()), "40c0d9fa177a017eaec963ea8f1b5f61cf25f9feabfe84cdbab688077448539342a4bc680cb2c1091d649453a1771a6f2d473982f55af9ac30f82b1730a1898e"); + EXPECT_EQ(output.error_message(), ""); + EXPECT_EQ(output.json(), ""); +} + +TEST(TerraSigner, SignWasmSendTx) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(3407705); + input.set_chain_id("phoenix-1"); + input.set_memo(""); + input.set_sequence(4); + + Address fromAddress; + ASSERT_TRUE(Address::decode("terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", fromAddress)); + Address toAddress; + ASSERT_TRUE(Address::decode("terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", toAddress)); + const auto tokenContractAddress = "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76"; + + auto msg = input.add_messages(); + auto& message = *msg->mutable_wasm_execute_contract_send_message(); + message.set_sender_address(fromAddress.string()); + message.set_contract_address(tokenContractAddress); + const auto amount = store(uint256_t(250000), 0); + message.set_amount(amount.data(), amount.size()); + message.set_recipient_contract_address(toAddress.string()); + const auto msgMsg = Base64::encode(data(std::string(R"({"some_message":{}})"))); + EXPECT_EQ(msgMsg, "eyJzb21lX21lc3NhZ2UiOnt9fQ=="); + message.set_msg(msgMsg); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uluna"); + amountOfFee->set_amount("3000"); + + std::string json; + google::protobuf::util::MessageToJsonString(input, &json); + assertJSONEqual(json, R"( + { + "signingMode": "Protobuf", + "accountNumber": "3407705", + "chainId": "phoenix-1", + "fee": { + "amounts": [ + { + "denom": "uluna", + "amount": "3000" + } + ], + "gas": "200000" + }, + "sequence": "4", + "messages": [ + { + "wasmExecuteContractSendMessage": { + "senderAddress": "terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf", + "contractAddress": "terra14z56l0fp2lsf86zy3hty2z47ezkhnthtr9yq76", + "amount": "A9CQ", + "recipientContractAddress": "terra1jlgaqy9nvn2hf5t2sra9ycz8s77wnf9l0kmgcp", + "msg": "eyJzb21lX21lc3NhZ2UiOnt9fQ==" + } + } + ] + } + )"); + + auto privateKey = parse_hex("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Cosmos::Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeTerraV2); + + assertJSONEqual(output.serialized(), R"( + { + "tx_bytes": "CoUCCoICCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QS2QEKLHRlcnJhMTh3dWtwODRkcTIyN3d1NG1naDBqbTZuOW5sbmo2cnM4MnBwOXdmEix0ZXJyYTE0ejU2bDBmcDJsc2Y4Nnp5M2h0eTJ6NDdlemtobnRodHI5eXE3Nhp7eyJzZW5kIjp7ImFtb3VudCI6IjI1MDAwMCIsImNvbnRyYWN0IjoidGVycmExamxnYXF5OW52bjJoZjV0MnNyYTl5Y3o4czc3d25mOWwwa21nY3AiLCJtc2ciOiJleUp6YjIxbFgyMWxjM05oWjJVaU9udDlmUT09In19EmcKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNwZjrHsPmJKW/rXOWfukpQ1+lOHOJW3/IlFFnKLNmsABIECgIIARgEEhMKDQoFdWx1bmESBDMwMDAQwJoMGkBKJbW1GDrv9j2FIckm7MtpDZzP2RjgDjU84oYmOHNHsxEBPLjtt3YAjsKWBCAsjbnbVoJ3s2XFG08nxQXS9xBK", + "mode": "BROADCAST_MODE_BLOCK" + } + )"); + EXPECT_EQ(hex(output.signature()), "4a25b5b5183aeff63d8521c926eccb690d9ccfd918e00e353ce28626387347b311013cb8edb776008ec29604202c8db9db568277b365c51b4f27c505d2f7104a"); + EXPECT_EQ(output.error_message(), ""); + EXPECT_EQ(output.json(), ""); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/TerraV2/TWCoinTypeTests.cpp b/tests/chains/Cosmos/TerraV2/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..a38f54b77e8 --- /dev/null +++ b/tests/chains/Cosmos/TerraV2/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + +TEST(TWTerraCoinType, TWCoinType20) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTerraV2)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("CFF732C6EBEE06FFA08ABE54EE1657FD53E90FAA81604619E2062C46572A6986")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTerraV2, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("terra16t3gx5rqvz6ru37yzn3shuu20erv4ngmfr59zf")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTerraV2, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTerraV2)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTerraV2)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTerraV2), 6); + ASSERT_EQ(TWBlockchainCosmos, TWCoinTypeBlockchain(TWCoinTypeTerraV2)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTerraV2)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTerraV2)); + assertStringsEqual(symbol, "LUNA"); + assertStringsEqual(txUrl, "https://finder.terra.money/mainnet/tx/CFF732C6EBEE06FFA08ABE54EE1657FD53E90FAA81604619E2062C46572A6986"); + assertStringsEqual(accUrl, "https://finder.terra.money/mainnet/address/terra16t3gx5rqvz6ru37yzn3shuu20erv4ngmfr59zf"); + assertStringsEqual(id, "terrav2"); + assertStringsEqual(name, "Terra"); +} + +} // namespace TW::Cosmos::tests + diff --git a/tests/chains/Cosmos/Tia/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Tia/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..29c181f36b2 --- /dev/null +++ b/tests/chains/Cosmos/Tia/TWAnyAddressTests.cpp @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gTiaAddr = "celestia1mry47pkga5tdswtluy0m8teslpalkdq00tp7xc"; +static const std::string gTiaHrp = "celestia"; + +TEST(TWTiaAnyAddress, AllTiaAddressTests) { + CosmosAddressParameters parameters{.hrp = gTiaHrp, .coinType = TWCoinTypeTia, .address = gTiaAddr}; + TestCosmosAddressParameters(parameters); +} + +} + \ No newline at end of file diff --git a/tests/chains/Cosmos/Tia/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Tia/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..17f8d8d59a0 --- /dev/null +++ b/tests/chains/Cosmos/Tia/TWCoinTypeTests.cpp @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + +TEST(TWTiaCoinType, TWCoinType) { + const auto coin = TWCoinTypeTia; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("FF370C65D8D67B8236F9D3A8D2B1256337C60C1965092CADD1FA970288FCE99B")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("celestia1tt4tv4jrs4twdtzwywxd8u65duxgk8y73wvfu2")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + assertStringsEqual(id, "tia"); + assertStringsEqual(name, "Celestia"); + assertStringsEqual(symbol, "TIA"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "celestia"); + assertStringsEqual(txUrl, "https://www.mintscan.io/celestia/txs/FF370C65D8D67B8236F9D3A8D2B1256337C60C1965092CADD1FA970288FCE99B"); + assertStringsEqual(accUrl, "https://www.mintscan.io/celestia/account/celestia1tt4tv4jrs4twdtzwywxd8u65duxgk8y73wvfu2"); +} + +} // namespace TW::Cosmos::tests diff --git a/tests/chains/Cosmos/TransactionCompilerTests.cpp b/tests/chains/Cosmos/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..e55e966a13f --- /dev/null +++ b/tests/chains/Cosmos/TransactionCompilerTests.cpp @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base64.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Cosmos.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(CosmosCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeCosmos; + TW::Cosmos::Proto::SigningInput input; + + PrivateKey privateKey = + PrivateKey(parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + /// Step 1: Prepare transaction input (protobuf) + input.set_account_number(546179); + input.set_chain_id("cosmoshub-4"); + input.set_memo(""); + input.set_sequence(0); + + PublicKey publicKey = privateKey.getPublicKey(TWCoinTypePublicKeyType(coin)); + const auto pubKeyBz = publicKey.bytes; + input.set_public_key(pubKeyBz.data(), pubKeyBz.size()); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address("cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx"); + message.set_to_address("cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp"); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("uatom"); + amountOfTx->set_amount("400000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("uatom"); + amountOfFee->set_amount("1000"); + + /// Step 2: Obtain protobuf preimage hash + input.set_signing_mode(TW::Cosmos::Proto::Protobuf); + auto protoInputString = input.SerializeAsString(); + auto protoInputData = TW::Data(protoInputString.begin(), protoInputString.end()); + + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, protoInputData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + auto preImage = preSigningOutput.data(); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ( + hex(preImage), + "0a92010a8f010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e64126f0a2d636f736d6f" + "73316d6b793639636e38656b74777930383435766563397570736470686b7478743033676b776c78122d636f73" + "6d6f733138733068646e736c6c6763636c7765753961796d77346e676b7472326b30726b7967647a64701a0f0a" + "057561746f6d120634303030303012650a4e0a460a1f2f636f736d6f732e63727970746f2e736563703235366b" + "312e5075624b657912230a2102ecef5ce437a302c67f95468de4b31f36e911f467d7e6a52b41c1e13e1d563649" + "12040a02080112130a0d0a057561746f6d12043130303010c09a0c1a0b636f736d6f736875622d342083ab21"); + EXPECT_EQ(hex(preImageHash), + "fa7990e1814c900efaedf1bdbedba22c22336675befe0ae39974130fc204f3de"); + + auto expectedTx = + "7b226d6f6465223a2242524f4144434153545f4d4f44455f424c4f434b222c2274785f6279746573223a224370" + "4942436f3842436877765932397a6257397a4c6d4a68626d7375646a46695a5852684d53354e633264545a5735" + "6b456d384b4c574e7663323176637a467461336b324f574e754f475672644864354d4467304e585a6c597a6c31" + "63484e6b63476872644868304d444e6e61336473654249745932397a6257397a4d54687a4d47686b626e4e7362" + "47646a593278335a58553559586c74647a52755a327430636a4a724d484a726557646b656d52774767384b4258" + "566864473974456759304d4441774d4441535a51704f436b594b4879396a62334e7462334d7559334a35634852" + "764c6e4e6c593341794e545a724d53355164574a4c5a586b5349776f6841757a76584f51336f774c4766355647" + "6a65537a487a62704566526e312b616c4b30484234543464566a5a4a4567514b4167674245684d4b44516f4664" + "57463062323053424445774d444151774a6f4d476b437676564536643239503330634f392f6c6e587947756e57" + "4d50784e5931324e75714463436e466b4e4d304834435551646c314763392b6f67494a62726f356e797a5a7a6c" + "7639726c322f47735a6f782f4a586f4358227d"; + Data signature; + + { + TW::Cosmos::Proto::SigningOutput output; + ANY_SIGN(input, coin); + + assertJSONEqual( + output.serialized(), + "{\"tx_bytes\": " + "\"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZl" + "Yzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg" + "8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLG" + "f5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/" + "lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX\", \"mode\": " + "\"BROADCAST_MODE_BLOCK\"}"); + + signature = data(output.signature()); + EXPECT_EQ(hex(signature), + "afbd513a776f4fdf470ef7f9675f21ae9d630fc4d635d8dbaa0dc0a716434cd07e02510765d4673dfa88" + "0825bae8e67cb367396ff6b976fc6b19a31fc95e8097"); + + ASSERT_TRUE(publicKey.verify(signature, data(preImageHash.data()))); + EXPECT_EQ(hex(output.serialized()), expectedTx); + } + + { + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, protoInputData, {signature}, {publicKey.bytes}); + Cosmos::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(hex(output.serialized()), expectedTx); + } + + { // Negative: not enough signatures + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, protoInputData, {signature, signature}, {publicKey.bytes}); + Cosmos::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.serialized().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, protoInputData, {}, {}); + Cosmos::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.serialized().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); + } + + /// Step 3: Obtain json preimage hash + input.set_signing_mode(TW::Cosmos::Proto::JSON); + auto jsonInputString = input.SerializeAsString(); + auto jsonInputData = TW::Data(jsonInputString.begin(), jsonInputString.end()); + + const auto jsonPreImageHashData = TransactionCompiler::preImageHashes(coin, jsonInputData); + auto jsonPreSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE(jsonPreSigningOutput.ParseFromArray(jsonPreImageHashData.data(), + (int)jsonPreImageHashData.size())); + ASSERT_EQ(jsonPreSigningOutput.error(), 0); + auto jsonPreImage = jsonPreSigningOutput.data(); + auto jsonPreImageHash = jsonPreSigningOutput.data_hash(); + EXPECT_EQ(hex(jsonPreImage), + "7b226163636f756e745f6e756d626572223a22353436313739222c22636861696e5f6964223a22636f73" + "6d6f736875622d34222c22666565223a7b22616d6f756e74223a5b7b22616d6f756e74223a2231303030" + "222c2264656e6f6d223a227561746f6d227d5d2c22676173223a22323030303030227d2c226d656d6f22" + "3a22222c226d736773223a5b7b2274797065223a22636f736d6f732d73646b2f4d736753656e64222c22" + "76616c7565223a7b22616d6f756e74223a5b7b22616d6f756e74223a22343030303030222c2264656e6f" + "6d223a227561746f6d227d5d2c2266726f6d5f61646472657373223a22636f736d6f73316d6b79363963" + "6e38656b74777930383435766563397570736470686b7478743033676b776c78222c22746f5f61646472" + "657373223a22636f736d6f733138733068646e736c6c6763636c7765753961796d77346e676b7472326b" + "30726b7967647a6470227d7d5d2c2273657175656e6365223a2230227d"); + EXPECT_EQ(hex(jsonPreImageHash), + "0a31f6cd50f1a5c514929ba68a977e222a7df2dc11e8470e93118cc3545e6b37"); + + signature = Base64::decode("tTyOrburrHEHa14qiw78e9StoZyyGmoku98IxYrWCmtN8Qo5mTeKa0BKKDfgG4LmmNdwYcrXtqQQ7F4dL3c26g=="); + + { // JSON + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, jsonInputData, {signature}, {publicKey.bytes}); + Cosmos::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(hex(output.json()), "7b226d6f6465223a22626c6f636b222c227478223a7b22666565223a7b22616d6f756e74223a5b7b22616d6f756e74223a2231303030222c2264656e6f6d223a227561746f6d227d5d2c22676173223a22323030303030227d2c226d656d6f223a22222c226d7367223a5b7b2274797065223a22636f736d6f732d73646b2f4d736753656e64222c2276616c7565223a7b22616d6f756e74223a5b7b22616d6f756e74223a22343030303030222c2264656e6f6d223a227561746f6d227d5d2c2266726f6d5f61646472657373223a22636f736d6f73316d6b793639636e38656b74777930383435766563397570736470686b7478743033676b776c78222c22746f5f61646472657373223a22636f736d6f733138733068646e736c6c6763636c7765753961796d77346e676b7472326b30726b7967647a6470227d7d5d2c227369676e617475726573223a5b7b227075625f6b6579223a7b2274797065223a2274656e6465726d696e742f5075624b6579536563703235366b31222c2276616c7565223a2241757a76584f51336f774c47663556476a65537a487a62704566526e312b616c4b30484234543464566a5a4a227d2c227369676e6174757265223a227454794f72627572724845486131347169773738653953746f5a7979476d6f6b7539384978597257436d744e38516f356d54654b6130424b4b44666747344c6d6d4e64775963725874715151374634644c33633236673d3d227d5d7d7d"); + } +} diff --git a/tests/chains/Cosmos/Umee/TWAnyAddressTests.cpp b/tests/chains/Cosmos/Umee/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..2e1ed50c9f3 --- /dev/null +++ b/tests/chains/Cosmos/Umee/TWAnyAddressTests.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "../CosmosTestHelpers.h" + +namespace TW::Cosmos::tests { + +static const std::string gUmeeAddr = "umee1mry47pkga5tdswtluy0m8teslpalkdq0vhd3c8"; +static const std::string gUmeeHrp = "umee"; + +TEST(TWUmeeAnyAddress, AllUmeeAddressTests) { + CosmosAddressParameters parameters{.hrp = gUmeeHrp, .coinType = TWCoinTypeUmee, .address = gUmeeAddr}; + TestCosmosAddressParameters(parameters); +} + +} diff --git a/tests/chains/Cosmos/Umee/TWCoinTypeTests.cpp b/tests/chains/Cosmos/Umee/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..be9788b42c0 --- /dev/null +++ b/tests/chains/Cosmos/Umee/TWCoinTypeTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +namespace TW::Cosmos::tests { + TEST(TWUmeeCoinType, TWCoinType) { + const auto coin = TWCoinTypeUmee; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("65B4B52C5F324F2287540847A114F645D89D544D99F793879FB3DBFF2CFEFC84")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("umee16934q0qf4akw8qruy5y8v748rvtxxjckgsecq4")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "umee"); + assertStringsEqual(name, "Umee"); + assertStringsEqual(symbol, "UMEE"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "umee-1"); + assertStringsEqual(txUrl, "https://www.mintscan.io/umee/txs/65B4B52C5F324F2287540847A114F645D89D544D99F793879FB3DBFF2CFEFC84"); + assertStringsEqual(accUrl, "https://www.mintscan.io/umee/account/umee16934q0qf4akw8qruy5y8v748rvtxxjckgsecq4"); + } +} diff --git a/tests/chains/Cronos/TWAnyAddressTests.cpp b/tests/chains/Cronos/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..2e1c6a0dbdd --- /dev/null +++ b/tests/chains/Cronos/TWAnyAddressTests.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include +#include + +#include + +TEST(CronosAnyAddress, Validate) { + auto string = STRING("0xEC49280228b0D05Aa8e8b756503254e1eE7835ab"); + + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeCronosChain)); + + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "ec49280228b0d05aa8e8b756503254e1ee7835ab"); +} diff --git a/tests/chains/Cronos/TWCoinTypeTests.cpp b/tests/chains/Cronos/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..0b05cd3f785 --- /dev/null +++ b/tests/chains/Cronos/TWCoinTypeTests.cpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +TEST(TWCronosCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeCronosChain)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x850131282053328ad569fa91200aa970cbed7d97b52951fe8b449cca3708789e")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeCronosChain, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x44eed2bb80b688a8778173c19fe11cd6876af15a")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeCronosChain, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeCronosChain)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeCronosChain)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeCronosChain), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeCronosChain)); + + assertStringsEqual(symbol, "CRO"); + assertStringsEqual(txUrl, "https://cronoscan.com/tx/0x850131282053328ad569fa91200aa970cbed7d97b52951fe8b449cca3708789e"); + assertStringsEqual(accUrl, "https://cronoscan.com/address/0x44eed2bb80b688a8778173c19fe11cd6876af15a"); + assertStringsEqual(id, "cronos"); + assertStringsEqual(name, "Cronos Chain"); +} diff --git a/tests/chains/Dash/TWCoinTypeTests.cpp b/tests/chains/Dash/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..6b6cab670eb --- /dev/null +++ b/tests/chains/Dash/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWDashCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeDash)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeDash, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeDash, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeDash)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeDash)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeDash), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeDash)); + ASSERT_EQ(0x10, TWCoinTypeP2shPrefix(TWCoinTypeDash)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeDash)); + assertStringsEqual(symbol, "DASH"); + assertStringsEqual(txUrl, "https://blockchair.com/dash/transaction/t123"); + assertStringsEqual(accUrl, "https://blockchair.com/dash/address/a12"); + assertStringsEqual(id, "dash"); + assertStringsEqual(name, "Dash"); +} diff --git a/tests/chains/Dash/TWDashTests.cpp b/tests/chains/Dash/TWDashTests.cpp new file mode 100644 index 00000000000..2130faba855 --- /dev/null +++ b/tests/chains/Dash/TWDashTests.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include + +#include + +TEST(Dash, LockScripts) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("XgkpWEFe59pX3aMhx6PrDawjNnoZKHeZbp").get(), TWCoinTypeDash)); + auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); + assertHexEqual(scriptData, "76a91442914f5b70c61619eca5359df57d0b9bdcf8ccff88ac"); + + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("7eprxeVjKfVgS8p2RNsZ89K72YV61xg4gq").get(), TWCoinTypeDash)); + auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); + assertHexEqual(scriptData2, "a9148835ae54f297ad069552a1401e535dfe5f396f6187"); +} diff --git a/tests/chains/Decred/AddressTests.cpp b/tests/chains/Decred/AddressTests.cpp new file mode 100644 index 00000000000..51c855ce811 --- /dev/null +++ b/tests/chains/Decred/AddressTests.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Decred/Address.h" + +#include "Coin.h" +#include "HDWallet.h" +#include "HexCoding.h" + +#include + +namespace TW::Decred::tests { + +TEST(DecredAddress, FromPublicKey) { + { + const auto publicKey = PublicKey(parse_hex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), TWPublicKeyTypeSECP256k1); + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "DsmcYVbP1Nmag2H4AS17UTvmWXmGeA7nLDx"); + } + { + const auto privateKey = PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")); + const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeED25519)); + EXPECT_ANY_THROW(new Address(publicKey)); + } +} + +TEST(DecredAddress, Valid) { + ASSERT_TRUE(Address::isValid("DsmcYVbP1Nmag2H4AS17UTvmWXmGeA7nLDx")); + ASSERT_TRUE(Address::isValid("Dcur2mcGjmENx4DhNqDctW5wJCVyT3Qeqkx")); +} + +TEST(DecredAddress, Invalid) { + ASSERT_FALSE(Address::isValid("rnBFvgZphmN39GWzUJeUitaP22Fr9be75H")); + ASSERT_FALSE(Address::isValid("t3gQDEavk5VzAAHK8TrQu2BWDLxEiF1unBm")); +} + +TEST(DecredAddress, FromString) { + const auto string = "DsmcYVbP1Nmag2H4AS17UTvmWXmGeA7nLDx"; + const auto address = Address(string); + + ASSERT_EQ(address.string(), string); +} + +TEST(DecredAddress, Derive) { + const auto mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + const auto wallet = HDWallet(mnemonic, ""); + const auto path = TW::derivationPath(TWCoinTypeDecred); + const auto address = TW::deriveAddress(TWCoinTypeDecred, wallet.getKey(TWCoinTypeDecred, path)); + ASSERT_EQ(address, "DsVMHD5D86dpRnt2GPZvv4bYUJZg6B9Pzqa"); +} + +} // namespace TW::Decred::tests diff --git a/tests/chains/Decred/SignerTests.cpp b/tests/chains/Decred/SignerTests.cpp new file mode 100644 index 00000000000..e698910b17c --- /dev/null +++ b/tests/chains/Decred/SignerTests.cpp @@ -0,0 +1,517 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Decred/Address.h" +#include "Decred/Signer.h" +#include "proto/Decred.pb.h" + +#include "Hash.h" +#include "HexCoding.h" +#include "PrivateKey.h" + +#include +#include + +using namespace TW; + +namespace TW::Decred::tests { + +// clang-format off +TEST(DecredSigner, SignP2PKH) { + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); + + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + + // For this example, create a fake transaction that represents what would + // ordinarily be the real transaction that is being spent. It contains a + // single output that pays to address in the amount of 1 DCR. + auto originTx = Transaction(); + + auto txInOrigin = TransactionInput(); + txInOrigin.previousOutput = OutPoint(std::array{}, UINT32_MAX, 0); + txInOrigin.valueIn = 100'000'000; + txInOrigin.script = Bitcoin::Script(Data{OP_0, OP_0}); + originTx.inputs.push_back(txInOrigin); + + auto txOutOrigin = TransactionOutput(); + txOutOrigin.value = 100'000'000; + txOutOrigin.script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + originTx.outputs.push_back(txOutOrigin); + + ASSERT_EQ(hex(originTx.hash()), "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a"); + + + // Setup input + Bitcoin::Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(100'000'000); + input.set_byte_fee(1); + input.set_to_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + input.set_change_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + auto utxoKey0 = parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto utxo0 = input.add_utxo(); + auto utxo0Script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(100'000'000); + utxo0->mutable_out_point()->set_hash(originTx.hash().data(), originTx.hash().size()); + utxo0->mutable_out_point()->set_index(0); + + + // Create the transaction to redeem the fake transaction. + auto redeemTx = Transaction(); + + auto txIn = TransactionInput(); + txIn.previousOutput = OutPoint(originTx.hash(), 0, 0); + txIn.valueIn = 100'000'000; + redeemTx.inputs.push_back(txIn); + + auto txOut = TransactionOutput(); + redeemTx.outputs.push_back(txOut); + + auto plan = input.mutable_plan(); + plan->set_amount(100'000'000); + plan->set_available_amount(100'000'000); + plan->set_fee(0); + plan->set_change(0); + auto utxop0 = plan->add_utxos(); + *utxop0 = *utxo0; + + // Sign + auto signer = Signer(std::move(input)); + signer._transaction = redeemTx; + signer.txPlan.amount = 100'000'000; + const auto result = signer.sign(); + + ASSERT_TRUE(result); + + const auto expectedSignature = "47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f0121" + "02a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf5"; + EXPECT_EQ(hex(result.payload().inputs[0].script.bytes), expectedSignature); + + const auto expectedEncoded = + "0100" // Serialize type + "0000" // Version + "01" // Inputs + "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a" // Hash + "00000000" // Index + "00" // Tree + "ffffffff" // Sequence + "01" // Outputs + "0000000000000000" // Value + "0000" // Version + "00" // Script + "00000000" // Lock time + "00000000" // Expiry + "01" + "00e1f50500000000" // Value + "00000000" // Block height + "ffffffff" // Block index + "6a47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f012102a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf5"; + auto encoded = Data(); + result.payload().encode(encoded); + EXPECT_EQ(hex(encoded), expectedEncoded); +} + +TEST(DecredSigner, SignP2PK) { + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); + + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + + // For this example, create a fake transaction that represents what would + // ordinarily be the real transaction that is being spent. It contains a + // single output that pays to address in the amount of 1 DCR. + auto originTx = Transaction(); + + auto txInOrigin = TransactionInput(); + txInOrigin.previousOutput = OutPoint(std::array{}, UINT32_MAX, 0); + txInOrigin.valueIn = 100'000'000; + txInOrigin.script = Bitcoin::Script(Data{OP_0, OP_0}); + originTx.inputs.push_back(txInOrigin); + + auto txOutOrigin = TransactionOutput(); + txOutOrigin.value = 100'000'000; + txOutOrigin.script = Bitcoin::Script::buildPayToPublicKey(publicKey.bytes); + originTx.outputs.push_back(txOutOrigin); + + ASSERT_EQ(hex(originTx.hash()), "ac59d53c1eaf3fa41a47a9fca9d6b5b79857e7ef8cbe2556460e690e71eeb08b"); + + // Setup input + Bitcoin::Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(100'000'000); + input.set_byte_fee(1); + input.set_to_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + input.set_change_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + auto utxoKey0 = parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto utxo0 = input.add_utxo(); + auto utxo0Script = Bitcoin::Script::buildPayToPublicKey(publicKey.bytes); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(100'000'000); + utxo0->mutable_out_point()->set_hash(originTx.hash().data(), originTx.hash().size()); + utxo0->mutable_out_point()->set_index(0); + + // Create the transaction to redeem the fake transaction. + auto redeemTx = Transaction(); + + auto txIn = TransactionInput(); + txIn.previousOutput = OutPoint(originTx.hash(), 0, 0); + txIn.valueIn = 100'000'000; + redeemTx.inputs.push_back(txIn); + + auto txOut = TransactionOutput(); + redeemTx.outputs.push_back(txOut); + + auto plan = input.mutable_plan(); + plan->set_amount(100'000'000); + plan->set_available_amount(100'000'000); + plan->set_fee(0); + plan->set_change(0); + auto utxop0 = plan->add_utxos(); + *utxop0 = *utxo0; + + // Sign + auto signer = Signer(std::move(input)); + signer._transaction = redeemTx; + signer.txPlan.amount = 100'000'000; + const auto result = signer.sign(); + + ASSERT_TRUE(result); + + const auto expectedSignature = "47304402202f9e6b7c849fc4ebcea8f544680f2652d24cede340b9370084d740e6483f44a60220582c9e70faa35fdf5af38a4268ef5564bbb527fb4e39303afba6ce71848a7b9301"; + EXPECT_EQ(hex(result.payload().inputs[0].script.bytes), expectedSignature); + + const auto expectedEncoded = + "0100000001ac59d53c1eaf3fa41a47a9fca9d6b5b79857e7ef8cbe2556460e690e71eeb08b0000000000ffffffff01000000000000000000000000000000000000000100e1f5050000000000000000ffffffff4847304402202f9e6b7c849fc4ebcea8f544680f2652d24cede340b9370084d740e6483f44a60220582c9e70faa35fdf5af38a4268ef5564bbb527fb4e39303afba6ce71848a7b9301"; + auto encoded = Data(); + result.payload().encode(encoded); + EXPECT_EQ(hex(encoded), expectedEncoded); +} + +TEST(DecredSigner, SignP2SH) { + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); + + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + + // For this example, create a fake transaction that represents what would + // ordinarily be the real transaction that is being spent. It contains a + // single output that pays to address in the amount of 1 DCR. + auto originTx = Transaction(); + + auto txInOrigin = TransactionInput(); + txInOrigin.previousOutput = OutPoint(std::array{}, UINT32_MAX, 0); + txInOrigin.valueIn = 100'000'000; + txInOrigin.script = Bitcoin::Script(Data{OP_0, OP_0}); + originTx.inputs.push_back(txInOrigin); + + auto txOutOrigin = TransactionOutput(); + txOutOrigin.value = 100'000'000; + txOutOrigin.script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + originTx.outputs.push_back(txOutOrigin); + + ASSERT_EQ(hex(originTx.hash()), "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a"); + + + // Setup input + Bitcoin::Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(100'000'000); + input.set_byte_fee(1); + input.set_to_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + input.set_change_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + auto utxoKey0 = parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto redeemScript = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[hex(scriptHash)] = scriptString; + + auto utxo0 = input.add_utxo(); + auto utxo0Script = Bitcoin::Script::buildPayToScriptHash(scriptHash); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(100'000'000); + utxo0->mutable_out_point()->set_hash(originTx.hash().data(), originTx.hash().size()); + utxo0->mutable_out_point()->set_index(0); + + auto plan = input.mutable_plan(); + plan->set_amount(100'000'000); + plan->set_available_amount(100'000'000); + plan->set_fee(0); + plan->set_change(0); + auto utxop0 = plan->add_utxos(); + *utxop0 = *utxo0; + + // Create the transaction to redeem the fake transaction. + auto redeemTx = Transaction(); + + auto txIn = TransactionInput(); + txIn.previousOutput = OutPoint(originTx.hash(), 0, 0); + txIn.valueIn = 100'000'000; + redeemTx.inputs.push_back(txIn); + + auto txOut = TransactionOutput(); + redeemTx.outputs.push_back(txOut); + + + // Sign + auto signer = Signer(std::move(input)); + signer._transaction = redeemTx; + signer.txPlan.amount = 100'000'000; + const auto result = signer.sign(); + + ASSERT_TRUE(result); + + const auto expectedSignature = "47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f012102a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf51976a914f5eba6730a4052ddeef0a93d93d24004f49db51e88ac1976a914f5eba6730a4052ddeef0a93d93d24004f49db51e88ac"; + EXPECT_EQ(hex(result.payload().inputs[0].script.bytes), expectedSignature); + + const auto expectedEncoded = + "0100" // Serialize type + "0000" // Version + "01" // Inputs + "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a" // Hash + "00000000" // Index + "00" // Tree + "ffffffff" // Sequence + "01" // Outputs + "0000000000000000" // Value + "0000" // Version + "00" // Script + "00000000" // Lock time + "00000000" // Expiry + "01" + "00e1f50500000000" // Value + "00000000" // Block height + "ffffffff" // Block index + "9e47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f012102a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf51976a914f5eba6730a4052ddeef0a93d93d24004f49db51e88ac1976a914f5eba6730a4052ddeef0a93d93d24004f49db51e88ac"; + auto encoded = Data(); + result.payload().encode(encoded); + EXPECT_EQ(hex(encoded), expectedEncoded); +} + +TEST(DecredSigner, SignNegativeNoUtxo) { + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); + + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + auto originTx = Transaction(); + + auto txInOrigin = TransactionInput(); + txInOrigin.previousOutput = OutPoint(std::array{}, UINT32_MAX, 0); + txInOrigin.valueIn = 100'000'000; + txInOrigin.script = Bitcoin::Script(Data{OP_0, OP_0}); + originTx.inputs.push_back(txInOrigin); + + auto txOutOrigin = TransactionOutput(); + txOutOrigin.value = 100'000'000; + txOutOrigin.script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + originTx.outputs.push_back(txOutOrigin); + + ASSERT_EQ(hex(originTx.hash()), "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a"); + + // Setup input + Bitcoin::Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(100'000'000); + input.set_byte_fee(1); + input.set_to_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + input.set_change_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + auto utxoKey0 = parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + // Create the transaction to redeem the fake transaction. + auto redeemTx = Transaction(); + + auto txIn = TransactionInput(); + txIn.previousOutput = OutPoint(originTx.hash(), 0, 0); + txIn.valueIn = 100'000'000; + redeemTx.inputs.push_back(txIn); + + auto txOut = TransactionOutput(); + redeemTx.outputs.push_back(txOut); + + // Sign + auto signer = Signer(std::move(input)); + signer._transaction = redeemTx; + signer.txPlan.amount = 100'000'000; + const auto result = signer.sign(); + + // Fails as there are 0 utxos + ASSERT_FALSE(result); +} + +TEST(DecredSigner, SignP2PKH_NoPlan) { + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); + + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + + // For this example, create a fake transaction that represents what would + // ordinarily be the real transaction that is being spent. It contains a + // single output that pays to address in the amount of 1 DCR. + auto originTx = Transaction(); + + auto txInOrigin = TransactionInput(); + txInOrigin.previousOutput = OutPoint(std::array{}, UINT32_MAX, 0); + txInOrigin.valueIn = 150'000'000; + txInOrigin.script = Bitcoin::Script(Data{OP_0, OP_0}); + originTx.inputs.push_back(txInOrigin); + + auto txOutOrigin = TransactionOutput(); + txOutOrigin.value = 100'000'000; + txOutOrigin.script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + originTx.outputs.push_back(txOutOrigin); + + ASSERT_EQ(hex(originTx.hash()), "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a"); + + + // Setup input + Bitcoin::Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(100'000'000); + input.set_byte_fee(1); + input.set_to_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + input.set_change_address("DsoPDLh462ULTy1QMSvBGLqGKQENerrdZDH"); + + auto utxoKey0 = parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto utxo0 = input.add_utxo(); + auto utxo0Script = Bitcoin::Script::buildPayToPublicKeyHash(keyhash); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(150'000'000); + utxo0->mutable_out_point()->set_hash(originTx.hash().data(), originTx.hash().size()); + utxo0->mutable_out_point()->set_index(0); + + + // Create the transaction to redeem the fake transaction. + auto redeemTx = Transaction(); + + auto txIn = TransactionInput(); + txIn.previousOutput = OutPoint(originTx.hash(), 0, 0); + txIn.valueIn = 100'000'000; + redeemTx.inputs.push_back(txIn); + + auto txOut = TransactionOutput(); + redeemTx.outputs.push_back(txOut); + + + // Sign + auto signer = Signer(std::move(input)); + signer._transaction = redeemTx; + //signer.txPlan.utxos.push_back(*utxo0); + //signer.txPlan.amount = 100'000'000; + const auto result = signer.sign(); + + ASSERT_TRUE(result); + ASSERT_TRUE(result.payload().inputs.size() >= 1); + + const auto expectedSignature = "47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f0121" + "02a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf5"; + EXPECT_EQ(hex(result.payload().inputs[0].script.bytes), expectedSignature); + + const auto expectedEncoded = + "0100" // Serialize type + "0000" // Version + "01" // Inputs + "0ff6ff7c6774a56ccc51598b11724c9c441cadc52978ddb5f08f3511a0cc777a" // Hash + "00000000" // Index + "00" // Tree + "ffffffff" // Sequence + "01" // Outputs + "0000000000000000" // Value + "0000" // Version + "00" // Script + "00000000" // Lock time + "00000000" // Expiry + "01" + "00e1f50500000000" // Value + "00000000" // Block height + "ffffffff" // Block index + "6a47304402201ac7bdf56a9d12f3bc09cf7b47cdfafc1348628f659e37b455d497cb6e7a748802202b3630eedee1bbc9248424e4a1b8671e14631a069f36ac8860dee0bb9ea1541f012102a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf5"; + auto encoded = Data(); + result.payload().encode(encoded); + EXPECT_EQ(hex(encoded), expectedEncoded); +} + +TEST(DecredSigning, SignP2WPKH_NegativeAddressWrongType) { + auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); + auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); + + // Setup input + Bitcoin::Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(335'790'000); + input.set_byte_fee(1); + input.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); + input.set_change_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"); + + auto utxoKey0 = parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto utxoKey1 = parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"); + input.add_private_key(utxoKey1.data(), utxoKey1.size()); + + auto scriptPub1 = Bitcoin::Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); + auto scriptHash = std::vector(); + scriptPub1.matchPayToWitnessPublicKeyHash(scriptHash); + auto scriptHashHex = hex(scriptHash); + ASSERT_EQ(scriptHashHex, "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + + auto redeemScript = Bitcoin::Script::buildPayToPublicKeyHash(scriptHash); + auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*input.mutable_scripts())[scriptHashHex] = scriptString; + + auto utxo0 = input.add_utxo(); + auto utxo0Script = parse_hex("2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac"); + utxo0->set_script(utxo0Script.data(), utxo0Script.size()); + utxo0->set_amount(625'000'000); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + auto utxo1 = input.add_utxo(); + auto utxo1Script = parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); + utxo1->set_script(utxo1Script.data(), utxo1Script.size()); + utxo1->set_amount(600'000'000); + utxo1->mutable_out_point()->set_hash(hash1.data(), hash1.size()); + utxo1->mutable_out_point()->set_index(1); + utxo1->mutable_out_point()->set_sequence(UINT32_MAX); + + // Sign + auto result = Signer::sign(std::move(input)); + ASSERT_NE(result.error(), Common::Proto::OK); + + // PreImageHashes + auto preResult = Signer::preImageHashes(std::move(input)); + ASSERT_NE(preResult.error(), Common::Proto::OK); +} +// clang-format on + +} // namespace TW::Decred::tests diff --git a/tests/chains/Decred/TWAnySignerTests.cpp b/tests/chains/Decred/TWAnySignerTests.cpp new file mode 100644 index 00000000000..faafc923616 --- /dev/null +++ b/tests/chains/Decred/TWAnySignerTests.cpp @@ -0,0 +1,112 @@ + +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include "HexCoding.h" +#include "proto/Bitcoin.pb.h" +#include "proto/Decred.pb.h" +#include +#include +#include +#include + + +namespace TW::Decred { + +Bitcoin::Proto::SigningInput createInput() { + const int64_t utxoValue = 39900000; + const int64_t amount = 10000000; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address("Dsesp1V6DZDEtcq2behmBVKdYqKMdkh96hL"); + input.set_change_address("DsUoWCAxprdGNtKQqambFbTcSBgH1SHn9Gp"); + input.set_coin_type(TWCoinTypeDecred); + + auto& utxo = *input.add_utxo(); + + auto hash = parse_hex("fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd11550"); + auto script = parse_hex("76a914b75fdec70b2e731795dd123ab40f918bf099fee088ac"); + auto utxoKey = parse_hex("ba005cd605d8a02e3d5dfd04234cef3a3ee4f76bfbad2722d1fb5af8e12e6764"); + + utxo.set_amount(utxoValue); + utxo.set_script(script.data(), script.size()); + + auto& outpoint = *utxo.mutable_out_point(); + outpoint.set_hash(hash.data(), hash.size()); + outpoint.set_index(0); + + input.add_private_key(utxoKey.data(), utxoKey.size()); + return input; +} + +TEST(TWAnySignerDecred, Signing) { + auto input = createInput(); + + const int64_t utxoValue = 39900000; + const int64_t amount = 10000000; + const int64_t fee = 100000; + + auto& plan = *input.mutable_plan(); + plan.set_amount(amount); + plan.set_available_amount(utxoValue); + plan.set_fee(fee); + plan.set_change(utxoValue - amount - fee); + auto& planUtxo = *plan.add_utxos(); + planUtxo = input.utxo(0); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeDecred); + + ASSERT_EQ(output.error(), Common::Proto::OK); + ASSERT_EQ(hex(output.encoded()), "0100000001fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd1155000000000000000000002809698000000000000001976a914989b1aecabf1c24e213cc0f2d8a22ffee25dd4e188ac40b6c6010000000000001976a9142a194fc92e27fef9cc2b057bc9060c580cbb484888ac000000000000000001000000000000000000000000ffffffff6a47304402206ee887c9239e5fff0048674bdfff2a8cfbeec6cd4a3ccebcc12fac44b24cc5ac0220718f7c760818fde18bc5ba8457d43d5a145cc4cf13d2a5557cba9107e9f4558d0121026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6"); +} + +TEST(TWAnySignerDecred, Plan) { + auto input = createInput(); + + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(input, plan, TWCoinTypeDecred); + + EXPECT_EQ(plan.amount(), 10000000); + EXPECT_EQ(plan.available_amount(), 39900000); + EXPECT_EQ(plan.fee(), 254); + EXPECT_EQ(plan.change(), 29899746); + EXPECT_EQ(plan.utxos_size(), 1); + EXPECT_EQ(plan.branch_id(), ""); +} + +TEST(TWAnySignerDecred, PlanAndSign) { + auto input = createInput(); + + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(input, plan, TWCoinTypeDecred); + + EXPECT_EQ(plan.amount(), 10000000); + EXPECT_EQ(plan.available_amount(), 39900000); + EXPECT_EQ(plan.fee(), 254); + EXPECT_EQ(plan.change(), 29899746); + EXPECT_EQ(plan.utxos_size(), 1); + EXPECT_EQ(plan.branch_id(), ""); + + // copy over plan fields + *input.mutable_plan() = plan; + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeDecred); + + ASSERT_EQ(output.error(), Common::Proto::OK); + ASSERT_EQ(output.encoded().size(), 251ul); + ASSERT_EQ(hex(output.encoded()), "0100000001fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd1155000000000000000000002809698000000000000001976a914989b1aecabf1c24e213cc0f2d8a22ffee25dd4e188ace23bc8010000000000001976a9142a194fc92e27fef9cc2b057bc9060c580cbb484888ac000000000000000001000000000000000000000000ffffffff6a47304402203e6ee9e16d6bc36bb4242f7a4cac333a1c2a150ea16143412b88b721f6ae16bf02201019affdf815a5c22e4b0fb7e4685c4707094922d6a41354f9055d3bb0f26e630121026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6"); +} + +TEST(TWAnySignerDecred, SupportsJSON) { + ASSERT_FALSE(TWAnySignerSupportsJSON(TWCoinTypeDecred)); +} + +} // namespace diff --git a/tests/chains/Decred/TWCoinTypeTests.cpp b/tests/chains/Decred/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..2a385b663bc --- /dev/null +++ b/tests/chains/Decred/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWDecredCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeDecred)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeDecred, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeDecred, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeDecred)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeDecred)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeDecred), 8); + ASSERT_EQ(TWBlockchainDecred, TWCoinTypeBlockchain(TWCoinTypeDecred)); + ASSERT_EQ(0x1a, TWCoinTypeP2shPrefix(TWCoinTypeDecred)); + ASSERT_EQ(0x7, TWCoinTypeStaticPrefix(TWCoinTypeDecred)); + assertStringsEqual(symbol, "DCR"); + assertStringsEqual(txUrl, "https://dcrdata.decred.org/tx/t123"); + assertStringsEqual(accUrl, "https://dcrdata.decred.org/address/a12"); + assertStringsEqual(id, "decred"); + assertStringsEqual(name, "Decred"); +} diff --git a/tests/Decred/TWDecredTests.cpp b/tests/chains/Decred/TWDecredTests.cpp similarity index 87% rename from tests/Decred/TWDecredTests.cpp rename to tests/chains/Decred/TWDecredTests.cpp index 1b05944929b..647df8ed1e6 100644 --- a/tests/Decred/TWDecredTests.cpp +++ b/tests/chains/Decred/TWDecredTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/Decred/TransactionCompilerTests.cpp b/tests/chains/Decred/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..23d49b69bf1 --- /dev/null +++ b/tests/chains/Decred/TransactionCompilerTests.cpp @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bitcoin/Script.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" +#include "proto/Decred.pb.h" + +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::Decred::tests { + +TEST(DecredCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeDecred; + + const int64_t utxoValue = 39900000; + const int64_t amount = 10000000; + const int64_t fee = 100000; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address("Dsesp1V6DZDEtcq2behmBVKdYqKMdkh96hL"); + input.set_change_address("DsUoWCAxprdGNtKQqambFbTcSBgH1SHn9Gp"); + input.set_coin_type(coin); + + auto& utxo = *input.add_utxo(); + + auto hash = parse_hex("fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd11550"); + auto script = parse_hex("76a914b75fdec70b2e731795dd123ab40f918bf099fee088ac"); + auto utxoKey = parse_hex("ba005cd605d8a02e3d5dfd04234cef3a3ee4f76bfbad2722d1fb5af8e12e6764"); + + utxo.set_amount(utxoValue); + utxo.set_script(script.data(), script.size()); + + auto& outpoint = *utxo.mutable_out_point(); + outpoint.set_hash(hash.data(), hash.size()); + outpoint.set_index(0); + + auto& plan = *input.mutable_plan(); + plan.set_amount(amount); + plan.set_available_amount(utxoValue); + plan.set_fee(fee); + plan.set_change(utxoValue - amount - fee); + auto& planUtxo = *plan.add_utxos(); + planUtxo = input.utxo(0); + + // build preimage + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + ASSERT_EQ(preSigningOutput.hash_public_keys().size(), 1); + + auto preImageHash = data(preSigningOutput.hash_public_keys()[0].data_hash()); + EXPECT_EQ(hex(preImageHash), + "9e4305478d1a69ee5c89a2e234d1cf270798d447d5db983b8fc3c817afddec34"); + + // compile + auto publicKey = PrivateKey(utxoKey).getPublicKey(TWPublicKeyTypeSECP256k1); + auto signature = parse_hex("304402206ee887c9239e5fff0048674bdfff2a8cfbeec6cd4a3ccebcc12fac44b24cc5ac0220718f7c760818fde18bc5ba8457d43d5a145cc4cf13d2a5557cba9107e9f4558d"); + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKey.bytes}); + Decred::Proto::SigningOutput signingOutput; + ASSERT_TRUE(signingOutput.ParseFromArray(outputData.data(), (int)outputData.size())); + ASSERT_EQ(hex(signingOutput.encoded()), + "0100000001fdbfe9dd703f306794a467f175be5bd9748a7925033ea1cf9889d7cf4dd1155000000000000000000002809698000000000000001976a914989b1aecabf1c24e213cc0f2d8a22ffee25dd4e188ac40b6c6010000000000001976a9142a194fc92e27fef9cc2b057bc9060c580cbb484888ac000000000000000001000000000000000000000000ffffffff6a47304402206ee887c9239e5fff0048674bdfff2a8cfbeec6cd4a3ccebcc12fac44b24cc5ac0220718f7c760818fde18bc5ba8457d43d5a145cc4cf13d2a5557cba9107e9f4558d0121026cc34b92cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b6"); + + { + input.add_private_key(utxoKey.data(), utxoKey.size()); + Decred::Proto::SigningOutput output; + ANY_SIGN(input, coin); + ASSERT_EQ(output.encoded(), signingOutput.encoded()); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, {publicKey.bytes}); + Decred::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Decred::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +TEST(DecredCompiler, UtxoWithTree) { + const auto coin = TWCoinTypeDecred; + + const int64_t utxoValue = 10000000; + const int64_t amount = 1000000; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address("Dcur2mcGjmENx4DhNqDctW5wJCVyT3Qeqkx"); + input.set_change_address("DskhnpQqQVgoSuKeyM6Unn2CEbfaenbcJBT"); + input.set_coin_type(coin); + + auto& utxo = *input.add_utxo(); + + auto script = Bitcoin::Script::lockScriptForAddress("DskhnpQqQVgoSuKeyM6Unn2CEbfaenbcJBT", coin); + auto hash = parse_hex("3f7b77a111634faa107c539b0c7db54e2cdbddc0c979568034aaa1ef56d2db90"); + std::reverse(hash.begin(), hash.end()); + utxo.set_amount(utxoValue); + utxo.set_script(script.bytes.data(), script.bytes.size()); + + auto& outpoint = *utxo.mutable_out_point(); + outpoint.set_hash(hash.data(), hash.size()); + outpoint.set_index(0); + outpoint.set_sequence(UINT32_MAX); + outpoint.set_tree(1); + + // build preimage + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + ASSERT_EQ(preSigningOutput.hash_public_keys().size(), 1); + + auto preImageHash = data(preSigningOutput.hash_public_keys()[0].data_hash()); + EXPECT_EQ(hex(preImageHash), + "cca7dcac2ac86f40037a51aeac7b6aaacf57e3304354449e140b698023b3fce7"); +} + +} // namespace TW::Decred::tests \ No newline at end of file diff --git a/tests/chains/Decred/TransactionTests.cpp b/tests/chains/Decred/TransactionTests.cpp new file mode 100644 index 00000000000..30414251e4f --- /dev/null +++ b/tests/chains/Decred/TransactionTests.cpp @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bitcoin/Script.h" +#include "Decred/OutPoint.h" +#include "Decred/Transaction.h" +#include "Decred/TransactionInput.h" +#include "Decred/TransactionOutput.h" +#include "HexCoding.h" +#include "TestUtilities.h" + +#include + +#include + +using namespace TW; +using namespace TW::Decred; + +TEST(DecredTransaction, SignatureHash) { + Decred::Transaction transaction; + + auto po0 = OutPoint( + parse_hex("5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f"), 0, 0); + transaction.inputs.emplace_back(po0, Bitcoin::Script(), 4294967295); + + auto po1 = OutPoint( + parse_hex("bf829c6bcf84579331337659d31f89dfd138f7f7785802d5501c92333145ca7c"), 18, 0); + transaction.inputs.emplace_back(po1, Bitcoin::Script(), 4294967295); + + auto po2 = OutPoint( + parse_hex("22a6f904655d53ae2ff70e701a0bbd90aa3975c0f40bfc6cc996a9049e31cdfc"), 1, 0); + transaction.inputs.emplace_back(po2, Bitcoin::Script(), 4294967295); + + auto oscript0 = + Bitcoin::Script(parse_hex("76a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac")); + transaction.outputs.emplace_back(18000000, 0, oscript0); + + auto oscript1 = + Bitcoin::Script(parse_hex("0x76a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac")); + transaction.outputs.emplace_back(400000000, 0, oscript1); + + auto preOutScript = + Bitcoin::Script(parse_hex("a914f5916158e3e2c4551c1796708db8367207ed13bb87")); + + // throw exception + EXPECT_EXCEPTION(transaction.computeSignatureHash(preOutScript, transaction.outputs.size(), + TWBitcoinSigHashTypeSingle), + "attempt to sign single input at index larger than the number of outputs"); + + // All + auto hash = transaction.computeSignatureHash(preOutScript, 1, TWBitcoinSigHashTypeAll); + EXPECT_EQ(hex(hash), "05b01b517f41112e279b1a9da89d7847a64e5143dba799d7355b1c6c97b4b397"); + + // AnyoneCanPay|Single + hash = transaction.computeSignatureHash( + preOutScript, 1, + TWBitcoinSigHashType(TWBitcoinSigHashTypeAnyoneCanPay | TWBitcoinSigHashTypeSingle)); + EXPECT_EQ(hex(hash), "fa2a276cd2c4d9f56e05ccae6022ca8c201dccffda36b45c39a031711135bc58"); + + // AnyoneCanPay|None + hash = transaction.computeSignatureHash( + preOutScript, 1, + TWBitcoinSigHashType(TWBitcoinSigHashTypeAnyoneCanPay | TWBitcoinSigHashTypeNone)); + EXPECT_EQ(hex(hash), "82338ab38b4d154c72de55c4700909ad97c0f9bb10d8858759d0c90acb220edb"); + + // All & noWitness + Data result; + transaction.serializeType = SerializeType::noWitness; + transaction.encode(result); + EXPECT_EQ(hex(result), + "01000100035897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f0000000000" + "ffffffffbf829c6bcf84579331337659d31f89dfd138f7f7785802d5501c92333145ca7c1200000000ff" + "ffffff22a6f904655d53ae2ff70e701a0bbd90aa3975c0f40bfc6cc996a9049e31cdfc0100000000ffff" + "ffff0280a812010000000000001976a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac0084d7" + "170000000000001976a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac0000000000000000"); + + // All & onlyWitness + result.clear(); + transaction.serializeType = SerializeType::onlyWitness; + transaction.encode(result); + EXPECT_EQ(hex(result), "0100020003000000000000000000000000ffffffff00000000000000000000000000fff" + "fffff00000000000000000000000000ffffffff00"); +} \ No newline at end of file diff --git a/tests/chains/DigiByte/TWCoinTypeTests.cpp b/tests/chains/DigiByte/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..af6949e5c34 --- /dev/null +++ b/tests/chains/DigiByte/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWDigiByteCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeDigiByte)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeDigiByte, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeDigiByte, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeDigiByte)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeDigiByte)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeDigiByte), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeDigiByte)); + ASSERT_EQ(0x3f, TWCoinTypeP2shPrefix(TWCoinTypeDigiByte)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeDigiByte)); + assertStringsEqual(symbol, "DGB"); + assertStringsEqual(txUrl, "https://digiexplorer.info/tx/t123"); + assertStringsEqual(accUrl, "https://digiexplorer.info/address/a12"); + assertStringsEqual(id, "digibyte"); + assertStringsEqual(name, "DigiByte"); +} diff --git a/tests/DigiByte/TWDigiByteTests.cpp b/tests/chains/DigiByte/TWDigiByteTests.cpp similarity index 81% rename from tests/DigiByte/TWDigiByteTests.cpp rename to tests/chains/DigiByte/TWDigiByteTests.cpp index c135a7d662a..fe907773a33 100644 --- a/tests/DigiByte/TWDigiByteTests.cpp +++ b/tests/chains/DigiByte/TWDigiByteTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "Bitcoin/OutPoint.h" #include "Bitcoin/TransactionBuilder.h" @@ -16,8 +14,7 @@ #include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { TEST(DigiByteTransaction, SignTransaction) { /* @@ -33,6 +30,7 @@ TEST(DigiByteTransaction, SignTransaction) { const int64_t fee = 1000; auto input = Bitcoin::Proto::SigningInput(); + input.set_coin_type(TWCoinTypeDigiByte); input.set_hash_type(TWBitcoinSigHashTypeAll); input.set_amount(amount); input.set_byte_fee(1); @@ -61,28 +59,29 @@ TEST(DigiByteTransaction, SignTransaction) { protoPlan = plan.proto(); // Sign - auto result = TransactionSigner::sign(Bitcoin::SigningInput(input)); + auto result = Bitcoin::TransactionSigner::sign(Bitcoin::SigningInput(input)); auto signedTx = result.payload(); ASSERT_TRUE(result); Data serialized; - signedTx.encode(serialized, Transaction::SegwitFormatMode::NonSegwit); + signedTx.encode(serialized, Bitcoin::Transaction::SegwitFormatMode::NonSegwit); ASSERT_EQ( hex(serialized), "01000000" "01" - "ea63bdc39035ebe02df7ad999581156f996303a70f9a3358811454a7ca806b96" - "00000000" - "6a" - "473044022003e9756b12ecbe5788fdb6eb4b6d7b58f9f9410df32f3047edb0dd0ebffb0d630220499d00d17e50c48b4bac6c0ce148f13bb3109a8845fa3400a2d6a57dabf2c4010121024e525e582452cece7b869532d9e354cfec58b71cbed76f7238c91274a64b2116" - "ffffffff" - "02" - "4023050600000000""19" - "76a9142d5b215a11029ee51a1dd9404d271c7e4a74f5f288ac" - "18053d0000000000""19" - "76a91447825943ca6a936b177fdc7c9dc05251640169c288ac" + "ea63bdc39035ebe02df7ad999581156f996303a70f9a3358811454a7ca806b96" "00000000" - ); + "6a" + "473044022003e9756b12ecbe5788fdb6eb4b6d7b58f9f9410df32f3047edb0dd0ebffb0d630220499d00d17e50c48b4bac6c0ce148f13bb3109a8845fa3400a2d6a57dabf2c4010121024e525e582452cece7b869532d9e354cfec58b71cbed76f7238c91274a64b2116" + "ffffffff" + "02" + "4023050600000000" + "19" + "76a9142d5b215a11029ee51a1dd9404d271c7e4a74f5f288ac" + "18053d0000000000" + "19" + "76a91447825943ca6a936b177fdc7c9dc05251640169c288ac" + "00000000"); } TEST(DigiByteTransaction, SignP2WPKH) { @@ -98,6 +97,7 @@ TEST(DigiByteTransaction, SignP2WPKH) { const int64_t amount = 2000000; Proto::SigningInput input; + input.set_coin_type(TWCoinTypeDigiByte); input.set_hash_type(TWBitcoinSigHashTypeAll); input.set_amount(amount); input.set_byte_fee(1); @@ -108,7 +108,7 @@ TEST(DigiByteTransaction, SignP2WPKH) { input.add_private_key(utxoKey0.data(), utxoKey0.size()); auto utxo0 = input.add_utxo(); - auto utxo0Script = Script(parse_hex("00144b62694cfdd7bdac59cbed211288ccd5c0dabd02")); + auto utxo0Script = Bitcoin::Script(parse_hex("00144b62694cfdd7bdac59cbed211288ccd5c0dabd02")); utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); utxo0->set_amount(utxo_amount); auto hash0 = parse_hex("80a16412a880d13b0c88929397a50341018da2e78b70b313062b4a496fea5940"); @@ -116,7 +116,7 @@ TEST(DigiByteTransaction, SignP2WPKH) { utxo0->mutable_out_point()->set_index(1); utxo0->mutable_out_point()->set_sequence(UINT32_MAX); - auto result = TransactionSigner::sign(input); + auto result = Bitcoin::TransactionSigner::sign(input); ASSERT_TRUE(result) << std::to_string(result.error()); auto signedTx = result.payload(); @@ -127,7 +127,7 @@ TEST(DigiByteTransaction, SignP2WPKH) { TEST(DigiByteTransaction, LockScripts) { // https://dgb2.trezor.io/tx/966b80caa754148158339a0fa70363996f15819599adf72de0eb3590c3bd63ea - + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("DBfCffUdSbhqKZhjuvrJ6AgvJofT4E2kp4").get(), TWCoinTypeDigiByte)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a91447825943ca6a936b177fdc7c9dc05251640169c288ac"); @@ -137,8 +137,10 @@ TEST(DigiByteTransaction, LockScripts) { assertHexEqual(scriptData2, "0014885534ab5dc680b68d95c0af49ec2acc2e9915c4"); // https://dgb2.trezor.io/tx/965eb4afcd0aa6e3f4f8fc3513ca042f09e6e2235367fa006cbd1f546c293a2a - + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("SUngTA1vaC2E62mbnc81Mdos3TcvZHwsVo").get(), TWCoinTypeDigiByte)); auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "a91452356ed3d2d31eb8b263ace5d164e3cf3b37096687"); } + +} // namespace TW::Bitcoin diff --git a/tests/chains/Dogecoin/TWCoinTypeTests.cpp b/tests/chains/Dogecoin/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..c08b7a08bdc --- /dev/null +++ b/tests/chains/Dogecoin/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWDogecoinCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeDogecoin)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeDogecoin, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeDogecoin, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeDogecoin)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeDogecoin)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeDogecoin), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeDogecoin)); + ASSERT_EQ(0x16, TWCoinTypeP2shPrefix(TWCoinTypeDogecoin)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeDogecoin)); + assertStringsEqual(symbol, "DOGE"); + assertStringsEqual(txUrl, "https://blockchair.com/dogecoin/transaction/t123"); + assertStringsEqual(accUrl, "https://blockchair.com/dogecoin/address/a12"); + assertStringsEqual(id, "doge"); + assertStringsEqual(name, "Dogecoin"); +} diff --git a/tests/chains/Dogecoin/TWDogeTests.cpp b/tests/chains/Dogecoin/TWDogeTests.cpp new file mode 100644 index 00000000000..9ee431a3414 --- /dev/null +++ b/tests/chains/Dogecoin/TWDogeTests.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include + +#include + +TEST(Doge, LockScripts) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("DLSSSUS3ex7YNDACJDxMER1ZMW579Vy8Zy").get(), TWCoinTypeDogecoin)); + auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); + assertHexEqual(scriptData, "76a914a7d191ec42aa113e28cd858cceaa7c733ba2f77788ac"); + + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("AETZJzedcmLM2rxCM6VqCGF3YEMUjA3jMw").get(), TWCoinTypeDogecoin)); + auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); + assertHexEqual(scriptData2, "a914f191149f72f235548746654f5b473c58258f7fb687"); +} diff --git a/tests/chains/Dydx/TWCoinTypeTests.cpp b/tests/chains/Dydx/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..1dd49ee8eb4 --- /dev/null +++ b/tests/chains/Dydx/TWCoinTypeTests.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +TEST(TWDydxCoinType, TWCoinType) { + const auto coin = TWCoinTypeDydx; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("F236222E4F7C92FA84711FD6451ED22DD56CBDFA319BFDAFB99A21E4E9B9EC2F")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("dydx1adl7usw7z2dnysyn7wvrghu0u0q6gr7jqs4gtt")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "dydx"); + assertStringsEqual(name, "dYdX"); + assertStringsEqual(symbol, "DYDX"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0); + assertStringsEqual(txUrl, "https://www.mintscan.io/dydx/tx/F236222E4F7C92FA84711FD6451ED22DD56CBDFA319BFDAFB99A21E4E9B9EC2F"); + assertStringsEqual(accUrl, "https://www.mintscan.io/dydx/address/dydx1adl7usw7z2dnysyn7wvrghu0u0q6gr7jqs4gtt"); +} diff --git a/tests/chains/ECO/TWCoinTypeTests.cpp b/tests/chains/ECO/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..c982259660f --- /dev/null +++ b/tests/chains/ECO/TWCoinTypeTests.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWHECOCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeECOChain)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x2e62832615f5b68b3bbcd72046a24260ce47052841c1679841b9c574d3959f13")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeECOChain, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xc5a5b3e49e5d06afe163553c942dc59b4e358cf1")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeECOChain, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeECOChain)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeECOChain)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeECOChain), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeECOChain)); + + assertStringsEqual(symbol, "HT"); + assertStringsEqual(txUrl, "https://hecoinfo.com/tx/0x2e62832615f5b68b3bbcd72046a24260ce47052841c1679841b9c574d3959f13"); + assertStringsEqual(accUrl, "https://hecoinfo.com/address/0xc5a5b3e49e5d06afe163553c942dc59b4e358cf1"); + assertStringsEqual(id, "heco"); + assertStringsEqual(name, "Huobi ECO Chain"); +} diff --git a/tests/chains/ECash/TWCoinTypeTests.cpp b/tests/chains/ECash/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..f6487e721d9 --- /dev/null +++ b/tests/chains/ECash/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWECashCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeECash)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("6bc767e69cfacffd954c9e5acd178d3140bf00d094b92c6f6052b517500c553b")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeECash, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("ecash:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdg2jj94l5j")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeECash, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeECash)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeECash)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeECash), 2); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeECash)); + ASSERT_EQ(0x5, TWCoinTypeP2shPrefix(TWCoinTypeECash)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeECash)); + assertStringsEqual(symbol, "XEC"); + assertStringsEqual(txUrl, "https://explorer.bitcoinabc.org/tx/6bc767e69cfacffd954c9e5acd178d3140bf00d094b92c6f6052b517500c553b"); + assertStringsEqual(accUrl, "https://explorer.bitcoinabc.org/address/ecash:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdg2jj94l5j"); + assertStringsEqual(id, "ecash"); + assertStringsEqual(name, "eCash"); +} diff --git a/tests/ECash/TWECashTests.cpp b/tests/chains/ECash/TWECashTests.cpp similarity index 87% rename from tests/ECash/TWECashTests.cpp rename to tests/chains/ECash/TWECashTests.cpp index 97fc2924a36..bd9f5738b3b 100644 --- a/tests/ECash/TWECashTests.cpp +++ b/tests/chains/ECash/TWECashTests.cpp @@ -1,4 +1,4 @@ -// Copyright © 2021 Trust Wallet. +// Copyright © 2022 Trust Wallet. // // This file is part of Trust. The full Trust copyright notice, including // terms governing use, modification, and redistribution, is contained in the @@ -8,7 +8,7 @@ #include "Bitcoin/SigHashType.h" #include "HexCoding.h" #include "proto/Bitcoin.pb.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include @@ -22,8 +22,7 @@ #include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { TEST(ECash, Address) { EXPECT_TRUE(TWAnyAddressIsValid(STRING("pqx578nanz2h2estzmkr53zqdg6qt8xyqvh683mrz0").get(), TWCoinTypeECash)); @@ -93,9 +92,8 @@ TEST(ECash, LockScript) { TEST(ECash, ExtendedKeys) { // Same test as BCH, but with the 899 derivation path auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( - STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), - STRING("TREZOR").get() - )); + STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), + STRING("TREZOR").get())); auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeECash, TWHDVersionXPRV)); auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeECash, TWHDVersionXPUB)); @@ -126,6 +124,7 @@ TEST(ECash, SignTransaction) { // https://blockchair.com/ecash/transaction/96ee20002b34e468f9d3c5ee54f6a8ddaa61c118889c4f35395c2cd93ba5bbb4 auto input = Proto::SigningInput(); + input.set_coin_type(TWCoinTypeECash); input.set_hash_type(hashTypeForCoin(TWCoinTypeECash)); input.set_amount(amount); input.set_byte_fee(1); @@ -151,13 +150,20 @@ TEST(ECash, SignTransaction) { EXPECT_EQ(output.transaction().outputs_size(), 2); EXPECT_EQ(output.transaction().outputs(0).value(), amount); EXPECT_EQ(output.transaction().outputs(1).value(), 4325); - EXPECT_EQ(output.encoded().length(), 226); + EXPECT_EQ(output.encoded().length(), 226ul); ASSERT_EQ(hex(output.encoded()), - "01000000" - "01" - "e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05" "02000000" "6b483045022100b70d158b43cbcded60e6977e93f9a84966bc0cec6f2dfd1463d1223a90563f0d02207548d081069de570a494d0967ba388ff02641d91cadb060587ead95a98d4e3534121038eab72ec78e639d02758e7860cdec018b49498c307791f785aa3019622f4ea5b" "ffffffff" - "02" - "5802000000000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" - "e510000000000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" - "00000000"); + "01000000" + "01" + "e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05" + "02000000" + "6b483045022100b70d158b43cbcded60e6977e93f9a84966bc0cec6f2dfd1463d1223a90563f0d02207548d081069de570a494d0967ba388ff02641d91cadb060587ead95a98d4e3534121038eab72ec78e639d02758e7860cdec018b49498c307791f785aa3019622f4ea5b" + "ffffffff" + "02" + "5802000000000000" + "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "e510000000000000" + "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + "00000000"); } + +} // namespace TW::Bitcoin diff --git a/tests/chains/EOS/AddressTests.cpp b/tests/chains/EOS/AddressTests.cpp new file mode 100644 index 00000000000..28aed7426a4 --- /dev/null +++ b/tests/chains/EOS/AddressTests.cpp @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "EOS/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include + +#include + +using namespace TW; + +namespace TW::EOS::tests { + +TEST(EOSAddress, Invalid) { + ASSERT_FALSE(Address::isValid("abc")); + ASSERT_FALSE(Address::isValid("65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjTF")); + ASSERT_FALSE(Address::isValid("EOS65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjT")); + ASSERT_FALSE(Address::isValid("PUB_5hieQEFWh68h6bjaYAY25Ptd2bmqLCaFsunaneh9gZsmSgUBUe")); + ASSERT_FALSE(Address::isValid("PUB_K1_5hieQEFWh68h6bjaYAY25Ptd2bmqLCaFsunaneh9gZsmSgUBUe")); + + ASSERT_THROW(Address("PUB_K1_65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjTF"), std::invalid_argument); + ASSERT_THROW(EOS::Address(Data(0)), std::invalid_argument); +} + +TEST(EOSAddress, Base58) { + ASSERT_EQ( + Address("EOS65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjTF").string(), + "EOS65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjTF"); + ASSERT_EQ( + Address("EOS55hdeEZHoArE8LLTv6drj2yR1K1AH8wAPT4kjTVSnkmQc3nzwQ").string(), + "EOS55hdeEZHoArE8LLTv6drj2yR1K1AH8wAPT4kjTVSnkmQc3nzwQ"); + ASSERT_EQ( + Address("PUB_R1_5hieQEFWh68h6bjaYAY25Ptd2bmqLCaFsunaneh9gZsmSgUBUe").string(), + "PUB_R1_5hieQEFWh68h6bjaYAY25Ptd2bmqLCaFsunaneh9gZsmSgUBUe"); + ASSERT_EQ( + Address("PUB_R1_7M9ckjr6p5CmS3N3yLPg9vcTB5NHmLcMHwZ3iGccEVfbjJRHv3").string(), + "PUB_R1_7M9ckjr6p5CmS3N3yLPg9vcTB5NHmLcMHwZ3iGccEVfbjJRHv3"); +} + +TEST(EOSAddress, FromPrivateKey) { + std::string privArray[]{"8e14ef506fee5e0aaa32f03a45242d32d0eb993ffe25ce77542ef07219db667c", + "e2bfd815c5923f404388a3257aa5527f0f52e92ce364e1e26a04d270c901edda", + "e6b783120a21cb234d8e15077ce186c47261d1043781ab8b16b84f2acd377668", + "bb96c0a4a6ec9c93ccc0b2cbad6b0e8110b9ca4731aef9c6937b99552a319b03"}; + + Type privTypes[]{Type::Legacy, Type::Legacy, Type::ModernR1, Type::ModernK1}; + + std::string pubArray[]{"EOS6TFKUKVvtvjRq9T4fV9pdxNUuJke92nyb4rzSFtZfdR5ssmVuY", + "EOS5YtaCcbPJ3BknNBTDezE9eJoGNnAVuUwT8bnxhSRS5dqRvyfxr", + "PUB_R1_67itCyDj42CRgtpyP4fLbAccBYnVHGeZQujQAeK3fyNbvfvZM6", + "PUB_K1_6enPVMggisfqVVRZ1tj47d9UeHK46CBssoCmAz6sLDMBdtZk78"}; + + for (int i = 0; i < 4; i++) { + const auto privateKey = PrivateKey(parse_hex(privArray[i])); + const auto publicKey = PublicKey(privateKey.getPublicKey(privTypes[i] == Type::ModernR1 ? TWPublicKeyTypeNIST256p1 : TWPublicKeyTypeSECP256k1)); + const auto address = Address(publicKey, privTypes[i]); + + ASSERT_EQ(address.string(), pubArray[i]); + } +} + +TEST(EOSAddress, IsValid) { + ASSERT_TRUE(Address::isValid("EOS6Vm7RWMS1KKAM9kDXgggpu4sJkFMEpARhmsWA84tk4P22m29AV")); + ASSERT_TRUE(Address::isValid("PUB_R1_6pQRUVU5vdneRnmjSiZPsvu3zBqcptvg6iK2Vz4vKo4ugnzow3")); + ASSERT_TRUE(Address::isValid("EOS5mGcPvsqFDe8YRrA3yMMjQgjrCa6yiCho79KViDhvxh4ajQjgS")); + ASSERT_TRUE(Address::isValid("PUB_R1_82dMu3zSSfyHYc4cvWJ6SPsHZWB5mBNAyhL53xiM5xpqmfqetN")); + ASSERT_TRUE(Address::isValid("PUB_K1_6enPVMggisfqVVRZ1tj47d9UeHK46CBssoCmAz6sLDMBdtZk78")); + + ASSERT_NO_THROW(Address(parse_hex("039d91164ea04f4e751762643ef4ae520690af361b8e677cf341fd213419956b356cb721b7"), Type::ModernR1)); + ASSERT_NO_THROW(Address(parse_hex("02d3c8e736a9a50889766caf3c37bd16e2fecc7340b3130e25d4c01b153f996a10a78afc0e"), Type::Legacy)); +} + +} // namespace TW::EOS::tests diff --git a/tests/chains/EOS/AssetTests.cpp b/tests/chains/EOS/AssetTests.cpp new file mode 100644 index 00000000000..23ae9852a69 --- /dev/null +++ b/tests/chains/EOS/AssetTests.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "EOS/Asset.h" +#include "HexCoding.h" + +#include + +namespace TW::EOS::tests { + +TEST(EOSAsset, Serialization) { + Data buf; + Asset(5000, 3, "BRAVO").serialize(buf); + ASSERT_EQ(hex(buf), "881300000000000003425241564f0000"); + + buf.clear(); + Asset(90000, 3, "BRAVO").serialize(buf); + ASSERT_EQ(hex(buf), "905f01000000000003425241564f0000"); + + buf.clear(); + Asset(1000, 3, "BRAVO").serialize(buf); + ASSERT_EQ(hex(buf), "e80300000000000003425241564f0000"); + + std::string assetStr = "3.141 PI"; + ASSERT_EQ(Asset::fromString(assetStr).string(), assetStr); + + // add tests for negative amounts, fractional amounts +} + +} // namespace TW::EOS diff --git a/tests/chains/EOS/NameTests.cpp b/tests/chains/EOS/NameTests.cpp new file mode 100644 index 00000000000..03f80ce53af --- /dev/null +++ b/tests/chains/EOS/NameTests.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "EOS/Name.h" +#include "HexCoding.h" + +#include + +namespace TW::EOS::tests { + +TEST(EOSName, Invalid) { + ASSERT_THROW(Name(std::string(14, 'a')), std::invalid_argument); + + std::string invalidNames[] = {"Alice", "alice16", "12345satoshis"}; + for (auto name : invalidNames) { + ASSERT_FALSE(Name(name).string() == name); + } +} + +TEST(EOSName, Valid) { + ASSERT_NO_THROW(Name(std::string(13, 'a'))); + + std::string validName = "satoshis12345"; + ASSERT_EQ(Name(validName).string(), validName); + Data buf; + Name(validName).serialize(buf); + ASSERT_EQ(hex(buf), "458608d8354cb3c1"); +} + +} // namespace TW::EOS::tests \ No newline at end of file diff --git a/tests/chains/EOS/SignatureTests.cpp b/tests/chains/EOS/SignatureTests.cpp new file mode 100644 index 00000000000..0650c6432fb --- /dev/null +++ b/tests/chains/EOS/SignatureTests.cpp @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "EOS/Transaction.h" +#include "HexCoding.h" + +#include + +namespace TW::EOS::tests { + +TEST(EOSSignature, Serialization) { + Data buf; + Signature* sig = new Signature(parse_hex("1f14262320d5b145220fb94d8fe204117edd25e52bbe9557b6e0909dd00307af266f5be1deef001446979523ac9de32c7eae5e5be4180b5a60c0e6bf14b2dd3e05"), Type::ModernK1); + sig->serialize(buf); + + ASSERT_EQ( + hex(buf), + "001f14262320d5b145220fb94d8fe204117edd25e52bbe9557b6e0909dd00307af266f5be1deef001446979523ac9de32c7eae5e5be4180b5a60c0e6bf14b2dd3e05"); + + ASSERT_EQ( + sig->string(), + "SIG_K1_JwtfgsdSx5RuF5aejedQ7FJTexaKMrQyYosPUWUrU1mzdLx6JUgLTZJd7zWA8q8VdnXht3YmVt7jafmD2eEK7hTRpT9rY5"); + + delete sig; + sig = new Signature(parse_hex("1f5c419d16f573ddbf07d2eb959621f690f9cb856ea2d113e3af02b3b40005488410e82ffa37a079e119844d213f4eb066a640507db68851752bea6e61eb864d84"), Type::ModernR1); + buf.clear(); + sig->serialize(buf); + + ASSERT_EQ( + hex(buf), + "011f5c419d16f573ddbf07d2eb959621f690f9cb856ea2d113e3af02b3b40005488410e82ffa37a079e119844d213f4eb066a640507db68851752bea6e61eb864d84"); + + ASSERT_EQ( + sig->string(), + "SIG_R1_K7KpdLYqa6ebCP22TuiYAY9YoJh1dTWTZEVkdPzdoadFL6f8PkMYk5N8wtsF11cneEJ91XnEZP6wDJHhRyqr1fr68ouYcz"); + + delete sig; + ASSERT_THROW(sig = new Signature(parse_hex("1f5c419d16f573ddbf07d2eb959621f690f9cb856ea2d113e3af02b3b40005488410e82ffa37a079e119844d213f4eb066a640507db68851752bea6e61eb864d84"), Type::Legacy), std::invalid_argument); + ASSERT_THROW(sig = new Signature(parse_hex("011f5c419d16f573ddbf07d2eb959621f690f9cb856ea2d113e3af02b3b40005488410e82ffa37a079e119844d213f4eb066a640507db68851752bea6e61eb864d84"), Type::ModernR1), std::invalid_argument); +} + +} // namespace TW::EOS::tests \ No newline at end of file diff --git a/tests/chains/EOS/TWAnySignerTests.cpp b/tests/chains/EOS/TWAnySignerTests.cpp new file mode 100644 index 00000000000..575e71566c8 --- /dev/null +++ b/tests/chains/EOS/TWAnySignerTests.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include "HexCoding.h" +#include "proto/EOS.pb.h" + +#include + +namespace TW::EOS::tests { + +TEST(TWAnySignerEOS, Sign) { + Proto::SigningInput input; + auto chainId = parse_hex("cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f"); + auto refBlock = parse_hex("000067d6f6a7e7799a1f3d487439a679f8cf95f1c986f35c0d2fa320f51a7144"); + auto key = parse_hex("559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd"); + + auto& asset = *input.mutable_asset(); + asset.set_amount(300000); + asset.set_decimals(4); + asset.set_symbol("TKN"); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_reference_block_id(refBlock.data(), refBlock.size()); + input.set_reference_block_time(1554209118); + input.set_currency("token"); + input.set_sender("token"); + input.set_recipient("eosio"); + input.set_memo("my second transfer"); + input.set_private_key(key.data(), key.size()); + input.set_private_key_type(Proto::KeyType::MODERNK1); + + { + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEOS); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(output.json_encoded(), R"({"compression":"none","packed_context_free_data":"","packed_trx":"6e67a35cd6679a1f3d4800000000010000000080a920cd000000572d3ccdcd010000000080a920cd00000000a8ed3232330000000080a920cd0000000000ea3055e09304000000000004544b4e00000000126d79207365636f6e64207472616e7366657200","signatures":["SIG_K1_K9RdLC7DEDWjTfR64GU8BtDHcAjzR1ntcT651JMcfHNTpdsvDrUwfyzF1FkvL9fxEi2UCtGJZ9zYoNbJoMF1fbU64cRiJ7"]})"); + } + + input.set_private_key_type(Proto::KeyType::LEGACY); + { + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEOS); + EXPECT_EQ(output.error(), Common::Proto::Error_internal); + EXPECT_TRUE(output.json_encoded().empty()); + } +} + +} // namespace TW::EOS::tests diff --git a/tests/chains/EOS/TWCoinTypeTests.cpp b/tests/chains/EOS/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..9996fd7467f --- /dev/null +++ b/tests/chains/EOS/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWEOSCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeEOS)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeEOS, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeEOS, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeEOS)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeEOS)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeEOS), 4); + ASSERT_EQ(TWBlockchainEOS, TWCoinTypeBlockchain(TWCoinTypeEOS)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeEOS)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeEOS)); + assertStringsEqual(symbol, "EOS"); + assertStringsEqual(txUrl, "https://bloks.io/transaction/t123"); + assertStringsEqual(accUrl, "https://bloks.io/account/a12"); + assertStringsEqual(id, "eos"); + assertStringsEqual(name, "EOS"); +} diff --git a/tests/chains/EOS/TransactionCompilerTests.cpp b/tests/chains/EOS/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..bb4bb23f72b --- /dev/null +++ b/tests/chains/EOS/TransactionCompilerTests.cpp @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "EOS/Signer.h" + +#include "proto/EOS.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(EOSCompiler, CompileWithSignatures) { + /// Step 1: Prepare transaction input (protobuf) + const auto coin = TWCoinTypeEOS; + EOS::Proto::SigningInput input; + auto chainId = parse_hex("cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f"); + auto refBlock = parse_hex("000067d6f6a7e7799a1f3d487439a679f8cf95f1c986f35c0d2fa320f51a7144"); + auto key = parse_hex("559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd"); + + auto& asset = *input.mutable_asset(); + asset.set_amount(300000); + asset.set_decimals(4); + asset.set_symbol("TKN"); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_reference_block_id(refBlock.data(), refBlock.size()); + input.set_reference_block_time(1554209118); + input.set_currency("token"); + input.set_sender("token"); + input.set_recipient("eosio"); + input.set_memo("my second transfer"); + input.set_private_key(key.data(), key.size()); + input.set_private_key_type(EOS::Proto::KeyType::MODERNK1); + + auto txInputData = data(input.SerializeAsString()); + { + EOS::Proto::SigningOutput output; + ANY_SIGN(input, coin); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(output.json_encoded(), R"({"compression":"none","packed_context_free_data":"","packed_trx":"6e67a35cd6679a1f3d4800000000010000000080a920cd000000572d3ccdcd010000000080a920cd00000000a8ed3232330000000080a920cd0000000000ea3055e09304000000000004544b4e00000000126d79207365636f6e64207472616e7366657200","signatures":["SIG_K1_K9RdLC7DEDWjTfR64GU8BtDHcAjzR1ntcT651JMcfHNTpdsvDrUwfyzF1FkvL9fxEi2UCtGJZ9zYoNbJoMF1fbU64cRiJ7"]})"); + } + + /// Step 2: Obtain preimage hash + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = data(preSigningOutput.data_hash()); + EXPECT_EQ(hex(preImageHash), "14fc3299ee3e1113096bf1869dfa14c04a7ffdedd8ebdabf530683e4cfcd726c"); + + // Simulate signature, normally obtained from signature server + const PublicKey publicKey = PrivateKey(key).getPublicKey(TWPublicKeyTypeNIST256p1); + const auto signature = parse_hex("1f6c4efceb5a6dadab271fd7e2153d97d22690938475b23f017cf9ec29e20d25725e90e541e130daa83c38fc4c933725f05837422c3f4a51f8c1d07208c8fd5e0b"); // data("SIG_K1_K9RdLC7DEDWjTfR64GU8BtDHcAjzR1ntcT651JMcfHNTpdsvDrUwfyzF1FkvL9fxEi2UCtGJZ9zYoNbJoMF1fbU64cRiJ7"); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKey.bytes}); + + const auto ExpectedTx = R"({"compression":"none","packed_context_free_data":"","packed_trx":"6e67a35cd6679a1f3d4800000000010000000080a920cd000000572d3ccdcd010000000080a920cd00000000a8ed3232330000000080a920cd0000000000ea3055e09304000000000004544b4e00000000126d79207365636f6e64207472616e7366657200","signatures":["SIG_K1_K9RdLC7DEDWjTfR64GU8BtDHcAjzR1ntcT651JMcfHNTpdsvDrUwfyzF1FkvL9fxEi2UCtGJZ9zYoNbJoMF1fbU64cRiJ7"]})"; + { + EOS::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.json_encoded(), ExpectedTx); + } + + input.set_private_key_type(EOS::Proto::KeyType::LEGACY); + { + EOS::Proto::SigningOutput output; + ANY_SIGN(input, coin); + EXPECT_EQ(output.error(), Common::Proto::Error_internal); + EXPECT_TRUE(output.json_encoded().empty()); + } +} diff --git a/tests/chains/EOS/TransactionTests.cpp b/tests/chains/EOS/TransactionTests.cpp new file mode 100644 index 00000000000..013db25ed5a --- /dev/null +++ b/tests/chains/EOS/TransactionTests.cpp @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "EOS/Action.h" +#include "EOS/Address.h" +#include "EOS/Signer.h" +#include "EOS/Transaction.h" +#include "Hash.h" +#include "HexCoding.h" +#include "PrivateKey.h" + +#include + +using namespace TW; +namespace TW::EOS::tests { + +static std::string k1Sigs[5]{ + "SIG_K1_K9RdLC7DEDWjTfR64GU8BtDHcAjzR1ntcT651JMcfHNTpdsvDrUwfyzF1FkvL9fxEi2UCtGJZ9zYoNbJoMF1fbU64cRiJ7", + "SIG_K1_K4oXhfa8xJnAB26EeTz8FLfZtY4kw4kjqUqQLz5snryP7USRJ7yGyuBBfYzTBtZ8djBo87pAW53xHsUxjvQvaKHGQRhKd5", + "SIG_K1_K2BumXj5Qtk2CtaYe6EvMSZ2JXLCFeNsTQirx4cnZFURBFpkchuS4sNFxGLH5Qrdv4R7cN4rax6ZF9HBMz4f4d6GFoTNN2", + "SIG_K1_K8LNseWYiePTM646LZduWevssozJ9t3gaNe2ipZfXbbSFsx36dJFXnk5UBasT2G3cX1Niu7LSUVFspkDYSSPxMFGbWcAvk", + "SIG_K1_Jx2JFftzdx28PZXkmoeWk2afm3KHYsn5knYxynA3GrGevMEJVd1GhcuS5h3f2wdUS2ZUojqycyyVizyJFVSajeR2LZGnJr"}; + +static std::string r1Sigs[5]{ + "SIG_R1_KC4qBxibZpXLESzN37F46Jv8w7dQtyPDeRmeFs8Z4DmEFhr3FACLkjcbSLkVN7acBt4eb6zUa9N76UfJncr4htxCSe7huZ", + "SIG_R1_KaWNQefJReykjKUsS51caChJRgeywUoeuucFReyKY1SPNveSoFFVgxT3jEzW56ZLtpN7qGgekoSNsKs1BzzyZ9snhCALcG", + "SIG_R1_KarN7JJxHeKgRJLmscWzCsdDpfdktWrBGJLvVFN7RYZpSeNHBsqNV7dKqxkvKtbhsqHukLKw1EQNTjcUcxUD6hUTVvNWcP", + "SIG_R1_KvHdcwEDW8RQWEPTA3BoK9RVZqtAwKqVvYQN9Wz44XUrzjrNyRkpc7vguqc8q6FLMJBUUen59hyXM3BuLvvrp2x4S6m1o8", + "SIG_R1_KZB6ivprUS1zwGxMxZQJ7UvWk4Tpvoo6WiFKUPJuUHj4Es39ejiFVoD7ZB6MfSJxAaRPvnAF39hnApwzFAM8Erxmj3Suvm"}; + +TEST(EOSTransaction, Serialization) { + ASSERT_THROW(TransferAction("token", "eosio", "token", Asset::fromString("-20.1234 TKN"), "my first transfer"), std::invalid_argument); + + Data referenceBlockId = parse_hex("000046dc08ad384ca452d92c59348aba888fcbb6ef1ebffc3181617706664d4c"); + int32_t referenceBlockTime = 1554121728; + auto chainId = parse_hex("cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f"); + + Transaction tx{referenceBlockId, referenceBlockTime}; + tx.actions.push_back(TransferAction("token", "eosio", "token", Asset::fromString("20.1234 TKN"), "my first transfer")); + ASSERT_EQ(tx.actions.back().serialize().dump(), "{\"account\":\"token\",\"authorizations\":[{\"actor\":\"eosio\",\"permission\":\"active\"}],\"data\":\"0000000000ea30550000000080a920cd121203000000000004544b4e00000000116d79206669727374207472616e73666572\",\"name\":\"transfer\"}"); + + Data buf; + tx.serialize(buf); + + Signer signer{chainId}; + + ASSERT_EQ( + hex(buf), + "1012a25cdc46a452d92c00000000010000000080a920cd000000572d3ccdcd010000000000ea305500000000a8ed3232320000000000ea30550000000080a920cd121203000000000004544b4e00000000116d79206669727374207472616e7366657200"); + + ASSERT_EQ( + hex(signer.hash(tx)), + "acbf7e6beb6fd4e224462e87c1d70bca6a15b76f8d9e0b31782c5cdfd493b050"); + + // make transaction invalid and see if signing succeeds + tx.maxNetUsageWords = UINT32_MAX; + ASSERT_THROW(signer.sign(PrivateKey(Hash::sha256(std::string("A"))), Type::ModernK1, tx), std::invalid_argument); + + referenceBlockId = parse_hex("000067d6f6a7e7799a1f3d487439a679f8cf95f1c986f35c0d2fa320f51a7144"); + referenceBlockTime = 1554209118; + + Transaction tx2{referenceBlockId, referenceBlockTime}; + tx2.actions.push_back(TransferAction("token", "token", "eosio", Asset::fromString("30.0000 TKN"), "my second transfer")); + + buf.clear(); + tx2.serialize(buf); + ASSERT_EQ( + hex(buf), + "6e67a35cd6679a1f3d4800000000010000000080a920cd000000572d3ccdcd010000000080a920cd00000000a8ed3232330000000080a920cd0000000000ea3055e09304000000000004544b4e00000000126d79207365636f6e64207472616e7366657200"); + + ASSERT_EQ( + hex(signer.hash(tx2)), + "14fc3299ee3e1113096bf1869dfa14c04a7ffdedd8ebdabf530683e4cfcd726c"); + + ASSERT_NO_THROW(tx2.serialize()); + + // verify k1 sigs + for (int i = 0; i < 5; i++) { + PrivateKey pk(Hash::sha256(std::string(i + 1, 'A'))); + ASSERT_NO_THROW(signer.sign(pk, Type::ModernK1, tx2)); + + ASSERT_EQ( + tx2.signatures.back().string(), + k1Sigs[i]); + } + + // verify r1 sigs + for (int i = 0; i < 5; i++) { + PrivateKey pk(Hash::sha256(std::string(i + 1, 'A'))); + ASSERT_NO_THROW(signer.sign(pk, Type::ModernR1, tx2)); + + ASSERT_EQ( + tx2.signatures.back().string(), + r1Sigs[i]); + } + + referenceBlockId = parse_hex(""); + referenceBlockTime = 0; + EXPECT_ANY_THROW(new Transaction(referenceBlockId, referenceBlockTime)); + + referenceBlockId = parse_hex("000067d6f6a7e7799a1f3d487439a679f8cf95f1c986f35c0d2fa320f51a7144"); + referenceBlockTime = 1554209118; + Transaction tx3{referenceBlockId, referenceBlockTime}; + Data extBuf; + auto ext = Extension(uint16_t(0), extBuf); + tx3.transactionExtensions.push_back(ext); + buf.clear(); + tx3.serialize(buf); + ASSERT_EQ(hex(buf), "6e67a35cd6679a1f3d48000000000001000000"); +} + +TEST(EOSTransaction, formatDate) { + EXPECT_EQ(Transaction::formatDate(1554209148), "2019-04-02T12:45:48"); + EXPECT_EQ(Transaction::formatDate(1654160000), "2022-06-02T08:53:20"); + EXPECT_EQ(Transaction::formatDate(0), "1970-01-01T00:00:00"); + EXPECT_EQ(Transaction::formatDate(std::numeric_limits::max()), "2038-01-19T03:14:07"); +} + +TEST(EOSTransaction, Create) { + auto emptyData = Data{}; + EXPECT_ANY_THROW(new Transaction(emptyData, 0)); +} + +} // namespace TW::EOS::tests diff --git a/tests/chains/Ethereum/AddressTests.cpp b/tests/chains/Ethereum/AddressTests.cpp new file mode 100644 index 00000000000..b2b320cf82e --- /dev/null +++ b/tests/chains/Ethereum/AddressTests.cpp @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Ethereum/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" + +#include + +using namespace TW; + +namespace TW::Ethereum::tests { + +TEST(EthereumAddress, Invalid) { + ASSERT_FALSE(Address::isValid("abc")); + ASSERT_FALSE(Address::isValid("aaeb60f3e94c9b9a09f33669435e7ef1beaed")); + ASSERT_FALSE(Address::isValid("fB6916095ca1df60bB79Ce92cE3Ea74c37c5d359")); +} + +TEST(EthereumAddress, EIP55) { + ASSERT_EQ( + Address(parse_hex("5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")).string(), + "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"); + ASSERT_EQ( + Address(parse_hex("0x5AAEB6053F3E94C9b9A09f33669435E7Ef1BEAED")).string(), + "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"); + ASSERT_EQ( + Address(parse_hex("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359")).string(), + "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"); + ASSERT_EQ( + Address(parse_hex("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB")).string(), + "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB"); + ASSERT_EQ( + Address(parse_hex("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb")).string(), + "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"); +} + +TEST(EthereumAddress, String) { + const auto address = Address("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"); + ASSERT_EQ(address.string(), "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"); +} + +TEST(EthereumAddress, FromPrivateKey) { + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); + const auto address = Address(publicKey); + + ASSERT_EQ(address.string(), "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309"); + + const auto publicKey2 = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeED25519)); + EXPECT_ANY_THROW(new Address(publicKey2)); +} + +TEST(EthereumAddress, IsValid) { + ASSERT_FALSE(Address::isValid("abc")); + ASSERT_TRUE(Address::isValid("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")); +} + +TEST(EthereumAddress, FromData) { + EXPECT_ANY_THROW(new Address(Data{})); +} + +} // namespace TW::Ethereum::tests diff --git a/tests/chains/Ethereum/BarzTests.cpp b/tests/chains/Ethereum/BarzTests.cpp new file mode 100644 index 00000000000..479726d7393 --- /dev/null +++ b/tests/chains/Ethereum/BarzTests.cpp @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include +#include +#include +#include +#include "proto/Ethereum.pb.h" +#include "HexCoding.h" +#include +#include +#include + +namespace TW::Barz::tests { + +// https://testnet.bscscan.com/tx/0x6c6e1fe81c722c0abce1856b9b4e078ab2cad06d51f2d1b04945e5ba2286d1b4 +TEST(Barz, GetInitCode) { + const PublicKey& publicKey = PublicKey(parse_hex("0x04e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b02"), TWPublicKeyTypeNIST256p1Extended); + + // C++ + { + const std::string& factoryAddress = "0x3fC708630d85A3B5ec217E53100eC2b735d4f800"; + const std::string& verificationFacetAddress = "0x6BF22ff186CC97D88ECfbA47d1473a234CEBEFDf"; + const auto salt = 0; + + const auto& initCode = Barz::getInitCode(factoryAddress, publicKey, verificationFacetAddress, salt); + ASSERT_EQ(hexEncoded(initCode), "0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000"); + } + + { + const std::string& factoryAddress = "0x3fC708630d85A3B5ec217E53100eC2b735d4f800"; + const std::string& verificationFacetAddress = "0x6BF22ff186CC97D88ECfbA47d1473a234CEBEFDf"; + const auto salt = 1; + + const auto& initCode = Barz::getInitCode(factoryAddress, publicKey, verificationFacetAddress, salt); + ASSERT_EQ(hexEncoded(initCode), "0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000"); + } + + // C + { + const auto factoryAddress = STRING("0x3fC708630d85A3B5ec217E53100eC2b735d4f800"); + const auto verificationFacetAddress = STRING("0x6BF22ff186CC97D88ECfbA47d1473a234CEBEFDf"); + const auto salt = 0; + + const auto& initCodeData = TWBarzGetInitCode(factoryAddress.get(), WRAP(TWPublicKey, new TWPublicKey{ TW::PublicKey(publicKey) }).get(), verificationFacetAddress.get(), salt); + const auto& initCode = hexEncoded(*reinterpret_cast(WRAPD(initCodeData).get())); + EXPECT_EQ(initCode, "0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000"); + } + + { + const auto factoryAddress = STRING("0x3fC708630d85A3B5ec217E53100eC2b735d4f800"); + const auto verificationFacetAddress = STRING("0x6BF22ff186CC97D88ECfbA47d1473a234CEBEFDf"); + const auto salt = 1; + + const auto& initCodeData = TWBarzGetInitCode(factoryAddress.get(), WRAP(TWPublicKey, new TWPublicKey{ TW::PublicKey(publicKey) }).get(), verificationFacetAddress.get(), salt); + const auto& initCode = hexEncoded(*reinterpret_cast(WRAPD(initCodeData).get())); + EXPECT_EQ(initCode, "0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000006bf22ff186cc97d88ecfba47d1473a234cebefdf00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004104e6f4e0351e2f556fd7284a9a033832bae046ac31fd529ad02ab6220870624b79eb760e718fdaed7a037dd1d77a561759cee9f2706eb55a729dc953e0d5719b0200000000000000000000000000000000000000000000000000000000000000"); + } +} + +TEST(Barz, GetCounterfactualAddress) { + TW::Barz::Proto::ContractAddressInput input; + input.set_factory("0x2c97f4a366Dd5D91178ec9E36c5C1fcA393A538C"); + input.set_account_facet("0x3322C04EAe11B9b14c6c289f2668b6f07071b591"); + input.set_verification_facet("0x90A6fE0A938B0d4188e9013C99A0d7D9ca6bFB63"); + input.set_entry_point("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"); + input.set_facet_registry("0xFd1A8170c12747060324D9079a386BD4290e6f93"); + input.set_default_fallback("0x22eB0720d9Fc4bC90BB812B309e939880B71c20d"); + input.set_bytecode("0x608060405260405162003cc638038062003cc68339818101604052810190620000299190620019ad565b60008673ffffffffffffffffffffffffffffffffffffffff16633253960f6040518163ffffffff1660e01b81526004016020604051808303816000875af115801562000079573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200009f919062001aec565b9050600060e01b817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191603620000fe576040517f5a5b4d3900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600267ffffffffffffffff8111156200011e576200011d6200196b565b5b6040519080825280602002602001820160405280156200015b57816020015b62000147620018ff565b8152602001906001900390816200013d5790505b5090506000600167ffffffffffffffff8111156200017e576200017d6200196b565b5b604051908082528060200260200182016040528015620001ad5781602001602082028036833780820191505090505b5090506000600167ffffffffffffffff811115620001d057620001cf6200196b565b5b604051908082528060200260200182016040528015620001ff5781602001602082028036833780820191505090505b509050631f931c1c60e01b8260008151811062000221576200022062001b21565b5b60200260200101907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152505060405180606001604052808d73ffffffffffffffffffffffffffffffffffffffff16815260200160006002811115620002ab57620002aa62001b37565b5b81526020018381525083600081518110620002cb57620002ca62001b21565b5b60200260200101819052508381600081518110620002ee57620002ed62001b21565b5b60200260200101907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152505060405180606001604052808b73ffffffffffffffffffffffffffffffffffffffff1681526020016000600281111562000378576200037762001b37565b5b8152602001828152508360018151811062000398576200039762001b21565b5b6020026020010181905250620003b9846200047e60201b620001671760201c565b6200046c838c8b8e8e8d8d8d8d604051602401620003de979695949392919062001b8c565b6040516020818303038152906040527f95a21aec000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050620004b060201b620001911760201c565b5050505050505050505050506200211f565b806200048f6200073460201b60201c565b60010160016101000a81548163ffffffff021916908360e01c021790555050565b60005b8351811015620006df576000848281518110620004d557620004d462001b21565b5b602002602001015160200151905060006002811115620004fa57620004f962001b37565b5b81600281111562000510576200050f62001b37565b5b0362000570576200056a85838151811062000530576200052f62001b21565b5b60200260200101516000015186848151811062000552576200055162001b21565b5b6020026020010151604001516200073960201b60201c565b620006c8565b6001600281111562000587576200058662001b37565b5b8160028111156200059d576200059c62001b37565b5b03620005fd57620005f7858381518110620005bd57620005bc62001b21565b5b602002602001015160000151868481518110620005df57620005de62001b21565b5b602002602001015160400151620009db60201b60201c565b620006c7565b60028081111562000613576200061262001b37565b5b81600281111562000629576200062862001b37565b5b0362000689576200068385838151811062000649576200064862001b21565b5b6020026020010151600001518684815181106200066b576200066a62001b21565b5b60200260200101516040015162000c8f60201b60201c565b620006c6565b6040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620006bd9062001be7565b60405180910390fd5b5b5b508080620006d69062001c61565b915050620004b3565b507f8faa70878671ccd212d20771b795c50af8fd3ff6cf27f4bde57e5d4de0aeb673838383604051620007159392919062001c82565b60405180910390a16200072f828262000e3760201b60201c565b505050565b600090565b600081511162000780576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620007779062001d97565b60405180910390fd5b60006200079262000f6b60201b60201c565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160362000806576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620007fd9062001dfb565b60405180910390fd5b60008160010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054905090506000816bffffffffffffffffffffffff16036200087c576200087b828562000f9860201b60201c565b5b60005b8351811015620009d4576000848281518110620008a157620008a062001b21565b5b602002602001015190506000846000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161462000998576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200098f9062001e5f565b60405180910390fd5b620009ac8583868a6200107c60201b60201c565b8380620009b99062001ec3565b94505050508080620009cb9062001c61565b9150506200087f565b5050505050565b600081511162000a22576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000a199062001d97565b60405180910390fd5b600062000a3462000f6b60201b60201c565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160362000aa8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000a9f9062001dfb565b60405180910390fd5b60008160010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054905090506000816bffffffffffffffffffffffff160362000b1e5762000b1d828562000f9860201b60201c565b5b60005b835181101562000c8857600084828151811062000b435762000b4262001b21565b5b602002602001015190506000846000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508673ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160362000c39576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000c309062001eef565b60405180910390fd5b62000c4c8582846200122960201b60201c565b62000c608583868a6200107c60201b60201c565b838062000c6d9062001ec3565b9450505050808062000c7f9062001c61565b91505062000b21565b5050505050565b600081511162000cd6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000ccd9062001d97565b60405180910390fd5b600062000ce862000f6b60201b60201c565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161462000d5c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000d539062001f53565b60405180910390fd5b60005b825181101562000e3157600083828151811062000d815762000d8062001b21565b5b602002602001015190506000836000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905062000e198482846200122960201b60201c565b5050808062000e289062001c61565b91505062000d5f565b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16031562000f675762000e988260405180606001604052806028815260200162003c7a60289139620018aa60201b60201c565b6000808373ffffffffffffffffffffffffffffffffffffffff168360405162000ec2919062001fb7565b600060405180830381855af49150503d806000811462000eff576040519150601f19603f3d011682016040523d82523d6000602084013e62000f04565b606091505b50915091508162000f645760008151111562000f235780518082602001fd5b83836040517f192105d700000000000000000000000000000000000000000000000000000000815260040162000f5b92919062001fd7565b60405180910390fd5b50505b5050565b6000807f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f90508091505090565b62000fc38160405180606001604052806024815260200162003ca260249139620018aa60201b60201c565b81600201805490508260010160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001018190555081600201819080600181540180825580915050600190039060005260206000200160009091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b81846000016000857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508360010160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018390806001815401808255809150506001900390600052602060002090600891828204019190066004029091909190916101000a81548163ffffffff021916908360e01c021790555080846000016000857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036200129b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620012929062002003565b60405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036200130c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620013039062002067565b60405180910390fd5b6000836000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160149054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff169050600060018560010160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000180549050620013e59190620020cb565b9050808214620015805760008560010160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000182815481106200144a576200144962001b21565b5b90600052602060002090600891828204019190066004029054906101000a900460e01b9050808660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018481548110620014c957620014c862001b21565b5b90600052602060002090600891828204019190066004026101000a81548163ffffffff021916908360e01c021790555082866000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550505b8460010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600001805480620015d757620015d6620020ec565b5b60019003818190600052602060002090600891828204019190066004026101000a81549063ffffffff02191690559055846000016000847bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001908152602001600020600080820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556000820160146101000a8154906bffffffffffffffffffffffff0219169055505060008103620018a357600060018660020180549050620016c49190620020cb565b905060008660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001015490508181146200180c57600087600201838154811062001732576200173162001b21565b5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508088600201838154811062001779576200177862001b21565b5b9060005260206000200160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550818860010160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010181905550505b86600201805480620018235762001822620020ec565b5b6001900381819060005260206000200160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905590558660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001016000905550505b5050505050565b6000823b9050600081118290620018f9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620018f0919062002102565b60405180910390fd5b50505050565b6040518060600160405280600073ffffffffffffffffffffffffffffffffffffffff168152602001600060028111156200193e576200193d62001b37565b5b8152602001606081525090565b60008151905060018060a01b03811681146200196657600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b83811015620019a157808201518184015260208101905062001984565b50600083830152505050565b600080600080600080600080610100898b031215620019cb57600080fd5b620019d6896200194b565b9750620019e660208a016200194b565b9650620019f660408a016200194b565b955062001a0660608a016200194b565b945062001a1660808a016200194b565b935062001a2660a08a016200194b565b925062001a3660c08a016200194b565b915060e089015160018060401b038082111562001a5257600080fd5b818b0191508b601f83011262001a6757600080fd5b81518181111562001a7d5762001a7c6200196b565b5b601f1960405181603f83601f860116011681019150808210848311171562001aaa5762001aa96200196b565b5b816040528281528e602084870101111562001ac457600080fd5b62001ad783602083016020880162001981565b80955050505050509295985092959890939650565b60006020828403121562001aff57600080fd5b815163ffffffff60e01b8116811462001b1757600080fd5b8091505092915050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052602160045260246000fd5b60018060a01b03811682525050565b6000815180845262001b7681602086016020860162001981565b6020601f19601f83011685010191505092915050565b600060018060a01b03808a168352808916602084015280881660408401528087166060840152808616608084015280851660a08401525060e060c083015262001bd960e083018462001b5c565b905098975050505050505050565b60208152602760208201527f4c69624469616d6f6e644375743a20496e636f7272656374204661636574437560408201527f74416374696f6e0000000000000000000000000000000000000000000000000060608201526000608082019050919050565b634e487b7160e01b600052601160045260246000fd5b60008019820362001c775762001c7662001c4b565b5b600182019050919050565b60006060808301818452808751808352608092508286019150828160051b8701016020808b0160005b8481101562001d6357607f198a8503018652815188850160018060a01b038251168652848201516003811062001cf157634e487b7160e01b600052602160045260246000fd5b80868801525060408083015192508a81880152508082518083528a880191508684019350600092505b8083101562001d465763ffffffff60e01b84511682528682019150868401935060018301925062001d1a565b508096505050508282019150828601955060018101905062001cab565b505062001d738189018b62001b4d565b50868103604088015262001d88818962001b5c565b95505050505050949350505050565b60208152602b60208201527f4c69624469616d6f6e644375743a204e6f2073656c6563746f727320696e206660408201527f6163657420746f2063757400000000000000000000000000000000000000000060608201526000608082019050919050565b60208152602c60208201527f4c69624469616d6f6e644375743a204164642066616365742063616e2774206260408201527f652061646472657373283029000000000000000000000000000000000000000060608201526000608082019050919050565b60208152603560208201527f4c69624469616d6f6e644375743a2043616e2774206164642066756e6374696f60408201527f6e207468617420616c726561647920657869737473000000000000000000000060608201526000608082019050919050565b600060018060601b0380831681810362001ee25762001ee162001c4b565b5b6001810192505050919050565b60208152603860208201527f4c69624469616d6f6e644375743a2043616e2774207265706c6163652066756e60408201527f6374696f6e20776974682073616d652066756e6374696f6e000000000000000060608201526000608082019050919050565b60208152603660208201527f4c69624469616d6f6e644375743a2052656d6f7665206661636574206164647260408201527f657373206d75737420626520616464726573732830290000000000000000000060608201526000608082019050919050565b6000825162001fcb81846020870162001981565b80830191505092915050565b60018060a01b038316815260406020820152600062001ffa604083018462001b5c565b90509392505050565b60208152603760208201527f4c69624469616d6f6e644375743a2043616e27742072656d6f76652066756e6360408201527f74696f6e207468617420646f65736e277420657869737400000000000000000060608201526000608082019050919050565b60208152602e60208201527f4c69624469616d6f6e644375743a2043616e27742072656d6f766520696d6d7560408201527f7461626c652066756e6374696f6e00000000000000000000000000000000000060608201526000608082019050919050565b6000828203905081811115620020e657620020e562001c4b565b5b92915050565b634e487b7160e01b600052603160045260246000fd5b60208152600062002117602083018462001b5c565b905092915050565b611b4b806200212f6000396000f3fe60806040523661000b57005b6000807f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f9050809150600082600001600080357fffffffff00000000000000000000000000000000000000000000000000000000167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610141576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610138906114d3565b60405180910390fd5b3660008037600080366000845af43d6000803e8060008114610162573d6000f35b3d6000fd5b806101706103c0565b60010160016101000a81548163ffffffff021916908360e01c021790555050565b60005b83518110156103755760008482815181106101b2576101b1611510565b5b6020026020010151602001519050600060028111156101d4576101d3611526565b5b8160028111156101e7576101e6611526565b5b036102375761023285838151811061020257610201611510565b5b60200260200101516000015186848151811061022157610220611510565b5b6020026020010151604001516103c5565b610361565b6001600281111561024b5761024a611526565b5b81600281111561025e5761025d611526565b5b036102ae576102a985838151811061027957610278611510565b5b60200260200101516000015186848151811061029857610297611510565b5b60200260200101516040015161063c565b610360565b6002808111156102c1576102c0611526565b5b8160028111156102d4576102d3611526565b5b036103245761031f8583815181106102ef576102ee611510565b5b60200260200101516000015186848151811061030e5761030d611510565b5b6020026020010151604001516108bd565b61035f565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103569061153c565b60405180910390fd5b5b5b50808061036d906115b6565b915050610194565b507f8faa70878671ccd212d20771b795c50af8fd3ff6cf27f4bde57e5d4de0aeb6738383836040516103a99392919061163b565b60405180910390a16103bb8282610a48565b505050565b600090565b6000815111610409576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161040090611747565b60405180910390fd5b6000610413610b6a565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610484576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161047b906117ab565b60405180910390fd5b60008160010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054905090506000816bffffffffffffffffffffffff16036104f1576104f08285610b97565b5b60005b835181101561063557600084828151811061051257610511611510565b5b602002602001015190506000846000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610606576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105fd9061180f565b60405180910390fd5b6106128583868a610c72565b838061061d90611873565b9450505050808061062d906115b6565b9150506104f4565b5050505050565b6000815111610680576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067790611747565b60405180910390fd5b600061068a610b6a565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036106fb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106f2906117ab565b60405180910390fd5b60008160010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054905090506000816bffffffffffffffffffffffff1603610768576107678285610b97565b5b60005b83518110156108b657600084828151811061078957610788611510565b5b602002602001015190506000846000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508673ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361087c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610873906118a2565b60405180910390fd5b610887858284610e1f565b6108938583868a610c72565b838061089e90611873565b945050505080806108ae906115b6565b91505061076b565b5050505050565b6000815111610901576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108f890611747565b60405180910390fd5b600061090b610b6a565b9050600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161461097c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161097390611906565b60405180910390fd5b60005b8251811015610a4257600083828151811061099d5761099c611510565b5b602002602001015190506000836000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050610a2d848284610e1f565b50508080610a3a906115b6565b91505061097f565b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160315610b6657610a9f82604051806060016040528060288152602001611aca60289139611481565b6000808373ffffffffffffffffffffffffffffffffffffffff1683604051610ac7919061196a565b600060405180830381855af49150503d8060008114610b02576040519150601f19603f3d011682016040523d82523d6000602084013e610b07565b606091505b509150915081610b6357600081511115610b245780518082602001fd5b83836040517f192105d7000000000000000000000000000000000000000000000000000000008152600401610b5a929190611988565b60405180910390fd5b50505b5050565b6000807f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f90508091505090565b610bb981604051806060016040528060248152602001611af260249139611481565b81600201805490508260010160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001018190555081600201819080600181540180825580915050600190039060005260206000200160009091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b81846000016000857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508360010160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018390806001815401808255809150506001900390600052602060002090600891828204019190066004029091909190916101000a81548163ffffffff021916908360e01c021790555080846000016000857bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610e8e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e85906119b2565b60405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610efc576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ef390611a16565b60405180910390fd5b6000836000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160149054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff169050600060018560010160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000180549050610fd39190611a7a565b90508082146111675760008560010160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600001828154811061103457611033611510565b5b90600052602060002090600891828204019190066004029054906101000a900460e01b9050808660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000184815481106110b0576110af611510565b5b90600052602060002090600891828204019190066004026101000a81548163ffffffff021916908360e01c021790555082866000016000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060000160146101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550505b8460010160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018054806111bb576111ba611a98565b5b60019003818190600052602060002090600891828204019190066004026101000a81549063ffffffff02191690559055846000016000847bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001908152602001600020600080820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556000820160146101000a8154906bffffffffffffffffffffffff021916905550506000810361147a576000600186600201805490506112a59190611a7a565b905060008660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001015490508181146113e657600087600201838154811061130f5761130e611510565b5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508088600201838154811061135357611352611510565b5b9060005260206000200160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550818860010160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010181905550505b866002018054806113fa576113f9611a98565b5b6001900381819060005260206000200160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905590558660010160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001016000905550505b5050505050565b6000823b90506000811182906114cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114c49190611aae565b60405180910390fd5b50505050565b602081526020808201527f4469616d6f6e643a2046756e6374696f6e20646f6573206e6f7420657869737460408201526000606082019050919050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052602160045260246000fd5b60208152602760208201527f4c69624469616d6f6e644375743a20496e636f7272656374204661636574437560408201527f74416374696f6e0000000000000000000000000000000000000000000000000060608201526000608082019050919050565b634e487b7160e01b600052601160045260246000fd5b6000801982036115c9576115c86115a0565b5b600182019050919050565b60018060a01b03811682525050565b60005b838110156116015780820151818401526020810190506115e6565b50600083830152505050565b600081518084526116258160208601602086016115e3565b6020601f19601f83011685010191505092915050565b60006060808301818452808751808352608092508286019150828160051b8701016020808b0160005b8481101561171757607f198a8503018652815188850160018060a01b03825116865284820151600381106116a857634e487b7160e01b600052602160045260246000fd5b80868801525060408083015192508a81880152508082518083528a880191508684019350600092505b808310156116fb5763ffffffff60e01b8451168252868201915086840193506001830192506116d1565b5080965050505082820191508286019550600181019050611664565b50506117258189018b6115d4565b508681036040880152611738818961160d565b95505050505050949350505050565b60208152602b60208201527f4c69624469616d6f6e644375743a204e6f2073656c6563746f727320696e206660408201527f6163657420746f2063757400000000000000000000000000000000000000000060608201526000608082019050919050565b60208152602c60208201527f4c69624469616d6f6e644375743a204164642066616365742063616e2774206260408201527f652061646472657373283029000000000000000000000000000000000000000060608201526000608082019050919050565b60208152603560208201527f4c69624469616d6f6e644375743a2043616e2774206164642066756e6374696f60408201527f6e207468617420616c726561647920657869737473000000000000000000000060608201526000608082019050919050565b60006bffffffffffffffffffffffff808316818103611895576118946115a0565b5b6001810192505050919050565b60208152603860208201527f4c69624469616d6f6e644375743a2043616e2774207265706c6163652066756e60408201527f6374696f6e20776974682073616d652066756e6374696f6e000000000000000060608201526000608082019050919050565b60208152603660208201527f4c69624469616d6f6e644375743a2052656d6f7665206661636574206164647260408201527f657373206d75737420626520616464726573732830290000000000000000000060608201526000608082019050919050565b6000825161197c8184602087016115e3565b80830191505092915050565b60018060a01b03831681526040602082015260006119a9604083018461160d565b90509392505050565b60208152603760208201527f4c69624469616d6f6e644375743a2043616e27742072656d6f76652066756e6360408201527f74696f6e207468617420646f65736e277420657869737400000000000000000060608201526000608082019050919050565b60208152602e60208201527f4c69624469616d6f6e644375743a2043616e27742072656d6f766520696d6d7560408201527f7461626c652066756e6374696f6e00000000000000000000000000000000000060608201526000608082019050919050565b6000828203905081811115611a9257611a916115a0565b5b92915050565b634e487b7160e01b600052603160045260246000fd5b602081526000611ac1602083018461160d565b90509291505056fe4c69624469616d6f6e644375743a205f696e6974206164647265737320686173206e6f20636f64654c69624469616d6f6e644375743a204e657720666163657420686173206e6f20636f6465a264697066735822122045b771fb2128a1a34c5b052e9a86464933844b34929cf0d65bbea6a4e76e3b2764736f6c634300081200334c69624469616d6f6e644375743a205f696e6974206164647265737320686173206e6f20636f64654c69624469616d6f6e644375743a204e657720666163657420686173206e6f20636f6465"); + input.set_public_key("0xB5547FBdC56DCE45e1B8ef75569916D438e09c46"); + input.set_salt(0); + + // C++ + { + const std::string& address = getCounterfactualAddress(input); + ASSERT_EQ(address, "0x77F62bb3E43190253D4E198199356CD2b25063cA"); + } + + // C + { + const auto inputData = input.SerializeAsString(); + const auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size())); + const auto& result = WRAPS(TWBarzGetCounterfactualAddress(inputTWData.get())); + assertStringsEqual(result, "0x77F62bb3E43190253D4E198199356CD2b25063cA"); + } +} + +TEST(Barz, GetCounterfactualAddressNonZeroSalt) { + TW::Barz::Proto::ContractAddressInput input; + input.set_factory("0x96C489979E39F877BDb8637b75A25C1a5B2DE14C"); + input.set_account_facet("0xF6F5e5fC74905e65e3FF53c6BacEba8535dd14d1"); + input.set_verification_facet("0xaB84813cbf26Fd951CB3d7E33Dccb8995027e490"); + input.set_entry_point("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"); + input.set_facet_registry("0x9a95d201BB8F559771784D12c01F8084278c65E5"); + input.set_default_fallback("0x522cDc7558b5f798dF5D61AB09B6D95Ebd342EF9"); + input.set_bytecode("0x60806040526040516104c83803806104c883398101604081905261002291610163565b6000858585858560405160240161003d959493929190610264565b60408051601f198184030181529181526020820180516001600160e01b0316634a93641760e01b1790525190915060009081906001600160a01b038a16906100869085906102c3565b600060405180830381855af49150503d80600081146100c1576040519150601f19603f3d011682016040523d82523d6000602084013e6100c6565b606091505b50915091508115806100e157506100dc816102df565b600114155b156100ff57604051636ff35f8960e01b815260040160405180910390fd5b505050505050505050610306565b80516001600160a01b038116811461012457600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561015a578181015183820152602001610142565b50506000910152565b60008060008060008060c0878903121561017c57600080fd5b6101858761010d565b95506101936020880161010d565b94506101a16040880161010d565b93506101af6060880161010d565b92506101bd6080880161010d565b60a08801519092506001600160401b03808211156101da57600080fd5b818901915089601f8301126101ee57600080fd5b81518181111561020057610200610129565b604051601f8201601f19908116603f0116810190838211818310171561022857610228610129565b816040528281528c602084870101111561024157600080fd5b61025283602083016020880161013f565b80955050505050509295509295509295565b600060018060a01b0380881683528087166020840152808616604084015280851660608401525060a0608083015282518060a08401526102ab8160c085016020870161013f565b601f01601f19169190910160c0019695505050505050565b600082516102d581846020870161013f565b9190910192915050565b80516020808301519190811015610300576000198160200360031b1b821691505b50919050565b6101b3806103156000396000f3fe60806040523661000b57005b600080356001600160e01b03191681527f183cde5d4f6bb7b445b8fc2f7f15d0fd1d162275aded24183babbffee7cd491f6020819052604090912054819060601c806100cf576004838101546040516366ffd66360e11b81526000356001600160e01b031916928101929092526001600160a01b03169063cdffacc690602401602060405180830381865afa1580156100a8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100cc919061014d565b90505b6001600160a01b0381166101295760405162461bcd60e51b815260206004820152601d60248201527f4261727a3a2046756e6374696f6e20646f6573206e6f74206578697374000000604482015260640160405180910390fd5b3660008037600080366000845af43d6000803e808015610148573d6000f35b3d6000fd5b60006020828403121561015f57600080fd5b81516001600160a01b038116811461017657600080fd5b939250505056fea2646970667358221220d35db061bb6ecdb7688c3674af669ce44d527cae4ded59214d06722d73da62be64736f6c63430008120033"); + input.set_public_key("0xB5547FBdC56DCE45e1B8ef75569916D438e09c46"); + input.set_salt(123456); + + // C++ + { + const std::string& address = getCounterfactualAddress(input); + ASSERT_EQ(address, "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"); + } + + // C + { + const auto inputData = input.SerializeAsString(); + const auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size())); + const auto& result = WRAPS(TWBarzGetCounterfactualAddress(inputTWData.get())); + assertStringsEqual(result, "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"); + } +} + +TEST(Barz, GetFormattedSignature) { + // C++ + { + const Data& signature = parse_hex("0x3044022012d89e3b41e253dc9e90bd34dc1750d059b76d0b1d16af2059aa26e90b8960bf0220256d8a05572c654906ce422464693e280e243e6d9dbc5f96a681dba846bca276"); + const Data& challenge = parse_hex("0xcf267a78c5adaf96f341a696eb576824284c572f3e61be619694d539db1925f9"); + const Data& authenticatorData = parse_hex("0x1a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d00000000"); + const std::string& clientDataJSON = "{\"type\":\"webauthn.get\",\"challenge\":\"zyZ6eMWtr5bzQaaW61doJChMVy8-Yb5hlpTVOdsZJfk\",\"origin\":\"https://trustwallet.com\"}"; + + const auto& formattedSignature = Barz::getFormattedSignature(signature, challenge, authenticatorData, clientDataJSON); + ASSERT_EQ(hexEncoded(formattedSignature), "0x12d89e3b41e253dc9e90bd34dc1750d059b76d0b1d16af2059aa26e90b8960bf256d8a05572c654906ce422464693e280e243e6d9dbc5f96a681dba846bca27600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000251a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025222c226f726967696e223a2268747470733a2f2f747275737477616c6c65742e636f6d227d000000000000000000000000000000000000000000000000000000"); + } + + // C + { + const auto signature = DATA("0x3044022012d89e3b41e253dc9e90bd34dc1750d059b76d0b1d16af2059aa26e90b8960bf0220256d8a05572c654906ce422464693e280e243e6d9dbc5f96a681dba846bca276"); + const auto challenge = DATA("0xcf267a78c5adaf96f341a696eb576824284c572f3e61be619694d539db1925f9"); + const auto authenticatorData = DATA("0x1a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d00000000"); + const auto clientDataJSON = STRING("{\"type\":\"webauthn.get\",\"challenge\":\"zyZ6eMWtr5bzQaaW61doJChMVy8-Yb5hlpTVOdsZJfk\",\"origin\":\"https://trustwallet.com\"}"); + + const auto& formattedSignatureData = TWBarzGetFormattedSignature(signature.get(), challenge.get(), authenticatorData.get(), clientDataJSON.get()); + const auto& formattedSignature = hexEncoded(*reinterpret_cast(WRAPD(formattedSignatureData).get())); + EXPECT_EQ(formattedSignature, "0x12d89e3b41e253dc9e90bd34dc1750d059b76d0b1d16af2059aa26e90b8960bf256d8a05572c654906ce422464693e280e243e6d9dbc5f96a681dba846bca27600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000251a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025222c226f726967696e223a2268747470733a2f2f747275737477616c6c65742e636f6d227d000000000000000000000000000000000000000000000000000000"); + } +} + +// https://testnet.bscscan.com/tx/0x43fc13dfdf06bbb09da8ce070953753764f1e43782d0c8b621946d8b45749419 +TEST(Barz, SignK1TransferAccountDeployed) { + TW::Ethereum::Proto::SigningInput input; + auto chainId = store(uint256_t(97)); + auto nonce = store(uint256_t(2)); + auto amount = store(uint256_t(0x2386f26fc10000)); + auto gasLimit = store(uint256_t(0x186A0)); + auto verificationGasLimit = store(uint256_t(0x186a0)); + auto maxFeePerGas = store(uint256_t(0x1a339c9e9)); + auto maxInclusionFeePerGas = store(uint256_t(0x1a339c9e9)); + auto preVerificationGas = store(uint256_t(0xb708)); + auto entryPoint = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"; + auto sender = "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f"; + auto to = "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9"; + + auto key = parse_hex("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483"); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_tx_mode(TW::Ethereum::Proto::TransactionMode::UserOp); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); + input.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); + input.set_to_address(to); + + auto& user_operation = *input.mutable_user_operation(); + user_operation.set_verification_gas_limit(verificationGasLimit.data(), verificationGasLimit.size()); + user_operation.set_pre_verification_gas(preVerificationGas.data(), preVerificationGas.size()); + user_operation.set_entry_point(entryPoint); + user_operation.set_sender(sender); + + input.set_private_key(key.data(), key.size()); + auto& transfer = *input.mutable_transaction()->mutable_transfer(); + transfer.set_amount(amount.data(), amount.size()); + + std::string expected = "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"100000\",\"initCode\":\"0x\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"2\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0xb16Db98B365B1f89191996942612B14F1Da4Bd5f\",\"signature\":\"0x80e84992ebf8d5f71180231163ed150a7557ed0aa4b4bcee23d463a09847e4642d0fbf112df2e5fa067adf4b2fa17fc4a8ac172134ba5b78e3ec9c044e7f28d71c\",\"verificationGasLimit\":\"100000\"}"; + + { + // sign test + TW::Ethereum::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hexEncoded(output.pre_hash()), "0x2d37191a8688f69090451ed90a0a9ba69d652c2062ee9d023b3ebe964a3ed2ae"); + ASSERT_EQ(std::string(output.encoded()), expected); + } +} + +// https://testnet.bscscan.com/tx/0xea1f5cddc0653e116327cbcb3bc770360a642891176eff2ec69c227e46791c31 +TEST(Barz, SignR1TransferAccountNotDeployed) { + TW::Ethereum::Proto::SigningInput input; + auto chainId = store(uint256_t(97)); + auto nonce = store(uint256_t(0)); + auto amount = store(uint256_t(0x2386f26fc10000)); + auto gasLimit = store(uint256_t(0x2625A0)); + auto verificationGasLimit = store(uint256_t(0x2DC6C0)); + auto maxFeePerGas = store(uint256_t(0x1a339c9e9)); + auto maxInclusionFeePerGas = store(uint256_t(0x1a339c9e9)); + auto preVerificationGas = store(uint256_t(0xb708)); + auto entryPoint = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"; + auto sender = "0x1392Ae041BfBdBAA0cFF9234a0C8F64df97B7218"; + auto to = "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9"; + auto factory = "0x3fC708630d85A3B5ec217E53100eC2b735d4f800"; + auto verificationFacet = "0x5034534Efe9902779eD6eA6983F435c00f3bc510"; + auto publicKey = PublicKey(parse_hex("0x04b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f"), TWPublicKeyTypeNIST256p1Extended); + auto salt = 0; + auto initCode = Barz::getInitCode(factory, publicKey, verificationFacet, salt); + + auto key = parse_hex("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483"); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_tx_mode(TW::Ethereum::Proto::TransactionMode::UserOp); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); + input.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); + input.set_to_address(to); + + auto& user_operation = *input.mutable_user_operation(); + user_operation.set_verification_gas_limit(verificationGasLimit.data(), verificationGasLimit.size()); + user_operation.set_pre_verification_gas(preVerificationGas.data(), preVerificationGas.size()); + user_operation.set_entry_point(entryPoint); + user_operation.set_sender(sender); + user_operation.set_init_code(initCode.data(), initCode.size()); + + input.set_private_key(key.data(), key.size()); + auto& transfer = *input.mutable_transaction()->mutable_transfer(); + transfer.set_amount(amount.data(), amount.size()); + + std::string expected = "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"2500000\",\"initCode\":\"0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000005034534efe9902779ed6ea6983f435c00f3bc51000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f00000000000000000000000000000000000000000000000000000000000000\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"0\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0x1392Ae041BfBdBAA0cFF9234a0C8F64df97B7218\",\"signature\":\"0xbf1b68323974e71ad9bd6dfdac07dc062599d150615419bb7876740d2bcf3c8909aa7e627bb0e08a2eab930e2e7313247c9b683c884236dd6ea0b6834fb2cb0a1b\",\"verificationGasLimit\":\"3000000\"}"; + { + // sign test + TW::Ethereum::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hexEncoded(output.pre_hash()), "0x548c13a0bb87981d04a3a24a78ad5e4ba8d0afbf3cfe9311250e07b54cd38937"); + ASSERT_EQ(std::string(output.encoded()), expected); + } +} + +// https://testnet.bscscan.com/tx/0x872f709815a9f79623a349f2f16d93b52c4d5136967bab53a586f045edbe9203 +TEST(Barz, SignR1BatchedTransferAccountDeployed) { + TW::Ethereum::Proto::SigningInput input; + auto chainId = store(uint256_t(97)); + auto nonce = store(uint256_t(3)); + auto amount = store(uint256_t(0x00)); + auto gasLimit = store(uint256_t(0x015A61)); + auto verificationGasLimit = store(uint256_t(0x07F7C4)); + auto maxFeePerGas = store(uint256_t(0x02540BE400)); + auto maxInclusionFeePerGas = store(uint256_t(0x02540BE400)); + auto preVerificationGas = store(uint256_t(0xDAFC)); + auto entryPoint = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"; + auto sender = "0x1e6c542ebc7c960c6a155a9094db838cef842cf5"; + auto to = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266"; + + auto key = parse_hex("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483"); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_tx_mode(TW::Ethereum::Proto::TransactionMode::UserOp); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); + input.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); + input.set_to_address(to); + + auto& user_operation = *input.mutable_user_operation(); + user_operation.set_verification_gas_limit(verificationGasLimit.data(), verificationGasLimit.size()); + user_operation.set_pre_verification_gas(preVerificationGas.data(), preVerificationGas.size()); + user_operation.set_entry_point(entryPoint); + user_operation.set_sender(sender); + + + // approve + TWEthereumAbiFunction* approveFunc = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("approve")).get()); + TWEthereumAbiFunctionAddParamAddress(approveFunc, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("5FF137D4b0FDCD49DcA30c7CF57E578a026d2789")).get())).get(), false); + TWEthereumAbiFunctionAddParamUInt256(approveFunc, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("8AC7230489E80000")).get())).get(), false); + auto approveCallEncoded = WRAPD(TWEthereumAbiEncode(approveFunc)); + auto approveCall = data(TWDataBytes(approveCallEncoded.get()), TWDataSize(approveCallEncoded.get())); + EXPECT_EQ(hex(approveCall), "095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000"); + + // transfer + TWEthereumAbiFunction* transferFunc = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("transfer")).get()); + TWEthereumAbiFunctionAddParamAddress(transferFunc, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("5FF137D4b0FDCD49DcA30c7CF57E578a026d2789")).get())).get(), false); + TWEthereumAbiFunctionAddParamUInt256(transferFunc, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("8AC7230489E80000")).get())).get(), false); + auto transferCallEncoded = WRAPD(TWEthereumAbiEncode(transferFunc)); + auto transferCall = data(TWDataBytes(transferCallEncoded.get()), TWDataSize(transferCallEncoded.get())); + EXPECT_EQ(hex(transferCall), "a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000"); + + auto *batch = input.mutable_transaction()->mutable_batch(); + auto *c1 = batch->add_calls(); + c1->set_address(to); + c1->set_amount(amount.data(), amount.size()); + c1->set_payload(approveCall.data(), approveCall.size()); + auto *c2 = batch->add_calls(); + c2->set_address(to); + c2->set_amount(amount.data(), amount.size()); + c2->set_payload(transferCall.data(), transferCall.size()); + + input.set_private_key(key.data(), key.size()); + + std::string expected = "{\"callData\":\"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"88673\",\"initCode\":\"0x\",\"maxFeePerGas\":\"10000000000\",\"maxPriorityFeePerGas\":\"10000000000\",\"nonce\":\"3\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"56060\",\"sender\":\"0x1E6c542ebC7c960c6A155A9094DB838cEf842cf5\",\"signature\":\"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c\",\"verificationGasLimit\":\"522180\"}"; + { + // sign test + TW::Ethereum::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hexEncoded(output.pre_hash()), "0x84d0464f5a2b191e06295443970ecdcd2d18f565d0d52b5a79443192153770ab"); + ASSERT_EQ(std::string(output.encoded()), expected); + } + + TWEthereumAbiFunctionDelete(approveFunc); + TWEthereumAbiFunctionDelete(transferFunc); +} + +TEST(Barz, GetPrefixedMsgHash) { + { + const Data& msgHash = parse_hex("0xa6ebe22d8c1ec7edbd7f5776e49a161f67ab97161d7b8c648d80abf365765cf2"); + const std::string& barzAddress = "0x913233BfC283ffe89a5E70ADC39c0926d240bbD9"; + const auto chainId = 3604; + + const auto& prefixedMsgHash = Barz::getPrefixedMsgHash(msgHash, barzAddress, chainId); + ASSERT_EQ(hexEncoded(prefixedMsgHash), "0x0488fb3e4fdaa890bf55532fc9840fb9edef9c38244f431c9430a78a86d89157"); + } +} + +TEST(Barz, GetPrefixedMsgHashWithZeroChainId) { + { + const Data& msgHash = parse_hex("0xcf267a78c5adaf96f341a696eb576824284c572f3e61be619694d539db1925f9"); + const std::string& barzAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; + const auto chainId = 0; + + const auto& prefixedMsgHash = Barz::getPrefixedMsgHash(msgHash, barzAddress, chainId); + ASSERT_EQ(hexEncoded(prefixedMsgHash), "0xc74e78634261222af51530703048f98a1b7b995a606a624f0a008e7aaba7a21b"); + } +} + +TEST(Barz, GetDiamondCutCode) { + { + TW::Barz::Proto::DiamondCutInput input; + + input.set_init_address("0x0000000000000000000000000000000000000000"); + input.set_init_data("0x00"); + + auto* facetCutAdd = input.add_facet_cuts(); + facetCutAdd->set_facet_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); + facetCutAdd->set_action(TW::Barz::Proto::FacetCutAction::ADD); + + auto functionSelectorAdd = parse_hex("0xfdd8a83c"); + facetCutAdd->add_function_selectors(functionSelectorAdd.data(), functionSelectorAdd.size()); + + const auto& diamondCutCode = Barz::getDiamondCutCode(input); + ASSERT_EQ(hexEncoded(diamondCutCode), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001fdd8a83c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"); + } +} + +TEST(Barz, GetDiamondCutCodeWithMultipleCut) { + { + TW::Barz::Proto::DiamondCutInput input; + + input.set_init_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); + input.set_init_data("0x12341234"); + + auto* facetCutMigrationFacet = input.add_facet_cuts(); + facetCutMigrationFacet->set_facet_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); + facetCutMigrationFacet->set_action(TW::Barz::Proto::FacetCutAction::ADD); + + auto migrationSignature = parse_hex("0xfdd8a83c"); + + facetCutMigrationFacet->add_function_selectors(migrationSignature.data(), migrationSignature.size()); + facetCutMigrationFacet->add_function_selectors(migrationSignature.data(), migrationSignature.size()); + facetCutMigrationFacet->add_function_selectors(migrationSignature.data(), migrationSignature.size()); + + auto* facetCutTestFacet = input.add_facet_cuts(); + facetCutTestFacet->set_facet_address("0x6e3c94d74af6227aEeF75b54a679e969189a6aEC"); + facetCutTestFacet->set_action(TW::Barz::Proto::FacetCutAction::ADD); + + auto testSignature = parse_hex("0x12345678"); + facetCutTestFacet->add_function_selectors(testSignature.data(), testSignature.size()); + + + const auto& diamondCutCode = Barz::getDiamondCutCode(input); + ASSERT_EQ(hexEncoded(diamondCutCode), "0x1f931c1c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003fdd8a83c00000000000000000000000000000000000000000000000000000000fdd8a83c00000000000000000000000000000000000000000000000000000000fdd8a83c000000000000000000000000000000000000000000000000000000000000000000000000000000006e3c94d74af6227aeef75b54a679e969189a6aec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001123456780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041234123400000000000000000000000000000000000000000000000000000000"); + } +} + +TEST(Barz, GetDiamondCutCodeWithZeroSelector) { + { + TW::Barz::Proto::DiamondCutInput input; + + input.set_init_address("0x0000000000000000000000000000000000000000"); + input.set_init_data("0x00"); + auto* facetCutAdd = input.add_facet_cuts(); + facetCutAdd->set_facet_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); + facetCutAdd->set_action(TW::Barz::Proto::FacetCutAction::ADD); + + const auto& diamondCutCode = Barz::getDiamondCutCode(input); + ASSERT_EQ(hexEncoded(diamondCutCode), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"); + } +} + +TEST(Barz, GetDiamondCutCodeWithLongInitData) { + { + TW::Barz::Proto::DiamondCutInput input; + + input.set_init_address("0x0000000000000000000000000000000000000000"); + input.set_init_data("0xb61d27f6000000000000000000000000c2ce171d25837cd43e496719f5355a847edc679b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a526d83b00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b90600000000000000000000000000000000000000000000000000000000"); + auto* facetCutAdd = input.add_facet_cuts(); + facetCutAdd->set_facet_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); + facetCutAdd->set_action(TW::Barz::Proto::FacetCutAction::ADD); + + const auto& diamondCutCode = Barz::getDiamondCutCode(input); + ASSERT_EQ(hexEncoded(diamondCutCode), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c4b61d27f6000000000000000000000000c2ce171d25837cd43e496719f5355a847edc679b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a526d83b00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b9060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + } +} + +} + diff --git a/tests/chains/Ethereum/ContractCallTests.cpp b/tests/chains/Ethereum/ContractCallTests.cpp new file mode 100644 index 00000000000..8ddf3cddad3 --- /dev/null +++ b/tests/chains/Ethereum/ContractCallTests.cpp @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Ethereum/ContractCall.h" +#include "HexCoding.h" + +#include +#include + +extern std::string TESTS_ROOT; + +namespace TW::Ethereum::ABI::tests { + +static nlohmann::json load_json(const std::string& path) { + std::ifstream stream(path); + nlohmann::json json; + stream >> json; + return json; +} + +static std::string load_json_str(const std::string& path) { + std::ifstream stream(path); + std::string json((std::istreambuf_iterator(stream)), + std::istreambuf_iterator()); + return json; +} + +TEST(ContractCall, Approval) { + auto path = TESTS_ROOT + "/chains/Ethereum/Data/erc20.json"; + auto abi = load_json_str(path); + auto call = parse_hex("095ea7b30000000000000000000000005aaeb6053f3e94c9b9a09f33669435e7ef1beaed" + "0000000000000000000000000000000000000000000000000000000000000001"); + auto decoded = decodeCall(call, abi); + + auto expected = + R"|({"function":"approve(address,uint256)","inputs":[{"name":"_spender","type":"address","value":"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"},{"name":"_value","type":"uint256","value":"1"}]})|"; + + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, UniswapSwapTokens) { + auto path = TESTS_ROOT + "/chains/Ethereum/Data/uniswap_router_v2.json"; + auto abi = load_json_str(path); + // https://etherscan.io/tx/0x57a2414f3cd9ca373b7e663ae67ecf933e40cb77a6e4ed28e4e28b5aa0d8ec63 + auto call = parse_hex( + "0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000" + "00000000000000000000000000000000229f7e501ad62bdb000000000000000000000000000000000000000000" + "00000000000000000000a00000000000000000000000007d8bf18c7ce84b3e175b339c4ca93aed1dd166f10000" + "00000000000000000000000000000000000000000000000000005f0ed070000000000000000000000000000000" + "00000000000000000000000000000000040000000000000000000000006b175474e89094c44da98b954eedeac4" + "95271d0f0000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a2000000000000000000" + "000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000e41d2489571d32218924" + "6dafa5ebde1f4699f498"); + auto decoded = decodeCall(call, abi); + auto expected = + R"|({"function":"swapExactTokensForTokens(uint256,uint256,address[],address,uint256)","inputs":[{"name":"amountIn","type":"uint256","value":"1000000000000000000"},{"name":"amountOutMin","type":"uint256","value":"2494851601099271131"},{"name":"path","type":"address[]","value":["0x6B175474E89094C44Da98b954EedeAC495271d0F","0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2","0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","0xE41d2489571d322189246DaFA5ebDe1F4699F498"]},{"name":"to","type":"address","value":"0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1"},{"name":"deadline","type":"uint256","value":"1594806384"}]})|"; + + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, KyberTrade) { + auto path = TESTS_ROOT + "/chains/Ethereum/Data/kyber_proxy.json"; + auto abi = load_json_str(path); + + // https://etherscan.io/tx/0x51ffab782b9a27d754389505d5a50db525c04c68142ce20512d579f10f9e13e4 + auto call = parse_hex( + "ae591d54000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000" + "000000000000000000000000000004a97d605a3b980000000000000000000000000000dac17f958d2ee523a220" + "6206994597c13d831ec70000000000000000000000007755297c6a26d495739206181fe81646dbd0bf39ffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000" + "000000000000000ce32ff7d63c35d189000000000000000000000000440bbd6a888a36de6e2f6a25f65bc4e168" + "74faa9000000000000000000000000000000000000000000000000000000000000000800000000000000000000" + "000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000" + "000000000000000000"); + auto decoded = decodeCall(call, abi); + + auto expected = + R"|({"function":"tradeWithHintAndFee(address,uint256,address,address,uint256,uint256,address,uint256,bytes)","inputs":[{"name":"src","type":"address","value":"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"},{"name":"srcAmount","type":"uint256","value":"86000000000000000000"},{"name":"dest","type":"address","value":"0xdAC17F958D2ee523a2206206994597C13D831ec7"},{"name":"destAddress","type":"address","value":"0x7755297C6A26D495739206181Fe81646dbD0Bf39"},{"name":"maxDestAmount","type":"uint256","value":"115792089237316195423570985008687907853269984665640564039457584007913129639935"},{"name":"minConversionRate","type":"uint256","value":"237731504554534883721"},{"name":"platformWallet","type":"address","value":"0x440bBd6a888a36DE6e2F6A25f65bc4e16874faa9"},{"name":"platformFeeBps","type":"uint256","value":"8"},{"name":"hint","type":"bytes","value":"0x"}]})|"; + + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, ApprovalForAll) { + auto path = TESTS_ROOT + "/chains/Ethereum/Data/erc721.json"; + auto abi = load_json_str(path); + + // https://etherscan.io/tx/0xc2744000a107aee4761cf8a638657f91c3003a54e2f1818c37d781be7e48187a + auto call = parse_hex("0xa22cb46500000000000000000000000088341d1a8f672d2780c8dc725902aae72f143b" + "0c0000000000000000000000000000000000000000000000000000000000000001"); + auto decoded = decodeCall(call, abi); + + auto expected = + R"|({"function":"setApprovalForAll(address,bool)","inputs":[{"name":"to","type":"address","value":"0x88341d1a8F672D2780C8dC725902AAe72F143B0c"},{"name":"approved","type":"bool","value":true}]})|"; + + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, CustomCall) { + auto path = TESTS_ROOT + "/chains/Ethereum/Data/custom.json"; + auto abi = load_json_str(path); + + auto call = parse_hex("ec37a4a000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000067472757374790000000000000000000000000000000000000000000000000000"); + auto decoded = decodeCall(call, abi); + auto expected = + R"|({"function":"setName(string,uint256,int32)","inputs":[{"name":"name","type":"string","value":"trusty"},{"name":"age","type":"uint256","value":"3"},{"name":"height","type":"int32","value":"100"}]})|"; + + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, SetResolver) { + auto call = parse_hex("0x1896f70ae71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c" + "6f0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41"); + auto path = TESTS_ROOT + "/chains/Ethereum/Data/ens.json"; + auto abi = load_json_str(path); + auto decoded = decodeCall(call, abi); + auto expected = + R"|({"function":"setResolver(bytes32,address)","inputs":[{"name":"node","type":"bytes32","value":"0xe71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f"},{"name":"resolver","type":"address","value":"0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41"}]})|"; + + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, RenewENS) { + auto call = parse_hex( + "0xacf1a84100000000000000000000000000000000000000000000000000000000000000400000000000000000" + "000000000000000000000000000000000000000001e18558000000000000000000000000000000000000000000" + "000000000000000000000a68657769676f76656e7300000000000000000000000000000000000000000000"); + auto path = TESTS_ROOT + "/chains/Ethereum/Data/ens.json"; + auto abi = load_json_str(path); + auto decoded = decodeCall(call, abi); + auto expected = + R"|({"function":"renew(string,uint256)","inputs":[{"name":"name","type":"string","value":"hewigovens"},{"name":"duration","type":"uint256","value":"31556952"}]})|"; + + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, Multicall) { + auto call = parse_hex( + "0xac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000" + "000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000" + "000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000" + "0000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000" + "000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000" + "00000044d5fa2b00e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f0000000000" + "0000000000000047331175b23c2f067204b506ca1501c26731c990000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000000000000064304e6a" + "dee71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f000000000000000000000000" + "000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000a48b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e" + "574c32a17a67156fa9ed3c4c6f000000000000000000000000000000000000000000000000000000000000003c" + "000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000" + "0000000000000000000000000000000000001447331175b23c2f067204b506ca1501c26731c990000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000a48b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e57" + "4c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000002ca00" + "000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000" + "000000000000000000000000000000000014d30f834b53d8f7e851e87b90ffa65757a35b850500000000000000" + "000000000000000000000000000000000000000000000000000000000000000000"); + ASSERT_EQ(4 + 928ul, call.size()); + auto path = TESTS_ROOT + "/chains/Ethereum/Data/ens.json"; + auto abi = load_json_str(path); + auto decoded = decodeCall(call, abi); + auto expected = + R"|({"function":"multicall(bytes[])","inputs":[{"name":"data","type":"bytes[]","value":["0xd5fa2b00e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000047331175b23c2f067204b506ca1501c26731c990","0x304e6adee71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000","0x8b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001447331175b23c2f067204b506ca1501c26731c990000000000000000000000000","0x8b95dd71e71cd96d4ba1c4b512b0c5bee30d2b6becf61e574c32a17a67156fa9ed3c4c6f00000000000000000000000000000000000000000000000000000000000002ca00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000014d30f834b53d8f7e851e87b90ffa65757a35b8505000000000000000000000000"]}]})|"; + + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, Invalid) { + EXPECT_FALSE(decodeCall(Data(), "{}").has_value()); + EXPECT_FALSE(decodeCall(parse_hex("0xa22cb46500"), "{}").has_value()); +} + +TEST(ContractCall, GetAmountsOut) { + auto call = parse_hex( + "d06ca61f" + "0000000000000000000000000000000000000000000000000000000000000064" + "0000000000000000000000000000000000000000000000000000000000000040" + "0000000000000000000000000000000000000000000000000000000000000001" + "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"); + auto path = TESTS_ROOT + "/chains/Ethereum/Data/getAmountsOut.json"; + auto abi = load_json_str(path); + + auto decoded = decodeCall(call, abi); + ASSERT_TRUE(decoded.has_value()); + auto expected = + R"|({"function":"getAmountsOut(uint256,address[])","inputs":[{"name":"amountIn","type":"uint256","value":"100"},{"name":"path","type":"address[]","value":["0xF784682C82526e245F50975190EF0fff4E4fC077"]}]})|"; + EXPECT_EQ(decoded.value(), expected); +} + +TEST(ContractCall, 1inch) { + auto abiPath = TESTS_ROOT + "/chains/Ethereum/Data/1inch.json"; + auto decodedPath = TESTS_ROOT + "/chains/Ethereum/Data/1inch_decoded.json"; + + auto abi = load_json_str(abiPath); + auto expected = load_json(decodedPath); + + // https://etherscan.io/tx/0xc2d113151124579c21332d4cc6ab2b7f61e81d62392ed8596174513cb47e35ba + auto call = parse_hex( + "7c02520000000000000000000000000027239549dd40e1d60f5b80b0c4196923745b1fd2000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001800000000000000000000000002b591e99afe9f32eaa6214f7b7629768c40eeb39000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000027239549dd40e1d60f5b80b0c4196923745b1fd20000000000000000000000001611c227725c5e420ef058275ae772b41775e261000000000000000000000000000000000000000000000000000005d0fadb1c0000000000000000000000000000000000000000000000000000000005c31df1da000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002080000000000000000000000069d91b94f0aaf8e8a2586909fa77a5c2c89818d50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000104128acb080000000000000000000000001611c227725c5e420ef058275ae772b41775e2610000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000005d0fadb1c0000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000002b591e99afe9f32eaa6214f7b7629768c40eeb39000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000"); + auto decoded = decodeCall(call, abi); + + ASSERT_TRUE(decoded.has_value()); + nlohmann::json decodedJson = nlohmann::json::parse(decoded.value()); + EXPECT_EQ(decodedJson, expected); +} + +TEST(ContractCall, TupleNested) { + auto abiPath = TESTS_ROOT + "/chains/Ethereum/Data/tuple_nested.json"; + auto decodedPath = TESTS_ROOT + "/chains/Ethereum/Data/tuple_nested_decoded.json"; + auto abi = load_json_str(abiPath); + auto expected = load_json(decodedPath); + + auto call = parse_hex( + "74b6ef0b" + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000002" + "0000000000000000000000000000000000000000000000000000000000000003" + "0000000000000000000000000000000000000000000000000000000000000004" + "0000000000000000000000000000000000000000000000000000000000000005" + "0000000000000000000000000000000000000000000000000000000000000001"); + auto decoded = decodeCall(call, abi); + ASSERT_TRUE(decoded.has_value()); + nlohmann::json decodedJson = nlohmann::json::parse(decoded.value()); + EXPECT_EQ(decodedJson, expected); +} + +TEST(ContractCall, TupleArray) { + auto abiPath = TESTS_ROOT + "/chains/Ethereum/Data/swap_v2.json"; + auto decodedPath = TESTS_ROOT + "/chains/Ethereum/Data/swap_v2_decoded.json"; + auto abi = load_json_str(abiPath); + auto expectedJson = load_json(decodedPath); + + auto call = parse_hex("846a1bc6000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000470de4df82000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000740000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000007c00000000000000000000000000000000000000000000000000000000000000820000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000099a58482bd75cbab83b27ec03ca68ff489b5788f00000000000000000000000000000000000000000000000000470de4df820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000099a58482bd75cbab83b27ec03ca68ff489b5788f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000003840651cb35000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000bebc44782c7db0a1a60cb6fe97d0b483032ff1c7000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470de4df8200000000000000000000000000000000000000000000000000000000298ce42936ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d66600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000762696e616e636500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a307863653136463639333735353230616230313337376365374238386635424138433438463844363636000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000000072000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000ac000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000000000000000000000000000000000000000000010000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb1400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf3890000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000640000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7044000000000000000000000000000000000000000000000000000029a23529cf68000000000000000000000000000000000000000000005af4f3f913bd553d03b900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf30000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000100000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf38900000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000001f40000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7045000000000000000000000000000000000000000000005b527785e694f805bdd300000000000000000000000000000000000000000000005f935a1fa5c4a6ec61000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000242e1a7d4d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + auto decoded = decodeCall(call, abi); + ASSERT_TRUE(decoded.has_value()); + + nlohmann::json parsedJson = nlohmann::json::parse(decoded.value()); + EXPECT_EQ(parsedJson, expectedJson); +} + +} // namespace TW::Ethereum::ABI::tests diff --git a/tests/Ethereum/Data/1inch.json b/tests/chains/Ethereum/Data/1inch.json similarity index 100% rename from tests/Ethereum/Data/1inch.json rename to tests/chains/Ethereum/Data/1inch.json diff --git a/tests/chains/Ethereum/Data/1inch_decoded.json b/tests/chains/Ethereum/Data/1inch_decoded.json new file mode 100644 index 00000000000..a9f8a63895e --- /dev/null +++ b/tests/chains/Ethereum/Data/1inch_decoded.json @@ -0,0 +1,61 @@ +{ + "function": "swap(address,(address,address,address,address,uint256,uint256,uint256,bytes),bytes)", + "inputs": [ + { + "name": "caller", + "type": "address", + "value": "0x27239549DD40E1D60F5B80B0C4196923745B1FD2" + }, + { + "components": [ + { + "name": "srcToken", + "type": "address", + "value": "0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39" + }, + { + "name": "dstToken", + "type": "address", + "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + }, + { + "name": "srcReceiver", + "type": "address", + "value": "0x27239549DD40E1D60F5B80B0C4196923745B1FD2" + }, + { + "name": "dstReceiver", + "type": "address", + "value": "0x1611C227725c5E420Ef058275AE772b41775e261" + }, + { + "name": "amount", + "type": "uint256", + "value": "6395120000000" + }, + { + "name": "minReturnAmount", + "type": "uint256", + "value": "24748356058" + }, + { + "name": "flags", + "type": "uint256", + "value": "4" + }, + { + "name": "permit", + "type": "bytes", + "value": "0x" + } + ], + "name": "desc", + "type": "tuple" + }, + { + "name": "data", + "type": "bytes", + "value": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002080000000000000000000000069d91b94f0aaf8e8a2586909fa77a5c2c89818d50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000104128acb080000000000000000000000001611c227725c5e420ef058275ae772b41775e2610000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000005d0fadb1c0000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000002b591e99afe9f32eaa6214f7b7629768c40eeb39000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000" + } + ] +} \ No newline at end of file diff --git a/tests/chains/Ethereum/Data/custom.json b/tests/chains/Ethereum/Data/custom.json new file mode 100644 index 00000000000..711a8c86217 --- /dev/null +++ b/tests/chains/Ethereum/Data/custom.json @@ -0,0 +1,20 @@ +{ + "ec37a4a0": { + "constant": false, + "inputs": [{ + "name": "name", + "type": "string" + }, { + "name": "age", + "type": "uint" + }, { + "name": "height", + "type": "int32" + }], + "name": "setName", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +} \ No newline at end of file diff --git a/tests/chains/Ethereum/Data/eip712_cryptofights.json b/tests/chains/Ethereum/Data/eip712_cryptofights.json new file mode 100644 index 00000000000..563c8eaaa37 --- /dev/null +++ b/tests/chains/Ethereum/Data/eip712_cryptofights.json @@ -0,0 +1,83 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + }, + { + "name": "salt", + "type": "bytes32" + } + ], + "Trade": [ + { + "name": "nonce", + "type": "bytes32" + }, + { + "name": "firstParty", + "type": "address" + }, + { + "name": "askingId", + "type": "uint256" + }, + { + "name": "askingQty", + "type": "uint256" + }, + { + "name": "offeringId", + "type": "uint256" + }, + { + "name": "offeringQty", + "type": "uint256" + }, + { + "name": "maxFee", + "type": "uint256" + }, + { + "name": "secondParty", + "type": "address" + }, + { + "name": "count", + "type": "uint8" + } + ] + }, + "domain": { + "name": "CryptoFights Trading", + "version": "1", + "chainId": 1, + "verifyingContract": "0xdc45529aC0FA3185f79A005e57deF64F600c4e97", + "salt": "0x0" + }, + "primaryType": "Trade", + "message": { + "count": 1, + "offeringQty": 1, + "askingQty": 2, + "nonce": "0xcfe49aa546453df3f2e768a97204a3268cef7c27df53cc2f2d47385cfeaf", + "firstParty": "0xC36edF48e21cf395B206352A1819DE658fD7f988", + "askingId": "0x0000000000000000000000000000000000000000000000000000000000000000", + "offeringId": "0x0000000000000000000000000000000000000000000000000000000000000000", + "maxFee": "1000000000000000000", + "secondParty": "0x0000000000000000000000000000000000000000" + } +} \ No newline at end of file diff --git a/tests/Ethereum/Data/eip712_emptyArray.json b/tests/chains/Ethereum/Data/eip712_emptyArray.json similarity index 100% rename from tests/Ethereum/Data/eip712_emptyArray.json rename to tests/chains/Ethereum/Data/eip712_emptyArray.json diff --git a/tests/Ethereum/Data/eip712_emptyString.json b/tests/chains/Ethereum/Data/eip712_emptyString.json similarity index 100% rename from tests/Ethereum/Data/eip712_emptyString.json rename to tests/chains/Ethereum/Data/eip712_emptyString.json diff --git a/tests/chains/Ethereum/Data/eip712_greenfield.json b/tests/chains/Ethereum/Data/eip712_greenfield.json new file mode 100644 index 00000000000..31382ae06ab --- /dev/null +++ b/tests/chains/Ethereum/Data/eip712_greenfield.json @@ -0,0 +1,149 @@ +{ + "types": { + "Coin": [ + { + "name": "amount", + "type": "uint256" + }, + { + "name": "denom", + "type": "string" + } + ], + "EIP712Domain": [ + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "salt", + "type": "string" + }, + { + "name": "verifyingContract", + "type": "string" + }, + { + "name": "version", + "type": "string" + } + ], + "Fee": [ + { + "name": "amount", + "type": "Coin[]" + }, + { + "name": "gas_limit", + "type": "uint256" + }, + { + "name": "granter", + "type": "string" + }, + { + "name": "payer", + "type": "string" + } + ], + "Msg1": [ + { + "name": "amount", + "type": "TypeMsg1Amount[]" + }, + { + "name": "from_address", + "type": "string" + }, + { + "name": "to_address", + "type": "string" + }, + { + "name": "type", + "type": "string" + } + ], + "Tx": [ + { + "name": "account_number", + "type": "uint256" + }, + { + "name": "chain_id", + "type": "uint256" + }, + { + "name": "fee", + "type": "Fee" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "msg1", + "type": "Msg1" + }, + { + "name": "sequence", + "type": "uint256" + }, + { + "name": "timeout_height", + "type": "uint256" + } + ], + "TypeMsg1Amount": [ + { + "name": "amount", + "type": "string" + }, + { + "name": "denom", + "type": "string" + } + ] + }, + "primaryType": "Tx", + "domain": { + "name": "Greenfield Tx", + "version": "1.0.0", + "chainId": "0x15e0", + "verifyingContract": "greenfield", + "salt": "0" + }, + "message": { + "sequence": "2", + "account_number": "15560", + "chain_id": "5600", + "memo": "", + "fee": { + "amount": [ + { + "amount": "2000000000000000", + "denom": "BNB" + } + ], + "gas_limit": "200000", + "payer": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1", + "granter": "" + }, + "timeout_height": "0", + "msg1": { + "from_address": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1", + "to_address": "0x280b27f3676db1C4475EE10F75D510Eb527fd155", + "amount": [ + { + "amount": "1000000000000000", + "denom": "BNB" + } + ], + "type": "/cosmos.bank.v1beta1.MsgSend" + } + } +} \ No newline at end of file diff --git a/tests/Ethereum/Data/eip712_rarible.json b/tests/chains/Ethereum/Data/eip712_rarible.json similarity index 100% rename from tests/Ethereum/Data/eip712_rarible.json rename to tests/chains/Ethereum/Data/eip712_rarible.json diff --git a/tests/Ethereum/Data/eip712_snapshot_v4.json b/tests/chains/Ethereum/Data/eip712_snapshot_v4.json similarity index 100% rename from tests/Ethereum/Data/eip712_snapshot_v4.json rename to tests/chains/Ethereum/Data/eip712_snapshot_v4.json diff --git a/tests/Ethereum/Data/eip712_walletconnect.json b/tests/chains/Ethereum/Data/eip712_walletconnect.json similarity index 100% rename from tests/Ethereum/Data/eip712_walletconnect.json rename to tests/chains/Ethereum/Data/eip712_walletconnect.json diff --git a/tests/Ethereum/Data/ens.json b/tests/chains/Ethereum/Data/ens.json similarity index 100% rename from tests/Ethereum/Data/ens.json rename to tests/chains/Ethereum/Data/ens.json diff --git a/tests/Ethereum/Data/erc20.json b/tests/chains/Ethereum/Data/erc20.json similarity index 100% rename from tests/Ethereum/Data/erc20.json rename to tests/chains/Ethereum/Data/erc20.json diff --git a/tests/Ethereum/Data/erc721.json b/tests/chains/Ethereum/Data/erc721.json similarity index 100% rename from tests/Ethereum/Data/erc721.json rename to tests/chains/Ethereum/Data/erc721.json diff --git a/tests/Ethereum/Data/eth_feeHistory.json b/tests/chains/Ethereum/Data/eth_feeHistory.json similarity index 100% rename from tests/Ethereum/Data/eth_feeHistory.json rename to tests/chains/Ethereum/Data/eth_feeHistory.json diff --git a/tests/Ethereum/Data/eth_feeHistory2.json b/tests/chains/Ethereum/Data/eth_feeHistory2.json similarity index 100% rename from tests/Ethereum/Data/eth_feeHistory2.json rename to tests/chains/Ethereum/Data/eth_feeHistory2.json diff --git a/tests/Ethereum/Data/eth_feeHistory3.json b/tests/chains/Ethereum/Data/eth_feeHistory3.json similarity index 100% rename from tests/Ethereum/Data/eth_feeHistory3.json rename to tests/chains/Ethereum/Data/eth_feeHistory3.json diff --git a/tests/Ethereum/Data/eth_feeHistory4.json b/tests/chains/Ethereum/Data/eth_feeHistory4.json similarity index 100% rename from tests/Ethereum/Data/eth_feeHistory4.json rename to tests/chains/Ethereum/Data/eth_feeHistory4.json diff --git a/tests/Ethereum/Data/getAmountsOut.json b/tests/chains/Ethereum/Data/getAmountsOut.json similarity index 100% rename from tests/Ethereum/Data/getAmountsOut.json rename to tests/chains/Ethereum/Data/getAmountsOut.json diff --git a/tests/Ethereum/Data/kyber_proxy.json b/tests/chains/Ethereum/Data/kyber_proxy.json similarity index 100% rename from tests/Ethereum/Data/kyber_proxy.json rename to tests/chains/Ethereum/Data/kyber_proxy.json diff --git a/tests/chains/Ethereum/Data/seaport_712.json b/tests/chains/Ethereum/Data/seaport_712.json new file mode 100644 index 00000000000..3a427db3584 --- /dev/null +++ b/tests/chains/Ethereum/Data/seaport_712.json @@ -0,0 +1,84 @@ +{ + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "OrderComponents": [ + { "name": "offerer", "type": "address" }, + { "name": "zone", "type": "address" }, + { "name": "offer", "type": "OfferItem[]" }, + { "name": "consideration", "type": "ConsiderationItem[]" }, + { "name": "orderType", "type": "uint8" }, + { "name": "startTime", "type": "uint256" }, + { "name": "endTime", "type": "uint256" }, + { "name": "zoneHash", "type": "bytes32" }, + { "name": "salt", "type": "uint256" }, + { "name": "conduitKey", "type": "bytes32" }, + { "name": "counter", "type": "uint256" } + ], + "OfferItem": [ + { "name": "itemType", "type": "uint8" }, + { "name": "token", "type": "address" }, + { "name": "identifierOrCriteria", "type": "uint256" }, + { "name": "startAmount", "type": "uint256" }, + { "name": "endAmount", "type": "uint256" } + ], + "ConsiderationItem": [ + { "name": "itemType", "type": "uint8" }, + { "name": "token", "type": "address" }, + { "name": "identifierOrCriteria", "type": "uint256" }, + { "name": "startAmount", "type": "uint256" }, + { "name": "endAmount", "type": "uint256" }, + { "name": "recipient", "type": "address" } + ] + }, + "primaryType": "OrderComponents", + "domain": { + "name": "Seaport", + "version": "1.1", + "chainId": "1", + "verifyingContract": "0x00000000006c3852cbEf3e08E8dF289169EdE581" + }, + "message": { + "offerer": "0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1", + "offer": [ + { + "itemType": "2", + "token": "0x3F53082981815Ed8142384EDB1311025cA750Ef1", + "identifierOrCriteria": "134", + "startAmount": "1", + "endAmount": "1" + } + ], + "orderType": "2", + "consideration": [ + { + "itemType": "0", + "token": "0x0000000000000000000000000000000000000000", + "identifierOrCriteria": "0", + "startAmount": "975000000000000000", + "endAmount": "975000000000000000", + "recipient": "0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1" + }, + { + "itemType": "0", + "token": "0x0000000000000000000000000000000000000000", + "identifierOrCriteria": "0", + "startAmount": "25000000000000000", + "endAmount": "25000000000000000", + "recipient": "0x8De9C5A032463C561423387a9648c5C7BCC5BC90" + } + ], + "startTime": "1655450129", + "endTime": "1658042129", + "zone": "0x004C00500000aD104D7DBd00e3ae0A5C00560C00", + "zoneHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "salt": "795459960395409", + "conduitKey": "0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000", + "totalOriginalConsiderationItems": "2", + "counter": "0" + } +} diff --git a/tests/chains/Ethereum/Data/swap_v2.json b/tests/chains/Ethereum/Data/swap_v2.json new file mode 100644 index 00000000000..3651cd7b1ac --- /dev/null +++ b/tests/chains/Ethereum/Data/swap_v2.json @@ -0,0 +1,70 @@ +{ + "846a1bc6":{ + "inputs":[ + { + "name":"token", + "type":"address" + }, + { + "name":"amount", + "type":"uint256" + }, + { + "components":[ + { + "name":"callType", + "type":"uint8" + }, + { + "name":"target", + "type":"address" + }, + { + "name":"value", + "type":"uint256" + }, + { + "name":"callData", + "type":"bytes" + }, + { + "name":"payload", + "type":"bytes" + } + ], + "name":"calls", + "type":"tuple[]" + }, + { + "name":"bridgedTokenSymbol", + "type":"string" + }, + { + "name":"destinationChain", + "type":"string" + }, + { + "name":"destinationAddress", + "type":"string" + }, + { + "name":"payload", + "type":"bytes" + }, + { + "name":"gasRefundRecipient", + "type":"address" + }, + { + "name":"enableExpress", + "type":"bool" + } + ], + "name":"callBridgeCall", + "outputs":[ + + ], + "stateMutability":"nonpayable", + "type":"function" + } +} \ No newline at end of file diff --git a/tests/chains/Ethereum/Data/swap_v2_decoded.json b/tests/chains/Ethereum/Data/swap_v2_decoded.json new file mode 100644 index 00000000000..be3dab098ec --- /dev/null +++ b/tests/chains/Ethereum/Data/swap_v2_decoded.json @@ -0,0 +1,105 @@ +{ + "function": "callBridgeCall(address,uint256,(uint8,address,uint256,bytes,bytes)[],string,string,string,bytes,address,bool)", + "inputs": [ + { + "name": "token", + "type": "address", + "value": "0xdAC17F958D2ee523a2206206994597C13D831ec7" + }, + { + "name": "amount", + "type": "uint256", + "value": "20000000000000000" + }, + { + "components": [ + [ + { + "name": "callType", + "type": "uint8", + "value": "0" + }, + { + "name": "target", + "type": "address", + "value": "0xdAC17F958D2ee523a2206206994597C13D831ec7" + }, + { + "name": "value", + "type": "uint256", + "value": "0" + }, + { + "name": "callData", + "type": "bytes", + "value": "0x095ea7b300000000000000000000000099a58482bd75cbab83b27ec03ca68ff489b5788f00000000000000000000000000000000000000000000000000470de4df820000" + }, + { + "name": "payload", + "type": "bytes", + "value": "0x" + } + ], + [ + { + "name": "callType", + "type": "uint8", + "value": "0" + }, + { + "name": "target", + "type": "address", + "value": "0x99a58482BD75cbab83b27EC03CA68fF489b5788f" + }, + { + "name": "value", + "type": "uint256", + "value": "0" + }, + { + "name": "callData", + "type": "bytes", + "value": "0x0651cb35000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000bebc44782c7db0a1a60cb6fe97d0b483032ff1c7000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470de4df8200000000000000000000000000000000000000000000000000000000298ce42936ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666" + }, + { + "name": "payload", + "type": "bytes", + "value": "0x" + } + ] + ], + "name": "calls", + "type": "tuple[]" + }, + { + "name": "bridgedTokenSymbol", + "type": "string", + "value": "USDC" + }, + { + "name": "destinationChain", + "type": "string", + "value": "binance" + }, + { + "name": "destinationAddress", + "type": "string", + "value": "0xce16F69375520ab01377ce7B88f5BA8C48F8D666" + }, + { + "name": "payload", + "type": "bytes", + "value": "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000000072000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000ac000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000000000000000000000000000000000000000000010000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb1400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf3890000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000640000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7044000000000000000000000000000000000000000000000000000029a23529cf68000000000000000000000000000000000000000000005af4f3f913bd553d03b900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf30000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000100000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf38900000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000001f40000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7045000000000000000000000000000000000000000000005b527785e694f805bdd300000000000000000000000000000000000000000000005f935a1fa5c4a6ec61000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000242e1a7d4d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + { + "name": "gasRefundRecipient", + "type": "address", + "value": "0xa140F413C63FBDA84E9008607E678258ffFbC76b" + }, + { + "name": "enableExpress", + "type": "bool", + "value": true + } + ] +} \ No newline at end of file diff --git a/tests/Ethereum/Data/tuple_nested.json b/tests/chains/Ethereum/Data/tuple_nested.json similarity index 100% rename from tests/Ethereum/Data/tuple_nested.json rename to tests/chains/Ethereum/Data/tuple_nested.json diff --git a/tests/chains/Ethereum/Data/tuple_nested_decoded.json b/tests/chains/Ethereum/Data/tuple_nested_decoded.json new file mode 100644 index 00000000000..806049882ad --- /dev/null +++ b/tests/chains/Ethereum/Data/tuple_nested_decoded.json @@ -0,0 +1,47 @@ +{ + "function": "nested_tuple(uint16,(uint16,(uint16,uint64),uint32),bool)", + "inputs": [ + { + "name": "param1", + "type": "uint16", + "value": "1" + }, + { + "components": [ + { + "name": "param21", + "type": "uint16", + "value": "2" + }, + { + "components": [ + { + "name": "param221", + "type": "uint16", + "value": "3" + }, + { + "name": "param222", + "type": "uint64", + "value": "4" + } + ], + "name": "param22", + "type": "tuple" + }, + { + "name": "param23", + "type": "uint32", + "value": "5" + } + ], + "name": "param2", + "type": "tuple" + }, + { + "name": "param3", + "type": "bool", + "value": true + } + ] +} \ No newline at end of file diff --git a/tests/Ethereum/Data/uniswap_router_v2.json b/tests/chains/Ethereum/Data/uniswap_router_v2.json similarity index 100% rename from tests/Ethereum/Data/uniswap_router_v2.json rename to tests/chains/Ethereum/Data/uniswap_router_v2.json diff --git a/tests/Ethereum/Data/zilliqa_data_tx.json b/tests/chains/Ethereum/Data/zilliqa_data_tx.json similarity index 100% rename from tests/Ethereum/Data/zilliqa_data_tx.json rename to tests/chains/Ethereum/Data/zilliqa_data_tx.json diff --git a/tests/chains/Ethereum/EIP1014Tests.cpp b/tests/chains/Ethereum/EIP1014Tests.cpp new file mode 100644 index 00000000000..6b53150ee4a --- /dev/null +++ b/tests/chains/Ethereum/EIP1014Tests.cpp @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include +#include +#include +#include +#include + +#include + +namespace TW::Ethereum::tests { + TEST(EthereumEip1014, Example0) { + // C++ + { + const std::string& from = "0x0000000000000000000000000000000000000000"; + const Data salt = parse_hex("0x0000000000000000000000000000000000000000000000000000000000000000"); + Data initCodeHash = Hash::keccak256(parse_hex("0x00")); + const auto& addressData = Ethereum::create2Address(from, salt, initCodeHash); + ASSERT_EQ(Ethereum::checksumed(Ethereum::Address(hexEncoded(addressData))), "0x4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38"); + } + } + + TEST(EthereumEip1014, Example1) { + // C++ + { + const std::string& from = "0xdeadbeef00000000000000000000000000000000"; + const Data salt = parse_hex("0x0000000000000000000000000000000000000000000000000000000000000000"); + Data initCodeHash = Hash::keccak256(parse_hex("0x00")); + const auto& addressData = Ethereum::create2Address(from, salt, initCodeHash); + ASSERT_EQ(Ethereum::checksumed(Ethereum::Address(hexEncoded(addressData))), "0xB928f69Bb1D91Cd65274e3c79d8986362984fDA3"); + } + } + + TEST(EthereumEip1014, Example2) { + const std::string& from = "0xdeadbeef00000000000000000000000000000000"; + const Data salt = parse_hex("0x000000000000000000000000feed000000000000000000000000000000000000"); + Data initCode = parse_hex("0x00"); + initCode.resize(32); + const auto& addressData = Ethereum::create2Address(from, salt, initCode); + ASSERT_EQ(Ethereum::checksumed(Ethereum::Address(hexEncoded(addressData))), "0x2DB27D1d6BE32C9abfA484BA3d591101881D4B9f"); + } + + TEST(EthereumEip1014, Example3) { + const std::string& from = "0x0000000000000000000000000000000000000000"; + const Data salt = parse_hex("0x0000000000000000000000000000000000000000000000000000000000000000"); + Data initCode = parse_hex("0xdeadbeef"); + initCode.resize(32); + const auto& addressData = Ethereum::create2Address(from, salt, initCode); + ASSERT_EQ(Ethereum::checksumed(Ethereum::Address(hexEncoded(addressData))), "0x219438aC82230Cb9A9C13Cd99D324fA1d66CF018"); + } + + TEST(EthereumEip1014, Example4) { + const std::string& from = "0x00000000000000000000000000000000deadbeef"; + const Data salt = parse_hex("0x00000000000000000000000000000000000000000000000000000000cafebabe"); + Data initCodeHash = Hash::keccak256(parse_hex("0xdeadbeef")); + const auto& addressData = Ethereum::create2Address(from, salt, initCodeHash); + ASSERT_EQ(Ethereum::checksumed(Ethereum::Address(hexEncoded(addressData))), "0x60f3f640a8508fC6a86d45DF051962668E1e8AC7"); + } + + TEST(EthereumEip1014, Example5) { + const std::string& from = "0x00000000000000000000000000000000deadbeef"; + const Data salt = parse_hex("0x00000000000000000000000000000000000000000000000000000000cafebabe"); + Data initCodeHash = Hash::keccak256(parse_hex("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); + const auto& addressData = Ethereum::create2Address(from, salt, initCodeHash); + ASSERT_EQ(Ethereum::checksumed(Ethereum::Address(hexEncoded(addressData))), "0x1d8bfDC5D46DC4f61D6b6115972536eBE6A8854C"); + } + + TEST(EthereumEip1014, Example6) { + const std::string& from = "0x0000000000000000000000000000000000000000"; + const Data salt = parse_hex("0x0000000000000000000000000000000000000000000000000000000000000000"); + Data initCodeHash = Hash::keccak256(parse_hex("0x")); + const auto& addressData = Ethereum::create2Address(from, salt, initCodeHash); + ASSERT_EQ(Ethereum::checksumed(Ethereum::Address(hexEncoded(addressData))), "0xE33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0"); + } + + TEST(EthereumEip1014, Example7) { + const std::string& from = "0x7EF2e0048f5bAeDe046f6BF797943daF4ED8CB47"; + const Data salt = parse_hex("0x0000000000000000000000000000000000000000000000000000000000000000"); + Data initCodeHash = Hash::keccak256(parse_hex("0x608060405260405162000c5138038062000c51833981810160405281019062000029919062000580565b6200003d828260006200004560201b60201c565b5050620007d7565b62000056836200008860201b60201c565b600082511180620000645750805b156200008357620000818383620000df60201b620000371760201c565b505b505050565b62000099816200011560201b60201c565b8073ffffffffffffffffffffffffffffffffffffffff167fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b60405160405180910390a250565b60606200010d838360405180606001604052806027815260200162000c2a60279139620001eb60201b60201c565b905092915050565b6200012b816200027d60201b620000641760201c565b6200016d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000164906200066d565b60405180910390fd5b80620001a77f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b620002a060201b620000871760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60606000808573ffffffffffffffffffffffffffffffffffffffff1685604051620002179190620006dc565b600060405180830381855af49150503d806000811462000254576040519150601f19603f3d011682016040523d82523d6000602084013e62000259565b606091505b50915091506200027286838387620002aa60201b60201c565b925050509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b606083156200031a5760008351036200031157620002ce856200027d60201b60201c565b62000310576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620003079062000745565b60405180910390fd5b5b8290506200032d565b6200032c83836200033560201b60201c565b5b949350505050565b600082511115620003495781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200037f9190620007b3565b60405180910390fd5b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620003c9826200039c565b9050919050565b620003db81620003bc565b8114620003e757600080fd5b50565b600081519050620003fb81620003d0565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b62000456826200040b565b810181811067ffffffffffffffff821117156200047857620004776200041c565b5b80604052505050565b60006200048d62000388565b90506200049b82826200044b565b919050565b600067ffffffffffffffff821115620004be57620004bd6200041c565b5b620004c9826200040b565b9050602081019050919050565b60005b83811015620004f6578082015181840152602081019050620004d9565b60008484015250505050565b6000620005196200051384620004a0565b62000481565b90508281526020810184848401111562000538576200053762000406565b5b62000545848285620004d6565b509392505050565b600082601f83011262000565576200056462000401565b5b81516200057784826020860162000502565b91505092915050565b600080604083850312156200059a576200059962000392565b5b6000620005aa85828601620003ea565b925050602083015167ffffffffffffffff811115620005ce57620005cd62000397565b5b620005dc858286016200054d565b9150509250929050565b600082825260208201905092915050565b7f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60008201527f6f74206120636f6e747261637400000000000000000000000000000000000000602082015250565b600062000655602d83620005e6565b91506200066282620005f7565b604082019050919050565b60006020820190508181036000830152620006888162000646565b9050919050565b600081519050919050565b600081905092915050565b6000620006b2826200068f565b620006be81856200069a565b9350620006d0818560208601620004d6565b80840191505092915050565b6000620006ea8284620006a5565b915081905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b60006200072d601d83620005e6565b91506200073a82620006f5565b602082019050919050565b6000602082019050818103600083015262000760816200071e565b9050919050565b600081519050919050565b60006200077f8262000767565b6200078b8185620005e6565b93506200079d818560208601620004d6565b620007a8816200040b565b840191505092915050565b60006020820190508181036000830152620007cf818462000772565b905092915050565b61044380620007e76000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b610025610091565b610035610030610093565b6100a2565b565b606061005c83836040518060600160405280602781526020016103e7602791396100c8565b905092915050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b565b600061009d61014e565b905090565b3660008037600080366000845af43d6000803e80600081146100c3573d6000f35b3d6000fd5b60606000808573ffffffffffffffffffffffffffffffffffffffff16856040516100f291906102db565b600060405180830381855af49150503d806000811461012d576040519150601f19603f3d011682016040523d82523d6000602084013e610132565b606091505b5091509150610143868383876101a5565b925050509392505050565b600061017c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b610087565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606083156102075760008351036101ff576101bf85610064565b6101fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101f59061034f565b60405180910390fd5b5b829050610212565b610211838361021a565b5b949350505050565b60008251111561022d5781518083602001fd5b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161026191906103c4565b60405180910390fd5b600081519050919050565b600081905092915050565b60005b8381101561029e578082015181840152602081019050610283565b60008484015250505050565b60006102b58261026a565b6102bf8185610275565b93506102cf818560208601610280565b80840191505092915050565b60006102e782846102aa565b915081905092915050565b600082825260208201905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b6000610339601d836102f2565b915061034482610303565b602082019050919050565b600060208201905081810360008301526103688161032c565b9050919050565b600081519050919050565b6000601f19601f8301169050919050565b60006103968261036f565b6103a081856102f2565b93506103b0818560208601610280565b6103b98161037a565b840191505092915050565b600060208201905081810360008301526103de818461038b565b90509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e57dd3eafc9985be746025b6d82d4f011b9a7bb3db56f9a1eb7eadfddd376b6064736f6c63430008110033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564000000000000000000000000d9ec9e840bb5df076dbbb488d01485058f421e5800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000024c4d66de8000000000000000000000000be8fa0112dcb7d21dc63645b633073651e19934800000000000000000000000000000000000000000000000000000000")); + const auto& addressData = Ethereum::create2Address(from, salt, initCodeHash); + ASSERT_EQ(Ethereum::checksumed(Ethereum::Address(hexEncoded(addressData))), "0x4455e5f0038795939c001aa4d296A45956C460AA"); + } +} diff --git a/tests/chains/Ethereum/EIP1967Tests.cpp b/tests/chains/Ethereum/EIP1967Tests.cpp new file mode 100644 index 00000000000..e0cd759e8f6 --- /dev/null +++ b/tests/chains/Ethereum/EIP1967Tests.cpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include +#include +#include +#include + +#include + +namespace TW::Ethereum::tests { + +TEST(EthereumEip1967, Example0) { + // C++ + { + const std::string& login_address = "0x5C9eb5D6a6C2c1B3EFc52255C0b356f116f6f66D"; + const Data data = parse_hex("0xc4d66de8000000000000000000000000a5a1dddef094095afb7b6e322de72961df2e1988"); + const Data initCode = Ethereum::getEIP1967ProxyInitCode(login_address, data); + ASSERT_EQ(hexEncoded(initCode), "0x608060405260405162000c5138038062000c51833981810160405281019062000029919062000580565b6200003d828260006200004560201b60201c565b5050620007d7565b62000056836200008860201b60201c565b600082511180620000645750805b156200008357620000818383620000df60201b620000371760201c565b505b505050565b62000099816200011560201b60201c565b8073ffffffffffffffffffffffffffffffffffffffff167fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b60405160405180910390a250565b60606200010d838360405180606001604052806027815260200162000c2a60279139620001eb60201b60201c565b905092915050565b6200012b816200027d60201b620000641760201c565b6200016d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000164906200066d565b60405180910390fd5b80620001a77f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b620002a060201b620000871760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60606000808573ffffffffffffffffffffffffffffffffffffffff1685604051620002179190620006dc565b600060405180830381855af49150503d806000811462000254576040519150601f19603f3d011682016040523d82523d6000602084013e62000259565b606091505b50915091506200027286838387620002aa60201b60201c565b925050509392505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b606083156200031a5760008351036200031157620002ce856200027d60201b60201c565b62000310576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620003079062000745565b60405180910390fd5b5b8290506200032d565b6200032c83836200033560201b60201c565b5b949350505050565b600082511115620003495781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200037f9190620007b3565b60405180910390fd5b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620003c9826200039c565b9050919050565b620003db81620003bc565b8114620003e757600080fd5b50565b600081519050620003fb81620003d0565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b62000456826200040b565b810181811067ffffffffffffffff821117156200047857620004776200041c565b5b80604052505050565b60006200048d62000388565b90506200049b82826200044b565b919050565b600067ffffffffffffffff821115620004be57620004bd6200041c565b5b620004c9826200040b565b9050602081019050919050565b60005b83811015620004f6578082015181840152602081019050620004d9565b60008484015250505050565b6000620005196200051384620004a0565b62000481565b90508281526020810184848401111562000538576200053762000406565b5b62000545848285620004d6565b509392505050565b600082601f83011262000565576200056462000401565b5b81516200057784826020860162000502565b91505092915050565b600080604083850312156200059a576200059962000392565b5b6000620005aa85828601620003ea565b925050602083015167ffffffffffffffff811115620005ce57620005cd62000397565b5b620005dc858286016200054d565b9150509250929050565b600082825260208201905092915050565b7f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60008201527f6f74206120636f6e747261637400000000000000000000000000000000000000602082015250565b600062000655602d83620005e6565b91506200066282620005f7565b604082019050919050565b60006020820190508181036000830152620006888162000646565b9050919050565b600081519050919050565b600081905092915050565b6000620006b2826200068f565b620006be81856200069a565b9350620006d0818560208601620004d6565b80840191505092915050565b6000620006ea8284620006a5565b915081905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b60006200072d601d83620005e6565b91506200073a82620006f5565b602082019050919050565b6000602082019050818103600083015262000760816200071e565b9050919050565b600081519050919050565b60006200077f8262000767565b6200078b8185620005e6565b93506200079d818560208601620004d6565b620007a8816200040b565b840191505092915050565b60006020820190508181036000830152620007cf818462000772565b905092915050565b61044380620007e76000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b610025610091565b610035610030610093565b6100a2565b565b606061005c83836040518060600160405280602781526020016103e7602791396100c8565b905092915050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000819050919050565b565b600061009d61014e565b905090565b3660008037600080366000845af43d6000803e80600081146100c3573d6000f35b3d6000fd5b60606000808573ffffffffffffffffffffffffffffffffffffffff16856040516100f291906102db565b600060405180830381855af49150503d806000811461012d576040519150601f19603f3d011682016040523d82523d6000602084013e610132565b606091505b5091509150610143868383876101a5565b925050509392505050565b600061017c7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b610087565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606083156102075760008351036101ff576101bf85610064565b6101fe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101f59061034f565b60405180910390fd5b5b829050610212565b610211838361021a565b5b949350505050565b60008251111561022d5781518083602001fd5b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161026191906103c4565b60405180910390fd5b600081519050919050565b600081905092915050565b60005b8381101561029e578082015181840152602081019050610283565b60008484015250505050565b60006102b58261026a565b6102bf8185610275565b93506102cf818560208601610280565b80840191505092915050565b60006102e782846102aa565b915081905092915050565b600082825260208201905092915050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b6000610339601d836102f2565b915061034482610303565b602082019050919050565b600060208201905081810360008301526103688161032c565b9050919050565b600081519050919050565b6000601f19601f8301169050919050565b60006103968261036f565b6103a081856102f2565b93506103b0818560208601610280565b6103b98161037a565b840191505092915050565b600060208201905081810360008301526103de818461038b565b90509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220e57dd3eafc9985be746025b6d82d4f011b9a7bb3db56f9a1eb7eadfddd376b6064736f6c63430008110033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c65640000000000000000000000005c9eb5d6a6c2c1b3efc52255c0b356f116f6f66d00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000024c4d66de8000000000000000000000000a5a1dddef094095afb7b6e322de72961df2e198800000000000000000000000000000000000000000000000000000000"); + } +} + +} diff --git a/tests/chains/Ethereum/EthereumMessageSignerTests.cpp b/tests/chains/Ethereum/EthereumMessageSignerTests.cpp new file mode 100644 index 00000000000..6165fe32e47 --- /dev/null +++ b/tests/chains/Ethereum/EthereumMessageSignerTests.cpp @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include +#include +#include +#include + +#include +#include + +extern std::string TESTS_ROOT; + +std::string load_file(const std::string& path) { + std::ifstream stream(path); + std::string content((std::istreambuf_iterator(stream)), (std::istreambuf_iterator())); + return content; +} + +namespace TW::Ethereum { + TEST(EthereumEip712, SignMessageAndVerifyLegacy) { + PrivateKey ethKey(parse_hex("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")); + auto msg = R"( + { + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"} + ], + "Person": [ + {"name": "name", "type": "string"}, + {"name": "wallet", "type": "address"} + ] + }, + "primaryType": "Person", + "domain": { + "name": "Ether Person", + "version": "1", + "chainId": 0, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "name": "Cow", + "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + } + })"; + auto signature = Ethereum::MessageSigner::signTypedData(ethKey, msg, MessageType::Legacy); + ASSERT_EQ(signature, "446434e4c34d6b7456e5f07a1b994b88bf85c057234c68d1e10c936b1c85706c4e19147c0ac3a983bc2d56ebfd7146f8b62bcea6114900fe8e7d7351f44bf3761c"); + auto pubKey = ethKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + ASSERT_TRUE(Ethereum::MessageSigner::verifyMessage(pubKey, msg, signature)); + } + + TEST(EthereumEip712, SignMessageAndVerifyEip155) { + PrivateKey ethKey(parse_hex("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")); + auto msg = R"( + { + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"} + ], + "Person": [ + {"name": "name", "type": "string"}, + {"name": "wallet", "type": "address"} + ] + }, + "primaryType": "Person", + "domain": { + "name": "Ether Person", + "version": "1", + "chainId": 0, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "name": "Cow", + "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + } + })"; + auto signature = Ethereum::MessageSigner::signTypedData(ethKey, msg, MessageType::Eip155, 0); + ASSERT_EQ(signature, "446434e4c34d6b7456e5f07a1b994b88bf85c057234c68d1e10c936b1c85706c4e19147c0ac3a983bc2d56ebfd7146f8b62bcea6114900fe8e7d7351f44bf37624"); + auto pubKey = ethKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + ASSERT_TRUE(Ethereum::MessageSigner::verifyMessage(pubKey, msg, signature)); + } + + TEST(EthereumEip712, SignMessageAndVerifyInvalidEip155) { + PrivateKey ethKey(parse_hex("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")); + auto msg = R"( + { + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"} + ], + "Person": [ + {"name": "name", "type": "string"}, + {"name": "wallet", "type": "address"} + ] + }, + "primaryType": "Person", + "domain": { + "name": "Ether Person", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "name": "Cow", + "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + } + })"; + auto signature = Ethereum::MessageSigner::signTypedData(ethKey, msg, MessageType::Eip155, 0); + ASSERT_EQ(signature, ""); + } + + TEST(EthereumEip191, SignMessageAndVerifyLegacy) { + PrivateKey ethKey(parse_hex("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")); + auto msg = "Foo"; + auto signature = Ethereum::MessageSigner::signMessage(ethKey, msg, MessageType::Legacy); + ASSERT_EQ(signature, "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be71101b"); + auto pubKey = ethKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + ASSERT_TRUE(Ethereum::MessageSigner::verifyMessage(pubKey, msg, signature)); + } + + TEST(EthereumEip191, SignMessageAndVerifyEip155) { + PrivateKey ethKey(parse_hex("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")); + auto msg = "Foo"; + auto signature = Ethereum::MessageSigner::signMessage(ethKey, msg, MessageType::Eip155, 0); + ASSERT_EQ(signature, "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be711023"); + auto pubKey = ethKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + ASSERT_TRUE(Ethereum::MessageSigner::verifyMessage(pubKey, msg, signature)); + } + + TEST(EthereumEip191, SignMessageAndVerifyImmutableX) { + PrivateKey ethKey(parse_hex("3b0a61f46fdae924007146eacb6db6642de7a5603ad843ec58e10331d89d4b84")); + auto msg = "Only sign this request if you’ve initiated an action with Immutable X.\n\nFor internal use:\nbd717ba31dca6e0f3f136f7c4197babce5f09a9f25176044c0b3112b1b6017a3"; + auto signature = Ethereum::MessageSigner::signMessage(ethKey, msg, MessageType::ImmutableX); + ASSERT_EQ(signature, "32cd5a58f3419fc5db672e3d57f76199b853eda0856d491b38f557b629b0a0814ace689412bf354a1af81126d2749207dffae8ae8845160f33948a6b787e17ee01"); + auto pubKey = ethKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + ASSERT_TRUE(Ethereum::MessageSigner::verifyMessage(pubKey, msg, signature)); + } + + TEST(TWEthereumMessageSigner, SignAndVerifyImmutableX) { + const auto privKeyData = "3b0a61f46fdae924007146eacb6db6642de7a5603ad843ec58e10331d89d4b84"; + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto message = STRING("Only sign this request if you’ve initiated an action with Immutable X.\n\nFor internal use:\nbd717ba31dca6e0f3f136f7c4197babce5f09a9f25176044c0b3112b1b6017a3"); + + const auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeEthereum)); + const auto signature = WRAPS(TWEthereumMessageSignerSignMessageImmutableX(privateKey.get(), message.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature.get())), "32cd5a58f3419fc5db672e3d57f76199b853eda0856d491b38f557b629b0a0814ace689412bf354a1af81126d2749207dffae8ae8845160f33948a6b787e17ee01"); + EXPECT_TRUE(TWEthereumMessageSignerVerifyMessage(pubKey.get(), message.get(), signature.get())); + } + + TEST(TWEthereumMessageSigner, SignAndVerifyLegacy) { + const auto privKeyData = "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"; + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto message = STRING("Foo"); + + const auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeEthereum)); + const auto signature = WRAPS(TWEthereumMessageSignerSignMessage(privateKey.get(), message.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature.get())), "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be71101b"); + EXPECT_TRUE(TWEthereumMessageSignerVerifyMessage(pubKey.get(), message.get(), signature.get())); + } + + TEST(TWEthereumMessageSigner, SignAndVerifyEip155) { + const auto privKeyData = "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"; + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto message = STRING("Foo"); + + const auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeEthereum)); + const auto signature = WRAPS(TWEthereumMessageSignerSignMessageEip155(privateKey.get(), message.get(), 0)); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature.get())), "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be711023"); + EXPECT_TRUE(TWEthereumMessageSignerVerifyMessage(pubKey.get(), message.get(), signature.get())); + } + + TEST(TWEthereumEip712, SignMessageAndVerifyLegacy) { + const auto privKeyData = "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"; + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + auto msg = STRING(R"( + { + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"} + ], + "Person": [ + {"name": "name", "type": "string"}, + {"name": "wallet", "type": "address"} + ] + }, + "primaryType": "Person", + "domain": { + "name": "Ether Person", + "version": "1", + "chainId": 0, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "name": "Cow", + "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + } + })"); + const auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeEthereum)); + const auto signature = WRAPS(TWEthereumMessageSignerSignTypedMessage(privateKey.get(), msg.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature.get())), "446434e4c34d6b7456e5f07a1b994b88bf85c057234c68d1e10c936b1c85706c4e19147c0ac3a983bc2d56ebfd7146f8b62bcea6114900fe8e7d7351f44bf3761c"); + EXPECT_TRUE(TWEthereumMessageSignerVerifyMessage(pubKey.get(), msg.get(), signature.get())); + } + + TEST(TWEthereumEip712, SignMessageAndVerifyEip155) { + const auto privKeyData = "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"; + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + auto msg = STRING(R"( + { + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"} + ], + "Person": [ + {"name": "name", "type": "string"}, + {"name": "wallet", "type": "address"} + ] + }, + "primaryType": "Person", + "domain": { + "name": "Ether Person", + "version": "1", + "chainId": 0, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "name": "Cow", + "wallet": "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + } + })"); + const auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeEthereum)); + const auto signature = WRAPS(TWEthereumMessageSignerSignTypedMessageEip155(privateKey.get(), msg.get(), 0)); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature.get())), "446434e4c34d6b7456e5f07a1b994b88bf85c057234c68d1e10c936b1c85706c4e19147c0ac3a983bc2d56ebfd7146f8b62bcea6114900fe8e7d7351f44bf37624"); + EXPECT_TRUE(TWEthereumMessageSignerVerifyMessage(pubKey.get(), msg.get(), signature.get())); + } + + // The test checks if extra types are ordered correctly. + // The typed message was used to sign a Greenfield transaction: + // https://greenfieldscan.com/tx/9F895CF2DD64FB1F428CEFCF2A6585A813C3540FC9FE1EF42DB1DA2CB1DF55AB + TEST(TWEthereumEip712, SignTypedMessageExtraTypesOrder) { + auto path = TESTS_ROOT + "/chains/Ethereum/Data/eip712_greenfield.json"; + auto typeData = load_file(path); + + const auto privKeyData = "9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"; + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + auto msg = STRING(typeData.c_str()); + auto expected = "cb3a4684a991014a387a04a85b59227ebb79567c2025addcb296b4ca856e9f810d3b526f2a0d0fad6ad1b126b3b9516f8b3be020a7cca9c03ce3cf47f4199b6d1b"; + const auto signature = WRAPS(TWEthereumMessageSignerSignTypedMessage(privateKey.get(), msg.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature.get())), expected); + } + + // Test `TWEthereumMessageSignerSignTypedMessageEip155` where `domain.chainId` is a base10 decimal string. + // Generated by using https://metamask.github.io/test-dapp/ + TEST(EthereumEip712, SignMessageAndVerifyEip155ChainIdString) { + PrivateKey ethKey(parse_hex("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0")); + // 5600 + auto chainId = 0x15e0; + auto msg = R"( + { + "domain": { + "chainId": "5600", + "name": "Ether Mail", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + "version": "1" + }, + "message": { + "contents": "Hello, Bob!", + "from": { + "name": "Cow", + "wallets": [ + "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" + ] + }, + "to": [ + { + "name": "Bob", + "wallets": [ + "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", + "0xB0B0b0b0b0b0B000000000000000000000000000" + ] + } + ] + }, + "primaryType": "Mail", + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Group": [ + { + "name": "name", + "type": "string" + }, + { + "name": "members", + "type": "Person[]" + } + ], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Person[]" + }, + { + "name": "contents", + "type": "string" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "wallets", + "type": "address[]" + } + ] + } + })"; + auto signature = Ethereum::MessageSigner::signTypedData(ethKey, msg, MessageType::Eip155, chainId); + ASSERT_EQ(signature, "248b45acf2920a9cef00d3b469a875482b5f0e8ce16f6290212d395aaec7f3be0645d6a5cb6fcdfdca9ecefbadd4e77dae656124094ecc984c5fcb9cb4384b05e3"); + } +} diff --git a/tests/chains/Ethereum/TWAnySignerTests.cpp b/tests/chains/Ethereum/TWAnySignerTests.cpp new file mode 100644 index 00000000000..f1ffacc0a1e --- /dev/null +++ b/tests/chains/Ethereum/TWAnySignerTests.cpp @@ -0,0 +1,584 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include "HexCoding.h" +#include "uint256.h" +#include "proto/Ethereum.pb.h" +#include "Ethereum/ABI/Function.h" +#include "PrivateKey.h" + +#include + +namespace TW::Ethereum { + +TEST(TWEthereumSigner, EmptyValue) { + auto str = std::string(""); + uint256_t zero = load(str); + + ASSERT_EQ(zero, uint256_t(0)); +} + +TEST(TWEthereumSigner, BigInt) { + // Check uint256_t loading + Data expectedData = {0x52, 0x08}; + auto value = uint256_t(21000); + auto loaded = load(expectedData); + ASSERT_EQ(loaded, value); + + // Check proto storing + Proto::SigningInput input; + auto storedData = store(value); + input.set_gas_limit(storedData.data(), storedData.size()); + ASSERT_EQ(hex(input.gas_limit()), hex(expectedData)); + + // Check proto loading + auto protoLoaded = load(input.gas_limit()); + ASSERT_EQ(protoLoaded, value); +} + +TEST(TWAnySignerEthereum, Sign) { + // from http://thetokenfactory.com/#/factory + // https://etherscan.io/tx/0x63879f20909a315bcffe629bc03b20e5bc65ba2a377bd7152e3b69c4bd4cd6cc + Proto::SigningInput input; + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(11)); + auto gasPrice = store(uint256_t(20000000000)); + auto gasLimit = store(uint256_t(1000000)); + auto data = parse_hex("0x60a060405260046060527f48302e31000000000000000000000000000000000000000000000000000000006080526006805460008290527f48302e310000000000000000000000000000000000000000000000000000000882556100b5907ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f602060026001841615610100026000190190931692909204601f01919091048101905b8082111561017957600081556001016100a1565b505060405161094b38038061094b83398101604052808051906020019091908051820191906020018051906020019091908051820191906020015050836000600050600033600160a060020a0316815260200190815260200160002060005081905550836002600050819055508260036000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061017d57805160ff19168380011785555b506101ad9291506100a1565b5090565b8280016001018555821561016d579182015b8281111561016d57825182600050559160200191906001019061018f565b50506004805460ff19168317905560058054825160008390527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0602060026001851615610100026000190190941693909304601f90810184900482019386019083901061022d57805160ff19168380011785555b5061025d9291506100a1565b82800160010185558215610221579182015b8281111561022157825182600050559160200191906001019061023f565b5050505050506106da806102716000396000f36060604052361561008d5760e060020a600035046306fdde038114610095578063095ea7b3146100f357806318160ddd1461016857806323b872dd14610171578063313ce5671461025c57806354fd4d501461026857806370a08231146102c657806395d89b41146102f4578063a9059cbb14610352578063cae9ca51146103f7578063dd62ed3e146105be575b6105f2610002565b6040805160038054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03908116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b6102e260025481565b610662600435602435604435600160a060020a0383166000908152602081905260408120548290108015906101c4575060016020908152604080832033600160a060020a03168452909152812054829010155b80156101d05750600082115b156106bf57600160a060020a0383811660008181526020818152604080832080548801905588851680845281842080548990039055600183528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016106c3565b61067660045460ff1681565b6040805160068054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b600160a060020a03600435166000908152602081905260409020545b60408051918252519081900360200190f35b6105f46005805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03166000908152602081905260408120548290108015906103845750600082115b156106ca5733600160a060020a0390811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a3506001610162565b604080516020604435600481810135601f810184900484028501840190955284845261066294813594602480359593946064949293910191819084018382808284375094965050505050505033600160a060020a03908116600081815260016020908152604080832094881680845294825280832087905580518781529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a383600160a060020a031660405180807f72656365697665417070726f76616c28616464726573732c75696e743235362c81526020017f616464726573732c627974657329000000000000000000000000000000000000815260200150602e019050604051809103902060e060020a9004338530866040518560e060020a0281526004018085600160a060020a0316815260200184815260200183600160a060020a031681526020018280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156105965780820380516001836020036101000a031916815260200191505b509450505050506000604051808303816000876161da5a03f19250505015156106d257610002565b6102e2600435602435600160a060020a03828116600090815260016020908152604080832093851683529290522054610162565b005b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156106545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080519115158252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b81548152906001019060200180831161069a57829003601f168201915b505050505081565b5060005b9392505050565b506000610162565b5060016106c35600000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000754204275636b73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003544f540000000000000000000000000000000000000000000000000000000000"); + auto key = parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + // tx_mode not set, Legacy is the default + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_private_key(key.data(), key.size()); + auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); + transfer.set_data(data.data(), data.size()); + + std::string expected = "f90a9e0b8504a817c800830f42408080b90a4b60a060405260046060527f48302e31000000000000000000000000000000000000000000000000000000006080526006805460008290527f48302e310000000000000000000000000000000000000000000000000000000882556100b5907ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f602060026001841615610100026000190190931692909204601f01919091048101905b8082111561017957600081556001016100a1565b505060405161094b38038061094b83398101604052808051906020019091908051820191906020018051906020019091908051820191906020015050836000600050600033600160a060020a0316815260200190815260200160002060005081905550836002600050819055508260036000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061017d57805160ff19168380011785555b506101ad9291506100a1565b5090565b8280016001018555821561016d579182015b8281111561016d57825182600050559160200191906001019061018f565b50506004805460ff19168317905560058054825160008390527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0602060026001851615610100026000190190941693909304601f90810184900482019386019083901061022d57805160ff19168380011785555b5061025d9291506100a1565b82800160010185558215610221579182015b8281111561022157825182600050559160200191906001019061023f565b5050505050506106da806102716000396000f36060604052361561008d5760e060020a600035046306fdde038114610095578063095ea7b3146100f357806318160ddd1461016857806323b872dd14610171578063313ce5671461025c57806354fd4d501461026857806370a08231146102c657806395d89b41146102f4578063a9059cbb14610352578063cae9ca51146103f7578063dd62ed3e146105be575b6105f2610002565b6040805160038054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03908116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b6102e260025481565b610662600435602435604435600160a060020a0383166000908152602081905260408120548290108015906101c4575060016020908152604080832033600160a060020a03168452909152812054829010155b80156101d05750600082115b156106bf57600160a060020a0383811660008181526020818152604080832080548801905588851680845281842080548990039055600183528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016106c3565b61067660045460ff1681565b6040805160068054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b600160a060020a03600435166000908152602081905260409020545b60408051918252519081900360200190f35b6105f46005805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03166000908152602081905260408120548290108015906103845750600082115b156106ca5733600160a060020a0390811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a3506001610162565b604080516020604435600481810135601f810184900484028501840190955284845261066294813594602480359593946064949293910191819084018382808284375094965050505050505033600160a060020a03908116600081815260016020908152604080832094881680845294825280832087905580518781529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a383600160a060020a031660405180807f72656365697665417070726f76616c28616464726573732c75696e743235362c81526020017f616464726573732c627974657329000000000000000000000000000000000000815260200150602e019050604051809103902060e060020a9004338530866040518560e060020a0281526004018085600160a060020a0316815260200184815260200183600160a060020a031681526020018280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156105965780820380516001836020036101000a031916815260200191505b509450505050506000604051808303816000876161da5a03f19250505015156106d257610002565b6102e2600435602435600160a060020a03828116600090815260016020908152604080832093851683529290522054610162565b005b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156106545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080519115158252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b81548152906001019060200180831161069a57829003601f168201915b505050505081565b5060005b9392505050565b506000610162565b5060016106c35600000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000754204275636b73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003544f54000000000000000000000000000000000000000000000000000000000026a042556c4f2a3f4e4e639cca524d1da70e60881417d4643e5382ed110a52719eafa0172f591a2a763d0bd6b13d042d8c5eb66e87f129c9dc77ada66b6041012db2b3"; + + { + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), expected); + ASSERT_EQ(hex(output.data()), hex(data)); + } +} + +TEST(TWAnySignerEthereum, SignERC20TransferAsERC20) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(0)); + auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 + auto gasLimit = store(uint256_t(78009)); // 130B9 + auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; + auto token = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI + auto amount = uint256_t(2000000000000000000); + auto amountData = store(amount); + auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); + + Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + // tx_mode not set, Legacy is the default + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_to_address(token); + input.set_private_key(key.data(), key.size()); + auto& erc20 = *input.mutable_transaction()->mutable_erc20_transfer(); + erc20.set_to(toAddress); + erc20.set_amount(amountData.data(), amountData.size()); + + // https://etherscan.io/tx/0x199a7829fc5149e49b452c2cab76d8fa5a9682fee6e4891b8acb697ac142513e + std::string expected = "f8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec8000025a0724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98cea0032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931"; + + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), expected); + + // expected payload + Data payload; + { + payload = ABI::Function::encodeFunctionCall("transfer", Ethereum::ABI::BaseParams{ + std::make_shared(toAddress), + std::make_shared(amount) + }).value(); + } + ASSERT_EQ(hex(output.data()), hex(payload)); + ASSERT_EQ(hex(output.data()), "a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000"); +} + +TEST(TWAnySignerEthereum, SignERC20TransferAsGenericContract) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(0)); + auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 + auto gasLimit = store(uint256_t(78009)); // 130B9 + auto toAddress = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI + // payload: transfer(0x5322b34c88ed0691971bf52a7047448f0f4efc84, 2000000000000000000) + auto data = parse_hex("0xa9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000"); + auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); + + Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + // tx_mode not set, Legacy is the default + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_to_address(toAddress); + input.set_private_key(key.data(), key.size()); + auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); + transfer.set_data(data.data(), data.size()); + + // https://etherscan.io/tx/0x199a7829fc5149e49b452c2cab76d8fa5a9682fee6e4891b8acb697ac142513e + std::string expected = "f8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec8000025a0724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98cea0032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931"; + + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), expected); + ASSERT_EQ(hex(output.data()), hex(data)); +} + +TEST(TWAnySignerEthereum, SignERC20TransferInvalidAddress) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(0)); + auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 + auto gasLimit = store(uint256_t(78009)); // 130B9 + auto invalidAddress = "0xdeadbeef"; + auto amount = store(uint256_t(2000000000000000000)); + auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); + + Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + // tx_mode not set, Legacy is the default + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_to_address(invalidAddress); + input.set_private_key(key.data(), key.size()); + auto& erc20 = *input.mutable_transaction()->mutable_erc20_transfer(); + erc20.set_to(invalidAddress); + erc20.set_amount(amount.data(), amount.size()); + + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), ""); +} + +TEST(TWAnySignerEthereum, SignERC20Approve) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(0)); + auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 + auto gasLimit = store(uint256_t(78009)); // 130B9 + auto spenderAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; + auto token = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI + auto amount = store(uint256_t(2000000000000000000)); + auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); + + Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + // tx_mode not set, Legacy is the default + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_to_address(token); + input.set_private_key(key.data(), key.size()); + auto& erc20 = *input.mutable_transaction()->mutable_erc20_approve(); + erc20.set_spender(spenderAddress); + erc20.set_amount(amount.data(), amount.size()); + + std::string expected = "f8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844095ea7b30000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec8000025a0d8136d66da1e0ba8c7208d5c4f143167f54b89a0fe2e23440653bcca28b34dc1a049222a79339f1a9e4641cb4ad805c49c225ae704299ffc10627bf41c035c464a"; + + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), expected); +} + +TEST(TWAnySignerEthereum, SignERC721Transfer) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(0)); + auto gasPrice = store(uint256_t(42000000000)); + auto gasLimit = store(uint256_t(78009)); + auto tokenContract = "0x4e45e92ed38f885d39a733c14f1817217a89d425"; + auto fromAddress = "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc"; + auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; + auto tokenId = parse_hex("23c47ee5"); + auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); + + Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + // tx_mode not set, Legacy is the default + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_to_address(tokenContract); + input.set_private_key(key.data(), key.size()); + auto& erc721 = *input.mutable_transaction()->mutable_erc721_transfer(); + erc721.set_from(fromAddress); + erc721.set_to(toAddress); + erc721.set_token_id(tokenId.data(), tokenId.size()); + + std::string expected = "f8ca808509c7652400830130b9944e45e92ed38f885d39a733c14f1817217a89d42580b86423b872dd000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee526a0d38440a4dc140a4100d301eb49fcc35b64439e27d1d8dd9b55823dca04e6e659a03b5f56a57feabc3406f123d6f8198cd7d7e2ced7e2d58d375f076952ecd9ce88"; + + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), expected); + ASSERT_EQ(hex(output.data()), "23b872dd000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee5"); +} + +TEST(TWAnySignerEthereum, SignERC1155Transfer) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(0)); + auto gasPrice = store(uint256_t(42000000000)); + auto gasLimit = store(uint256_t(78009)); + auto tokenContract = "0x4e45e92ed38f885d39a733c14f1817217a89d425"; + auto fromAddress = "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc"; + auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; + auto tokenId = parse_hex("23c47ee5"); + auto value = uint256_t(2000000000000000000); + auto valueData = store(value); + auto data = parse_hex("01020304"); + auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); + + Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + // tx_mode not set, Legacy is the default + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_to_address(tokenContract); + input.set_private_key(key.data(), key.size()); + auto& erc1155 = *input.mutable_transaction()->mutable_erc1155_transfer(); + erc1155.set_from(fromAddress); + erc1155.set_to(toAddress); + erc1155.set_token_id(tokenId.data(), tokenId.size()); + erc1155.set_value(valueData.data(), valueData.size()); + erc1155.set_data(data.data(), data.size()); + + std::string expected = "f9014a808509c7652400830130b9944e45e92ed38f885d39a733c14f1817217a89d42580b8e4f242432a000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee50000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000004010203040000000000000000000000000000000000000000000000000000000026a010315488201ac801ce346bffd1570de147615462d7e7db3cf08cf558465c6b79a06643943b24593bc3904a9fda63bb169881730994c973ab80f07d66a698064573"; + + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), expected); + + // expected payload + Data payload; + { + auto funcData = ABI::Function::encodeFunctionCall("safeTransferFrom", Ethereum::ABI::BaseParams{ + std::make_shared(fromAddress), + std::make_shared(toAddress), + std::make_shared(uint256_t(0x23c47ee5)), + std::make_shared(value), + std::make_shared(data)}); + payload = funcData.value(); + } + ASSERT_EQ(hex(output.data()), hex(payload)); + ASSERT_EQ(hex(output.data()), "f242432a000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee50000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000"); +} + +TEST(TWAnySignerEthereum, SignJSON) { + auto json = STRING(R"({"chainId":"AQ==","gasPrice":"1pOkAA==","gasLimit":"Ugg=","toAddress":"0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1","transaction":{"transfer":{"amount":"A0i8paFgAA=="}}})"); + auto key = DATA("17209af590a86462395d5881e60d11c7fa7d482cfb02b5a01b93c2eeef243543"); + auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeEthereum)); + + ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeEthereum)); + assertStringsEqual(result, "f86a8084d693a400825208947d8bf18c7ce84b3e175b339c4ca93aed1dd166f1870348bca5a160008025a0fe5802b49e04c6b1705088310e133605ed8b549811a18968ad409ea02ad79f21a05bf845646fb1e1b9365f63a7fd5eb5e984094e3ed35c3bed7361aebbcbf41f10"); +} + +TEST(TWAnySignerEthereum, PlanNotSupported) { + // Ethereum does not use plan(), call it nonetheless + Proto::SigningInput input; + auto inputData = input.SerializeAsString(); + auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t*)inputData.data(), inputData.size())); + auto outputTWData = WRAPD(TWAnySignerPlan(inputTWData.get(), TWCoinTypeEthereum)); + EXPECT_EQ(TWDataSize(outputTWData.get()), 0ul); +} + +TEST(TWAnySignerEthereum, SignERC1559Transfer_1442) { + auto chainId = store(uint256_t(3)); + auto nonce = store(uint256_t(6)); + auto gasLimit = store(uint256_t(21100)); + auto maxInclusionFeePerGas = store(uint256_t(2000000000)); + auto maxFeePerGas = store(uint256_t(3000000000)); + auto toAddress = "0xB9F5771C27664bF2282D98E09D7F50cEc7cB01a7"; + auto value = uint256_t(543210987654321); + auto valueData = store(value); + auto key = parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904"); + + Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_tx_mode(Proto::TransactionMode::Enveloped); // EIP1559 + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); + input.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); + input.set_to_address(toAddress); + input.set_private_key(key.data(), key.size()); + auto& transfer = *input.mutable_transaction()->mutable_transfer(); + transfer.set_amount(valueData.data(), valueData.size()); + transfer.set_data(Data().data(), 0); + + // https://ropsten.etherscan.io/tx/0x14429509307efebfdaa05227d84c147450d168c68539351fbc01ed87c916ab2e + std::string expected = "02f8710306847735940084b2d05e0082526c94b9f5771c27664bf2282d98e09d7f50cec7cb01a78701ee0c29f50cb180c080a092c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c64a06487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e58"; + + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + EXPECT_EQ(hex(output.encoded()), expected); + EXPECT_EQ(hex(output.v()), "00"); + EXPECT_EQ(hex(output.r()), "92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c64"); + EXPECT_EQ(hex(output.s()), "6487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e58"); + EXPECT_EQ(hex(output.data()), ""); +} + +TEST(TWAnySignerEthereum, SignERC20Transfer_1559) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(0)); + auto gasLimit = store(uint256_t(78009)); // 130B9 + auto maxInclusionFeePerGas = store(uint256_t(2000000000)); // 77359400 + auto maxFeePerGas = store(uint256_t(3000000000)); // B2D05E00 + auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; + auto token = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI + auto amount = uint256_t(2000000000000000000); + auto amountData = store(amount); + auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); + + Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_tx_mode(Proto::TransactionMode::Enveloped); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); + input.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); + input.set_to_address(token); + input.set_private_key(key.data(), key.size()); + auto& erc20 = *input.mutable_transaction()->mutable_erc20_transfer(); + erc20.set_to(toAddress); + erc20.set_amount(amountData.data(), amountData.size()); + + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), "02f8b00180847735940084b2d05e00830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000c080a0adfcfdf98d4ed35a8967a0c1d78b42adb7c5d831cf5a3272654ec8f8bcd7be2ea011641e065684f6aa476f4fd250aa46cd0b44eccdb0a6e1650d658d1998684cdf"); +} + +TEST(TWAnySignerEthereum, SignERC20Approve_1559) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(0)); + auto gasLimit = store(uint256_t(78009)); // 130B9 + auto maxInclusionFeePerGas = store(uint256_t(2000000000)); + auto maxFeePerGas = store(uint256_t(3000000000)); + auto spenderAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; + auto token = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI + auto amount = store(uint256_t(2000000000000000000)); + auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); + + Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_tx_mode(Proto::TransactionMode::Enveloped); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); + input.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); + input.set_to_address(token); + input.set_private_key(key.data(), key.size()); + auto& erc20 = *input.mutable_transaction()->mutable_erc20_approve(); + erc20.set_spender(spenderAddress); + erc20.set_amount(amount.data(), amount.size()); + + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), "02f8b00180847735940084b2d05e00830130b9946b175474e89094c44da98b954eedeac495271d0f80b844095ea7b30000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000c080a05a43dda3dc193480ee532a5ed67ba8fbd2e3afb9eee218f4fb955b415d592925a01300e5b5f51c8cd5bf80f018cea3fb347fae589e65355068ac44ffc996313c60"); +} + +TEST(TWAnySignerEthereum, SignERC721Transfer_1559) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(0)); + auto gasLimit = store(uint256_t(78009)); + auto maxInclusionFeePerGas = store(uint256_t(2000000000)); + auto maxFeePerGas = store(uint256_t(3000000000)); + auto tokenContract = "0x4e45e92ed38f885d39a733c14f1817217a89d425"; + auto fromAddress = "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc"; + auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; + auto tokenId = parse_hex("23c47ee5"); + auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); + + Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_tx_mode(Proto::TransactionMode::Enveloped); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); + input.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); + input.set_to_address(tokenContract); + input.set_private_key(key.data(), key.size()); + auto& erc721 = *input.mutable_transaction()->mutable_erc721_transfer(); + erc721.set_from(fromAddress); + erc721.set_to(toAddress); + erc721.set_token_id(tokenId.data(), tokenId.size()); + + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), "02f8d00180847735940084b2d05e00830130b9944e45e92ed38f885d39a733c14f1817217a89d42580b86423b872dd000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee5c080a0dbd591d1eac39bad62d7c158d5e1d55e7014d2218998f8980462e2f283f42d4aa05acadb904484a0fb5526a4c64b8addb8aac4f6548f90199e40eb787b79faed4a"); +} + +TEST(TWAnySignerEthereum, SignERC1155Transfer_1559) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(0)); + auto gasLimit = store(uint256_t(78009)); + auto maxInclusionFeePerGas = store(uint256_t(2000000000)); + auto maxFeePerGas = store(uint256_t(3000000000)); + auto tokenContract = "0x4e45e92ed38f885d39a733c14f1817217a89d425"; + auto fromAddress = "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc"; + auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; + auto tokenId = parse_hex("23c47ee5"); + auto value = uint256_t(2000000000000000000); + auto valueData = store(value); + auto data = parse_hex("01020304"); + auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); + + Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_tx_mode(Proto::TransactionMode::Enveloped); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); + input.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); + input.set_to_address(tokenContract); + input.set_private_key(key.data(), key.size()); + auto& erc1155 = *input.mutable_transaction()->mutable_erc1155_transfer(); + erc1155.set_from(fromAddress); + erc1155.set_to(toAddress); + erc1155.set_token_id(tokenId.data(), tokenId.size()); + erc1155.set_value(valueData.data(), valueData.size()); + erc1155.set_data(data.data(), data.size()); + + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), "02f901500180847735940084b2d05e00830130b9944e45e92ed38f885d39a733c14f1817217a89d42580b8e4f242432a000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee50000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000c080a0533df41dda5540c57257b7fe89c29cefff0155c333e063220df2bf9680fcc15aa036a844fd20de5a51de96ceaaf078558e87d86426a4a5d4b215ee1fd0fa397f8a"); +} + +TEST(TWAnySignerEthereum, StakeRocketPool) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(1)); + auto gasPrice = store(uint256_t(2002000000)); + auto gasLimit = store(uint256_t(205000)); + auto maxFeePerGas = store(uint256_t(27900000000)); + auto maxInclusionFeePerGas = store(uint256_t(1000000000)); + auto toAddress = "0x2cac916b2a963bf162f076c0a8a4a8200bcfbfb4"; + auto key = parse_hex("9f56448d33de406db1561aae15fce64bdf0e9706ff15c45d4409e8fcbfd1a498"); + const auto pk = PrivateKey(key); + + // 0.01 ETH + auto valueData = store(uint256_t(10000000000000000)); + + Data payload; + { + auto funcData = ABI::Function::encodeFunctionCall("deposit", Ethereum::ABI::BaseParams{ }); + payload = funcData.value(); + } + + + Proto::SigningInput input; + input.set_tx_mode(Proto::TransactionMode::Enveloped); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); + input.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); + + input.set_to_address(toAddress); + input.set_private_key(key.data(), key.size()); + + auto& transfer = *input.mutable_transaction()->mutable_transfer(); + transfer.set_amount(valueData.data(), valueData.size()); + transfer.set_data(payload.data(), payload.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + // https://etherscan.io/tx/0xfeba0c579f3e964fbc4eafa500e86891b9f4113735b1364edd4433d765506f1e + EXPECT_EQ(hex(output.r()), "fb39e5079d7a0598ec45785d73a06b91fe1db707b9c6a150c87ffce2492c66d6"); + EXPECT_EQ(hex(output.v()), "00"); + EXPECT_EQ(hex(output.s()), "7fbd43a6f4733b2b4f98ad1bc4678ea2615f5edf56ad91408337adec2f07c0ac"); + EXPECT_EQ(hex(output.encoded()), "02f8770101843b9aca0085067ef83700830320c8942cac916b2a963bf162f076c0a8a4a8200bcfbfb4872386f26fc1000084d0e30db0c080a0fb39e5079d7a0598ec45785d73a06b91fe1db707b9c6a150c87ffce2492c66d6a07fbd43a6f4733b2b4f98ad1bc4678ea2615f5edf56ad91408337adec2f07c0ac"); +} + +TEST(TWAnySignerEthereum, UnstakeRocketPool) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(3)); + auto gasPrice = store(uint256_t(2002000000)); + auto gasLimit = store(uint256_t(350000)); + auto maxFeePerGas = store(uint256_t(27900000000)); + auto maxInclusionFeePerGas = store(uint256_t(1000000000)); + auto toAddress = "0xae78736Cd615f374D3085123A210448E74Fc6393"; + auto key = parse_hex("9f56448d33de406db1561aae15fce64bdf0e9706ff15c45d4409e8fcbfd1a498"); + const auto pk = PrivateKey(key); + + auto valueData = store(uint256_t(0)); + + Data payload; + { + auto funcData = ABI::Function::encodeFunctionCall("burn", ABI::BaseParams{ + std::make_shared(uint256_t(0x21faa32ab2502b)) + }); + payload = funcData.value(); + } + + Proto::SigningInput input; + input.set_tx_mode(Proto::TransactionMode::Enveloped); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); + input.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); + + input.set_to_address(toAddress); + input.set_private_key(key.data(), key.size()); + + auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); + transfer.set_amount(valueData.data(), valueData.size()); + transfer.set_data(payload.data(), payload.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + // https://etherscan.io/tx/0x7fd3c0e9b8b309b4258baa7677c60f5e00e8db7b647fbe3a52adda25058a4b37 + EXPECT_EQ(hex(output.r()), "1fc6e94908107584357799e952b4e3fb87f088aeb66d7930a7015643f19c9e7f"); + EXPECT_EQ(hex(output.v()), "00"); + EXPECT_EQ(hex(output.s()), "2c56a0b70ff2e52bf374a3dcd404bc42317d5ca15d319f5e33665352eb48f06f"); + EXPECT_EQ(hex(output.encoded()), "02f8900103843b9aca0085067ef837008305573094ae78736cd615f374d3085123a210448e74fc639380a442966c680000000000000000000000000000000000000000000000000021faa32ab2502bc080a01fc6e94908107584357799e952b4e3fb87f088aeb66d7930a7015643f19c9e7fa02c56a0b70ff2e52bf374a3dcd404bc42317d5ca15d319f5e33665352eb48f06f"); +} + + +} // namespace TW::Ethereum diff --git a/tests/chains/Ethereum/TWCoinTypeTests.cpp b/tests/chains/Ethereum/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..4f205ff7a76 --- /dev/null +++ b/tests/chains/Ethereum/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWEthereumCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeEthereum)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x9edaf0f7d9c6629c31bbf0471fc07d696c73b566b93783f7e25d8d5d2b62fa4f")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeEthereum, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x5bb497e8d9fe26e92dd1be01e32076c8e024d167")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeEthereum, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeEthereum)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeEthereum)); + const auto chainId = WRAPS(TWCoinTypeChainId(TWCoinTypeEthereum)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeEthereum), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeEthereum)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeEthereum)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeEthereum)); + assertStringsEqual(symbol, "ETH"); + assertStringsEqual(txUrl, "https://etherscan.io/tx/0x9edaf0f7d9c6629c31bbf0471fc07d696c73b566b93783f7e25d8d5d2b62fa4f"); + assertStringsEqual(accUrl, "https://etherscan.io/address/0x5bb497e8d9fe26e92dd1be01e32076c8e024d167"); + assertStringsEqual(id, "ethereum"); + assertStringsEqual(name, "Ethereum"); + assertStringsEqual(chainId, "1"); +} diff --git a/tests/chains/Ethereum/TWEthereumAbiTests.cpp b/tests/chains/Ethereum/TWEthereumAbiTests.cpp new file mode 100644 index 00000000000..607c2c58193 --- /dev/null +++ b/tests/chains/Ethereum/TWEthereumAbiTests.cpp @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include + +#include "Data.h" +#include "HexCoding.h" +#include "proto/EthereumAbi.pb.h" +#include "uint256.h" + +#include "TestUtilities.h" +#include +#include + +namespace TW::Ethereum { + +TEST(TWEthereumAbi, FuncCreateEmpty) { + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("baz")).get()); + EXPECT_TRUE(func != nullptr); + + auto type = WRAPS(TWEthereumAbiFunctionGetType(func)); + std::string type2 = TWStringUTF8Bytes(type.get()); + EXPECT_EQ("baz()", type2); + + // delete + TWEthereumAbiFunctionDelete(func); +} + +TEST(TWEthereumAbi, FuncCreate1) { + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("baz")).get()); + EXPECT_TRUE(func != nullptr); + + auto p1index = TWEthereumAbiFunctionAddParamUInt64(func, 69, false); + EXPECT_EQ(0, p1index); + auto p2index = TWEthereumAbiFunctionAddParamUInt64(func, 9, true); + EXPECT_EQ(0, p2index); + // check back get value + auto p2val2 = TWEthereumAbiFunctionGetParamUInt64(func, p2index, true); + EXPECT_EQ(9ul, p2val2); + + auto type = WRAPS(TWEthereumAbiFunctionGetType(func)); + std::string type2 = TWStringUTF8Bytes(type.get()); + EXPECT_EQ("baz(uint64)", type2); + + // delete + TWEthereumAbiFunctionDelete(func); +} + +TEST(TWEthereumAbi, FuncCreate2) { + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("baz")).get()); + EXPECT_TRUE(func != nullptr); + + auto p1valStr = WRAPS(TWStringCreateWithUTF8Bytes("0045")); + auto p1val = WRAPD(TWDataCreateWithHexString(p1valStr.get())); + auto p1index = TWEthereumAbiFunctionAddParamUInt256(func, p1val.get(), false); + EXPECT_EQ(0, p1index); + + Data dummy = store(0); + auto p2index = TWEthereumAbiFunctionAddParamUInt256(func, &dummy, true); + EXPECT_EQ(0, p2index); + + // check back get value + auto p2val2 = WRAPD(TWEthereumAbiFunctionGetParamUInt256(func, p2index, true)); + Data p2val3 = data(TWDataBytes(p2val2.get()), TWDataSize(p2val2.get())); + EXPECT_EQ("00", hex(p2val3)); + + auto type = WRAPS(TWEthereumAbiFunctionGetType(func)); + EXPECT_EQ("baz(uint256)", std::string(TWStringUTF8Bytes(type.get()))); + + // delete + TWEthereumAbiFunctionDelete(func); +} + +TEST(TWEthereumAbi, EncodeFuncCase1) { + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("sam")).get()); + EXPECT_TRUE(func != nullptr); + + EXPECT_EQ(0, TWEthereumAbiFunctionAddParamBytes(func, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("64617665")).get())).get(), false)); + EXPECT_EQ(1, TWEthereumAbiFunctionAddParamBool(func, true, false)); + auto paramArrIdx = TWEthereumAbiFunctionAddParamArray(func, false); + EXPECT_EQ(2, paramArrIdx); + EXPECT_EQ(0, TWEthereumAbiFunctionAddInArrayParamUInt256(func, paramArrIdx, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("01")).get())).get())); + EXPECT_EQ(1, TWEthereumAbiFunctionAddInArrayParamUInt256(func, paramArrIdx, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("02")).get())).get())); + EXPECT_EQ(2, TWEthereumAbiFunctionAddInArrayParamUInt256(func, paramArrIdx, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("03")).get())).get())); + + auto type = WRAPS(TWEthereumAbiFunctionGetType(func)); + EXPECT_EQ("sam(bytes,bool,uint256[])", std::string(TWStringUTF8Bytes(type.get()))); + + auto encoded = WRAPD(TWEthereumAbiEncode(func)); + Data enc2 = data(TWDataBytes(encoded.get()), TWDataSize(encoded.get())); + EXPECT_EQ("a5643bf2" + "0000000000000000000000000000000000000000000000000000000000000060" + "0000000000000000000000000000000000000000000000000000000000000001" + "00000000000000000000000000000000000000000000000000000000000000a0" + "0000000000000000000000000000000000000000000000000000000000000004" + "6461766500000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000003" + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000002" + "0000000000000000000000000000000000000000000000000000000000000003", + hex(enc2)); + + // delete + TWEthereumAbiFunctionDelete(func); +} + +TEST(TWEthereumAbi, EncodeFuncCase2) { + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("f")).get()); + EXPECT_TRUE(func != nullptr); + + EXPECT_EQ(0, TWEthereumAbiFunctionAddParamUInt256(func, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get(), false)); + auto paramArrIdx = TWEthereumAbiFunctionAddParamArray(func, false); + EXPECT_EQ(1, paramArrIdx); + EXPECT_EQ(0, TWEthereumAbiFunctionAddInArrayParamUInt32(func, paramArrIdx, 0x456)); + EXPECT_EQ(1, TWEthereumAbiFunctionAddInArrayParamUInt32(func, paramArrIdx, 0x789)); + EXPECT_EQ(2, TWEthereumAbiFunctionAddParamBytesFix(func, 10, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("31323334353637383930")).get())).get(), false)); + EXPECT_EQ(3, TWEthereumAbiFunctionAddParamString(func, WRAPS(TWStringCreateWithUTF8Bytes("Hello, world!")).get(), false)); + + auto type = WRAPS(TWEthereumAbiFunctionGetType(func)); + EXPECT_EQ("f(uint256,uint32[],bytes10,string)", std::string(TWStringUTF8Bytes(type.get()))); + + auto encoded = WRAPD(TWEthereumAbiEncode(func)); + Data enc2 = data(TWDataBytes(encoded.get()), TWDataSize(encoded.get())); + EXPECT_EQ("47b941bf" + "0000000000000000000000000000000000000000000000000000000000000123" + "0000000000000000000000000000000000000000000000000000000000000080" + "3132333435363738393000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000e0" + "0000000000000000000000000000000000000000000000000000000000000002" + "0000000000000000000000000000000000000000000000000000000000000456" + "0000000000000000000000000000000000000000000000000000000000000789" + "000000000000000000000000000000000000000000000000000000000000000d" + "48656c6c6f2c20776f726c642100000000000000000000000000000000000000", + hex(enc2)); + + // delete + TWEthereumAbiFunctionDelete(func); +} + +TEST(TWEthereumAbi, EncodeFuncMonster) { + // Monster function with all parameters types + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("monster")).get()); + EXPECT_TRUE(func != nullptr); + + TWEthereumAbiFunctionAddParamUInt8(func, 1, false); + TWEthereumAbiFunctionAddParamUInt16(func, 2, false); + TWEthereumAbiFunctionAddParamUInt32(func, 3, false); + TWEthereumAbiFunctionAddParamUInt64(func, 4, false); + TWEthereumAbiFunctionAddParamUIntN(func, 168, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get(), false); + TWEthereumAbiFunctionAddParamUInt256(func, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get(), false); + TWEthereumAbiFunctionAddParamInt8(func, 1, false); + TWEthereumAbiFunctionAddParamInt16(func, -3, false); + TWEthereumAbiFunctionAddParamInt32(func, 3, false); + TWEthereumAbiFunctionAddParamInt64(func, 4, false); + TWEthereumAbiFunctionAddParamIntN(func, 168, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get(), false); + TWEthereumAbiFunctionAddParamInt256(func, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get(), false); + TWEthereumAbiFunctionAddParamBool(func, true, false); + TWEthereumAbiFunctionAddParamString(func, WRAPS(TWStringCreateWithUTF8Bytes("Hello, world!")).get(), false); + TWEthereumAbiFunctionAddParamAddress(func, + WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("f784682c82526e245f50975190ef0fff4e4fc077")).get())).get(), false); + TWEthereumAbiFunctionAddParamBytes(func, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("3132333435")).get())).get(), false); + TWEthereumAbiFunctionAddParamBytesFix(func, 5, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("3132333435")).get())).get(), false); + + TWEthereumAbiFunctionAddInArrayParamUInt8(func, TWEthereumAbiFunctionAddParamArray(func, false), 1); + TWEthereumAbiFunctionAddInArrayParamUInt16(func, TWEthereumAbiFunctionAddParamArray(func, false), 2); + TWEthereumAbiFunctionAddInArrayParamUInt32(func, TWEthereumAbiFunctionAddParamArray(func, false), 3); + TWEthereumAbiFunctionAddInArrayParamUInt64(func, TWEthereumAbiFunctionAddParamArray(func, false), 4); + TWEthereumAbiFunctionAddInArrayParamUIntN(func, TWEthereumAbiFunctionAddParamArray(func, false), 168, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get()); + TWEthereumAbiFunctionAddInArrayParamUInt256(func, TWEthereumAbiFunctionAddParamArray(func, false), WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get()); + TWEthereumAbiFunctionAddInArrayParamInt8(func, TWEthereumAbiFunctionAddParamArray(func, false), 1); + TWEthereumAbiFunctionAddInArrayParamInt16(func, TWEthereumAbiFunctionAddParamArray(func, false), -3); + TWEthereumAbiFunctionAddInArrayParamInt32(func, TWEthereumAbiFunctionAddParamArray(func, false), 3); + TWEthereumAbiFunctionAddInArrayParamInt64(func, TWEthereumAbiFunctionAddParamArray(func, false), 4); + TWEthereumAbiFunctionAddInArrayParamIntN(func, TWEthereumAbiFunctionAddParamArray(func, false), 168, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get()); + TWEthereumAbiFunctionAddInArrayParamInt256(func, TWEthereumAbiFunctionAddParamArray(func, false), WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0123")).get())).get()); + TWEthereumAbiFunctionAddInArrayParamBool(func, TWEthereumAbiFunctionAddParamArray(func, false), true); + TWEthereumAbiFunctionAddInArrayParamString(func, TWEthereumAbiFunctionAddParamArray(func, false), WRAPS(TWStringCreateWithUTF8Bytes("Hello, world!")).get()); + TWEthereumAbiFunctionAddInArrayParamAddress(func, TWEthereumAbiFunctionAddParamArray(func, false), + WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("f784682c82526e245f50975190ef0fff4e4fc077")).get())).get()); + TWEthereumAbiFunctionAddInArrayParamBytes(func, TWEthereumAbiFunctionAddParamArray(func, false), WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("3132333435")).get())).get()); + TWEthereumAbiFunctionAddInArrayParamBytesFix(func, TWEthereumAbiFunctionAddParamArray(func, false), 5, WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("3132333435")).get())).get()); + + // check back out params + EXPECT_EQ(1, TWEthereumAbiFunctionGetParamUInt8(func, 0, false)); + EXPECT_EQ(4ul, TWEthereumAbiFunctionGetParamUInt64(func, 3, false)); + EXPECT_EQ(true, TWEthereumAbiFunctionGetParamBool(func, 12, false)); + EXPECT_EQ(std::string("Hello, world!"), std::string(TWStringUTF8Bytes(WRAPS(TWEthereumAbiFunctionGetParamString(func, 13, false)).get()))); + WRAPD(TWEthereumAbiFunctionGetParamAddress(func, 14, false)); + + auto type = WRAPS(TWEthereumAbiFunctionGetType(func)); + EXPECT_EQ( + "monster(uint8,uint16,uint32,uint64,uint168,uint256,int8,int16,int32,int64,int168,int256,bool,string,address,bytes,bytes5," + "uint8[],uint16[],uint32[],uint64[],uint168[],uint256[],int8[],int16[],int32[],int64[],int168[],int256[],bool[],string[],address[],bytes[],bytes5[])", + std::string(TWStringUTF8Bytes(type.get()))); + + auto encoded = WRAPD(TWEthereumAbiEncode(func)); + Data enc2 = data(TWDataBytes(encoded.get()), TWDataSize(encoded.get())); + EXPECT_EQ(4ul + 76 * 32, enc2.size()); + EXPECT_EQ(hex(enc2), "70efb5a50000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000123000000000000000000000000000000000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000440000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc0770000000000000000000000000000000000000000000000000000000000000480313233343500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000540000000000000000000000000000000000000000000000000000000000000058000000000000000000000000000000000000000000000000000000000000005c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000068000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000740000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000007c00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000000000000000000000000000008c00000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c64210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000053132333435000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000123000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000123000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005313233343500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013132333435000000000000000000000000000000000000000000000000000000"); + + // delete + TWEthereumAbiFunctionDelete(func); +} + +TEST(TWEthereumAbi, DecodeOutputFuncCase1) { + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("readout")).get()); + EXPECT_TRUE(func != nullptr); + + TWEthereumAbiFunctionAddParamAddress(func, + WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("f784682c82526e245f50975190ef0fff4e4fc077")).get())).get(), false); + TWEthereumAbiFunctionAddParamUInt64(func, 1000, false); + EXPECT_EQ(0, TWEthereumAbiFunctionAddParamUInt64(func, 0, true)); + + // original output value + EXPECT_EQ(0ul, TWEthereumAbiFunctionGetParamUInt64(func, 0, true)); + + // decode + auto encoded = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes("0000000000000000000000000000000000000000000000000000000000000045")).get())); + EXPECT_EQ(true, TWEthereumAbiDecodeOutput(func, encoded.get())); + + // new output value + EXPECT_EQ(0x45ul, TWEthereumAbiFunctionGetParamUInt64(func, 0, true)); + + // delete + TWEthereumAbiFunctionDelete(func); +} + +TEST(TWEthereumAbi, GetParamWrongType) { + TWEthereumAbiFunction* func = TWEthereumAbiFunctionCreateWithString(WRAPS(TWStringCreateWithUTF8Bytes("func")).get()); + ASSERT_TRUE(func != nullptr); + // add parameters + EXPECT_EQ(0, TWEthereumAbiFunctionAddParamUInt8(func, 1, true)); + EXPECT_EQ(1, TWEthereumAbiFunctionAddParamUInt64(func, 2, true)); + + // GetParameter with correct types + EXPECT_EQ(1, TWEthereumAbiFunctionGetParamUInt8(func, 0, true)); + EXPECT_EQ(2ul, TWEthereumAbiFunctionGetParamUInt64(func, 1, true)); + + // GetParameter with wrong type, default value (0) is returned + EXPECT_EQ(0, TWEthereumAbiFunctionGetParamUInt8(func, 1, true)); + EXPECT_EQ(0ul, TWEthereumAbiFunctionGetParamUInt64(func, 0, true)); + EXPECT_EQ("00", hex(*(static_cast(WRAPD(TWEthereumAbiFunctionGetParamUInt256(func, 0, true)).get())))); + EXPECT_EQ(false, TWEthereumAbiFunctionGetParamBool(func, 0, true)); + EXPECT_EQ("", std::string(TWStringUTF8Bytes(WRAPS(TWEthereumAbiFunctionGetParamString(func, 0, true)).get()))); + EXPECT_EQ("", hex(*(static_cast(WRAPD(TWEthereumAbiFunctionGetParamAddress(func, 0, true)).get())))); + + // GetParameter with wrong index, default value (0) is returned + EXPECT_EQ(0, TWEthereumAbiFunctionGetParamUInt8(func, 99, true)); + EXPECT_EQ(0ul, TWEthereumAbiFunctionGetParamUInt64(func, 99, true)); + EXPECT_EQ("00", hex(*(static_cast(WRAPD(TWEthereumAbiFunctionGetParamUInt256(func, 99, true)).get())))); + EXPECT_EQ(false, TWEthereumAbiFunctionGetParamBool(func, 99, true)); + EXPECT_EQ("", std::string(TWStringUTF8Bytes(WRAPS(TWEthereumAbiFunctionGetParamString(func, 99, true)).get()))); + EXPECT_EQ("", hex(*(static_cast(WRAPD(TWEthereumAbiFunctionGetParamAddress(func, 99, true)).get())))); + + // delete + TWEthereumAbiFunctionDelete(func); +} + +TEST(TWEthereumAbi, DecodeCall) { + auto encodedCall = parse_hex("c47f0027000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000086465616462656566000000000000000000000000000000000000000000000000"); + auto abiJson = R"|({"c47f0027":{"constant":false,"inputs":[{"name":"name","type":"string"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}})|"; + + EthereumAbi::Proto::ContractCallDecodingInput input; + input.set_encoded(encodedCall.data(), encodedCall.size()); + input.set_smart_contract_abi_json(abiJson); + + const auto inputData = data(input.SerializeAsString()); + auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t*)inputData.data(), inputData.size())); + auto outputTWData = WRAPD(TWEthereumAbiDecodeContractCall(TWCoinTypeEthereum, inputTWData.get())); + + EthereumAbi::Proto::ContractCallDecodingOutput output; + output.ParseFromArray(TWDataBytes(outputTWData.get()), static_cast(TWDataSize(outputTWData.get()))); + + auto expected = R"|({"function":"setName(string)","inputs":[{"name":"name","type":"string","value":"deadbeef"}]})|"; + EXPECT_EQ(output.decoded_json(), expected); +} + +TEST(TWEthereumAbi, DecodeInvalidCall) { + auto callHex = STRING("c47f002700"); + auto call = WRAPD(TWDataCreateWithHexString(callHex.get())); + + auto decoded1 = TWEthereumAbiDecodeCall(call.get(), STRING(",,").get()); + auto decoded2 = TWEthereumAbiDecodeCall(call.get(), STRING("{}").get()); + + EXPECT_TRUE(decoded1 == nullptr); + EXPECT_TRUE(decoded2 == nullptr); +} + +TEST(TWEthereumAbi, encodeTyped) { + auto message = WRAPS(TWStringCreateWithUTF8Bytes( + R"({ + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"} + ], + "Person": [ + {"name": "name", "type": "string"}, + {"name": "wallets", "type": "address[]"} + ], + "Mail": [ + {"name": "from", "type": "Person"}, + {"name": "to", "type": "Person[]"}, + {"name": "contents", "type": "string"} + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallets": [ + "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" + ] + }, + "to": [ + { + "name": "Bob", + "wallets": [ + "bBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + "B0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", + "B0B0b0b0b0b0B000000000000000000000000000" + ] + } + ], + "contents": "Hello, Bob!" + } + })")); + auto hash = WRAPD(TWEthereumAbiEncodeTyped(message.get())); + + EXPECT_EQ( + hex(TW::data(TWDataBytes(hash.get()), TWDataSize(hash.get()))), + "a85c2e2b118698e88db68a8105b794a8cc7cec074e89ef991cb4f5f533819cc2" + ); +} + +} // namespace TW::Ethereum diff --git a/tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp b/tests/chains/Ethereum/TWEthereumAbiValueDecoderTests.cpp similarity index 92% rename from tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp rename to tests/chains/Ethereum/TWEthereumAbiValueDecoderTests.cpp index 2f84327195b..ba4607d10ca 100644 --- a/tests/Ethereum/TWEthereumAbiValueDecoderTests.cpp +++ b/tests/chains/Ethereum/TWEthereumAbiValueDecoderTests.cpp @@ -1,14 +1,12 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include "Data.h" #include "HexCoding.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; @@ -49,7 +47,7 @@ TEST(TWEthereumAbiValue, decodeValue) { { const auto input = "000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"; const auto type = "address"; - const auto expected = "0xf784682c82526e245f50975190ef0fff4e4fc077"; + const auto expected = "0xF784682C82526e245F50975190EF0fff4E4fC077"; auto data = WRAPD(TWDataCreateWithHexString(STRING(input).get())); auto result = WRAPS(TWEthereumAbiValueDecodeValue(data.get(), WRAPS(TWStringCreateWithUTF8Bytes(type)).get())); EXPECT_EQ(std::string(expected), std::string(TWStringUTF8Bytes(result.get()))); @@ -97,8 +95,8 @@ TEST(TWEthereumAbiValue, decodeArray) { "0000000000000000000000002e00cd222cb42b616d86d037cc494e8ab7f5c9a3"; const auto type = "address[]"; const auto expected = - "[\"0xf784682c82526e245f50975190ef0fff4e4fc077\"," - "\"0x2e00cd222cb42b616d86d037cc494e8ab7f5c9a3\"]"; + "[\"0xF784682C82526e245F50975190EF0fff4E4fC077\"," + "\"0x2e00CD222Cb42B616D86D037Cc494e8ab7F5c9a3\"]"; auto data = WRAPD(TWDataCreateWithHexString(STRING(input).get())); auto result = WRAPS(TWEthereumAbiValueDecodeArray(data.get(), WRAPS(TWStringCreateWithUTF8Bytes(type)).get())); EXPECT_EQ(std::string(expected), std::string(TWStringUTF8Bytes(result.get()))); diff --git a/tests/Ethereum/TWEthereumAbiValueEncodeTests.cpp b/tests/chains/Ethereum/TWEthereumAbiValueEncodeTests.cpp similarity index 92% rename from tests/Ethereum/TWEthereumAbiValueEncodeTests.cpp rename to tests/chains/Ethereum/TWEthereumAbiValueEncodeTests.cpp index b4b120adbd3..1d73afc40e6 100644 --- a/tests/Ethereum/TWEthereumAbiValueEncodeTests.cpp +++ b/tests/chains/Ethereum/TWEthereumAbiValueEncodeTests.cpp @@ -1,15 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include "Data.h" #include "HexCoding.h" #include "uint256.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace TW; diff --git a/tests/chains/Ethereum/TWRlpTests.cpp b/tests/chains/Ethereum/TWRlpTests.cpp new file mode 100644 index 00000000000..661529562dc --- /dev/null +++ b/tests/chains/Ethereum/TWRlpTests.cpp @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TrustWalletCore/TWEthereumRlp.h" +#include "proto/EthereumRlp.pb.h" +#include "HexCoding.h" +#include "TestUtilities.h" +#include "uint256.h" + +#include + +using namespace TW; + +TEST(TWEthereumRlp, Eip1559) { + auto chainId = store(10); + auto nonce = store(6); + auto maxInclusionFeePerGas = 2'000'000'000; + auto maxFeePerGas = store(3'000'000'000); + auto gasLimit = store(21'100); + const auto* to = "0x6b175474e89094c44da98b954eedeac495271d0f"; + auto amount = 0; + auto payload = parse_hex("a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1"); + + EthereumRlp::Proto::EncodingInput input; + auto* list = input.mutable_item()->mutable_list(); + + list->add_items()->set_number_u256(chainId.data(), chainId.size()); + list->add_items()->set_number_u256(nonce.data(), nonce.size()); + list->add_items()->set_number_u64(maxInclusionFeePerGas); + list->add_items()->set_number_u256(maxFeePerGas.data(), maxFeePerGas.size()); + list->add_items()->set_number_u256(gasLimit.data(), gasLimit.size()); + list->add_items()->set_address(to); + list->add_items()->set_number_u64(amount); + list->add_items()->set_data(payload.data(), payload.size()); + // Append an empty `access_list`. + list->add_items()->mutable_list(); + + auto inputData = input.SerializeAsString(); + auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size())); + auto outputTWData = WRAPD(TWEthereumRlpEncode(TWCoinTypeEthereum, inputTWData.get())); + + EthereumRlp::Proto::EncodingOutput output; + output.ParseFromArray(TWDataBytes(outputTWData.get()), static_cast(TWDataSize(outputTWData.get()))); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + EXPECT_TRUE(output.error_message().empty()); + EXPECT_EQ(hex(output.encoded()), "f86c0a06847735940084b2d05e0082526c946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1c0"); +} diff --git a/tests/chains/Ethereum/TransactionCompilerTests.cpp b/tests/chains/Ethereum/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..3660ed2e610 --- /dev/null +++ b/tests/chains/Ethereum/TransactionCompilerTests.cpp @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "uint256.h" + +#include "proto/Ethereum.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(EthereumCompiler, CompileWithSignatures) { + /// Step 1: Prepare transaction input (protobuf) + const auto coin = TWCoinTypeEthereum; + Ethereum::Proto::SigningInput input; + + const auto nonce = store(uint256_t(11)); + const auto chainId = store(uint256_t(1)); + const auto gasPrice = store(uint256_t(20000000000)); + const auto gasLimit = store(uint256_t(21000)); + const auto amount = store(uint256_t(1'000'000'000'000'000'000)); + + input.set_nonce(nonce.data(), nonce.size()); + input.set_chain_id(chainId.data(), chainId.size()); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_tx_mode(Ethereum::Proto::Legacy); + input.set_to_address("0x3535353535353535353535353535353535353535"); + + input.mutable_transaction()->mutable_transfer()->set_amount(amount.data(), amount.size()); + + // Serialize back, this shows how to serialize SigningInput protobuf to byte array + const auto txInputData = data(input.SerializeAsString()); + EXPECT_EQ(txInputData.size(), 75ul); + + /// Step 2: Obtain preimage hash + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::SigningError::OK); + + auto preImageHash = data(preSigningOutput.data_hash()); + EXPECT_EQ(hex(preImageHash), "15e180a6274b2f6a572b9b51823fce25ef39576d10188ecdcd7de44526c47217"); + + // Simulate signature, normally obtained from signature server + const Data publicKeyData = + parse_hex("044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad711" + "9ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1Extended); + const auto signature = + parse_hex("360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a19" + "1d844db458893b928f3efbfee90c9febf51ab84c9796677900"); + + // Verify signature (pubkey & hash & signature) + { EXPECT_TRUE(publicKey.verify(signature, preImageHash)); } + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKeyData}); + + // We dont care about public key in ethereum. It is not part of the transaction. + auto outputDataWithoutPubKey = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {}); + + EXPECT_EQ(outputData, outputDataWithoutPubKey); + + const auto ExpectedTx = + "f86c0b8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0" + "360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07ba053bd9d1376e15a191d844db4" + "58893b928f3efbfee90c9febf51ab84c97966779"; + { + EXPECT_EQ(outputData.size(), 217ul); + Ethereum::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded().size(), 110ul); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Ethereum::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(txInputData.data(), (int)txInputData.size())); + auto key = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + signingInput.set_private_key(key.data(), key.size()); + + Ethereum::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Ethereum::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); + } +} diff --git a/tests/chains/Ethereum/ValueDecoderTests.cpp b/tests/chains/Ethereum/ValueDecoderTests.cpp new file mode 100644 index 00000000000..8a47e82e822 --- /dev/null +++ b/tests/chains/Ethereum/ValueDecoderTests.cpp @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Ethereum/ABI/ValueDecoder.h" +#include + +#include + +namespace TW::Ethereum::tests { + +uint256_t decodeFromHex(std::string s) { + auto data = parse_hex(s); + return ABI::ValueDecoder::decodeUInt256(data); +} + +TEST(EthereumAbiValueDecoder, decodeUInt256) { + EXPECT_EQ(uint256_t(0), decodeFromHex("")); + EXPECT_EQ(uint256_t(0), decodeFromHex("0000000000000000000000000000000000000000000000000000000000000000")); + EXPECT_EQ(uint256_t(1), decodeFromHex("0000000000000000000000000000000000000000000000000000000000000001")); + EXPECT_EQ(uint256_t(123456), decodeFromHex("01e240")); + EXPECT_EQ(uint256_t(10020405371567), decodeFromHex("0000000000000000000000000000000000000000000000000000091d0eb3e2af")); + EXPECT_EQ(uint256_t(10020405371567), decodeFromHex("0000000000000000000000000000000000000000000000000000091d0eb3e2af00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")); +} + +TEST(EthereumAbiValueDecoder, decodeValue) { + EXPECT_EQ("42", ABI::ValueDecoder::decodeValue(parse_hex("000000000000000000000000000000000000000000000000000000000000002a"), "uint")); + EXPECT_EQ("24", ABI::ValueDecoder::decodeValue(parse_hex("0000000000000000000000000000000000000000000000000000000000000018"), "uint8")); + EXPECT_EQ("123456", ABI::ValueDecoder::decodeValue(parse_hex("000000000000000000000000000000000000000000000000000000000001e240"), "uint256")); + EXPECT_EQ("0xF784682C82526e245F50975190EF0fff4E4fC077", ABI::ValueDecoder::decodeValue(parse_hex("000000000000000000000000f784682c82526e245f50975190ef0fff4e4fc077"), "address")); + EXPECT_EQ("Hello World! Hello World! Hello World!", + ABI::ValueDecoder::decodeValue(parse_hex( + "000000000000000000000000000000000000000000000000000000000000002c" + "48656c6c6f20576f726c64212020202048656c6c6f20576f726c642120202020" + "48656c6c6f20576f726c64210000000000000000000000000000000000000000"), + "string")); + EXPECT_EQ("0x31323334353637383930", ABI::ValueDecoder::decodeValue(parse_hex("3132333435363738393000000000000000000000000000000000000000000000"), "bytes10")); +} + +} // namespace TW::Ethereum::tests diff --git a/tests/Ethereum/ValueEncoderTests.cpp b/tests/chains/Ethereum/ValueEncoderTests.cpp similarity index 84% rename from tests/Ethereum/ValueEncoderTests.cpp rename to tests/chains/Ethereum/ValueEncoderTests.cpp index bc6f8fd22a5..58cca246524 100644 --- a/tests/Ethereum/ValueEncoderTests.cpp +++ b/tests/chains/Ethereum/ValueEncoderTests.cpp @@ -1,22 +1,18 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Ethereum/ABI/ValueEncoder.h" #include #include -using namespace TW; -using namespace TW::Ethereum; - -Data data; +namespace TW::Ethereum::tests { void checkLast32BytesEqual(const Data& data, const char* expected) { EXPECT_EQ(hex(subData(data, data.size() - 32, 32)), expected); } + TEST(EthereumAbiValueEncoder, encodeBool) { Data data; ABI::ValueEncoder::encodeBool(false, data); @@ -121,16 +117,18 @@ TEST(EthereumAbiValueEncoder, uint256FromInt256) { } TEST(EthereumAbiValueEncoder, pad32) { - EXPECT_EQ(64, ABI::ValueEncoder::paddedTo32(40)); - EXPECT_EQ(32, ABI::ValueEncoder::paddedTo32(32)); - EXPECT_EQ(64, ABI::ValueEncoder::paddedTo32(33)); - EXPECT_EQ(64, ABI::ValueEncoder::paddedTo32(63)); - EXPECT_EQ(64, ABI::ValueEncoder::paddedTo32(64)); - EXPECT_EQ(96, ABI::ValueEncoder::paddedTo32(65)); - EXPECT_EQ(24, ABI::ValueEncoder::padNeeded32(40)); - EXPECT_EQ(0, ABI::ValueEncoder::padNeeded32(32)); - EXPECT_EQ(31, ABI::ValueEncoder::padNeeded32(33)); - EXPECT_EQ(1, ABI::ValueEncoder::padNeeded32(63)); - EXPECT_EQ(0, ABI::ValueEncoder::padNeeded32(64)); - EXPECT_EQ(31, ABI::ValueEncoder::padNeeded32(65)); + EXPECT_EQ(64ul, ABI::ValueEncoder::paddedTo32(40)); + EXPECT_EQ(32ul, ABI::ValueEncoder::paddedTo32(32)); + EXPECT_EQ(64ul, ABI::ValueEncoder::paddedTo32(33)); + EXPECT_EQ(64ul, ABI::ValueEncoder::paddedTo32(63)); + EXPECT_EQ(64ul, ABI::ValueEncoder::paddedTo32(64)); + EXPECT_EQ(96ul, ABI::ValueEncoder::paddedTo32(65)); + EXPECT_EQ(24ul, ABI::ValueEncoder::padNeeded32(40)); + EXPECT_EQ(0ul, ABI::ValueEncoder::padNeeded32(32)); + EXPECT_EQ(31ul, ABI::ValueEncoder::padNeeded32(33)); + EXPECT_EQ(1ul, ABI::ValueEncoder::padNeeded32(63)); + EXPECT_EQ(0ul, ABI::ValueEncoder::padNeeded32(64)); + EXPECT_EQ(31ul, ABI::ValueEncoder::padNeeded32(65)); } + +} // namespace TW::Ethereum::tests diff --git a/tests/chains/EthereumClassic/TWCoinTypeTests.cpp b/tests/chains/EthereumClassic/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..e547ced83a0 --- /dev/null +++ b/tests/chains/EthereumClassic/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWEthereumClassicCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeEthereumClassic)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x66004165d3901819dc22e568931591d2e4287bda54995f4ce2701a12016f5997")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeEthereumClassic, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x9eab4b0fc468a7f5d46228bf5a76cb52370d068d")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeEthereumClassic, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeEthereumClassic)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeEthereumClassic)); + const auto chainId = WRAPS(TWCoinTypeChainId(TWCoinTypeEthereumClassic)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeEthereumClassic), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeEthereumClassic)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeEthereumClassic)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeEthereumClassic)); + assertStringsEqual(symbol, "ETC"); + assertStringsEqual(txUrl, "https://blockscout.com/etc/mainnet/tx/0x66004165d3901819dc22e568931591d2e4287bda54995f4ce2701a12016f5997"); + assertStringsEqual(accUrl, "https://blockscout.com/etc/mainnet/address/0x9eab4b0fc468a7f5d46228bf5a76cb52370d068d"); + assertStringsEqual(id, "classic"); + assertStringsEqual(name, "Ethereum Classic"); + assertStringsEqual(chainId, "61"); +} diff --git a/tests/chains/Everscale/AddressTests.cpp b/tests/chains/Everscale/AddressTests.cpp new file mode 100644 index 00000000000..f984140bd74 --- /dev/null +++ b/tests/chains/Everscale/AddressTests.cpp @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Everscale/Address.h" +#include "Everscale/WorkchainType.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include + +using namespace TW; + +namespace TW::Everscale { + +TEST(EverscaleAddress, Valid) { + ASSERT_TRUE(Address::isValid("0:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a")); +} + +TEST(EverscaleAddress, Invalid) { + ASSERT_FALSE(Address::isValid("hello world")); + ASSERT_FALSE(Address::isValid("83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a")); + ASSERT_FALSE(Address::isValid("1:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a")); + ASSERT_FALSE(Address::isValid("-2:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a")); + ASSERT_FALSE(Address::isValid("2147483648:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a")); + ASSERT_FALSE(Address::isValid("0:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469ab")); +} + +TEST(EverscaleAddress, FromString) { + auto address = Address("0:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a"); + ASSERT_EQ(address.string(), "0:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a"); + + auto address_uppercase = Address("0:83A0352908060FA87839195D8A763A8D9AB28F8FA41468832B398A719CC6469A"); + ASSERT_EQ(address_uppercase.string(), "0:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a"); +} + +TEST(EverscaleAddress, FromPrivateKey) { + auto privateKey = PrivateKey(parse_hex("5b59e0372d19b6355c73fa8cc708fa3301ae2ec21bb6277e8b79d386ccb7846f")); + auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519), WorkchainType::Basechain); + ASSERT_EQ(address.string(), "0:269fee242eb410786abe1777a14785c8bbeb1e34100c7570e17698b36ad66fb0"); +} + +TEST(EverscaleAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("e4925f9932df8d7fd0042efff3e2178a972028b644ded3a3b66f6d0577f82e78"), TWPublicKeyTypeED25519); + auto address = Address(publicKey, WorkchainType::Basechain); + ASSERT_EQ(address.string(), "0:269fee242eb410786abe1777a14785c8bbeb1e34100c7570e17698b36ad66fb0"); +} + +} // namespace TW::Everscale diff --git a/tests/chains/Everscale/CellBuilderTest.cpp b/tests/chains/Everscale/CellBuilderTest.cpp new file mode 100644 index 00000000000..73ad08f382b --- /dev/null +++ b/tests/chains/Everscale/CellBuilderTest.cpp @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "BinaryCoding.h" +#include "HexCoding.h" +#include "PublicKey.h" + +#include "Everscale/CommonTON/Cell.h" +#include "Everscale/CommonTON/CellBuilder.h" +#include "Everscale/Wallet.h" + +#include + +using boost::multiprecision::uint128_t; + +namespace TW::Everscale { + +void checkBuilder(const uint128_t& value, uint16_t bitLen, const std::string& hash) { + CellBuilder dataBuilder; + dataBuilder.appendU128(value); + const auto cell = dataBuilder.intoCell(); + ASSERT_EQ(cell->bitLen, bitLen); + ASSERT_EQ(hex(cell->hash), hash); +} + +TEST(EverscaleCell, BuilderVarUint16) { + const uint128_t oneEver{1'000'000'000u}; + + checkBuilder(0, 4, "5331fed036518120c7f345726537745c5929b8ea1fa37b99b2bb58f702671541"); + checkBuilder(1, 12, "d46edee086ccbace01f45c13d26d49b68f74cd1b7616f4662e699c82c6ec728b"); + checkBuilder(255, 12, "bd16b2d60c93163fbed832e91a5faec484715c48176857c57dcedf9f6e0f32f6"); + checkBuilder(256, 20, "16559011ce6f0f7aaa765179e73ef293f39610f5baa3838a1dc8c52da95793b3"); + checkBuilder(oneEver, 36, "e139b2d96d0bd76da98c3c23b0dc0481dcfe19562798fefbb7bf2e56d8ef37b5"); + checkBuilder(10 * oneEver, 44, "8882fead71f2deb3aa7b8dbd15bbb42c651fcaae8da82e6d5cf8e49825eed12b"); + checkBuilder(1000000 * oneEver, 60, "125f2f85da07f9d92148c067bc19aecbf4da65becdd6b51f17ae3a2aeb2c1bdd"); + checkBuilder(1'000'000'000'000u * oneEver, 76, "39bcb314cdb31de5159764d9c28779de27be44210ffcc52a27aa01bff1d82bf7"); +} + +TEST(EverscaleCell, ComputeContractAddress) { + const auto seqno = 0; + const auto walletId = WALLET_ID; + const auto publicKey = PublicKey(parse_hex("7dbe83e9b223157e85bed2628430e2cdb531d5c99ab428618b7dd29b567a0369"), TWPublicKeyTypeED25519); + + CellBuilder dataBuilder; + dataBuilder.appendU32(seqno); + dataBuilder.appendU32(walletId); + dataBuilder.appendRaw(publicKey.bytes, 256); + + const auto data = dataBuilder.intoCell(); + + // Builder should be empty after `intoCell` + { + const auto emptyCell = dataBuilder.intoCell(); + ASSERT_EQ(hex(emptyCell->hash), "96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7"); + } + + const auto code = Cell::deserialize(Wallet::code.data(), Wallet::code.size()); + + CellBuilder stateInitBuilder; + stateInitBuilder.appendBitZero(); // split_depth + stateInitBuilder.appendBitZero(); // special + stateInitBuilder.appendBitOne(); // code + stateInitBuilder.appendReferenceCell(code); + stateInitBuilder.appendBitOne(); // data + stateInitBuilder.appendReferenceCell(data); + stateInitBuilder.appendBitZero(); // library + + auto stateInit = stateInitBuilder.intoCell(); + + ASSERT_EQ(hex(stateInit->hash), "5a0f742c28067da91e05830f0b072a2069f0617a5f6529d295f6c517d63d67c6"); +} + +TEST(EverscaleCell, UnalignedRead) { + CellBuilder dataBuilder; + dataBuilder.appendU32(0x12312312); + + auto cell = dataBuilder.intoCell(); + auto slice = CellSlice(cell.get()); + + slice.dataOffset += 1; + const auto nextBytes = slice.getNextBytes(2); + ASSERT_TRUE(nextBytes.size() == 2 && nextBytes[0] == 0x24 && nextBytes[1] == 0x62); +} + +TEST(EverscaleCell, ReadZeroBytes) { + CellBuilder dataBuilder; + dataBuilder.appendU32(0x12312312); + + auto cell = dataBuilder.intoCell(); + auto slice = CellSlice(cell.get()); + + ASSERT_EQ(slice.getNextBytes(0), Data{}); +} + +TEST(EverscaleCell, InvalidBuilderData) { + CellBuilder dataBuilder; + ASSERT_ANY_THROW(dataBuilder.appendRaw(Data{}, 1)); +} + +TEST(EverscaleCell, DataOverflow) { + CellBuilder dataBuilder; + + Data data(128, 0x00); + ASSERT_ANY_THROW(dataBuilder.appendRaw(data, data.size() * 8)); +} + +TEST(EverscaleCell, DataUnderflow) { + CellBuilder dataBuilder; + dataBuilder.appendU32(0x12312312); + + auto cell = dataBuilder.intoCell(); + auto slice = CellSlice(cell.get()); + + ASSERT_ANY_THROW(slice.getNextBytes(100)); +} + +} // namespace TW::Everscale diff --git a/tests/chains/Everscale/CellTests.cpp b/tests/chains/Everscale/CellTests.cpp new file mode 100644 index 00000000000..139878ec6c1 --- /dev/null +++ b/tests/chains/Everscale/CellTests.cpp @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base64.h" +#include "HexCoding.h" + +#include "Everscale/CommonTON/Cell.h" +#include "Everscale/Wallet.h" + +#include +#include + +namespace TW::Everscale { + +// All hashes could be verified using https://ever.bytie.moe/visualizer + +static constexpr auto TX = "te6ccgECDgEAAyUAA7V6uRyM7ESqbjssMUQyAqYyQTlEkfDkEhWjBiC1fvKLabAAAaKsEuhQGeqOSyMlWG32ehDzoVCXMh6ugfMLr6pOPj3b6KD4DR/wAAGirA4jnSYtmdCAADR6V/lIBQQBAg8MQQYcxc1EQAMCAG/JkrcETDHn2AAAAAAAAgAAAAAAAop8XDVxQ98+QpgCzzW0U0opAulbEzfySLp3wLLoHzboQRA6FACdROMjE4gAAAAAAAAAACHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIACCcgkc5v5RyBDaeDdbY8Q+SpmX5OOkhzxFyB/ug4o/bJwJXDMKjAeOEr9OvfcJlgyx80ukSBl43/FO2DXIt1+SuAQCAeAJBgEB3wcBsWgBVyORnYiVTcdlhiiGQFTGSCcokj4cgkK0YMQWr95RbTcAIXxdqfc1KmavZDEIGsfjdBuS4lE5Ox4rJZ+Z+rauOxDRZaC8AAYx6CQAADRVgl0KBMWzOhDACAF7C04VCAAAAAAvMK5pAAAAAAAAAAAAAAAAAA7xIIAFdGVusOGq5cSrATb2hH5h5turvUDrer4E0Mf51wPlefAMAd2IAVcjkZ2IlU3HZYYohkBUxkgnKJI+HIJCtGDEFq/eUW02AMrbR14n5UXrp1deXDU5rl8kQvDfSKbnA+d09dtQe2mo8t94jbtllx/DjCgucIpvywjhJBhWNQjtWXh4dP6qiDAAAAADFszqDukuhIYKAQTQAwsB42IAQvi7U+5qVM1eyGIQNY/G6DclxKJydjxWSz8z9W1cdiGiy0F4AAAAAAAAAAAAAAAAAAALThUIAAAAAC8wrmkAAAAAAAAAAAAAAAAADvEggAV0ZW6w4arlxKsBNvaEfmHm26u9QOt6vgTQx/nXA+V58AwBY4AUk5qcKxU0Kqq8xI6zxcnOzaRMAW8AGxI8jfJSPgiVJ6AAAAAAAAAAAAAARVadlK9wDQBDgBVyORnYiVTcdlhiiGQFTGSCcokj4cgkK0YMQWr95RbTcA=="; +static constexpr auto BIG_TX = "te6ccgECdQEAFD4AA7d61FeCHf8yG3+VsHLEilQ6UuYy8wcpWi1+/eB+QyLiYMAAAahJB7OYkL2Pl+S0sBbapRCERwSsVWKsZ/1WGbNouh5HwPhxSiGAAAGoP8TeAJYumenAAFSARBWHSAUEAQIbBIgLyR0Jj8WYgCdHLREDAgBxygFZfVBPmUqMAAAAAAAGAAIAAAAEW8033SNQ2/1TE9Nzuof+lf33h4/sRfFyiGy4DaJos2pa1CzcAJ5KDiw9CQAAAAAAAAAAATMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIJyjI1j6xocdf/ts8UpUE4PQBjq+eZ4ePLwpE1tDGf3pHcFUnuY03DQrIX8ExegcAhTl+187mhCPuegYR4tBS3ejwIB4HEGAgHdCgcBASAIAbFoAVqK8EO/5kNv8rYOWJFKh0pcxl5g5StFr9+8D8hkXEwZAB03C5ZU3g0onstArcVRQt2q942P0t/N68CNCNeI9IxfETHWk3wGKJa2AAA1CSD2cxbF0z04wAkBa2eguV8AAAAAAAAAAAAAAANFARhPgB374hjvHSKb7xHdyPtVEPFzv/+BeyMtxMrKJu6jDYpu8HMBASALArNoAVqK8EO/5kNv8rYOWJFKh0pcxl5g5StFr9+8D8hkXEwZAB03C5ZU3g0onstArcVRQt2q942P0t/N68CNCNeI9IxfEI8NGAAIA3C5RAAANQkg9nMUxdM9OeBTDAJTFaA4+wAAAAGAHfviGO8dIpvvEd3I+1UQ8XO//4F7Iy3Eysom7qMNim7wDg0AQ4AVmHZxK25XVSkUA1uAHGOMneWjMhSxqkQWdAyAImMM0RACBorbNXAPBCSK7VMg4wMgwP/jAiDA/uMC8gtNERB0A77tRNDXScMB+GaJ+Gkh2zzTAAGOGoECANcYIPkBAdMAAZTT/wMBkwL4QuL5EPKoldMAAfJ64tM/AfhDIbnytCD4I4ED6KiCCBt3QKC58rT4Y9MfAfgjvPK50x8B2zzyPGodEgR87UTQ10nDAfhmItDTA/pAMPhpqTgA+ER/b3GCCJiWgG9ybW9zcG90+GTjAiHHAOMCIdcNH/K8IeMDAds88jxKa2sSAiggghBnoLlfu+MCIIIQfW/yVLvjAh8TAzwgghBotV8/uuMCIIIQc+IhQ7rjAiCCEH1v8lS64wIcFhQDNjD4RvLgTPhCbuMAIZPU0dDe+kDR2zww2zzyAEwVUABo+Ev4SccF8uPo+Ev4TfhKcMjPhYDKAHPPQM5xzwtuVSDIz5BT9raCyx/OAcjOzc3JgED7AANOMPhG8uBM+EJu4wAhk9TR0N7Tf/pA03/U0dD6QNIA1NHbPDDbPPIATBdQBG74S/hJxwXy4+glwgDy5Bol+Ey78uQkJPpCbxPXC//DACX4S8cFs7Dy5AbbPHD7AlUD2zyJJcIAUTpqGAGajoCcIfkAyM+KAEDL/8nQ4jH4TCehtX/4bFUhAvhLVQZVBH/Iz4WAygBzz0DOcc8LblVAyM+RnoLlfst/zlUgyM7KAMzNzcmBAID7AFsZAQpUcVTbPBoCuPhL+E34QYjIz44rbNbMzslVBCD5APgo+kJvEsjPhkDKB8v/ydAGJsjPhYjOAfoCi9AAAAAAAAAAAAAAAAAHzxYh2zzMz4NVMMjPkFaA4+7Myx/OAcjOzc3JcfsAcBsANNDSAAGT0gQx3tIAAZPSATHe9AT0BPQE0V8DARww+EJu4wD4RvJz0fLAZB0CFu1E0NdJwgGOgOMNHkwDZnDtRND0BXEhgED0Do6A33IigED0Do6A33AgiPhu+G34bPhr+GqAQPQO8r3XC//4YnD4Y2lpdARQIIIQDwJYqrvjAiCCECDrx2274wIgghBGqdfsu+MCIIIQZ6C5X7vjAj0yKSAEUCCCEElpWH+64wIgghBWJUituuMCIIIQZl3On7rjAiCCEGeguV+64wInJSMhA0ow+Eby4Ez4Qm7jACGT1NHQ3tN/+kDU0dD6QNIA1NHbPDDbPPIATCJQAuT4SSTbPPkAyM+KAEDL/8nQxwXy5EzbPHL7AvhMJaC1f/hsAY41UwH4SVNW+Er4S3DIz4WAygBzz0DOcc8LblVQyM+Rw2J/Js7Lf1UwyM5VIMjOWcjOzM3Nzc2aIcjPhQjOgG/PQOLJgQCApgK1B/sAXwQ6UQPsMPhG8uBM+EJu4wDTH/hEWG91+GTR2zwhjiUj0NMB+kAwMcjPhyDOjQQAAAAAAAAAAAAAAAAOZdzp+M8WzMlwji74RCBvEyFvEvhJVQJvEchyz0DKAHPPQM4B+gL0AIBqz0D4RG8VzwsfzMn4RG8U4vsA4wDyAEwkSAE0+ERwb3KAQG90cG9x+GT4QYjIz44rbNbMzslwA0Yw+Eby4Ez4Qm7jACGT1NHQ3tN/+kDU0dD6QNTR2zww2zzyAEwmUAEW+Ev4SccF8uPo2zxCA/Aw+Eby4Ez4Qm7jANMf+ERYb3X4ZNHbPCGOJiPQ0wH6QDAxyM+HIM6NBAAAAAAAAAAAAAAAAAyWlYf4zxbLf8lwji/4RCBvEyFvEvhJVQJvEchyz0DKAHPPQM4B+gL0AIBqz0D4RG8Vzwsfy3/J+ERvFOL7AOMA8gBMKEgAIPhEcG9ygEBvdHBvcfhk+EwEUCCCEDIE7Cm64wIgghBDhPKYuuMCIIIQRFdChLrjAiCCEEap1+y64wIwLiwqA0ow+Eby4Ez4Qm7jACGT1NHQ3tN/+kDU0dD6QNIA1NHbPDDbPPIATCtQAcz4S/hJxwXy4+gkwgDy5Bok+Ey78uQkI/pCbxPXC//DACT4KMcFs7Dy5AbbPHD7AvhMJaG1f/hsAvhLVRN/yM+FgMoAc89AznHPC25VQMjPkZ6C5X7Lf85VIMjOygDMzc3JgQCA+wBRA+Iw+Eby4Ez4Qm7jANMf+ERYb3X4ZNHbPCGOHSPQ0wH6QDAxyM+HIM5xzwthAcjPkxFdChLOzclwjjH4RCBvEyFvEvhJVQJvEchyz0DKAHPPQM4B+gL0AHHPC2kByPhEbxXPCx/Ozcn4RG8U4vsA4wDyAEwtSAAg+ERwb3KAQG90cG9x+GT4SgNAMPhG8uBM+EJu4wAhk9TR0N7Tf/pA0gDU0ds8MNs88gBML1AB8PhK+EnHBfLj8ts8cvsC+EwkoLV/+GwBjjJUcBL4SvhLcMjPhYDKAHPPQM5xzwtuVTDIz5Hqe3iuzst/WcjOzM3NyYEAgKYCtQf7AI4oIfpCbxPXC//DACL4KMcFs7COFCHIz4UIzoBvz0DJgQCApgK1B/sA3uJfA1ED9DD4RvLgTPhCbuMA0x/4RFhvdfhk0x/R2zwhjiYj0NMB+kAwMcjPhyDOjQQAAAAAAAAAAAAAAAALIE7CmM8WygDJcI4v+EQgbxMhbxL4SVUCbxHIcs9AygBzz0DOAfoC9ACAas9A+ERvFc8LH8oAyfhEbxTi+wDjAPIATDFIAJr4RHBvcoBAb3Rwb3H4ZCCCEDIE7Cm6IYIQT0efo7oighAqSsQ+uiOCEFYlSK26JIIQDC/yDbolghB+3B03ulUFghAPAliqurGxsbGxsQRQIIIQEzKpMbrjAiCCEBWgOPu64wIgghAfATKRuuMCIIIQIOvHbbrjAjs3NTMDNDD4RvLgTPhCbuMAIZPU0dDe+kDR2zzjAPIATDRIAUL4S/hJxwXy4+jbPHD7AsjPhQjOgG/PQMmBAICmArUH+wBSA+Iw+Eby4Ez4Qm7jANMf+ERYb3X4ZNHbPCGOHSPQ0wH6QDAxyM+HIM5xzwthAcjPknwEykbOzclwjjH4RCBvEyFvEvhJVQJvEchyz0DKAHPPQM4B+gL0AHHPC2kByPhEbxXPCx/Ozcn4RG8U4vsA4wDyAEw2SAAg+ERwb3KAQG90cG9x+GT4SwNMMPhG8uBM+EJu4wAhltTTH9TR0JPU0x/i+kDU0dD6QNHbPOMA8gBMOEgCePhJ+ErHBSCOgN/y4GTbPHD7AiD6Qm8T1wv/wwAh+CjHBbOwjhQgyM+FCM6Ab89AyYEAgKYCtQf7AN5fBDlRASYwIds8+QDIz4oAQMv/ydD4SccFOgBUcMjL/3BtgED0Q/hKcViAQPQWAXJYgED0Fsj0AMn4TsjPhID0APQAz4HJA/Aw+Eby4Ez4Qm7jANMf+ERYb3X4ZNHbPCGOJiPQ0wH6QDAxyM+HIM6NBAAAAAAAAAAAAAAAAAkzKpMYzxbLH8lwji/4RCBvEyFvEvhJVQJvEchyz0DKAHPPQM4B+gL0AIBqz0D4RG8Vzwsfyx/J+ERvFOL7AOMA8gBMPEgAIPhEcG9ygEBvdHBvcfhk+E0ETCCCCIV++rrjAiCCCzaRmbrjAiCCEAwv8g264wIgghAPAliquuMCR0NAPgM2MPhG8uBM+EJu4wAhk9TR0N76QNHbPDDbPPIATD9QAEL4S/hJxwXy4+j4TPLULsjPhQjOgG/PQMmBAICmILUH+wADRjD4RvLgTPhCbuMAIZPU0dDe03/6QNTR0PpA1NHbPDDbPPIATEFQARb4SvhJxwXy4/LbPEIBmiPCAPLkGiP4TLvy5CTbPHD7AvhMJKG1f/hsAvhLVQP4Sn/Iz4WAygBzz0DOcc8LblVAyM+QZK1Gxst/zlUgyM5ZyM7Mzc3NyYEAgPsAUQNEMPhG8uBM+EJu4wAhltTTH9TR0JPU0x/i+kDR2zww2zzyAExEUAIo+Er4SccF8uPy+E0iuo6AjoDiXwNGRQFy+ErIzvhLAc74TAHLf/hNAcsfUiDLH1IQzvhOAcwj+wQj0CCLOK2zWMcFk9dN0N7XTNDtHu1Tyds8YgEy2zxw+wIgyM+FCM6Ab89AyYEAgKYCtQf7AFED7DD4RvLgTPhCbuMA0x/4RFhvdfhk0ds8IY4lI9DTAfpAMDHIz4cgzo0EAAAAAAAAAAAAAAAACAhX76jPFszJcI4u+EQgbxMhbxL4SVUCbxHIcs9AygBzz0DOAfoC9ACAas9A+ERvFc8LH8zJ+ERvFOL7AOMA8gBMSUgAKO1E0NP/0z8x+ENYyMv/yz/Oye1UACD4RHBvcoBAb3Rwb3H4ZPhOA7wh1h8x+Eby4Ez4Qm7jANs8cvsCINMfMiCCEGeguV+6jj0h038z+EwhoLV/+Gz4SQH4SvhLcMjPhYDKAHPPQM5xzwtuVSDIz5CfQjemzst/AcjOzc3JgQCApgK1B/sATFFLAYyOQCCCEBkrUbG6jjUh038z+EwhoLV/+Gz4SvhLcMjPhYDKAHPPQM5xzwtuWcjPkHDKgrbOy3/NyYEAgKYCtQf7AN7iW9s8UABK7UTQ0//TP9MAMfpA1NHQ+kDTf9Mf1NH4bvht+Gz4a/hq+GP4YgIK9KQg9KFObQQsoAAAAALbPHL7Aon4aon4a3D4bHD4bVFqak8Dpoj4bokB0CD6QPpA03/TH9Mf+kA3XkD4avhr+Gww+G0y1DD4biD6Qm8T1wv/wwAh+CjHBbOwjhQgyM+FCM6Ab89AyYEAgKYCtQf7AN4w2zz4D/IAdGpQAEb4TvhN+Ez4S/hK+EP4QsjL/8s/z4POVTDIzst/yx/MzcntVAEe+CdvEGim/mChtX/bPLYJUgAMghAF9eEAAgE0WlQBAcBVAgPPoFdWAENIAVmHZxK25XVSkUA1uAHGOMneWjMhSxqkQWdAyAImMM0RAgEgWVgAQyAE+QMzZwkbAX69TKqEV1UHZyfJCZqB4G/esE2ZHPnIDxQAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAIGits1cFsEJIrtUyDjAyDA/+MCIMD+4wLyC2xdXHQDiu1E0NdJwwH4Zon4aSHbPNMAAZ+BAgDXGCD5AVj4QvkQ8qje0z8B+EMhufK0IPgjgQPoqIIIG3dAoLnytPhj0x8B2zzyPGpmXgNS7UTQ10nDAfhmItDTA/pAMPhpqTgA3CHHAOMCIdcNH/K8IeMDAds88jxra14BFCCCEBWgOPu64wJfBJAw+EJu4wD4RvJzIZbU0x/U0dCT1NMf4vpA1NHQ+kDR+En4SscFII6A346AjhQgyM+FCM6Ab89AyYEAgKYgtQf7AOJfBNs88gBmY2BvAQhdIts8YQJ8+ErIzvhLAc5wAct/cAHLHxLLH874QYjIz44rbNbMzskBzCH7BAHQIIs4rbNYxwWT103Q3tdM0O0e7VPJ2zxwYgAE8AIBHjAh+kJvE9cL/8MAII6A3mQBEDAh2zz4SccFZQF+cMjL/3BtgED0Q/hKcViAQPQWAXJYgED0Fsj0AMn4QYjIz44rbNbMzsnIz4SA9AD0AM+ByfkAyM+KAEDL/8nQcAIW7UTQ10nCAY6A4w1oZwA07UTQ0//TP9MAMfpA1NHQ+kDR+Gv4avhj+GICVHDtRND0BXEhgED0Do6A33IigED0Do6A3/hr+GqAQPQO8r3XC//4YnD4Y2lpAQKJagBDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAK+Eby4EwCCvSkIPShbm0AFHNvbCAwLjU3LjEBGKAAAAACMNs8+A/yAG8ALPhK+EP4QsjL/8s/z4PO+EvIzs3J7VQADCD4Ye0e2QGxaAHfviGO8dIpvvEd3I+1UQ8XO//4F7Iy3Eysom7qMNim7wArUV4Id/zIbf5WwcsSKVDpS5jLzBylaLX794H5DIuJgxHQmPxYBisxYgAANQkg9nMQxdM9OMByAYtz4iFDAAAAAAAAAAAAAAADRQEYT4AVmHZxK25XVSkUA1uAHGOMneWjMhSxqkQWdAyAImMM0QAAAAAAAAAAAAAAAAR4aMAQcwFDgBWYdnErbldVKRQDW4AcY4yd5aMyFLGqRBZ0DIAiYwzRCHQAAA=="; + +TEST(EverscaleCell, DeserializeTransaction) { + const auto tx = Cell::fromBase64(TX); + ASSERT_EQ(hex(tx->hash), "88a02e7bd8833d384f37d63d4d01deef9a1806937b94a313cc5e8c3cc7643032"); + + const auto bigTx = Cell::fromBase64(BIG_TX); + ASSERT_EQ(hex(bigTx->hash), "37f29d9f3aa5c7cc783962d861c08705f245f5e5bfedd208dfe08d02e80a47d8"); +} + +TEST(EverscaleCell, DeserializeWallet) { + const auto cell = Cell::deserialize(Wallet::code.data(), Wallet::code.size()); + ASSERT_EQ(hex(cell->hash), "84dafa449f98a6987789ba232358072bc0f76dc4524002a5d0918b9a75d2d599"); +} + +TEST(EverscaleCell, SerializeTransaction) { + const auto cell = Cell::fromBase64(TX); + Data data; + cell->serialize(data); + + const auto decoded = Cell::deserialize(data.data(), data.size()); + ASSERT_EQ(hex(decoded->hash), "88a02e7bd8833d384f37d63d4d01deef9a1806937b94a313cc5e8c3cc7643032"); +} + +TEST(EverscaleCell, SerializeWallet) { + const auto cell = Cell::deserialize(Wallet::code.data(), Wallet::code.size()); + Data data; + cell->serialize(data); + + const auto decoded = Cell::deserialize(data.data(), data.size()); + ASSERT_EQ(hex(decoded->hash), "84dafa449f98a6987789ba232358072bc0f76dc4524002a5d0918b9a75d2d599"); +} + +TEST(EverscaleCell, EmptyCell) { + const auto EMPTY_CELL = "96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7"; + + const auto cell1 = Cell::fromBase64("te6ccgEBAQEAAgAAAA=="); + ASSERT_EQ(hex(cell1->hash), EMPTY_CELL); + + // With index + const auto cell2 = Cell::fromBase64("te6ccoEBAQEAAwACAAA="); + ASSERT_EQ(hex(cell2->hash), EMPTY_CELL); + + // With d2 > 0 && d2%8 != 0 but without end flag (computeBitLen should just return 0) + const auto cell3 = Cell::fromBase64("te6ccgEBAQEAAwAABQAAAA=="); + ASSERT_EQ(hex(cell3->hash), EMPTY_CELL); + + // With `storeHashes` (provided hash should be skipped) + const auto cell4 = Cell::fromBase64("te6ccgEBAQEAJAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); + ASSERT_EQ(hex(cell4->hash), EMPTY_CELL); +} + +TEST(EverscaleCell, InvalidCell) { + // unexpected eof + ASSERT_THROW(Cell::fromBase64("te4="), std::runtime_error); + // unknown magic + ASSERT_THROW(Cell::fromBase64("aGVsbG8gd29ybGQK"), std::runtime_error); + // refSize > 4 + ASSERT_THROW(Cell::fromBase64("te6ccgUCAAAAAAEAAAAAAQAAAAAAFD4AAAAAAAO3etQ="), std::runtime_error); + // unsupported root count + ASSERT_THROW(Cell::fromBase64("te6ccgECdRIAFD4AA7d61A=="), std::runtime_error); + // root count is greater than cell count + ASSERT_THROW(Cell::fromBase64("te6ccgECAAEAFD4AA7d61A=="), std::runtime_error); + // absent cells are not supported + ASSERT_THROW(Cell::fromBase64("te6ccgECAQESFD4AA7d61A=="), std::runtime_error); + // non-zero level is not supported + ASSERT_THROW(Cell::fromBase64("te6ccgECAQEAFD4AY7d61FeCHf8yG3+VsHLEilQ6UuYy8wcpWi1+/Q=="), std::runtime_error); + // exotic cells are not supported + ASSERT_THROW(Cell::fromBase64("te6ccgECAQEAFD4AC7d61FeCHf8yG3+VsHLEilQ6UuYy8wcpWi1+/Q=="), std::runtime_error); + // absent cells are not supported + ASSERT_THROW(Cell::fromBase64("te6ccgECAQEAFD4AF7d61FeCHf8yG3+VsHLEilQ6UuYy8wcpWi1+/Q=="), std::runtime_error); + // invalid ref count + ASSERT_THROW(Cell::fromBase64("te6ccgEBAQEAAwAFAA=="), std::runtime_error); + // invalid child index + ASSERT_THROW(Cell::fromBase64("te6ccgEBAQEAJAABAP8="), std::runtime_error); + // invalid child index (reference to itself) + ASSERT_THROW(Cell::fromBase64("te6ccgEBAQEAAgABAAA="), std::runtime_error); + // invalid root index + ASSERT_THROW(Cell::fromBase64("te6ccgEBAQEAAwEAAA"), std::runtime_error); + // child cell not found + ASSERT_THROW(Cell::fromBase64("te6ccgEBAQEAAgABAAE="), std::runtime_error); +} + +} // namespace TW::Everscale diff --git a/tests/chains/Everscale/SignerTests.cpp b/tests/chains/Everscale/SignerTests.cpp new file mode 100644 index 00000000000..832eb068f80 --- /dev/null +++ b/tests/chains/Everscale/SignerTests.cpp @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Everscale/Messages.h" +#include "Everscale/Signer.h" + +#include "Base64.h" +#include "HexCoding.h" + +#include + +using namespace TW; + +namespace TW::Everscale { + +TEST(EverscaleSigner, TransferWithDeploy) { + auto input = Proto::SigningInput(); + + auto& transfer = *input.mutable_transfer(); + transfer.set_bounce(false); + transfer.set_behavior(Proto::MessageBehavior::SimpleTransfer); + transfer.set_amount(500000000); + transfer.set_expired_at(1680770631); + transfer.set_to("0:db18a67f4626f15ac0537a18445937f685f9b30184f0d7b28be4bdeb92d2fd90"); + + // NOTE: There is `set_encoded_contract_data` because contract was not deployed yet + + auto privateKey = parse_hex("542bd4288352f1c6b270046f153d406aec054a0a06000ab9b36b5c6dd3050ad4"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input); + + ASSERT_EQ(hex(Cell::fromBase64(output.encoded())->hash), "bfb18e56e9d00d783c7eb1726f08bf613dd0f01a110a130c0f8f91bb13390a39"); + + // Link to the message: https://everscan.io/messages/bfb18e56e9d00d783c7eb1726f08bf613dd0f01a110a130c0f8f91bb13390a39 + ASSERT_EQ(output.encoded(), "te6ccgICAAQAAQAAAUoAAAPhiAG+Ilaz1wTyTEauoymMGl6o+NGqhszIlHS8BXAmXniYrBGMBTen55/RbfcIBoeCrPB1cxPMcHRx7xyBzJmdtewBPaTu/WuHgnqg09jQaxTEcii+Nuqm7p3b6iMq+/6598ggCXUlsUyF0MjgAAAAAHAAAwACAAEAaEIAbYxTP6MTeK1gKb0MIiyb+0L82YDCeGvZRfJe9clpfsgg7msoAAAAAAAAAAAAAAAAAAAAUAAAAABLqS2KOWKN+7Y5OSiKhKisiw6t/h2ovvR3WbapyAtrdctwupwA3v8AIN0gggFMl7ohggEznLqxn3Gw7UTQ0x/THzHXC//jBOCk8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVA=="); +} + +TEST(EverscaleSigner, Transfer1) { + auto input = Proto::SigningInput(); + + auto& transfer = *input.mutable_transfer(); + transfer.set_bounce(false); + transfer.set_behavior(Proto::MessageBehavior::SimpleTransfer); + transfer.set_amount(100000000); + transfer.set_expired_at(1680770631); + transfer.set_to("0:db18a67f4626f15ac0537a18445937f685f9b30184f0d7b28be4bdeb92d2fd90"); + + transfer.set_encoded_contract_data("te6ccgEBAQEAKgAAUAAAAAFLqS2KOWKN+7Y5OSiKhKisiw6t/h2ovvR3WbapyAtrdctwupw="); + + auto privateKey = parse_hex("542bd4288352f1c6b270046f153d406aec054a0a06000ab9b36b5c6dd3050ad4"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input); + + ASSERT_EQ(hex(Cell::fromBase64(output.encoded())->hash), "73807b0a3ca2d8564c023dccd5b9da222a270f68338c6fc2c064dda376a2c59d"); + + // Link to the message: https://everscan.io/messages/73807b0a3ca2d8564c023dccd5b9da222a270f68338c6fc2c064dda376a2c59d + ASSERT_EQ(output.encoded(), "te6ccgICAAIAAQAAAKoAAAHfiAG+Ilaz1wTyTEauoymMGl6o+NGqhszIlHS8BXAmXniYrAImASIQKH2jIwoA65IGC6aua4gAA4fFo/Nuxgb3sIRELhZnSXIS7IsE2E4D+8hk3EWGVZX+ICqlN/ka9DvXduhaXUlsUyF0MjgAAAAIHAABAGhCAG2MUz+jE3itYCm9DCIsm/tC/NmAwnhr2UXyXvXJaX7IIC+vCAAAAAAAAAAAAAAAAAAA"); +} + +TEST(EverscaleSigner, Transfer2) { + auto input = Proto::SigningInput(); + + auto& transfer = *input.mutable_transfer(); + transfer.set_bounce(true); + transfer.set_behavior(Proto::MessageBehavior::SendAllBalance); + transfer.set_amount(200000000); + transfer.set_expired_at(1680770631); + transfer.set_to("0:df112b59eb82792623575194c60d2f547c68d54366644a3a5e02b8132f3c4c56"); + + transfer.set_encoded_contract_data("te6ccgEBAQEAKgAAUAAAAAJLqS2KOWKN+7Y5OSiKhKisiw6t/h2ovvR3WbapyAtrdctwupw="); + + auto privateKey = parse_hex("542bd4288352f1c6b270046f153d406aec054a0a06000ab9b36b5c6dd3050ad4"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Signer::sign(input); + + ASSERT_EQ(hex(Cell::fromBase64(output.encoded())->hash), "e35616cfa88e115580f07c6b41ae3ded1902d2bab1efefb74f677b4aececef24"); + + // Link to the message: https://everscan.io/messages/e35616cfa88e115580f07c6b41ae3ded1902d2bab1efefb74f677b4aececef24 + ASSERT_EQ(output.encoded(), "te6ccgICAAIAAQAAAKoAAAHfiAG+Ilaz1wTyTEauoymMGl6o+NGqhszIlHS8BXAmXniYrANrT0ivIEpuMGjKoyS9J03Wbl24jowXvdzQdLD6L3USLETUyRGbbmbUfBcNtF1FwKtmIQd0lNR1qIX9K/eloMgaXUlsUyF0MjgAAAAUFAABAGhiAG+Ilaz1wTyTEauoymMGl6o+NGqhszIlHS8BXAmXniYrIF9eEAAAAAAAAAAAAAAAAAAA"); +} + +} // namespace TW::Everscale diff --git a/tests/chains/Everscale/TWAnyAddressTests.cpp b/tests/chains/Everscale/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..743f8c56d79 --- /dev/null +++ b/tests/chains/Everscale/TWAnyAddressTests.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Everscale { + +TEST(TWEverscale, HDWallet) { + auto mnemonic = + STRING("shoot island position soft burden budget tooth cruel issue economy destroy above"); + auto passphrase = STRING(""); + + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(mnemonic.get(), passphrase.get())); + + auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypeEverscale, WRAPS(TWCoinTypeDerivationPath(TWCoinTypeEverscale)).get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey.get())); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeEverscale)); + auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + + assertStringsEqual(addressStr, "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04"); +} + +} // namespace TW::Everscale diff --git a/tests/chains/Everscale/TWAnySignerTests.cpp b/tests/chains/Everscale/TWAnySignerTests.cpp new file mode 100644 index 00000000000..eb101efdee8 --- /dev/null +++ b/tests/chains/Everscale/TWAnySignerTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base64.h" +#include "HexCoding.h" +#include "proto/Everscale.pb.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::Everscale { + +TEST(TWAnySignerEverscale, SignMessageToDeployWallet) { + Proto::SigningInput input; + + auto& transfer = *input.mutable_transfer(); + transfer.set_bounce(false); + transfer.set_behavior(Proto::MessageBehavior::SimpleTransfer); + transfer.set_amount(500000000); + transfer.set_expired_at(1680770631); + transfer.set_to("0:db18a67f4626f15ac0537a18445937f685f9b30184f0d7b28be4bdeb92d2fd90"); + + // NOTE: There is `set_encoded_contract_data` because contract was not deployed yet + + auto privateKey = parse_hex("542bd4288352f1c6b270046f153d406aec054a0a06000ab9b36b5c6dd3050ad4"); + input.set_private_key(privateKey.data(), privateKey.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEverscale); + + ASSERT_EQ(output.encoded(), "te6ccgICAAQAAQAAAUoAAAPhiAG+Ilaz1wTyTEauoymMGl6o+NGqhszIlHS8BXAmXniYrBGMBTen55/RbfcIBoeCrPB1cxPMcHRx7xyBzJmdtewBPaTu/WuHgnqg09jQaxTEcii+Nuqm7p3b6iMq+/6598ggCXUlsUyF0MjgAAAAAHAAAwACAAEAaEIAbYxTP6MTeK1gKb0MIiyb+0L82YDCeGvZRfJe9clpfsgg7msoAAAAAAAAAAAAAAAAAAAAUAAAAABLqS2KOWKN+7Y5OSiKhKisiw6t/h2ovvR3WbapyAtrdctwupwA3v8AIN0gggFMl7ohggEznLqxn3Gw7UTQ0x/THzHXC//jBOCk8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVA=="); +} + +} // namespace TW::Everscale diff --git a/tests/chains/Everscale/TWCoinTypeTests.cpp b/tests/chains/Everscale/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..f4e59de8cd9 --- /dev/null +++ b/tests/chains/Everscale/TWCoinTypeTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +namespace TW::Everscale { + +TEST(TWEverscaleCoinType, TWCoinType) { + const auto coin = TWCoinTypeEverscale; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("781238b2b0d15cd4cd2e2a0a142753750cd5e1b2c8b506fcede75a90e02f1268")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0:d2bf59964a05dee84a0dd1ddc0ad83ba44d49719cf843d689dc8b726d0fb59d8")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "everscale"); + assertStringsEqual(name, "Everscale"); + assertStringsEqual(symbol, "EVER"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 9); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEverscale); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(txUrl, "https://everscan.io/transactions/781238b2b0d15cd4cd2e2a0a142753750cd5e1b2c8b506fcede75a90e02f1268"); + assertStringsEqual(accUrl, "https://everscan.io/accounts/0:d2bf59964a05dee84a0dd1ddc0ad83ba44d49719cf843d689dc8b726d0fb59d8"); +} + +} // namespace TW::Everscale diff --git a/tests/chains/Evmos/SignerTests.cpp b/tests/chains/Evmos/SignerTests.cpp new file mode 100644 index 00000000000..aba0816b53a --- /dev/null +++ b/tests/chains/Evmos/SignerTests.cpp @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Base64.h" +#include "proto/Cosmos.pb.h" +#include "Cosmos/Address.h" +#include "TestUtilities.h" + +#include +#include + +#include +#include + +namespace TW::Cosmos::evmos::tests { + +TEST(EvmosSigner, SignTxJsonEthermintKeyType) { + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::JSON); // obsolete + input.set_account_number(1037); + input.set_chain_id("evmos_9001-2"); + input.set_memo(""); + input.set_sequence(8); + + auto fromAddress = Address("evmos", parse_hex("BC2DA90C84049370D1B7C528BC164BC588833F21")); + auto toAddress = Address("evmos", parse_hex("12E8FE8B81ECC1F4F774EA6EC8DF267138B9F2D9")); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address(fromAddress.string()); + message.set_to_address(toAddress.string()); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("muon"); + amountOfTx->set_amount("1"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("muon"); + amountOfFee->set_amount("200"); + + auto privateKey = parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeNativeEvmos); + auto anotherExpectedJson =R"( + { + "mode":"block", + "tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"}, + "memo":"", + "msg":[{"type":"cosmos-sdk/MsgSend", + "value":{"amount":[{"amount":"1","denom":"muon"}], + "from_address":"evmos1hsk6jryyqjfhp5dhc55tc9jtckygx0ep4mur4z", + "to_address":"evmos1zt50azupanqlfam5afhv3hexwyutnuke45f6ye"}}], + "signatures": + [ + { + "pub_key": + { + "type":"ethermint/PubKeyEthSecp256k1", + "value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" + }, + "signature":"1hMFtRqKjB8tiuyHYVYZundPdomebIIvHLC1gj9uXtFc+iO3UAHBysBjFB4brd9AD5yriS3uUDTAqqfg6fNGNg==" + } + ]} + })"_json; + + /// This tx is not broadcasted, we just want to test the signature format (ethermint/PubKeyEthSecp256k1) + EXPECT_EQ(anotherExpectedJson, nlohmann::json::parse(output.json())); + + auto signatures = nlohmann::json::parse(output.signature_json()); + + auto expectedSignatures = R"( + [ + { + "pub_key": + { + "type":"ethermint/PubKeyEthSecp256k1", + "value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F" + }, + "signature":"1hMFtRqKjB8tiuyHYVYZundPdomebIIvHLC1gj9uXtFc+iO3UAHBysBjFB4brd9AD5yriS3uUDTAqqfg6fNGNg==" + } + ])"_json; + EXPECT_EQ(signatures, expectedSignatures); +} + +TEST(EvmosSigner, CompoundingAuthz) { + // Successfully broadcasted https://www.mintscan.io/evmos/txs/8D811CEC078420C41220F0B584EA0AC037763380FAC31E0E352E4BB4D1D18B79 + auto input = Proto::SigningInput(); + input.set_signing_mode(Proto::Protobuf); + input.set_account_number(2139877); + input.set_chain_id("evmos_9001-2"); + input.set_memo(""); + input.set_sequence(3); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_auth_grant(); + message.set_granter("evmos12m9grgas60yk0kult076vxnsrqz8xpjy9rpf3e"); + message.set_grantee("evmos18fzq4nac28gfma6gqfvkpwrgpm5ctar2z9mxf3"); + auto& grant_stake = *message.mutable_grant_stake(); + grant_stake.mutable_allow_list()->add_address("evmosvaloper1umk407eed7af6anvut6llg2zevnf0dn0feqqny"); + grant_stake.set_authorization_type(TW::Cosmos::Proto::Message_AuthorizationType_DELEGATE); + message.set_expiration(1692309600); + + auto& fee = *input.mutable_fee(); + fee.set_gas(180859); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("aevmos"); + amountOfFee->set_amount("4521475000000000"); + + auto privateKey = parse_hex("79bcbded1a5678ab34e6d9db9ad78e4e480e7b22723cc5fbf59e843732e1a8e5"); + input.set_private_key(privateKey.data(), privateKey.size()); + + auto output = Proto::SigningOutput(); + ANY_SIGN(input, TWCoinTypeNativeEvmos); + // Please note the signature has been updated according to the serialization of the `StakeAuthorization` message. + // Previous: CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5EjUKM2V2bW9zdmFsb3BlcjF1bWs0MDdlZWQ3YWY2YW52dXQ2bGxnMnpldm5mMGRuMGZlcXFueSABEgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkAm17CZgB7m+CPVlITnrHosklMTL9zrUeGRs8FL8N0GcRami9zdJ+e3xuXOtJmwP7G6QNh85CRYjFj8a8lpmmJM + auto expected = R"( + { + "mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5IAESNQozZXZtb3N2YWxvcGVyMXVtazQwN2VlZDdhZjZhbnZ1dDZsbGcyemV2bmYwZG4wZmVxcW55EgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkBXaTo3nk5EMFW9Euheez5ADx2bWo7XisNJ5vuGj1fKXh6CGNJGfJj/q1XUkBzaCvPNg+EcFHgtJdVSyF4cJZTg" + })"; + assertJSONEqual(output.serialized(), expected); +} +} diff --git a/tests/chains/Evmos/TWAnyAddressTests.cpp b/tests/chains/Evmos/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..d36bfa9c64f --- /dev/null +++ b/tests/chains/Evmos/TWAnyAddressTests.cpp @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include +#include +#include + +#include + +namespace TW::Evmos::tests { + +TEST(EvmosAnyAddress, EvmosValidate) { + auto string = STRING("0x30627903124Aa1e71384bc52e1cb96E4AB3252b6"); + + EXPECT_TRUE(TWAnyAddressIsValid(string.get(), TWCoinTypeEvmos)); + + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeEvmos)); + + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "30627903124aa1e71384bc52e1cb96e4ab3252b6"); +} + +TEST(EvmosAnyAddress, EvmosCreate) { + auto publicKeyHex = "045a0c6b83b8bd9827e507270cadb499b7e3a9095246f6a2213281f783d877c98b256742741b0639f317768fe4f4c2762660c2112283a7685d815507dee3229173"; // shoot island position ... + const auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyHex).get(), TWPublicKeyTypeSECP256k1Extended)); + + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeEvmos)); + + EXPECT_EQ(std::string(TWStringUTF8Bytes(WRAPS(TWAnyAddressDescription(addr.get())).get())), std::string("0x8f348F300873Fd5DA36950B2aC75a26584584feE")); + + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "8f348f300873fd5da36950b2ac75a26584584fee"); +} + +} // namespace TW::Evmos::tests diff --git a/tests/chains/Evmos/TWCoinTypeTests.cpp b/tests/chains/Evmos/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..781e1e4e22b --- /dev/null +++ b/tests/chains/Evmos/TWCoinTypeTests.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +namespace TW::Evmos::tests { + +TEST(TWEvmosCoinType, TWCoinTypeEvmos) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeEvmos)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x24af42cf4977a96d62e3a82c3cd9b519c3e7c53dd83398b88f0cb435d867b422")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeEvmos, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x30627903124Aa1e71384bc52e1cb96E4AB3252b6")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeEvmos, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeEvmos)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeEvmos)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeEvmos), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeEvmos)); + + assertStringsEqual(symbol, "EVMOS"); + assertStringsEqual(txUrl, "https://evm.evmos.org/tx/0x24af42cf4977a96d62e3a82c3cd9b519c3e7c53dd83398b88f0cb435d867b422"); + assertStringsEqual(accUrl, "https://evm.evmos.org/address/0x30627903124Aa1e71384bc52e1cb96E4AB3252b6"); + assertStringsEqual(id, "evmos"); + assertStringsEqual(name, "Evmos"); +} + +} // namespace TW::Evmos::tests diff --git a/tests/chains/Evmos/TransactionCompilerTests.cpp b/tests/chains/Evmos/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..47a700bdf94 --- /dev/null +++ b/tests/chains/Evmos/TransactionCompilerTests.cpp @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base64.h" +#include "HexCoding.h" +#include "proto/Cosmos.pb.h" +#include "proto/TransactionCompiler.pb.h" +#include "TrustWalletCore/TWAnySigner.h" +#include "TestUtilities.h" +#include "TransactionCompiler.h" + +namespace TW::Cosmos::evmos::tests { + +TEST(EvmosCompiler, CompileWithSignatures) { + // Successfully broadcasted: https://www.mintscan.io/evmos/transactions/02105B186FCA473C9F467B2D3BF487F6CE5DB26EE54BCD1667DDB7A2DA0E2489 + + const auto coin = TWCoinTypeNativeEvmos; + TW::Cosmos::Proto::SigningInput input; + + PrivateKey privateKey = + PrivateKey(parse_hex("727513ec3c54eb6fae24f2ff756bbc4c89b82945c6538bbd173613ae3de719d3")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + /// Step 1: Prepare transaction input (protobuf) + input.set_account_number(106619981); + input.set_chain_id("evmos_9001-2"); + input.set_memo(""); + input.set_sequence(0); + + PublicKey publicKey = privateKey.getPublicKey(TWCoinTypePublicKeyType(coin)); + const auto pubKeyBz = publicKey.bytes; + ASSERT_EQ(hex(pubKeyBz), "04088ac2919987d927368cb2be2ade44cd0ed3616745a9699cae264b3fc5a7c3607d99f441b8340990ee990cb3eaf560f1f0bafe600c7e94a4be8392166984f728"); + input.set_public_key(pubKeyBz.data(), pubKeyBz.size()); + + auto msg = input.add_messages(); + auto& message = *msg->mutable_send_coins_message(); + message.set_from_address("evmos1d0jkrsd09c7pule43y3ylrul43lwwcqa7vpy0g"); + message.set_to_address("evmos17dh3frt0m6kdd3m9lr6e6sr5zz0rz8cvxd7u5t"); + auto amountOfTx = message.add_amounts(); + amountOfTx->set_denom("aevmos"); + amountOfTx->set_amount("10000000000000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(137840); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("aevmos"); + amountOfFee->set_amount("5513600000000000"); + + /// Step 2: Obtain protobuf preimage hash + input.set_signing_mode(TW::Cosmos::Proto::Protobuf); + auto protoInputString = input.SerializeAsString(); + auto protoInputData = TW::Data(protoInputString.begin(), protoInputString.end()); + + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, protoInputData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + auto preImage = preSigningOutput.data(); + auto preImageHash = preSigningOutput.data_hash(); + + EXPECT_EQ( + hex(preImage), + "0a9c010a99010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412790a2c65766d6f733164306a6b7273643039633770756c6534337933796c72756c34336c7777637161377670793067122c65766d6f733137646833667274306d366b6464336d396c723665367372357a7a30727a3863767864377535741a1b0a066165766d6f7312113130303030303030303030303030303030127b0a570a4f0a282f65746865726d696e742e63727970746f2e76312e657468736563703235366b312e5075624b657912230a2102088ac2919987d927368cb2be2ade44cd0ed3616745a9699cae264b3fc5a7c36012040a02080112200a1a0a066165766d6f7312103535313336303030303030303030303010f0b4081a0c65766d6f735f393030312d3220cdc8eb32"); + EXPECT_EQ(hex(preImageHash), + "9912eb629e215027b8d587939b1af72a9f70ae326bcaf48dfe77a729fc4ac632"); + + + auto expectedTx = R"({"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpwBCpkBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnkKLGV2bW9zMWQwamtyc2QwOWM3cHVsZTQzeTN5bHJ1bDQzbHd3Y3FhN3ZweTBnEixldm1vczE3ZGgzZnJ0MG02a2RkM205bHI2ZTZzcjV6ejByejhjdnhkN3U1dBobCgZhZXZtb3MSETEwMDAwMDAwMDAwMDAwMDAwEnsKVwpPCigvZXRoZXJtaW50LmNyeXB0by52MS5ldGhzZWNwMjU2azEuUHViS2V5EiMKIQIIisKRmYfZJzaMsr4q3kTNDtNhZ0WpaZyuJks/xafDYBIECgIIARIgChoKBmFldm1vcxIQNTUxMzYwMDAwMDAwMDAwMBDwtAgaQKrmMaaSKnohf3ahyCOYdRJKBKJjr4WkkA/cbn6FRdF0Gd6FHSzBP8S4v4VNiy3KC47TD0C+sUBO413gCzjo8/U="})"; + Data signature; + + { + TW::Cosmos::Proto::SigningOutput output; + ANY_SIGN(input, coin); + assertJSONEqual( + output.serialized(), + expectedTx); + + signature = data(output.signature()); + EXPECT_EQ(hex(signature), + "aae631a6922a7a217f76a1c8239875124a04a263af85a4900fdc6e7e8545d17419de851d2cc13fc4b8bf854d8b2dca0b8ed30f40beb1404ee35de00b38e8f3f5"); + + ASSERT_TRUE(publicKey.verify(signature, data(preImageHash.data()))); + } + + { + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, protoInputData, {signature}, {publicKey.bytes}); + Cosmos::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(output.serialized(), expectedTx); + EXPECT_EQ(hex(output.signature()), hex(signature)); + } +} + +} diff --git a/tests/chains/FIO/AddressTests.cpp b/tests/chains/FIO/AddressTests.cpp new file mode 100644 index 00000000000..ffc99746168 --- /dev/null +++ b/tests/chains/FIO/AddressTests.cpp @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "FIO/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include + +#include + +namespace TW::FIO::tests { + +TEST(FIOAddress, ValidateString) { + ASSERT_FALSE(Address::isValid("abc")); + ASSERT_FALSE(Address::isValid("65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjTF")); + ASSERT_FALSE(Address::isValid("EOS65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjT")); + + ASSERT_TRUE(Address::isValid("FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o")); +} + +TEST(FIOAddress, ValidateData) { + Address address("FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); + EXPECT_EQ(address.bytes.size(), 37ul); + Data addrData = TW::data(address.bytes.data(), address.bytes.size()); + + EXPECT_EQ(Address::isValid(addrData), true); + + // create invalid data, too short + Data addressDataShort = subData(addrData, 0, addrData.size() - 1); + EXPECT_EQ(Address::isValid(addressDataShort), false); +} + +TEST(FIOAddress, FromString) { + Address addr("FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); + ASSERT_EQ(addr.string(), "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); + ASSERT_EQ(hex(addr.bytes), "0271195c66ec2799e436757a70cd8431d4b17733a097b18a5f7f1b6b085978ff0f343fc54e"); +} + +TEST(FIOAddress, FromStringInvalid) { + try { + Address address("WRP5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); + } catch (std::invalid_argument&) { + return; // ok + } + ADD_FAILURE() << "Missed expected exeption"; +} + +TEST(FIOAddress, FromPublicKey) { + auto key = PrivateKey(parse_hex("ea8eb60b7e5868e218f248e032769020b4fea5dcfd02f2992861eaf4fb534854")); + auto publicKey = key.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(publicKey.bytes), "0271195c66ec2799e436757a70cd8431d4b17733a097b18a5f7f1b6b085978ff0f"); + auto address = Address(publicKey); + + ASSERT_EQ(address.string(), "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); +} + +TEST(FIOAddress, GetPublicKey) { + const auto publicKeyHex = "0271195c66ec2799e436757a70cd8431d4b17733a097b18a5f7f1b6b085978ff0f"; + const PublicKey publicKey(parse_hex(publicKeyHex), TWPublicKeyTypeSECP256k1); + auto address = Address(publicKey); + EXPECT_EQ(hex(address.publicKey().bytes), publicKeyHex); +} + +} // namespace TW::FIO::tests diff --git a/tests/FIO/EncryptionTests.cpp b/tests/chains/FIO/EncryptionTests.cpp similarity index 94% rename from tests/FIO/EncryptionTests.cpp rename to tests/chains/FIO/EncryptionTests.cpp index 55aa29793f1..db8341d9eb4 100644 --- a/tests/FIO/EncryptionTests.cpp +++ b/tests/chains/FIO/EncryptionTests.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "FIO/Encryption.h" #include "FIO/Address.h" @@ -15,8 +13,8 @@ #include -using namespace TW; -using namespace TW::FIO; +namespace TW::FIO::EncryptionTests { + using namespace std; TEST(FIOEncryption, checkEncrypt) { @@ -24,7 +22,7 @@ TEST(FIOEncryption, checkEncrypt) { const Data secret = parse_hex("02332627b9325cb70510a70f0f6be4bcb008fbbc7893ca51dedf5bf46aa740c0fc9d3fbd737d09a3c4046d221f4f1a323f515332c3fef46e7f075db561b1a2c9"); const Data plaintext = TW::data("secret message"); Data iv = parse_hex("f300888ca4f512cebdc0020ff0f7224c"); - + Data result = Encryption::checkEncrypt(secret, plaintext, iv); EXPECT_EQ(hex(result), "f300888ca4f512cebdc0020ff0f7224c7f896315e90e172bed65d005138f224da7301d5563614e3955750e4480aabf7753f44b4975308aeb8e23c31e114962ab"); } @@ -34,7 +32,7 @@ TEST(FIOEncryption, checkDecrypt) { const Data secret = parse_hex("02332627b9325cb70510a70f0f6be4bcb008fbbc7893ca51dedf5bf46aa740c0fc9d3fbd737d09a3c4046d221f4f1a323f515332c3fef46e7f075db561b1a2c9"); const Data encrypted = parse_hex("f300888ca4f512cebdc0020ff0f7224c7f896315e90e172bed65d005138f224da7301d5563614e3955750e4480aabf7753f44b4975308aeb8e23c31e114962ab"); const Data expectedPlaintext = TW::data("secret message"); - + Data result = Encryption::checkDecrypt(secret, encrypted); EXPECT_EQ(hex(result), hex(expectedPlaintext)); } @@ -53,7 +51,7 @@ TEST(FIOEncryption, checkEncryptInvalidIvLength) { TEST(FIOEncryption, checkDecryptInvalidMessageHMAC) { const Data secret = parse_hex("02332627b9325cb70510a70f0f6be4bcb008fbbc7893ca51dedf5bf46aa740c0fc9d3fbd737d09a3c4046d221f4f1a323f515332c3fef46e7f075db561b1a2c9"); const Data encrypted = parse_hex("f300888ca4f512cebdc0020ff0f7224c7f896315e90e172bed65d005138f224da7301d5563614e3955750e4480aabf7753f44b4975308aeb8e23c31e114962ab00"); - try { + try { Encryption::checkDecrypt(secret, encrypted); } catch (std::invalid_argument&) { // expected exception, OK @@ -64,7 +62,7 @@ TEST(FIOEncryption, checkDecryptInvalidMessageHMAC) { TEST(FIOEncryption, checkDecryptMessageTooShort) { const Data secret = parse_hex("02332627b9325cb70510a70f0f6be4bcb008fbbc7893ca51dedf5bf46aa740c0fc9d3fbd737d09a3c4046d221f4f1a323f515332c3fef46e7f075db561b1a2c9"); - try { + try { Encryption::checkDecrypt(secret, Data(60)); } catch (std::invalid_argument&) { // expected exception, OK @@ -75,7 +73,7 @@ TEST(FIOEncryption, checkDecryptMessageTooShort) { Data randomBuffer(size_t size) { Data d(size); - for (auto i = 0; i < size; ++i) { + for (auto i = 0ul; i < size; ++i) { d[i] = (TW::byte)(256.0 * rand() / RAND_MAX); } return d; @@ -116,21 +114,21 @@ TEST(FIOEncryption, getSharedSecret) { const PrivateKey privateKey(parse_hex("2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90")); const PublicKey publicKey(parse_hex("024edfcf9dfe6c0b5c83d1ab3f78d1b39a46ebac6798e08e19761f5ed89ec83c10"), TWPublicKeyTypeSECP256k1); Data secret = Encryption::getSharedSecret(privateKey, publicKey); - EXPECT_EQ(secret.size(), 64); + EXPECT_EQ(secret.size(), 64ul); EXPECT_EQ(hex(secret), "a71b4ec5a9577926a1d2aa1d9d99327fd3b68f6a1ea597200a0d890bd3331df300a2d49fec0b2b3e6969ce9263c5d6cf47c191c1ef149373ecc9f0d98116b598"); } { const PrivateKey privateKey(parse_hex("81b637d8fcd2c6da6359e6963113a1170de795e4b725b84d1e0b4cfd9ec58ce9")); const PublicKey publicKey(parse_hex("039997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be"), TWPublicKeyTypeSECP256k1); Data secret = Encryption::getSharedSecret(privateKey, publicKey); - EXPECT_EQ(secret.size(), 64); + EXPECT_EQ(secret.size(), 64ul); EXPECT_EQ(hex(secret), "a71b4ec5a9577926a1d2aa1d9d99327fd3b68f6a1ea597200a0d890bd3331df300a2d49fec0b2b3e6969ce9263c5d6cf47c191c1ef149373ecc9f0d98116b598"); } { const PrivateKey privateKey(parse_hex("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); const PublicKey publicKey(parse_hex("03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd"), TWPublicKeyTypeSECP256k1); Data secret = Encryption::getSharedSecret(privateKey, publicKey); - EXPECT_EQ(secret.size(), 64); + EXPECT_EQ(secret.size(), 64ul); EXPECT_EQ(hex(secret), "3f0840df1912e24d85f39008a56550c31403e096fce7fa9d7886fab8e5c2ceb66b4139c8f4f4172fd9f455e76c2e8913a3d734f51a1951090ce9ec660671957d"); } } @@ -170,7 +168,7 @@ TEST(FIOEncryption, encryptEncodeDecodeDecrypt) { EXPECT_EQ(addressBob.string(), "FIO5VE6Dgy9FUmd1mFotXwF88HkQN1KysCWLPqpVnDMjRvGRi1YrM"); const Data message = parse_hex("0b70757273652e616c69636501310a66696f2e7265716f6274000000"); const Data iv = parse_hex("f300888ca4f512cebdc0020ff0f7224c"); - + const Data encrypted = Encryption::encrypt(privateKeyAlice, publicKeyBob, message, iv); EXPECT_EQ(hex(encrypted), "f300888ca4f512cebdc0020ff0f7224c0db2984c4ad9afb12629f01a8c6a76328bbde17405655dc4e3cb30dad272996fb1dea8e662e640be193e25d41147a904c571b664a7381ab41ef062448ac1e205"); const string encoded = Encryption::encode(encrypted); @@ -182,3 +180,5 @@ TEST(FIOEncryption, encryptEncodeDecodeDecrypt) { // verify that decrypted is the same as the original EXPECT_EQ(hex(decrypted), hex(message)); } + +} // namespace TW::FIO::EncryptionTests diff --git a/tests/chains/FIO/SignerTests.cpp b/tests/chains/FIO/SignerTests.cpp new file mode 100644 index 00000000000..a3104e15afc --- /dev/null +++ b/tests/chains/FIO/SignerTests.cpp @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "FIO/Actor.h" +#include "FIO/Signer.h" +#include "FIO/TransactionBuilder.h" + +#include "Base58.h" +#include "Hash.h" +#include "HexCoding.h" + +#include + +namespace TW::FIO::tests { + +using namespace std; + +TEST(FIOSigner, SignEncode) { + string sig1 = Signer::signatureToBase58(parse_hex("1f4fccc30bcba876963aef6de584daf7258306c02f4528fe25b116b517de8b349968bdc080cd6bef36f5a46d31a7c01ed0806ad215bb66a94f61e27a895d610983")); + + EXPECT_EQ("SIG_K1_K5hJTPeiV4bDkNR13mf66N2DY5AtVL4NU1iWE4G4dsczY2q68oCcUVxhzFdxjgV2eAeb2jNV1biqtCJ3SaNL8kkNgoZ43H", sig1); +} + +TEST(FIOSigner, SignInternals) { + // 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf + PrivateKey pk = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); + { + Data pk2 = parse_hex("80"); + append(pk2, pk.bytes); + EXPECT_EQ("5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri", Base58::encodeCheck(pk2)); + } + Data rawData = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c6574000000000000000000000000000000000000000000000000000000000000000000"); + Data hash = Hash::sha256(rawData); + EXPECT_EQ("6a82a57fb9bfc43918aa757d6094ba71fa2c7ece1691c4b8551a0607273771d7", hex(hash)); + Data sign2 = Signer::signData(pk, rawData); + EXPECT_EQ("1f6ccee1f4cd188cc8aefa63f8fda8c90c0493ca1504806d3a26a7300a9687bb701f188337bc9a32f01ee0c2ecf030aee197b050460d72f7272cc6ce36ef14c95b", hex(sign2)); + + string sigStr = Signer::signatureToBase58(sign2); + EXPECT_EQ("SIG_K1_K9VRCnvaTYN7vgcoVKVXgyJTdKUGV8hLXgFLoEbvqAcFxy7DXQ1rSnAfEuabi4ATkgmvnpaSBdVFN7TBtM1wrbZYqeJQw9", sigStr); + EXPECT_TRUE(Signer::verify(pk.getPublicKey(TWPublicKeyTypeSECP256k1), hash, sign2)); +} + +TEST(FIOSigner, Actor) { + { + const auto addr1 = "FIO6cDpi7vPnvRwMEdXtLnAmFwygaQ8CzD7vqKLBJ2GfgtHBQ4PPy"; + Address addr = Address(addr1); + EXPECT_EQ(addr1, addr.string()); + + uint64_t shortenedKey = Actor::shortenKey(addr.bytes); + EXPECT_EQ(1518832697283783336U, shortenedKey); + string name = Actor::name(shortenedKey); + EXPECT_EQ("2odzomo2v4pec", name); + } + const int n = 4; + const string addrArr[n] = { + "FIO6cDpi7vPnvRwMEdXtLnAmFwygaQ8CzD7vqKLBJ2GfgtHBQ4PPy", + "FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE", + "FIO7bxrQUTbQ4mqcoefhWPz1aFieN4fA9RQAiozRz7FrUChHZ7Rb8", + "FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf", + }; + const string actorArr[n] = { + "2odzomo2v4pe", + "hhq2g4qgycfb", + "5kmx4qbqlpld", + "qdfejz2a5wpl", + }; + for (int i = 0; i < n; ++i) { + Address addr = Address(addrArr[i]); + EXPECT_EQ(addrArr[i], addr.string()); + + string actor = Actor::actor(addr); + EXPECT_EQ(actorArr[i], actor); + } +} + +TEST(FIOSigner, compile) { + const Data chainId = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77"); + // 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf + const PrivateKey privKeyBA = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); + const PublicKey pubKey6M = privKeyBA.getPublicKey(TWPublicKeyTypeSECP256k1); + const Address addr6M(pubKey6M); + Proto::SigningInput input; + input.set_expiry(1579790000); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(addr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(50000); + input.mutable_chain_params()->set_ref_block_prefix(4000123456); + + input.mutable_action()->mutable_transfer_message()->set_amount(1000000000); + input.mutable_action()->mutable_transfer_message()->set_fee(250000000); + input.mutable_action()->mutable_transfer_message()->set_payee_public_key("FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE"); + + auto txBytes = TransactionBuilder::buildUnsignedTxBytes(input); + // create signature + Data sigBuf(chainId); + append(sigBuf, txBytes); + append(sigBuf, TW::Data(32)); // context_free + Data signature = Signer::signData(privKeyBA, sigBuf); + + Proto::SigningOutput result = Signer::compile(input, signature); + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K9VRCnvaTYN7vgcoVKVXgyJTdKUGV8hLXgFLoEbvqAcFxy7DXQ1rSnAfEuabi4ATkgmvnpaSBdVFN7TBtM1wrbZYqeJQw9"]})" + , result.json()); + EXPECT_EQ(result.action_name(), "trnsfiopubky"); +} + +} // namespace TW::FIO::tests diff --git a/tests/chains/FIO/TWCoinTypeTests.cpp b/tests/chains/FIO/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..e961f70263e --- /dev/null +++ b/tests/chains/FIO/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWFIOCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeFIO)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("930d1d3cf8988b39b5f64b64e9d61314a3e05a155d9e3505bdf863aab1adddf3")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeFIO, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("f5axfpgffiqz")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeFIO, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeFIO)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeFIO)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeFIO), 9); + ASSERT_EQ(TWBlockchainFIO, TWCoinTypeBlockchain(TWCoinTypeFIO)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeFIO)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeFIO)); + assertStringsEqual(symbol, "FIO"); + assertStringsEqual(txUrl, "https://explorer.fioprotocol.io/transaction/930d1d3cf8988b39b5f64b64e9d61314a3e05a155d9e3505bdf863aab1adddf3"); + assertStringsEqual(accUrl, "https://explorer.fioprotocol.io/account/f5axfpgffiqz"); + assertStringsEqual(id, "fio"); + assertStringsEqual(name, "FIO"); +} diff --git a/tests/FIO/TWFIOAccountTests.cpp b/tests/chains/FIO/TWFIOAccountTests.cpp similarity index 83% rename from tests/FIO/TWFIOAccountTests.cpp rename to tests/chains/FIO/TWFIOAccountTests.cpp index 3c4764d4801..2afa675188e 100644 --- a/tests/FIO/TWFIOAccountTests.cpp +++ b/tests/chains/FIO/TWFIOAccountTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/FIO/TWFIOTests.cpp b/tests/chains/FIO/TWFIOTests.cpp new file mode 100644 index 00000000000..1c4f35285b3 --- /dev/null +++ b/tests/chains/FIO/TWFIOTests.cpp @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include +#include "proto/FIO.pb.h" +#include "FIO/Address.h" +#include "Data.h" +#include "TestUtilities.h" +#include "HexCoding.h" +#include "PrivateKey.h" + +#include + +namespace TW::FIO::TWFIOTests { + +using namespace std; + +TEST(TWFIO, Address) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035").get())); + ASSERT_NE(nullptr, privateKey.get()); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false)); + ASSERT_NE(nullptr, publicKey.get()); + ASSERT_EQ(65ul, publicKey.get()->impl.bytes.size()); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeFIO)); + auto addressString = WRAPS(TWAnyAddressDescription(address.get())); + assertStringsEqual(addressString, "FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf"); + + auto address2 = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING("FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf").get(), TWCoinTypeFIO)); + ASSERT_NE(nullptr, address2.get()); + auto address2String = WRAPS(TWAnyAddressDescription(address2.get())); + assertStringsEqual(address2String, "FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf"); + + ASSERT_TRUE(TWAnyAddressEqual(address.get(), address2.get())); +} + +const Data gChainId = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77"); +const Data gChainIdMainnet = parse_hex("21dcae42c0182200e93f954a074011f9048a7624c6fe81d3c9541a614a88bd1c"); +// 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf +const PrivateKey privKeyBA = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); +const PublicKey pubKey6M = privKeyBA.getPublicKey(TWPublicKeyTypeSECP256k1); +const Address addr6M(pubKey6M); + +TEST(TWFIO, RegisterFioAddress) { + Proto::SigningInput input; + input.set_expiry(1579784511); + input.mutable_chain_params()->set_chain_id(string(gChainId.begin(), gChainId.end())); + input.mutable_chain_params()->set_head_block_number(39881); + input.mutable_chain_params()->set_ref_block_prefix(4279583376); + input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); + input.set_tpid("rewards@wallet"); + input.mutable_action()->mutable_register_fio_address_message()->set_fio_address("adam@fiotestnet"); + input.mutable_action()->mutable_register_fio_address_message()->set_owner_fio_public_key(addr6M.string()); + input.mutable_action()->mutable_register_fio_address_message()->set_fee(5000000000); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeFIO); + EXPECT_EQ(Common::Proto::OK, output.error()); + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"3f99295ec99b904215ff0000000001003056372503a85b0000c6eaa66498ba01102b2f46fca756b200000000a8ed3232650f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K19ugLriG3ApYgjJCRDsy21p9xgsjbDtqBuZrmAEix9XYzndR1kNbJ6fXCngMJMAhxUHfwHAsPnh58otXiJZkazaM1EkS5"]})", output.json()); + EXPECT_EQ(output.action_name(), "regaddress"); +} + +TEST(TWFIO, AddPubAddress) { + Proto::SigningInput input; + input.set_expiry(1579729429); + input.mutable_chain_params()->set_chain_id(string(gChainId.begin(), gChainId.end())); + input.mutable_chain_params()->set_head_block_number(11565); + input.mutable_chain_params()->set_ref_block_prefix(4281229859); + input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); + input.set_tpid("rewards@wallet"); + auto action = input.mutable_action()->mutable_add_pub_address_message(); + action->set_fio_address("adam@fiotestnet"); + action->add_public_addresses(); + action->add_public_addresses(); + action->add_public_addresses(); + action->mutable_public_addresses(0)->set_coin_symbol("BTC"); + action->mutable_public_addresses(0)->set_address("bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"); + action->mutable_public_addresses(1)->set_coin_symbol("ETH"); + action->mutable_public_addresses(1)->set_address("0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a8F51"); + action->mutable_public_addresses(2)->set_coin_symbol("BNB"); + action->mutable_public_addresses(2)->set_address("bnb1ts3dg54apwlvr9hupv2n0j6e46q54znnusjk9s"); + action->set_fee(0); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeFIO); + EXPECT_EQ(Common::Proto::OK, output.error()); + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"15c2285e2d2d23622eff0000000001003056372503a85b0000c6eaa664523201102b2f46fca756b200000000a8ed3232c9010f6164616d4066696f746573746e65740303425443034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c787770373064377603455448034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e4203424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b39730000000000000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K3zimaMKU8cBhVRPw46KM2u7uQWaAKXrnoeYZ7MEbp6sVJcDQmQR2RtdavpUPwkAnYUkd8NqLun8H48tcxZBcTUgkiPGVJ"]})", output.json()); + EXPECT_EQ(output.action_name(), "addaddress"); +} + +TEST(TWFIO, RemovePubAddress) { + auto privateKey = parse_hex("93083dc4d9e8f613a57e3a862a1fa5d665c5e90141a8428990c945d1c2b56491"); + + Proto::SigningInput input; + input.set_expiry(1713269931); + input.mutable_chain_params()->set_chain_id(string(gChainIdMainnet.begin(), gChainIdMainnet.end())); + input.mutable_chain_params()->set_head_block_number(256054093); + input.mutable_chain_params()->set_ref_block_prefix(2438027034); + input.set_private_key(string(privateKey.begin(), privateKey.end())); + input.set_tpid("trust@fiomembers"); + auto action = input.mutable_action()->mutable_remove_pub_address_message(); + action->set_fio_address("sergeitrust@wallet"); + action->add_public_addresses(); + action->mutable_public_addresses(0)->set_coin_symbol("BTC"); + action->mutable_public_addresses(0)->set_address("bc1q68caps3gqt2c9qxtnkhmzf3whxenrs9cav4wlm"); + action->set_fee(4878336459); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeFIO); + EXPECT_EQ(Common::Proto::OK, output.error()); + std::cout << output.json() << std::endl; + // Successfully broadcasted: https://fio.bloks.io/transaction/0bb6da24a3ea9e3ee57906de1cfa8bad18709acd64bf30908713dd61c54cfaea + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"ab6c1e664d131a5751910000000001003056372503a85b0000c6eaa664a4ba01b038b9d6c13372f700000000a8ed3232681273657267656974727573744077616c6c65740103425443034254432a62633171363863617073336771743263397178746e6b686d7a6633776878656e72733963617634776c6dcb81c52201000000b038b9d6c13372f71074727573744066696f6d656d6265727300","signatures":["SIG_K1_K3cKHXCFYYB9aLFc9qk2idmWgEA4Q9192fECc3cF7MYHXkw9kZamdeHv3qbVoifG9oS8h6nVAJwJvj5YcnhHmnd3u89ND7"]})", output.json()); + EXPECT_EQ(output.action_name(), "remaddress"); +} + +TEST(TWFIO, RemoveAllPubAddresses) { + auto privateKey = parse_hex("93083dc4d9e8f613a57e3a862a1fa5d665c5e90141a8428990c945d1c2b56491"); + + Proto::SigningInput input; + input.set_expiry(1713458993); + input.mutable_chain_params()->set_chain_id(string(gChainIdMainnet.begin(), gChainIdMainnet.end())); + input.mutable_chain_params()->set_head_block_number(256432311); + input.mutable_chain_params()->set_ref_block_prefix(2287536876); + input.set_private_key(string(privateKey.begin(), privateKey.end())); + input.set_tpid("trust@fiomembers"); + auto action = input.mutable_action()->mutable_remove_all_pub_addresses_message(); + action->set_fio_address("sergeitrust@wallet"); + action->set_fee(0); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeFIO); + EXPECT_EQ(Common::Proto::OK, output.error()); + // Successfully broadcasted: https://fio.bloks.io/transaction/f2facdebfcba1981377537424a6d7b7e7ebd8222c87ba4d25a480d1b968704b2 + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"314f2166b7d8ec0a59880000000001003056372503a85b00c04dc9c468a4ba01b038b9d6c13372f700000000a8ed3232341273657267656974727573744077616c6c65740000000000000000b038b9d6c13372f71074727573744066696f6d656d6265727300","signatures":["SIG_K1_KXXtpz7NWhzCms7Dj54nSwwtCw6w4zLCyTLxs3tqqgLscrz91cMjcbN4yxcySvZ7t4MER8HPteeJZUnR16uLyDa1gFGzrx"]})", output.json()); + EXPECT_EQ(output.action_name(), "remalladdr"); +} + +TEST(TWFIO, Transfer) { + Proto::SigningInput input; + input.set_expiry(1579790000); + input.mutable_chain_params()->set_chain_id(string(gChainId.begin(), gChainId.end())); + input.mutable_chain_params()->set_head_block_number(50000); + input.mutable_chain_params()->set_ref_block_prefix(4000123456); + input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); + input.set_tpid("rewards@wallet"); + input.mutable_action()->mutable_transfer_message()->set_payee_public_key("FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE"); + input.mutable_action()->mutable_transfer_message()->set_amount(1000000000); + input.mutable_action()->mutable_transfer_message()->set_fee(250000000); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeFIO); + EXPECT_EQ(Common::Proto::OK, output.error()); + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K9VRCnvaTYN7vgcoVKVXgyJTdKUGV8hLXgFLoEbvqAcFxy7DXQ1rSnAfEuabi4ATkgmvnpaSBdVFN7TBtM1wrbZYqeJQw9"]})", output.json()); + EXPECT_EQ(output.action_name(), "trnsfiopubky"); +} + +TEST(TWFIO, RenewFioAddress) { + Proto::SigningInput input; + input.set_expiry(1579785000); + input.mutable_chain_params()->set_chain_id(string(gChainId.begin(), gChainId.end())); + input.mutable_chain_params()->set_head_block_number(39881); + input.mutable_chain_params()->set_ref_block_prefix(4279583376); + input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); + input.set_tpid("rewards@wallet"); + input.mutable_action()->mutable_renew_fio_address_message()->set_fio_address("nick@fiotestnet"); + input.mutable_action()->mutable_renew_fio_address_message()->set_owner_fio_public_key(addr6M.string()); + input.mutable_action()->mutable_renew_fio_address_message()->set_fee(3000000000); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeFIO); + EXPECT_EQ(Common::Proto::OK, output.error()); + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"289b295ec99b904215ff0000000001003056372503a85b80b1ba2919aea6ba01102b2f46fca756b200000000a8ed32322f0f6e69636b4066696f746573746e6574005ed0b200000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_Jxz7oCJ7Z4ECsxqb2utqBcyP3zPQCeQCBws9wWQjyptUKoWVk2AyCVEqtdMHJwqtLniio5Z7npMnaZB8E4pa2G75P9uGkb"]})", output.json()); + EXPECT_EQ(output.action_name(), "renewaddress"); +} + +TEST(TWFIO, NewFundsRequest) { + Proto::SigningInput input; + input.set_expiry(1579785000); + input.mutable_chain_params()->set_chain_id(string(gChainId.begin(), gChainId.end())); + input.mutable_chain_params()->set_head_block_number(39881); + input.mutable_chain_params()->set_ref_block_prefix(4279583376); + input.set_private_key(string(privKeyBA.bytes.begin(), privKeyBA.bytes.end())); + input.set_tpid("rewards@wallet"); + input.mutable_action()->mutable_new_funds_request_message()->set_payer_fio_name("mario@fiotestnet"); + input.mutable_action()->mutable_new_funds_request_message()->set_payer_fio_address("FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o"); + input.mutable_action()->mutable_new_funds_request_message()->set_payee_fio_name("alice@fiotestnet"); + input.mutable_action()->mutable_new_funds_request_message()->mutable_content()->set_payee_public_address("bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"); + input.mutable_action()->mutable_new_funds_request_message()->mutable_content()->set_amount("5"); + input.mutable_action()->mutable_new_funds_request_message()->mutable_content()->set_coin_symbol("BTC"); + input.mutable_action()->mutable_new_funds_request_message()->mutable_content()->set_memo("Memo"); + input.mutable_action()->mutable_new_funds_request_message()->mutable_content()->set_hash("Hash"); + input.mutable_action()->mutable_new_funds_request_message()->mutable_content()->set_offline_url("https://trustwallet.com"); + input.mutable_action()->mutable_new_funds_request_message()->set_fee(3000000000); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeFIO); + // Packed transaction varies, as there is no way to control encryption IV parameter from this level. + // Therefore full equality cannot be checked, tail is cut off. The first N chars are checked, works in this case. + EXPECT_EQ( + R"({"compression":"none","packed_context_free_data":"","packed_trx":"289b295ec99b904215ff000000000100403ed4aa0ba85b00acba384dbdb89a01102b2f46fca756b200000000a8ed32328802106d6172696f4066696f746573746)", + output.json().substr(0, 195)); + EXPECT_EQ(output.action_name(), "newfundsreq"); +} + +TEST(TWFIO, AddBundledTransactions) { + auto privateKey = parse_hex("93083dc4d9e8f613a57e3a862a1fa5d665c5e90141a8428990c945d1c2b56491"); + + Proto::SigningInput input; + input.set_expiry(1713458594); + input.mutable_chain_params()->set_chain_id(string(gChainIdMainnet.begin(), gChainIdMainnet.end())); + input.mutable_chain_params()->set_head_block_number(256431437); + input.mutable_chain_params()->set_ref_block_prefix(791306279); + input.set_private_key(string(privateKey.begin(), privateKey.end())); + input.set_tpid("trust@fiomembers"); + auto action = input.mutable_action()->mutable_add_bundled_transactions_message(); + action->set_fio_address("sergeitrust@wallet"); + action->set_bundle_sets(1); + action->set_fee(100000000000); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeFIO); + EXPECT_EQ(Common::Proto::OK, output.error()); + // Successfully broadcasted: https://fio.bloks.io/transaction/2c00f2051ca3738c4fe03ceddb82c48fefd9c534d8bb793dc7dce5d12f4f4f9c + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"a24d21664dd527602a2f0000000001003056372503a85b000056314d7d523201b038b9d6c13372f700000000a8ed32323c1273657267656974727573744077616c6c6574010000000000000000e87648170000001074727573744066696f6d656d62657273b038b9d6c13372f700","signatures":["SIG_K1_KjWGZ4Yd48VJcTAgox3HYVQhXeLhpRCgz2WqiF5WHRFSnbHouKxPgLQmymoABHC8EX51G1jU4ocWg2RKU17UYm4L5kTXP6"]})", output.json()); + EXPECT_EQ(output.action_name(), "addbundles"); +} + +} // namespace TW::FIO::TWFIOTests diff --git a/tests/chains/FIO/TransactionBuilderTests.cpp b/tests/chains/FIO/TransactionBuilderTests.cpp new file mode 100644 index 00000000000..16f16f92b06 --- /dev/null +++ b/tests/chains/FIO/TransactionBuilderTests.cpp @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "FIO/Action.h" +#include "FIO/NewFundsRequest.h" +#include "FIO/Signer.h" +#include "FIO/Transaction.h" +#include "FIO/TransactionBuilder.h" + +#include "BinaryCoding.h" +#include "HexCoding.h" + +#include +#include + +namespace TW::FIO::TransactionBuilderTests { +using namespace std; + +const Data chainId = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77"); +// 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf +const PrivateKey gPrivKeyBA = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); +const PublicKey gPubKey6MA = gPrivKeyBA.getPublicKey(TWPublicKeyTypeSECP256k1); +const Address gAddr6M(gPubKey6MA); + +TEST(FIOTransactionBuilder, RegisterFioAddressGeneric) { + Proto::SigningInput input; + input.set_expiry(1579784511); + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(39881); + input.mutable_chain_params()->set_ref_block_prefix(4279583376); + input.set_private_key(string(gPrivKeyBA.bytes.begin(), gPrivKeyBA.bytes.end())); + input.set_tpid("rewards@wallet"); + input.mutable_action()->mutable_register_fio_address_message()->set_fio_address("adam@fiotestnet"); + input.mutable_action()->mutable_register_fio_address_message()->set_owner_fio_public_key(gAddr6M.string()); + input.mutable_action()->mutable_register_fio_address_message()->set_fee(5000000000); + + auto json = TransactionBuilder::sign(input); + + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"3f99295ec99b904215ff0000000001003056372503a85b0000c6eaa66498ba01102b2f46fca756b200000000a8ed3232650f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K19ugLriG3ApYgjJCRDsy21p9xgsjbDtqBuZrmAEix9XYzndR1kNbJ6fXCngMJMAhxUHfwHAsPnh58otXiJZkazaM1EkS5"]})", json); +} + +TEST(FIOTransactionBuilder, RegisterFioAddress) { + ChainParams chainParams{chainId, 39881, 4279583376}; + uint64_t fee = 5000000000; + + string t = TransactionBuilder::createRegisterFioAddress(gAddr6M, gPrivKeyBA, "adam@fiotestnet", + chainParams, fee, "rewards@wallet", 1579784511); + + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"3f99295ec99b904215ff0000000001003056372503a85b0000c6eaa66498ba01102b2f46fca756b200000000a8ed3232650f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K19ugLriG3ApYgjJCRDsy21p9xgsjbDtqBuZrmAEix9XYzndR1kNbJ6fXCngMJMAhxUHfwHAsPnh58otXiJZkazaM1EkS5"]})", t); +} + +TEST(FIOTransactionBuilder, AddPubAddress) { + ChainParams chainParams{chainId, 11565, 4281229859}; + + string t = TransactionBuilder::createAddPubAddress(gAddr6M, gPrivKeyBA, "adam@fiotestnet", {{"BTC", "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"}, {"ETH", "0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a8F51"}, {"BNB", "bnb1ts3dg54apwlvr9hupv2n0j6e46q54znnusjk9s"}}, + chainParams, 0, "rewards@wallet", 1579729429); + + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"15c2285e2d2d23622eff0000000001003056372503a85b0000c6eaa664523201102b2f46fca756b200000000a8ed3232c9010f6164616d4066696f746573746e65740303425443034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c787770373064377603455448034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e4203424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b39730000000000000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K3zimaMKU8cBhVRPw46KM2u7uQWaAKXrnoeYZ7MEbp6sVJcDQmQR2RtdavpUPwkAnYUkd8NqLun8H48tcxZBcTUgkiPGVJ"]})", t); +} + +TEST(FIOTransactionBuilder, Transfer) { + ChainParams chainParams{chainId, 50000, 4000123456}; + string payee = "FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE"; + uint64_t amount = 1000000000; + uint64_t fee = 250000000; + + string t = TransactionBuilder::createTransfer(gAddr6M, gPrivKeyBA, payee, amount, + chainParams, fee, "rewards@wallet", 1579790000); + + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K9VRCnvaTYN7vgcoVKVXgyJTdKUGV8hLXgFLoEbvqAcFxy7DXQ1rSnAfEuabi4ATkgmvnpaSBdVFN7TBtM1wrbZYqeJQw9"]})", t); +} + +TEST(FIOTransactionBuilder, RenewFioAddress) { + ChainParams chainParams{chainId, 39881, 4279583376}; + uint64_t fee = 3000000000; + + string t = TransactionBuilder::createRenewFioAddress(gAddr6M, gPrivKeyBA, "nick@fiotestnet", + chainParams, fee, "rewards@wallet", 1579785000); + + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"289b295ec99b904215ff0000000001003056372503a85b80b1ba2919aea6ba01102b2f46fca756b200000000a8ed32322f0f6e69636b4066696f746573746e6574005ed0b200000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_Jxz7oCJ7Z4ECsxqb2utqBcyP3zPQCeQCBws9wWQjyptUKoWVk2AyCVEqtdMHJwqtLniio5Z7npMnaZB8E4pa2G75P9uGkb"]})", t); +} + +TEST(FIOTransactionBuilder, NewFundsRequest) { + { + ChainParams chainParams{chainId, 18484, 3712870657}; + const Data iv = parse_hex("000102030405060708090a0b0c0d0e0f"); // use fixed iv for testability + string t = TransactionBuilder::createNewFundsRequest( + Address("FIO5NMm9Vf3NjYFnhoc7yxTCrLW963KPUCzeMGv3SJ6zR3GMez4ub"), gPrivKeyBA, + "tag@fiotestnet", "FIO7iYHtDhs45smFgSqLyJ6Zi4D3YG8K5bZGyxmshLCDXXBPbbmJN", "dapixbp@fiotestnet", "14R4wEsGT58chmqo7nB65Dy4je6TiijDWc", + "1", "BTC", "payment", "", "", + chainParams, 800000000, "", 1583528215, iv); + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"17b9625e344801e94ddd000000000100403ed4aa0ba85b00acba384dbdb89a01702096fedf5bf9f900000000a8ed3232d2010e7461674066696f746573746e657412646170697862704066696f746573746e6574980141414543417751464267634943516f4c4441304f447a684533513779504a4738592f52486a69545576436163734a444a516243612f41643763354e36354f56366d3441566974596379654e7a4749306d4c366b5a71567348737837537845623471724d346435567258364939746a6842447067566b3078596575325861676759516b323168684972306c76412b7535546977545661673d3d0008af2f000000000c7a62777072727a796d736b620000","signatures":["SIG_K1_K95jnXSBCf1BnQXQPZzxKYPGxugwpbeVp2NSjN1kmYd9SQibvnSfh2ggmSVXii4Jvq3dtRHFA8s7n3kcQdLhY4KMrkgDgp"]})", t); + } + + ChainParams chainParams{chainId, 39881, 4279583376}; + uint64_t fee = 3000000000; + + const Data iv = parse_hex("000102030405060708090a0b0c0d0e0f"); // use fixed iv for testability + string t = TransactionBuilder::createNewFundsRequest(gAddr6M, gPrivKeyBA, + "mario@fiotestnet", "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o", "alice@fiotestnet", "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v", + "5", "BTC", "Memo", "Hash", "https://trustwallet.com", + chainParams, fee, "rewards@wallet", 1579785000, iv); + + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"289b295ec99b904215ff000000000100403ed4aa0ba85b00acba384dbdb89a01102b2f46fca756b200000000a8ed32328802106d6172696f4066696f746573746e657410616c6963654066696f746573746e6574c00141414543417751464267634943516f4c4441304f442f3575342f6b436b7042554c4a44682f546951334d31534f4e4938426668496c4e54766d39354249586d54396f616f7a55632f6c6c3942726e57426563464e767a76766f6d3751577a517250717241645035683433305732716b52355266416555446a704f514732364c347a6936767241553052764855474e382b685779736a6971506b2b7a455a444952534678426268796c69686d59334f4752342f5a46466358484967343241327834005ed0b2000000000c716466656a7a32613577706c0e726577617264734077616c6c657400","signatures":["SIG_K1_Kk79iVcQMpqpVgZwGTmC1rxgCTLy5BDFtHd8FvjRNm2FqNHR9dpeUmPTNqBKGMNG3BsPy4c5u26TuEDpS87SnyMpF43cZk"]})", t); +} + +TEST(FIOTransaction, ActionRegisterFioAddressInternal) { + RegisterFioAddressData radata("adam@fiotestnet", gAddr6M.string(), + 5000000000, "rewards@wallet", "qdfejz2a5wpl"); + Data ser1; + radata.serialize(ser1); + EXPECT_EQ( + hex(parse_hex("0F6164616D4066696F746573746E65743546494F366D31664D645470526B52426E6564765973685843784C4669433573755255384B44667838787874587032686E7478706E6600F2052A01000000102B2F46FCA756B20E726577617264734077616C6C6574")), + hex(ser1)); + + Action raAction; + raAction.account = "fio.address"; + raAction.name = "regaddress"; + raAction.actionDataSer = ser1; + raAction.auth.authArray.push_back(Authorization{"qdfejz2a5wpl", "active"}); + Data ser2; + raAction.serialize(ser2); + EXPECT_EQ( + "003056372503a85b0000c6eaa66498ba0" + "1102b2f46fca756b200000000a8ed3232" + "65" + "0f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400", + hex(ser2)); + + Transaction tx; + tx.expiration = 1579784511; + tx.refBlockNumber = 39881; + tx.refBlockPrefix = 4279583376; + tx.actions.push_back(raAction); + Data ser3; + tx.serialize(ser3); + EXPECT_EQ( + "3f99295ec99b904215ff0000000001" + "003056372503a85b0000c6eaa66498ba0" + "1102b2f46fca756b200000000a8ed3232" + "65" + "0f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400", + hex(ser3)); +} + +TEST(FIOTransaction, ActionAddPubAddressInternal) { + PubAddressActionData aadata("adam@fiotestnet", {{"BTC", "BTC", "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"}, {"ETH", "ETH", "0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a8F51"}, {"BNB", "BNB", "bnb1ts3dg54apwlvr9hupv2n0j6e46q54znnusjk9s"}}, + 0, "rewards@wallet", "qdfejz2a5wpl"); + Data ser1; + aadata.serialize(ser1); + EXPECT_EQ( + "0f6164616d4066696f746573746e65740303425443034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c787770373064377603455448034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e4203424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b39730000000000000000102b2f46fca756b20e726577617264734077616c6c6574", + hex(ser1)); + + Action aaAction; + aaAction.account = "fio.address"; + aaAction.name = "addaddress"; + aaAction.actionDataSer = ser1; + aaAction.auth.authArray.push_back(Authorization{"qdfejz2a5wpl", "active"}); + Data ser2; + aaAction.serialize(ser2); + EXPECT_EQ( + "003056372503a85b0000c6eaa66452320" + "1102b2f46fca756b200000000a8ed3232" + "c901" + "0f6164616d4066696f746573746e65740303425443034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c787770373064377603455448034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e4203424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b39730000000000000000102b2f46fca756b20e726577617264734077616c6c657400", + hex(ser2)); + + Transaction tx; + tx.expiration = 1579729429; + tx.refBlockNumber = 11565; + tx.refBlockPrefix = 4281229859; + tx.actions.push_back(aaAction); + Data ser3; + tx.serialize(ser3); + EXPECT_EQ( + "15c2285e2d2d23622eff0000000001" + "003056372503a85b0000c6eaa66452320" + "1102b2f46fca756b200000000a8ed3232" + "c901" + "0f6164616d4066696f746573746e65740303425443034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c787770373064377603455448034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e4203424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b39730000000000000000102b2f46fca756b20e726577617264734077616c6c657400", + hex(ser3)); +} + +TEST(FIONewFundsContent, serialize) { + { + NewFundsContent newFunds{"bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v", "10", "BTC", "BTC", "Memo", "Hash", "https://trustwallet.com"}; + Data ser; + newFunds.serialize(ser); + EXPECT_EQ(hex(ser), "2a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c78777037306437760231300342544303425443044d656d6f04486173681768747470733a2f2f747275737477616c6c65742e636f6d0000000000"); + } + { + // empty struct + NewFundsContent newFunds{"", "", "", "", "", "", ""}; + Data ser; + newFunds.serialize(ser); + EXPECT_EQ(hex(ser), "000000000000000000000000"); + } + { + // test from https://github.com/fioprotocol/fiojs/blob/master/src/tests/encryption-fio.test.ts + NewFundsContent newFunds{"purse.alice", "1", "", "fio.reqobt", "", "", ""}; + Data ser; + newFunds.serialize(ser); + EXPECT_EQ(hex(ser), "0b70757273652e616c6963650131000a66696f2e7265716f62740000000000000000"); + } +} + +TEST(FIONewFundsContent, deserialize) { + { + const Data ser = parse_hex("2a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c78777037306437760231300342544303425443044d656d6f04486173681768747470733a2f2f747275737477616c6c65742e636f6d0000000000"); + size_t index = 0; + const auto newFunds = NewFundsContent::deserialize(ser, index); + EXPECT_EQ(newFunds.payeePublicAddress, "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"); + EXPECT_EQ(newFunds.amount, "10"); + EXPECT_EQ(newFunds.coinSymbol, "BTC"); + EXPECT_EQ(newFunds.offlineUrl, "https://trustwallet.com"); + } + { + // incomplete input + const Data ser = parse_hex("0b6d"); + size_t index = 0; + const auto newFunds = NewFundsContent::deserialize(ser, index); + EXPECT_EQ(newFunds.payeePublicAddress, ""); + EXPECT_EQ(newFunds.amount, ""); + EXPECT_EQ(newFunds.offlineUrl, ""); + } +} + +TEST(FIOTransactionBuilder, expirySetDefault) { + uint32_t expiry = 1579790000; + EXPECT_EQ(TransactionBuilder::expirySetDefaultIfNeeded(expiry), false); + EXPECT_EQ(expiry, 1579790000ul); + expiry = 0; + EXPECT_EQ(expiry, 0ul); + EXPECT_EQ(TransactionBuilder::expirySetDefaultIfNeeded(expiry), true); + EXPECT_TRUE(expiry > 1579790000); +} + +// May throw nlohmann::json::type_error +void createTxWithChainParam(const ChainParams& paramIn, ChainParams& paramOut) { + string tx = TransactionBuilder::createAddPubAddress(gAddr6M, gPrivKeyBA, "adam@fiotestnet", {{"BTC", "bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"}}, + paramIn, 0, "rewards@wallet", 1579729429); + // retrieve chain params from encoded tx; parse out packed tx + try { + nlohmann::json txJson = nlohmann::json::parse(tx); + Data txData = parse_hex(txJson.at("packed_trx").get()); + // decode values + ASSERT_TRUE(txData.size() >= 10); + paramOut.headBlockNumber = decode16LE(txData.data() + 4); + paramOut.refBlockPrefix = decode32LE(txData.data() + 4 + 2); + } catch (nlohmann::json::type_error& e) { + FAIL() << "Json parse error " << e.what(); + } +} + +void checkBlockNum(uint64_t blockNumIn, uint64_t blockNumExpected) { + ChainParams paramOut; + createTxWithChainParam(ChainParams{chainId, blockNumIn, 4281229859}, paramOut); + EXPECT_EQ(paramOut.headBlockNumber, blockNumExpected); +} + +void checkRefBlockPrefix(uint64_t blockPrefixIn, uint64_t blockPrefixExpected) { + ChainParams paramOut; + createTxWithChainParam(ChainParams{chainId, 11565, blockPrefixIn}, paramOut); + EXPECT_EQ(paramOut.refBlockPrefix, blockPrefixExpected); +} + +TEST(FIOTransactionBuilder, chainParansRange) { + // headBlockNumber, 2 bytes + checkBlockNum(101, 101); + checkBlockNum(0xFFFF, 0xFFFF); + checkBlockNum(0x00011234, 0x1234); + // large values truncated + checkBlockNum(0xFFAB1234, 0x1234); + checkBlockNum(0x0000000112345678, 0x5678); + checkBlockNum(0xFFABCDEF12345678, 0x5678); + + // refBlockPrefix, 4 bytes; Large refBlockPrefix values used to cause problem + checkRefBlockPrefix(101, 101); + checkRefBlockPrefix(4281229859, 4281229859); + checkRefBlockPrefix(0xFFFFFFFF, 0xFFFFFFFF); + // large values truncated + checkRefBlockPrefix(0x0000000112345678, 0x12345678); + checkRefBlockPrefix(0xFFABCDEF12345678, 0x12345678); +} + +TEST(FIOTransactionBuilder, encodeVarInt) { + { + Data data; + EXPECT_EQ(TW::FIO::encodeVarInt(0x11, data), 1); + EXPECT_EQ(hex(data), "11"); + } + { + Data data; + EXPECT_EQ(TW::FIO::encodeVarInt(0x7F, data), 1); + EXPECT_EQ(hex(data), "7f"); + } + { + Data data; + EXPECT_EQ(TW::FIO::encodeVarInt(0x80, data), 2); + EXPECT_EQ(hex(data), "8001"); + } + { + Data data; + EXPECT_EQ(TW::FIO::encodeVarInt(0xFF, data), 2); + EXPECT_EQ(hex(data), "ff01"); + } + { + Data data; + EXPECT_EQ(TW::FIO::encodeVarInt(0x100, data), 2); + EXPECT_EQ(hex(data), "8002"); + } + { + Data data; + EXPECT_EQ(TW::FIO::encodeVarInt(0x3FFF, data), 2); + EXPECT_EQ(hex(data), "ff7f"); + } + { + Data data; + EXPECT_EQ(TW::FIO::encodeVarInt(0x4000, data), 3); + EXPECT_EQ(hex(data), "808001"); + } + { + Data data; + EXPECT_EQ(TW::FIO::encodeVarInt(0xFFFFFFFF, data), 5); + EXPECT_EQ(hex(data), "ffffffff0f"); + } +} + +TEST(FIOTransactionBuilder, encodeString) { + { + Data data; + const string text = "ABC"; + TW::FIO::encodeString(text, data); + EXPECT_EQ(hex(data), "03" + hex(text)); + } + { + Data data; + const string text = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; + EXPECT_EQ(text.length(), 130ul); + TW::FIO::encodeString(text, data); + // length on 2 bytes + EXPECT_EQ(hex(data), "8201" + hex(text)); + } +} + +TEST(FIOTransactionBuilder, buildUnsignedTxBytes) { + { + // Test register_fio_address_message + Proto::SigningInput input; + + input.set_expiry(1579784511); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(gAddr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(39881); + input.mutable_chain_params()->set_ref_block_prefix(4279583376); + + input.mutable_action()->mutable_register_fio_address_message()->set_fio_address("adam@fiotestnet"); + input.mutable_action()->mutable_register_fio_address_message()->set_owner_fio_public_key(gAddr6M.string()); + input.mutable_action()->mutable_register_fio_address_message()->set_fee(5000000000); + + auto result = TransactionBuilder::buildUnsignedTxBytes(input); + //packed_trx: 3f99295ec99b904215ff0000000001003056372503a85b0000c6eaa66498ba01102b2f46fca756b200000000a8ed3232650f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400 + EXPECT_EQ("3f99295ec99b904215ff0000000001003056372503a85b0000c6eaa66498ba01102b2f46fca756b200000000a8ed3232650f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400" + , hex(result)); + } + { + // Test add_pub_address_message + Proto::SigningInput input; + + input.set_expiry(1579729429); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(gAddr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(11565); + input.mutable_chain_params()->set_ref_block_prefix(4281229859); + + input.mutable_action()->mutable_add_pub_address_message()->set_fee(0); + input.mutable_action()->mutable_add_pub_address_message()->set_fio_address("adam@fiotestnet"); + + TW::FIO::Proto::PublicAddress *value; + value = input.mutable_action()->mutable_add_pub_address_message()->add_public_addresses(); + value->set_address("bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"); + value->set_coin_symbol("BTC"); + TW::FIO::Proto::PublicAddress *value1; + value1 = input.mutable_action()->mutable_add_pub_address_message()->add_public_addresses(); + value1->set_address("0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a8F51"); + value1->set_coin_symbol("ETH"); + TW::FIO::Proto::PublicAddress *value2; + value2 = input.mutable_action()->mutable_add_pub_address_message()->add_public_addresses(); + value2->set_address("bnb1ts3dg54apwlvr9hupv2n0j6e46q54znnusjk9s"); + value2->set_coin_symbol("BNB"); + + auto result = TransactionBuilder::buildUnsignedTxBytes(input); + + EXPECT_EQ("15c2285e2d2d23622eff0000000001003056372503a85b0000c6eaa664523201102b2f46fca756b200000000a8ed3232c9010f6164616d4066696f746573746e65740303425443034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c787770373064377603455448034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e4203424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b39730000000000000000102b2f46fca756b20e726577617264734077616c6c657400" + , hex(result)); + } + + { + // Test transfer_message + Proto::SigningInput input; + input.set_expiry(1579790000); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(gAddr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(50000); + input.mutable_chain_params()->set_ref_block_prefix(4000123456); + + input.mutable_action()->mutable_transfer_message()->set_amount(1000000000); + input.mutable_action()->mutable_transfer_message()->set_fee(250000000); + input.mutable_action()->mutable_transfer_message()->set_payee_public_key("FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE"); + + auto result = TransactionBuilder::buildUnsignedTxBytes(input); + EXPECT_EQ("b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c657400" + , hex(result)); + } + + { + // Test renew_fio_address_message + Proto::SigningInput input; + input.set_expiry(1579785000); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(gAddr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(39881); + input.mutable_chain_params()->set_ref_block_prefix(4279583376); + + input.mutable_action()->mutable_renew_fio_address_message()->set_fee(3000000000); + input.mutable_action()->mutable_renew_fio_address_message()->set_fio_address("nick@fiotestnet"); + + auto result = TransactionBuilder::buildUnsignedTxBytes(input); + EXPECT_EQ("289b295ec99b904215ff0000000001003056372503a85b80b1ba2919aea6ba01102b2f46fca756b200000000a8ed32322f0f6e69636b4066696f746573746e6574005ed0b200000000102b2f46fca756b20e726577617264734077616c6c657400" + , hex(result)); + } + +} + +TEST(FIOTransactionBuilder, buildSigningOutput) { + { + // Test register_fio_address_message + Proto::SigningInput input; + + input.set_expiry(1579784511); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(gAddr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(39881); + input.mutable_chain_params()->set_ref_block_prefix(4279583376); + + input.mutable_action()->mutable_register_fio_address_message()->set_fio_address("adam@fiotestnet"); + input.mutable_action()->mutable_register_fio_address_message()->set_owner_fio_public_key(gAddr6M.string()); + input.mutable_action()->mutable_register_fio_address_message()->set_fee(5000000000); + + Data txBytes = TransactionBuilder::buildUnsignedTxBytes(input); + Data sigBuf = TransactionBuilder::buildPreSignTxData(chainId, txBytes); + // create signature + Data signature = Signer::signData(gPrivKeyBA, sigBuf); + + Proto::SigningOutput result = TransactionBuilder::buildSigningOutput(input, signature); + + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"3f99295ec99b904215ff0000000001003056372503a85b0000c6eaa66498ba01102b2f46fca756b200000000a8ed3232650f6164616d4066696f746573746e65743546494f366d31664d645470526b52426e6564765973685843784c4669433573755255384b44667838787874587032686e7478706e6600f2052a01000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K19ugLriG3ApYgjJCRDsy21p9xgsjbDtqBuZrmAEix9XYzndR1kNbJ6fXCngMJMAhxUHfwHAsPnh58otXiJZkazaM1EkS5"]})" + , result.json()); + EXPECT_EQ(result.action_name(), "regaddress"); + + } + { + // Test add_pub_address_message + Proto::SigningInput input; + + input.set_expiry(1579729429); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(gAddr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(11565); + input.mutable_chain_params()->set_ref_block_prefix(4281229859); + + input.mutable_action()->mutable_add_pub_address_message()->set_fee(0); + input.mutable_action()->mutable_add_pub_address_message()->set_fio_address("adam@fiotestnet"); + + TW::FIO::Proto::PublicAddress *value; + value = input.mutable_action()->mutable_add_pub_address_message()->add_public_addresses(); + value->set_address("bc1qvy4074rggkdr2pzw5vpnn62eg0smzlxwp70d7v"); + value->set_coin_symbol("BTC"); + TW::FIO::Proto::PublicAddress *value1; + value1 = input.mutable_action()->mutable_add_pub_address_message()->add_public_addresses(); + value1->set_address("0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a8F51"); + value1->set_coin_symbol("ETH"); + TW::FIO::Proto::PublicAddress *value2; + value2 = input.mutable_action()->mutable_add_pub_address_message()->add_public_addresses(); + value2->set_address("bnb1ts3dg54apwlvr9hupv2n0j6e46q54znnusjk9s"); + value2->set_coin_symbol("BNB"); + + Data txBytes = TransactionBuilder::buildUnsignedTxBytes(input); + Data sigBuf = TransactionBuilder::buildPreSignTxData(chainId, txBytes); + // create signature + Data signature = Signer::signData(gPrivKeyBA, sigBuf); + + Proto::SigningOutput result = TransactionBuilder::buildSigningOutput(input, signature); + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"15c2285e2d2d23622eff0000000001003056372503a85b0000c6eaa664523201102b2f46fca756b200000000a8ed3232c9010f6164616d4066696f746573746e65740303425443034254432a626331717679343037347267676b647232707a773576706e6e3632656730736d7a6c787770373064377603455448034554482a30786365356342366339324461333762624261393142643430443443394434443732344133613846353103424e4203424e422a626e6231747333646735346170776c76723968757076326e306a366534367135347a6e6e75736a6b39730000000000000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K3zimaMKU8cBhVRPw46KM2u7uQWaAKXrnoeYZ7MEbp6sVJcDQmQR2RtdavpUPwkAnYUkd8NqLun8H48tcxZBcTUgkiPGVJ"]})" + , result.json()); + EXPECT_EQ(result.action_name(), "addaddress"); + } + { + // Test transfer_message + Proto::SigningInput input; + input.set_expiry(1579790000); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(gAddr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(50000); + input.mutable_chain_params()->set_ref_block_prefix(4000123456); + + input.mutable_action()->mutable_transfer_message()->set_amount(1000000000); + input.mutable_action()->mutable_transfer_message()->set_fee(250000000); + input.mutable_action()->mutable_transfer_message()->set_payee_public_key("FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE"); + + Data txBytes = TransactionBuilder::buildUnsignedTxBytes(input); + Data sigBuf = TransactionBuilder::buildPreSignTxData(chainId, txBytes); + // create signature + Data signature = Signer::signData(gPrivKeyBA, sigBuf); + + Proto::SigningOutput result = TransactionBuilder::buildSigningOutput(input, signature); + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K9VRCnvaTYN7vgcoVKVXgyJTdKUGV8hLXgFLoEbvqAcFxy7DXQ1rSnAfEuabi4ATkgmvnpaSBdVFN7TBtM1wrbZYqeJQw9"]})" + , result.json()); + EXPECT_EQ(result.action_name(), "trnsfiopubky"); + } + { + // Test renew_fio_address_message + Proto::SigningInput input; + input.set_expiry(1579785000); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(gAddr6M.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(39881); + input.mutable_chain_params()->set_ref_block_prefix(4279583376); + + input.mutable_action()->mutable_renew_fio_address_message()->set_fee(3000000000); + input.mutable_action()->mutable_renew_fio_address_message()->set_fio_address("nick@fiotestnet"); + + Data txBytes = TransactionBuilder::buildUnsignedTxBytes(input); + Data sigBuf = TransactionBuilder::buildPreSignTxData(chainId, txBytes); + // create signature + Data signature = Signer::signData(gPrivKeyBA, sigBuf); + + Proto::SigningOutput result = TransactionBuilder::buildSigningOutput(input, signature); + EXPECT_EQ(R"({"compression":"none","packed_context_free_data":"","packed_trx":"289b295ec99b904215ff0000000001003056372503a85b80b1ba2919aea6ba01102b2f46fca756b200000000a8ed32322f0f6e69636b4066696f746573746e6574005ed0b200000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_Jxz7oCJ7Z4ECsxqb2utqBcyP3zPQCeQCBws9wWQjyptUKoWVk2AyCVEqtdMHJwqtLniio5Z7npMnaZB8E4pa2G75P9uGkb"]})" + , result.json()); + EXPECT_EQ(result.action_name(), "renewaddress"); + } +} + +} // namespace TW::FIO::TransactionBuilderTests diff --git a/tests/chains/FIO/TransactionCompilerTests.cpp b/tests/chains/FIO/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..f3eaecbc159 --- /dev/null +++ b/tests/chains/FIO/TransactionCompilerTests.cpp @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "uint256.h" + +#include "FIO/Address.h" +#include "FIO/Action.h" +#include "FIO/NewFundsRequest.h" +#include "FIO/Transaction.h" +#include "FIO/TransactionBuilder.h" +#include "FIO/Signer.h" + +#include "proto/FIO.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; +using namespace TW::FIO; +using namespace std; + +TEST(FIOCompiler, CompileWithSignatures) { + Data chainId = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77"); + // 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf + PrivateKey privateKey = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); + PublicKey pubKeyA = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + Address addrA(pubKeyA); + const auto coin = TWCoinTypeFIO; + + /// Step 1: Prepare transaction input (protobuf) + Proto::SigningInput input; + input.set_expiry(1579790000); + input.set_tpid("rewards@wallet"); + input.set_owner_public_key(addrA.string()); + + input.mutable_chain_params()->set_chain_id(string(chainId.begin(), chainId.end())); + input.mutable_chain_params()->set_head_block_number(50000); + input.mutable_chain_params()->set_ref_block_prefix(4000123456); + + input.mutable_action()->mutable_transfer_message()->set_amount(1000000000); + input.mutable_action()->mutable_transfer_message()->set_fee(250000000); + input.mutable_action()->mutable_transfer_message()->set_payee_public_key("FIO7uMZoeei5HtXAD24C4yCkpWWbf24bjYtrRNjWdmGCXHZccwuiE"); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = data(preSigningOutput.data()); + auto preImageHash = data(preSigningOutput.data_hash()); + + std::string expectedPreImage = "4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c6574000000000000000000000000000000000000000000000000000000000000000000"; + std::string expectedPreImageHash = "6a82a57fb9bfc43918aa757d6094ba71fa2c7ece1691c4b8551a0607273771d7"; + ASSERT_EQ(hex(preImage), expectedPreImage); + ASSERT_EQ(hex(preImageHash), expectedPreImageHash); + + // create signature + Data signature = Signer::signData(privateKey, preImage); + EXPECT_EQ("1f6ccee1f4cd188cc8aefa63f8fda8c90c0493ca1504806d3a26a7300a9687bb701f188337bc9a32f01ee0c2ecf030aee197b050460d72f7272cc6ce36ef14c95b", hex(signature)); + std::string sigStr = Signer::signatureToBase58(signature); + EXPECT_EQ("SIG_K1_K9VRCnvaTYN7vgcoVKVXgyJTdKUGV8hLXgFLoEbvqAcFxy7DXQ1rSnAfEuabi4ATkgmvnpaSBdVFN7TBtM1wrbZYqeJQw9", sigStr); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(Signer::verify(pubKeyA, preImageHash, signature)); + + const auto ExpectedTx = R"({"compression":"none","packed_context_free_data":"","packed_trx":"b0ae295e50c3400a6dee00000000010000980ad20ca85be0e1d195ba85e7cd01102b2f46fca756b200000000a8ed32325d3546494f37754d5a6f6565693548745841443234433479436b70575762663234626a597472524e6a57646d474358485a63637775694500ca9a3b0000000080b2e60e00000000102b2f46fca756b20e726577617264734077616c6c657400","signatures":["SIG_K1_K9VRCnvaTYN7vgcoVKVXgyJTdKUGV8hLXgFLoEbvqAcFxy7DXQ1rSnAfEuabi4ATkgmvnpaSBdVFN7TBtM1wrbZYqeJQw9"]})"; + /// Step 3: Compile transaction info + { + const Data outputData = TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {pubKeyA.bytes}); + + TW::FIO::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(ExpectedTx, output.json()); + EXPECT_EQ(output.action_name(), "trnsfiopubky"); + } + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::FIO::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + TW::FIO::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.json(), ExpectedTx); + } + { // Negative: not enough signatures + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {}, {pubKeyA.bytes}); + + TW::FIO::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.json().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + EXPECT_EQ(output.error_message(), "empty signatures or publickeys"); + } + + { // Negative: not enough publicKey + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {TW::data(sigStr)}, {}); + + TW::FIO::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.json().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + EXPECT_EQ(output.error_message(), "empty signatures or publickeys"); + } + + { // Negative: not one to one + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {TW::data(sigStr)}, {pubKeyA.bytes, pubKeyA.bytes}); + + TW::FIO::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.json().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + EXPECT_EQ(output.error_message(), "signatures and publickeys size can only be one"); + } +} \ No newline at end of file diff --git a/tests/chains/Fantom/TWCoinTypeTests.cpp b/tests/chains/Fantom/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..e574fc3b825 --- /dev/null +++ b/tests/chains/Fantom/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWFantomCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeFantom)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xb0a741d882291951de1fac72e90b9baf886ddb0c9c87658a0c206490dfaa5202")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeFantom, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x9474feb9917b87da6f0d830ba66ee0035835c0d3")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeFantom, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeFantom)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeFantom)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeFantom), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeFantom)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeFantom)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeFantom)); + assertStringsEqual(symbol, "FTM"); + assertStringsEqual(txUrl, "https://ftmscan.com/tx/0xb0a741d882291951de1fac72e90b9baf886ddb0c9c87658a0c206490dfaa5202"); + assertStringsEqual(accUrl, "https://ftmscan.com/address/0x9474feb9917b87da6f0d830ba66ee0035835c0d3"); + assertStringsEqual(id, "fantom"); + assertStringsEqual(name, "Fantom"); +} diff --git a/tests/chains/Filecoin/AddressTests.cpp b/tests/chains/Filecoin/AddressTests.cpp new file mode 100644 index 00000000000..ebe9dc553c1 --- /dev/null +++ b/tests/chains/Filecoin/AddressTests.cpp @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Filecoin/Address.h" +#include "HexCoding.h" + +#include +#include + +using namespace TW; + +namespace TW::Filecoin::tests { +// clang-format off + +struct address_test { + std::string string; + std::string encoded; + uint64_t actorID; + std::string payloadHex; +}; + +static const address_test validAddresses[] = { + // ID addresses + {"f00", "0000", 0, ""}, + {"f01", "0001", 1, ""}, + {"f010", "000a", 10, ""}, + {"f0150", "009601", 150, ""}, + {"f0499", "00f303", 499, ""}, + {"f01024", "008008", 1024, ""}, + {"f01729", "00c10d", 1729, ""}, + {"f0999999", "00bf843d", 999999, ""}, + {"f018446744073709551615", "00ffffffffffffffffff01", 18446744073709551615U, ""}, + // secp256k1 addresses + {"f15ihq5ibzwki2b4ep2f46avlkrqzhpqgtga7pdrq", "01ea0f0ea039b291a0f08fd179e0556a8c3277c0d3", 0, "ea0f0ea039b291a0f08fd179e0556a8c3277c0d3"}, + {"f12fiakbhe2gwd5cnmrenekasyn6v5tnaxaqizq6a", "01d1500504e4d1ac3e89ac891a4502586fabd9b417", 0, "d1500504e4d1ac3e89ac891a4502586fabd9b417"}, + {"f1wbxhu3ypkuo6eyp6hjx6davuelxaxrvwb2kuwva", "01b06e7a6f0f551de261fe3a6fe182b422ee0bc6b6", 0, "b06e7a6f0f551de261fe3a6fe182b422ee0bc6b6"}, + {"f1xtwapqc6nh4si2hcwpr3656iotzmlwumogqbuaa", "01bcec07c05e69f92468e2b3e3bf77c874f2c5da8c", 0, "bcec07c05e69f92468e2b3e3bf77c874f2c5da8c"}, + {"f1xcbgdhkgkwht3hrrnui3jdopeejsoatkzmoltqy", "01b882619d46558f3d9e316d11b48dcf211327026a", 0, "b882619d46558f3d9e316d11b48dcf211327026a"}, + {"f17uoq6tp427uzv7fztkbsnn64iwotfrristwpryy", "01fd1d0f4dfcd7e99afcb99a8326b7dc459d32c628", 0, "fd1d0f4dfcd7e99afcb99a8326b7dc459d32c628"}, + // Actor addresses + {"f24vg6ut43yw2h2jqydgbg2xq7x6f4kub3bg6as6i", "02e54dea4f9bc5b47d261819826d5e1fbf8bc5503b", 0, "e54dea4f9bc5b47d261819826d5e1fbf8bc5503b"}, + {"f25nml2cfbljvn4goqtclhifepvfnicv6g7mfmmvq", "02eb58bd08a15a6ade19d0989674148fa95a8157c6", 0, "eb58bd08a15a6ade19d0989674148fa95a8157c6"}, + {"f2nuqrg7vuysaue2pistjjnt3fadsdzvyuatqtfei", "026d21137eb4c4814269e894d296cf6500e43cd714", 0, "6d21137eb4c4814269e894d296cf6500e43cd714"}, + {"f24dd4ox4c2vpf5vk5wkadgyyn6qtuvgcpxxon64a", "02e0c7c75f82d55e5ed55db28033630df4274a984f", 0, "e0c7c75f82d55e5ed55db28033630df4274a984f"}, + {"f2gfvuyh7v2sx3patm5k23wdzmhyhtmqctasbr23y", "02316b4c1ff5d4afb7826ceab5bb0f2c3e0f364053", 0, "316b4c1ff5d4afb7826ceab5bb0f2c3e0f364053"}, + // BLS addresses + {"f3vvmn62lofvhjd2ugzca6sof2j2ubwok6cj4xxbfzz4yuxfkgobpihhd2thlanmsh3w2ptld2gqkn2jvlss4a","03ad58df696e2d4e91ea86c881e938ba4ea81b395e12797b84b9cf314b9546705e839c7a99d606b247ddb4f9ac7a3414dd", 0, "ad58df696e2d4e91ea86c881e938ba4ea81b395e12797b84b9cf314b9546705e839c7a99d606b247ddb4f9ac7a3414dd"}, + {"f3wmuu6crofhqmm3v4enos73okk2l366ck6yc4owxwbdtkmpk42ohkqxfitcpa57pjdcftql4tojda2poeruwa","03b3294f0a2e29e0c66ebc235d2fedca5697bf784af605c75af608e6a63d5cd38ea85ca8989e0efde9188b382f9372460d", 0, "b3294f0a2e29e0c66ebc235d2fedca5697bf784af605c75af608e6a63d5cd38ea85ca8989e0efde9188b382f9372460d"}, + {"f3s2q2hzhkpiknjgmf4zq3ejab2rh62qbndueslmsdzervrhapxr7dftie4kpnpdiv2n6tvkr743ndhrsw6d3a","0396a1a3e4ea7a14d49985e661b22401d44fed402d1d0925b243c923589c0fbc7e32cd04e29ed78d15d37d3aaa3fe6da33", 0, "96a1a3e4ea7a14d49985e661b22401d44fed402d1d0925b243c923589c0fbc7e32cd04e29ed78d15d37d3aaa3fe6da33"}, + {"f3q22fijmmlckhl56rn5nkyamkph3mcfu5ed6dheq53c244hfmnq2i7efdma3cj5voxenwiummf2ajlsbxc65a","0386b454258c589475f7d16f5aac018a79f6c1169d20fc33921dd8b5ce1cac6c348f90a3603624f6aeb91b64518c2e8095", 0, "86b454258c589475f7d16f5aac018a79f6c1169d20fc33921dd8b5ce1cac6c348f90a3603624f6aeb91b64518c2e8095"}, + {"f3u5zgwa4ael3vuocgc5mfgygo4yuqocrntuuhcklf4xzg5tcaqwbyfabxetwtj4tsam3pbhnwghyhijr5mixa","03a7726b038022f75a384617585360cee629070a2d9d28712965e5f26ecc40858382803724ed34f2720336f09db631f074", 0, "a7726b038022f75a384617585360cee629070a2d9d28712965e5f26ecc40858382803724ed34f2720336f09db631f074"}, + // Delegated addresses + {"f432f77777777x32lpna", "0420ffffffffff", 32, "ffffffffff"}, + {"f418446744073709551615ftnkyfaq", "04ffffffffffffffffff01", 18446744073709551615U, ""}, + {"f410frw6wy7w6sbsguyn3yzeygg34fgf72n5ao5sxyky", "040a8dbd6c7ede90646a61bbc649831b7c298bfd37a0", 10, "8dbd6c7ede90646a61bbc649831b7c298bfd37a0"}, + {"f410f2oekwcmo2pueydmaq53eic2i62crtbeyuzx2gmy", "040ad388ab098ed3e84c0d808776440b48f685198498", 10, "d388ab098ed3e84c0d808776440b48f685198498"}, + {"f418446744073709551615faaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaafbbuagu","04ffffffffffffffffff01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 18446744073709551615U, "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, +}; + +static const std::string invalidAddresses[] = { + "", + "f0-1", // Negative :) + "f018446744073709551616", // Greater than max uint64_t + "f418446744073709551615", // No "f" separator + "f4f77777777vnmsana", // Empty Actor ID + "f15ihq5ibzwki2b4ep2f46avlkr\0zhpqgtga7pdrq", // Embedded NUL + "t15ihq5ibzwki2b4ep2f46avlkrqzhpqgtga7pdrq", // Test net + "a15ihq5ibzwki2b4ep2f46avlkrqzhpqgtga7pdrq", // Unknown net + "f95ihq5ibzwki2b4ep2f46avlkrqzhpqgtga7pdrq", // Unknown address type + // Invalid checksum cases + "f15ihq5ibzwki2b4ep2f46avlkrqzhpqgtga7rdrr", + "f24vg6ut43yw2h2jqydgbg2xq7x6f4kub3bg6as66", + "f3vvmn62lofvhjd2ugzca6sof2j2ubwok6cj4xxbfzz4yuxfkgobpihhd2thlanmsh3w2ptld2gqkn2jvlss44", + "f0vvmn62lofvhjd2ugzca6sof2j2ubwok6cj4xxbfzz4yuxfkgobpihhd2thlanmsh3w2ptld2gqkn2jvlss44", + "f410f2oekwcmo2pueydmaq53eic2i62crtbeyuzx2gma", +}; + +TEST(FilecoinAddress, IsValid) { + for (auto&& test : validAddresses) { + ASSERT_TRUE(Address::isValid(test.string)) + << "isValid(string) != true: " << test.string; + + const Data encodedBytes = parse_hex(test.encoded); + ASSERT_TRUE(Address::isValid(encodedBytes)) + << "isValid(Data) != true: " << test.encoded; + } +} + +TEST(FilecoinAddress, IsInvalid) { + for (auto&& address : invalidAddresses) { + ASSERT_FALSE(Address::isValid(address)) + << "isValid(string) != false: " << address; + } + + ASSERT_FALSE(Address::isValid(parse_hex("00"))) << "Empty varuint"; + ASSERT_FALSE(Address::isValid(parse_hex("00ff"))) << "Short varuint"; + ASSERT_FALSE(Address::isValid(parse_hex("00ff00ff"))) << "Varuint with hole"; + ASSERT_FALSE(Address::isValid(parse_hex("000101"))) << "Long varuint"; + ASSERT_FALSE(Address::isValid(parse_hex("000000"))) << "Long varuint"; + ASSERT_FALSE(Address::isValid(parse_hex("00ffffffffffffffffff80"))) << "Overflow"; +} + +TEST(FilecoinAddress, Equal) { + for (auto&& test : validAddresses) { + const Data encodedBytes = parse_hex(test.encoded); + const Address lhs(test.string), rhs(encodedBytes); + ASSERT_EQ(lhs, rhs) << "Address(string) != Address(Data)"; + } + + EXPECT_ANY_THROW(new Address(Data{})); +} + +TEST(FilecoinAddress, ExpectedProperties) { + for (auto&& test : validAddresses) { + const Data encodedBytes = parse_hex(test.encoded); + const uint8_t expectedType = encodedBytes[0]; + + const Address address(encodedBytes); + + const auto actualType = static_cast(address.type); + ASSERT_TRUE(actualType == expectedType) + << "Unexpected type: " << actualType << " != " << expectedType << ": " << test.string; + ASSERT_TRUE(address.actorID == test.actorID) + << "Unexpected actorID: " << address.actorID << " != " << test.actorID << ": " << test.string; + ASSERT_TRUE(address.payload == parse_hex(test.payloadHex)) + << "Unexpected payload: " << hex(address.payload) << " != " << test.payloadHex; + } +} + +TEST(FilecoinAddress, ToString) { + for (auto&& test : validAddresses) { + Address a(parse_hex(test.encoded)); + ASSERT_EQ(a.string(), test.string) << "Address(" << test.encoded << ")"; + } +} + +TEST(FilecoinAddress, ToBytes) { + for (auto&& test : validAddresses) { + Address a(test.string); + ASSERT_EQ(hex(a.toBytes()), test.encoded) << "Address(" << test.string << ")"; + } + + for (const auto& test : invalidAddresses) + EXPECT_ANY_THROW(new Address(test)); +} + +} // TW::Filecoin::tests diff --git a/tests/chains/Filecoin/SignerTests.cpp b/tests/chains/Filecoin/SignerTests.cpp new file mode 100644 index 00000000000..8eccd8f6f76 --- /dev/null +++ b/tests/chains/Filecoin/SignerTests.cpp @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Filecoin/Address.h" +#include "Filecoin/Signer.h" +#include "HexCoding.h" +#include "PrivateKey.h" + +#include + +namespace TW::Filecoin { + +TEST(FilecoinSigner, DerivePublicKey) { + const PrivateKey privateKey( + parse_hex("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe")); + const PublicKey publicKey((privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended))); + const Address address = Address::secp256k1Address(publicKey); + ASSERT_EQ(address.string(), "f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq"); +} + +TEST(FilecoinSigner, Sign) { + const PrivateKey privateKey( + parse_hex("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe")); + const PublicKey publicKey((privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended))); + const Address fromAddress = Address::secp256k1Address(publicKey); + const Address toAddress("f1rletqqhinhagw6nxjcr4kbfws25thgt7owzuruy"); + + Transaction tx(toAddress, fromAddress, + /*nonce*/ 1, + /*value*/ 6000, + /*gasLimit*/ 23423423423423, + /*gasFeeCap*/ 456456456456445645, + /*gasPremium*/ 5675674564734345, + /*method*/ Transaction::MethodType::SEND, + /*params*/ Data()); + + Data signature = Signer::sign(privateKey, tx); + + ASSERT_EQ( + tx.serialize(Transaction::SignatureType::SECP256K1, signature), + R"({"Message":{"From":"f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq","GasFeeCap":"456456456456445645","GasLimit":23423423423423,"GasPremium":"5675674564734345","Method":0,"Nonce":1,"To":"f1rletqqhinhagw6nxjcr4kbfws25thgt7owzuruy","Value":"6000"},"Signature":{"Data":"3GOUpn2Wiwe20QXLC8ixx23WiKDwrVkfxYi3CgzZ5jBVKZT4WUOZNuZhpUFky0PqGaM7vErEOi//yqBGSIQQUAA=","Type":1}})" + ); +} + +/// Successfully broadcasted: +/// https://filfox.info/en/message/bafy2bzaceczvto7d2af7cq3kuwlvmanlh5xica4apl3vwxu37yaeozq72mvgm +TEST(FilecoinSigner, SignToDelegated) { + Proto::SigningInput input; + + auto privateKey = parse_hex("d3d6ed8b97dcd4661f62a1162bee6949401fd3935f394e6eacf15b6d5005483c"); + auto toAddress = "f410frw6wy7w6sbsguyn3yzeygg34fgf72n5ao5sxyky"; + uint64_t nonce = 0; + // 0.001 FIL + auto value = store(uint256_t(1'000'000) * uint256_t(1'000'000'000)); + uint64_t gasLimit = 6152567; + auto gasFeeCap = store(uint256_t(4435940585)); + auto gasPremium = store(uint256_t(11597139)); + + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_to(toAddress); + input.set_nonce(nonce); + input.set_value(value.data(), value.size()); + input.set_gas_limit(gasLimit); + input.set_gas_fee_cap(gasFeeCap.data(), gasFeeCap.size()); + input.set_gas_premium(gasPremium.data(), gasPremium.size()); + + Proto::SigningOutput output = Signer::sign(input); + + ASSERT_EQ(output.json(), R"({"Message":{"From":"f1mzyorxlcvdoqn5cto7urefbucugrcxxghpjc5hi","GasFeeCap":"4435940585","GasLimit":6152567,"GasPremium":"11597139","Method":3844450837,"Nonce":0,"To":"f410frw6wy7w6sbsguyn3yzeygg34fgf72n5ao5sxyky","Value":"1000000000000000"},"Signature":{"Data":"bxZhnsOYjdArPa3W0SpggwqtXPgvfRSoM2dU5lXYar9lWhTGc6FvPWk2RTUGyA8UtzMIdOPSUKfzU1iA2eA3YwA=","Type":1}})"); +} + +/// Successfully broadcasted: +/// https://filfox.info/en/message/bafy2bzacea3ioez23o7t2hae6t2qwwkow46nhc42ffm5lqyxzzvrzblofnleu +TEST(FilecoinSigner, SignFromDelegated) { + Proto::SigningInput input; + + auto privateKey = parse_hex("825d2bb32965764a98338139412c7591ed54c951dd65504cd8ddaeaa0fea7b2a"); + auto invalidToAddress = "f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq"; + auto toAddress = "f410frw6wy7w6sbsguyn3yzeygg34fgf72n5ao5sxyky"; + uint64_t nonce = 0; + // 0.001 FIL + auto value = store(uint256_t(1'000'000) * uint256_t(1'000'000'000)); + uint64_t gasLimit = 2154192; + auto gasFeeCap = store(uint256_t(516751679)); + auto gasPremium = store(uint256_t(12729639)); + + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_nonce(nonce); + input.set_value(value.data(), value.size()); + input.set_gas_limit(gasLimit); + input.set_gas_fee_cap(gasFeeCap.data(), gasFeeCap.size()); + input.set_gas_premium(gasPremium.data(), gasPremium.size()); + input.set_derivation(Proto::DerivationType::DELEGATED); + + // Check if the output.error is set if `to` address is invalid. + input.set_to(invalidToAddress); + Proto::SigningOutput errorOutput = Signer::sign(input); + ASSERT_FALSE(errorOutput.error_message().empty()); + ASSERT_TRUE(errorOutput.json().empty()); + + // Set the valid `to` address. Expect to get a valid response. + input.set_to(toAddress); + Proto::SigningOutput output = Signer::sign(input); + + ASSERT_TRUE(output.error_message().empty()); + ASSERT_EQ(output.json(), R"({"Message":{"From":"f410fvak24cyg3saddajborn6idt7rrtfj2ptauk5pbq","GasFeeCap":"516751679","GasLimit":2154192,"GasPremium":"12729639","Method":3844450837,"Nonce":0,"To":"f410frw6wy7w6sbsguyn3yzeygg34fgf72n5ao5sxyky","Value":"1000000000000000"},"Signature":{"Data":"fMOvlBAnc5OYDVEMaSSQURiqmrJbfktSPcUI2ptAoKtK0xl++cnTSKtqZyNV4yH0X5Ly2N2bqeNlFwAFANHoFAE=","Type":3}})"); +} + +} // namespace TW::Filecoin diff --git a/tests/chains/Filecoin/TWAddressConverterTests.cpp b/tests/chains/Filecoin/TWAddressConverterTests.cpp new file mode 100644 index 00000000000..b50eb9e58aa --- /dev/null +++ b/tests/chains/Filecoin/TWAddressConverterTests.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include +#include + +#include "TestUtilities.h" + +namespace TW::Filecoin::tests { +// clang-format off + +struct AddressTest { + const char* filecoin; + const char* eth; +}; + +TEST(TWFilecoinAddressConverter, ConvertToEth) { + const AddressTest tests[] = { + {"f09876", "0xff00000000000000000000000000000000002694"}, + {"f410frw6wy7w6sbsguyn3yzeygg34fgf72n5ao5sxyky", "0x8dbD6c7Ede90646a61Bbc649831b7c298BFd37A0"}, + // The following addresses can't be converted to ETH. Expect an empty result. + {"f15ihq5ibzwki2b4ep2f46avlkrqzhpqgtga7pdrq", ""}, + {"f24vg6ut43yw2h2jqydgbg2xq7x6f4kub3bg6as6i", ""}, + {"f3vvmn62lofvhjd2ugzca6sof2j2ubwok6cj4xxbfzz4yuxfkgobpihhd2thlanmsh3w2ptld2gqkn2jvlss4a", ""}, + // Should fail since `actorID != 10`. Expect an empty result. + {"f432f77777777x32lpna", ""}, + {"f418446744073709551615ftnkyfaq", ""}, + // The following addresses are invalid. Expect an empty result. + {"f432f77777777x32lpn", ""}, + {"f018446744073709551616", ""}, + }; + + for (const auto& test : tests) { + auto filecoinAddress = STRING(test.filecoin); + auto result = WRAPS(TWFilecoinAddressConverterConvertToEthereum(filecoinAddress.get())); + assertStringsEqual(result, test.eth); + } +} + +TEST(TWFilecoinAddressConverter, ConvertFromEth) { + const AddressTest tests[] = { + {"f410f74aaaaaaaaaaaaaaaaaaaaaaaaaaajuu3nnltyi", "0xff00000000000000000000000000000000002694"}, + {"f410frw6wy7w6sbsguyn3yzeygg34fgf72n5ao5sxyky", "0x8dbD6c7Ede90646a61Bbc649831b7c298BFd37A0"}, + {"f410f2oekwcmo2pueydmaq53eic2i62crtbeyuzx2gmy", "0xd388ab098ed3e84c0d808776440b48f685198498"}, + // The following addresses are invalid. Expect an empty result. + {"", "0xd388ab098ed3e84c0d808776440b48f68519849"}, + {"", "0x"}, + }; + + for (const auto& test : tests) { + auto ethAddress = STRING(test.eth); + auto result = WRAPS(TWFilecoinAddressConverterConvertFromEthereum(ethAddress.get())); + assertStringsEqual(result, test.filecoin); + } +} + +} diff --git a/tests/chains/Filecoin/TWAnySignerTests.cpp b/tests/chains/Filecoin/TWAnySignerTests.cpp new file mode 100644 index 00000000000..27b8eaacf9c --- /dev/null +++ b/tests/chains/Filecoin/TWAnySignerTests.cpp @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include + +#include "Data.h" +#include "HexCoding.h" +#include "proto/Filecoin.pb.h" +#include "uint256.h" + +#include + +using namespace TW; + +namespace TW::Filecoin::tests { + +TEST(TWAnySignerFilecoin, Sign) { + Proto::SigningInput input; + auto privateKey = parse_hex("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe"); + auto toAddress = + "f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq"; + uint64_t nonce = 2; + // 600 FIL + // auto value = parse_hex("2086ac351052600000"); + auto value = store(uint256_t(600) * uint256_t(1'000'000'000) * uint256_t(1'000'000'000)); + uint64_t gasLimit = 1000; + // auto gasFeeCap = parse_hex("25f273933db5700000"); + auto gasFeeCap = store(uint256_t(700) * uint256_t(1'000'000'000) * uint256_t(1'000'000'000)); + // auto gasPremium = parse_hex("2b5e3af16b18800000"); + auto gasPremium = store(uint256_t(800) * uint256_t(1'000'000'000) * uint256_t(1'000'000'000)); + + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_to(toAddress); + input.set_nonce(nonce); + input.set_value(value.data(), value.size()); + input.set_gas_limit(gasLimit); + input.set_gas_fee_cap(gasFeeCap.data(), gasFeeCap.size()); + input.set_gas_premium(gasPremium.data(), gasPremium.size()); + + auto inputString = input.SerializeAsString(); + auto inputData = WRAPD(TWDataCreateWithBytes((const byte*)inputString.data(), inputString.size())); + + auto outputData = WRAPD(TWAnySignerSign(inputData.get(), TWCoinTypeFilecoin)); + + Proto::SigningOutput output; + output.ParseFromArray(TWDataBytes(outputData.get()), static_cast(TWDataSize(outputData.get()))); + + ASSERT_EQ(output.json(), R"({"Message":{"From":"f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq","GasFeeCap":"700000000000000000000","GasLimit":1000,"GasPremium":"800000000000000000000","Method":0,"Nonce":2,"To":"f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq","Value":"600000000000000000000"},"Signature":{"Data":"jMRu+OZ/lfppgmqSfGsntFrRLWZnUg3ZYmJTTRLsVt4V1310vR3VKGJpaE6S4sNvDOE6sEgmN9YmfTkPVK2qMgE=","Type":1}})"); +} + +TEST(TWAnySignerFilecoin, SignJSON) { + auto json = STRING( + R"({ + "to": "f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq", + "nonce": "2", + "value": "IIasNRBSYAAA", + "gasLimit": 1000, + "gasFeeCap": "JfJzkz21cAAA", + "gasPremium": "K1468WsYgAAA" + })"); + auto key = DATA("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe"); + auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeFilecoin)); + + ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeFilecoin)); + assertStringsEqual(result, R"({"Message":{"From":"f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq","GasFeeCap":"700000000000000000000","GasLimit":1000,"GasPremium":"800000000000000000000","Method":0,"Nonce":2,"To":"f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq","Value":"600000000000000000000"},"Signature":{"Data":"jMRu+OZ/lfppgmqSfGsntFrRLWZnUg3ZYmJTTRLsVt4V1310vR3VKGJpaE6S4sNvDOE6sEgmN9YmfTkPVK2qMgE=","Type":1}})"); +} + +} // namespace TW::Filecoin::tests diff --git a/tests/chains/Filecoin/TWCoinTypeTests.cpp b/tests/chains/Filecoin/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..8fd34f04380 --- /dev/null +++ b/tests/chains/Filecoin/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWFilecoinCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeFilecoin)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("bafy2bzacedsgjcd6xfhrrymmfrqubb44otlyhvgqkgsh533d5j5hwniiqespm")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeFilecoin, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("f1abjxfbp274xpdqcpuaykwkfb43omjotacm2p3za")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeFilecoin, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeFilecoin)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeFilecoin)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeFilecoin), 18); + ASSERT_EQ(TWBlockchainFilecoin, TWCoinTypeBlockchain(TWCoinTypeFilecoin)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeFilecoin)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeFilecoin)); + assertStringsEqual(symbol, "FIL"); + assertStringsEqual(txUrl, "https://filfox.info/en/message/bafy2bzacedsgjcd6xfhrrymmfrqubb44otlyhvgqkgsh533d5j5hwniiqespm"); + assertStringsEqual(accUrl, "https://filfox.info/en/address/f1abjxfbp274xpdqcpuaykwkfb43omjotacm2p3za"); + assertStringsEqual(id, "filecoin"); + assertStringsEqual(name, "Filecoin"); +} diff --git a/tests/chains/Filecoin/TransactionCompilerTests.cpp b/tests/chains/Filecoin/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..f04883c127d --- /dev/null +++ b/tests/chains/Filecoin/TransactionCompilerTests.cpp @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "uint256.h" + +#include "proto/Filecoin.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(FilecoinCompiler, CompileWithSignatures) { + auto coin = TWCoinTypeFilecoin; + + /// Step 1: Prepare transaction input (protobuf) + Filecoin::Proto::SigningInput input; + auto privateKey = parse_hex("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe"); + auto key = PrivateKey(privateKey); + auto publicKey = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + auto toAddress = + "f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq"; + uint64_t nonce = 2; + // 600 FIL + // auto value = parse_hex("2086ac351052600000"); + auto value = store(uint256_t(600) * uint256_t(1'000'000'000) * uint256_t(1'000'000'000)); + uint64_t gasLimit = 1000; + // auto gasFeeCap = parse_hex("25f273933db5700000"); + auto gasFeeCap = store(uint256_t(700) * uint256_t(1'000'000'000) * uint256_t(1'000'000'000)); + // auto gasPremium = parse_hex("2b5e3af16b18800000"); + auto gasPremium = store(uint256_t(800) * uint256_t(1'000'000'000) * uint256_t(1'000'000'000)); + + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_to(toAddress); + input.set_nonce(nonce); + input.set_value(value.data(), value.size()); + input.set_gas_limit(gasLimit); + input.set_gas_fee_cap(gasFeeCap.data(), gasFeeCap.size()); + input.set_gas_premium(gasPremium.data(), gasPremium.size()); + input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + + auto txInputData = data(input.SerializeAsString()); + auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = data(preSigningOutput.data_hash()); + EXPECT_EQ(hex(preImageHash), "8368c0f622b2c529c7fa147d75aa02aaa7fc13fc4847d4dc57e7a5c59048aafe"); + + // Simulate signature, normally obtained from signature server + const auto signature = key.sign(preImageHash, TWCurveSECP256k1); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKey.bytes}); + + const auto ExpectedTx = R"({"Message":{"From":"f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq","GasFeeCap":"700000000000000000000","GasLimit":1000,"GasPremium":"800000000000000000000","Method":0,"Nonce":2,"To":"f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq","Value":"600000000000000000000"},"Signature":{"Data":"jMRu+OZ/lfppgmqSfGsntFrRLWZnUg3ZYmJTTRLsVt4V1310vR3VKGJpaE6S4sNvDOE6sEgmN9YmfTkPVK2qMgE=","Type":1}})"; + { + Filecoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + ASSERT_EQ(output.json(), ExpectedTx); + } + + // double check + { + Filecoin::Proto::SigningOutput output; + ANY_SIGN(input, coin); + + ASSERT_EQ(output.json(), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, {publicKey.bytes}); + Filecoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.json().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Filecoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.json().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/Filecoin/TransactionTests.cpp b/tests/chains/Filecoin/TransactionTests.cpp new file mode 100644 index 00000000000..82650f13499 --- /dev/null +++ b/tests/chains/Filecoin/TransactionTests.cpp @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Filecoin/Address.h" +#include "Filecoin/Transaction.h" +#include "HexCoding.h" +#include "PrivateKey.h" + +#include + +namespace TW::Filecoin { + +TEST(FilecoinTransaction, EncodeBigInt) { + ASSERT_EQ(hex(encodeBigInt(0)), ""); + ASSERT_EQ(hex(encodeBigInt(1)), "0001"); + ASSERT_EQ(hex(encodeBigInt(16)), "0010"); + ASSERT_EQ(hex(encodeBigInt(1111111111111)), "000102b36211c7"); + uint256_t reallyBig = 1; + reallyBig <<= 128; + ASSERT_EQ(hex(encodeBigInt(reallyBig)), "000100000000000000000000000000000000"); +} + +TEST(FilecoinTransaction, Serialize) { + const PrivateKey privateKey( + parse_hex("2f0f1d2c8de955c7c3fb4d9cae02539fadcb13fa998ccd9a1e871bed95f1941e")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + const Address fromAddress = Address::secp256k1Address(publicKey); + const Address toAddress("f1hvadvq4rd2pyayrigjx2nbqz2nvemqouslw4wxi"); + + Transaction tx(toAddress, fromAddress, + /*nonce*/ 0x1234567890, + /*value*/ 1000, + /*gasLimit*/ 3333333333, + /*gasFeeCap*/ 11111111, + /*gasPremium*/ 333333, + /*method*/ Transaction::MethodType::SEND, + /*params*/ Data()); + + ASSERT_EQ(hex(tx.message().encoded()), + "8a0055013d403ac3911e9f806228326fa68619d36a4641d455013d413d4c3fe3d89f99495a48c6046224" + "a71f0cd71b0000001234567890430003e81ac6aea1554400a98ac744000516150040"); + ASSERT_EQ(hex(tx.cid()), + "0171a0e40220a3b06c2837a94e3a431a78b00536d0298455ceec3d304adf26a3868147c4e6e1"); +} + +} // namespace TW::Filecoin diff --git a/tests/chains/Firo/TWCoinTypeTests.cpp b/tests/chains/Firo/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..8a29b13ddd0 --- /dev/null +++ b/tests/chains/Firo/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWFiroCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeFiro)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("09a60d58b3d17519a42a8eca60750c33b710ca8f3ca71994192e05c248a2a111")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeFiro, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a8ULhhDgfdSiXJhSZVdhb8EuDc6R3ogsaM")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeFiro, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeFiro)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeFiro)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeFiro), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeFiro)); + ASSERT_EQ(0x7, TWCoinTypeP2shPrefix(TWCoinTypeFiro)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeFiro)); + assertStringsEqual(symbol, "FIRO"); + assertStringsEqual(txUrl, "https://explorer.firo.org/tx/09a60d58b3d17519a42a8eca60750c33b710ca8f3ca71994192e05c248a2a111"); + assertStringsEqual(accUrl, "https://explorer.firo.org/address/a8ULhhDgfdSiXJhSZVdhb8EuDc6R3ogsaM"); + assertStringsEqual(id, "firo"); + assertStringsEqual(name, "Firo"); +} diff --git a/tests/chains/Firo/TWFiroAddressTests.cpp b/tests/chains/Firo/TWFiroAddressTests.cpp new file mode 100644 index 00000000000..9054b6100c5 --- /dev/null +++ b/tests/chains/Firo/TWFiroAddressTests.cpp @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +TEST(TWZCoin, Address) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeFiro))); + auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); + assertStringsEqual(addressString, "aAbqxogrjdy2YHVcnQxFHMzqpt2fhjCTVT"); +} + +TEST(TWZCoin, ExchangeAddress_CreateWithString) { + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING("aJtPAs49k2RYonsUoY9SGgmpzv4awdPfVP").get(), TWCoinTypeFiro)); + auto addressData = WRAPD(TWAnyAddressData(address.get())); + assertHexEqual(addressData, "c7529bf17541410428c7b23b402761acb83fdfba"); + + auto exchangeAddress = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING("EXXYdhSMM9Em5Z3kzdUWeUm2vFMNyXFSAEE9").get(), TWCoinTypeFiro)); + auto exchangeAddressData = WRAPD(TWAnyAddressData(exchangeAddress.get())); + assertHexEqual(exchangeAddressData, "c7529bf17541410428c7b23b402761acb83fdfba"); +} + +TEST(TWZCoin, ExchangeAddress_DeriveFromPublicKey) { + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("034cc1963365aa67d35643f419d6601eca6ef7f62e46bf7f8b6ffa64e2f44fd0bf").get(), TWPublicKeyTypeSECP256k1)); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyFiroAddressType(publicKey.get(), TWFiroAddressTypeExchange)); + auto addressDesc = WRAPS(TWAnyAddressDescription(address.get())); + assertStringsEqual(addressDesc, "EXXWKhUtcaFKVW1NeRFuqPq33zAJMtQJwR4y"); + + auto defaultAddress = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyFiroAddressType(publicKey.get(), TWFiroAddressTypeDefault)); + auto defaultAddressDesc = WRAPS(TWAnyAddressDescription(defaultAddress.get())); + assertStringsEqual(defaultAddressDesc, "aGaPDQKakaqVmQXGawLMLguZoqSx6CnSfK"); +} + +TEST(TWZCoin, ExtendedKeys) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( + STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), + STRING("TREZOR").get() + )); + + auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeFiro, TWHDVersionXPUB)); + auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeFiro, TWHDVersionXPRV)); + + assertStringsEqual(xpub, "xpub6Cb8Q6pDeS8PdKNbDv9Hvq4WpJXL3JvKvmHHwR1wD2H543hiCUE1f1tB5AXE6yg13k7xZ6PzEXMNUFHXk6kkx4RYte8VB1i4tCX9rwQVR4a"); + assertStringsEqual(xprv, "xprv9ybmzbHKp4a6QqJ87tcHZh7nGGgqdrCUZYMh92cKegk6BFNZevum7DZhDuVDqqMdcBT9B4wJSEmwJW9JNdkMcUUjEWKqppxNrJjKFSyKsCr"); +} + +TEST(TWZcoin, DeriveFromXpub) { + auto xpub = STRING("xpub6Cb8Q6pDeS8PdKNbDv9Hvq4WpJXL3JvKvmHHwR1wD2H543hiCUE1f1tB5AXE6yg13k7xZ6PzEXMNUFHXk6kkx4RYte8VB1i4tCX9rwQVR4a"); + auto pubKey3 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeFiro, STRING("m/44'/136'/0'/0/3").get())); + auto pubKey5 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeFiro, STRING("m/44'/136'/0'/0/5").get())); + + auto address3 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey3.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeFiro))); + auto address3String = WRAPS(TWBitcoinAddressDescription(address3.get())); + + auto address5 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey5.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeFiro))); + auto address5String = WRAPS(TWBitcoinAddressDescription(address5.get())); + + assertStringsEqual(address3String, "aLnztJEbyACnxF9H7SFC8YjUxedwyQsgVm"); + assertStringsEqual(address5String, "aJj2jdMzHyKFJLEFTxhpn379avEqRKFUyw"); +} + +TEST(TWZcoin, LockScripts) { + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("a4YtT82mWWxHZhLmdx7e5aroW92dqJoRs3").get(), TWCoinTypeFiro)); + auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); + assertHexEqual(scriptData2, "76a9142a10f88e30768d2712665c279922b9621ce58bc788ac"); + + auto script3 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("4CFa4fnAQvFz4VpikGNzQ9XfCDXMmdk6sh").get(), TWCoinTypeFiro)); + auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); + assertHexEqual(scriptData3, "a914f010b17a9189e0f2737d71ae9790359eb5bbc13787"); +} diff --git a/tests/chains/Firo/TransactionCompilerTests.cpp b/tests/chains/Firo/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..c5265971f52 --- /dev/null +++ b/tests/chains/Firo/TransactionCompilerTests.cpp @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include + +#include "Bitcoin/Script.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(FiroCompiler, FiroCompileWithSignatures) { + // tx on mainnet + // https://explorer.firo.org/tx/f1e9a418eb8d2bc96856ac221e9112ee061805af35d52be261caf7a7c9c48756 + + const auto coin = TWCoinTypeFiro; + const int64_t amount = 9999741; + const int64_t fee = 259; + const std::string toAddress = "EXXQe1Xhay75BzoFFhXgpqNTtLomdBKSfyMZ"; + + auto toScript = Bitcoin::Script::lockScriptForAddress(toAddress, coin); + ASSERT_EQ(hex(toScript.bytes), "e076a9146fa0b49c4fe011eeeeba6abb9ea6832d15acda1488ac"); + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("EXXWKhUtcaFKVW1NeRFuqPq33zAJMtQJwR4y"); + input.set_coin_type(coin); + input.set_lock_time(824147); + + auto txHash0 = parse_hex("7d46af1b51ac6d55554e4748f08d87727214da7c6148da037cb71dc893b6297f"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(1); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX - 1); + utxo0->set_amount(10000000); + + auto utxoAddr0 = "EXXWKhUtcaFKVW1NeRFuqPq33zAJMtQJwR4y"; + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, coin); + ASSERT_EQ(hex(script0.bytes), "e076a914adfae82521fb6bba65fecc265fe67e5ee476b5df88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + EXPECT_EQ(input.utxo_size(), 1); + + // Plan + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(input, plan, coin); + + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(0); + + // Extend input with accepted plan + *input.mutable_plan() = plan; + + // Serialize input + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT((int)txInputData.size(), 0); + + /// Step 2: Obtain preimage hashes + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + TW::Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), (int)preImageHashes.size())); + + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].data_hash()), + "c4841429065d36ec089c0d27b6f803b8fb1b2fb22d25629f38dcb40e2afff80d"); + + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].public_key_hash()), "adfae82521fb6bba65fecc265fe67e5ee476b5df"); + + auto publicKeyHex = "034cc1963365aa67d35643f419d6601eca6ef7f62e46bf7f8b6ffa64e2f44fd0bf"; + auto publicKey = PublicKey(parse_hex(publicKeyHex), TWPublicKeyTypeSECP256k1); + auto preImageHash = preSigningOutput.hash_public_keys()[0].data_hash(); + auto signature = parse_hex("304402206c5135f0ebfe329b1f1ba3b53730b2e1d02a6afca9c7c9ce007b8b956f9a235a0220482e76d74375b097bcd6275ab30d0c7a716263e744ecbbc33c651f83c15c4d99"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE( + publicKey.verifyAsDER(signature, TW::Data(preImageHash.begin(), preImageHash.end()))); + + // Simulate signatures, normally obtained from signature server. + std::vector signatureVec; + std::vector pubkeyVec; + signatureVec.push_back(signature); + pubkeyVec.push_back(publicKey.bytes); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, pubkeyVec); + + const auto ExpectedTx = + "01000000017f29b693c81db77c03da48617cda147272878df048474e55556dac511baf467d010000006a47304402206c5135f0ebfe329b1f1ba3b53730b2e1d02a6afca9c7c9ce007b8b956f9a235a0220482e76d74375b097bcd6275ab30d0c7a716263e744ecbbc33c651f83c15c4d990121034cc1963365aa67d35643f419d6601eca6ef7f62e46bf7f8b6ffa64e2f44fd0bffeffffff017d959800000000001ae076a9146fa0b49c4fe011eeeeba6abb9ea6832d15acda1488ac53930c00"; + { + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: not enough signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, pubkeyVec); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + { // Negative: invalid public key + const auto publicKeyBlake = + parse_hex("b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"); + EXPECT_EXCEPTION( + TransactionCompiler::compileWithSignatures( + coin, txInputData, signatureVec, {publicKeyBlake}), + "Invalid public key"); + } + { // Negative: wrong signature (formally valid) + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, + {parse_hex("415502201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b349022" + "00a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f3b51")}, + pubkeyVec); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signing); + } +} diff --git a/tests/chains/GoChain/TWCoinTypeTests.cpp b/tests/chains/GoChain/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..052a2700c5a --- /dev/null +++ b/tests/chains/GoChain/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWGoChainCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeGoChain)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeGoChain, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeGoChain, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeGoChain)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeGoChain)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeGoChain), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeGoChain)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeGoChain)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeGoChain)); + assertStringsEqual(symbol, "GO"); + assertStringsEqual(txUrl, "https://explorer.gochain.io/tx/t123"); + assertStringsEqual(accUrl, "https://explorer.gochain.io/addr/a12"); + assertStringsEqual(id, "gochain"); + assertStringsEqual(name, "GoChain"); +} diff --git a/tests/chains/Greenfield/TWAnyAddressTests.cpp b/tests/chains/Greenfield/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..5e4736f9de0 --- /dev/null +++ b/tests/chains/Greenfield/TWAnyAddressTests.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWGreenfield, Address) { + auto string = STRING("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeGreenfield)); + auto actual = WRAPS(TWAnyAddressDescription(addr.get())); + auto expected = STRING("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"); + EXPECT_TRUE(TWStringEqual(actual.get(), expected.get())); +} diff --git a/tests/chains/Greenfield/TWAnySignerTests.cpp b/tests/chains/Greenfield/TWAnySignerTests.cpp new file mode 100644 index 00000000000..12afa89b367 --- /dev/null +++ b/tests/chains/Greenfield/TWAnySignerTests.cpp @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/Greenfield.pb.h" +#include "TestUtilities.h" + +#include +#include + +namespace TW::Greenfield::tests { + +TEST(TWAnySignerGreenfield, Sign) { + // Successfully broadcasted: https://greenfieldscan.com/tx/ED8508F3C174C4430B8EE718A6D6F0B02A8C516357BE72B1336CF74356529D19 + + Proto::SigningInput input; + input.set_signing_mode(Proto::Eip712); + input.set_account_number(15952); + input.set_cosmos_chain_id("greenfield_5600-1"); + input.set_eth_chain_id("5600"); + input.set_sequence(0); + input.set_mode(Proto::BroadcastMode::SYNC); + input.set_memo("Trust Wallet test memo"); + + auto &msg = *input.add_messages(); + auto &msgSend = *msg.mutable_send_coins_message(); + msgSend.set_from_address("0xA815ae0b06dC80318121745BE40e7F8c6654e9f3"); + msgSend.set_to_address("0x8dbD6c7Ede90646a61Bbc649831b7c298BFd37A0"); + auto amountOfTx = msgSend.add_amounts(); + amountOfTx->set_denom("BNB"); + amountOfTx->set_amount("1234500000000000"); + + auto &fee = *input.mutable_fee(); + fee.set_gas(1200); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("BNB"); + amountOfFee->set_amount("6000000000000"); + + auto privateKey = parse_hex("825d2bb32965764a98338139412c7591ed54c951dd65504cd8ddaeaa0fea7b2a"); + input.set_private_key(privateKey.data(), privateKey.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeGreenfield); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + EXPECT_EQ(hex(output.signature()), "c1b45bd6a1005b06aa55f9a9d4c9fb88c8bbc3057fa0f8b6276796f4d04874da24cbe64bfae7a04bf918f9fba708eaea559f8a6e897dfdd8c057e6d068d501d31c"); + EXPECT_EQ(output.serialized(), R"({"mode":"BROADCAST_MODE_SYNC","tx_bytes":"CqwBCpEBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnEKKjB4QTgxNWFlMGIwNmRDODAzMTgxMjE3NDVCRTQwZTdGOGM2NjU0ZTlmMxIqMHg4ZGJENmM3RWRlOTA2NDZhNjFCYmM2NDk4MzFiN2MyOThCRmQzN0EwGhcKA0JOQhIQMTIzNDUwMDAwMDAwMDAwMBIWVHJ1c3QgV2FsbGV0IHRlc3QgbWVtbxJzClYKTQomL2Nvc21vcy5jcnlwdG8uZXRoLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohAhm/mQgs8vzaqBLW66HrqQNv86PYTBgXyElU1OiuKD/sEgUKAwjIBRIZChQKA0JOQhINNjAwMDAwMDAwMDAwMBCwCRpBwbRb1qEAWwaqVfmp1Mn7iMi7wwV/oPi2J2eW9NBIdNoky+ZL+uegS/kY+funCOrqVZ+Kbol9/djAV+bQaNUB0xw="})"); +} + +} // namespace TW::Greenfield::tests diff --git a/tests/chains/Greenfield/TWCoinTypeTests.cpp b/tests/chains/Greenfield/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..37292589ba1 --- /dev/null +++ b/tests/chains/Greenfield/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWGreenfieldCoinType, TWCoinType) { + const auto coin = TWCoinTypeGreenfield; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x150eac42070957115fd538b1f348fadd78d710fb641c248120efcf35d1e7e4f3")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xcf0f6b88ed72653b00fdebbffc90b98072cb3285")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "greenfield"); + assertStringsEqual(name, "BNB Greenfield"); + assertStringsEqual(symbol, "BNB"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainGreenfield); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "1017"); + assertStringsEqual(txUrl, "https://greenfieldscan.com/tx/0x150eac42070957115fd538b1f348fadd78d710fb641c248120efcf35d1e7e4f3"); + assertStringsEqual(accUrl, "https://greenfieldscan.com/account/0xcf0f6b88ed72653b00fdebbffc90b98072cb3285"); +} diff --git a/tests/chains/Greenfield/TransactionCompilerTests.cpp b/tests/chains/Greenfield/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..9766bbcaa0c --- /dev/null +++ b/tests/chains/Greenfield/TransactionCompilerTests.cpp @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TestUtilities.h" +#include "TransactionCompiler.h" + +#include "proto/Greenfield.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include + +#include + +using namespace TW; + +namespace TW::Greenfield { + +TEST(GreenfieldCompiler, PreHashCompile) { + // Successfully broadcasted https://greenfieldscan.com/tx/0x9f895cf2dd64fb1f428cefcf2a6585a813c3540fc9fe1ef42db1da2cb1df55ab + + auto privateKeyData = parse_hex("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"); + PrivateKey privateKey(privateKeyData); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + + Proto::SigningInput input; + input.set_signing_mode(Proto::Eip712); + input.set_account_number(15560); + input.set_cosmos_chain_id("greenfield_5600-1"); + input.set_eth_chain_id("5600"); + input.set_sequence(2); + input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + + auto& msg = *input.add_messages(); + auto& msgSend = *msg.mutable_send_coins_message(); + msgSend.set_from_address("0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1"); + msgSend.set_to_address("0x280b27f3676db1C4475EE10F75D510Eb527fd155"); + auto amountOfTx = msgSend.add_amounts(); + amountOfTx->set_denom("BNB"); + amountOfTx->set_amount("1000000000000000"); + + auto& fee = *input.mutable_fee(); + fee.set_gas(200000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("BNB"); + amountOfFee->set_amount("2000000000000000"); + + // Step 1: PreHash + + auto inputData = data(input.SerializeAsString()); + auto preOutputData = TransactionCompiler::preImageHashes(TWCoinTypeGreenfield, inputData); + TW::TxCompiler::Proto::PreSigningOutput preOutput; + preOutput.ParseFromArray(preOutputData.data(), static_cast(preOutputData.size())); + + EXPECT_EQ(preOutput.error(), Common::Proto::SigningError::OK); + EXPECT_EQ(hex(preOutput.data_hash()), "b8c62654582ca96b37ca94966199682bf70ed934e740d2f874ff54675a0ac344"); + + // Step 2: Sign "remotely" + + auto signature = privateKey.sign(data(preOutput.data_hash()), TWCurveSECP256k1); + + EXPECT_EQ(hex(signature), "cb3a4684a991014a387a04a85b59227ebb79567c2025addcb296b4ca856e9f810d3b526f2a0d0fad6ad1b126b3b9516f8b3be020a7cca9c03ce3cf47f4199b6d00"); + + // Step 3: Compile + + auto outputData = TransactionCompiler::compileWithSignatures(TWCoinTypeGreenfield, inputData, {signature}, {publicKey.bytes}); + Proto::SigningOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + EXPECT_EQ(hex(output.signature()), "cb3a4684a991014a387a04a85b59227ebb79567c2025addcb296b4ca856e9f810d3b526f2a0d0fad6ad1b126b3b9516f8b3be020a7cca9c03ce3cf47f4199b6d1b"); + EXPECT_EQ(output.serialized(), R"({"mode":"BROADCAST_MODE_SYNC","tx_bytes":"CpQBCpEBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnEKKjB4OWQxZDk3YURGY2QzMjRCYmQ2MDNEMzg3MkJENzhlMDQwOTg1MTBiMRIqMHgyODBiMjdmMzY3NmRiMUM0NDc1RUUxMEY3NUQ1MTBFYjUyN2ZkMTU1GhcKA0JOQhIQMTAwMDAwMDAwMDAwMDAwMBJ5ClgKTQomL2Nvc21vcy5jcnlwdG8uZXRoLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohAnnvNAZNoQ2wRjxwSAYWugIHA+w6RQJt73vr0ggvXW/IEgUKAwjIBRgCEh0KFwoDQk5CEhAyMDAwMDAwMDAwMDAwMDAwEMCaDBpByzpGhKmRAUo4egSoW1kifrt5VnwgJa3cspa0yoVun4ENO1JvKg0PrWrRsSazuVFvizvgIKfMqcA8489H9BmbbRs="})"); +} + +} // namespace TW::Greenfield diff --git a/tests/chains/Groestlcoin/AddressTests.cpp b/tests/chains/Groestlcoin/AddressTests.cpp new file mode 100644 index 00000000000..8fcce675012 --- /dev/null +++ b/tests/chains/Groestlcoin/AddressTests.cpp @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Groestlcoin/Address.h" + +#include "Coin.h" +#include "HDWallet.h" +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +namespace TW::Groestlcoin::tests { + +TEST(GroestlcoinAddress, FromPublicKey) { + const auto publicKey = PublicKey(parse_hex("03b85cc59b67c35851eb5060cfc3a759a482254553c5857075c9e247d74d412c91"), TWPublicKeyTypeSECP256k1); + const auto address = Address(publicKey, 36); + ASSERT_EQ(address.string(), "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); + + const auto privateKey = PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")); + const auto publicKey2 = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeED25519)); + EXPECT_ANY_THROW(new Address(publicKey2, 36)); +} + +TEST(GroestlcoinAddress, Valid) { + ASSERT_TRUE(Address::isValid(std::string("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"))); + TW::Data addressData; + addressData.push_back(TW::p2pkhPrefix(TWCoinTypeGroestlcoin)); + + auto addressHash = parse_hex("98af0aaca388a7e1024f505c033626d908e3b54a"); + std::copy(addressHash.begin(), addressHash.end(), std::back_inserter(addressData)); + + ASSERT_EQ(Address(addressData).string(), "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); +} + +TEST(GroestlcoinAddress, Invalid) { + EXPECT_EXCEPTION(Address("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLo"), "Invalid address string"); // Invalid address + EXPECT_EXCEPTION(Address(parse_hex("98af0aaca388a7e1024f505c033626d908e3b5")), "Invalid address key data"); // Invalid address data + ASSERT_FALSE(Address::isValid(std::string("1JAd7XCBzGudGpJQSDSfpmJhiygtLQWaGL"))); // Valid bitcoin address +} + +TEST(GroestlcoinAddress, FromString) { + const auto string = "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"; + const auto address = Address(string); + + ASSERT_EQ(address.string(), string); +} + +TEST(GroestlcoinAddress, Derive) { + const auto mnemonic = "all all all all all all all all all all all all"; + const auto wallet = HDWallet(mnemonic, ""); + const auto path = TW::derivationPath(TWCoinTypeGroestlcoin); + auto address = TW::deriveAddress(TWCoinTypeGroestlcoin, wallet.getKey(TWCoinTypeGroestlcoin, path)); + ASSERT_EQ(address, "grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne"); + + address = TW::deriveAddress(TWCoinTypeGroestlcoin, wallet.getKey(TWCoinTypeGroestlcoin, path), TWDerivationBitcoinLegacy); + ASSERT_EQ(address, "FfsAQrzfdECwEsApubn2rvxgamU8CcqsLT"); +} + +TEST(GroestlcoinAddress, AddressData) { + const auto publicKey = PublicKey(parse_hex("03b85cc59b67c35851eb5060cfc3a759a482254553c5857075c9e247d74d412c91"), TWPublicKeyTypeSECP256k1); + auto address = TW::deriveAddress(TWCoinTypeGroestlcoin, publicKey, TWDerivationBitcoinLegacy); + ASSERT_EQ(address, "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); + + auto addressData = TW::addressToData(TWCoinTypeGroestlcoin, address); + ASSERT_EQ(hex(addressData), "98af0aaca388a7e1024f505c033626d908e3b54a"); + + address = TW::deriveAddress(TWCoinTypeGroestlcoin, publicKey, TWDerivationBitcoinSegwit); + EXPECT_EQ(address, "grs1qnzhs4t9r3zn7zqj02pwqxd3xmyyw8d22q55nf8"); + + addressData = TW::addressToData(TWCoinTypeGroestlcoin, address); + EXPECT_EQ(hex(addressData), "98af0aaca388a7e1024f505c033626d908e3b54a"); +} + +} // namespace TW::Groestlcoin::tests diff --git a/tests/chains/Groestlcoin/TWCoinTypeTests.cpp b/tests/chains/Groestlcoin/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..974fdbe9881 --- /dev/null +++ b/tests/chains/Groestlcoin/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWGroestlcoinCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeGroestlcoin)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeGroestlcoin, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeGroestlcoin, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeGroestlcoin)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeGroestlcoin)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeGroestlcoin), 8); + ASSERT_EQ(TWBlockchainGroestlcoin, TWCoinTypeBlockchain(TWCoinTypeGroestlcoin)); + ASSERT_EQ(0x5, TWCoinTypeP2shPrefix(TWCoinTypeGroestlcoin)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeGroestlcoin)); + assertStringsEqual(symbol, "GRS"); + assertStringsEqual(txUrl, "https://blockchair.com/groestlcoin/transaction/t123"); + assertStringsEqual(accUrl, "https://blockchair.com/groestlcoin/address/a12"); + assertStringsEqual(id, "groestlcoin"); + assertStringsEqual(name, "Groestlcoin"); +} diff --git a/tests/Groestlcoin/TWGroestlcoinSigningTests.cpp b/tests/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp similarity index 88% rename from tests/Groestlcoin/TWGroestlcoinSigningTests.cpp rename to tests/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp index 7e81ec429b1..db0c4dc4702 100644 --- a/tests/Groestlcoin/TWGroestlcoinSigningTests.cpp +++ b/tests/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp @@ -1,19 +1,15 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "Bitcoin/OutPoint.h" #include "Bitcoin/Script.h" +#include "Groestlcoin/Signer.h" #include "Hash.h" #include "HexCoding.h" #include "PrivateKey.h" #include "proto/Bitcoin.pb.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "../Bitcoin/TxComparisonHelper.h" - -#include #include #include #include @@ -21,11 +17,11 @@ #include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { TEST(GroestlcoinSigning, SignP2WPKH) { Proto::SigningInput input; + input.set_coin_type(TWCoinTypeGroestlcoin); input.set_hash_type(TWBitcoinSigHashTypeAll); input.set_amount(2500); input.set_byte_fee(1); @@ -68,6 +64,7 @@ TEST(GroestlcoinSigning, SignP2WPKH) { TEST(GroestlcoinSigning, SignP2PKH) { Proto::SigningInput input; + input.set_coin_type(TWCoinTypeGroestlcoin); input.set_hash_type(TWBitcoinSigHashTypeAll); input.set_amount(2500); input.set_byte_fee(1); @@ -108,9 +105,27 @@ TEST(GroestlcoinSigning, SignP2PKH) { ASSERT_EQ(hex(output.encoded()), "01000000019568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f000000006a47304402202163ab98b028aa13563f0de00b785d6df81df5eac0b7c91d23f5be7ea674aa3702202bf6cd7055c6f8f697ce045b1a4f9b997cf6e5761a661d27696ac34064479d19012103b85cc59b67c35851eb5060cfc3a759a482254553c5857075c9e247d74d412c91ffffffff02c4090000000000001600147557920fbc32a1ef4ef26bae5e8ce3f95abf09cee20800000000000017a9140055b0c94df477ee6b9f75185dfc9aa8ce2e52e48700000000"); } +TEST(GroestlcoinSigning, SignWithError) { + Proto::SigningInput input; + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(2500); + input.set_byte_fee(1); + input.set_to_address("grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne"); + input.set_change_address("31inaRqambLsd9D7Ke4USZmGEVd3PHkh7P"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeGroestlcoin); + + ASSERT_NE(output.error(), Common::Proto::OK); + + auto result = Groestlcoin::Signer::preImageHashes(input); + ASSERT_NE(result.error(), Common::Proto::OK); +} + TEST(GroestlcoinSigning, SignP2SH_P2WPKH) { // TX outputs Proto::SigningInput input; + input.set_coin_type(TWCoinTypeGroestlcoin); input.set_hash_type(TWBitcoinSigHashTypeAll); input.set_amount(5'000); input.set_byte_fee(1); @@ -121,14 +136,14 @@ TEST(GroestlcoinSigning, SignP2SH_P2WPKH) { auto utxoKey0 = PrivateKey(parse_hex("302fc195a8fc96c5a581471e67e4c1ac2efda252f76ad5c77a53764c70d58f91")); auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey0.bytes)); - EXPECT_EQ(hex(utxoPubkeyHash.begin(), utxoPubkeyHash.end()), "2fc7d70acef142d1f7b5ef2f20b1a9b759797674"); + EXPECT_EQ(hex(utxoPubkeyHash), "2fc7d70acef142d1f7b5ef2f20b1a9b759797674"); input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); - ASSERT_EQ(hex(scriptHash.begin(), scriptHash.end()), "0055b0c94df477ee6b9f75185dfc9aa8ce2e52e4"); + ASSERT_EQ(hex(scriptHash), "0055b0c94df477ee6b9f75185dfc9aa8ce2e52e4"); auto scriptString = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - (*input.mutable_scripts())[hex(scriptHash.begin(), scriptHash.end())] = scriptString; + (*input.mutable_scripts())[hex(scriptHash)] = scriptString; auto utxo0 = input.add_utxo(); auto utxo0Script = Script(parse_hex("a9140055b0c94df477ee6b9f75185dfc9aa8ce2e52e487")); @@ -163,6 +178,7 @@ TEST(GroestlcoinSigning, SignP2SH_P2WPKH) { TEST(GroestlcoinSigning, PlanP2WPKH) { Proto::SigningInput input; + input.set_coin_type(TWCoinTypeGroestlcoin); input.set_hash_type(TWBitcoinSigHashTypeAll); input.set_amount(2500); input.set_byte_fee(1); @@ -187,3 +203,5 @@ TEST(GroestlcoinSigning, PlanP2WPKH) { EXPECT_TRUE(verifyPlan(plan, {4774}, 2500, 145)); EXPECT_EQ(plan.branch_id(), ""); } + +} // namespace TW::Bitcoin diff --git a/tests/Groestlcoin/TWGroestlcoinTests.cpp b/tests/chains/Groestlcoin/TWGroestlcoinTests.cpp similarity index 93% rename from tests/Groestlcoin/TWGroestlcoinTests.cpp rename to tests/chains/Groestlcoin/TWGroestlcoinTests.cpp index a00b684b019..bb186cfd48f 100644 --- a/tests/Groestlcoin/TWGroestlcoinTests.cpp +++ b/tests/chains/Groestlcoin/TWGroestlcoinTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -23,11 +21,17 @@ TEST(Groestlcoin, Address) { auto addressString = WRAPS(TWGroestlcoinAddressDescription(address.get())); assertStringsEqual(addressString, "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); + ASSERT_TRUE(TWGroestlcoinAddressIsValidString(addressString.get())); + auto address2 = WRAP(TWGroestlcoinAddress, TWGroestlcoinAddressCreateWithString(STRING("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM").get())); auto address2String = WRAPS(TWGroestlcoinAddressDescription(address2.get())); assertStringsEqual(address2String, "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); ASSERT_TRUE(TWGroestlcoinAddressEqual(address.get(), address2.get())); + + // invalid address + auto address3 = WRAP(TWGroestlcoinAddress, TWGroestlcoinAddressCreateWithString(STRING("Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLo").get())); + ASSERT_EQ(address3, nullptr); } TEST(Groestlcoin, BuildForLegacyAddress) { diff --git a/tests/chains/Groestlcoin/TransactionCompilerTests.cpp b/tests/chains/Groestlcoin/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..dfe513977cf --- /dev/null +++ b/tests/chains/Groestlcoin/TransactionCompilerTests.cpp @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" + +#include +#include +#include + +#include "Groestlcoin/Signer.h" +#include "Groestlcoin/Transaction.h" + +#include "../Bitcoin/TxComparisonHelper.h" +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::Bitcoin { + +TEST(GroestlcoinCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeGroestlcoin; + + Bitcoin::Proto::SigningInput input; + input.set_coin_type(coin); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(2500); + input.set_byte_fee(1); + input.set_to_address("grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne"); + input.set_change_address("31inaRqambLsd9D7Ke4USZmGEVd3PHkh7P"); + + auto utxoKey0 = parse_hex("3c3385ddc6fd95ba7282051aeb440bc75820b8c10db5c83c052d7586e3e98e84"); + input.add_private_key(utxoKey0.data(), utxoKey0.size()); + + auto utxo0 = input.add_utxo(); + auto utxo0Script = Bitcoin::Script(parse_hex("76a91498af0aaca388a7e1024f505c033626d908e3b54a88ac")); + utxo0->set_script(utxo0Script.bytes.data(), utxo0Script.bytes.size()); + utxo0->set_amount(5000); + auto hash0 = parse_hex("9568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f"); + utxo0->mutable_out_point()->set_hash(hash0.data(), hash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + + Bitcoin::Proto::TransactionPlan plan; + { + // try plan first + ANY_PLAN(input, plan, coin); + EXPECT_TRUE(verifyPlan(plan, {5000}, 2500, 221)); + } + + // Supply plan for signing, to match fee of previously-created real TX + *input.mutable_plan() = plan; + input.mutable_plan()->set_fee(226); + input.mutable_plan()->set_change(2274); + + // build preimage + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + ASSERT_EQ(preSigningOutput.hash_public_keys().size(), 1); + + auto preImageHash = data(preSigningOutput.hash_public_keys()[0].data_hash()); + EXPECT_EQ(hex(preImageHash), + "0fb3da786ad1028574f0b40ff1446515eb85cacccff3f3d0459e191b660597b3"); + + // compile + auto publicKey = PrivateKey(utxoKey0).getPublicKey(TWPublicKeyTypeSECP256k1); + auto signature = parse_hex("304402202163ab98b028aa13563f0de00b785d6df81df5eac0b7c91d23f5be7ea674aa3702202bf6cd7055c6f8f697ce045b1a4f9b997cf6e5761a661d27696ac34064479d19"); + { + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKey.bytes}); + Bitcoin::Proto::SigningOutput signingOutput; + ASSERT_TRUE(signingOutput.ParseFromArray(outputData.data(), (int)outputData.size())); + ASSERT_EQ(hex(signingOutput.encoded()), + "01000000019568b09e6c6d940302ec555a877c9e5f799de8ee473e18d3a19ae14478cc4e8f000000" + "006a47304402202163ab98b028aa13563f0de00b785d6df81df5eac0b7c91d23f5be7ea674aa3702" + "202bf6cd7055c6f8f697ce045b1a4f9b997cf6e5761a661d27696ac34064479d19012103b85cc59b" + "67c35851eb5060cfc3a759a482254553c5857075c9e247d74d412c91ffffffff02c4090000000000" + "001600147557920fbc32a1ef4ef26bae5e8ce3f95abf09cee20800000000000017a9140055b0c94d" + "f477ee6b9f75185dfc9aa8ce2e52e48700000000"); + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(input, coin); + ASSERT_EQ(output.encoded(), signingOutput.encoded()); + } + + { // Negative: not enough signatures + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature}, {publicKey.bytes, publicKey.bytes}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + { // Negative: empty signatures + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +} // TW::Bitcoin \ No newline at end of file diff --git a/tests/chains/Harmony/AddressTests.cpp b/tests/chains/Harmony/AddressTests.cpp new file mode 100644 index 00000000000..3d7786b76be --- /dev/null +++ b/tests/chains/Harmony/AddressTests.cpp @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Harmony/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" + +#include + +using namespace TW; + +namespace TW::Harmony::tests { + +TEST(HarmonyAddress, FromString) { + Address sender; + ASSERT_TRUE(Address::decode("one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe", sender)); + Address receiver; + ASSERT_TRUE(Address::decode("one1tp7xdd9ewwnmyvws96au0e7e7mz6f8hjqr3g3p", receiver)); + + ASSERT_EQ("ed1ebe4fd1f73f86388f231997859ca42c07da5d", hex(sender.getKeyHash())); + ASSERT_EQ("587c66b4b973a7b231d02ebbc7e7d9f6c5a49ef2", hex(receiver.getKeyHash())); +} + +TEST(HarmonyAddress, FromData) { + const auto address = Address(parse_hex("0x587c66b4b973a7b231d02ebbc7e7d9f6c5a49ef2")); + const auto address_2 = Address(parse_hex("0xed1ebe4fd1f73f86388f231997859ca42c07da5d")); + ASSERT_EQ(address.string(), "one1tp7xdd9ewwnmyvws96au0e7e7mz6f8hjqr3g3p"); + ASSERT_EQ(address_2.string(), "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe"); + + EXPECT_ANY_THROW(new Address(parse_hex(""))); +} + +TEST(HarmonyAddress, InvalidHarmonyAddress) { + ASSERT_FALSE(Address::isValid("one1a50tun737ulcvwy0yvve0pe")); + ASSERT_FALSE(Address::isValid("oe1tp7xdd9ewwnmyvws96au0ee7e7mz6f8hjqr3g3p")); +} + +TEST(HarmonyAddress, FromPublicKey) { + const auto privateKey = + PrivateKey(parse_hex("e2f88b4974ae763ca1c2db49218802c2e441293a09eaa9ab681779e05d1b7b94")); + const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe"); + + const auto publicKey2 = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeED25519)); + EXPECT_ANY_THROW(new Address(publicKey2)); +} + +} // namespace TW::Harmony::tests diff --git a/tests/chains/Harmony/SignerTests.cpp b/tests/chains/Harmony/SignerTests.cpp new file mode 100644 index 00000000000..dc917bf26b5 --- /dev/null +++ b/tests/chains/Harmony/SignerTests.cpp @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "Ethereum/RLP.h" +#include "Harmony/Address.h" +#include "Harmony/Signer.h" +#include "HexCoding.h" +#include "proto/Harmony.pb.h" + +namespace TW::Harmony { + +using namespace boost::multiprecision; + +class SignerExposed : public Signer { + public: + SignerExposed(uint256_t chainID) : Signer(chainID) {} + using Signer::hash; +}; + +static uint256_t MAIN_NET = 0x1; + +static uint256_t LOCAL_NET = 0x2; + +static uint256_t TEST_AMOUNT = uint256_t("0x4c53ecdc18a60000"); + +static Address TEST_RECEIVER; +static bool testReceiverDecodeResult = + Address::decode("one1d2rngmem4x2c6zxsjjz29dlah0jzkr0k2n88wc", TEST_RECEIVER); + +static auto TEST_TRANSACTION = Transaction(/* nonce: */ 0x9, + /* gasPrice: */ 0x0, + /* gasLimit: */ 0x5208, + /* fromShardID */ 0x1, + /* toShardID */ 0x0, + /* to: */ TEST_RECEIVER, + /* amount: */ TEST_AMOUNT, + /* payload: */ {}); + +TEST(HarmonySigner, RLPEncodingAndHashAssumeLocalNet) { + auto rlpUnhashedShouldBe = "e909808252080180946a87346f3ba9958d08d09484a" + "2b7fdbbe42b0df6884c53ecdc18a6000080028080"; + auto rlpHashedShouldBe = "610238ad72e4492af494f49bf5d92" + "13626a0ee5adb8256bb2558e990ee4da8f0"; + auto signer = SignerExposed(LOCAL_NET); + auto rlpHex = signer.txnAsRLPHex(TEST_TRANSACTION); + auto hash = signer.hash(TEST_TRANSACTION); + + ASSERT_EQ(rlpHex, rlpUnhashedShouldBe); + ASSERT_EQ(hex(hash), rlpHashedShouldBe); +} + +TEST(HarmonySigner, SignAssumeLocalNet) { + auto key = + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + auto signer = SignerExposed(LOCAL_NET); + + uint256_t v("0x28"); + uint256_t r("0x325aed6caa01a5235b7a508c8ab67f0c43946b05a1ea6a3e0628de4033fe372d"); + uint256_t s("0x6c19085d3376c30f6dc47cec795991cd37d6d0ebddfa633b0a8f494bc19cd01b"); + + auto transaction = Transaction(TEST_TRANSACTION); + auto hash = signer.hash(transaction); + + signer.sign(key, hash, transaction); + + ASSERT_EQ(transaction.v, v); + ASSERT_EQ(transaction.r, r); + ASSERT_EQ(transaction.s, s); +} + +TEST(HarmonySigner, SignProtoBufAssumeLocalNet) { + auto input = Proto::SigningInput(); + auto trasactionMsg = input.mutable_transaction_message(); + + trasactionMsg->set_to_address(TEST_RECEIVER.string()); + const auto privateKey = + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + auto payload = parse_hex(""); + trasactionMsg->set_payload(payload.data(), payload.size()); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto value = store(LOCAL_NET); + input.set_chain_id(value.data(), value.size()); + + value = store(uint256_t("0x9")); + trasactionMsg->set_nonce(value.data(), value.size()); + + value = store(uint256_t("")); + trasactionMsg->set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x5208")); + trasactionMsg->set_gas_limit(value.data(), value.size()); + + value = store(uint256_t("0x1")); + trasactionMsg->set_from_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x0")); + trasactionMsg->set_to_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x4c53ecdc18a60000")); + trasactionMsg->set_amount(value.data(), value.size()); + + auto proto_output = Signer::sign(input); + + auto shouldBeV = "28"; + auto shouldBeR = "325aed6caa01a5235b7a508c8ab67f0c43946b05a1ea6a3e0628de4033fe372d"; + auto shouldBeS = "6c19085d3376c30f6dc47cec795991cd37d6d0ebddfa633b0a8f494bc19cd01b"; + + ASSERT_EQ(hex(proto_output.v()), shouldBeV); + ASSERT_EQ(hex(proto_output.r()), shouldBeR); + ASSERT_EQ(hex(proto_output.s()), shouldBeS); +} + +TEST(HarmonySigner, SignOverProtoBufAssumeMainNet) { + auto input = Proto::SigningInput(); + auto trasactionMsg = input.mutable_transaction_message(); + trasactionMsg->set_to_address(TEST_RECEIVER.string()); + const auto privateKey = + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + auto payload = parse_hex(""); + trasactionMsg->set_payload(payload.data(), payload.size()); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto value = store(MAIN_NET); + input.set_chain_id(value.data(), value.size()); + + value = store(uint256_t("0xa")); + trasactionMsg->set_nonce(value.data(), value.size()); + + value = store(uint256_t("")); + trasactionMsg->set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x5208")); + trasactionMsg->set_gas_limit(value.data(), value.size()); + + value = store(uint256_t("0x1")); + trasactionMsg->set_from_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x0")); + trasactionMsg->set_to_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x4c53ecdc18a60000")); + trasactionMsg->set_amount(value.data(), value.size()); + + auto proto_output = Signer::sign(input); + + auto expectEncoded = "f8690a808252080180946a87346f3ba9958d08d09484a2b7fdbbe42b0df6884c53ecdc18a" + "600008026a074acbc63a58e7861e54ca24babf1cb800c5b694da25c3ae2b1543045053667" + "08a0616ab8262ee6f6fb30ffcab3e9e8261479c7469ce97010a70b3d3f962842c61a"; + + auto v = "26"; + auto r = "74acbc63a58e7861e54ca24babf1cb800c5b694da25c3ae2b154304505366708"; + auto s = "616ab8262ee6f6fb30ffcab3e9e8261479c7469ce97010a70b3d3f962842c61a"; + + ASSERT_EQ(hex(proto_output.encoded()), expectEncoded); + ASSERT_EQ(hex(proto_output.v()), v); + ASSERT_EQ(hex(proto_output.r()), r); + ASSERT_EQ(hex(proto_output.s()), s); +} + +TEST(HarmonySigner, BuildSigningOutput) { + auto input = Proto::SigningInput(); + auto trasactionMsg = input.mutable_transaction_message(); + trasactionMsg->set_to_address(TEST_RECEIVER.string()); + const auto privateKey = + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + auto payload = parse_hex(""); + trasactionMsg->set_payload(payload.data(), payload.size()); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto value = store(MAIN_NET); + input.set_chain_id(value.data(), value.size()); + + value = store(uint256_t("0xa")); + trasactionMsg->set_nonce(value.data(), value.size()); + + value = store(uint256_t("")); + trasactionMsg->set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x5208")); + trasactionMsg->set_gas_limit(value.data(), value.size()); + + value = store(uint256_t("0x1")); + trasactionMsg->set_from_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x0")); + trasactionMsg->set_to_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x4c53ecdc18a60000")); + trasactionMsg->set_amount(value.data(), value.size()); + + Data signature = parse_hex("74acbc63a58e7861e54ca24babf1cb800c5b694da25c3ae2b154304505366708616ab8262ee6f6fb30ffcab3e9e8261479c7469ce97010a70b3d3f962842c61a01"); + + Signer signer(uint256_t(load(input.chain_id()))); + auto proto_output = signer.buildSigningOutput(input, signature); + + auto expectEncoded = "f8690a808252080180946a87346f3ba9958d08d09484a2b7fdbbe42b0df6884c53ecdc18a" + "600008026a074acbc63a58e7861e54ca24babf1cb800c5b694da25c3ae2b1543045053667" + "08a0616ab8262ee6f6fb30ffcab3e9e8261479c7469ce97010a70b3d3f962842c61a"; + + auto v = "26"; + auto r = "74acbc63a58e7861e54ca24babf1cb800c5b694da25c3ae2b154304505366708"; + auto s = "616ab8262ee6f6fb30ffcab3e9e8261479c7469ce97010a70b3d3f962842c61a"; + + ASSERT_EQ(hex(proto_output.encoded()), expectEncoded); + ASSERT_EQ(hex(proto_output.v()), v); + ASSERT_EQ(hex(proto_output.r()), r); + ASSERT_EQ(hex(proto_output.s()), s); +} + +TEST(HarmonySigner, BuildUnsignedTxBytes) { + auto input = Proto::SigningInput(); + auto trasactionMsg = input.mutable_transaction_message(); + trasactionMsg->set_to_address(TEST_RECEIVER.string()); + const auto privateKey = + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + auto payload = parse_hex(""); + trasactionMsg->set_payload(payload.data(), payload.size()); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto value = store(MAIN_NET); + input.set_chain_id(value.data(), value.size()); + + value = store(uint256_t("0xa")); + trasactionMsg->set_nonce(value.data(), value.size()); + + value = store(uint256_t("")); + trasactionMsg->set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x5208")); + trasactionMsg->set_gas_limit(value.data(), value.size()); + + value = store(uint256_t("0x1")); + trasactionMsg->set_from_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x0")); + trasactionMsg->set_to_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x4c53ecdc18a60000")); + trasactionMsg->set_amount(value.data(), value.size()); + + Signer signer(uint256_t(load(input.chain_id()))); + auto unsignedTxBytes = signer.buildUnsignedTxBytes(input); + + auto expectEncoded = "e90a808252080180946a87346f3ba9958d08d09484a2b7fdbbe42b0df6884c53ecdc18a6000080018080"; + + ASSERT_EQ(hex(unsignedTxBytes), expectEncoded); +} + +TEST(HarmonySigner, BuildUnsignedStakingTxBytes) { + auto input = Proto::SigningInput(); + auto stakingMsg = input.mutable_staking_message(); + const auto privateKey = + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto value = store(MAIN_NET); + input.set_chain_id(value.data(), value.size()); + + value = store(uint256_t("0xa")); + stakingMsg->set_nonce(value.data(), value.size()); + + value = store(uint256_t("")); + stakingMsg->set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x5208")); + stakingMsg->set_gas_limit(value.data(), value.size()); + + // delegate message + auto delegateMsg = stakingMsg->mutable_delegate_message(); + delegateMsg->set_delegator_address(TEST_RECEIVER.string()); + delegateMsg->set_validator_address(TEST_RECEIVER.string()); + + value = store(uint256_t("0x4c53ecdc18a60000")); + delegateMsg->set_amount(value.data(), value.size()); + + Signer signer(uint256_t(load(input.chain_id()))); + auto unsignedTxBytes = signer.buildUnsignedTxBytes(input); + + auto expectEncoded = "f83d02f3946a87346f3ba9958d08d09484a2b7fdbbe42b0df6946a87346f3ba9958d08d09484a2b7fdbbe42b0df6884c53ecdc18a600000a80825208018080"; + + ASSERT_EQ(hex(unsignedTxBytes), expectEncoded); +} +} // namespace TW::Harmony diff --git a/tests/chains/Harmony/StakingTests.cpp b/tests/chains/Harmony/StakingTests.cpp new file mode 100644 index 00000000000..bbcce90bb7d --- /dev/null +++ b/tests/chains/Harmony/StakingTests.cpp @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base64.h" +#include "Coin.h" +#include "HDWallet.h" +#include "Harmony/Address.h" +#include "Harmony/Signer.h" +#include "HexCoding.h" +#include "proto/Harmony.pb.h" + +#include +#include + +namespace TW::Harmony { + +static Address TEST_ACCOUNT; +static bool testAccountDecodeResult = + Address::decode("one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9", TEST_ACCOUNT); + +static auto PRIVATE_KEY = + PrivateKey(parse_hex("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48")); + +TEST(HarmonyStaking, SignCreateValidator) { + auto input = Proto::SigningInput(); + input.set_private_key(PRIVATE_KEY.bytes.data(), PRIVATE_KEY.bytes.size()); + + auto value = store(uint256_t("0x2")); + input.set_chain_id(value.data(), value.size()); + + auto stakingMessage = input.mutable_staking_message(); + auto createValidatorMsg = stakingMessage->mutable_create_validator_message(); + + createValidatorMsg->set_validator_address(TEST_ACCOUNT.string()); + + auto description = createValidatorMsg->mutable_description(); + description->set_name("Alice"); + description->set_identity("alice"); + description->set_website("alice.harmony.one"); + description->set_security_contact("Bob"); + description->set_details("Don't mess with me!!!"); + auto commission = createValidatorMsg->mutable_commission_rates(); + + // (value, precision): (1, 1) represents 0.1 + value = store(uint256_t("1")); + commission->mutable_rate()->set_value(value.data(), value.size()); + value = store(uint256_t("1")); + commission->mutable_rate()->set_precision(value.data(), value.size()); + + // (value, precision): (9, 1) represents 0.9 + value = store(uint256_t("9")); + commission->mutable_max_rate()->set_value(value.data(), value.size()); + value = store(uint256_t("1")); + commission->mutable_max_rate()->set_precision(value.data(), value.size()); + + // (value, precision): (5, 2) represents 0.05 + value = store(uint256_t("5")); + commission->mutable_max_change_rate()->set_value(value.data(), value.size()); + value = store(uint256_t("2")); + commission->mutable_max_change_rate()->set_precision(value.data(), value.size()); + + value = store(uint256_t("10")); + createValidatorMsg->set_min_self_delegation(value.data(), value.size()); + + value = store(uint256_t("3000")); + createValidatorMsg->set_max_total_delegation(value.data(), value.size()); + + value = parse_hex("b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df" + "2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611"); + createValidatorMsg->add_slot_pub_keys(value.data(), value.size()); + + value = parse_hex("4252b0f1210efb0d5061e8a706a7ea9d62292a7947a975472f" + "b77e1af7278a1c3c2e6eeba73c0581ece398613829940df129" + "f3071c9a24b4b448bb1e880dc5872a58cb07eed94294c4e01a" + "5c864771cafef7b96be541cb3c521a98f01838dd94"); + createValidatorMsg->add_slot_key_sigs(value.data(), value.size()); + + value = store(uint256_t("100")); + createValidatorMsg->set_amount(value.data(), value.size()); + + value = store(uint256_t("0x02")); + stakingMessage->set_nonce(value.data(), value.size()); + + value = store(uint256_t("0x0")); + stakingMessage->set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x64")); + stakingMessage->set_gas_limit(value.data(), value.size()); + + auto proto_output = Signer::sign(input); + + auto expectEncoded = + "f9015280f9010894ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c696365916" + "16c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121dd" + "c988016345785d8a0000c9880c7d713b49da0000c887b1a2bc2ec500000a820bb8f1b0b9486167ab9087ab8" + "18dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611f862b860" + "4252b0f1210efb0d5061e8a706a7ea9d62292a7947a975472fb77e1af7278a1c3c2e6eeba73c0581ece3986" + "13829940df129f3071c9a24b4b448bb1e880dc5872a58cb07eed94294c4e01a5c864771cafef7b96be541cb" + "3c521a98f01838dd946402806428a00d8437f81be3481b01542e9baef0445f3758cf084c5e1fba93d087ccc" + "e084cb1a0404c1a42442c2d39f84582353a1c67012451ff83ef6d3622f684041df9bf0072"; + + auto v = "28"; + auto r = "0d8437f81be3481b01542e9baef0445f3758cf084c5e1fba93d087ccce084cb1"; + auto s = "404c1a42442c2d39f84582353a1c67012451ff83ef6d3622f684041df9bf0072"; + + ASSERT_EQ(hex(proto_output.encoded()), expectEncoded); + ASSERT_EQ(hex(proto_output.v()), v); + ASSERT_EQ(hex(proto_output.r()), r); + ASSERT_EQ(hex(proto_output.s()), s); +} + +TEST(HarmonyStaking, SignEditValidator) { + auto input = Proto::SigningInput(); + input.set_private_key(PRIVATE_KEY.bytes.data(), PRIVATE_KEY.bytes.size()); + + auto value = store(uint256_t("0x2")); + input.set_chain_id(value.data(), value.size()); + + auto stakingMessage = input.mutable_staking_message(); + auto editValidatorMsg = stakingMessage->mutable_edit_validator_message(); + + editValidatorMsg->set_validator_address(TEST_ACCOUNT.string()); + + auto description = editValidatorMsg->mutable_description(); + description->set_name("Alice"); + description->set_identity("alice"); + description->set_website("alice.harmony.one"); + description->set_security_contact("Bob"); + description->set_details("Don't mess with me!!!"); + + auto commissionRate = editValidatorMsg->mutable_commission_rate(); + + // (value, precision): (1, 1) represents 0.1 + value = store(uint256_t("1")); + commissionRate->set_value(value.data(), value.size()); + value = store(uint256_t("1")); + commissionRate->set_precision(value.data(), value.size()); + + value = store(uint256_t("10")); + editValidatorMsg->set_min_self_delegation(value.data(), value.size()); + + value = store(uint256_t("3000")); + editValidatorMsg->set_max_total_delegation(value.data(), value.size()); + + value = parse_hex("b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df" + "2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611"); + editValidatorMsg->set_slot_key_to_remove(value.data(), value.size()); + editValidatorMsg->set_slot_key_to_add(value.data(), value.size()); + + value = parse_hex("4252b0f1210efb0d5061e8a706a7ea9d62292a7947a975472f" + "b77e1af7278a1c3c2e6eeba73c0581ece398613829940df129" + "f3071c9a24b4b448bb1e880dc5872a58cb07eed94294c4e01a" + "5c864771cafef7b96be541cb3c521a98f01838dd94"); + editValidatorMsg->set_slot_key_to_add_sig(value.data(), value.size()); + + value = store(uint256_t("1")); + editValidatorMsg->set_active(value.data(), value.size()); + + value = store(uint256_t("0x02")); + stakingMessage->set_nonce(value.data(), value.size()); + + value = store(uint256_t("0x0")); + stakingMessage->set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x64")); // 0x5208 + stakingMessage->set_gas_limit(value.data(), value.size()); + + auto proto_output = Signer::sign(input); + + auto expectEncoded = + "f9016c01f9012294ebcd16e8c1d8f493ba04e99a56474122d81a9c58f83885416c69636585616c69636591616c6963652e6861726d6f6e792e6f6e6583426f6295446f6e2774206d6573732077697468206d65212121c988016345785d8a00000a820bb8b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611b0b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611b8604252b0f1210efb0d5061e8a706a7ea9d62292a7947a975472fb77e1af7278a1c3c2e6eeba73c0581ece398613829940df129f3071c9a24b4b448bb1e880dc5872a58cb07eed94294c4e01a5c864771cafef7b96be541cb3c521a98f01838dd940102806427a089d6f87855619c31e933d5f00638ca58737dfffdfdf8b66a048a2e45f103e05da04aafc5c51a95412760c089371b411a5ab8f235b456291a9754d544b162df4eef"; + + auto v = "27"; + auto r = "89d6f87855619c31e933d5f00638ca58737dfffdfdf8b66a048a2e45f103e05d"; + auto s = "4aafc5c51a95412760c089371b411a5ab8f235b456291a9754d544b162df4eef"; + + ASSERT_EQ(hex(proto_output.encoded()), expectEncoded); + ASSERT_EQ(hex(proto_output.v()), v); + ASSERT_EQ(hex(proto_output.r()), r); + ASSERT_EQ(hex(proto_output.s()), s); +} + +TEST(HarmonyStaking, SignDelegate) { + auto input = Proto::SigningInput(); + input.set_private_key(PRIVATE_KEY.bytes.data(), PRIVATE_KEY.bytes.size()); + + auto value = store(uint256_t("0x2")); + input.set_chain_id(value.data(), value.size()); + + auto stakingMessage = input.mutable_staking_message(); + auto delegateMsg = stakingMessage->mutable_delegate_message(); + delegateMsg->set_delegator_address(TEST_ACCOUNT.string()); + delegateMsg->set_validator_address(TEST_ACCOUNT.string()); + + value = store(uint256_t("0xa")); + delegateMsg->set_amount(value.data(), value.size()); + + value = store(uint256_t("0x02")); + stakingMessage->set_nonce(value.data(), value.size()); + + value = store(uint256_t("0x0")); + stakingMessage->set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x64")); + stakingMessage->set_gas_limit(value.data(), value.size()); + + auto proto_output = Signer::sign(input); + + auto expectEncoded = + "f87302eb94ebcd16e8c1d8f493ba04e99a56474122d81a9c5894ebcd16e8c1d8f493ba04e99a56474122d81a" + "9c580a02806428a0ada9a8fb49eb3cd74f0f861e16bc1f1d56a0c6e3c25b0391f9e07a7963317e80a05c28dbc4" + "1763dc2391263e1aae30f842f90734d7ec68cee2352af0d4b0662b54"; + + auto v = "28"; + auto r = "ada9a8fb49eb3cd74f0f861e16bc1f1d56a0c6e3c25b0391f9e07a7963317e80"; + auto s = "5c28dbc41763dc2391263e1aae30f842f90734d7ec68cee2352af0d4b0662b54"; + + ASSERT_EQ(hex(proto_output.encoded()), expectEncoded); + ASSERT_EQ(hex(proto_output.v()), v); + ASSERT_EQ(hex(proto_output.r()), r); + ASSERT_EQ(hex(proto_output.s()), s); +} + +TEST(HarmonyStaking, SignUndelegate) { + auto input = Proto::SigningInput(); + input.set_private_key(PRIVATE_KEY.bytes.data(), PRIVATE_KEY.bytes.size()); + + auto value = store(uint256_t("0x2")); + input.set_chain_id(value.data(), value.size()); + + auto stakingMessage = input.mutable_staking_message(); + auto undelegateMsg = stakingMessage->mutable_undelegate_message(); + undelegateMsg->set_delegator_address(TEST_ACCOUNT.string()); + undelegateMsg->set_validator_address(TEST_ACCOUNT.string()); + + value = store(uint256_t("0xa")); + undelegateMsg->set_amount(value.data(), value.size()); + + value = store(uint256_t("0x02")); + stakingMessage->set_nonce(value.data(), value.size()); + + value = store(uint256_t("0x0")); + stakingMessage->set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x64")); + stakingMessage->set_gas_limit(value.data(), value.size()); + + auto proto_output = Signer::sign(input); + + auto expectEncoded = + "f87303eb94ebcd16e8c1d8f493ba04e99a56474122d81a9c5894ebcd16e8c1d8f493ba04e99a56474122d81a9c" + "580a02806428a05bf8c653567defe2c3728732bc9d67dd099a977df91c740a883fd89e03abb6e2a05202c4b516" + "52d5144c6a30d14d1a7a316b5a4a6b49be985b4bc6980e49f7acb7"; + + auto v = "28"; + auto r = "5bf8c653567defe2c3728732bc9d67dd099a977df91c740a883fd89e03abb6e2"; + auto s = "5202c4b51652d5144c6a30d14d1a7a316b5a4a6b49be985b4bc6980e49f7acb7"; + + ASSERT_EQ(hex(proto_output.encoded()), expectEncoded); + ASSERT_EQ(hex(proto_output.v()), v); + ASSERT_EQ(hex(proto_output.r()), r); + ASSERT_EQ(hex(proto_output.s()), s); +} + +TEST(HarmonyStaking, SignCollectRewards) { + auto input = Proto::SigningInput(); + input.set_private_key(PRIVATE_KEY.bytes.data(), PRIVATE_KEY.bytes.size()); + + auto value = store(uint256_t("0x2")); + input.set_chain_id(value.data(), value.size()); + + auto stakingMessage = input.mutable_staking_message(); + auto collectRewardsMsg = stakingMessage->mutable_collect_rewards(); + collectRewardsMsg->set_delegator_address(TEST_ACCOUNT.string()); + + value = store(uint256_t("0x02")); + stakingMessage->set_nonce(value.data(), value.size()); + + value = store(uint256_t("0x0")); + stakingMessage->set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x64")); + stakingMessage->set_gas_limit(value.data(), value.size()); + + auto proto_output = Signer::sign(input); + + auto expectEncoded = "f85d04d594ebcd16e8c1d8f493ba04e99a56474122d81a9c5802806428a04c15c72f425" + "77001083a9c7ff9d9724077aec704a524e53dc7c9afe97ca4e625a055c13ea17c3efd1cd9" + "1f2988c7e7673950bac5a08c174f2d0af27a82039f1e3d"; + + auto v = "28"; + auto r = "4c15c72f42577001083a9c7ff9d9724077aec704a524e53dc7c9afe97ca4e625"; + auto s = "55c13ea17c3efd1cd91f2988c7e7673950bac5a08c174f2d0af27a82039f1e3d"; + + ASSERT_EQ(hex(proto_output.encoded()), expectEncoded); + ASSERT_EQ(hex(proto_output.v()), v); + ASSERT_EQ(hex(proto_output.r()), r); + ASSERT_EQ(hex(proto_output.s()), s); +} + +} // namespace TW::Harmony diff --git a/tests/chains/Harmony/TWAnyAddressTests.cpp b/tests/chains/Harmony/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..70cd48bfa18 --- /dev/null +++ b/tests/chains/Harmony/TWAnyAddressTests.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include +#include + +#include + +TEST(HarmonyAnyAddress, Harmony) { + auto string = STRING("one1c8dpswxg2p50znzecnq0peuxlxtcm9je7q7yje"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeHarmony)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "c1da1838c85068f14c59c4c0f0e786f9978d9659"); +} diff --git a/tests/chains/Harmony/TWAnySignerTests.cpp b/tests/chains/Harmony/TWAnySignerTests.cpp new file mode 100644 index 00000000000..24c722b2646 --- /dev/null +++ b/tests/chains/Harmony/TWAnySignerTests.cpp @@ -0,0 +1,75 @@ + +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/Harmony.pb.h" +#include "uint256.h" +#include "TestUtilities.h" +#include + +#include + +using namespace TW; + +namespace TW::Harmony::tests { + +static auto TEST_RECEIVER = "one129r9pj3sk0re76f7zs3qz92rggmdgjhtwge62k"; + +static uint256_t LOCAL_NET = 0x2; + +TEST(TWAnySignerHarmony, Sign) { + Proto::SigningInput input; + + auto transactionMessage = input.mutable_transaction_message(); + transactionMessage->set_to_address(TEST_RECEIVER); + const auto privateKey = parse_hex("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48"); + + input.set_private_key(privateKey.data(), privateKey.size()); + + auto value = store(LOCAL_NET); + input.set_chain_id(value.data(), value.size()); + + value = store(uint256_t("0x1")); + transactionMessage->set_nonce(value.data(), value.size()); + + value = store(uint256_t("")); + transactionMessage->set_gas_price(value.data(), value.size()); + + value = store(uint256_t("0x5208")); + transactionMessage->set_gas_limit(value.data(), value.size()); + + value = store(uint256_t("0x1")); + transactionMessage->set_from_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x0")); + transactionMessage->set_to_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x6bfc8da5ee8220000")); + transactionMessage->set_amount(value.data(), value.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeHarmony); + + auto shouldBeV = "28"; + auto shouldBeR = "84cc200aab11f5e1b2f7ece0d56ec67385ac50cefb6e3dc2a2f3e036ed575a5c"; + auto shouldBeS = "643f18005b790cac8d8e7dc90e6147df0b83874b52db198864694ea28a79e6fc"; + + ASSERT_EQ(hex(output.v()), shouldBeV); + ASSERT_EQ(hex(output.r()), shouldBeR); + ASSERT_EQ(hex(output.s()), shouldBeS); + + ASSERT_EQ(hex(output.encoded()), "f86a0180825208018094514650ca30b3c79f693e14220115434236d44aeb8906bfc8da5ee82200008028a084cc200aab11f5e1b2f7ece0d56ec67385ac50cefb6e3dc2a2f3e036ed575a5ca0643f18005b790cac8d8e7dc90e6147df0b83874b52db198864694ea28a79e6fc"); +} + +TEST(TWAnySignerHarmony, SignJSON) { + auto json = STRING(R"({"chainId":"Ag==","transactionMessage":{"nonce":"AQ==","gasPrice":"AA==","gasLimit":"Ugg=","toAddress":"one129r9pj3sk0re76f7zs3qz92rggmdgjhtwge62k","amount":"Br/I2l7oIgAA","fromShardId":"AQ==","toShardId":"AA=="}})"); + auto key = DATA("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48"); + auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeHarmony)); + + ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeHarmony)); + assertStringsEqual(result, "f86a0180825208018094514650ca30b3c79f693e14220115434236d44aeb8906bfc8da5ee82200008028a084cc200aab11f5e1b2f7ece0d56ec67385ac50cefb6e3dc2a2f3e036ed575a5ca0643f18005b790cac8d8e7dc90e6147df0b83874b52db198864694ea28a79e6fc"); +} + +} // namespace TW::Harmony::tests diff --git a/tests/chains/Harmony/TWCoinTypeTests.cpp b/tests/chains/Harmony/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..66cb9c51292 --- /dev/null +++ b/tests/chains/Harmony/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWHarmonyCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeHarmony)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeHarmony, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeHarmony, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeHarmony)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeHarmony)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeHarmony), 18); + ASSERT_EQ(TWBlockchainHarmony, TWCoinTypeBlockchain(TWCoinTypeHarmony)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeHarmony)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeHarmony)); + assertStringsEqual(symbol, "ONE"); + assertStringsEqual(txUrl, "https://explorer.harmony.one/#/tx/t123"); + assertStringsEqual(accUrl, "https://explorer.harmony.one/#/address/a12"); + assertStringsEqual(id, "harmony"); + assertStringsEqual(name, "Harmony"); +} diff --git a/tests/Harmony/TWHarmonyStakingTests.cpp b/tests/chains/Harmony/TWHarmonyStakingTests.cpp similarity index 97% rename from tests/Harmony/TWHarmonyStakingTests.cpp rename to tests/chains/Harmony/TWHarmonyStakingTests.cpp index 81f6ae50108..5c0283c81f0 100644 --- a/tests/Harmony/TWHarmonyStakingTests.cpp +++ b/tests/chains/Harmony/TWHarmonyStakingTests.cpp @@ -1,15 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Data.h" #include "Harmony/Staking.h" #include "HexCoding.h" #include "PrivateKey.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "proto/Harmony.pb.h" #include "uint256.h" #include @@ -17,7 +15,7 @@ #include using namespace TW; -using namespace Harmony; +namespace TW::Harmony::tests { static auto TEST_ACCOUNT = "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9"; @@ -91,7 +89,6 @@ TEST(TWHarmonyStakingSigner, CreateValidator) { value = store(uint256_t("0x64")); stakingMessage->set_gas_limit(value.data(), value.size()); - Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeHarmony); @@ -104,7 +101,6 @@ TEST(TWHarmonyStakingSigner, CreateValidator) { ASSERT_EQ(hex(output.s()), shouldBeS); } - TEST(TWHarmonyStakingSigner, EditValidator) { auto input = Proto::SigningInput(); input.set_private_key(PRIVATE_KEY.bytes.data(), PRIVATE_KEY.bytes.size()); @@ -149,7 +145,7 @@ TEST(TWHarmonyStakingSigner, EditValidator) { "5c864771cafef7b96be541cb3c521a98f01838dd94"); editValidatorMsg->set_slot_key_to_add_sig(value.data(), value.size()); - //test active + // test active value = store(uint256_t("1")); editValidatorMsg->set_active(value.data(), value.size()); @@ -219,7 +215,7 @@ TEST(TWHarmonyStakingSigner, EditValidator2) { "5c864771cafef7b96be541cb3c521a98f01838dd94"); editValidatorMsg->set_slot_key_to_add_sig(value.data(), value.size()); - //test null + // test null value = store(uint256_t("0")); editValidatorMsg->set_active(value.data(), value.size()); @@ -289,7 +285,7 @@ TEST(TWHarmonyStakingSigner, EditValidator3) { "5c864771cafef7b96be541cb3c521a98f01838dd94"); editValidatorMsg->set_slot_key_to_add_sig(value.data(), value.size()); - //test inactive + // test inactive value = store(uint256_t("2")); editValidatorMsg->set_active(value.data(), value.size()); @@ -314,7 +310,6 @@ TEST(TWHarmonyStakingSigner, EditValidator3) { ASSERT_EQ(hex(output.s()), shouldBeS); } - TEST(TWHarmonyStakingSigner, Delegate) { auto input = Proto::SigningInput(); input.set_private_key(PRIVATE_KEY.bytes.data(), PRIVATE_KEY.bytes.size()); @@ -351,8 +346,6 @@ TEST(TWHarmonyStakingSigner, Delegate) { ASSERT_EQ(hex(output.s()), shouldBeS); } - - TEST(TWHarmonyStakingSigner, Undelegate) { auto input = Proto::SigningInput(); input.set_private_key(PRIVATE_KEY.bytes.data(), PRIVATE_KEY.bytes.size()); @@ -420,3 +413,5 @@ TEST(TWHarmonyStakingSigner, CollectRewards) { ASSERT_EQ(hex(output.r()), shouldBeR); ASSERT_EQ(hex(output.s()), shouldBeS); } + +} // namespace TW::Harmony::tests diff --git a/tests/chains/Harmony/TransactionCompilerTests.cpp b/tests/chains/Harmony/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..5cc34ad9765 --- /dev/null +++ b/tests/chains/Harmony/TransactionCompilerTests.cpp @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "uint256.h" + +#include "proto/Harmony.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(HarmonyCompiler, CompileWithSignatures) { + // txHash 0x238c0db5f139422d64d12b3d5208243b4b355bfb87024cec7795660291a628d0 on https://explorer.ps.hmny.io/ + /// Step 1: Prepare transaction input (protobuf) + auto coin = TWCoinTypeHarmony; + auto input = TW::Harmony::Proto::SigningInput(); + auto trasactionMsg = input.mutable_transaction_message(); + auto receiver = "one1y563nrrtcpu7874cry68ehxwrpteyhp0sztlym"; + trasactionMsg->set_to_address(receiver); + auto payload = parse_hex(""); + trasactionMsg->set_payload(payload.data(), payload.size()); + + uint256_t MAIN_NET = 0x4; + auto value = store(MAIN_NET); + input.set_chain_id(value.data(), value.size()); + + value = store(uint256_t(0)); + trasactionMsg->set_nonce(value.data(), value.size()); + + value = store(uint256_t(1000000000000000)); + trasactionMsg->set_gas_price(value.data(), value.size()); + + value = store(uint256_t(1000000)); + trasactionMsg->set_gas_limit(value.data(), value.size()); + + value = store(uint256_t("0x0")); + trasactionMsg->set_from_shard_id(value.data(), value.size()); + + value = store(uint256_t("0x0")); + trasactionMsg->set_to_shard_id(value.data(), value.size()); + + value = store(uint256_t(10)); + trasactionMsg->set_amount(value.data(), value.size()); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = data(preSigningOutput.data()); + auto preImageHash = data(preSigningOutput.data_hash()); + + std::string expectedPreImage = "e98087038d7ea4c68000830f42408080942535198c6bc079e3fab819347cdcce1857925c2f0a80048080"; + std::string expectedPreImageHash = "fd1be8579542dc60f15a6218887cc1b42945bf04b50205d15ad7df8b5fac5714"; + ASSERT_EQ(hex(preImage), expectedPreImage); + ASSERT_EQ(hex(preImageHash), expectedPreImageHash); + + const auto privateKey = PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + Data signature = parse_hex("43824f50bf4b16ebe1020114de16e3579bdb5f3dcaa26117de87a73b5414b72550506609fd60e3cb565b1f9bae0952d37f3a6c6be262380f7f18cbda5216f34300"); + const PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, preImageHash)); + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKey.bytes}); + const auto ExpectedTx = + "f8698087038d7ea4c68000830f42408080942535198c6bc079e3fab819347cdcce1857925c2f0a802ba043824f50bf4b16ebe1020114de16e3579bdb5f3dcaa26117de87a73b5414b725a050506609fd60e3cb565b1f9bae0952d37f3a6c6be262380f7f18cbda5216f343"; + { + TW::Harmony::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::Harmony::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::Harmony::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKey.bytes}); + Harmony::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {}, {}); + Harmony::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/Hedera/AddressTests.cpp b/tests/chains/Hedera/AddressTests.cpp new file mode 100644 index 00000000000..4065b5164b2 --- /dev/null +++ b/tests/chains/Hedera/AddressTests.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Hedera/Address.h" +#include "PublicKey.h" +#include "PrivateKey.h" + +#include "TestUtilities.h" + +#include +#include + +namespace TW::Hedera::tests { + +TEST(HederaAddress, FromStandardArgument) { + { + // 0.0.1377988 + Address addr(0uL, 0uL, 1'377'988uL); + ASSERT_EQ(addr.shard(), 0uL); + ASSERT_EQ(addr.realm(), 0uL); + ASSERT_EQ(addr.num(), 1'377'988uL); + ASSERT_EQ(addr.string(), "0.0.1377988"); + ASSERT_TRUE(addr.isValid(addr.string())); + } + + { + // 0.0.302a300506032b65700321007df3e1ab790b28de4706d36a7aa99a0e043cb3e2c3d6ec6686e4af7f638b0860 + // https://github.com/hashgraph/hedera-sdk-rust/blob/c1c10d5750552e6bb857132cc824c430bd890a6b/sdk/rust/src/key/public_key/mod.rs#L306 + auto pubkey = PublicKey(parse_hex("7df3e1ab790b28de4706d36a7aa99a0e043cb3e2c3d6ec6686e4af7f638b0860"), TWPublicKeyTypeED25519); + Address addr(0uL, 0uL, 0uL, pubkey); + ASSERT_EQ(addr.shard(), 0uL); + ASSERT_EQ(addr.realm(), 0uL); + ASSERT_EQ(addr.num(), 0uL); + ASSERT_EQ(addr.alias().string(), "302a300506032b65700321007df3e1ab790b28de4706d36a7aa99a0e043cb3e2c3d6ec6686e4af7f638b0860"); + ASSERT_EQ(addr.string(), "0.0.302a300506032b65700321007df3e1ab790b28de4706d36a7aa99a0e043cb3e2c3d6ec6686e4af7f638b0860"); + ASSERT_TRUE(addr.isValid(addr.string())); + } +} + +TEST(HederaAddress, Valid) { + ASSERT_FALSE(Address::isValid("invalid")); + ASSERT_FALSE(Address::isValid("302a300506032b65700321007df3e1ab790b28de4706d36a7aa99a0e043cb3e2c3d6ec6686e4af7f638b0860")); + ASSERT_FALSE(Address::isValid("0.0.abc")); + ASSERT_TRUE(Address::isValid("0.0.1")); + ASSERT_TRUE(Address::isValid("0.0.1377988")); + ASSERT_TRUE(Address::isValid("0.0.302a300506032b65700321007df3e1ab790b28de4706d36a7aa99a0e043cb3e2c3d6ec6686e4af7f638b0860")); +} + +TEST(HederaAddress, FromString) { + auto address = Address("0.0.1377988"); + ASSERT_EQ(address.string(), "0.0.1377988"); +} + +} // namespace TW::Hedera::tests diff --git a/tests/chains/Hedera/SignerTests.cpp b/tests/chains/Hedera/SignerTests.cpp new file mode 100644 index 00000000000..b1b4220226a --- /dev/null +++ b/tests/chains/Hedera/SignerTests.cpp @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Hedera/Address.h" +#include "Hedera/Protobuf/basic_types.pb.h" +#include "Hedera/Protobuf/crypto_transfer.pb.h" +#include "Hedera/Protobuf/transaction_body.pb.h" +#include "Hedera/Signer.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" + +#include + +namespace TW::Hedera::tests { + +TEST(HederaSigner, Sign) { + // Successfully broadcasted: https://hashscan.io/testnet/transaction/0.0.48694347-1667222879-749068449?t=1667222891.440398729&p=1 + Proto::SigningInput input; + auto privateKey = PrivateKey(parse_hex("e87a5584c0173263e138db689fdb2a7389025aaae7cb1a18a1017d76012130e8")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto* body = input.mutable_body(); + + *body->mutable_memo() = ""; + *body->mutable_nodeaccountid() = "0.0.9"; + body->set_transactionfee(100000000); + body->set_transactionvalidduration(120); + auto* transferMsg = body->mutable_transfer(); + transferMsg->set_from("0.0.48694347"); + transferMsg->set_to("0.0.48462050"); + transferMsg->set_amount(100000000); + + auto* transactionID = body->mutable_transactionid(); + transactionID->mutable_transactionvalidstart()->set_seconds(1667222879); + transactionID->mutable_transactionvalidstart()->set_nanos(749068449); + transactionID->set_accountid("0.0.48694347"); + + auto result = Signer::sign(input); + ASSERT_EQ(hex(result.encoded()), "0a440a150a0c08df9aff9a0610a1c197e502120518cb889c17120218091880c2d72f22020878721e0a1c0a0c0a0518e2f18d17108084af5f0a0c0a0518cb889c1710ff83af5f12660a640a205d3a70d08b2beafb72c7a68986b3ff819a306078b8c359d739e4966e82e6d40e1a40612589c3b15f1e3ed6084b5a3a5b1b81751578cac8d6c922f31731b3982a5bac80a22558b2197276f5bae49b62503a4d39448ceddbc5ef3ba9bee4c0f302f70c"); +} + +TEST(HederaSigner, SignWithMemo) { + // Successfully broadcasted: https://hashscan.io/testnet/transaction/0.0.48694347-1667227300-854561449?t=1667227312.554926003 + Proto::SigningInput input; + auto privateKey = PrivateKey(parse_hex("e87a5584c0173263e138db689fdb2a7389025aaae7cb1a18a1017d76012130e8")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto* body = input.mutable_body(); + + *body->mutable_memo() = "wallet core"; + *body->mutable_nodeaccountid() = "0.0.7"; + body->set_transactionfee(100000000); + body->set_transactionvalidduration(120); + auto* transferMsg = body->mutable_transfer(); + transferMsg->set_from("0.0.48694347"); + transferMsg->set_to("0.0.48462050"); + transferMsg->set_amount(100000000); + + auto* transactionID = body->mutable_transactionid(); + transactionID->mutable_transactionvalidstart()->set_seconds(1667227300); + transactionID->mutable_transactionvalidstart()->set_nanos(854561449); + transactionID->set_accountid("0.0.48694347"); + + auto result = Signer::sign(input); + ASSERT_EQ(hex(result.encoded()), "0a510a150a0c08a4bdff9a0610a9a5be9703120518cb889c17120218071880c2d72f22020878320b77616c6c657420636f7265721e0a1c0a0c0a0518e2f18d17108084af5f0a0c0a0518cb889c1710ff83af5f12660a640a205d3a70d08b2beafb72c7a68986b3ff819a306078b8c359d739e4966e82e6d40e1a40ee1764c9acf79b68a675c1a78c8c43cb7d136f5f230b48b44992ad3e7ba87a8256758b823120a76142e58b94f082a0551000cf68cd3336fc4393c6b2191d8603"); +} + +TEST(HederaSigner, SignWithMemoMainnet) { + // Successfully broadcasted: https://hashscan.io/mainnet/transaction/0.0.1377988-1667566445-926176449?t=1667566457.533804616 + Proto::SigningInput input; + auto privateKey = PrivateKey(parse_hex("650c5120cbdc6244e3d10001eb27eea4dd3f80c331b3b6969fa434797d4edd50")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto* body = input.mutable_body(); + + *body->mutable_memo() = "wallet core"; + *body->mutable_nodeaccountid() = "0.0.12"; + body->set_transactionfee(100000000); + body->set_transactionvalidduration(120); + auto* transferMsg = body->mutable_transfer(); + transferMsg->set_from("0.0.1377988"); + transferMsg->set_to("0.0.19783"); + transferMsg->set_amount(100000000); + + auto* transactionID = body->mutable_transactionid(); + transactionID->mutable_transactionvalidstart()->set_seconds(1667566445); + transactionID->mutable_transactionvalidstart()->set_nanos(926176449); + transactionID->set_accountid("0.0.1377988"); + + auto result = Signer::sign(input); + ASSERT_EQ(hex(result.encoded()), "0a4e0a140a0c08ed96949b0610c1a9d1b903120418c48d541202180c1880c2d72f22020878320b77616c6c657420636f7265721c0a1a0a0b0a0418c79a01108084af5f0a0b0a0418c48d5410ff83af5f12660a640a207df3e1ab790b28de4706d36a7aa99a0e043cb3e2c3d6ec6686e4af7f638b08601a4020a527f81c10a256b089fb2fbe2a1fc5917e0d221c0d06b8bb9095a6b26390634610f2034b99025ad70db4d84e08751841c2a70651220e32d1213a4b05ec9906"); +} + +TEST(HederaSigner, ProtoTestsTransferList) { + auto transferList = proto::TransferList(); + auto* to = transferList.add_accountamounts(); + to->set_amount(100000000); + auto accountIdTo = to->mutable_accountid(); + accountIdTo->set_shardnum(0); + accountIdTo->set_realmnum(0); + accountIdTo->set_accountnum(48462050); + + auto encoded = hex(transferList.SerializeAsString()); + ASSERT_EQ(encoded, "0a0c0a0518e2f18d17108084af5f"); +} + +TEST(HederaSigner, ProtoTestsDoubleTransferList) { + auto transferList = proto::TransferList(); + + { + auto* to = transferList.add_accountamounts(); + to->set_amount(100000000); + auto* accountIdTo = to->mutable_accountid(); + accountIdTo->set_shardnum(0); + accountIdTo->set_realmnum(0); + accountIdTo->set_accountnum(48462050); + } + + { + auto* from = transferList.add_accountamounts(); + from->set_amount(-100000000); + auto* accountIdFrom = from->mutable_accountid(); + accountIdFrom->set_shardnum(0); + accountIdFrom->set_realmnum(0); + accountIdFrom->set_accountnum(48694347); + } + + auto encoded = hex(transferList.SerializeAsString()); + ASSERT_EQ(encoded, "0a0c0a0518e2f18d17108084af5f0a0c0a0518cb889c1710ff83af5f"); +} + +TEST(HederaSigner, ProtoTestsCryptoTransfer) { + auto transferList = proto::TransferList(); + + { + auto* to = transferList.add_accountamounts(); + to->set_amount(100000000); + auto* accountIdTo = to->mutable_accountid(); + accountIdTo->set_shardnum(0); + accountIdTo->set_realmnum(0); + accountIdTo->set_accountnum(48462050); + } + + { + auto* from = transferList.add_accountamounts(); + from->set_amount(-100000000); + auto* accountIdFrom = from->mutable_accountid(); + accountIdFrom->set_shardnum(0); + accountIdFrom->set_realmnum(0); + accountIdFrom->set_accountnum(48694347); + } + + auto cryptoTransfer = proto::CryptoTransferTransactionBody(); + *cryptoTransfer.mutable_transfers() = transferList; + + auto encoded = hex(cryptoTransfer.SerializeAsString()); + ASSERT_EQ(encoded, "0a1c0a0c0a0518e2f18d17108084af5f0a0c0a0518cb889c1710ff83af5f"); +} + +TEST(HederaSigner, ProtoTestsTransactionBody) { + auto transferList = proto::TransferList(); + + { + auto* to = transferList.add_accountamounts(); + to->set_amount(100000000); + auto* accountIdTo = to->mutable_accountid(); + accountIdTo->set_shardnum(0); + accountIdTo->set_realmnum(0); + accountIdTo->set_accountnum(48462050); + } + + { + auto* from = transferList.add_accountamounts(); + from->set_amount(-100000000); + auto* accountIdFrom = from->mutable_accountid(); + accountIdFrom->set_shardnum(0); + accountIdFrom->set_realmnum(0); + accountIdFrom->set_accountnum(48694347); + } + + auto cryptoTransfer = proto::CryptoTransferTransactionBody(); + *cryptoTransfer.mutable_transfers() = transferList; + + auto transactionBody = proto::TransactionBody(); + *transactionBody.mutable_cryptotransfer() = cryptoTransfer; + transactionBody.set_transactionfee(100000000); + transactionBody.mutable_nodeaccountid()->set_accountnum(9); + transactionBody.mutable_transactionvalidduration()->set_seconds(120); + auto& transactionID = *transactionBody.mutable_transactionid(); + transactionID.mutable_accountid()->set_accountnum(48694347); + transactionID.mutable_transactionvalidstart()->set_nanos(749068449); + transactionID.mutable_transactionvalidstart()->set_seconds(1667222879); + + auto encoded = hex(transactionBody.SerializeAsString()); + + ASSERT_EQ(encoded, "0a150a0c08df9aff9a0610a1c197e502120518cb889c17120218091880c2d72f22020878721e0a1c0a0c0a0518e2f18d17108084af5f0a0c0a0518cb889c1710ff83af5f"); +} + +TEST(HederaSigner, ProtoTestsTransactionBodyWithMemo) { + auto transferList = proto::TransferList(); + { + auto* to = transferList.add_accountamounts(); + to->set_amount(100000000); + auto* accountIdTo = to->mutable_accountid(); + accountIdTo->set_shardnum(0); + accountIdTo->set_realmnum(0); + accountIdTo->set_accountnum(48462050); + } + + { + auto* from = transferList.add_accountamounts(); + from->set_amount(-100000000); + auto* accountIdFrom = from->mutable_accountid(); + accountIdFrom->set_shardnum(0); + accountIdFrom->set_realmnum(0); + accountIdFrom->set_accountnum(48694347); + } + + auto cryptoTransfer = proto::CryptoTransferTransactionBody(); + *cryptoTransfer.mutable_transfers() = transferList; + + auto transactionBody = proto::TransactionBody(); + transactionBody.set_memo("wallet core"); + *transactionBody.mutable_cryptotransfer() = cryptoTransfer; + transactionBody.set_transactionfee(100000000); + transactionBody.mutable_nodeaccountid()->set_accountnum(3); + transactionBody.mutable_transactionvalidduration()->set_seconds(120); + auto& transactionID = *transactionBody.mutable_transactionid(); + transactionID.mutable_accountid()->set_accountnum(48694347); + transactionID.mutable_transactionvalidstart()->set_nanos(942876449); + transactionID.mutable_transactionvalidstart()->set_seconds(1667215034); + + auto encoded = hex(transactionBody.SerializeAsString()); + ASSERT_EQ(encoded, "0a150a0c08baddfe9a0610a1ceccc103120518cb889c17120218031880c2d72f22020878320b77616c6c657420636f7265721e0a1c0a0c0a0518e2f18d17108084af5f0a0c0a0518cb889c1710ff83af5f"); +} + +} // namespace TW::Hedera::tests diff --git a/tests/chains/Hedera/TWAnySignerTests.cpp b/tests/chains/Hedera/TWAnySignerTests.cpp new file mode 100644 index 00000000000..0e2d900604f --- /dev/null +++ b/tests/chains/Hedera/TWAnySignerTests.cpp @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "Hedera/Signer.h" +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +namespace TW::Hedera::tests { + +TEST(TWAnySignerHedera, Sign) { + // Successfully broadcasted: https://hashscan.io/testnet/transaction/0.0.48694347-1667222879-749068449?t=1667222891.440398729&p=1 + Proto::SigningInput input; + auto privateKey = PrivateKey(parse_hex("e87a5584c0173263e138db689fdb2a7389025aaae7cb1a18a1017d76012130e8")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto* body = input.mutable_body(); + + *body->mutable_memo() = ""; + *body->mutable_nodeaccountid() = "0.0.9"; + body->set_transactionfee(100000000); + body->set_transactionvalidduration(120); + auto* transferMsg = body->mutable_transfer(); + transferMsg->set_from("0.0.48694347"); + transferMsg->set_to("0.0.48462050"); + transferMsg->set_amount(100000000); + + auto* transactionID = body->mutable_transactionid(); + transactionID->mutable_transactionvalidstart()->set_seconds(1667222879); + transactionID->mutable_transactionvalidstart()->set_nanos(749068449); + transactionID->set_accountid("0.0.48694347"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeHedera); + ASSERT_EQ(hex(output.encoded()), "0a440a150a0c08df9aff9a0610a1c197e502120518cb889c17120218091880c2d72f22020878721e0a1c0a0c0a0518e2f18d17108084af5f0a0c0a0518cb889c1710ff83af5f12660a640a205d3a70d08b2beafb72c7a68986b3ff819a306078b8c359d739e4966e82e6d40e1a40612589c3b15f1e3ed6084b5a3a5b1b81751578cac8d6c922f31731b3982a5bac80a22558b2197276f5bae49b62503a4d39448ceddbc5ef3ba9bee4c0f302f70c"); +} + +} diff --git a/tests/chains/Hedera/TWCoinTypeTests.cpp b/tests/chains/Hedera/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..f3ee374a49d --- /dev/null +++ b/tests/chains/Hedera/TWCoinTypeTests.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWHederaCoinType, TWCoinType) { + const auto coin = TWCoinTypeHedera; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0.0.19790-1666769504-858851231")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0.0.19790")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "hedera"); + assertStringsEqual(name, "Hedera"); + assertStringsEqual(symbol, "HBAR"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 8); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainHedera); + assertStringsEqual(txUrl, "https://hashscan.io/mainnet/transaction/0.0.19790-1666769504-858851231"); + assertStringsEqual(accUrl, "https://hashscan.io/mainnet/account/0.0.19790"); +} diff --git a/tests/chains/ICON/AddressTests.cpp b/tests/chains/ICON/AddressTests.cpp new file mode 100644 index 00000000000..2d8229fac8f --- /dev/null +++ b/tests/chains/ICON/AddressTests.cpp @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Icon/Address.h" +#include "PrivateKey.h" + +#include + +using namespace TW; + +namespace TW::Icon::tests { + +TEST(IconAddress, Validation) { + ASSERT_TRUE(Address::isValid("cx116f042497e5f34268b1b91e742680f84cf4e9f3")); + ASSERT_TRUE(Address::isValid("hx116f042497e5f34268b1b91e742680f84cf4e9f3")); + + ASSERT_FALSE(Address::isValid("abc")); + ASSERT_FALSE(Address::isValid("dshadghasdghsadadsadjsad")); + ASSERT_FALSE(Address::isValid("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")); +} + +TEST(IconAddress, String) { + const auto address = Address("hx116f042497e5f34268b1b91e742680f84cf4e9f3"); + ASSERT_EQ(address.string(), "hx116f042497e5f34268b1b91e742680f84cf4e9f3"); + + const auto address2 = Address("cx116f042497e5f34268b1b91e742680f84cf4e9f3"); + ASSERT_EQ(address2.string(), "cx116f042497e5f34268b1b91e742680f84cf4e9f3"); + + EXPECT_ANY_THROW(new Address("")); +} + +TEST(IconAddress, FromPrivateKey) { + const auto privateKey = PrivateKey(parse_hex("94d1a980d5e528067d44bf8a60d646f556e40ca71e17cd4ead2d56f89e4bd20f")); + const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); + const auto address = Address(publicKey, TypeAddress); + + ASSERT_EQ(address.string(), "hx98c0832ca5bd8e8bf355ca9491888aa9725c2c48"); +} + +} // namespace TW::Icon::tests diff --git a/tests/chains/ICON/TWAnySignerTests.cpp b/tests/chains/ICON/TWAnySignerTests.cpp new file mode 100644 index 00000000000..c010fe5e159 --- /dev/null +++ b/tests/chains/ICON/TWAnySignerTests.cpp @@ -0,0 +1,49 @@ + +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Data.h" +#include "HexCoding.h" +#include "proto/Icon.pb.h" +#include "uint256.h" +#include "TestUtilities.h" +#include + +#include + +using namespace TW; + +namespace TW::Icon::tests { + +TEST(TWAnySignerIcon, Sign) { + auto key = parse_hex("2d42994b2f7735bbc93a3e64381864d06747e574aa94655c516f9ad0a74eed79"); + auto input = Proto::SigningInput(); + + input.set_from_address("hxbe258ceb872e08851f1f59694dac2558708ece11"); + input.set_to_address("hx5bfdb090f43a808005ffc27c25b213145e80b7cd"); + + auto value = uint256_t(1000000000000000000); + auto valueData = store(value); + input.set_value(valueData.data(), valueData.size()); + + auto stepLimit = uint256_t("74565"); + auto stepLimitData = store(stepLimit); + input.set_step_limit(stepLimitData.data(), stepLimitData.size()); + + auto one = uint256_t("01"); + auto oneData = store(one); + input.set_network_id(oneData.data(), oneData.size()); + input.set_nonce(oneData.data(), oneData.size()); + + input.set_timestamp(1516942975500598); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeICON); + + auto expected = std::string("{\"from\":\"hxbe258ceb872e08851f1f59694dac2558708ece11\",\"nid\":\"0x1\",\"nonce\":\"0x1\",\"signature\":\"xR6wKs+IA+7E91bT8966jFKlK5mayutXCvayuSMCrx9KB7670CsWa0B7LQzgsxU0GLXaovlAT2MLs1XuDiSaZQE=\",\"stepLimit\":\"0x12345\",\"timestamp\":\"0x563a6cf330136\",\"to\":\"hx5bfdb090f43a808005ffc27c25b213145e80b7cd\",\"value\":\"0xde0b6b3a7640000\",\"version\":\"0x3\"}"); + ASSERT_EQ(output.encoded(), expected); +} + +} // namespace TW::Icon::tests diff --git a/tests/chains/ICON/TWCoinTypeTests.cpp b/tests/chains/ICON/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..cd0f6fff5a0 --- /dev/null +++ b/tests/chains/ICON/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWICONCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeICON)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeICON, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeICON, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeICON)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeICON)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeICON), 18); + ASSERT_EQ(TWBlockchainIcon, TWCoinTypeBlockchain(TWCoinTypeICON)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeICON)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeICON)); + assertStringsEqual(symbol, "ICX"); + assertStringsEqual(txUrl, "https://tracker.icon.foundation/transaction/t123"); + assertStringsEqual(accUrl, "https://tracker.icon.foundation/address/a12"); + assertStringsEqual(id, "icon"); + assertStringsEqual(name, "ICON"); +} diff --git a/tests/chains/ICON/TransactionCompilerTests.cpp b/tests/chains/ICON/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..a47dc54d2be --- /dev/null +++ b/tests/chains/ICON/TransactionCompilerTests.cpp @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/Icon.pb.h" +#include "proto/TransactionCompiler.pb.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include +#include + +using namespace TW; + +TEST(IconCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeICON; + TW::Icon::Proto::SigningInput input; + + input.set_from_address("hxbe258ceb872e08851f1f59694dac2558708ece11"); + input.set_to_address("hx5bfdb090f43a808005ffc27c25b213145e80b7cd"); + + auto value = uint256_t(1000000000000000000); + auto valueData = store(value); + input.set_value(valueData.data(), valueData.size()); + + auto stepLimit = uint256_t("74565"); + auto stepLimitData = store(stepLimit); + input.set_step_limit(stepLimitData.data(), stepLimitData.size()); + + auto one = uint256_t("01"); + auto oneData = store(one); + input.set_network_id(oneData.data(), oneData.size()); + input.set_nonce(oneData.data(), oneData.size()); + + input.set_timestamp(1516942975500598); + + auto protoInputString = input.SerializeAsString(); + auto protoInputData = TW::Data(protoInputString.begin(), protoInputString.end()); + + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, protoInputData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + auto preImage = preSigningOutput.data(); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImage),"6963785f73656e645472616e73616374696f6e2e66726f6d2e6878626532353863656238373265303838353166316635393639346461633235353837303865636531312e6e69642e3078312e6e6f6e63652e3078312e737465704c696d69742e307831323334352e74696d657374616d702e3078353633613663663333303133362e746f2e6878356266646230393066343361383038303035666663323763323562323133313435653830623763642e76616c75652e30786465306236623361373634303030302e76657273696f6e2e307833"); + EXPECT_EQ(hex(preImageHash),"f0c68a4f588233d722fff7b5a738ffa6b56ad4cb62ad6bc9fb3e5facb0c25059"); + + auto key = parse_hex("2d42994b2f7735bbc93a3e64381864d06747e574aa94655c516f9ad0a74eed79"); + const auto privateKey = PrivateKey(key); + const auto publicKey = privateKey.getPublicKey(TWCoinTypePublicKeyType(coin)); + const auto signature = privateKey.sign(parse_hex(hex(preImageHash)), TWCurveSECP256k1); + + const Data outputData = TransactionCompiler::compileWithSignatures(coin, protoInputData, {signature}, {publicKey.bytes}); + Icon::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(hex(output.encoded()), "7b2266726f6d223a22687862653235386365623837326530383835316631663539363934646163323535383730386563653131222c226e6964223a22307831222c226e6f6e6365223a22307831222c227369676e6174757265223a22785236774b732b49412b374539316254383936366a464b6c4b356d61797574584376617975534d437278394b4237363730437357613042374c517a6773785530474c58616f766c4154324d4c73315875446953615a51453d222c22737465704c696d6974223a2230783132333435222c2274696d657374616d70223a22307835363361366366333330313336222c22746f223a22687835626664623039306634336138303830303566666332376332356232313331343565383062376364222c2276616c7565223a223078646530623662336137363430303030222c2276657273696f6e223a22307833227d"); +} \ No newline at end of file diff --git a/tests/chains/IOST/AddressTests.cpp b/tests/chains/IOST/AddressTests.cpp new file mode 100644 index 00000000000..42c4e41de0d --- /dev/null +++ b/tests/chains/IOST/AddressTests.cpp @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HDWallet.h" +#include "HexCoding.h" +#include "IOST/Account.h" +#include "proto/IOST.pb.h" + +#include + +using namespace TW; +using namespace TW::IOST; + +TEST(IOSTAddress, ValidateAccount) { + // https://www.iostabc.com/tx/DnR4QuRDJAjUZ2qfJK9MT92p95BBub2FnyigeXn2Z1es + ASSERT_TRUE(Account::isValid("12345xusong")); + ASSERT_TRUE(Account::isValid("12345")); + ASSERT_TRUE(Account::isValid("1234_xuson")); + ASSERT_TRUE(Account::isValid("EKRQPgX7nKt8hJABwm9m3BKWGj7kcSECkJnCBauHQWin")); + + ASSERT_FALSE(TW::validateAddress(TWCoinTypeIOST, "1234")); + ASSERT_FALSE(TW::validateAddress(TWCoinTypeIOST, "1234_xusonG")); + ASSERT_FALSE(TW::validateAddress(TWCoinTypeIOST, "12345xusong6")); + ASSERT_FALSE(TW::validateAddress(TWCoinTypeIOST, "bc1quvuarfksewfeuevuc6tn0kfyptgjvwsvrprk9d")); + ASSERT_FALSE(TW::validateAddress(TWCoinTypeIOST, "DJRFZNg8jkUtjcpo2zJd92FUAzwRjitw6f")); + + ASSERT_EQ(TW::normalizeAddress(TWCoinTypeIOST, "EKRQPgX7nKt8hJABwm9m3BKWGj7kcSECkJnCBauHQWin"), "EKRQPgX7nKt8hJABwm9m3BKWGj7kcSECkJnCBauHQWin"); + ASSERT_EQ(TW::normalizeAddress(TWCoinTypeIOST, "Gcv8c2tH8qZrUYnKdEEdTtASsxivic2834MQW6mgxqto"), "Gcv8c2tH8qZrUYnKdEEdTtASsxivic2834MQW6mgxqto"); +} + +TEST(IOSTAddress, Account) { + std::string key = ("63095105a37b4e896e5ebbd740e751c6f9df7cca2410beba3261dc5680299cebe" + "812b52ea9ad5cba9a9af03afcc6f2942a4524b0df3c0344dc195072831670c4"); + Data secKeyBytes = parse_hex(key); + std::string secKey(secKeyBytes.begin(), secKeyBytes.end()); + + Proto::AccountInfo ai; + ai.set_active_key(secKey); + ai.set_owner_key(secKey); + + Account account(ai); + ASSERT_EQ(hex(account.activeKey), "63095105a37b4e896e5ebbd740e751c6f9df7cca2410beba3261dc5680299ceb"); + EXPECT_EQ(hex(account.ownerKey), "63095105a37b4e896e5ebbd740e751c6f9df7cca2410beba3261dc5680299ceb"); + + auto pubKey = account.publicOwnerKey(); + auto address = Account::address(std::string(pubKey.begin(), pubKey.end())); + EXPECT_EQ(address, "Gcv8c2tH8qZrUYnKdEEdTtASsxivic2834MQW6mgxqto"); + + EXPECT_EQ(hex(TW::addressToData(TWCoinTypeIOST, address)), "e812b52ea9ad5cba9a9af03afcc6f2942a4524b0df3c0344dc195072831670c4"); +} + +TEST(IOSTAddress, FromPrivateKey) { + auto wallet = HDWallet( + "shoot island position soft burden budget tooth cruel issue economy destroy above", ""); + auto address = wallet.deriveAddress(TWCoinTypeIOST); + ASSERT_EQ(address, "4av8w81EyzUgHonsVWqfs15WM4Vrpgox4BYYQWhNQDVu"); +} + +TEST(IOSTAddress, EqualOperator) { + auto acnt1 = Account("account1"); + auto acnt2 = Account("account2"); + ASSERT_TRUE(acnt1 == acnt1); + ASSERT_FALSE(acnt1 == acnt2); +} \ No newline at end of file diff --git a/tests/chains/IOST/SignerTests.cpp b/tests/chains/IOST/SignerTests.cpp new file mode 100644 index 00000000000..cde607ed48a --- /dev/null +++ b/tests/chains/IOST/SignerTests.cpp @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "IOST/Signer.h" +#include "proto/IOST.pb.h" +#include + +#include + +using namespace TW; +using namespace TW::IOST; + +TEST(IOSTSigner, Sign) { + auto tx = Proto::Transaction(); + tx.set_time(1550137587000000000); + tx.set_expiration(tx.time() + int64_t(1000000000) * 300); + tx.set_gas_ratio(1); + tx.set_gas_limit(1000000); + tx.set_chain_id(1024); + + tx.add_amount_limit(); + auto amountLimit = tx.mutable_amount_limit(0); + amountLimit->set_token("*"); + amountLimit->set_value("unlimited"); + + Data secKeyBytes = parse_hex("63095105a37b4e896e5ebbd740e751c6f9df7cca2410beba3261dc5680299cebe" + "812b52ea9ad5cba9a9af03afcc6f2942a4524b0df3c0344dc195072831670c4"); + std::string secKey(secKeyBytes.begin(), secKeyBytes.end()); + Proto::AccountInfo account; + account.set_active_key(secKey); + account.set_name("myname"); + + Proto::SigningInput input; + input.mutable_account()->CopyFrom(account); + input.mutable_transaction_template()->CopyFrom(tx); + input.set_transfer_destination("admin"); + input.set_transfer_amount("10"); + input.set_transfer_memo(""); + + Signer signer(input); + std::string signature = signer.sign(input).transaction().publisher_sigs(0).signature(); + + ASSERT_EQ(hex(signature), "e8ce15214bad39683021c15dd318e963da8541fd8f3d8484df5042b4ea7fdafb7f46" + "505b85841367d6e1736c7d3b433ca72089b88a23f43661dfb0429a10cb03"); +} + +TEST(IOSTSigner, EncodeTransaction) { + auto tx = Proto::Transaction(); + tx.set_time(1550137587000000000); + tx.set_expiration(tx.time() + int64_t(1000000000) * 300); + tx.set_gas_ratio(1); + tx.set_gas_limit(1000000); + tx.set_chain_id(1024); + + tx.add_amount_limit(); + auto amountLimit = tx.mutable_amount_limit(0); + amountLimit->set_token("*"); + amountLimit->set_value("unlimited"); + + Data secKeyBytes = parse_hex("63095105a37b4e896e5ebbd740e751c6f9df7cca2410beba3261dc5680299cebe" + "812b52ea9ad5cba9a9af03afcc6f2942a4524b0df3c0344dc195072831670c4"); + std::string secKey(secKeyBytes.begin(), secKeyBytes.end()); + Proto::AccountInfo account; + account.set_active_key(secKey); + account.set_name("myname"); + + tx.add_signers(); + *tx.mutable_signers(0) = secKey; + + const auto signature = + parse_hex("1e5e2de66512658e9317fa56766678166abcf492d020863935723db2030f736710e13437cef0981f" + "cc376eae45349759508767d407b6c9963712910ada2c3606"); + + tx.add_signatures(); + auto* sig = tx.mutable_signatures(0); + sig->set_algorithm(Proto::Algorithm::ED25519); + sig->set_public_key(secKey); + sig->set_signature(std::string(signature.begin(), signature.end())); + + auto encoded = Signer::encodeTransaction(tx); + ASSERT_EQ(hex(encoded), "158331ec221dbe0015833231fb82760000000000000000640000000005f5e10000000000000000000000040000000000000000010000004063095105a37b4e896e5ebbd740e751c6f9df7cca2410beba3261dc5680299cebe812b52ea9ad5cba9a9af03afcc6f2942a4524b0df3c0344dc195072831670c4000000000000000100000012000000012a00000009756e6c696d69746564000000010000008902000000401e5e2de66512658e9317fa56766678166abcf492d020863935723db2030f736710e13437cef0981fcc376eae45349759508767d407b6c9963712910ada2c36060000004063095105a37b4e896e5ebbd740e751c6f9df7cca2410beba3261dc5680299cebe812b52ea9ad5cba9a9af03afcc6f2942a4524b0df3c0344dc195072831670c4"); +} diff --git a/tests/chains/IOST/TWCoinTypeTests.cpp b/tests/chains/IOST/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..3b4f8817d7d --- /dev/null +++ b/tests/chains/IOST/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWIOSTCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeIOST)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("7dKQzgTkPBNrZqrQ2Bqhqq132CHGPKANFDtzRsjHRCqx")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeIOST, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("4av8w81EyzUgHonsVWqfs15WM4Vrpgox4BYYQWhNQDVu")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeIOST, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeIOST)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeIOST)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeIOST), 2); + ASSERT_EQ(TWBlockchainIOST, TWCoinTypeBlockchain(TWCoinTypeIOST)); + ASSERT_EQ(0x5, TWCoinTypeP2shPrefix(TWCoinTypeIOST)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeIOST)); + assertStringsEqual(symbol, "IOST"); + assertStringsEqual(txUrl, "https://explorer.iost.io/tx/7dKQzgTkPBNrZqrQ2Bqhqq132CHGPKANFDtzRsjHRCqx"); + assertStringsEqual(accUrl, "https://explorer.iost.io/account/4av8w81EyzUgHonsVWqfs15WM4Vrpgox4BYYQWhNQDVu"); + assertStringsEqual(id, "iost"); + assertStringsEqual(name, "IOST"); +} diff --git a/tests/chains/IOST/TransactionCompilerTests.cpp b/tests/chains/IOST/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..9db3d3e0d84 --- /dev/null +++ b/tests/chains/IOST/TransactionCompilerTests.cpp @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base58.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/IOST.pb.h" +#include "proto/Theta.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(IostCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeIOST; + /// Step 1: Prepare transaction input (protobuf) + const auto privKeyBytes = Base58::decode( + "4TQwN7wWXg26ByuU5WkUPErd5v6PD6HsDuULyGNJgpS979wXF7jRU8NKviJs5boHrRKbLMomKycbek4NyDy6cLb8"); + const auto pkFrom = PrivateKey(Data(privKeyBytes.begin(), privKeyBytes.begin() + 32)); + const auto publicKey = pkFrom.getPublicKey(TWPublicKeyTypeED25519); + TW::IOST::Proto::SigningInput input; + input.set_transfer_memo(""); + auto t = input.mutable_transaction_template(); + t->set_publisher("astastooo"); + t->set_time(1647421579901555000); + t->set_expiration(1647421879901555000); + t->set_gas_ratio(1); + t->set_gas_limit(100000); + t->set_chain_id(1023); + t->set_delay(0); + t->set_publisher("astastooo"); + t->add_actions(); + auto action = t->mutable_actions(0); + action->set_contract("token.iost"); + action->set_action_name("transfer"); + action->set_data("[\"IOST\",\"astastooo\",\"test_iosted\",\"0.001\",\"test\"]"); + t->add_amount_limit(); + auto amountLimit = t->mutable_amount_limit(0); + amountLimit->set_token("*"); + amountLimit->set_value("unlimited"); + input.mutable_account()->set_active_key(privKeyBytes.data(), 32); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "451ed1e542da2422ed152bff6f30c95e2a8ee2153f4d36f15c45914fa2d2e9f1"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = publicKey.bytes; + const auto signature = + parse_hex("1e5e2de66512658e9317fa56766678166abcf492d020863935723db2030f736710e13437cef0981f" + "cc376eae45349759508767d407b6c9963712910ada2c3606"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preSigningOutput.data_hash()))); + + /// Step 3: Compile transaction info + const auto expectedTx = + "7b2274696d65223a2231363437343231353739393031353535303030222c2265787069726174696f6e223a2231" + "363437343231383739393031353535303030222c226761735f726174696f223a312c226761735f6c696d697422" + "3a3130303030302c22636861696e5f6964223a313032332c22616374696f6e73223a5b7b22636f6e7472616374" + "223a22746f6b656e2e696f7374222c22616374696f6e5f6e616d65223a227472616e73666572222c2264617461" + "223a225b5c22494f53545c222c5c226173746173746f6f6f5c222c5c22746573745f696f737465645c222c5c22" + "302e3030315c222c5c22746573745c225d227d5d2c22616d6f756e745f6c696d6974223a5b7b22746f6b656e22" + "3a222a222c2276616c7565223a22756e6c696d69746564227d5d2c227075626c6973686572223a226173746173" + "746f6f6f222c227075626c69736865725f73696773223a5b7b22616c676f726974686d223a322c227369676e61" + "74757265223a22486c3474356d55535a593654462f7057646d5a34466d7138394a4c51494959354e5849397367" + "4d5063326351345451337a76435948387733627135464e4a645a5549646e31416532795a59334570454b326977" + "3242673d3d222c227075626c69635f6b6579223a2234687a496d2b6d383234536f663866645641474545332b64" + "667931554c7a69786e6f4c5047694a565879383d227d5d7d"; + { + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + + IOST::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), expectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::IOST::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + + TW::IOST::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), expectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKey.bytes}); + IOST::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: invalid signatures + const auto invalidSignature = + parse_hex("fb43727477caaa12542b9060856816d42eedef6ebf2e98e4f8dff4355fe384751925833c4a26b2fed1707aebe655cb3317504a61ee59697c086f7baa6ca06a09"); + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {invalidSignature}, {publicKey.bytes}); + IOST::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_internal); + EXPECT_EQ(output.error_message(), "Invalid signature/hash/publickey combination"); + } + + { // Negative: empty signatures + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {}, {}); + IOST::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/ImmutableX/StarkKeyTests.cpp b/tests/chains/ImmutableX/StarkKeyTests.cpp new file mode 100644 index 00000000000..6f9efca154e --- /dev/null +++ b/tests/chains/ImmutableX/StarkKeyTests.cpp @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Ethereum/EIP2645.h" +#include "HexCoding.h" +#include "ImmutableX/Constants.h" +#include "ImmutableX/StarkKey.h" +#include + +namespace TW::ImmutableX::tests { + +TEST(ImmutableX, PathFromAddress) { + // https://github.com/immutable/imx-core-sdk-swift/blob/main/Tests/ImmutableXCoreTests/Crypto/Stark/StarkKeyTests.swift#L30 + auto res = Ethereum::accountPathFromAddress("0xa76e3eeb2f7143165618ab8feaabcd395b6fac7f", internal::gLayer, internal::gApplication, internal::gIndex); + ASSERT_EQ(res, "m/2645'/579218131'/211006541'/1534045311'/1431804530'/1"); +} + +TEST(ImmutableX, ExtraGrinding) { + using namespace internal; + std::string signature = "0x6d1550458c7a9a1257d73adbcf0fabc12f4497e970d9fa62dd88bf7d9e12719148c96225c1402d8707fd061b1aae2222bdf13571dfc82b3aa9974039f247f2b81b"; + std::string address = "0xa4864d977b944315389d1765ffa7e66F74ee8cd7"; + auto data = parse_hex(signature); + auto path = DerivationPath(Ethereum::accountPathFromAddress(address, gLayer, gApplication, gIndex)); + auto privKey = ImmutableX::getPrivateKeyFromRawSignature(parse_hex(signature), path); + auto pubKey = privKey.getPublicKey(TWPublicKeyTypeStarkex); + ASSERT_EQ(hexEncoded(pubKey.bytes), "0x035919acd61e97b3ecdc75ff8beed8d1803f7ea3cad2937926ae59cc3f8070d4"); +} + +TEST(ImmutableX, GrindKey) { + auto seed = parse_hex("86F3E7293141F20A8BAFF320E8EE4ACCB9D4A4BF2B4D295E8CEE784DB46E0519"); + auto res = grindKey(seed); + ASSERT_EQ(res, "5c8c8683596c732541a59e03007b2d30dbbbb873556fe65b5fb63c16688f941"); +} + +TEST(ImmutableX, GetPrivateKeySignature) { + std::string signature = "0x21fbf0696d5e0aa2ef41a2b4ffb623bcaf070461d61cf7251c74161f82fec3a4370854bc0a34b3ab487c1bc021cd318c734c51ae29374f2beb0e6f2dd49b4bf41c"; + auto data = parse_hex(signature); + // The signature is `rsv`, where `r` starts at 0 and is 32 long. + auto seed = subData(data, 0, 32); + auto result = grindKey(seed); + ASSERT_EQ(result, "766f11e90cd7c7b43085b56da35c781f8c067ac0d578eabdceebc4886435bda"); +} + +TEST(ImmutableX, GetPrivateKeyFromSignature) { + using namespace internal; + std::string address = "0xa76e3eeb2f7143165618ab8feaabcd395b6fac7f"; + std::string signature = "0x5a263fad6f17f23e7c7ea833d058f3656d3fe464baf13f6f5ccba9a2466ba2ce4c4a250231bcac7beb165aec4c9b049b4ba40ad8dd287dc79b92b1ffcf20cdcf1b"; + auto path = DerivationPath(Ethereum::accountPathFromAddress(address, gLayer, gApplication, gIndex)); + auto privKey = ImmutableX::getPrivateKeyFromRawSignature(parse_hex(signature), path); + ASSERT_EQ(hex(privKey.bytes), "058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe"); + ASSERT_TRUE(PrivateKey::isValid(privKey.bytes)); +} + +TEST(ImmutableX, GetPublicKeyFromPrivateKey) { + auto privKeyData = parse_hex("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe", true); + PrivateKey privKey(privKeyData); + auto pubKey = privKey.getPublicKey(TWPublicKeyTypeStarkex); + auto pubKeyHex = hexEncoded(pubKey.bytes); + ASSERT_EQ(pubKeyHex, "0x02a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd"); +} + +TEST(ImmutableX, SimpleSign) { + auto privKeyBytes = parse_hex("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79"); + PrivateKey privKey(privKeyBytes); + auto digest = parse_hex("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); + auto signature = hex(privKey.sign(digest, TWCurve::TWCurveStarkex)); + auto expectedSignature = "061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"; + ASSERT_EQ(signature.size(), 128ULL); + ASSERT_EQ(signature.substr(0, 64), "061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f"); + ASSERT_EQ(signature.substr(64, 64), "04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"); + ASSERT_EQ(signature, expectedSignature); +} + +TEST(ImmutableX, VerifySign) { + // valid + { + auto pubKeyData = parse_hex("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159"); + auto hash = parse_hex("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); + auto signature = parse_hex("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"); + auto pubKey = PublicKey(pubKeyData, TWPublicKeyTypeStarkex); + ASSERT_TRUE(pubKey.verify(signature, hash)); + } + // invalid + { + auto pubKeyData = parse_hex("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159"); + auto hash = parse_hex("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); + auto signature = parse_hex("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9b"); + auto pubKey = PublicKey(pubKeyData, TWPublicKeyTypeStarkex); + ASSERT_FALSE(pubKey.verify(signature, hash)); + } +} + +} // namespace TW::ImmutableX::tests diff --git a/tests/chains/InternetComputer/TWAnyAddressTests.cpp b/tests/chains/InternetComputer/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..5833e05b68d --- /dev/null +++ b/tests/chains/InternetComputer/TWAnyAddressTests.cpp @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWInternetComputer, Address) { + auto string = STRING("58b26ace22a36a0011608a130e84c7cf34ba469c38d24ccf606152ce7de91f4e"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeInternetComputer)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); +} diff --git a/tests/chains/InternetComputer/TWAnySignerTests.cpp b/tests/chains/InternetComputer/TWAnySignerTests.cpp new file mode 100644 index 00000000000..803c5f1e487 --- /dev/null +++ b/tests/chains/InternetComputer/TWAnySignerTests.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/Common.pb.h" +#include "proto/InternetComputer.pb.h" +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::InternetComputer { + +TEST(TWAnySignerInternetComputer, Sign) { + auto key = parse_hex("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be"); + auto input = Proto::SigningInput(); + input.set_private_key(key.data(), key.size()); + input.mutable_transaction()->mutable_transfer()->set_to_account_identifier("943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a"); + input.mutable_transaction()->mutable_transfer()->set_amount(100'000'000); + input.mutable_transaction()->mutable_transfer()->set_memo(0); + input.mutable_transaction()->mutable_transfer()->set_current_timestamp_nanos(1'691'709'940'000'000'000); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeInternetComputer); + + const auto signed_transaction = output.signed_transaction(); + const auto hex_encoded = hex(signed_transaction); + ASSERT_EQ(hex_encoded, "81826b5452414e53414354494f4e81a266757064617465a367636f6e74656e74a66c726571756573745f747970656463616c6c6e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d026b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f706263617267583b0a0012070a050880c2d72f2a220a20943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a3a0a088090caa5a3a78abd176d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f736967984013186f18b9181c189818b318a8186518b2186118d418971618b1187d18eb185818e01826182f1873183b185018cb185d18ef18d81839186418b3183218da1824182f184e18a01880182718c0189018c918a018fd18c418d9189e189818b318ef1874183b185118e118a51864185918e718ed18c71889186c1822182318ca6a726561645f7374617465a367636f6e74656e74a46c726571756573745f747970656a726561645f73746174656e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d0265706174687381824e726571756573745f7374617475735820e8fbc2d5b0bf837b3a369249143e50d4476faafb2dd620e4e982586a51ebcf1e6d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f7369679840182d182718201888188618ce187f0c182a187a18d718e818df18fb18d318d41118a5186a184b18341842185318d718e618e8187a1828186c186a183618461418f3183318bd18a618a718bc18d918c818b7189d186e1865188418ff18fd18e418e9187f181b18d705184818b21872187818d6181c161833184318a2"); +} + +} // namespace TW::InternetComputer diff --git a/tests/chains/InternetComputer/TWCoinTypeTests.cpp b/tests/chains/InternetComputer/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..ca29c0f517a --- /dev/null +++ b/tests/chains/InternetComputer/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +TEST(TWInternetComputerCoinType, TWCoinType) { + const auto coin = TWCoinTypeInternetComputer; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("9e32c54975adf84a1d98f19df41bbc34a752a899c32cc9c0000200b2c4308f85")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("529ea51c22e8d66e8302eabd9297b100fdb369109822248bb86939a671fbc55b")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "internet_computer"); + assertStringsEqual(name, "Internet Computer"); + assertStringsEqual(symbol, "ICP"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 8); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainInternetComputer); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(txUrl, "https://dashboard.internetcomputer.org/transaction/9e32c54975adf84a1d98f19df41bbc34a752a899c32cc9c0000200b2c4308f85"); + assertStringsEqual(accUrl, "https://dashboard.internetcomputer.org/account/529ea51c22e8d66e8302eabd9297b100fdb369109822248bb86939a671fbc55b"); +} diff --git a/tests/chains/IoTeX/AddressTests.cpp b/tests/chains/IoTeX/AddressTests.cpp new file mode 100644 index 00000000000..fd94363fdff --- /dev/null +++ b/tests/chains/IoTeX/AddressTests.cpp @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" + +#include "IoTeX/Address.h" + +namespace TW::IoTeX { + +TEST(IoTeXAddress, Invalid) { + ASSERT_FALSE(Address::isValid("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8")); + ASSERT_FALSE(Address::isValid("io187wzp08vnhjpkydnr97qlh8kh0dpkkytfam8j")); + ASSERT_FALSE(Address::isValid("it187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j")); + ASSERT_FALSE(Address::isValid("ko187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j")); + ASSERT_FALSE(Address::isValid("io187wzp18vnhjjpkydnr97qlh8kh0dpkkytfam8j")); + ASSERT_FALSE(Address::isValid("io187wzp08vnhjbpkydnr97qlh8kh0dpkkytfam8j")); + ASSERT_FALSE(Address::isValid("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8k")); +} + +TEST(IoTeXAddress, Valid) { + ASSERT_TRUE(Address::isValid("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j")); +} + +TEST(IoTeXAddress, FromString) { + Address address; + ASSERT_TRUE(Address::decode("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j", address)); + ASSERT_EQ("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j", address.string()); + + ASSERT_FALSE(Address::decode("io187wzp08vnhjbpkydnr97qlh8kh0dpkkytfam8j", address)); +} + +TEST(IoTeXAddress, FromPrivateKey) { + const auto privateKey = PrivateKey(parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f")); + const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); + + EXPECT_THROW({ + try + { + const auto _publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); + const auto _address = Address(_publicKey); + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ("address may only be an extended SECP256k1 public key", e.what()); + throw; + } + }, std::invalid_argument); +} + +TEST(IoTeXAddress, FromKeyHash) { + const auto keyHash = parse_hex("3f9c20bcec9de520d88d98cbe07ee7b5ded0dac4"); + const auto address = Address(keyHash); + ASSERT_EQ(address.string(), "io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); + + EXPECT_THROW({ + try + { + const auto _keyHash = parse_hex("3f9c20bcec9de520d88d98cbe07ee7b5ded0da"); + const auto _address = Address(_keyHash); + } + catch( const std::invalid_argument& e ) + { + EXPECT_STREQ("invalid address data", e.what()); + throw; + } + }, std::invalid_argument); +} + +} // namespace TW::IoTeX diff --git a/tests/chains/IoTeX/SignerTests.cpp b/tests/chains/IoTeX/SignerTests.cpp new file mode 100644 index 00000000000..bb743500c66 --- /dev/null +++ b/tests/chains/IoTeX/SignerTests.cpp @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "HexCoding.h" +#include "Hash.h" +#include "IoTeX/Address.h" +#include "IoTeX/Signer.h" +#include "proto/IoTeX.pb.h" + +namespace TW::IoTeX { + +TEST(IoTeXSigner, Sign) { + auto input = Proto::SigningInput(); + input.set_version(1); + input.set_nonce(123); + input.set_gaslimit(888); + input.set_gasprice("999"); + auto key = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); + input.set_privatekey(key.data(), key.size()); + auto tsf = input.mutable_transfer(); + tsf->set_amount("456"); + tsf->set_recipient("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); + auto text = parse_hex("68656c6c6f20776f726c6421"); // "hello world!" + tsf->set_payload(text.data(), text.size()); + + auto signer = IoTeX::Signer(std::move(input)); + auto h = signer.hash(); + ASSERT_EQ(hex(h), "0f17cd7f43bdbeff73dfe8f5cb0c0045f2990884e5050841de887cf22ca35b50"); + auto sig = signer.sign(); + ASSERT_EQ(hex(sig), "555cc8af4181bf85c044c3201462eeeb95374f78aa48c67b87510ee63d5e502372e53082f03e9a11c1e351de539cedf85d8dff87de9d003cb9f92243541541a000"); +} + +TEST(IoTeXSigner, Build) { + auto input = Proto::SigningInput(); + input.set_version(1); + input.set_nonce(123); + input.set_gaslimit(888); + input.set_gasprice("999"); + auto keyhex = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); + input.set_privatekey(keyhex.data(), keyhex.size()); + auto tsf = input.mutable_transfer(); + tsf->set_amount("456"); + tsf->set_recipient("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); + auto text = parse_hex("68656c6c6f20776f726c6421"); // "hello world!" + tsf->set_payload(text.data(), text.size()); + + auto signer = IoTeX::Signer(std::move(input)); + auto output = signer.build(); + auto encoded = output.encoded(); // signed action's serialized bytes + ASSERT_EQ(hex(encoded), "0a4c0801107b18f8062203393939523e0a033435361229696f313837777a703038766e686a6a706b79646e723937716c68386b683064706b6b797466616d386a1a0c68656c6c6f20776f726c64211241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a41555cc8af4181bf85c044c3201462eeeb95374f78aa48c67b87510ee63d5e502372e53082f03e9a11c1e351de539cedf85d8dff87de9d003cb9f92243541541a000"); + auto h = output.hash(); // signed action's hash + ASSERT_EQ(hex(h), "6c84ac119058e859a015221f87a4e187c393d0c6ee283959342eac95fad08c33"); +} + +TEST(IoTeXSigner, Compile) { + auto input = Proto::SigningInput(); + input.set_version(1); + input.set_nonce(123); + input.set_gaslimit(888); + input.set_gasprice("999"); + auto tsf = input.mutable_transfer(); + tsf->set_amount("456"); + tsf->set_recipient("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); + auto text = parse_hex("68656c6c6f20776f726c6421"); // "hello world!" + tsf->set_payload(text.data(), text.size()); + + //build preImage + auto preInputData = IoTeX::Signer(std::move(input)); + auto h = preInputData.hash(); + auto checkHash = "0f17cd7f43bdbeff73dfe8f5cb0c0045f2990884e5050841de887cf22ca35b50"; + //check un sign hash + ASSERT_EQ(hex(h), checkHash); + + //build sign + auto key = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); + input.set_privatekey(key.data(), key.size()); + auto signer = IoTeX::Signer(std::move(input)); + Data sig = signer.sign(); + auto checkSig = "555cc8af4181bf85c044c3201462eeeb95374f78aa48c67b87510ee63d5e502372e53082f03e9a11c1e351de539cedf85d8dff87de9d003cb9f92243541541a000"; + //check signature + ASSERT_EQ(hex(sig), checkSig); + + //build compile + auto k = PrivateKey(key); + PublicKey pk = k.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + //merge hash data and signature + auto output = signer.compile(input, sig, pk); + + auto encoded = output.encoded(); + auto hash = output.hash(); // signed action's hash + + auto checkEncoded = "0a4c0801107b18f8062203393939523e0a033435361229696f313837777a703038766e686a6a706b79646e723937716c68386b683064706b6b797466616d386a1a0c68656c6c6f20776f726c64211241044e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34fbeb71270d4bad3d648d91a41555cc8af4181bf85c044c3201462eeeb95374f78aa48c67b87510ee63d5e502372e53082f03e9a11c1e351de539cedf85d8dff87de9d003cb9f92243541541a000"; + auto compileHash = "6c84ac119058e859a015221f87a4e187c393d0c6ee283959342eac95fad08c33"; + //check encoded + ASSERT_EQ(hex(encoded), checkEncoded); + //check hash + ASSERT_EQ(hex(hash), compileHash); +} + +TEST(IoTeXSigner, SignaturePreimage) { + auto input = Proto::SigningInput(); + input.set_version(1); + input.set_nonce(123); + input.set_gaslimit(888); + input.set_gasprice("999"); + auto key = parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); + input.set_privatekey(key.data(), key.size()); + auto tsf = input.mutable_transfer(); + tsf->set_amount("456"); + tsf->set_recipient("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); + auto text = parse_hex("68656c6c6f20776f726c6421"); // "hello world!" + tsf->set_payload(text.data(), text.size()); + + auto signer = IoTeX::Signer(std::move(input)); + + auto preImage = signer.signaturePreimage(); + ASSERT_EQ(hex(preImage), "0801107b18f8062203393939523e0a033435361229696f313837777a703038766e686a6a706b79646e723937716c68386b683064706b6b797466616d386a1a0c68656c6c6f20776f726c6421"); +} + +} // namespace TW::IoTeX diff --git a/tests/chains/IoTeX/StakingTests.cpp b/tests/chains/IoTeX/StakingTests.cpp new file mode 100644 index 00000000000..16515808566 --- /dev/null +++ b/tests/chains/IoTeX/StakingTests.cpp @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Data.h" +#include "HexCoding.h" +#include "IoTeX/Staking.h" +#include "proto/IoTeX.pb.h" +#include "TestUtilities.h" +#include +#include + +namespace TW::IoTeX::tests { + +TEST(TWIoTeXStaking, Create) { + std::string IOTEX_STAKING_CANDIDATE = "io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj"; + std::string IOTEX_STAKING_PAYLOAD = "payload"; + std::string IOTEX_STAKING_AMOUNT = "100"; + Data candidate(IOTEX_STAKING_CANDIDATE.begin(), IOTEX_STAKING_CANDIDATE.end()); + Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); + Data amount(IOTEX_STAKING_AMOUNT.begin(), IOTEX_STAKING_AMOUNT.end()); + + auto stake = stakingCreate(candidate, amount, 10000, true, payload); + ASSERT_EQ(hex(stake), "0a29696f313964307033616834673877773964376b63786671383779786537666e723872" + "7074683573686a120331303018904e20012a077061796c6f6164"); +} + +TEST(TWIoTeXStaking, AddDeposit) { + std::string IOTEX_STAKING_PAYLOAD = "payload"; + std::string IOTEX_STAKING_AMOUNT = "10"; + Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); + Data amount(IOTEX_STAKING_AMOUNT.begin(), IOTEX_STAKING_AMOUNT.end()); + + auto stake = stakingAddDeposit(10, amount, payload); + + ASSERT_EQ(hex(stake), "080a120231301a077061796c6f6164"); +} + +TEST(TWIoTeXStaking, Unstake) { + std::string IOTEX_STAKING_PAYLOAD = "payload"; + Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); + + auto stake = stakingUnstake(10, payload); + + ASSERT_EQ(hex(stake), "080a12077061796c6f6164"); +} + +TEST(TWIoTeXStaking, Withdraw) { + std::string IOTEX_STAKING_PAYLOAD = "payload"; + Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); + + auto stake = stakingWithdraw(10, payload); + + ASSERT_EQ(hex(stake), "080a12077061796c6f6164"); +} + +TEST(TWIoTeXStaking, Restake) { + std::string IOTEX_STAKING_PAYLOAD = "payload"; + Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); + + auto stake = stakingRestake(10, 1000, true, payload); + + ASSERT_EQ(hex(stake), "080a10e807180122077061796c6f6164"); +} + +TEST(TWIoTeXStaking, ChangeCandidate) { + std::string IOTEX_STAKING_CANDIDATE = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza"; + std::string IOTEX_STAKING_PAYLOAD = "payload"; + Data candidate(IOTEX_STAKING_CANDIDATE.begin(), IOTEX_STAKING_CANDIDATE.end()); + Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); + + auto stake = stakingChangeCandidate(10, candidate, payload); + + ASSERT_EQ(hex(stake), "080a1229696f3178707136326177383575717a72636367397935686e727976386c" + "64326e6b7079636333677a611a077061796c6f6164"); +} + +TEST(TWIoTeXStaking, Transfer) { + std::string IOTEX_STAKING_CANDIDATE = "io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza"; + std::string IOTEX_STAKING_PAYLOAD = "payload"; + Data candidate(IOTEX_STAKING_CANDIDATE.begin(), IOTEX_STAKING_CANDIDATE.end()); + Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); + + auto stake = stakingTransfer(10, candidate, payload); + + ASSERT_EQ(hex(stake), "080a1229696f3178707136326177383575717a72636367397935686e727976386c6432" + "6e6b7079636333677a611a077061796c6f6164"); +} + +TEST(TWIoTeXStaking, CandidateRegister) { + std::string IOTEX_STAKING_NAME = "test"; + std::string IOTEX_STAKING_OPERATOR = "io10a298zmzvrt4guq79a9f4x7qedj59y7ery84he"; + std::string IOTEX_STAKING_REWARD = "io13sj9mzpewn25ymheukte4v39hvjdtrfp00mlyv"; + std::string IOTEX_STAKING_OWNER = "io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj"; + std::string IOTEX_STAKING_AMOUNT = "100"; + std::string IOTEX_STAKING_PAYLOAD = "payload"; + Data name(IOTEX_STAKING_NAME.begin(), IOTEX_STAKING_NAME.end()); + Data operatorAddress(IOTEX_STAKING_OPERATOR.begin(), IOTEX_STAKING_OPERATOR.end()); + Data reward(IOTEX_STAKING_REWARD.begin(), IOTEX_STAKING_REWARD.end()); + Data amount(IOTEX_STAKING_AMOUNT.begin(), IOTEX_STAKING_AMOUNT.end()); + Data owner(IOTEX_STAKING_OWNER.begin(), IOTEX_STAKING_OWNER.end()); + Data payload(IOTEX_STAKING_PAYLOAD.begin(), IOTEX_STAKING_PAYLOAD.end()); + + auto stake = + candidateRegister(name, operatorAddress, reward, amount, 10000, false, owner, payload); + + ASSERT_EQ(hex(stake), + "0a5c0a04746573741229696f3130613239387a6d7a7672743467757137396139663478377165646a3539" + "7937657279383468651a29696f3133736a396d7a7065776e3235796d6865756b74653476333968766a64" + "7472667030306d6c7976120331303018904e2a29696f313964307033616834673877773964376b637866" + "71383779786537666e7238727074683573686a32077061796c6f6164"); +} + +TEST(TWIoTeXStaking, CandidateUpdate) { + std::string IOTEX_STAKING_NAME = "test"; + std::string IOTEX_STAKING_OPERATOR = "io1cl6rl2ev5dfa988qmgzg2x4hfazmp9vn2g66ng"; + std::string IOTEX_STAKING_REWARD = "io1juvx5g063eu4ts832nukp4vgcwk2gnc5cu9ayd"; + Data name(IOTEX_STAKING_NAME.begin(), IOTEX_STAKING_NAME.end()); + Data operatorAddress(IOTEX_STAKING_OPERATOR.begin(), IOTEX_STAKING_OPERATOR.end()); + Data reward(IOTEX_STAKING_REWARD.begin(), IOTEX_STAKING_REWARD.end()); + + auto stake = candidateUpdate(name, operatorAddress, reward); + + ASSERT_EQ(hex(stake), "0a04746573741229696f31636c36726c32657635646661393838716d677a6732783468" + "66617a6d7039766e326736366e671a29696f316a757678356730363365753474733833" + "326e756b7034766763776b32676e6335637539617964"); +} + +Proto::SigningInput createSigningInput() { + auto keyhex = parse_hex("cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1"); + auto input = Proto::SigningInput(); + input.set_version(1); + input.set_nonce(0); + input.set_gaslimit(1000000); + input.set_gasprice("10"); + input.set_privatekey(keyhex.data(), keyhex.size()); + return input; +} + +TEST(TWIoTeXStaking, SignAll) { + { // sign stakecreate + auto input = createSigningInput(); + Proto::SigningOutput output; + auto& action = *input.mutable_stakecreate(); + action.set_candidatename("io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj"); + action.set_stakedamount("100"); + action.set_stakedduration(10000); + action.set_autostake(true); + action.set_payload("payload"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ(hex(output.encoded()), + "0a4b080118c0843d22023130c2023e0a29696f313964307033616834673877773964376b63786671" + "3837797865" + "37666e7238727074683573686a120331303018904e20012a077061796c6f6164124104755ce6d890" + "3f6b3793bd" + "db4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c" + "0bc76ef30d" + "d6a1038ed9da8daf331a412e8bac421bab88dcd99c26ac8ffbf27f11ee57a41e7d2537891bfed5ae" + "d8e2e026d4" + "6e55d1b856787bc1cd7c1216a6e2534c5b5d1097c3afe8e657aa27cbbb0801"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "f1785e47b4200c752bb6518bd18097a41e075438b8c18c9cb00e1ae2f38ce767"); + } + { // sign stakeadddeposit + auto input = createSigningInput(); + Proto::SigningOutput output; + auto& action = *input.mutable_stakeadddeposit(); + action.set_bucketindex(10); + action.set_amount("10"); + action.set_payload("payload"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ( + hex(output.encoded()), + "0a1c080118c0843d22023130da020f080a120231301a077061796c6f6164124104755ce6d8903f6b3793" + "bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0b" + "c76ef30dd6a1038ed9da8daf331a41a48ab1feba8181d760de946aefed7d815a89fd9b1ab503d2392bb5" + "5e1bb75eec42dddc8bd642f89accc3a37b3cf15a103a95d66695fdf0647b202869fdd66bcb01"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "ca8937d6f224a4e4bf93cb5605581de2d26fb0481e1dfc1eef384ee7ccf94b73"); + } + { // sign stakeunstake + auto input = createSigningInput(); + Proto::SigningOutput output; + auto& action = *input.mutable_stakeunstake(); + action.set_bucketindex(10); + action.set_payload("payload"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ( + hex(output.encoded()), + "0a18080118c0843d22023130ca020b080a12077061796c6f6164124104755ce6d8903f6b3793bddb4ea5" + "d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30d" + "d6a1038ed9da8daf331a4100adee39b48e1d3dbbd65298a57c7889709fc4df39987130da306f6997374a" + "184b7e7c232a42f21e89b06e6e7ceab81303c6b7483152d08d19ac829b22eb81e601"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "bed58b64a6c4e959eca60a86f0b2149ce0e1dd527ac5fd26aef725ebf7c22a7d"); + } + { // sign stakewithdraw + auto input = createSigningInput(); + Proto::SigningOutput output; + auto& action = *input.mutable_stakewithdraw(); + action.set_bucketindex(10); + action.set_payload("payload"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ( + hex(output.encoded()), + "0a18080118c0843d22023130d2020b080a12077061796c6f6164124104755ce6d8903f6b3793bddb4ea5" + "d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30d" + "d6a1038ed9da8daf331a4152644d102186be6640d46b517331f3402e24424b0d85129595421d28503d75" + "340b2922f5a0d4f667bbd6f576d9816770286b2ce032ba22eaec3952e24da4756b00"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "28049348cf34f1aa927caa250e7a1b08778c44efaf73b565b6fa9abe843871b4"); + } + { // sign stakerestake + auto input = createSigningInput(); + Proto::SigningOutput output; + auto& action = *input.mutable_stakerestake(); + action.set_bucketindex(10); + action.set_stakedduration(1000); + action.set_autostake(true); + action.set_payload("payload"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ( + hex(output.encoded()), + "0a1d080118c0843d22023130e20210080a10e807180122077061796c6f6164124104755ce6d8903f6b37" + "93bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c" + "0bc76ef30dd6a1038ed9da8daf331a41e2e763aed5b1fd1a8601de0f0ae34eb05162e34b0389ae3418ee" + "dbf762f64959634a968313a6516dba3a97b34efba4753bbed3a33d409ecbd45ac75007cd8e9101"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "8816e8f784a1fce40b54d1cd172bb6976fd9552f1570c73d1d9fcdc5635424a9"); + } + { // sign stakechangecandidate + auto input = createSigningInput(); + Proto::SigningOutput output; + auto& action = *input.mutable_stakechangecandidate(); + action.set_bucketindex(10); + action.set_candidatename("io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza"); + action.set_payload("payload"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ( + hex(output.encoded()), + "0a43080118c0843d22023130ea0236080a1229696f3178707136326177383575717a7263636739793568" + "6e727976386c64326e6b7079636333677a611a077061796c6f6164124104755ce6d8903f6b3793bddb4e" + "a5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef3" + "0dd6a1038ed9da8daf331a41d519eb3747163b945b862989b7e82a7f8468001e9683757cb88d5ddd95f8" + "1895047429e858bd48f7d59a88bfec92de231d216293aeba1e4fbe11461d9c9fc99801"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "186526b5b9fe74e25beb52c83c41780a69108160bef2ddaf3bffb9f1f1e5e73a"); + } + { // sign staketransfer + auto input = createSigningInput(); + Proto::SigningOutput output; + auto& action = *input.mutable_staketransferownership(); + action.set_bucketindex(10); + action.set_voteraddress("io1xpq62aw85uqzrccg9y5hnryv8ld2nkpycc3gza"); + action.set_payload("payload"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ( + hex(output.encoded()), + "0a43080118c0843d22023130f20236080a1229696f3178707136326177383575717a7263636739793568" + "6e727976386c64326e6b7079636333677a611a077061796c6f6164124104755ce6d8903f6b3793bddb4e" + "a5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef3" + "0dd6a1038ed9da8daf331a41fa26db427ab87a56a129196c1604f2e22c4dd2a1f99b2217bc916260757d" + "00093d9e6dccdf53e3b0b64e41a69d71c238fbf9281625164694a74dfbeba075d0ce01"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "74b2e1d6a09ba5d1298fa422d5850991ae516865077282196295a38f93c78b85"); + } + { // sign candidateupdate + auto input = createSigningInput(); + Proto::SigningOutput output; + auto& action = *input.mutable_candidateupdate(); + action.set_name("test"); + action.set_operatoraddress("io1cl6rl2ev5dfa988qmgzg2x4hfazmp9vn2g66ng"); + action.set_rewardaddress("io1juvx5g063eu4ts832nukp4vgcwk2gnc5cu9ayd"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ( + hex(output.encoded()), + "0a69080118c0843d2202313082035c0a04746573741229696f31636c36726c3265763564666139383871" + "6d677a673278346866617a6d7039766e326736366e671a29696f316a7576783567303633657534747338" + "33326e756b7034766763776b32676e6335637539617964124104755ce6d8903f6b3793bddb4ea5d3589d" + "637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99a5c1335b583c0bc76ef30dd6a103" + "8ed9da8daf331a4101885c9c6684a4a8f2f5bf11f8326f27be48658f292e8f55ec8a11a604bb0c563a11" + "ebf12d995ca1c152e00f8e0f0edf288db711aa10dbdfd5b7d73b4a28e1f701"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "ca1a28f0e9a58ffc67037cc75066dbdd8e024aa2b2e416e4d6ce16c3d86282e5"); + } + { // sign candidateregister + auto input = createSigningInput(); + Proto::SigningOutput output; + input.set_gasprice("1000"); + auto& cbi = *input.mutable_candidateregister()->mutable_candidate(); + cbi.set_name("test"); + cbi.set_operatoraddress("io10a298zmzvrt4guq79a9f4x7qedj59y7ery84he"); + cbi.set_rewardaddress("io13sj9mzpewn25ymheukte4v39hvjdtrfp00mlyv"); + + auto& action = *input.mutable_candidateregister(); + action.set_stakedamount("100"); + action.set_stakedduration(10000); + action.set_autostake(false); + action.set_owneraddress("io19d0p3ah4g8ww9d7kcxfq87yxe7fnr8rpth5shj"); + action.set_payload("payload"); + ANY_SIGN(input, TWCoinTypeIoTeX); + ASSERT_EQ(hex(output.encoded()), + "0aaa01080118c0843d220431303030fa029a010a5c0a04746573741229696f3130613239387a6d7a" + "7672743467" + "757137396139663478377165646a35397937657279383468651a29696f3133736a396d7a7065776e" + "3235796d68" + "65756b74653476333968766a647472667030306d6c7976120331303018904e2a29696f3139643070" + "3361683467" + "3877773964376b63786671383779786537666e7238727074683573686a32077061796c6f61641241" + "04755ce6d8" + "903f6b3793bddb4ea5d3589d637de2d209ae0ea930815c82db564ee8cc448886f639e8a0c7e94e99" + "a5c1335b58" + "3c0bc76ef30dd6a1038ed9da8daf331a417819b5bcb635e3577acc8ca757f2c3d6afa451c2b6ff8a" + "9179b141ac" + "68e2c50305679e5d09d288da6f0fb52876a86c74deab6a5247edc6d371de5c2f121e159400"); + // signed action's hash + ASSERT_EQ(hex(output.hash()), + "35f53a536e014b32b85df50483ef04849b80ad60635b3b1979c5ba1096b65237"); + } +} + +} // namespace TW::IoTeX::tests diff --git a/tests/chains/IoTeX/TWAnySignerTests.cpp b/tests/chains/IoTeX/TWAnySignerTests.cpp new file mode 100644 index 00000000000..84336d5fe2e --- /dev/null +++ b/tests/chains/IoTeX/TWAnySignerTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include "HexCoding.h" +#include "proto/IoTeX.pb.h" + +#include + +namespace TW::IoTeX::tests { + +TEST(TWAnySignerIoTeX, Sign) { + auto key = parse_hex("68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748"); + Proto::SigningInput input; + input.set_version(1); + input.set_nonce(1); + input.set_gaslimit(1); + input.set_gasprice("1"); + input.set_privatekey(key.data(), key.size()); + auto& transfer = *input.mutable_transfer(); + transfer.set_amount("1"); + transfer.set_recipient("io1e2nqsyt7fkpzs5x7zf2uk0jj72teu5n6aku3tr"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeIoTeX); + + ASSERT_EQ(hex(output.encoded()), "0a39080110011801220131522e0a01311229696f3165326e7173797437666b707a733578377a6632756b306a6a3732746575356e36616b75337472124104fb30b196ce3e976593ecc2da220dca9cdea8c84d2373770042a930b892ac0f5cf762f20459c9100eb9d4d7597f5817bf21e10b53a0120b9ec1ba5cddfdcb669b1a41ec9757ae6c9009315830faaab250b6db0e9535b00843277f596ae0b2b9efc0bd4e14138c056fc4cdfa285d13dd618052b3d1cb7a3f554722005a2941bfede96601"); +} + +} // namespace TW::IoTeX::tests diff --git a/tests/chains/IoTeX/TWCoinTypeTests.cpp b/tests/chains/IoTeX/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..25107bc3895 --- /dev/null +++ b/tests/chains/IoTeX/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWIoTeXCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeIoTeX)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeIoTeX, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeIoTeX, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeIoTeX)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeIoTeX)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeIoTeX), 18); + ASSERT_EQ(TWBlockchainIoTeX, TWCoinTypeBlockchain(TWCoinTypeIoTeX)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeIoTeX)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeIoTeX)); + assertStringsEqual(symbol, "IOTX"); + assertStringsEqual(txUrl, "https://iotexscan.io/action/t123"); + assertStringsEqual(accUrl, "https://iotexscan.io/address/a12"); + assertStringsEqual(id, "iotex"); + assertStringsEqual(name, "IoTeX"); +} diff --git a/tests/chains/IoTeX/TransactionCompilerTests.cpp b/tests/chains/IoTeX/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..4c25d7e5431 --- /dev/null +++ b/tests/chains/IoTeX/TransactionCompilerTests.cpp @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/IoTeX.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TransactionCompiler, IoTeXCompileWithSignatures) { + const auto coin = TWCoinTypeIoTeX; + + const auto privateKey0 = + parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"); + const auto privateKey1 = + parse_hex("6021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d"); + const auto pubKey0 = + parse_hex("034e18306ae9ef4ec9d07bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c"); + const auto pubKey1 = + parse_hex("0253ad2f3b734a197f64911242aabc9b5b10bf5744949f5396e56427f35448eafa"); + const auto ExpectedTx0 = + "0a4c0801107b18f8062203393939523e0a033435361229696f313837777a703038766e686a6a706b79646e7239" + "37716c68386b683064706b6b797466616d386a1a0c68656c6c6f20776f726c64211241044e18306ae9ef4ec9d0" + "7bf6e705442d4d1a75e6cdf750330ca2d880f2cc54607c9c33deb9eae9c06e06e04fe9ce3d43962cc67d5aa34f" + "beb71270d4bad3d648d91a41555cc8af4181bf85c044c3201462eeeb95374f78aa48c67b87510ee63d5e502372" + "e53082f03e9a11c1e351de539cedf85d8dff87de9d003cb9f92243541541a000"; + const auto ExpectedTx1 = + "0a4c0801107b18f8062203393939523e0a033435361229696f313837777a703038766e686a6a706b79646e7239" + "37716c68386b683064706b6b797466616d386a1a0c68656c6c6f20776f726c642112410453ad2f3b734a197f64" + "911242aabc9b5b10bf5744949f5396e56427f35448eafa84a5d74b49ecb56e011b18c3d5a300e8cff7c6b39d33" + "0d1d3799c4700a0b1be21a41de4be56ce74dce8e526590f5b5f947385b00947c4c2ead014429aa706a2470055c" + "56c7e57d1b119b487765d59b21bcdeafac25108f6929a14f9edf4b2309534501"; + + const auto prkey0 = PrivateKey(privateKey0); + const PublicKey pbkey0 = prkey0.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + + const auto prkey1 = PrivateKey(privateKey1); + const PublicKey pbkey1 = prkey1.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::IoTeX::Proto::SigningInput(); + input.set_version(1); + input.set_nonce(123); + input.set_gaslimit(888); + input.set_gasprice("999"); + auto tsf = input.mutable_transfer(); + tsf->set_amount("456"); + tsf->set_recipient("io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); + auto text = parse_hex("68656c6c6f20776f726c6421"); // "hello world!" + tsf->set_payload(text.data(), text.size()); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = data(preSigningOutput.data()); + auto preImageHash = data(preSigningOutput.data_hash()); + + std::string expectedPreImage = + "0801107b18f8062203393939523e0a033435361229696f313837777a703038766e686a6a706b79646e72393771" + "6c68386b683064706b6b797466616d386a1a0c68656c6c6f20776f726c6421"; + std::string expectedPreImageHash = + "0f17cd7f43bdbeff73dfe8f5cb0c0045f2990884e5050841de887cf22ca35b50"; + ASSERT_EQ(hex(preImage), expectedPreImage); + ASSERT_EQ(hex(preImageHash), expectedPreImageHash); + + Data signature = parse_hex("555cc8af4181bf85c044c3201462eeeb95374f78aa48c67b87510ee63d5e502372e" + "53082f03e9a11c1e351de539cedf85d8dff87de9d003cb9f92243541541a000"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(pbkey0.verify(signature, preImageHash)); + /// Step 3: Compile transaction info + { + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {pbkey0.bytes}); + + TW::IoTeX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx0); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::IoTeX::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + EXPECT_EQ(hex(PrivateKey(privateKey0).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + hex(pubKey0)); + signingInput.set_privatekey(prkey0.bytes.data(), prkey0.bytes.size()); + TW::IoTeX::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx0); + } + + { // more signatures + TW::IoTeX::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + EXPECT_EQ(hex(PrivateKey(privateKey1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + hex(pubKey1)); + signingInput.set_privatekey(prkey1.bytes.data(), prkey1.bytes.size()); + TW::IoTeX::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx1); + } + + { // Negative: invalid public key + const auto publicKeyBlake = + parse_hex("6641abedacf9483b793afe1718689cc9420bbb1c"); + EXPECT_EXCEPTION( + TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature}, {publicKeyBlake}), + "Invalid public key"); + } + + { // Negative: not enough signatures + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {}, {pbkey0.bytes}); + + TW::IoTeX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + EXPECT_EQ(output.error_message(), "empty signatures or publickeys"); + } + + { // Negative: not enough publicKey + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {}); + + TW::IoTeX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + EXPECT_EQ(output.error_message(), "empty signatures or publickeys"); + } + + { // Negative: not one to on + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature}, {pbkey0.bytes, pbkey1.bytes}); + + TW::IoTeX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + EXPECT_EQ(output.error_message(), "signatures and publickeys size can only be one"); + } +} \ No newline at end of file diff --git a/tests/chains/KavaEvm/TWCoinTypeTests.cpp b/tests/chains/KavaEvm/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..75ea26d7afc --- /dev/null +++ b/tests/chains/KavaEvm/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWKavaEvmCoinType, TWCoinType) { + const auto coin = TWCoinTypeKavaEvm; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xa999bd5183568ba178795e6a9d1561566fbf4a9ccc813cc475168832bc4909b3")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xF92d3DB0d9f912f285b1ec69578A6201A78487d7")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "kavaevm"); + assertStringsEqual(name, "KavaEvm"); + assertStringsEqual(symbol, "KAVA"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "2222"); + assertStringsEqual(txUrl, "https://explorer.kava.io/tx/0xa999bd5183568ba178795e6a9d1561566fbf4a9ccc813cc475168832bc4909b3"); + assertStringsEqual(accUrl, "https://explorer.kava.io/address/0xF92d3DB0d9f912f285b1ec69578A6201A78487d7"); +} diff --git a/tests/chains/Kin/TWCoinTypeTests.cpp b/tests/chains/Kin/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..0f8c6f5c509 --- /dev/null +++ b/tests/chains/Kin/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWKinCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeKin)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeKin, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeKin, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeKin)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeKin)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeKin), 5); + ASSERT_EQ(TWBlockchainStellar, TWCoinTypeBlockchain(TWCoinTypeKin)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeKin)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeKin)); + assertStringsEqual(symbol, "KIN"); + assertStringsEqual(txUrl, "https://www.kin.org/blockchainInfoPage/?&dataType=public&header=Transaction&id=t123"); + assertStringsEqual(accUrl, "https://www.kin.org/blockchainAccount/?&dataType=public&header=accountID&id=a12"); + assertStringsEqual(id, "kin"); + assertStringsEqual(name, "Kin"); +} diff --git a/tests/chains/Klaytn/TWCoinTypeTests.cpp b/tests/chains/Klaytn/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..3a07d5df016 --- /dev/null +++ b/tests/chains/Klaytn/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWKaiaCoinType, TWCoinType) { + const auto coin = TWCoinTypeKaia; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x93ea92687845fe7bb6cacd69c76a16a2a3c2bbb85a8a93ff0e032d0098d583d7")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x2ad9656bf5b82caf10847b431012e28e301e83ba")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "kaia"); + assertStringsEqual(name, "Kaia"); + assertStringsEqual(symbol, "KLAY"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "8217"); + assertStringsEqual(txUrl, "https://kaiascan.io/tx/0x93ea92687845fe7bb6cacd69c76a16a2a3c2bbb85a8a93ff0e032d0098d583d7"); + assertStringsEqual(accUrl, "https://kaiascan.io/account/0x2ad9656bf5b82caf10847b431012e28e301e83ba"); +} diff --git a/tests/chains/Komodo/AddressTests.cpp b/tests/chains/Komodo/AddressTests.cpp new file mode 100644 index 00000000000..ab2e54482db --- /dev/null +++ b/tests/chains/Komodo/AddressTests.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HDWallet.h" +#include "HexCoding.h" +#include "PublicKey.h" +#include +#include + +using namespace TW; + +namespace TW::Komodo::tests { + +TEST(KomodoAddress, Valid) { + ASSERT_TRUE(TW::validateAddress(TWCoinTypeKomodo, "RXL3YXG2ceaB6C5hfJcN4fvmLH2C34knhA")); +} + +TEST(KomodoAddress, Invalid) { + ASSERT_FALSE(TW::validateAddress(TWCoinTypeKomodo, "t1Wg9uPPAfwhBWeRjtDPa5ZHNzyBx9rJVKY")); +} + +TEST(KomodoAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("020e46e79a2a8d12b9b5d12c7a91adb4e454edfae43c0a0cb805427d2ac7613fd9"), TWPublicKeyTypeSECP256k1); + auto address = TW::deriveAddress(TWCoinTypeKomodo, publicKey); + ASSERT_EQ(address, "RXL3YXG2ceaB6C5hfJcN4fvmLH2C34knhA"); + + auto wallet = HDWallet("shoot island position soft burden budget tooth cruel issue economy destroy above", ""); + auto addr = wallet.deriveAddress(TWCoinTypeKomodo); + EXPECT_EQ(addr, "RCWJLXE5CSXydxdSnwcghzPgkFswERegyb"); +} + +} // namespace TW::Komodo::tests diff --git a/tests/chains/Komodo/TWAnyAddressTests.cpp b/tests/chains/Komodo/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..fb1374f99fe --- /dev/null +++ b/tests/chains/Komodo/TWAnyAddressTests.cpp @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::Komodo::tests { + +TEST(TWKomodo, Address) { + auto string = STRING("RALiENnMMjyubc38hM31h6oicPsuWdAMYg"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeKomodo)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "0ba28b3eebfe1d39dab038324be2c66090ee21a3"); +} + +} // namespace TW::Komodo::tests diff --git a/tests/chains/Komodo/TWCoinTypeTests.cpp b/tests/chains/Komodo/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..ca1c88a4c15 --- /dev/null +++ b/tests/chains/Komodo/TWCoinTypeTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +namespace TW::Komodo::tests { + +TEST(TWKomodoCoinType, TWCoinType) { + const auto coin = TWCoinTypeKomodo; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("f53bd1a5c0f5dc4b60ba9a1882742ea96faa996e1b870795812a29604dd7829e")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("RWvfkt8UjbPWXgeZEcgYmKw2vA1bbAx5t2")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "komodo"); + assertStringsEqual(name, "Komodo"); + assertStringsEqual(symbol, "KMD"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 8); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainZcash); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x55); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(txUrl, "https://kmdexplorer.io//tx/f53bd1a5c0f5dc4b60ba9a1882742ea96faa996e1b870795812a29604dd7829e"); + assertStringsEqual(accUrl, "https://kmdexplorer.io//address/RWvfkt8UjbPWXgeZEcgYmKw2vA1bbAx5t2"); +} + +} // namespace TW::Komodo::tests \ No newline at end of file diff --git a/tests/chains/Komodo/TransactionCompilerTests.cpp b/tests/chains/Komodo/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..70b12713334 --- /dev/null +++ b/tests/chains/Komodo/TransactionCompilerTests.cpp @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "Zcash/TransactionBuilder.h" + +#include "proto/Bitcoin.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include + +#include "TestUtilities.h" +#include + +using namespace TW; +namespace TW::Komodo::tests { + +TEST(KomodoCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeKomodo; + + // tx on mainnet + // https://kmdexplorer.io/tx/dab3e7a705b0f80f0cd557a1e727dc50d8ccd24ff0ae159ca8cdefda656d7c9b + + const int64_t amount = 892984972; + const int64_t fee = 407; + const std::string toAddress = "RVUiqSDZEqTw9Ny4XRBsp6fgJKtmUj5nXD"; + auto publicKey = PublicKey(parse_hex("021f5a3a5f78b1f0adbbd8685c2c32de45e00e5b83faa814db57ce410295405207"), TWPublicKeyTypeSECP256k1); + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_coin_type(coin); + + auto txHash0 = parse_hex("f6118b221c4e5f436d536eded8486f6b0cc6ab99ca424da120fec593304acd8c"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(1); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(892989042); + + auto utxoAddr0 = "R9TKEwwiDLA2oD7a1jt8YmCoX2cjg1pfEU"; + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, coin); + ASSERT_EQ(hex(script0.bytes), "76a91401ea238017d65b2c5152a81146b95582b5284c2f88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + + auto plan = Zcash::TransactionBuilder::plan(input); + ASSERT_EQ(plan.error, Common::Proto::OK); + + plan.amount = amount; + plan.fee = fee; + plan.change = 0; + std::copy(Zcash::SaplingBranchID.begin(), Zcash::SaplingBranchID.end(), std::back_inserter(plan.branchId)); + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // build preimage + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + ASSERT_EQ(preSigningOutput.hash_public_keys().size(), 1); + + auto preImageHash = data(preSigningOutput.hash_public_keys()[0].data_hash()); + EXPECT_EQ(hex(preImageHash), + "09323f2c24af2cf44453aa228c213f26f40e1f87548031bad35cc4c65edc087a"); + + // compile + TW::Data signature0 = parse_hex("3045022100fb6e7a940815bc0de683dd70ed85696ffe21199958161331e76954af2ba11b1b02204860632bcad9c5a3cbaa2d60c401f7616f529e4c65915f1996286d3bd54c01cb"); + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature0}, {publicKey.bytes}); + Bitcoin::Proto::SigningOutput signingOutput; + ASSERT_TRUE(signingOutput.ParseFromArray(outputData.data(), (int)outputData.size())); + ASSERT_EQ(signingOutput.error(), Common::Proto::OK); + ASSERT_EQ(hex(signingOutput.encoded()), "0400008085202f89018ccd4a3093c5fe20a14d42ca99abc60c6b6f48d8de6e536d435f4e1c228b11f6010000006b483045022100fb6e7a940815bc0de683dd70ed85696ffe21199958161331e76954af2ba11b1b02204860632bcad9c5a3cbaa2d60c401f7616f529e4c65915f1996286d3bd54c01cb0121021f5a3a5f78b1f0adbbd8685c2c32de45e00e5b83faa814db57ce410295405207ffffffff018cde3935000000001976a914dd90c41f2916bcfea10ed11cd10ed4db01c5be6488ac00000000000000000000000000000000000000"); + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature0, signature0}, {publicKey.bytes}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +} // namespace TW::Komodo::tests \ No newline at end of file diff --git a/tests/chains/KuCoinCommunityChain/TWCoinTypeTests.cpp b/tests/chains/KuCoinCommunityChain/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..c1f3e42147f --- /dev/null +++ b/tests/chains/KuCoinCommunityChain/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWKuCoinCommunityChainCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeKuCoinCommunityChain)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x2f0d79cd289a02f3181b68b9583a64c3809fe7387810b274275985c29d02c80d")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeKuCoinCommunityChain, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x4446fc4eb47f2f6586f9faab68b3498f86c07521")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeKuCoinCommunityChain, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeKuCoinCommunityChain)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeKuCoinCommunityChain)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeKuCoinCommunityChain), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeKuCoinCommunityChain)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeKuCoinCommunityChain)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeKuCoinCommunityChain)); + assertStringsEqual(symbol, "KCS"); + assertStringsEqual(txUrl, "https://explorer.kcc.io/en/tx/0x2f0d79cd289a02f3181b68b9583a64c3809fe7387810b274275985c29d02c80d"); + assertStringsEqual(accUrl, "https://explorer.kcc.io/en/address/0x4446fc4eb47f2f6586f9faab68b3498f86c07521"); + assertStringsEqual(id, "kcc"); + assertStringsEqual(name, "KuCoin Community Chain"); +} diff --git a/tests/chains/Kusama/AddressTests.cpp b/tests/chains/Kusama/AddressTests.cpp new file mode 100644 index 00000000000..a29aa2abf54 --- /dev/null +++ b/tests/chains/Kusama/AddressTests.cpp @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Kusama/Address.h" +#include "PublicKey.h" +#include "PrivateKey.h" +#include +#include + +namespace TW::Kusama::tests { + +TEST(KusamaAddress, Validation) { + // Substrate ed25519 + ASSERT_FALSE(Address::isValid("5FqqU2rytGPhcwQosKRtW1E3ha6BJKAjHgtcodh71dSyXhoZ")); + // Polkadot ed25519 + ASSERT_FALSE(Address::isValid("15AeCjMpcSt3Fwa47jJBd7JzQ395Kr2cuyF5Zp4UBf1g9ony")); + // Polkadot sr25519 + ASSERT_FALSE(Address::isValid("15AeCjMpcSt3Fwa47jJBd7JzQ395Kr2cuyF5Zp4UBf1g9ony")); + // Bitcoin + ASSERT_FALSE(Address::isValid("1ES14c7qLb5CYhLMUekctxLgc1FV2Ti9DA")); + + // Kusama ed25519 + ASSERT_TRUE(Address::isValid("FHKAe66mnbk8ke8zVWE9hFVFrJN1mprFPVmD5rrevotkcDZ")); + // Kusama secp256k1 + ASSERT_TRUE(Address::isValid("FxQFyTorsjVsjjMyjdgq8w5vGx8LiA1qhWbRYcFijxKKchx")); + // Kusama sr25519 + ASSERT_TRUE(Address::isValid("EJ5UJ12GShfh7EWrcNZFLiYU79oogdtXFUuDDZzk7Wb2vCe")); +} + +TEST(KusamaAddress, FromPrivateKey) { + // from subkey: tiny escape drive pupil flavor endless love walk gadget match filter luxury + auto privateKey = PrivateKey(parse_hex("0xa21981f3bb990c40837df44df639541ff57c5e600f9eb4ac00ed8d1f718364e5")); + auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519)); + ASSERT_EQ(address.string(), "CeVXtoU4py9e7F6upfM2ZarVave299TjcdaTSxhDDZrYgnM"); +} + +TEST(KusamaAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("0x032eb287017c5cde2940b5dd062d413f9d09f8aa44723fc80bf46b96c81ac23d"), TWPublicKeyTypeED25519); + auto address = Address(publicKey); + ASSERT_EQ(address.string(), "CeVXtoU4py9e7F6upfM2ZarVave299TjcdaTSxhDDZrYgnM"); +} + +TEST(KusamaAddress, FromString) { + auto address = Address("CeVXtoU4py9e7F6upfM2ZarVave299TjcdaTSxhDDZrYgnM"); + ASSERT_EQ(address.string(), "CeVXtoU4py9e7F6upfM2ZarVave299TjcdaTSxhDDZrYgnM"); +} + +} // namespace TW::Kusama::tests \ No newline at end of file diff --git a/tests/chains/Kusama/SignerTests.cpp b/tests/chains/Kusama/SignerTests.cpp new file mode 100644 index 00000000000..f271547e1e8 --- /dev/null +++ b/tests/chains/Kusama/SignerTests.cpp @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Polkadot/Extrinsic.h" +#include "Polkadot/SS58Address.h" +#include "Polkadot/Signer.h" +#include "Coin.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "proto/Polkadot.pb.h" +#include "uint256.h" + +#include +#include + + +namespace TW::Polkadot::tests { + extern PrivateKey privateKey; + extern PublicKey toPublicKey; + auto genesisHashKSM = parse_hex("b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); + +TEST(KusamaSigner, SignTransferKSM) { + auto blockHash = parse_hex("4955dd4813f3e91ef3fd5a825b928af2fc50a71380085f753ccef00bb1582891"); + auto toAddress = SS58Address(toPublicKey, TWSS58AddressTypeKusama); + + auto input = TW::Polkadot::Proto::SigningInput(); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_genesis_hash(genesisHashKSM.data(), genesisHashKSM.size()); + input.set_nonce(0); + input.set_spec_version(2019); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_network(ss58Prefix(TWCoinType::TWCoinTypeKusama)); + input.set_transaction_version(2); + + auto balanceCall = input.mutable_balance_call(); + auto& transfer = *balanceCall->mutable_transfer(); + auto value = store(uint256_t(12345)); + transfer.set_to_address(toAddress.string()); + transfer.set_value(value.data(), value.size()); + + auto extrinsic = TW::Polkadot::Extrinsic(input); + auto preimage = extrinsic.encodePayload(); + auto output = TW::Polkadot::Signer::sign(input); + + ASSERT_EQ(hex(preimage), "04008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0000000e307000002000000b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe4955dd4813f3e91ef3fd5a825b928af2fc50a71380085f753ccef00bb1582891"); + ASSERT_EQ(hex(output.encoded()), "25028488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee000765cfa76cfe19499f4f19ef7dc4527652ec5b2e6b5ecfaf68725dafd48ae2694ad52e61f44152a544784e847de10ddb2c56bee4406574dcbcfdb5e5d35b6d0300000004008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); +} + +} // namespace TW::Polkadot::tests diff --git a/tests/chains/Kusama/TWAnySignerTests.cpp b/tests/chains/Kusama/TWAnySignerTests.cpp new file mode 100644 index 00000000000..cb61bf2ee82 --- /dev/null +++ b/tests/chains/Kusama/TWAnySignerTests.cpp @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/Polkadot.pb.h" +#include "uint256.h" +#include "TestUtilities.h" +#include + +#include + +namespace TW::Polkadot::tests { + +TEST(TWAnySignerKusama, Sign) { + auto key = parse_hex("0x8cdc538e96f460da9d639afc5c226f477ce98684d77fb31e88db74c1f1dd86b2"); + auto genesisHash = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); + + Proto::SigningInput input; + input.set_block_hash(genesisHash.data(), genesisHash.size()); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_nonce(1); + input.set_spec_version(2019); + input.set_private_key(key.data(), key.size()); + input.set_network(TWCoinTypeSS58Prefix(TWCoinTypeKusama)); + input.set_transaction_version(2); + + auto balanceCall = input.mutable_balance_call(); + auto& transfer = *balanceCall->mutable_transfer(); + auto value = store(uint256_t(10000000000)); + transfer.set_to_address("CtwdfrhECFs3FpvCGoiE4hwRC4UsSiM8WL899HjRdQbfYZY"); + transfer.set_value(value.data(), value.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeKusama); + + ASSERT_EQ(hex(output.encoded()), "350284f41296779fd61a5bed6c2f506cc6c9ea93d6aeb357b9c69717193f434ba24ae700cd78b46eff36c433e642d7e9830805aab4f43eef70067ef32c8b2a294c510673a841c5f8a6e8900c03be40cfa475ae53e6f8aa61961563cb7cc0fa169ef9630d00040004000e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc720700e40b5402"); +} + +} // namespace TW::Polkadot::tests diff --git a/tests/chains/Kusama/TWCoinTypeTests.cpp b/tests/chains/Kusama/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..6dcf092e68d --- /dev/null +++ b/tests/chains/Kusama/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWKusamaCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeKusama)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xcbe0c2e2851c1245bedaae4d52f06eaa6b4784b786bea2f0bff11af7715973dd")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeKusama, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("DbCNECPna3k6MXFWWNZa5jGsuWycqEE6zcUxZYkxhVofrFk")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeKusama, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeKusama)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeKusama)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeKusama), 12); + ASSERT_EQ(TWBlockchainKusama, TWCoinTypeBlockchain(TWCoinTypeKusama)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeKusama)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeKusama)); + assertStringsEqual(symbol, "KSM"); + assertStringsEqual(txUrl, "https://kusama.subscan.io/extrinsic/0xcbe0c2e2851c1245bedaae4d52f06eaa6b4784b786bea2f0bff11af7715973dd"); + assertStringsEqual(accUrl, "https://kusama.subscan.io/account/DbCNECPna3k6MXFWWNZa5jGsuWycqEE6zcUxZYkxhVofrFk"); + assertStringsEqual(id, "kusama"); + assertStringsEqual(name, "Kusama"); +} diff --git a/tests/chains/Lightlink/TWCoinTypeTests.cpp b/tests/chains/Lightlink/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..1b41b162d87 --- /dev/null +++ b/tests/chains/Lightlink/TWCoinTypeTests.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +TEST(TWLightlinkCoinType, TWCoinType) { + const auto coin = TWCoinTypeLightlink; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xc65f82445aefc883fd4ffe09149c8ce48f61b670c0734528a49d4a8bd8309bb0")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x4566ED6c7a7fFc90E2C7cfF7eB9156262afD2fDe")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "lightlink"); + assertStringsEqual(name, "Lightlink Phoenix"); + assertStringsEqual(symbol, "ETH"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0); + assertStringsEqual(txUrl, "https://phoenix.lightlink.io/tx/0xc65f82445aefc883fd4ffe09149c8ce48f61b670c0734528a49d4a8bd8309bb0"); + assertStringsEqual(accUrl, "https://phoenix.lightlink.io/address/0x4566ED6c7a7fFc90E2C7cfF7eB9156262afD2fDe"); +} diff --git a/tests/chains/Linea/TWCoinTypeTests.cpp b/tests/chains/Linea/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..8570bd33cdd --- /dev/null +++ b/tests/chains/Linea/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +TEST(TWLineaCoinType, TWCoinType) { + const auto coin = TWCoinTypeLinea; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x0c7086f96865f4fcad58d7f3449db7baab9fce2625bcb79e7ea26676aa0d3420")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xbf71018f716ca6c64b0b12622f87a26b3b86100f")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "linea"); + assertStringsEqual(name, "Linea"); + assertStringsEqual(symbol, "ETH"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "59144"); + assertStringsEqual(txUrl, "https://lineascan.build/tx/0x0c7086f96865f4fcad58d7f3449db7baab9fce2625bcb79e7ea26676aa0d3420"); + assertStringsEqual(accUrl, "https://lineascan.build/address/0xbf71018f716ca6c64b0b12622f87a26b3b86100f"); +} diff --git a/tests/Litecoin/LitecoinAddressTests.cpp b/tests/chains/Litecoin/LitecoinAddressTests.cpp similarity index 81% rename from tests/Litecoin/LitecoinAddressTests.cpp rename to tests/chains/Litecoin/LitecoinAddressTests.cpp index 15e454f8e09..19bee636bb4 100644 --- a/tests/Litecoin/LitecoinAddressTests.cpp +++ b/tests/chains/Litecoin/LitecoinAddressTests.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Bitcoin/Address.h" #include "Bitcoin/SegwitAddress.h" @@ -13,11 +11,12 @@ #include #include -#include #include +#include using namespace TW; -using namespace TW::Bitcoin; + +namespace TW::Bitcoin::tests { TEST(LitecoinAddress, deriveAddress_legacy) { const auto pubKey = PublicKey(parse_hex("03b49081a4d7ad24b20e209bc6fe10491aadb5607777baf0509a036cce96025db0"), TWPublicKeyTypeSECP256k1); @@ -36,3 +35,5 @@ TEST(LitecoinAddress, deriveAddress_segwit) { const auto address = SegwitAddress(pubKey, stringForHRP(TWCoinTypeHRP(TWCoinTypeLitecoin))); EXPECT_EQ(address.string(), addrStr); } + +} // namespace TW::Bitcoin::tests diff --git a/tests/chains/Litecoin/TWCoinTypeTests.cpp b/tests/chains/Litecoin/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..0bb7e1376f6 --- /dev/null +++ b/tests/chains/Litecoin/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWLitecoinCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeLitecoin)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeLitecoin, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeLitecoin, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeLitecoin)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeLitecoin)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeLitecoin), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeLitecoin)); + ASSERT_EQ(0x32, TWCoinTypeP2shPrefix(TWCoinTypeLitecoin)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeLitecoin)); + assertStringsEqual(symbol, "LTC"); + assertStringsEqual(txUrl, "https://blockchair.com/litecoin/transaction/t123"); + assertStringsEqual(accUrl, "https://blockchair.com/litecoin/address/a12"); + assertStringsEqual(id, "litecoin"); + assertStringsEqual(name, "Litecoin"); +} diff --git a/tests/Litecoin/TWLitecoinTests.cpp b/tests/chains/Litecoin/TWLitecoinTests.cpp similarity index 92% rename from tests/Litecoin/TWLitecoinTests.cpp rename to tests/chains/Litecoin/TWLitecoinTests.cpp index 46dd87b280f..f85584a571a 100644 --- a/tests/Litecoin/TWLitecoinTests.cpp +++ b/tests/chains/Litecoin/TWLitecoinTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -15,6 +13,8 @@ #include +namespace TW::Litecoin::tests { + TEST(Litecoin, LegacyAddress) { auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get())); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); @@ -46,9 +46,8 @@ TEST(Litecoin, LockScriptForAddressM) { TEST(Litecoin, ExtendedKeys) { auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( - STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), - STRING("TREZOR").get() - )); + STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), + STRING("TREZOR").get())); // .bip44 auto lptv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeLitecoin, TWHDVersionLTPV)); @@ -99,3 +98,5 @@ TEST(Litecoin, LockScripts) { auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "76a914e771c6695c5dd189ccc4ef00cd0f3db3096d79bd88ac"); } + +} // namespace TW::Litecoin::tests diff --git a/tests/chains/MantaPacific/TWCoinTypeTests.cpp b/tests/chains/MantaPacific/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..53dbbf4ffde --- /dev/null +++ b/tests/chains/MantaPacific/TWCoinTypeTests.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +TEST(TWMantaPacificCoinType, TWCoinType) { + const auto coin = TWCoinTypeMantaPacific; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x2bbd5d85b0ed05d1416e30ce1197a6f0c27d10ce02593a2719e2baf486d2e8c2")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xF122a1aC569a36a5Cf6d0F828A22254c8A9afF84")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "manta"); + assertStringsEqual(name, "Manta Pacific"); + assertStringsEqual(symbol, "ETH"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0); + assertStringsEqual(txUrl, "https://pacific-explorer.manta.network/tx/0x2bbd5d85b0ed05d1416e30ce1197a6f0c27d10ce02593a2719e2baf486d2e8c2"); + assertStringsEqual(accUrl, "https://pacific-explorer.manta.network/address/0xF122a1aC569a36a5Cf6d0F828A22254c8A9afF84"); +} diff --git a/tests/chains/Mantle/TWCoinTypeTests.cpp b/tests/chains/Mantle/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..43518159914 --- /dev/null +++ b/tests/chains/Mantle/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWMantleCoinType, TWCoinType) { + const auto coin = TWCoinTypeMantle; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xfae996ea23f1ff9909ac04d26ae6e52ab600a84163fab9e0e893483c685629dd")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xA295EEFd401C8BE1457F266d3e73cdD015e5CFbb")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "mantle"); + assertStringsEqual(name, "Mantle"); + assertStringsEqual(symbol, "MNT"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "5000"); + assertStringsEqual(txUrl, "https://explorer.mantle.xyz/tx/0xfae996ea23f1ff9909ac04d26ae6e52ab600a84163fab9e0e893483c685629dd"); + assertStringsEqual(accUrl, "https://explorer.mantle.xyz/address/0xA295EEFd401C8BE1457F266d3e73cdD015e5CFbb"); +} diff --git a/tests/chains/Merlin/TWCoinTypeTests.cpp b/tests/chains/Merlin/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..c50d7b185bb --- /dev/null +++ b/tests/chains/Merlin/TWCoinTypeTests.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +TEST(TWMerlinCoinType, TWCoinType) { + const auto coin = TWCoinTypeMerlin; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xca6f2891959b669237738dd0bc2c1051d966778c9de90b94165032ce58364212")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xf7e017b3f61bD3410A3b03D7DAD7699FD6780584")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "merlin"); + assertStringsEqual(name, "Merlin"); + assertStringsEqual(symbol, "BTC"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0); + assertStringsEqual(txUrl, "https://scan.merlinchain.io/tx/0xca6f2891959b669237738dd0bc2c1051d966778c9de90b94165032ce58364212"); + assertStringsEqual(accUrl, "https://scan.merlinchain.io/address/0xf7e017b3f61bD3410A3b03D7DAD7699FD6780584"); +} diff --git a/tests/chains/Meter/TWCoinTypeTests.cpp b/tests/chains/Meter/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..711c4fb8577 --- /dev/null +++ b/tests/chains/Meter/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWMeterCoinType, TWCoinType) { + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeMeter)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x8ea268d5dbb40217c763b800a75fc063cf28b56f40f2bc69dc043f5c4bbdc144")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeMeter, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xe5a273954d24eddf9ae9ea4cef2347d584cfa3dd")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeMeter, accId.get())); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeMeter)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeMeter)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeMeter), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeMeter)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeMeter)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeMeter)); + assertStringsEqual(symbol, "MTR"); + assertStringsEqual(txUrl, "https://scan.meter.io/tx/0x8ea268d5dbb40217c763b800a75fc063cf28b56f40f2bc69dc043f5c4bbdc144"); + assertStringsEqual(accUrl, "https://scan.meter.io/address/0xe5a273954d24eddf9ae9ea4cef2347d584cfa3dd"); + assertStringsEqual(id, "meter"); + assertStringsEqual(name, "Meter"); +} diff --git a/tests/chains/Metis/TWCoinTypeTests.cpp b/tests/chains/Metis/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..b5696255acd --- /dev/null +++ b/tests/chains/Metis/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWMetisCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeMetis)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x422f2ebbede32d4434ad0cf0ae55d44a84e14d3d5725a760133255b42676d8ce")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeMetis, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xBe9E8Ec25866B21bA34e97b9393BCabBcB4A5C86")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeMetis, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeMetis)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeMetis)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeMetis), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeMetis)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeMetis)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeMetis)); + assertStringsEqual(symbol, "METIS"); + assertStringsEqual(txUrl, "https://andromeda-explorer.metis.io/tx/0x422f2ebbede32d4434ad0cf0ae55d44a84e14d3d5725a760133255b42676d8ce"); + assertStringsEqual(accUrl, "https://andromeda-explorer.metis.io/address/0xBe9E8Ec25866B21bA34e97b9393BCabBcB4A5C86"); + assertStringsEqual(id, "metis"); + assertStringsEqual(name, "Metis"); +} diff --git a/tests/chains/Monacoin/TWCoinTypeTests.cpp b/tests/chains/Monacoin/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..30ae52f4ef0 --- /dev/null +++ b/tests/chains/Monacoin/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWMonacoinCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeMonacoin)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeMonacoin, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeMonacoin, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeMonacoin)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeMonacoin)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeMonacoin), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeMonacoin)); + ASSERT_EQ(0x37, TWCoinTypeP2shPrefix(TWCoinTypeMonacoin)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeMonacoin)); + assertStringsEqual(symbol, "MONA"); + assertStringsEqual(txUrl, "https://blockbook.electrum-mona.org/tx/t123"); + assertStringsEqual(accUrl, "https://blockbook.electrum-mona.org/address/a12"); + assertStringsEqual(id, "monacoin"); + assertStringsEqual(name, "Monacoin"); +} diff --git a/tests/Monacoin/TWMonacoinAddressTests.cpp b/tests/chains/Monacoin/TWMonacoinAddressTests.cpp similarity index 95% rename from tests/Monacoin/TWMonacoinAddressTests.cpp rename to tests/chains/Monacoin/TWMonacoinAddressTests.cpp index e8f6c7c985d..a4ef2fa7fca 100644 --- a/tests/Monacoin/TWMonacoinAddressTests.cpp +++ b/tests/chains/Monacoin/TWMonacoinAddressTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Monacoin/TWMonacoinTransactionTests.cpp b/tests/chains/Monacoin/TWMonacoinTransactionTests.cpp similarity index 82% rename from tests/Monacoin/TWMonacoinTransactionTests.cpp rename to tests/chains/Monacoin/TWMonacoinTransactionTests.cpp index 5cc5da208b1..c2113f0c915 100644 --- a/tests/Monacoin/TWMonacoinTransactionTests.cpp +++ b/tests/chains/Monacoin/TWMonacoinTransactionTests.cpp @@ -1,11 +1,8 @@ - -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "HexCoding.h" #include "PublicKey.h" #include "proto/Bitcoin.pb.h" @@ -15,8 +12,7 @@ #include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { TEST(MonacoinTransaction, SignTransaction) { /* @@ -67,8 +63,7 @@ TEST(MonacoinTransaction, SignTransaction) { ASSERT_EQ(output.error(), Common::Proto::OK); ASSERT_EQ(hex(output.encoded()), - "0100000001441a513dccc3b660c09c42ceaac147fcdc12b5de4b8b56a078fce5d5ce420aed000000006a473044022047789dc7483b178199439bbfce0ab0caf532fec51095ba099d0d9b0b2169033402201745a0160d8d327655a8ef0542367396ce86bbb13df6b183d58c922e422cfa10012102fc08693599fda741558613cd44a50fc65953b1be797637f8790a495b85554f3effffffff0280f0fa02000000001976a914076df984229a2731cbf465ec8fbd35b8da94380f88ac60a2fa02000000001976a914fea39370769d4fed2d8ab98dd5daa482cc56113b88ac00000000" - ); + "0100000001441a513dccc3b660c09c42ceaac147fcdc12b5de4b8b56a078fce5d5ce420aed000000006a473044022047789dc7483b178199439bbfce0ab0caf532fec51095ba099d0d9b0b2169033402201745a0160d8d327655a8ef0542367396ce86bbb13df6b183d58c922e422cfa10012102fc08693599fda741558613cd44a50fc65953b1be797637f8790a495b85554f3effffffff0280f0fa02000000001976a914076df984229a2731cbf465ec8fbd35b8da94380f88ac60a2fa02000000001976a914fea39370769d4fed2d8ab98dd5daa482cc56113b88ac00000000"); } TEST(MonacoinTransaction, LockScripts) { @@ -93,3 +88,5 @@ TEST(MonacoinTransaction, LockScripts) { auto scriptData3 = WRAPD(TWBitcoinScriptData(script3.get())); assertHexEqual(scriptData3, "001422e6014ad3631f1939281c3625bc98db808fbfb0"); } + +} // namespace TW::Bitcoin diff --git a/tests/chains/Moonbeam/TWCoinTypeTests.cpp b/tests/chains/Moonbeam/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..4d30a26c0f5 --- /dev/null +++ b/tests/chains/Moonbeam/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWMoonbeamCoinType, TWCoinType) { + const auto coin = TWCoinTypeMoonbeam; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xb22a146c933e6e51affbfa5f712a266b5f5e92ae453cd2f252bcc3c36ff035a6")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x201bb4f276C765dF7225e5A4153E17edD23a67eC")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "moonbeam"); + assertStringsEqual(name, "Moonbeam"); + assertStringsEqual(symbol, "GLMR"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "1284"); + assertStringsEqual(txUrl, "https://moonscan.io/tx/0xb22a146c933e6e51affbfa5f712a266b5f5e92ae453cd2f252bcc3c36ff035a6"); + assertStringsEqual(accUrl, "https://moonscan.io/address/0x201bb4f276C765dF7225e5A4153E17edD23a67eC"); +} diff --git a/tests/chains/Moonriver/TWCoinTypeTests.cpp b/tests/chains/Moonriver/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..b278f21814c --- /dev/null +++ b/tests/chains/Moonriver/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWMoonriverCoinType, TWCoinType) { + const auto coin = TWCoinTypeMoonriver; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x2e2daa3943ba65d9bbb910a4f6765aa6a466a0ef8935090547ca9d30e201e032")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x899831D937937d011305E73EE782cce0455DF15a")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "moonriver"); + assertStringsEqual(name, "Moonriver"); + assertStringsEqual(symbol, "MOVR"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "1285"); + assertStringsEqual(txUrl, "https://moonriver.moonscan.io/tx/0x2e2daa3943ba65d9bbb910a4f6765aa6a466a0ef8935090547ca9d30e201e032"); + assertStringsEqual(accUrl, "https://moonriver.moonscan.io/address/0x899831D937937d011305E73EE782cce0455DF15a"); +} diff --git a/tests/chains/MultiversX/AddressTests.cpp b/tests/chains/MultiversX/AddressTests.cpp new file mode 100644 index 00000000000..2135f4678bd --- /dev/null +++ b/tests/chains/MultiversX/AddressTests.cpp @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include + +#include "MultiversX/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TestAccounts.h" + +using namespace TW; + +namespace TW::MultiversX::tests { + +TEST(MultiversXAddress, Valid) { + ASSERT_TRUE(Address::isValid(ALICE_BECH32)); + ASSERT_TRUE(Address::isValid(BOB_BECH32)); +} + +TEST(MultiversXAddress, Invalid) { + ASSERT_FALSE(Address::isValid("")); + ASSERT_FALSE(Address::isValid("foo")); + ASSERT_FALSE(Address::isValid("10z9xdugayn528ksaesdwlhf006fw5sg2qmmm0h52fvxczwgesyvq5pwemr")); + ASSERT_FALSE(Address::isValid("xerd10z9xdugayn528ksaesdwlhf006fw5sg2qmmm0h52fvxczwgesyvq5pwemr")); + ASSERT_FALSE(Address::isValid("foo10z9xdugayn528ksaesdwlhf006fw5sg2qmmm0h52fvxczwgesyvq5pwemr")); + ASSERT_FALSE(Address::isValid(ALICE_PUBKEY_HEX)); +} + +TEST(MultiversXAddress, FromString) { + Address alice, bob, carol; + ASSERT_TRUE(Address::decode(ALICE_BECH32, alice)); + ASSERT_TRUE(Address::decode(BOB_BECH32, bob)); + + ASSERT_EQ(ALICE_PUBKEY_HEX, hex(alice.getKeyHash())); + ASSERT_EQ(BOB_PUBKEY_HEX, hex(bob.getKeyHash())); +} + +TEST(MultiversXAddress, FromData) { + const auto alice = Address(parse_hex(ALICE_PUBKEY_HEX)); + const auto bob = Address(parse_hex(BOB_PUBKEY_HEX)); + + ASSERT_EQ(ALICE_BECH32, alice.string()); + ASSERT_EQ(BOB_BECH32, bob.string()); +} + +TEST(MultiversXAddress, FromPrivateKey) { + auto aliceKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto alice = Address(aliceKey.getPublicKey(TWPublicKeyTypeED25519)); + ASSERT_EQ(ALICE_BECH32, alice.string()); + + auto bobKey = PrivateKey(parse_hex(BOB_SEED_HEX)); + auto bob = Address(bobKey.getPublicKey(TWPublicKeyTypeED25519)); + ASSERT_EQ(BOB_BECH32, bob.string()); +} + +TEST(MultiversXAddress, FromPublicKey) { + auto alice = PublicKey(parse_hex(ALICE_PUBKEY_HEX), TWPublicKeyTypeED25519); + ASSERT_EQ(ALICE_BECH32, Address(alice).string()); + + auto bob = PublicKey(parse_hex(BOB_PUBKEY_HEX), TWPublicKeyTypeED25519); + ASSERT_EQ(BOB_BECH32, Address(bob).string()); +} + +} // namespace TW::MultiversX::tests diff --git a/tests/chains/MultiversX/SerializationTests.cpp b/tests/chains/MultiversX/SerializationTests.cpp new file mode 100644 index 00000000000..37651803c7d --- /dev/null +++ b/tests/chains/MultiversX/SerializationTests.cpp @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include + +#include "HexCoding.h" +#include "MultiversX/Serialization.h" +#include "TestAccounts.h" + +using namespace TW; + +namespace TW::MultiversX::tests { + +TEST(MultiversXSerialization, SerializeTransactionWithData) { + Transaction transaction; + transaction.nonce = 42; + transaction.value = "1000000000000000000"; + transaction.sender = ALICE_BECH32; + transaction.receiver = BOB_BECH32; + transaction.gasPrice = 1000000000; + transaction.gasLimit = 54500; + transaction.data = "foo"; + transaction.chainID = "1"; + transaction.version = 2; + + string expected = + "{" + R"("nonce":42,"value":"1000000000000000000",)" + R"("receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",)" + R"("sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th",)" + R"("gasPrice":1000000000,"gasLimit":54500,"data":"Zm9v","chainID":"1","version":2)" + "}"; + + string actual = serializeTransaction(transaction); + ASSERT_EQ(expected, actual); +} + +TEST(MultiversXSerialization, SerializeTransactionWithoutData) { + Transaction transaction; + transaction.nonce = 42; + transaction.value = "1000000000000000000"; + transaction.sender = ALICE_BECH32; + transaction.receiver = BOB_BECH32; + transaction.gasPrice = 1000000000; + transaction.gasLimit = 50000; + transaction.chainID = "1"; + transaction.version = 2; + + string expected = + "{" + R"("nonce":42,"value":"1000000000000000000",)" + R"("receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",)" + R"("sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th",)" + R"("gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":2)" + "}"; + + string actual = serializeTransaction(transaction); + ASSERT_EQ(expected, actual); +} + +TEST(MultiversXSerialization, SerializeTransactionWithUsernames) { + Transaction transaction; + transaction.nonce = 42; + transaction.value = "1000000000000000000"; + transaction.sender = ALICE_BECH32; + transaction.senderUsername = "alice"; + transaction.receiver = BOB_BECH32; + transaction.receiverUsername = "bob"; + transaction.gasPrice = 1000000000; + transaction.gasLimit = 100000; + transaction.chainID = "1"; + transaction.version = 2; + + string expected = + "{" + R"("nonce":42,"value":"1000000000000000000",)" + R"("receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",)" + R"("sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th",)" + R"("senderUsername":"YWxpY2U=","receiverUsername":"Ym9i",)" + R"("gasPrice":1000000000,"gasLimit":100000,"chainID":"1","version":2)" + "}"; + + string actual = serializeTransaction(transaction); + ASSERT_EQ(expected, actual); +} + +TEST(MultiversXSerialization, SerializeTransactionWithGuardianAddress) { + Transaction transaction; + transaction.nonce = 42; + transaction.value = "1000000000000000000"; + transaction.sender = ALICE_BECH32; + transaction.receiver = BOB_BECH32; + transaction.guardian = CAROL_BECH32; + transaction.gasPrice = 1000000000; + transaction.gasLimit = 100000; + transaction.chainID = "1"; + transaction.version = 2; + transaction.options = TransactionOptions::Guarded; + + string expected = + "{" + R"("nonce":42,"value":"1000000000000000000",)" + R"("receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",)" + R"("sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th",)" + R"("gasPrice":1000000000,"gasLimit":100000,"chainID":"1","version":2,"options":2,)" + R"("guardian":"erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8")" + "}"; + + string actual = serializeTransaction(transaction); + ASSERT_EQ(expected, actual); +} + +} // namespace TW::MultiversX::tests diff --git a/tests/chains/MultiversX/SignerTests.cpp b/tests/chains/MultiversX/SignerTests.cpp new file mode 100644 index 00000000000..73b1ed7cf29 --- /dev/null +++ b/tests/chains/MultiversX/SignerTests.cpp @@ -0,0 +1,651 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include + +#include "boost/format.hpp" +#include "HexCoding.h" +#include "MultiversX/Address.h" +#include "MultiversX/Codec.h" +#include "MultiversX/Signer.h" +#include "MultiversX/Transaction.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TestAccounts.h" +#include "TestUtilities.h" + +using namespace TW; + +namespace TW::MultiversX::tests { + +TEST(MultiversXSigner, SignGenericAction) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data("foo"); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(54500); + input.set_chain_id("1"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "61362540ad012ebff8436aa7fed7567639e7ef3150434b880975d844fde8cbb4e637e5537cb895ba2d0b12014ada866080b379dd96e2a7c150818a9956fb7b00"; + nlohmann::json expected = R"( + { + "chainID":"1", + "data":"Zm9v", + "gasLimit":54500, + "gasPrice":1000000000, + "nonce":7, + "receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "signature":"61362540ad012ebff8436aa7fed7567639e7ef3150434b880975d844fde8cbb4e637e5537cb895ba2d0b12014ada866080b379dd96e2a7c150818a9956fb7b00", + "value":"0", + "version":1 + })"_json; + + assertJSONEqual(expected, nlohmann::json::parse(encoded)); + ASSERT_EQ(expectedSignature, signature); +} + +TEST(MultiversXSigner, SignGenericActionUnDelegate) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(6); + input.mutable_generic_action()->mutable_accounts()->set_sender("erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa"); + input.mutable_generic_action()->mutable_accounts()->set_receiver("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r"); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data("unDelegate@" + TW::MultiversX::Codec::encodeBigInt("1000000000000000000")); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(12000000); + input.set_chain_id("1"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "89f9683af92f7b835bff4e1d0dbfcff5245b3367df4d23538eb799e0ad0a90be29ac3bd3598ce55b35b35ebef68bfa5738eed39fd01adc33476f65bd1b966e0b"; + nlohmann::json expected = R"( + { + "chainID":"1", + "data":"dW5EZWxlZ2F0ZUAwZGUwYjZiM2E3NjQwMDAw", + "gasLimit":12000000, + "gasPrice":1000000000, + "nonce":6, + "receiver":"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r", + "sender":"erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa", + "signature":"89f9683af92f7b835bff4e1d0dbfcff5245b3367df4d23538eb799e0ad0a90be29ac3bd3598ce55b35b35ebef68bfa5738eed39fd01adc33476f65bd1b966e0b", + "value":"0", + "version":1 + })"_json; + assertJSONEqual(expected, nlohmann::json::parse(encoded)); + ASSERT_EQ(expectedSignature, signature); + // Successfully broadcasted https://explorer.multiversx.com/transactions/3301ae5a6a77f0ab9ceb5125258f12539a113b0c6787de76a5c5867f2c515d65 +} + +TEST(MultiversXSigner, SignGenericActionRedelegateRewards) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); + input.mutable_generic_action()->mutable_accounts()->set_sender("erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa"); + input.mutable_generic_action()->mutable_accounts()->set_receiver("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r"); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data("reDelegateRewards"); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(12000000); + input.set_chain_id("1"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "fc0238d41e4d02a24ac8a502cc3d59e406258b5c186c883e2e9aeffa859a818f5317bf22c9bc6d3838c64529953a46c1d4aabc485f96675a4c4dd642f5f50402"; + nlohmann::json expected = R"( + { + "chainID":"1", + "data":"cmVEZWxlZ2F0ZVJld2FyZHM=", + "gasLimit":12000000, + "gasPrice":1000000000, + "nonce":7, + "receiver":"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r", + "sender":"erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa", + "signature":"fc0238d41e4d02a24ac8a502cc3d59e406258b5c186c883e2e9aeffa859a818f5317bf22c9bc6d3838c64529953a46c1d4aabc485f96675a4c4dd642f5f50402", + "value":"0", + "version":1 + })"_json; + assertJSONEqual(expected, nlohmann::json::parse(encoded)); + ASSERT_EQ(expectedSignature, signature); +} + +TEST(MultiversXSigner, SignGenericActionClaimRewards) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); + input.mutable_generic_action()->mutable_accounts()->set_sender("erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa"); + input.mutable_generic_action()->mutable_accounts()->set_receiver("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r"); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data("claimRewards"); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(6000000); + input.set_chain_id("1"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "c453652214d428045721ad5560194a699ce4194ba7edcbdc1c4f5d1e9a605b82bb0a0fd7dba708322b62518d5d5af3e7380efab0804ac00cdafe7598e7498900"; + nlohmann::json expected = R"( + { + "chainID":"1", + "data":"Y2xhaW1SZXdhcmRz", + "gasLimit":6000000, + "gasPrice":1000000000, + "nonce":7, + "receiver":"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r", + "sender":"erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa", + "signature":"c453652214d428045721ad5560194a699ce4194ba7edcbdc1c4f5d1e9a605b82bb0a0fd7dba708322b62518d5d5af3e7380efab0804ac00cdafe7598e7498900", + "value":"0", + "version":1 + })"_json; + assertJSONEqual(expected, nlohmann::json::parse(encoded)); + ASSERT_EQ(expectedSignature, signature); +} + +TEST(MultiversXSigner, SignGenericActionWithdrawStake) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); + input.mutable_generic_action()->mutable_accounts()->set_sender("erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa"); + input.mutable_generic_action()->mutable_accounts()->set_receiver("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r"); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data("withdraw"); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(12000000); + input.set_chain_id("1"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "a2a17498e78e29082c433c009895bd949fc68b2222620d8f5350f821350cde390c15ffe00df4f0e84a074abd892331b79503bf458a35cb90333d1350553d9302"; + nlohmann::json expected = R"( + { + "chainID":"1", + "data":"d2l0aGRyYXc=", + "gasLimit":12000000, + "gasPrice":1000000000, + "nonce":7, + "receiver":"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r", + "sender":"erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa", + "signature":"a2a17498e78e29082c433c009895bd949fc68b2222620d8f5350f821350cde390c15ffe00df4f0e84a074abd892331b79503bf458a35cb90333d1350553d9302", + "value":"0", + "version":1 + })"_json; + assertJSONEqual(expected, nlohmann::json::parse(encoded)); + ASSERT_EQ(expectedSignature, signature); + // Need to wait 9 days for broadcasting +} + +TEST(MultiversXSigner, SignGenericActionDelegate) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(1); + input.mutable_generic_action()->mutable_accounts()->set_sender("erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa"); + input.mutable_generic_action()->mutable_accounts()->set_receiver("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r"); + input.mutable_generic_action()->set_value("1"); + input.mutable_generic_action()->set_data("delegate"); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(12000000); + input.set_chain_id("1"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "3b9164d47a4e3c0330ae387cd29ba6391f9295acf5e43a16a4a2611645e66e5fa46bf22294ca68fe1948adf45cec8cb47b8792afcdb248bd9adec7c6e6c27108"; + nlohmann::json expected = R"( + { + "chainID":"1", + "data":"ZGVsZWdhdGU=", + "gasLimit":12000000, + "gasPrice":1000000000, + "nonce":1, + "receiver":"erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhllllscrt56r", + "sender":"erd1aajqh5xjka5fk0c235dwy7qd6lkz2e29tlhy8gncuq0mcr68q34qgswnqa", + "signature":"3b9164d47a4e3c0330ae387cd29ba6391f9295acf5e43a16a4a2611645e66e5fa46bf22294ca68fe1948adf45cec8cb47b8792afcdb248bd9adec7c6e6c27108", + "value":"1", + "version":1 + })"_json; + assertJSONEqual(expected, nlohmann::json::parse(encoded)); + ASSERT_EQ(expectedSignature, signature); + // Successfully broadcasted https://explorer.multiversx.com/transactions/e5007662780f8ed677b37b156007c24bf60b7366000f66ec3525cfa16a4564e7 +} + +TEST(MultiversXSigner, SignGenericActionJSON) { + // Shuffle some fields, assume arbitrary order in the input + auto input = R"( + { + "genericAction" : { + "accounts": { + "senderNonce": 7, + "receiver": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + }, + "data": "foo", + "value": "0", + "version": 1 + }, + "gasPrice": 1000000000, + "gasLimit": 54500, + "chainId": "1" + })"; + + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto encoded = Signer::signJSON(input, privateKey.bytes); + nlohmann::json expected = R"( + { + "chainID":"1", + "data":"Zm9v", + "gasLimit":54500, + "gasPrice":1000000000, + "nonce":7, + "receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "signature":"61362540ad012ebff8436aa7fed7567639e7ef3150434b880975d844fde8cbb4e637e5537cb895ba2d0b12014ada866080b379dd96e2a7c150818a9956fb7b00", + "value":"0", + "version":1 + })"_json; + + assertJSONEqual(expected, nlohmann::json::parse(encoded)); +} + +TEST(MultiversXSigner, SignWithoutData) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(0); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data(""); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(50000); + input.set_chain_id("1"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "c7253b821c68011584ebd3a5bb050ade19235c2d10260e411e523105826c40a79849b3eeb96fcc2a7a6b1fa140b6756f50b249e005be056ce0cf53125e0b1b00"; + nlohmann::json expected = R"( + { + "chainID":"1", + "gasLimit":50000, + "gasPrice":1000000000, + "nonce":0, + "receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "signature":"c7253b821c68011584ebd3a5bb050ade19235c2d10260e411e523105826c40a79849b3eeb96fcc2a7a6b1fa140b6756f50b249e005be056ce0cf53125e0b1b00", + "value":"0", + "version":1 + })"_json; + + ASSERT_EQ(expectedSignature, signature); + assertJSONEqual(expected, nlohmann::json::parse(encoded)); +} + +TEST(MultiversXSigner, SignJSONWithoutData) { + // Shuffle some fields, assume arbitrary order in the input + auto input = R"( + { + "genericAction" : { + "accounts": { + "senderNonce": 0, + "receiver": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + }, + "value": "0", + "version": 1 + }, + "gasPrice": 1000000000, + "gasLimit": 50000, + "chainId": "1" + })"; + + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto encoded = Signer::signJSON(input, privateKey.bytes); + nlohmann::json expected = R"( + { + "chainID":"1", + "gasLimit":50000, + "gasPrice":1000000000, + "nonce":0, + "receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "signature":"c7253b821c68011584ebd3a5bb050ade19235c2d10260e411e523105826c40a79849b3eeb96fcc2a7a6b1fa140b6756f50b249e005be056ce0cf53125e0b1b00", + "value":"0", + "version":1 + })"_json; + + assertJSONEqual(expected, nlohmann::json::parse(encoded)); +} + +TEST(MultiversXSigner, SignWithUsernames) { + // https://github.com/multiversx/mx-chain-go/blob/master/examples/construction_test.go, scenario "TestConstructTransaction_Usernames". + + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(89); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_sender_username("alice"); + input.mutable_generic_action()->mutable_accounts()->set_receiver_username("bob"); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data(""); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(50000); + input.set_chain_id("1"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "dfffb303eee7a6df0a027171feffde001637e59164a8b8c61d387da7fcefccd08d90f7b0e6fd0b4bc7357517edc5b6ea4a5088e0fb0be314e7e597e5248a8a03"; + nlohmann::json expected = R"( + { + "chainID":"1", + "gasLimit":50000, + "gasPrice":1000000000, + "nonce":89, + "receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "receiverUsername": "Ym9i", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "senderUsername": "YWxpY2U=", + "signature":"dfffb303eee7a6df0a027171feffde001637e59164a8b8c61d387da7fcefccd08d90f7b0e6fd0b4bc7357517edc5b6ea4a5088e0fb0be314e7e597e5248a8a03", + "value":"0", + "version":1 + })"_json; + + ASSERT_EQ(expectedSignature, signature); + assertJSONEqual(expected, nlohmann::json::parse(encoded)); +} + +TEST(MultiversXSigner, SignWithOptions) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(89); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data(""); + input.mutable_generic_action()->set_version(2); + // We'll set a dummy value on the "options" field (merely an example). + // Currently, the "options" field should be ignored (not set) by applications using TW Core. + input.mutable_generic_action()->set_options(42); + input.set_gas_price(1000000000); + input.set_gas_limit(50000); + input.set_chain_id("1"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "ea478652417dc319c3e898d7f99f3a7b04fd32b62a7d43d5d6822a6a46b9346853426ac2ad5cdc710f0f3c5a6f509b21195e712ed9b3c95f454c7ed85079cb0b"; + nlohmann::json expected = R"( + { + "chainID":"1", + "gasLimit":50000, + "gasPrice":1000000000, + "nonce":89, + "options": 42, + "receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "signature":"ea478652417dc319c3e898d7f99f3a7b04fd32b62a7d43d5d6822a6a46b9346853426ac2ad5cdc710f0f3c5a6f509b21195e712ed9b3c95f454c7ed85079cb0b", + "value":"0", + "version":2 + })"_json; + + ASSERT_EQ(expectedSignature, signature); + assertJSONEqual(expected, nlohmann::json::parse(encoded)); +} + +TEST(MultiversXSigner, SignEGLDTransfer) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_egld_transfer()->mutable_accounts()->set_sender_nonce(7); + input.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_egld_transfer()->set_amount("1000000000000000000"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "0f40dec9d37bde3c67803fc535088e536344e271807bb7c1aa24af3c69bffa9b705e149ff7bcaf21678f4900c4ee72741fa6ef08bf4c67fc6da1c6b0f337730e"; + nlohmann::json expected = R"( + { + "chainID":"1", + "gasLimit":50000, + "gasPrice":1000000000, + "nonce":7, + "receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "signature":"0f40dec9d37bde3c67803fc535088e536344e271807bb7c1aa24af3c69bffa9b705e149ff7bcaf21678f4900c4ee72741fa6ef08bf4c67fc6da1c6b0f337730e", + "value":"1000000000000000000", + "version":2 + })"_json; + + ASSERT_EQ(expectedSignature, signature); + assertJSONEqual(expected, nlohmann::json::parse(encoded)); +} + +TEST(MultiversXSigner, SignESDTTransfer) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_esdt_transfer()->mutable_accounts()->set_sender_nonce(7); + input.mutable_esdt_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_esdt_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_esdt_transfer()->set_token_identifier("MYTOKEN-1234"); + input.mutable_esdt_transfer()->set_amount("10000000000000"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "dd7cdc90aa09da6034b00a99e3ba0f1a2a38fa788fad018d53bf2e706f99e1a42c80063c28e6b48a5f2574c4054986f34c8eb36b1da63a22d19cf3ea5990b306"; + + // "ESDTTransfer@4d59544f4b454e2d31323334@09184e72a000" + nlohmann::json expected = R"( + { + "chainID":"1", + "data":"RVNEVFRyYW5zZmVyQDRkNTk1NDRmNGI0NTRlMmQzMTMyMzMzNEAwOTE4NGU3MmEwMDA=", + "gasLimit":425000, + "gasPrice":1000000000, + "nonce":7, + "receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "signature":"dd7cdc90aa09da6034b00a99e3ba0f1a2a38fa788fad018d53bf2e706f99e1a42c80063c28e6b48a5f2574c4054986f34c8eb36b1da63a22d19cf3ea5990b306", + "value":"0", + "version":2 + })"_json; + + ASSERT_EQ(expectedSignature, signature); + assertJSONEqual(expected, nlohmann::json::parse(encoded)); +} + +TEST(MultiversXSigner, SignESDTNFTTransfer) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_esdtnft_transfer()->mutable_accounts()->set_sender_nonce(7); + input.mutable_esdtnft_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_esdtnft_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_esdtnft_transfer()->set_token_collection("LKMEX-aab910"); + input.mutable_esdtnft_transfer()->set_token_nonce(4); + input.mutable_esdtnft_transfer()->set_amount("184300000000000000"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "59af89d9a9ece1f35bc34323c42061cae27bb5f9830f5eb26772e680732cbd901a86caa7c3eadacd392fe1024bef4c1f08ce1dfcafec257d6f41444ccea30a0c"; + + // "ESDTNFTTransfer@4c4b4d45582d616162393130@04@028ec3dfa01ac000@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8" + nlohmann::json expected = R"( + { + "chainID":"1", + "data":"RVNEVE5GVFRyYW5zZmVyQDRjNGI0ZDQ1NTgyZDYxNjE2MjM5MzEzMEAwNEAwMjhlYzNkZmEwMWFjMDAwQDgwNDlkNjM5ZTVhNjk4MGQxY2QyMzkyYWJjY2U0MTAyOWNkYTc0YTE1NjM1MjNhMjAyZjA5NjQxY2MyNjE4Zjg=", + "gasLimit":937500, + "gasPrice":1000000000, + "nonce":7, + "receiver":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "signature":"59af89d9a9ece1f35bc34323c42061cae27bb5f9830f5eb26772e680732cbd901a86caa7c3eadacd392fe1024bef4c1f08ce1dfcafec257d6f41444ccea30a0c", + "value":"0", + "version":2 + })"_json; + + ASSERT_EQ(expectedSignature, signature); + assertJSONEqual(expected, nlohmann::json::parse(encoded)); +} + +TEST(MultiversXSigner, SignGenericActionWithGuardian) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(42); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_guardian(CAROL_BECH32); + input.mutable_generic_action()->set_value("1000000000000000000"); + input.mutable_generic_action()->set_data(""); + input.mutable_generic_action()->set_version(2); + input.mutable_generic_action()->set_options(TransactionOptions::Guarded); + input.set_gas_price(1000000000); + input.set_gas_limit(100000); + input.set_chain_id("1"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "dae30e5cddb4a1f050009f939ce2c90843770870f9e6c77366be07e5cd7b3ebfdda38cd45d04e9070037d57761b6a68cee697e6043057f9dc565a4d0e632480d"; + nlohmann::json expected = R"( + { + "chainID":"1", + "gasLimit":100000, + "gasPrice":1000000000, + "guardian":"erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + "nonce":42, + "options":2, + "receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "signature":"dae30e5cddb4a1f050009f939ce2c90843770870f9e6c77366be07e5cd7b3ebfdda38cd45d04e9070037d57761b6a68cee697e6043057f9dc565a4d0e632480d", + "value":"1000000000000000000", + "version":2 + })"_json; + + ASSERT_EQ(expectedSignature, signature); + assertJSONEqual(expected, nlohmann::json::parse(encoded)); +} + +TEST(MultiversXSigner, SignEGLDTransferWithGuardian) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_egld_transfer()->mutable_accounts()->set_sender_nonce(7); + input.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_egld_transfer()->mutable_accounts()->set_guardian(CAROL_BECH32); + input.mutable_egld_transfer()->set_amount("1000000000000000000"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "741dd0d24db4df37a050f693f8481b6e51b8dd6dfc2f01a4f90aa1af3e59c89a8b0ef9d710af33103970e353d9f0cb9fd128a2e174731cbc88265d9737ed5604"; + nlohmann::json expected = R"( + { + "chainID":"1", + "gasLimit":100000, + "gasPrice":1000000000, + "guardian":"erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + "nonce":7, + "options":2, + "receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "signature":"741dd0d24db4df37a050f693f8481b6e51b8dd6dfc2f01a4f90aa1af3e59c89a8b0ef9d710af33103970e353d9f0cb9fd128a2e174731cbc88265d9737ed5604", + "value":"1000000000000000000", + "version":2 + })"_json; + + ASSERT_EQ(expectedSignature, signature); + assertJSONEqual(expected, nlohmann::json::parse(encoded)); +} + +TEST(ElrondSigner, buildUnsignedTxBytes) { + auto input = Proto::SigningInput(); + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data("foo"); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(50000); + input.set_chain_id("1"); + auto unsignedTxBytes = Signer::buildUnsignedTxBytes(input); + auto expectedData = TW::data((boost::format(R"({"nonce":7,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1})") % BOB_BECH32 % ALICE_BECH32).str()); + ASSERT_EQ(expectedData, unsignedTxBytes); +} + +TEST(ElrondSigner, buildSigningOutput) { + auto input = Proto::SigningInput(); + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data("foo"); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(50000); + input.set_chain_id("1"); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto unsignedTxBytes = Signer::buildUnsignedTxBytes(input); + auto signature = privateKey.sign(unsignedTxBytes, TWCurveED25519); + + auto output = Signer::buildSigningOutput(input, signature); + std::string expectedSignatureHex = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700"; + ASSERT_EQ(expectedSignatureHex, hex(signature)); + auto expectedEncoded = (boost::format(R"({"nonce":7,"value":"0","receiver":"%1%","sender":"%2%","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1,"signature":"%3%"})") % BOB_BECH32 % ALICE_BECH32 % expectedSignatureHex).str(); + ASSERT_EQ(output.signature(), expectedSignatureHex); + ASSERT_EQ(output.encoded(), expectedEncoded); +} + +} // namespace TW::MultiversX::tests diff --git a/tests/chains/MultiversX/TWAnySignerTests.cpp b/tests/chains/MultiversX/TWAnySignerTests.cpp new file mode 100644 index 00000000000..1b1311b1382 --- /dev/null +++ b/tests/chains/MultiversX/TWAnySignerTests.cpp @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "HexCoding.h" +#include "MultiversX/Signer.h" +#include "TestAccounts.h" +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::MultiversX::tests { + +TEST(TWAnySignerMultiversX, Sign) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->set_value("0"); + input.mutable_generic_action()->set_data("foo"); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(54500); + input.set_chain_id("1"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeMultiversX); + + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "61362540ad012ebff8436aa7fed7567639e7ef3150434b880975d844fde8cbb4e637e5537cb895ba2d0b12014ada866080b379dd96e2a7c150818a9956fb7b00"; + nlohmann::json expected = R"( + { + "chainID":"1", + "data":"Zm9v", + "gasLimit":54500, + "gasPrice":1000000000, + "nonce":7, + "receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "signature":"61362540ad012ebff8436aa7fed7567639e7ef3150434b880975d844fde8cbb4e637e5537cb895ba2d0b12014ada866080b379dd96e2a7c150818a9956fb7b00", + "value":"0", + "version":1 + })"_json; + + ASSERT_EQ(expectedSignature, signature); + assertJSONEqual(expected, nlohmann::json::parse(encoded)); +} + +TEST(TWAnySignerMultiversX, SignJSON) { + // Shuffle some fields, assume arbitrary order in the input + auto input = STRING(R"( + { + "genericAction" : { + "accounts": { + "senderNonce": 7, + "receiver": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + }, + "data": "foo", + "value": "0", + "version": 1 + }, + "gasPrice": 1000000000, + "gasLimit": 54500, + "chainId": "1" + })"); + + auto privateKey = DATA(ALICE_SEED_HEX); + auto encoded = WRAPS(TWAnySignerSignJSON(input.get(), privateKey.get(), TWCoinTypeMultiversX)); + nlohmann::json expected = R"( + { + "chainID":"1", + "data":"Zm9v", + "gasLimit":54500, + "gasPrice":1000000000, + "nonce":7, + "receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "signature":"61362540ad012ebff8436aa7fed7567639e7ef3150434b880975d844fde8cbb4e637e5537cb895ba2d0b12014ada866080b379dd96e2a7c150818a9956fb7b00", + "value":"0", + "version":1 + })"_json; + + ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeMultiversX)); + assertJSONEqual(expected, nlohmann::json::parse(TWStringUTF8Bytes(encoded.get()))); +} + +} // namespace TW::MultiversX::tests diff --git a/tests/chains/MultiversX/TWCoinTypeTests.cpp b/tests/chains/MultiversX/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..fd5a2248400 --- /dev/null +++ b/tests/chains/MultiversX/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWMultiversXCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeMultiversX)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("163b46551a74626415074b626d2f37d3c78aef0f6ccb628db434ee65a35ea127")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeMultiversX, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeMultiversX, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeMultiversX)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeMultiversX)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeMultiversX), 18); + ASSERT_EQ(TWBlockchainMultiversX, TWCoinTypeBlockchain(TWCoinTypeMultiversX)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeMultiversX)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeMultiversX)); + assertStringsEqual(symbol, "eGLD"); + assertStringsEqual(txUrl, "https://explorer.multiversx.com/transactions/163b46551a74626415074b626d2f37d3c78aef0f6ccb628db434ee65a35ea127"); + assertStringsEqual(accUrl, "https://explorer.multiversx.com/accounts/erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assertStringsEqual(id, "elrond"); + assertStringsEqual(name, "MultiversX"); +} diff --git a/tests/chains/MultiversX/TestAccounts.h b/tests/chains/MultiversX/TestAccounts.h new file mode 100644 index 00000000000..3026c9895ef --- /dev/null +++ b/tests/chains/MultiversX/TestAccounts.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +namespace TW::MultiversX::tests { + +// Well-known accounts on Testnet & Devnet, +// https://github.com/multiversx/mx-sdk-testwallets: +const auto ALICE_BECH32 = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; +const auto ALICE_PUBKEY_HEX = "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1"; +const auto ALICE_SEED_HEX = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"; +const auto BOB_BECH32 = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"; +const auto BOB_PUBKEY_HEX = "8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8"; +const auto BOB_SEED_HEX = "b8ca6f8203fb4b545a8e83c5384da033c415db155b53fb5b8eba7ff5a039d639"; +const auto CAROL_BECH32 = "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"; + +} // namespace TW::MultiversX::tests diff --git a/tests/chains/MultiversX/TransactionCompilerTests.cpp b/tests/chains/MultiversX/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..438add4c4c6 --- /dev/null +++ b/tests/chains/MultiversX/TransactionCompilerTests.cpp @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "TransactionCompiler.h" +#include "proto/MultiversX.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include +#include + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TestAccounts.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::MultiversX::tests { + +TEST(MultiversXCompiler, CompileGenericActionWithSignatures) { + // txHash 2d3d69707de60e93868a417353c8ecbc6b717e09e384f1a27100287067a5f970 on testnet + /// Step 1: Prepare transaction input (protobuf) + auto coin = TWCoinTypeMultiversX; + auto input = TW::MultiversX::Proto::SigningInput(); + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(2383); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->set_value("1"); + input.mutable_generic_action()->set_data("foo"); + input.mutable_generic_action()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(11100000); + input.set_chain_id("T"); + auto inputString = input.SerializeAsString(); + auto localInputData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, localInputData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = data(preSigningOutput.data()); + auto preImageHash = data(preSigningOutput.data_hash()); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + const PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + Data signature = parse_hex("4f0eb7dca9177f1849bc98b856ab4b3238a666abb3369b4fc0faba429b5c91c46b06893e841a8f411aa199c78cc456514abe39948108baf83a7be0b3fae9d70a"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, preImageHash)); + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, localInputData, {signature}, {publicKey.bytes}); + const auto ExpectedTx = "7b226e6f6e6365223a323338332c2276616c7565223a2231222c227265636569766572223a22657264317370796176773039353676713638786a38793474656e6a7071327764356139703263366a3867737a377a7479726e7078727275717a7536366a78222c2273656e646572223a2265726431717975357774686c647a72387778356339756367386b6a616767306a6673353373386e72337a707a336879706566736464387373796372367468222c226761735072696365223a313030303030303030302c226761734c696d6974223a31313130303030302c2264617461223a225a6d3976222c22636861696e4944223a2254222c2276657273696f6e223a312c227369676e6174757265223a223466306562376463613931373766313834396263393862383536616234623332333861363636616262333336396234666330666162613432396235633931633436623036383933653834316138663431316161313939633738636334353635313461626533393934383130386261663833613762653062336661653964373061227d"; + + { + TW::MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::MultiversX::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(localInputData.data(), (int)localInputData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::MultiversX::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, localInputData, {signature, signature}, {publicKey.bytes}); + MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, localInputData, {}, {}); + MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +TEST(MultiversXCompiler, CompileEGLDTransferWithSignatures) { + // txHash a4dd60099bb2cd14b57f3feb54d868d64dfe1b74d8ad90d8bd0668b96ead13af on testnet + /// Step 1: Prepare transaction input (protobuf) + auto coin = TWCoinTypeMultiversX; + auto input = TW::MultiversX::Proto::SigningInput(); + input.mutable_egld_transfer()->mutable_accounts()->set_sender_nonce(2418); + input.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_egld_transfer()->set_amount("1"); + input.mutable_egld_transfer()->set_data("foo"); + input.mutable_egld_transfer()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(11100000); + input.set_chain_id("T"); + auto inputString = input.SerializeAsString(); + auto localInputData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, localInputData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = data(preSigningOutput.data()); + auto preImageHash = data(preSigningOutput.data_hash()); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + const PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + Data signature = parse_hex("e55ad0642c7d47806410c12b1c93eb6250ccb76f711bbf82c5963bf59b5cdfe291d8b083b75de526f20457eede0c8a1dacf65c2c0034d47560c3bab5319c4006"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, preImageHash)); + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, localInputData, {signature}, {publicKey.bytes}); + const auto ExpectedTx = "7b226e6f6e6365223a323431382c2276616c7565223a2231222c227265636569766572223a22657264317370796176773039353676713638786a38793474656e6a7071327764356139703263366a3867737a377a7479726e7078727275717a7536366a78222c2273656e646572223a2265726431717975357774686c647a72387778356339756367386b6a616767306a6673353373386e72337a707a336879706566736464387373796372367468222c226761735072696365223a313030303030303030302c226761734c696d6974223a31313130303030302c2264617461223a225a6d3976222c22636861696e4944223a2254222c2276657273696f6e223a312c227369676e6174757265223a226535356164303634326337643437383036343130633132623163393365623632353063636237366637313162626638326335393633626635396235636466653239316438623038336237356465353236663230343537656564653063386131646163663635633263303033346434373536306333626162353331396334303036227d"; + { + TW::MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::MultiversX::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(localInputData.data(), (int)localInputData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::MultiversX::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, localInputData, {signature, signature}, {publicKey.bytes}); + MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, localInputData, {}, {}); + MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +TEST(MultiversXCompiler, CompileESDTTransferWithSignatures) { + // txHash d399477c5d2784d55521fadb2692d447e3459c6a006b1720a3bd513c68dc6848 on testnet + /// Step 1: Prepare transaction input (protobuf) + auto coin = TWCoinTypeMultiversX; + auto input = TW::MultiversX::Proto::SigningInput(); + input.mutable_esdt_transfer()->mutable_accounts()->set_sender_nonce(2529); + input.mutable_esdt_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_esdt_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_esdt_transfer()->set_amount("100"); + input.mutable_esdt_transfer()->set_token_identifier("MBONDTEST-4ce053"); + input.mutable_esdt_transfer()->set_version(1); + input.set_gas_price(1000000000); + input.set_gas_limit(11100000); + input.set_chain_id("T"); + auto inputString = input.SerializeAsString(); + auto localInputData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, localInputData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = data(preSigningOutput.data()); + auto preImageHash = data(preSigningOutput.data_hash()); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + const PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + Data signature = parse_hex("99314010bca2251e52f3a8c2efae2f02b81cb27c83edbaf553e7fb771ffbe69e99ac6304bdc8477ff6727e6e6a47b3d5e17c5537859e21d06a81ec8d632ad100"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, preImageHash)); + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, localInputData, {signature}, {publicKey.bytes}); + const auto ExpectedTx = "7b226e6f6e6365223a323532392c2276616c7565223a2230222c227265636569766572223a22657264317370796176773039353676713638786a38793474656e6a7071327764356139703263366a3867737a377a7479726e7078727275717a7536366a78222c2273656e646572223a2265726431717975357774686c647a72387778356339756367386b6a616767306a6673353373386e72337a707a336879706566736464387373796372367468222c226761735072696365223a313030303030303030302c226761734c696d6974223a31313130303030302c2264617461223a2252564e45564652795957357a5a6d56795144526b4e4449305a6a526c4e4451314e4451314e544d314e444a6b4d7a51324d7a59314d7a417a4e544d7a51445930222c22636861696e4944223a2254222c2276657273696f6e223a312c227369676e6174757265223a223939333134303130626361323235316535326633613863326566616532663032623831636232376338336564626166353533653766623737316666626536396539396163363330346264633834373766663637323765366536613437623364356531376335353337383539653231643036613831656338643633326164313030227d"; + + { + TW::MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::MultiversX::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(localInputData.data(), (int)localInputData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::MultiversX::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, localInputData, {signature, signature}, {publicKey.bytes}); + MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, localInputData, {}, {}); + MultiversX::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +} // namespace TW::MultiversX::tests diff --git a/tests/chains/MultiversX/TransactionFactoryTests.cpp b/tests/chains/MultiversX/TransactionFactoryTests.cpp new file mode 100644 index 00000000000..6b082245973 --- /dev/null +++ b/tests/chains/MultiversX/TransactionFactoryTests.cpp @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "boost/format.hpp" +#include +#include + +#include "MultiversX/TransactionFactory.h" +#include "TestAccounts.h" + +namespace TW::MultiversX::tests { + +TEST(MultiversXTransactionFactory, fromEGLDTransfer) { + auto input = Proto::SigningInput(); + input.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_egld_transfer()->set_amount("1000000000000000000"); + + TransactionFactory factory; + Transaction transaction = factory.fromEGLDTransfer(input); + + ASSERT_EQ(ALICE_BECH32, transaction.sender); + ASSERT_EQ(BOB_BECH32, transaction.receiver); + ASSERT_EQ("", transaction.data); + ASSERT_EQ("1000000000000000000", transaction.value); + ASSERT_EQ(50000ul, transaction.gasLimit); + ASSERT_EQ(1000000000ul, transaction.gasPrice); + ASSERT_EQ("1", transaction.chainID); + ASSERT_EQ(2ul, transaction.version); +} + +TEST(MultiversXTransactionFactory, fromESDTTransfer) { + auto input = Proto::SigningInput(); + input.mutable_esdt_transfer()->mutable_accounts()->set_sender_nonce(7); + input.mutable_esdt_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_esdt_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_esdt_transfer()->set_token_identifier("MYTOKEN-1234"); + input.mutable_esdt_transfer()->set_amount("10000000000000"); + + TransactionFactory factory; + Transaction transaction = factory.fromESDTTransfer(input); + + ASSERT_EQ(ALICE_BECH32, transaction.sender); + ASSERT_EQ(BOB_BECH32, transaction.receiver); + ASSERT_EQ("ESDTTransfer@4d59544f4b454e2d31323334@09184e72a000", transaction.data); + ASSERT_EQ("0", transaction.value); + ASSERT_EQ(425000ul, transaction.gasLimit); + ASSERT_EQ(1000000000ul, transaction.gasPrice); + ASSERT_EQ("1", transaction.chainID); + ASSERT_EQ(2ul, transaction.version); +} + +TEST(MultiversXTransactionFactory, fromESDTNFTTransfer) { + auto input = Proto::SigningInput(); + input.mutable_esdtnft_transfer()->mutable_accounts()->set_sender_nonce(7); + input.mutable_esdtnft_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_esdtnft_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_esdtnft_transfer()->set_token_collection("LKMEX-aab910"); + input.mutable_esdtnft_transfer()->set_token_nonce(4); + input.mutable_esdtnft_transfer()->set_amount("184300000000000000"); + + TransactionFactory factory; + Transaction transaction = factory.fromESDTNFTTransfer(input); + + ASSERT_EQ(ALICE_BECH32, transaction.sender); + ASSERT_EQ(ALICE_BECH32, transaction.receiver); + ASSERT_EQ("ESDTNFTTransfer@4c4b4d45582d616162393130@04@028ec3dfa01ac000@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", transaction.data); + ASSERT_EQ("0", transaction.value); + ASSERT_EQ(937500ul, transaction.gasLimit); + ASSERT_EQ(1000000000ul, transaction.gasPrice); + ASSERT_EQ("1", transaction.chainID); + ASSERT_EQ(2ul, transaction.version); +} + +TEST(MultiversXTransactionFactory, createTransfersWithProvidedConfig) { + TransactionFactoryConfig config; + + // Set dummy values: + config.setChainId("T"); + config.setMinGasPrice(1500000000); + config.setMinGasLimit(60000); + config.setGasPerDataByte(2000); + config.setGasCostESDTTransfer(300000); + config.setGasCostESDTNFTTransfer(300000); + config.setAdditionalGasForESDTTransfer(100000); + config.setAdditionalGasForESDTNFTTransfer(500000); + + Proto::SigningInput signingInputWithEGLDTransfer; + signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithEGLDTransfer.mutable_egld_transfer()->set_amount("0"); + + Proto::SigningInput signingInputWithESDTTransfer; + signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithESDTTransfer.mutable_esdt_transfer()->set_token_identifier("MYTOKEN-1234"); + signingInputWithESDTTransfer.mutable_esdt_transfer()->set_amount("10000000000000"); + + Proto::SigningInput signingInputWithESDTNFTTransfer; + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_token_collection("LKMEX-aab910"); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_token_nonce(4); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_amount("184300000000000000"); + + TransactionFactory factory(config); + Transaction tx1 = factory.fromEGLDTransfer(signingInputWithEGLDTransfer); + Transaction tx2 = factory.fromESDTTransfer(signingInputWithESDTTransfer); + Transaction tx3 = factory.fromESDTNFTTransfer(signingInputWithESDTNFTTransfer); + + ASSERT_EQ(60000ul, tx1.gasLimit); + ASSERT_EQ(1500000000ul, tx1.gasPrice); + ASSERT_EQ("T", tx1.chainID); + + ASSERT_EQ(560000ul, tx2.gasLimit); + ASSERT_EQ(1500000000ul, tx2.gasPrice); + ASSERT_EQ("T", tx2.chainID); + + ASSERT_EQ(1110000ul, tx3.gasLimit); + ASSERT_EQ(1500000000ul, tx3.gasPrice); + ASSERT_EQ("T", tx3.chainID); +} + +TEST(MultiversXTransactionFactory, createTransfersWithOverriddenNetworkParameters) { + Proto::SigningInput signingInputWithEGLDTransfer; + signingInputWithEGLDTransfer.set_gas_limit(50500); + signingInputWithEGLDTransfer.set_gas_price(1000000001); + signingInputWithEGLDTransfer.set_chain_id("A"); + + Proto::SigningInput signingInputWithESDTTransfer; + signingInputWithESDTTransfer.set_gas_limit(5000000); + signingInputWithESDTTransfer.set_gas_price(1000000002); + signingInputWithESDTTransfer.set_chain_id("B"); + + Proto::SigningInput signingInputWithESDTNFTTransfer; + signingInputWithESDTNFTTransfer.set_gas_limit(10000000); + signingInputWithESDTNFTTransfer.set_gas_price(1000000003); + signingInputWithESDTNFTTransfer.set_chain_id("C"); + + TransactionFactory factory; + Transaction tx1 = factory.fromEGLDTransfer(signingInputWithEGLDTransfer); + Transaction tx2 = factory.fromESDTTransfer(signingInputWithESDTTransfer); + Transaction tx3 = factory.fromESDTNFTTransfer(signingInputWithESDTNFTTransfer); + + ASSERT_EQ(50500ul, tx1.gasLimit); + ASSERT_EQ(1000000001ul, tx1.gasPrice); + ASSERT_EQ("A", tx1.chainID); + + ASSERT_EQ(5000000ul, tx2.gasLimit); + ASSERT_EQ(1000000002ul, tx2.gasPrice); + ASSERT_EQ("B", tx2.chainID); + + ASSERT_EQ(10000000ul, tx3.gasLimit); + ASSERT_EQ(1000000003ul, tx3.gasPrice); + ASSERT_EQ("C", tx3.chainID); +} + +TEST(MultiversXTransactionFactory, create) { + Proto::SigningInput signingInputWithGenericAction; + signingInputWithGenericAction.mutable_generic_action()->set_data("hello"); + + Proto::SigningInput signingInputWithEGLDTransfer; + signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithEGLDTransfer.mutable_egld_transfer()->set_amount("1"); + + Proto::SigningInput signingInputWithESDTTransfer; + signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithESDTTransfer.mutable_esdt_transfer()->set_token_identifier("MYTOKEN-1234"); + signingInputWithESDTTransfer.mutable_esdt_transfer()->set_amount("10000000000000"); + + Proto::SigningInput signingInputWithESDTNFTTransfer; + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_token_collection("LKMEX-aab910"); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_token_nonce(4); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_amount("184300000000000000"); + + TransactionFactory factory; + Transaction tx1 = factory.create(signingInputWithGenericAction); + Transaction tx2 = factory.create(signingInputWithEGLDTransfer); + Transaction tx3 = factory.create(signingInputWithESDTTransfer); + Transaction tx4 = factory.create(signingInputWithESDTNFTTransfer); + + ASSERT_EQ("hello", tx1.data); + ASSERT_EQ("1", tx2.value); + ASSERT_EQ("ESDTTransfer@4d59544f4b454e2d31323334@09184e72a000", tx3.data); + ASSERT_EQ("ESDTNFTTransfer@4c4b4d45582d616162393130@04@028ec3dfa01ac000@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", tx4.data); + + ASSERT_EQ(TransactionOptions::Default, tx1.options); + ASSERT_EQ(TransactionOptions::Default, tx2.options); + ASSERT_EQ(TransactionOptions::Default, tx3.options); + ASSERT_EQ(TransactionOptions::Default, tx4.options); +} + +TEST(MultiversXTransactionFactory, createWithGuardian) { + Proto::SigningInput signingInputWithGenericAction; + signingInputWithGenericAction.mutable_generic_action()->set_data("hello"); + // For generic actions, the caller is responsible with providing the appropriate options. + signingInputWithGenericAction.mutable_generic_action()->set_options(TransactionOptions::Guarded); + signingInputWithGenericAction.mutable_generic_action()->mutable_accounts()->set_guardian(CAROL_BECH32); + + Proto::SigningInput signingInputWithEGLDTransfer; + signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_guardian(CAROL_BECH32); + signingInputWithEGLDTransfer.mutable_egld_transfer()->set_amount("1"); + + Proto::SigningInput signingInputWithESDTTransfer; + signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_guardian(CAROL_BECH32); + signingInputWithESDTTransfer.mutable_esdt_transfer()->set_token_identifier("MYTOKEN-1234"); + signingInputWithESDTTransfer.mutable_esdt_transfer()->set_amount("10000000000000"); + + Proto::SigningInput signingInputWithESDTNFTTransfer; + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_guardian(CAROL_BECH32); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_token_collection("LKMEX-aab910"); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_token_nonce(4); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_amount("184300000000000000"); + + TransactionFactory factory; + Transaction tx1 = factory.create(signingInputWithGenericAction); + Transaction tx2 = factory.create(signingInputWithEGLDTransfer); + Transaction tx3 = factory.create(signingInputWithESDTTransfer); + Transaction tx4 = factory.create(signingInputWithESDTNFTTransfer); + + ASSERT_EQ("hello", tx1.data); + ASSERT_EQ("1", tx2.value); + ASSERT_EQ("ESDTTransfer@4d59544f4b454e2d31323334@09184e72a000", tx3.data); + ASSERT_EQ("ESDTNFTTransfer@4c4b4d45582d616162393130@04@028ec3dfa01ac000@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", tx4.data); + + ASSERT_EQ(TransactionOptions::Guarded, tx1.options); + ASSERT_EQ(TransactionOptions::Guarded, tx2.options); + ASSERT_EQ(TransactionOptions::Guarded, tx3.options); + ASSERT_EQ(TransactionOptions::Guarded, tx4.options); +} + +} // namespace TW::MultiversX::tests diff --git a/tests/chains/NEAR/AccountTests.cpp b/tests/chains/NEAR/AccountTests.cpp new file mode 100644 index 00000000000..880794bd506 --- /dev/null +++ b/tests/chains/NEAR/AccountTests.cpp @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "NEAR/Account.h" +#include "HexCoding.h" +#include + +namespace TW::NEAR::tests { + +TEST(NEARAccount, Validation) { + ASSERT_FALSE(Account::isValid("a")); + ASSERT_FALSE(Account::isValid("!?:")); + ASSERT_FALSE(Account::isValid("11111111111111111111111111111111222222222222222222222222222222223")); + + ASSERT_TRUE(Account::isValid("9902c136629fc630416e50d4f2fef6aff867ea7e.lockup.near")); + ASSERT_TRUE(Account::isValid("app_1.alice.near")); + ASSERT_TRUE(Account::isValid("test-trust.vlad.near")); + ASSERT_TRUE(Account::isValid("deadbeef")); +} + +} // namespace TW::NEAR::tests diff --git a/tests/chains/NEAR/AddressTests.cpp b/tests/chains/NEAR/AddressTests.cpp new file mode 100644 index 00000000000..7ae6b77a671 --- /dev/null +++ b/tests/chains/NEAR/AddressTests.cpp @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "NEAR/Address.h" +#include "Base58.h" +#include "PrivateKey.h" +#include + +#include + +namespace TW::NEAR::tests { + +TEST(NEARAddress, Validation) { + ASSERT_FALSE(Address::isValid("abc")); + ASSERT_FALSE(Address::isValid("65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjTF")); + ASSERT_FALSE(Address::isValid("EOS65QzSGJ579GPNKtZoZkChTzsxR4B48RCfiS82m2ymJR6VZCjT")); + ASSERT_FALSE(Address::isValid("NEAR2V8xUQn8gLZUd9ofzNbDz5ahVJwaQNHKoDSbysPA6GYfsJS4hbDPTSFzfrRKs8UsSkakdoC623ZYbfAJKYRbx4UJHjPoi9SdF7nh6ZM3ditbDYaXC9aan8VEtrEicpXsgnYdD91qEPfUL9jd1j7Uipz1jHR1SG3mTMHsZbE1JQyYJBB6sGRQ6GsLe5Yks5tuYuEU88cpu6rk8iHEL6hzXNhWSbg6MJLoaA6FdEddnBZjLpqM5fZahaLpL9HTPo8PpsCgoRKDEifakbHPRg3NSQnGR4vV3oonz1bisVL5gF1yfZTsH9VYTMJ6CiNqZ7Dk2fPu1WFn3Sp91e34ysyuddqTHeypsAgVtWBNP7bTTvomxVR9xwEH1bBZ83oqwnnyXwMNqi1aHGouyneYPNU363cjEiKHz2mXYBs3NFX5kB9NPqPBETGptFh71EHrbB1XQT2WTqCEeKmQ8RR28E82Ei2GsxSbVPLP8TDTk7XpHy5UtbHf1CS5bY8SkiSUfFPJnNabx2FeLtHTTdxWmxPRa2dYgFANQnjt6g4wKnGAorbaqeu3jHuUvbNeUuhCNuwWe176fhisoW4gxz2VrK2w4qNeMrgWkWt4N1PPGyfuF3bWigi8Kdek9c3HGCk1vuTs1okartuuibCyzahPDnGLYBLdSEVU4endvxBsMmfXSrhcXpxXtxMPih88HjGR69MMWrjYPrVJvhvbU2AYejpLE1QqCpbJ4pNesUet9zddS1QHE23TMmY1Gk2UhQV6SocFqeAgf1Wu17RjnZKF4p9MQNjXM2oaZvWL7ZEf46ZT9uizKPSrGfP5rqJ1JyYA7Mj7KsfYspf5HuFhLynfybQSwerqLKJk8s4EGA8KkeekGJTspBGnxtgqQThPmqfy9hKqP5JrhEcBC68nhC7kokhC8qgdERUL2LzAtguQG1ZrfhcQDdhePExiFa9QKNuzXzPfzuTwBAByrNSW54nH5wkWXUKuZ1fmV7XrAEzNJK2ozRgbxsnxBdqmkZfpyzzC5zGaH4Eisw4e8o7jYmtK8udp4vrxxQMzsHkKa9Xpgn2tKmyfMQPk9afSrV9GJK7HoMwWPBMaPEm4DgBRWYksSNgQQuLQm4SeLzmeyZpGycida5MfnTyB8jH8jMPEYTonxE7bgFwNDBkZxwDa4FNAkBhbKELp3inYsvHWJ18QG7XbLNa9F74Xug3wFzoXC7Z34xv8rnW2Quj9CPfvkMwz3qF3E1XJnm3Adamg799yEjahcbR1bKHSVLTkThoNMkF2z8D1XswB7ZiMqV7TCPVYu94GrWxbo6Mr8Jhs76eH9EvXapqCdMEiC62zycDWvhruF44f4F9E1WVxpdXQAbhCXtwqHzSJV18SFGjUmF91AU9oevKP5EW82jY7JP2")); + ASSERT_FALSE(Address::isValid("NEAR5y2")); + ASSERT_FALSE(Address::isValid("NEAR2fk7ax")); + ASSERT_FALSE(Address::isValid("NEAR2758Nk7CMUcxTwXdjVdSxNEidiZQWMZN3USJzj76q5ia3v2v")); + ASSERT_FALSE(Address::isValid("NEAR2758Nk7CMUcxTwXdjVdSxNEidiZQWMZN3USJzj76q5ia3v2v2v3")); + ASSERT_FALSE(Address::isValid("917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb7786")); + ASSERT_FALSE(Address::isValid("917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d1f")); + + ASSERT_TRUE(Address::isValid("NEAR2758Nk7CMUcxTwXdjVdSxNEidiZQWMZN3USJzj76q5ia3v2v2v")); + ASSERT_TRUE(Address::isValid("917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d")); +} + +TEST(NEARAddress, FromString) { + ASSERT_EQ( + Address("NEAR2758Nk7CMUcxTwXdjVdSxNEidiZQWMZN3USJzj76q5ia3v2v2v").string(), + "917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d"); + ASSERT_EQ( + Address("9685af3fe2dc231e5069ccff8ec6950eb961d42ebb9116a8ab9c0d38f9e45249").string(), + "9685af3fe2dc231e5069ccff8ec6950eb961d42ebb9116a8ab9c0d38f9e45249"); +} + +TEST(NEARAddress, FromPrivateKey) { + auto fullKey = Base58::decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + auto key = PrivateKey(Data(fullKey.begin(), fullKey.begin() + 32)); + auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); + auto address = Address(publicKey); + + ASSERT_EQ(address.string(), "917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d"); +} + +} // namespace TW::NEAR::tests diff --git a/tests/chains/NEAR/SerializationTests.cpp b/tests/chains/NEAR/SerializationTests.cpp new file mode 100644 index 00000000000..f534edd48f2 --- /dev/null +++ b/tests/chains/NEAR/SerializationTests.cpp @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Base58.h" +#include "proto/NEAR.pb.h" +#include "NEAR/Serialization.h" + +#include +#include + +namespace TW::NEAR { + +TEST(NEARSerialization, SerializeTransferTransaction) { + auto publicKey = Base58::decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + auto& transfer = *input.mutable_actions(0)->mutable_transfer(); + Data deposit(16, 0); + deposit[0] = 1; + transfer.set_deposit(deposit.data(), deposit.size()); + + auto blockHash = Base58::decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000301000000000000000000000000000000"); +} + +TEST(NEARSerialization, SerializeFunctionCallTransaction) { + auto publicKey = Base58::decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + auto& functionCall = *input.mutable_actions(0)->mutable_function_call(); + + functionCall.set_method_name("qqq"); + functionCall.set_gas(1000); + + Data deposit(16, 0); + deposit[0] = 1; + functionCall.set_deposit(deposit.data(), deposit.size()); + + Data args(3, 0); + args[0] = 1; + args[1] = 2; + args[2] = 3; + functionCall.set_args(args.data(), args.size()); + + auto blockHash = Base58::decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000020300000071717103000000010203e80300000000000001000000000000000000000000000000"); +} + +TEST(NEARSerialization, SerializeStakeTransaction) { + auto publicKey = Base58::decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + auto& stake = *input.mutable_actions(0)->mutable_stake(); + Data amount(16, 0); + amount[0] = 1; + stake.set_stake(amount.data(), amount.size()); + + auto& pKey = *stake.mutable_public_key(); + pKey.set_data(publicKey.data(), publicKey.size()); + pKey.set_key_type(0); + + auto blockHash = Base58::decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000040100000000000000000000000000000000917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d"); +} + +TEST(NEARSerialization, SerializeStakeTransaction2) { + auto publicKey = Base58::decode("C2P7YcEmBv31vtCHLBcESteN4Yi4vSCkXEXMTANyB649"); + + auto input = Proto::SigningInput(); + input.set_signer_id("vdx.testnet"); + input.set_nonce(93128451000005); + input.set_receiver_id("vdx.testnet"); + + input.add_actions(); + auto& stake = *input.mutable_actions(0)->mutable_stake(); + // 2490000000000000000000000000 + auto amount = parse_hex("000000fa4f3f757902ae0b0800000000"); // little endian + stake.set_stake(amount.data(), amount.size()); + + auto& pKey = *stake.mutable_public_key(); + pKey.set_data(publicKey.data(), publicKey.size()); + pKey.set_key_type(0); + + auto blockHash = Base58::decode("ByDnm7c25npQXwNUX5yivbYbpjFcNuNumF6BJjaK3vhJ"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::decode("5Cfk7QBnmDxxFxQk75FFq4ADrQS9gxHKe6vtuGH6JCCm8WV8aRPEGVqp579JHNmmHMUt49gkCVcH2t7NRnh2v7Qu"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "0b0000007664782e746573746e657400a3cb23dbb9810abd4a6804328eec47a17236383b5c234cae903b064e9dc426dac5863d28b35400000b0000007664782e746573746e6574a2fbdae8a769c636d109952e4fe760b03688e629933cbf693aedfd97a470c7a50100000004000000fa4f3f757902ae0b080000000000a3cb23dbb9810abd4a6804328eec47a17236383b5c234cae903b064e9dc426da"); +} + +TEST(NEARSerialization, SerializeAddKeyFunctionCallTransaction) { + auto publicKey = Base58::decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + auto& addKey = *input.mutable_actions(0)->mutable_add_key(); + + auto& pKey = *addKey.mutable_public_key(); + pKey.set_data(publicKey.data(), publicKey.size()); + pKey.set_key_type(0); + + auto& accessKey = *addKey.mutable_access_key(); + accessKey.set_nonce(0); + auto& functionCallPermission = *accessKey.mutable_function_call(); + functionCallPermission.set_receiver_id("zzz"); + functionCallPermission.add_method_names("www"); + + auto blockHash = Base58::decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000500917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d00000000000000000000030000007a7a7a0100000003000000777777"); +} + +TEST(NEARSerialization, SerializeAddKeyFullAccessTransaction) { + auto publicKey = Base58::decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + auto& addKey = *input.mutable_actions(0)->mutable_add_key(); + + auto& pKey = *addKey.mutable_public_key(); + pKey.set_data(publicKey.data(), publicKey.size()); + pKey.set_key_type(0); + + auto& accessKey = *addKey.mutable_access_key(); + accessKey.set_nonce(0); + + accessKey.mutable_full_access(); + + auto blockHash = Base58::decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000500917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d000000000000000001"); +} + +TEST(NEARSerialization, SerializeDeleteKeyTransaction) { + auto publicKey = Base58::decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + auto& deleteKey = *input.mutable_actions(0)->mutable_delete_key(); + + auto& pKey = *deleteKey.mutable_public_key(); + pKey.set_data(publicKey.data(), publicKey.size()); + pKey.set_key_type(0); + + auto blockHash = Base58::decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000600917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d"); +} + +TEST(NEARSerialization, SerializeCreateAccountTransaction) { + auto publicKey = Base58::decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + input.mutable_actions(0)->mutable_create_account(); + + auto blockHash = Base58::decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef60100000000"); +} + +TEST(NEARSerialization, SerializeDeleteAccountTransaction) { + auto publicKey = Base58::decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + auto& deleteAccount = *input.mutable_actions(0)->mutable_delete_account(); + deleteAccount.set_beneficiary_id("123"); + + auto blockHash = Base58::decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto serialized = transactionData(input); + auto serializedHex = hex(serialized); + + ASSERT_EQ(serializedHex, "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000703000000313233"); +} + +} diff --git a/tests/chains/NEAR/SignerTests.cpp b/tests/chains/NEAR/SignerTests.cpp new file mode 100644 index 00000000000..16659e4393c --- /dev/null +++ b/tests/chains/NEAR/SignerTests.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base64.h" +#include "Base58.h" +#include "HexCoding.h" +#include "proto/NEAR.pb.h" +#include "NEAR/Signer.h" + +#include +#include + +namespace TW::NEAR { + +TEST(NEARSigner, SignTx) { + auto publicKey = Base58::decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + + auto input = Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + + input.add_actions(); + auto& transfer = *input.mutable_actions(0)->mutable_transfer(); + Data deposit(16, 0); + deposit[0] = 1; + // uint128_t / little endian byte order + transfer.set_deposit(deposit.data(), deposit.size()); + + auto blockHash = Base58::decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto privateKey = Base58::decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + input.set_private_key(privateKey.data(), 32); + + auto output = Signer::sign(std::move(input)); + + auto signed_transaction = output.signed_transaction(); + auto outputInBase64 = Base64::encode(Data(signed_transaction.begin(), signed_transaction.end())); + + ASSERT_EQ(outputInBase64, "CQAAAHRlc3QubmVhcgCRez0mjUtY9/7BsVC9aNab4+5dTMOYVeNBU4Rlu3eGDQEAAAAAAAAADQAAAHdoYXRldmVyLm5lYXIPpHP9JpAd8pa+atxMxN800EDvokNSJLaYaRDmMML+9gEAAAADAQAAAAAAAAAAAAAAAAAAAACWmoMzIYbul1Xkg5MlUlgG4Ymj0tK7S0dg6URD6X4cTyLe7vAFmo6XExAO2m4ZFE2n6KDvflObIHCLodjQIb0B"); + ASSERT_EQ(hex(output.hash()), "eea6e680f3ea51a7f667e9a801d0bfadf66e03d41ed54975b3c6006351461b32"); +} + +} diff --git a/tests/chains/NEAR/TWAnySignerTests.cpp b/tests/chains/NEAR/TWAnySignerTests.cpp new file mode 100644 index 00000000000..7bd363fc63e --- /dev/null +++ b/tests/chains/NEAR/TWAnySignerTests.cpp @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/NEAR.pb.h" +#include "TestUtilities.h" +#include +#include "Base58.h" +#include "Base64.h" +#include + +namespace TW::NEAR { + +TEST(TWAnySignerNEAR, SignTransfer) { + + auto privateKey = parse_hex("8737b99bf16fba78e1e753e23ba00c4b5423ac9c45d9b9caae9a519434786568"); + auto blockHash = parse_hex("0fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6"); + // uint128_t / little endian byte order + auto deposit = parse_hex("01000000000000000000000000000000"); + + Proto::SigningInput input; + input.set_signer_id("test.near"); + input.set_nonce(1); + input.set_receiver_id("whatever.near"); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto& action = *input.add_actions(); + auto& transfer = *action.mutable_transfer(); + transfer.set_deposit(deposit.data(), deposit.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNEAR); + + ASSERT_EQ(hex(output.signed_transaction()), "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000030100000000000000000000000000000000969a83332186ee9755e4839325525806e189a3d2d2bb4b4760e94443e97e1c4f22deeef0059a8e9713100eda6e19144da7e8a0ef7e539b20708ba1d8d021bd01"); + ASSERT_EQ(hex(output.hash()), "eea6e680f3ea51a7f667e9a801d0bfadf66e03d41ed54975b3c6006351461b32"); +} + +TEST(TWAnySignerNEAR, SignStake) { + + auto privateKey = parse_hex("d22149327ceb8e86f70962be0c7293f8308d85d0cbea2cc24e47c3033da7440f"); + auto publicKey = parse_hex("a3cb23dbb9810abd4a6804328eec47a17236383b5c234cae903b064e9dc426da"); + auto blockHash = parse_hex("a2fbdae8a769c636d109952e4fe760b03688e629933cbf693aedfd97a470c7a5"); + + // 2490000000000000000000000000 + auto amount = parse_hex("000000fa4f3f757902ae0b0800000000"); // little endian + + Proto::SigningInput input; + input.set_signer_id("vdx.testnet"); + input.set_nonce(93128451000005); + input.set_receiver_id("vdx.testnet"); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto& action = *input.add_actions(); + auto& stake = *action.mutable_stake(); + stake.set_stake(amount.data(), amount.size()); + + auto& pubkey = *stake.mutable_public_key(); + pubkey.set_data(publicKey.data(), publicKey.size()); + pubkey.set_key_type(0); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNEAR); + + ASSERT_EQ(hex(output.signed_transaction()), "0b0000007664782e746573746e657400a3cb23dbb9810abd4a6804328eec47a17236383b5c234cae903b064e9dc426dac5863d28b35400000b0000007664782e746573746e6574a2fbdae8a769c636d109952e4fe760b03688e629933cbf693aedfd97a470c7a50100000004000000fa4f3f757902ae0b080000000000a3cb23dbb9810abd4a6804328eec47a17236383b5c234cae903b064e9dc426da0011fdbc234d4ce470ec7f2ac5e4d3d8f8fe1525f83e9a2425e7000aea52f7260ff4f5191beaa1a5ac29256e68c6acd368ada0d06ed033e9a204ee119f5ef1b104"); + ASSERT_EQ(hex(output.hash()), "c8aedbf75fcaa9b663a3959d27f1deae809e1923460791471e5219eafecc4ba8"); +} + +TEST(TWAnySignerNEAR, SignStakeMainnetReplication) { + auto privateKey = Base58::decode("3BPZ9Qu7CviWD4CeKy3DYbNc4suyuBJYnjhVT2oTRCrfb4CQPiTK5tFVdg8Z3ijozxWoxxt9Y1kwkwPntrcc3dom"); + auto blockHash = parse_hex("e78680996127b7a0f3f2343502e442f24366cba5f79cb72f8bc6d0debb26ce24"); + + // 0.1 with 24 decimal precision in big endian + auto amount = parse_hex("000080f64ae1c7022d15000000000000"); + + Proto::SigningInput input; + input.set_signer_id("b8d5df25047841365008f30fb6b30dd820e9a84d869f05623d114e96831f2fbf"); + input.set_nonce(77701544000004); + input.set_receiver_id("avado.poolv1.near"); + input.set_private_key(privateKey.data(), 32); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto& action = *input.add_actions(); + auto& call = *action.mutable_function_call(); + call.set_method_name("deposit_and_stake"); + call.set_args("{}"); + call.set_gas(125000000000000); + call.set_deposit(amount.data(), amount.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNEAR); + + // https://nearblocks.io/txns/kd7ajFw1CfXB8LiJXvhz5NDS7QpQXkuQraAbhb5MMMq + ASSERT_EQ(Base58::encode(data(output.hash())), "kd7ajFw1CfXB8LiJXvhz5NDS7QpQXkuQraAbhb5MMMq"); + ASSERT_EQ(Base64::encode(data(output.signed_transaction())), "QAAAAGI4ZDVkZjI1MDQ3ODQxMzY1MDA4ZjMwZmI2YjMwZGQ4MjBlOWE4NGQ4NjlmMDU2MjNkMTE0ZTk2ODMxZjJmYmYAzgCT6NK76nb1mB7pToefgkGUHfUe5BKvvr3gW/nq+MgEuu1Mq0YAABEAAABhdmFkby5wb29sdjEubmVhcueGgJlhJ7eg8/I0NQLkQvJDZsul95y3L4vG0N67Js4kAQAAAAIRAAAAZGVwb3NpdF9hbmRfc3Rha2UCAAAAe30A0JjUr3EAAAAAgPZK4ccCLRUAAAAAAAAALNrorr8qTL6u1nlxLpuPa45nFdYmjU96i7CmJP08mVHVzHUaw/bGN30Z3u3o1F2o2yefCBNqO9Ogn9fM25NGCg=="); +} + +TEST(TWAnySignerNEAR, SignUnstakeMainnetReplication) { + auto privateKey = Base58::decode("3BPZ9Qu7CviWD4CeKy3DYbNc4suyuBJYnjhVT2oTRCrfb4CQPiTK5tFVdg8Z3ijozxWoxxt9Y1kwkwPntrcc3dom"); + auto blockHash = Base58::decode("CehJc9uZhqE2m17ZrkqcAog4mxSz6JSvYv1JEK1iBsX9"); + + auto amount = parse_hex("00000000000000000000000000000000"); + + Proto::SigningInput input; + input.set_signer_id("b8d5df25047841365008f30fb6b30dd820e9a84d869f05623d114e96831f2fbf"); + input.set_nonce(77701544000006); + input.set_receiver_id("avado.poolv1.near"); + input.set_private_key(privateKey.data(), 32); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto& action = *input.add_actions(); + auto& call = *action.mutable_function_call(); + call.set_method_name("unstake_all"); + call.set_args("{}"); + call.set_gas(125000000000000); + call.set_deposit(amount.data(), amount.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNEAR); + + // https://nearblocks.io/txns/DH6QAX3TkY6XtkteorvKBoGT5hA5ADkURZdzrbbKRs8P + ASSERT_EQ(Base58::encode(data(output.hash())), "DH6QAX3TkY6XtkteorvKBoGT5hA5ADkURZdzrbbKRs8P"); + ASSERT_EQ(Base64::encode(data(output.signed_transaction())), "QAAAAGI4ZDVkZjI1MDQ3ODQxMzY1MDA4ZjMwZmI2YjMwZGQ4MjBlOWE4NGQ4NjlmMDU2MjNkMTE0ZTk2ODMxZjJmYmYAzgCT6NK76nb1mB7pToefgkGUHfUe5BKvvr3gW/nq+MgGuu1Mq0YAABEAAABhdmFkby5wb29sdjEubmVhcq0YnhRlt+TTtagkoy0qKn56zAfGhE+jkTJW6PR5k5r8AQAAAAILAAAAdW5zdGFrZV9hbGwCAAAAe30A0JjUr3EAAAAAAAAAAAAAAAAAAAAAAAAABaFP0EkfJU3VQZ4QAiTwq9ebWDJ7jx7TxbA+VGH4hwKX3gWnmDHVve+LK7/UbbffjF/y8vn0KrPxdh3ONAG0Ag=="); +} + +/// Implements NEP-141: +/// https://nomicon.io/Standards/Tokens/FungibleToken/Core +/// +/// Successfully broadcasted tx: +/// https://nearblocks.io/txns/ABQY6nfLdNrRVynHYNjYkfUM6Up5pDHHpuhRJe6FCMRu +TEST(TWAnySignerNEAR, SignTokenTransfer) { + auto privateKey = parse_hex("77006e227658c18da47546413926a26b839204b1b19e807c4a13d994d661c72e"); + + auto blockHash = Base58::decode("2dQBYs8XjprLLgtH7eVsJ3e58A5QuRcbuqFisSk9fFWQ"); + + // Deposit should be 1 yocto NEAR for security purposes. + auto deposit = parse_hex("01000000000000000000000000000000"); + + Proto::SigningInput input; + input.set_signer_id("105396228ac2e0ef144b93bcc5322fca1167d524422bb73d17440d35c714a58f"); + input.set_nonce(93062928000003); + input.set_receiver_id("token.paras.near"); + input.set_private_key(privateKey.data(), 32); + input.set_block_hash(blockHash.data(), blockHash.size()); + + auto& action = *input.add_actions(); + auto& tokenTransfer = *action.mutable_token_transfer(); + tokenTransfer.set_token_amount("100000000000000000"); + tokenTransfer.set_receiver_id("c6d5e3e8f328436f595856a598239b691d3d136b24c05a4614f9e9716edc14fe"); + tokenTransfer.set_gas(15000000000000); + tokenTransfer.set_deposit(deposit.data(), deposit.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNEAR); + + ASSERT_EQ(Base58::encode(data(output.hash())), "ABQY6nfLdNrRVynHYNjYkfUM6Up5pDHHpuhRJe6FCMRu"); + ASSERT_EQ(Base64::encode(data(output.signed_transaction())), "QAAAADEwNTM5NjIyOGFjMmUwZWYxNDRiOTNiY2M1MzIyZmNhMTE2N2Q1MjQ0MjJiYjczZDE3NDQwZDM1YzcxNGE1OGYAEFOWIorC4O8US5O8xTIvyhFn1SRCK7c9F0QNNccUpY8D5MPmo1QAABAAAAB0b2tlbi5wYXJhcy5uZWFyGC7O0jXN2b4SH1XfMtNISEnU8XATKOhZwxx0pLLZqTEBAAAAAgsAAABmdF90cmFuc2ZlcnAAAAB7ImFtb3VudCI6IjEwMDAwMDAwMDAwMDAwMDAwMCIsInJlY2VpdmVyX2lkIjoiYzZkNWUzZThmMzI4NDM2ZjU5NTg1NmE1OTgyMzliNjkxZDNkMTM2YjI0YzA1YTQ2MTRmOWU5NzE2ZWRjMTRmZSJ9APCrdaQNAAABAAAAAAAAAAAAAAAAAAAAANUjO7fmnTebSNW9EcHHwYwPNlQJcReGWJfJUuxWzPDAGEeo4JTcLB8pLCkqxKKsI0NE1Szv2+GAt5mCBum5mQY="); +} + +} // namespace TW::NEAR diff --git a/tests/chains/NEAR/TWCoinTypeTests.cpp b/tests/chains/NEAR/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..b62f545841a --- /dev/null +++ b/tests/chains/NEAR/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWNEARCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNEAR)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("FPQAMaVnvFHNwNBJWnTttXfdJhp5FvMGGDJEesB8gvbL")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNEAR, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("test-trust.vlad.near")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNEAR, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNEAR)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNEAR)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNEAR), 24); + ASSERT_EQ(TWBlockchainNEAR, TWCoinTypeBlockchain(TWCoinTypeNEAR)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeNEAR)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeNEAR)); + assertStringsEqual(symbol, "NEAR"); + assertStringsEqual(txUrl, "https://nearblocks.io/txns/FPQAMaVnvFHNwNBJWnTttXfdJhp5FvMGGDJEesB8gvbL"); + assertStringsEqual(accUrl, "https://nearblocks.io/address/test-trust.vlad.near"); + assertStringsEqual(id, "near"); + assertStringsEqual(name, "NEAR"); +} diff --git a/tests/NEAR/TWNEARAccountTests.cpp b/tests/chains/NEAR/TWNEARAccountTests.cpp similarity index 75% rename from tests/NEAR/TWNEARAccountTests.cpp rename to tests/chains/NEAR/TWNEARAccountTests.cpp index c97ba7d5c0d..9f09189dfd7 100644 --- a/tests/NEAR/TWNEARAccountTests.cpp +++ b/tests/chains/NEAR/TWNEARAccountTests.cpp @@ -1,12 +1,10 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // // -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/NEAR/TransactionCompilerTests.cpp b/tests/chains/NEAR/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..705ba950804 --- /dev/null +++ b/tests/chains/NEAR/TransactionCompilerTests.cpp @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base58.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/NEAR.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(NEARCompiler, CompileWithSignatures) { + auto privateKeyBytes = Base58::decode( + "3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); + auto publicKey = Base58::decode("Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"); + const auto coin = TWCoinTypeNEAR; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::NEAR::Proto::SigningInput(); + input.set_signer_id("test.near"); + input.set_receiver_id("whatever.near"); + input.set_nonce(1); + auto blockHash = Base58::decode("244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_public_key(publicKey.data(), publicKey.size()); + + input.add_actions(); + auto& transfer = *input.mutable_actions(0)->mutable_transfer(); + Data deposit(16, 0); + deposit[0] = 1; + transfer.set_deposit(deposit.data(), deposit.size()); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "eea6e680f3ea51a7f667e9a801d0bfadf66e03d41ed54975b3c6006351461b32"); + auto signature = parse_hex("969a83332186ee9755e4839325525806e189a3d2d2bb4b4760e94443e97e1c4f22d" + "eeef0059a8e9713100eda6e19144da7e8a0ef7e539b20708ba1d8d021bd01"); + + /// Step 3: Compile transaction info + const auto tx = "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341" + "538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901d" + "f296be6adc4cc4df34d040efa2435224b6986910e630c2fef60100000003010000000000000000" + "0000000000000000969a83332186ee9755e4839325525806e189a3d2d2bb4b4760e94443e97e1c" + "4f22deeef0059a8e9713100eda6e19144da7e8a0ef7e539b20708ba1d8d021bd01"; + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKey}); + + { + TW::NEAR::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.signed_transaction()), tx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::NEAR::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(privateKeyBytes.data(), 32); + + TW::NEAR::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.signed_transaction()), tx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKey}); + NEAR::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.signed_transaction().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } +} diff --git a/tests/chains/NEO/AddressTests.cpp b/tests/chains/NEO/AddressTests.cpp new file mode 100644 index 00000000000..af24375ebfe --- /dev/null +++ b/tests/chains/NEO/AddressTests.cpp @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "NEO/Address.h" +#include "NEO/Signer.h" +#include "PublicKey.h" + +#include + +using namespace std; +using namespace TW; + +namespace TW::NEO::tests { + +TEST(NEOAddress, FromPublicKey) { + const auto publicKey = PublicKey(parse_hex("0222b2277d039d67f4197a638dd5a1d99c290b17aa8c4a16ccee5165fe612de66a"), TWPublicKeyTypeSECP256k1); + const auto address = Address(publicKey); + EXPECT_EQ(string("AKmrAHRD9ZDUnu4m3vWWonpsojo4vgSuqp"), address.string()); +} + +TEST(NEOAddress, FromString) { + string neoAddress = "AXkgwcMJTy9wTAXHsbyhauxh7t2Tt31MmC"; + const auto address = Address(neoAddress); + EXPECT_EQ(address.string(), neoAddress); +} + +TEST(NEOAddress, isValid) { + string neoAddress = "AQAsqiyHS4SSVWZ4CmMmnCxWg7vJ84GEj4"; + string bitcoinAddress = "1Ma2DrB78K7jmAwaomqZNRMCvgQrNjE2QC"; + + EXPECT_TRUE(Address::isValid(neoAddress)); + EXPECT_FALSE(Address::isValid(bitcoinAddress)); +} + +TEST(NEOAddress, validation) { + EXPECT_FALSE(Address::isValid("abc")); + EXPECT_FALSE(Address::isValid("abeb60f3e94c1b9a09f33669435e7ef12eacd")); + EXPECT_FALSE(Address::isValid("abcb60f3e94c9b9a09f33669435e7ef1beaedads")); + EXPECT_TRUE(Address::isValid("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD")); +} + +TEST(NEOAddress, fromPubKey) { + auto address = Address(PublicKey(parse_hex("031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"), TWPublicKeyTypeNIST256p1)); + EXPECT_EQ("AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5", address.string()); +} + +TEST(NEOAddress, fromString) { + auto b58Str = "AYTxeseHT5khTWhtWX1pFFP1mbQrd4q1zz"; + auto address = Address(b58Str); + EXPECT_EQ(b58Str, address.string()); + auto errB58Str = "AATxeseHT5khTWhtWX1pFFP1mbQrd4q1zz"; + EXPECT_THROW(new Address(errB58Str), std::invalid_argument); +} + +TEST(NEOAddress, Valid) { + ASSERT_TRUE(Address::isValid("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD")); +} + +TEST(NEOAddress, Invalid) { + ASSERT_FALSE(Address::isValid("ANDfjwrUr54515515155WKRMyxFwvVwnZD")); +} + +TEST(NEOAddress, FromPrivateKey) { + auto key = PrivateKey(parse_hex("0x2A9EAB0FEC93CD94FA0A209AC5604602C1F0105FB02EAB398E17B4517C2FFBAB")); + auto publicKey = key.getPublicKey(TWPublicKeyTypeNIST256p1); + auto address = Address(publicKey); + ASSERT_EQ(address.string(), "AQCSMB3oSDA1dHPn6GXN6KB4NHmdo1fX41"); +} + +} // namespace TW::NEO::tests diff --git a/tests/chains/NEO/BinaryCodingTests.cpp b/tests/chains/NEO/BinaryCodingTests.cpp new file mode 100644 index 00000000000..ec01ba5fd98 --- /dev/null +++ b/tests/chains/NEO/BinaryCodingTests.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "NEO/BinaryCoding.h" + +#include + +namespace TW::NEO::tests { + +using namespace std; + +TEST(NEOBinaryCoding, encode256LE) { + Data d; + encode256LE(d, uint256_t(110000000)); + EXPECT_EQ(hex(d), "80778e06"); +} + +TEST(NEOBinaryCoding, encode256LEWithPadding) { + Data d; + encode256LE(d, uint256_t(10000000)); + EXPECT_EQ(hex(d), "80969800"); +} + +TEST(NEOBinaryCoding, encodeBytes) { + Data d; + Data value; + std::fill_n(std::back_inserter(value), 10, 'a'); + encodeBytes(d, value); + EXPECT_EQ(d.size(), value.size() + 1); + + d.clear(); + value.clear(); + std::fill_n(std::back_inserter(value), 255, 'a'); + encodeBytes(d, value); + EXPECT_EQ(d.size(), value.size() + 1 + 1); + + d.clear(); + value.clear(); + std::fill_n(std::back_inserter(value), 300, 'a'); + encodeBytes(d, value); + EXPECT_EQ(d.size(), value.size() + 1 + 2); + + d.clear(); + value.clear(); + std::fill_n(std::back_inserter(value), 0x10000, 'a'); + encodeBytes(d, value); + EXPECT_EQ(d.size(), value.size() + 1 + 4); +} + +} // namespace TW::NEO::tests diff --git a/tests/chains/NEO/CoinReferenceTests.cpp b/tests/chains/NEO/CoinReferenceTests.cpp new file mode 100644 index 00000000000..03f195fc6f4 --- /dev/null +++ b/tests/chains/NEO/CoinReferenceTests.cpp @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "uint256.h" +#include "HexCoding.h" +#include "NEO/CoinReference.h" +#include + +namespace TW::NEO::tests { + +using namespace std; + +TEST(NEOCoinReference, Serialize) { + auto coinReference = CoinReference(); + string prevHash = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"; + coinReference.prevHash = load(parse_hex(prevHash)); + coinReference.prevIndex = 1; + EXPECT_EQ(prevHash + "0100", hex(coinReference.serialize())); +} + +TEST(NEOCoinReference, SerializeWithZeroLeading) { + auto coinReference = CoinReference(); + string prevHash = "0037ebf259ca5c6c43a5e7117c910858ea1146290e07d39e48554bc00d890b94"; + coinReference.prevHash = load(parse_hex(prevHash)); + coinReference.prevIndex = 1; + EXPECT_EQ(prevHash + "0100", hex(coinReference.serialize())); +} + +TEST(NEOCoinReference, Deserialize) { + auto coinReference = CoinReference(); + coinReference.deserialize(parse_hex("bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a0100")); + EXPECT_EQ("bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a", hex(store(coinReference.prevHash))); + EXPECT_EQ(1, coinReference.prevIndex); +} + +TEST(NEOCoinReference, DeserializeError) { + auto coinReference = CoinReference(); + // rawRef is 33 bytes length, expected 34. + auto rawRef = parse_hex("bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a01"); + EXPECT_THROW(coinReference.deserialize(rawRef), std::invalid_argument); +} + +} // namespace TW::NEO::tests diff --git a/tests/chains/NEO/ReadDataTests.cpp b/tests/chains/NEO/ReadDataTests.cpp new file mode 100644 index 00000000000..02f16e02617 --- /dev/null +++ b/tests/chains/NEO/ReadDataTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "NEO/ReadData.h" + +#include + +#include "TestUtilities.h" + +namespace TW::NEO::tests { + +using namespace std; + +TEST(NEOReadData, readBytes) { + EXPECT_EXCEPTION(TW::readBytes(Data{}, 10), "Data::Cannot read enough bytes!"); +} + +TEST(NEOReadData, readVar) { + Data from{0xfe, 0x00, 0x00, 0x00, 0x01}; + int64_t max = 0; + EXPECT_EXCEPTION(TW::readVar(from, 0, max), "ReadData::ReadVarInt error: Too huge value! FormatException"); + + max = INT64_MAX; + ASSERT_EQ(TW::readVar(from, 0, max), 0x1000000); + + from = {0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; + ASSERT_EQ(TW::readVar(from, 0, max), 0x100000000000000); +} + +} // namespace TW::NEO::tests diff --git a/tests/chains/NEO/ScriptTests.cpp b/tests/chains/NEO/ScriptTests.cpp new file mode 100644 index 00000000000..2b36c1e1d10 --- /dev/null +++ b/tests/chains/NEO/ScriptTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "NEO/Script.h" +#include "NEO/Address.h" + +#include + +namespace TW::NEO::tests { + +using namespace std; + +TEST(NEOScript, Nep5TransferWithRet) { + auto assetId = parse_hex("0d821bd7b6d53f5c2b40e217c6defc8bbe896cf5"); + std::reverse(assetId.begin(),assetId.end()); + auto from = Address("AJzoeKrj7RHMwSrPQDPdv61ciVEYpmhkjk").toScriptHash(); + auto to = Address("AeRsDBqPiGKZhzNtL2vWhXbXGccJLCGrbJ").toScriptHash(); + auto script = Script::CreateNep5TransferScript(assetId, from, to, uint256_t(110000000), true); + + EXPECT_EQ(hex(script), "0480778e0614f88235a26e55cce0747ee827f39fd8167849672b14235a717ed7ed18a43de47499c3d05b8d4a4bcf3a53c1087472616e7366657267f56c89be8bfcdec617e2402b5c3fd5b6d71b820df166"); +} + +TEST(NEOScript, Nep5Transfer) { + auto assetId = parse_hex("f46719e2d16bf50cddcef9d4bbfece901f73cbb6"); + std::reverse(assetId.begin(),assetId.end()); + auto from = Address("APqYfjvV2cCwcvFjceVcSrcouyq74qNFKS").toScriptHash(); + auto to = Address("ANeo2toNeo3MigrationAddressxwPB2Hz").toScriptHash(); + auto script = Script::CreateNep5TransferScript(assetId, from, to, uint256_t(15000000000)); + + EXPECT_EQ(hex(script), "0500d6117e03144b721e06b50cc74e68b417716e3b099fb99757a8145872d3dd8741af4c8d5a94f8a1bfff5c617be01b53c1087472616e7366657267b6cb731f90cefebbd4f9cedd0cf56bd1e21967f4"); +} + +} // namespace TW::NEO::tests diff --git a/tests/chains/NEO/SignerTests.cpp b/tests/chains/NEO/SignerTests.cpp new file mode 100644 index 00000000000..55a214e09ae --- /dev/null +++ b/tests/chains/NEO/SignerTests.cpp @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PublicKey.h" +#include "PublicKeyLegacy.h" +#include "NEO/Address.h" +#include "NEO/Signer.h" + +#include + +namespace TW::NEO::tests { + +using namespace std; + +TEST(NEOSigner, FromPublicPrivateKey) { + auto hexPrvKey = "4646464646464646464646464646464646464646464646464646464646464646"; + auto hexPubKey = "031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"; + auto signer = Signer(PrivateKey(parse_hex(hexPrvKey))); + auto prvKey = signer.getPrivateKey(); + auto pubKey = signer.getPublicKey(); + + EXPECT_EQ(hexPrvKey, hex(prvKey.bytes)); + EXPECT_EQ(hexPubKey, hex(pubKey.bytes)); + + auto address = signer.getAddress(); + EXPECT_TRUE(Address::isValid(address.string())); + + EXPECT_EQ(Address(pubKey), address); +} + +TEST(NEOSigner, SigningData) { + auto signer = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); + auto verScript = "ba7908ddfe5a1177f2c9d3fa1d3dc71c9c289a3325b3bdd977e20c50136959ed02d1411efa5e8b897d970ef7e2325e6c0a3fdee4eb421223f0d86e455879a9ad"; + auto invocationScript = string("401642b3d538e138f34b32330e381a7fe3f5151fcf958f2030991e72e2e25043143e4a1ebd239634efba279c96fa0ab04a15aa15179d73a7ef5a886ac8a06af484401642b3d538e138f34b32330e381a7fe3f5151fcf958f2030991e72e2e25043143e4a1ebd239634efba279c96fa0ab04a15aa15179d73a7ef5a886ac8a06af484401642b3d538e138f34b32330e381a7fe3f5151fcf958f2030991e72e2e25043143e4a1ebd239634efba279c96fa0ab04a15aa15179d73a7ef5a886ac8a06af484"); + invocationScript = string(invocationScript.rbegin(), invocationScript.rend()); + + EXPECT_EQ(verScript, hex(signer.sign(parse_hex(invocationScript)))); +} + +TEST(NEOAccount, validity) { + auto hexPrvKey = "4646464646464646464646464646464646464646464646464646464646464646"; + auto hexPubKey = "031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"; + auto signer = Signer(PrivateKey(parse_hex(hexPrvKey))); + auto prvKey = signer.getPrivateKey(); + auto pubKey = signer.getPublicKey(); + EXPECT_EQ(hexPrvKey, hex(prvKey.bytes)); + EXPECT_EQ(hexPubKey, hex(pubKey.bytes)); +} + +TEST(NEOSigner, SigningTransaction) { + auto privateKey = PrivateKey(parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128")); + auto signer = Signer(privateKey); + auto transaction = Transaction(); + transaction.type = TransactionType::TT_ContractTransaction; + transaction.version = 0x00; + + CoinReference coin; + coin.prevHash = load(parse_hex("9c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de")); // reverse hash + coin.prevIndex = (uint16_t)1; + transaction.inInputs.push_back(coin); + + { + TransactionOutput out; + out.assetId = load(parse_hex("9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5")); + out.value = (int64_t)1 * 100000000; + auto scriptHash = TW::NEO::Address("Ad9A1xPbuA5YBFr1XPznDwBwQzdckAjCev").toScriptHash(); + out.scriptHash = load(scriptHash); + transaction.outputs.push_back(out); + } + + { + TransactionOutput out; + out.assetId = load(parse_hex("9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5")); + out.value = (int64_t)892 * 100000000; + auto scriptHash = TW::NEO::Address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV").toScriptHash(); + out.scriptHash = load(scriptHash); + transaction.outputs.push_back(out); + } + + signer.sign(transaction); + auto signedTx = transaction.serialize(); + EXPECT_EQ(hex(signedTx), "800000019c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de0100029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500fcbbc414000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac0141405046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb27e1fc01c1858576228f1fef8c0945a8ad69688e52a4ed19f5b85f5eff7e961d7232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); + // TODO uncomment when nist256p1 Rust implementation is enabled. + // EXPECT_EQ(hex(signedTx), "800000019c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de0100029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500fcbbc414000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac0141405046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb281e03fe2e7a7a89ed70e01073f6ba574e65071c87cc8cce59833d4d30479c37a232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); +} + +} // namespace TW::NEO::tests diff --git a/tests/chains/NEO/TWAnySignerTests.cpp b/tests/chains/NEO/TWAnySignerTests.cpp new file mode 100644 index 00000000000..e600d17ad3f --- /dev/null +++ b/tests/chains/NEO/TWAnySignerTests.cpp @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "PrivateKey.h" +#include "PublicKeyLegacy.h" +#include "TestUtilities.h" +#include +#include "HexCoding.h" +#include "proto/NEO.pb.h" + +#include + +namespace TW::NEO::tests { + +const std::string SECRET = "F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128"; + +Proto::SigningInput createInput() { + const std::string NEO_ASSET_ID = "9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5"; + const std::string GAS_ASSET_ID = "e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60"; + + Proto::SigningInput input; + auto privateKey = parse_hex(SECRET); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_fee(12345); // too low + input.set_gas_asset_id(GAS_ASSET_ID); + input.set_gas_change_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); + +#define ADD_UTXO_INPUT(hash, index, value, assetId) \ + { \ + auto utxo = input.add_inputs(); \ + utxo->set_prev_hash(parse_hex(hash).data(), parse_hex(hash).size()); \ + utxo->set_prev_index(index); \ + utxo->set_asset_id(assetId); \ + utxo->set_value(value); \ + } + + ADD_UTXO_INPUT("c61508268c5d0343af1875c60e569493100824dbdba108b31789e0e33bcb50fb", 1, 98899890000, GAS_ASSET_ID); + ADD_UTXO_INPUT("4eb2f96937a0d4dc96b77ba69a29e1de9574cbd62b16d881f1ee2061a291d70b", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("3fee0109d155dcfab272176117306b45b176914c88e8c379933c246a9e29ea0b", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("6ea9ce8c578bfeeecdf281f498e2a764689df3b93d6855a3cc45bd6b5213c426", 0, 400000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("f75ad3cbd277d83ee240e08f99a97ffd7e42a82a868e0f7043414f6d6147262b", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("054734e98f442b3e73a940ca8f594859ece1c7ddac14130b0e2f5e2799b85931", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("8b0c42d448912fc28c674fdcf8e21e4667d7d2133666168eaa0570488a9c5036", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 1, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 2, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 3, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 4, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 5, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 6, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 7, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 8, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 9, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("cf83bce600626b6077e136581c1aecc78a0bbb7d7649b1f580b6be881087ec40", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("9bd7572ba8df685e262369897d24f7217b42be496b9eed16e16a889dd83b394e", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("b4ee250397dde2f1001d782d3c803c38992447d3b351cdc9bf20cfaa2cbf995b", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("e1019ca259a1615f77263324156a70007b76cb4f26b01b2956b8f85e6842ac62", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("bd379df2aca526ac600919aaba0e59d4a1ad4e2f22d18966063cf45e431d016f", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("164c3f843b9b7bfa6a7376a1548f343acb5cdfa0193b8f31e8c9a647ea63ea7d", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("4acec74a76161eafe70e0791b1f504b5ba1d175fd4f340d5bf56804e25505e92", 0, 300000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("895c6629a71c84cbdc8956abea9ca2d9d215e909e6173b1a1a96289186a67796", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("54828143c4c3a0e1b09102e4ed29220b141089c2bc4200b1042eeb12e5e49296", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("5345e4abc86f7ace47112f5a91c129175833bafcaf9f1e1bcbbaf4d019c1c69d", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("c83e19d0d4210df97b3bc7768dc7184ae3acfc1b5b3ac9b05d2be0fe5a636b9f", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("3456b03f5cb688ce26ab1d09b7a15799136c8c886ca7c3c6bcb2363e61bb1bb1", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 10, 34000000000, NEO_ASSET_ID); + // all inputs below must be unused in this tx + ADD_UTXO_INPUT("e5a7887521b8b3aaf2d5426617ddabe8ef8ea3eab31c80a977c3b8f339df5be0", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("1455e9dd3cd6a04d81cd47acc07a7335212029ebbdcd0abc3e52c33f8b77f6eb", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("da711260085211b5573801d0dfe064235c69e61a55f9c15449ac55cc02b9adee", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("04486cfed371103dd51a89205b2c8bcc45ad887c49a768a62465f35810437bef", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("a5f27055a442db0e65103561900456d37af4233267960daded870c1ab2219ef4", 0, 500000000, NEO_ASSET_ID); + + { + auto output = input.add_outputs(); + output->set_asset_id(NEO_ASSET_ID); + output->set_to_address("Ad9A1xPbuA5YBFr1XPznDwBwQzdckAjCev"); + output->set_change_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); + output->set_amount(25000000000); + } + + return input; +} + +Proto::SigningInput createInputWithMultiOutput() { + const std::string NEO_ASSET_ID = "9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5"; + const std::string GAS_ASSET_ID = NEO_ASSET_ID; // use NEO as gas token to cover the gas token check + + Proto::SigningInput input; + auto privateKey = parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128"); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_fee(12345); // too low + input.set_gas_asset_id(GAS_ASSET_ID); + input.set_gas_change_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); + +#define ADD_UTXO_INPUT(hash, index, value, assetId) \ + { \ + auto utxo = input.add_inputs(); \ + utxo->set_prev_hash(parse_hex(hash).data(), parse_hex(hash).size()); \ + utxo->set_prev_index(index); \ + utxo->set_asset_id(assetId); \ + utxo->set_value(value); \ + } + + ADD_UTXO_INPUT("c61508268c5d0343af1875c60e569493100824dbdba108b31789e0e33bcb50fb", 1, 98899890000, GAS_ASSET_ID); + ADD_UTXO_INPUT("4eb2f96937a0d4dc96b77ba69a29e1de9574cbd62b16d881f1ee2061a291d70b", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("3fee0109d155dcfab272176117306b45b176914c88e8c379933c246a9e29ea0b", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("6ea9ce8c578bfeeecdf281f498e2a764689df3b93d6855a3cc45bd6b5213c426", 0, 400000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("f75ad3cbd277d83ee240e08f99a97ffd7e42a82a868e0f7043414f6d6147262b", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("054734e98f442b3e73a940ca8f594859ece1c7ddac14130b0e2f5e2799b85931", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("8b0c42d448912fc28c674fdcf8e21e4667d7d2133666168eaa0570488a9c5036", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 1, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 2, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 3, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 4, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 5, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 6, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 7, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 8, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 9, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("cf83bce600626b6077e136581c1aecc78a0bbb7d7649b1f580b6be881087ec40", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("9bd7572ba8df685e262369897d24f7217b42be496b9eed16e16a889dd83b394e", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("b4ee250397dde2f1001d782d3c803c38992447d3b351cdc9bf20cfaa2cbf995b", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("e1019ca259a1615f77263324156a70007b76cb4f26b01b2956b8f85e6842ac62", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("bd379df2aca526ac600919aaba0e59d4a1ad4e2f22d18966063cf45e431d016f", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("164c3f843b9b7bfa6a7376a1548f343acb5cdfa0193b8f31e8c9a647ea63ea7d", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("4acec74a76161eafe70e0791b1f504b5ba1d175fd4f340d5bf56804e25505e92", 0, 300000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("895c6629a71c84cbdc8956abea9ca2d9d215e909e6173b1a1a96289186a67796", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("54828143c4c3a0e1b09102e4ed29220b141089c2bc4200b1042eeb12e5e49296", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("5345e4abc86f7ace47112f5a91c129175833bafcaf9f1e1bcbbaf4d019c1c69d", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("c83e19d0d4210df97b3bc7768dc7184ae3acfc1b5b3ac9b05d2be0fe5a636b9f", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("3456b03f5cb688ce26ab1d09b7a15799136c8c886ca7c3c6bcb2363e61bb1bb1", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("048f73d6cc82d9d92b08044eccef66c78a0c22e836988ed25d6f7ffe24fb5b38", 10, 34000000000, NEO_ASSET_ID); + // all inputs below must be unused in this tx + ADD_UTXO_INPUT("e5a7887521b8b3aaf2d5426617ddabe8ef8ea3eab31c80a977c3b8f339df5be0", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("1455e9dd3cd6a04d81cd47acc07a7335212029ebbdcd0abc3e52c33f8b77f6eb", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("da711260085211b5573801d0dfe064235c69e61a55f9c15449ac55cc02b9adee", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("04486cfed371103dd51a89205b2c8bcc45ad887c49a768a62465f35810437bef", 0, 500000000, NEO_ASSET_ID); + ADD_UTXO_INPUT("a5f27055a442db0e65103561900456d37af4233267960daded870c1ab2219ef4", 0, 500000000, NEO_ASSET_ID); + + { + auto output = input.add_outputs(); + output->set_asset_id(NEO_ASSET_ID); + output->set_to_address("Ad9A1xPbuA5YBFr1XPznDwBwQzdckAjCev"); + output->set_change_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); + output->set_amount(25000000000); + + auto extra_output1 = output->add_extra_outputs(); + extra_output1->set_amount(100000000); + extra_output1->set_to_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); + + auto extra_output2 = output->add_extra_outputs(); + extra_output2->set_amount(200000000); + extra_output2->set_to_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); + } + + return input; +} + + +TEST(TWAnySignerNEO, Sign) { + Proto::SigningInput input = createInput(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNEO); + + // https://testnet-explorer.o3.network/transactions/0x7b138c753c24f474d0f70af30a9d79756e0ee9c1f38c12ed07fbdf6fc5132eaf + ASSERT_EQ(hex(output.encoded()), "8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf122956bc88746dc666759a2d67f120fe3ce1659f916d22a91e0b02421d3bddbd1232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); + // TODO uncomment when nist256p1 Rust implementation is enabled. + // ASSERT_EQ(hex(output.encoded()), "8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf1dd6a943678b9239a98a65d2980edf01beed0a0b4904573f31309a6a128a54980232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); +} + +TEST(TWAnySignerNEO, Plan) { + Proto::SigningInput input = createInput(); + Proto::TransactionPlan plan; + ANY_PLAN(input, plan, TWCoinTypeNEO); + + EXPECT_EQ(plan.inputs_size(), 30); + EXPECT_EQ(plan.outputs_size(), 2); + EXPECT_EQ(plan.fee(), 1408000); + EXPECT_EQ(plan.error(), Common::Proto::OK); +} + +TEST(TWAnySignerNEO, SignMultiOutput) { + Proto::SigningInput input = createInputWithMultiOutput(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNEO); + + ASSERT_EQ(hex(output.encoded()), "80000023fb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00e05bdf39f3b8c377a9801cb3eaa38eefe8abdd176642d5f2aab3b8217588a7e50000ebf6778b3fc3523ebc0acdbdeb29202135737ac0ac47cd814da0d63cdde955140000eeadb902cc55ac4954c1f9551ae6695c2364e0dfd0013857b5115208601271da0000ef7b431058f36524a668a7497c88ad45cc8b2c5b20891ad53d1071d3fe6c48040000f49e21b21a0c87edad0d96673223f47ad3560490613510650edb42a45570f2a50000049b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500c2eb0b00000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5e069f3d21c000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac01414057b786872d667a637ff44412e3ccf50933a9c30609016ecbcaec8b9d80f2b0e26eb0cf111674ff0802a42357671867b11c334807c40146419825eed8c45a6eed232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); + // TODO uncomment when nist256p1 Rust implementation is enabled. + // ASSERT_EQ(hex(output.encoded()), "80000023fb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00e05bdf39f3b8c377a9801cb3eaa38eefe8abdd176642d5f2aab3b8217588a7e50000ebf6778b3fc3523ebc0acdbdeb29202135737ac0ac47cd814da0d63cdde955140000eeadb902cc55ac4954c1f9551ae6695c2364e0dfd0013857b5115208601271da0000ef7b431058f36524a668a7497c88ad45cc8b2c5b20891ad53d1071d3fe6c48040000f49e21b21a0c87edad0d96673223f47ad3560490613510650edb42a45570f2a50000049b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500c2eb0b00000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5e069f3d21c000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac01414057b786872d667a637ff44412e3ccf50933a9c30609016ecbcaec8b9d80f2b0e2914f30ede98b00f8fd5bdca898e7984ea0b3b2a5e31658435b93dbea3808b664232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); +} + +TEST(TWAnySignerNEO, PlanMultiOutput) { + Proto::SigningInput input = createInputWithMultiOutput(); + Proto::TransactionPlan plan; + ANY_PLAN(input, plan, TWCoinTypeNEO); + + EXPECT_EQ(plan.inputs_size(), 35); + EXPECT_EQ(plan.outputs_size(), 1); + EXPECT_EQ(plan.fee(), 1638000); + EXPECT_EQ(plan.error(), Common::Proto::OK); +} + +} // namespace TW::NEO::tests diff --git a/tests/chains/NEO/TWCoinTypeTests.cpp b/tests/chains/NEO/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..167405c45ae --- /dev/null +++ b/tests/chains/NEO/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWNEOCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNEO)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("e0ddf7c81c732df26180aca0c36d5868ad009fdbbe6e7a56ebafc14bba41cd53")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNEO, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("AcxuqWhTureEQGeJgbmtSWNAtssjMLU7pb")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNEO, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNEO)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNEO)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNEO), 8); + ASSERT_EQ(TWBlockchainNEO, TWCoinTypeBlockchain(TWCoinTypeNEO)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeNEO)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeNEO)); + assertStringsEqual(symbol, "NEO"); + assertStringsEqual(txUrl, "https://neoscan.io/transaction/e0ddf7c81c732df26180aca0c36d5868ad009fdbbe6e7a56ebafc14bba41cd53"); + assertStringsEqual(accUrl, "https://neoscan.io/address/AcxuqWhTureEQGeJgbmtSWNAtssjMLU7pb"); + assertStringsEqual(id, "neo"); + assertStringsEqual(name, "NEO"); +} diff --git a/tests/chains/NEO/TransactionAttributeTests.cpp b/tests/chains/NEO/TransactionAttributeTests.cpp new file mode 100644 index 00000000000..10e667370e8 --- /dev/null +++ b/tests/chains/NEO/TransactionAttributeTests.cpp @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "uint256.h" +#include "HexCoding.h" +#include "NEO/ReadData.h" +#include "NEO/TransactionAttribute.h" +#include "NEO/TransactionAttributeUsage.h" +#include + +namespace TW::NEO::tests { + +using namespace std; + +TEST(NEOTransactionAttribute, Serialize) { + auto transactionAttribute = TransactionAttribute(); + string data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"; + transactionAttribute.usage = TransactionAttributeUsage::TAU_ContractHash; + transactionAttribute._data = parse_hex(data); + EXPECT_EQ("00" + data, hex(transactionAttribute.serialize())); + + data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4b"; + transactionAttribute.usage = TransactionAttributeUsage::TAU_Vote; + transactionAttribute._data = parse_hex(data); + EXPECT_EQ("30" + data, hex(transactionAttribute.serialize())); + + transactionAttribute.usage = TransactionAttributeUsage::TAU_ECDH02; + transactionAttribute._data = {(TW::byte)transactionAttribute.usage}; + auto d = parse_hex(data); + transactionAttribute._data.insert(transactionAttribute._data.end(), d.begin(), d.end()); + EXPECT_EQ("02" + data, hex(transactionAttribute.serialize())); + + data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af"; + transactionAttribute.usage = TransactionAttributeUsage::TAU_Script; + transactionAttribute._data = parse_hex(data); + EXPECT_EQ("20" + data, hex(transactionAttribute.serialize())); + + data = "bd"; + transactionAttribute.usage = TransactionAttributeUsage::TAU_DescriptionUrl; + transactionAttribute._data = parse_hex(data); + EXPECT_EQ("8101" + data, hex(transactionAttribute.serialize())); + + data = "bdecbb623eee6f9ade28d5a8ff5fb3ea"; + transactionAttribute.usage = TransactionAttributeUsage::TAU_Remark; + transactionAttribute._data = parse_hex(data); + EXPECT_EQ("f010" + data, hex(transactionAttribute.serialize())); +} + +TEST(NEOTransactionAttribute, Deserialize) { + auto transactionAttribute = TransactionAttribute(); + string data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"; + transactionAttribute.deserialize(parse_hex("00" + data)); + EXPECT_EQ(TransactionAttributeUsage::TAU_ContractHash, transactionAttribute.usage); + EXPECT_EQ(data, hex(transactionAttribute._data)); + + data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4b"; + transactionAttribute.deserialize(parse_hex("30" + data)); + EXPECT_EQ(TransactionAttributeUsage::TAU_Vote, transactionAttribute.usage); + EXPECT_EQ(data, hex(transactionAttribute._data)); + + transactionAttribute.deserialize(parse_hex("02" + data)); + EXPECT_EQ(TransactionAttributeUsage::TAU_ECDH02, transactionAttribute.usage); + EXPECT_EQ("02" + data, hex(transactionAttribute._data)); + + data = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af"; + transactionAttribute.deserialize(parse_hex("20" + data)); + EXPECT_EQ(TransactionAttributeUsage::TAU_Script, transactionAttribute.usage); + EXPECT_EQ(data, hex(transactionAttribute._data)); + + data = "bd"; + transactionAttribute.deserialize(parse_hex("8101" + data)); + EXPECT_EQ(TransactionAttributeUsage::TAU_DescriptionUrl, transactionAttribute.usage); + EXPECT_EQ(data, hex(transactionAttribute._data)); + + data = "bdecbb623eee6f9ade28d5a8ff5fb3ea"; + transactionAttribute.deserialize(parse_hex("f010" + data)); + EXPECT_EQ(TransactionAttributeUsage::TAU_Remark, transactionAttribute.usage); + EXPECT_EQ(data, hex(transactionAttribute._data)); + + EXPECT_THROW(transactionAttribute.deserialize(parse_hex("b1" + data)), std::invalid_argument); +} + +TEST(NEOTransactionAttribute, DeserializeInitialPositionAfterData) { + auto transactionAttribute = TransactionAttribute(); + EXPECT_THROW(transactionAttribute.deserialize(Data(), 1), std::invalid_argument); + EXPECT_THROW(transactionAttribute.deserialize(Data({1}), 2), std::invalid_argument); +} + + +} // namespace TW::NEO::tests diff --git a/tests/chains/NEO/TransactionCompilerTests.cpp b/tests/chains/NEO/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..121919c7eda --- /dev/null +++ b/tests/chains/NEO/TransactionCompilerTests.cpp @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/NEO.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "NEO/Address.h" +#include "NEO/Script.h" +#include "NEO/TransactionAttribute.h" +#include "NEO/TransactionAttributeUsage.h" + +#include "TestUtilities.h" +#include + +namespace TW::NEO::tests { + +TEST(NEOCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeNEO; + /// Step 1: Prepare transaction input (protobuf) + const std::string NEO_ASSET_ID = + "9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5"; + const std::string GAS_ASSET_ID = + "e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60"; + + TW::NEO::Proto::SigningInput input; + auto privateKey = + PrivateKey(parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128")); + auto publicKey = privateKey.getPublicKey(publicKeyType(coin)); + input.set_gas_asset_id(GAS_ASSET_ID); + input.set_gas_change_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); + + auto* utxo = input.add_inputs(); + auto hash = parse_hex("9c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de"); + std::reverse(hash.begin(), hash.end()); + utxo->set_prev_hash(hash.data(), hash.size()); + utxo->set_prev_index(1); + utxo->set_asset_id(NEO_ASSET_ID); + utxo->set_value(89300000000); + + auto txOutput = input.add_outputs(); + txOutput->set_asset_id(NEO_ASSET_ID); + txOutput->set_to_address("Ad9A1xPbuA5YBFr1XPznDwBwQzdckAjCev"); + txOutput->set_change_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); + txOutput->set_amount(100000000); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "7fd5629cfc7cb0f8f01f15fc6d8b37ed1240c4f818d0b397bac65266aa6466da"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = publicKey.bytes; + const auto signature = + parse_hex("5046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb27e1fc01c18585762" + "28f1fef8c0945a8ad69688e52a4ed19f5b85f5eff7e961d7"); + // TODO uncomment when nist256p1 Rust implementation is enabled. + // const auto signature = + // parse_hex("5046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb281e03fe2e7a7a89e" + // "d70e01073f6ba574e65071c87cc8cce59833d4d30479c37a"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + auto expectedTx = + "800000019c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de0100029b7cffdaa674" + "beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000ea610aa6db39bd8c8556c9" + "569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500fcbbc4" + "14000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac0141405046619c8e20e1fdeec92ce95f3019f6e7cc" + "057294eb16b2d5e55c105bf32eb27e1fc01c1858576228f1fef8c0945a8ad69688e52a4ed19f5b85f5eff7e961" + "d7232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"; + // TODO uncomment when nist256p1 Rust implementation is enabled. + // auto expectedTx = + // "800000019c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de0100029b7cffdaa674" + // "beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000ea610aa6db39bd8c8556c9" + // "569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500fcbbc4" + // "14000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac0141405046619c8e20e1fdeec92ce95f3019f6e7cc" + // "057294eb16b2d5e55c105bf32eb281e03fe2e7a7a89ed70e01073f6ba574e65071c87cc8cce59833d4d30479c3" + // "7a232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"; + + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + { + NEO::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), expectedTx); + } + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + NEO::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + NEO::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), expectedTx); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {}, {}); + NEO::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +TEST(NEOCompiler, Nep5TokenCompileWithSignatures) { + // tx on mainnet + // https://neoscan.io/transaction/6368FC6127DD7FAA3B7548CA97162D5BE18D4B2FA0046A79853E5864318E19B8 + + const auto coin = TWCoinTypeNEO; + /// Step 1: Prepare transaction input (protobuf) + const std::string ASSET_ID = + "f56c89be8bfcdec617e2402b5c3fd5b6d71b820d"; + const std::string GAS_ASSET_ID = + "e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60"; + + TW::NEO::Proto::SigningInput input; + auto publicKey = PublicKey(parse_hex("030ab39b99d8675cd9bd90aaec37cba964297cc817078d33e508ab11f1d245c068"), publicKeyType(coin)); + input.set_gas_asset_id(GAS_ASSET_ID); + input.set_gas_change_address("AJzoeKrj7RHMwSrPQDPdv61ciVEYpmhkjk"); + + auto* utxo = input.add_inputs(); + auto hash = parse_hex("f231ee7b5097d912a22fe7cb6b7417490d2ddde200e3c57042be5abcdf6f974c"); + utxo->set_prev_hash(hash.data(), hash.size()); + utxo->set_prev_index(0); + utxo->set_asset_id(GAS_ASSET_ID); + utxo->set_value(7011673); + + auto txOutput = input.add_outputs(); + txOutput->set_asset_id(GAS_ASSET_ID); + txOutput->set_to_address("AJzoeKrj7RHMwSrPQDPdv61ciVEYpmhkjk"); + txOutput->set_change_address("AJzoeKrj7RHMwSrPQDPdv61ciVEYpmhkjk"); + txOutput->set_amount(6911673); + + // set nep5 transfer + auto nep5Tx = input.mutable_transaction()->mutable_nep5_transfer(); + nep5Tx->set_asset_id(ASSET_ID); + nep5Tx->set_from("AJzoeKrj7RHMwSrPQDPdv61ciVEYpmhkjk"); + nep5Tx->set_to("AeRsDBqPiGKZhzNtL2vWhXbXGccJLCGrbJ"); + auto amount = store(110000000); + nep5Tx->set_amount(amount.data(), (int)amount.size()); + nep5Tx->set_script_with_ret(true); + + input.set_fee(100000); + + // Plan + NEO::Proto::TransactionPlan plan; + ANY_PLAN(input, plan, coin); + + ASSERT_EQ(plan.error(), Common::Proto::OK); + + auto attr = plan.add_attributes(); + auto remark = parse_hex("f15508a6ea4e15e8"); + attr->set_usage((int32_t)NEO::TransactionAttributeUsage::TAU_Remark); + attr->set_data(remark.data(), (int)remark.size()); + + attr = plan.add_attributes(); + auto script = parse_hex("235a717ed7ed18a43de47499c3d05b8d4a4bcf3a"); + attr->set_usage((int32_t)NEO::TransactionAttributeUsage::TAU_Script); + attr->set_data(script.data(), (int)script.size()); + + *input.mutable_plan() = plan; + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "d301bc00e59a1c92741a31955714c76689f627dcb9d7ec176435f269a981159c"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = publicKey.bytes; + const auto signature = + parse_hex("8b707d23f84d39ddaad7da100e2d8b657ef6c0858c6c09edc029f441f28e49ff6af994ba7ad180f90e12dd9d7828f8f28785ae5079ed9a52bb5ddd3bcce1b189"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + auto expectedTx = + "d101510480778e0614f88235a26e55cce0747ee827f39fd8167849672b14235a717ed7ed18a43de47499c3d05b8d4a4bcf3a53c1087472616e7366657267f56c89be8bfcdec617e2402b5c3fd5b6d71b820df166000000000000000002f008f15508a6ea4e15e820235a717ed7ed18a43de47499c3d05b8d4a4bcf3a014c976fdfbc5abe4270c5e300e2dd2d0d4917746bcbe72fa212d997507bee31f2000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60b976690000000000235a717ed7ed18a43de47499c3d05b8d4a4bcf3a0141408b707d23f84d39ddaad7da100e2d8b657ef6c0858c6c09edc029f441f28e49ff6af994ba7ad180f90e12dd9d7828f8f28785ae5079ed9a52bb5ddd3bcce1b1892321030ab39b99d8675cd9bd90aaec37cba964297cc817078d33e508ab11f1d245c068ac"; + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + { + NEO::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), expectedTx); + } +} + +TEST(NEOCompiler, InvocationTransactionCompileWithSignatures) { + // tx on mainnet + // https://neoscan.io/transaction/73f540f5ce6fd4f363262e4b168d411f5443c694f3c53beee36fc03861c00356 + + const auto coin = TWCoinTypeNEO; + /// Step 1: Prepare transaction input (protobuf) + auto ASSET_ID = parse_hex("f46719e2d16bf50cddcef9d4bbfece901f73cbb6"); + std::reverse(ASSET_ID.begin(), ASSET_ID.end()); + + const std::string GAS_ASSET_ID = + "e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60"; + + TW::NEO::Proto::SigningInput input; + auto publicKey = PublicKey(parse_hex("02337b2d3982d71aa234a112cd8507260f480994d20129921f5a95c77f8bbe1bb3"), publicKeyType(coin)); + + auto amount = store(15000000000); + + auto script = NEO::Script::CreateNep5TransferScript( + ASSET_ID, + NEO::Address("APqYfjvV2cCwcvFjceVcSrcouyq74qNFKS").toScriptHash(), + NEO::Address("ANeo2toNeo3MigrationAddressxwPB2Hz").toScriptHash(), + load(amount)); + + // set invocation transaction script + input.mutable_transaction()->mutable_invocation_generic()->set_script(script.data(), (int)script.size()); + + auto plan = input.mutable_plan(); + auto attr = plan->add_attributes(); + auto attrScript = parse_hex("5872d3dd8741af4c8d5a94f8a1bfff5c617be01b"); + attr->set_usage((int32_t)NEO::TransactionAttributeUsage::TAU_Script); + attr->set_data(attrScript.data(), (int)attrScript.size()); + + attr = plan->add_attributes(); + auto remark1 = parse_hex("46726f6d204e656f4c696e652061742031363539303030373533343031"); + attr->set_usage((int32_t)NEO::TransactionAttributeUsage::TAU_Remark1); + attr->set_data(remark1.data(), (int)remark1.size()); + + attr = plan->add_attributes(); + auto remark14 = parse_hex("4e55577138626836486363746e5357346167384a4a4b453734333841637374554d54"); + attr->set_usage((int32_t)NEO::TransactionAttributeUsage::TAU_Remark14); + attr->set_data(remark14.data(), (int)remark14.size()); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "a59a59f840dfc9426f070355bbbbe024b673095d86ba1b2810f61d5291f127f3"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = publicKey.bytes; + const auto signature = + parse_hex("0c22129691f4438adf0ff178acc4811dee1c625df0f65909791e2c0f563cd88f7967f0ccbb6b60609e5225fb7b2873d510fe42c43c2741d90ca002afb4861d5c"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + const auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + + NEO::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + auto expectedTx = + "d101500500d6117e03144b721e06b50cc74e68b417716e3b099fb99757a8145872d3dd8741af4c8d5a94f8a1bfff5c617be01b53c1087472616e7366657267b6cb731f90cefebbd4f9cedd0cf56bd1e21967f4000000000000000003205872d3dd8741af4c8d5a94f8a1bfff5c617be01bf11d46726f6d204e656f4c696e652061742031363539303030373533343031fe224e55577138626836486363746e5357346167384a4a4b453734333841637374554d5400000141400c22129691f4438adf0ff178acc4811dee1c625df0f65909791e2c0f563cd88f7967f0ccbb6b60609e5225fb7b2873d510fe42c43c2741d90ca002afb4861d5c232102337b2d3982d71aa234a112cd8507260f480994d20129921f5a95c77f8bbe1bb3ac"; + EXPECT_EQ(hex(output.encoded()), expectedTx); +} + +} // namespace TW::NEO::tests diff --git a/tests/chains/NEO/TransactionOutputTests.cpp b/tests/chains/NEO/TransactionOutputTests.cpp new file mode 100644 index 00000000000..cf1e76ed3d5 --- /dev/null +++ b/tests/chains/NEO/TransactionOutputTests.cpp @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "uint256.h" +#include "HexCoding.h" +#include "NEO/TransactionOutput.h" +#include + +namespace TW::NEO::tests { + +using namespace std; + +TEST(NEOTransactionOutput, Serialize) { + auto transactionOutput = TransactionOutput(); + string assetId = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"; + string scriptHash = "cbb23e6f9ade28d5a8ff3eac9d73af039e821b1b"; + transactionOutput.value = 1L; + transactionOutput.assetId = load(parse_hex(assetId)); + transactionOutput.scriptHash = load(parse_hex(scriptHash)); + EXPECT_EQ(assetId + "0100000000000000" + scriptHash, hex(transactionOutput.serialize())); + + transactionOutput.value = 0xff01; + EXPECT_EQ(assetId + "01ff000000000000" + scriptHash, hex(transactionOutput.serialize())); +} + +TEST(NEOTransactionOutput, Deserialize) { + string assetId = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"; + string scriptHash = "cbb23e6f9ade28d5a8ff3eac9d73af039e821b1b"; + auto transactionOutput = TransactionOutput(); + transactionOutput.deserialize(parse_hex(assetId + "0100000000000000" + scriptHash)); + EXPECT_EQ(1UL, transactionOutput.value); + EXPECT_EQ(assetId, hex(store(transactionOutput.assetId))); + EXPECT_EQ(scriptHash, hex(store(transactionOutput.scriptHash))); + + transactionOutput.deserialize(parse_hex(assetId + "01ff000000000000" + scriptHash)); + EXPECT_EQ(0xff01UL, transactionOutput.value); + EXPECT_EQ(assetId, hex(store(transactionOutput.assetId))); + EXPECT_EQ(scriptHash, hex(store(transactionOutput.scriptHash))); +} + +TEST(NEOTransactionOutput, DeserializeError) { + string assetId = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"; + // The scriptHash is 19 bytes length. Expected 20. + string scriptHash = "cbb23e6f9ade28d5a8ff3eac9d73af039e821b"; + auto transactionOutput = TransactionOutput(); + EXPECT_THROW(transactionOutput.deserialize(parse_hex(assetId + "0100000000000000" + scriptHash)), std::invalid_argument); +} + +} // namespace TW::NEO::tests diff --git a/tests/chains/NEO/TransactionTests.cpp b/tests/chains/NEO/TransactionTests.cpp new file mode 100644 index 00000000000..615716a9f58 --- /dev/null +++ b/tests/chains/NEO/TransactionTests.cpp @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "uint256.h" +#include "HexCoding.h" +#include "NEO/Transaction.h" +#include "NEO/TransactionType.h" +#include "NEO/TransactionAttributeUsage.h" +#include "NEO/TransactionAttribute.h" +#include + +namespace TW::NEO::tests { + +using namespace std; + +TEST(NEOTransaction, SerializeDeserializeEmpty) { + auto transaction = Transaction(); + EXPECT_EQ(transaction, transaction); + + EXPECT_EQ(0ul, transaction.attributes.size()); + EXPECT_EQ(0ul, transaction.inInputs.size()); + EXPECT_EQ(0ul, transaction.outputs.size()); + auto serialized = transaction.serialize(); + + auto deserializedTransaction = Transaction(); + deserializedTransaction.deserialize(serialized); + EXPECT_EQ(transaction, deserializedTransaction); +} + +TEST(NEOTransaction, SerializeDeserializeEmptyCollections) { + auto transaction = Transaction(); + transaction.type = TransactionType::TT_EnrollmentTransaction; + transaction.version = 0x07; + const string zeroVarLong = "00"; + auto serialized = transaction.serialize(); + EXPECT_EQ("2007" + zeroVarLong + zeroVarLong + zeroVarLong, hex(serialized)); + + auto deserializedTransaction = Transaction(); + deserializedTransaction.deserialize(serialized); + EXPECT_EQ(transaction, deserializedTransaction); + EXPECT_EQ(transaction, transaction); +} + +TEST(NEOTransaction, SerializeDeserializeAttribute) { + auto transaction = Transaction(); + transaction.type = TransactionType::TT_ContractTransaction; + transaction.version = 0x07; + const string zeroVarLong = "00"; + const string oneVarLong = "01"; + transaction.attributes.push_back(TransactionAttribute()); + transaction.attributes[0].usage = TransactionAttributeUsage::TAU_ContractHash; + transaction.attributes[0]._data = parse_hex("bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"); + auto serialized = transaction.serialize(); + EXPECT_EQ("8007" + oneVarLong + hex(transaction.attributes[0].serialize()) + zeroVarLong + zeroVarLong, hex(serialized)); + + auto deserializedTransaction = Transaction(); + deserializedTransaction.deserialize(serialized); + EXPECT_EQ(transaction, deserializedTransaction); + + transaction.attributes.push_back(TransactionAttribute()); + transaction.attributes[1].usage = TransactionAttributeUsage::TAU_ECDH02; + transaction.attributes[1]._data = parse_hex("02b7ecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a"); + serialized = transaction.serialize(); + const string twoVarLong = "02"; + string expectedSerialized = "8007" + twoVarLong; + expectedSerialized += hex(transaction.attributes[0].serialize()); + expectedSerialized += hex(transaction.attributes[1].serialize()); + expectedSerialized += zeroVarLong + zeroVarLong; + EXPECT_EQ(expectedSerialized, hex(serialized)); + + deserializedTransaction.deserialize(serialized); + EXPECT_EQ(transaction, deserializedTransaction); + EXPECT_EQ(transaction, transaction); +} + +TEST(NEOTransaction, SerializeDeserializeInputs) { + auto transaction = Transaction(); + transaction.type = TransactionType::TT_ContractTransaction; + transaction.version = 0x07; + const string zeroVarLong = "00"; + const string oneVarLong = "01"; + transaction.inInputs.push_back(CoinReference()); + transaction.inInputs[0].prevHash = load(parse_hex("bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a")); + transaction.inInputs[0].prevIndex = 0xa; + auto serialized = transaction.serialize(); + EXPECT_EQ("8007" + zeroVarLong + oneVarLong + hex(transaction.inInputs[0].serialize()) + zeroVarLong, hex(serialized)); + + auto deserializedTransaction = Transaction(); + deserializedTransaction.deserialize(serialized); + EXPECT_EQ(transaction, deserializedTransaction); + + transaction.inInputs.push_back(CoinReference()); + transaction.inInputs[1].prevHash = load(parse_hex("bdecbb623eee4f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a")); + transaction.inInputs[1].prevIndex = 0xbc; + serialized = transaction.serialize(); + const string twoVarLong = "02"; + string expectedSerialized = "8007" + zeroVarLong + twoVarLong; + expectedSerialized += hex(transaction.inInputs[0].serialize()); + expectedSerialized += hex(transaction.inInputs[1].serialize()); + expectedSerialized += zeroVarLong; + EXPECT_EQ(expectedSerialized, hex(serialized)); + + deserializedTransaction.deserialize(serialized); + EXPECT_EQ(transaction, deserializedTransaction); + EXPECT_EQ(transaction, transaction); +} + +TEST(NEOTransaction, SerializeDeserializeOutputs) { + auto transaction = Transaction(); + transaction.type = TransactionType::TT_ContractTransaction; + transaction.version = 0x07; + const string zeroVarLong = "00"; + const string oneVarLong = "01"; + transaction.outputs.push_back(TransactionOutput()); + transaction.outputs[0].assetId = load(parse_hex("bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a")); + transaction.outputs[0].scriptHash = load(parse_hex("cbb23e6f9ade28d5a8ff3eac9d73af039e821b1b")); + transaction.outputs[0].value = 0x2; + auto serialized = transaction.serialize(); + EXPECT_EQ("8007" + zeroVarLong + zeroVarLong + oneVarLong + hex(transaction.outputs[0].serialize()), hex(serialized)); + + auto deserializedTransaction = Transaction(); + deserializedTransaction.deserialize(serialized); + EXPECT_EQ(transaction, deserializedTransaction); + + transaction.outputs.push_back(TransactionOutput()); + transaction.outputs[1].assetId = load(parse_hex("bdecbb623eee6a9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a")); + transaction.outputs[1].scriptHash = load(parse_hex("cbb23e6f9a3e28d5a8ff3eac9d73af039e821b1b")); + transaction.outputs[1].value = 0x2; + serialized = transaction.serialize(); + const string twoVarLong = "02"; + string expectedSerialized = "8007" + zeroVarLong + zeroVarLong + twoVarLong; + expectedSerialized += hex(transaction.outputs[0].serialize()); + expectedSerialized += hex(transaction.outputs[1].serialize()); + EXPECT_EQ(expectedSerialized, hex(serialized)); + + deserializedTransaction.deserialize(serialized); + EXPECT_EQ(transaction, deserializedTransaction); +} + +TEST(NEOTransaction, SerializeDeserialize) { + auto transaction = Transaction(); + transaction.type = TransactionType::TT_ContractTransaction; + transaction.version = 0x07; + const string oneVarLong = "01"; + + transaction.attributes.push_back(TransactionAttribute()); + transaction.attributes[0].usage = TransactionAttributeUsage::TAU_ContractHash; + transaction.attributes[0]._data = parse_hex("bdecbb623eee6f9ade28d5a8ff5fbdea9c9d73af039e0286201b3b0291fb4d4a"); + + transaction.inInputs.push_back(CoinReference()); + transaction.inInputs[0].prevHash = load(parse_hex("bdecbb623eee679ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a")); + transaction.inInputs[0].prevIndex = 0xa; + + transaction.outputs.push_back(TransactionOutput()); + transaction.outputs[0].assetId = load(parse_hex("bdecbb623eee6f9ad328d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a")); + transaction.outputs[0].scriptHash = load(parse_hex("cbb23e6f9ade28a5a8ff3eac9d73af039e821b1b")); + transaction.outputs[0].value = 0x2; + + auto serialized = transaction.serialize(); + string expectedSerialized = "8007"; + expectedSerialized += oneVarLong + hex(transaction.attributes[0].serialize()); + expectedSerialized += oneVarLong + hex(transaction.inInputs[0].serialize()); + expectedSerialized += oneVarLong + hex(transaction.outputs[0].serialize()); + ASSERT_EQ(expectedSerialized, hex(serialized)); + + auto deserializedTransaction = Transaction(); + deserializedTransaction.deserialize(serialized); + EXPECT_EQ(transaction, deserializedTransaction); + + transaction.outputs.push_back(TransactionOutput()); + transaction.outputs[1].assetId = load(parse_hex("bdecbb623eee6a9a3e28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4a")); + transaction.outputs[1].scriptHash = load(parse_hex("cbb23e6f9a3e28d5a8ff3eac9da3af039e821b1b")); + transaction.outputs[1].value = 0x2; + serialized = transaction.serialize(); + const string twoVarLong = "02"; + expectedSerialized = "8007"; + expectedSerialized += oneVarLong + hex(transaction.attributes[0].serialize()); + expectedSerialized += oneVarLong + hex(transaction.inInputs[0].serialize()); + expectedSerialized += twoVarLong + hex(transaction.outputs[0].serialize()); + expectedSerialized += hex(transaction.outputs[1].serialize()); + EXPECT_EQ(expectedSerialized, hex(serialized)); + + deserializedTransaction.deserialize(serialized); + EXPECT_EQ(transaction, deserializedTransaction); + + transaction.inInputs.push_back(CoinReference()); + transaction.inInputs[1].prevHash = load(parse_hex("bdecbb623e3e6f9ade28d5a8ff4fb3ea9c9d73af039e0286201b3b0291fb4d4a")); + transaction.inInputs[1].prevIndex = 0xbc; + transaction.inInputs.push_back(CoinReference()); + transaction.inInputs[2].prevHash = load(parse_hex("bdecbb624eee6f9ade28d5a8ff3fb3ea9c9d73af039e0286201b3b0291fb4d4a")); + transaction.inInputs[2].prevIndex = 0x1f; + + serialized = transaction.serialize(); + const string threeVarLong = "03"; + expectedSerialized = "8007"; + expectedSerialized += oneVarLong + hex(transaction.attributes[0].serialize()); + expectedSerialized += threeVarLong + hex(transaction.inInputs[0].serialize()); + expectedSerialized += hex(transaction.inInputs[1].serialize()); + expectedSerialized += hex(transaction.inInputs[2].serialize()); + expectedSerialized += twoVarLong + hex(transaction.outputs[0].serialize()); + expectedSerialized += hex(transaction.outputs[1].serialize()); + EXPECT_EQ(expectedSerialized, hex(serialized)); + + deserializedTransaction.deserialize(serialized); + EXPECT_EQ(transaction, deserializedTransaction); +} + +TEST(NEOTransaction, SerializeDeserializeMiner) { + string block2tn = "0000d11f7a2800000000"; + std::unique_ptr deserializedTransaction(Transaction::deserializeFrom(parse_hex(block2tn))); + auto serialized = deserializedTransaction->serialize(); + std::unique_ptr serializedTransaction(Transaction::deserializeFrom(serialized)); + + EXPECT_EQ(*deserializedTransaction, *serializedTransaction); + + string notMiner = "1000d11f7a2800000000"; + EXPECT_THROW( + std::unique_ptr _deserializedTransaction(Transaction::deserializeFrom(parse_hex(notMiner))), + std::invalid_argument); +} + +TEST(NEOTransaction, SerializeDeserializeInvocation) { + string data = "d1014f04e0aebb00148b720fabfe9cf041f9a27df4f7f1daaabf73c66414f88235a26e55cce0747ee827f39fd8167849672b53c1087472616e7366657267e345419e7377286ee5b0a39b56e30f6213ab9e4d00000000000000000220f88235a26e55cce0747ee827f39fd8167849672bf0084d65822107fcfd5200000141407d9089f957dbaf526a425f7dc494c62511989124f357364c750a3c3bff94e9f677c9cd497d9dcea3c7a1abbfa59411608736c86ef23297d0813699cd279fe27923210339fbbe7bdce6be3101fd8a1fb45df6782cd8ab76c80c884bfee14d5daafac392ac"; + std::unique_ptr deserializedTransaction(Transaction::deserializeFrom(parse_hex(data))); + auto serialized = deserializedTransaction->serialize(); + std::unique_ptr serializedTransaction(Transaction::deserializeFrom(serialized)); + + EXPECT_EQ(*deserializedTransaction, *serializedTransaction); +} + +TEST(NEOTransaction, SerializeDeserializeContract) { + string data = "80000001820e56309f4c26b32cc00025d0e35e7faa25641fa03138877478a155794f54490000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000f88235a26e55cce0747ee827f39fd8167849672b0141401ca0aa69bccbcb97ca9423573a2c3c696422055c02e8d4ce37e2460ae88753c66144f4fbf392de6619c73ff27346f1d0a9f3d23967327d4d4f4b3b151dbb05942321025c01e1650f6bf488c318a0105ed7214c1b6a9a14cfebd9ec5da53a29723f8101ac"; + std::unique_ptr deserializedTransaction(Transaction::deserializeFrom(parse_hex(data))); + auto serialized = deserializedTransaction->serialize(); + std::unique_ptr serializedTransaction(Transaction::deserializeFrom(serialized)); + + EXPECT_EQ(*deserializedTransaction, *serializedTransaction); +} + +TEST(NEOTransaction, GetHash) { + string block2tn = "0000d11f7a2800000000"; + std::unique_ptr deserializedTransaction(Transaction::deserializeFrom(parse_hex(block2tn))); + + Data hash = parse_hex("8e3a32ba3a7e8bdb0ad9a2ad064713e45bd20eb0dab0d2e77df5b5ce985276d0"); + // It is flipped on the https://github.com/NeoResearch/neopt/blob/master/tests/ledger_Tests/Transaction.Test.cpp + hash = Data(hash.rbegin(), hash.rend()); + + EXPECT_EQ(hex(hash), hex(deserializedTransaction->getHash())); +} + +TEST(NEOTransaction, SerializeSize) { + auto transaction = Transaction(); + transaction.type = TransactionType::TT_EnrollmentTransaction; + transaction.version = 0x07; + const string zeroVarLong = "00"; + auto serialized = transaction.serialize(); + auto verSerialized = parse_hex("2007" + zeroVarLong + zeroVarLong + zeroVarLong); + EXPECT_EQ(hex(verSerialized), hex(serialized)); + EXPECT_EQ(verSerialized, serialized); + + EXPECT_EQ(serialized.size(), static_cast(transaction.size())); +} + +} // namespace TW::NEO::tests diff --git a/tests/NEO/WitnessTests.cpp b/tests/chains/NEO/WitnessTests.cpp similarity index 88% rename from tests/NEO/WitnessTests.cpp rename to tests/chains/NEO/WitnessTests.cpp index ff2a579846b..d31c122cc4a 100644 --- a/tests/NEO/WitnessTests.cpp +++ b/tests/chains/NEO/WitnessTests.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "uint256.h" #include "HexCoding.h" @@ -11,9 +9,9 @@ #include +namespace TW::NEO::tests { + using namespace std; -using namespace TW; -using namespace TW::NEO; TEST(NEOWitness, Serialize) { auto witness = Witness(); @@ -22,12 +20,14 @@ TEST(NEOWitness, Serialize) { witness.invocationScript = parse_hex(invocationScript); witness.verificationScript = parse_hex(verificationScript); EXPECT_EQ("20" + invocationScript + "14" + verificationScript, hex(witness.serialize())); + EXPECT_EQ((size_t)witness.size(), witness.invocationScript.size() + witness.verificationScript.size()); invocationScript = "bdecbb623eee6f9ade28d5a8ff5fb3ea9c9d73af039e0286201b3b0291fb4d4aba"; verificationScript = "cbb23e6f9ade28d5a8ff3eac9d73af039e821b"; witness.invocationScript = parse_hex(invocationScript); witness.verificationScript = parse_hex(verificationScript); EXPECT_EQ("21" + invocationScript + "13" + verificationScript, hex(witness.serialize())); + EXPECT_EQ((size_t)witness.size(), witness.invocationScript.size() + witness.verificationScript.size()); } TEST(NEOWitness, Deserialize) { @@ -62,3 +62,5 @@ TEST(NEOWitness, SerializeDeserialize) { deWitness.deserialize(witness.serialize()); EXPECT_EQ(witness, deWitness); } + +} // namespace TW::NEO::tests diff --git a/tests/chains/NULS/AddressTests.cpp b/tests/chains/NULS/AddressTests.cpp new file mode 100644 index 00000000000..542d939d183 --- /dev/null +++ b/tests/chains/NULS/AddressTests.cpp @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "NULS/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include + +namespace TW::NULS::tests { + +TEST(NULSAddress, StaticInvalid) { + ASSERT_FALSE(Address::isValid("abc")); + ASSERT_FALSE(Address::isValid("tNULSe")); + ASSERT_FALSE(Address::isValid("aaeb60f3e94c9b9a09f33669435e7ef1beaed")); + ASSERT_FALSE(Address::isValid("NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z")); + ASSERT_TRUE(Address::isValid("NULSd6HgUxmcJWc88iELEJ7RH9XHsazBQqnJc")); + ASSERT_TRUE(Address::isValid("NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z2")); + ASSERT_TRUE(Address::isValid("tNULSeBaMtdtUNbnNBZ5RoFoh3fHrrrm6dVZLH")); +} + +TEST(NULSAddress, ChainID) { + const auto address = Address("NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z2"); + ASSERT_TRUE(address.chainID() == 1); +} + +TEST(NULSAddress, Type) { + const auto address = Address("NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z2"); + ASSERT_TRUE(address.type() == 1); +} + +TEST(NULSAddress, FromString) { + const auto address = Address("NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z2"); + ASSERT_EQ(address.string(), "NULSd6HgbwcM8wz48f6UkFYHLVriT1L81X9z2"); + const auto address2 = Address("tNULSeBaMtdtUNbnNBZ5RoFoh3fHrrrm6dVZLH"); + ASSERT_EQ(address2.string(), "tNULSeBaMtdtUNbnNBZ5RoFoh3fHrrrm6dVZLH"); + + EXPECT_ANY_THROW(new Address("WrongPrefix")); + EXPECT_ANY_THROW(new Address("NULSdPrefixButInvalid")); +} + +TEST(NULSAddress, FromPrivateKey) { + const auto privateKey = + PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")); + const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); + const auto address = Address(publicKey, true); + ASSERT_EQ(address.string(), "NULSd6HghWa4CN5qdxqMwYVikQxRZyj57Jn4L"); + const auto address2 = Address(publicKey, false); + ASSERT_EQ(address2.string(), "tNULSeBaMtdtUNbnNBZ5RoFoh3fHrrrm6dVZLH"); +} + +TEST(NULSAddress, FromCompressedPublicKey) { + const auto publicKey = + PublicKey(parse_hex("0244d50ff36c3136b4bf81f0c74b066695bc2af43e28d7f0ca1d48fcfd084bea66"), TWPublicKeyTypeSECP256k1); + const auto address = Address(publicKey, true); + + ASSERT_EQ(address.string(), "NULSd6HgUiMKPNi221bPfqvvho8QpuYBvn1x3"); +} + +TEST(NULSAddress, FromPrivateKey33) { + const auto privateKey = PrivateKey(parse_hex("d77580833f0b3c35b7114c23d6b66790d726c308baf237ec8c369152f2c08d27")); + const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); + const auto address = Address(publicKey, true); + + ASSERT_EQ(address.string(), "NULSd6HgXx8YkwEjePLWUmdRSZzPQzK6BXnsB"); +} + +} // namespace TW::NULS::tests diff --git a/tests/chains/NULS/SignerTests.cpp b/tests/chains/NULS/SignerTests.cpp new file mode 100644 index 00000000000..3db30b5c2f4 --- /dev/null +++ b/tests/chains/NULS/SignerTests.cpp @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "NULS/Signer.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "proto/NULS.pb.h" + +#include + +namespace TW::NULS::tests { + +TEST(NULSSigner, Sign) { + auto privateKey = + parse_hex("0x9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b"); + auto amount = store(uint256_t(10000000)); + auto balance = store(uint256_t(100000000)); + std::string nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H"); + input.set_to("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(1); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1569228280); + input.set_nonce(nonce.data(), nonce.size()); + + Proto::SigningOutput output; + output = Signer::sign(input); + + EXPECT_EQ( + hex(output.encoded()), + "0200f885885d00008c0117010001f7ec6473df12e751d64cf20a8baa7edd50810f8101000100201d9a00000000" + "00000000000000000000000000000000000000000000000000080000000000000000000117010001f05e787897" + "1f3374515eabb6f16d75219d887312010001008096980000000000000000000000000000000000000000000000" + "0000000000000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae1" + "43c1d7e3463044022028019c0099e2233c7adb84bb03a9a5666ece4a5b65a026a090fa460f3679654702204df0" + "fcb8762b5944b3aba033fa1a287ccb098150035dd8b66f52dc58d3d0843a"); +} + +TEST(NULSSigner, SignToken) { + auto privateKey = + parse_hex("0x9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b"); + auto amount = store(uint256_t(10000000)); + auto balance = store(uint256_t(100000000)); + auto feePayerBalance = store(uint256_t(1000000)); + std::string nonce = "0000000000000000"; + std::string asset_nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H"); + input.set_to("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(9); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1569228280); + input.set_nonce(asset_nonce.data(), asset_nonce.size()); + input.set_fee_payer("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H"); + input.set_fee_payer_nonce(nonce.data(), nonce.size()); + input.set_fee_payer_balance(feePayerBalance.data(), feePayerBalance.size()); + + Proto::SigningOutput output; + output = Signer::sign(input); + + EXPECT_EQ(hex(output.encoded()), + "0200f885885d0000d20217010001f7ec6473df12e751d64cf20a8baa7edd50810f810900010080969800" + "000000000000000000000000000000000000000000000000000000000800000000000000000017010001" + "f7ec6473df12e751d64cf20a8baa7edd50810f8101000100a08601000000000000000000000000000000" + "0000000000000000000000000000080000000000000000000117010001f05e7878971f3374515eabb6f1" + "6d75219d8873120900010080969800000000000000000000000000000000000000000000000000000000" + "000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae143c1" + "d7e346304402204629e7e39708468a405f8d8dd208d0133a686beb5d3ae829b7a2f5867c74480902203d" + "0dffac8189b1caa9087e480fd57581e8c382cc4e17034b492dd2178dac851d"); +} + +TEST(NULSSigner, SignWithFeePayer) { + auto privateKey = + parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); + auto feePayerPrivateKey = + parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); + auto amount = store(uint256_t(100000)); + auto balance = store(uint256_t(1000000)); + auto feePayerBalance = store(uint256_t(1000000)); + std::string nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac"); + input.set_to("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(1); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1660632991); + input.set_nonce(nonce.data(), nonce.size()); + + // fee payer + input.set_fee_payer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA"); + input.set_fee_payer_nonce("0000000000000000"); + input.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + input.set_fee_payer_balance(feePayerBalance.data(), feePayerBalance.size()); + + Proto::SigningOutput output; + output = Signer::sign(input); + + EXPECT_EQ( + hex(output.encoded()), + "02009f3ffb620000d202170100014f019a4227bff89d51590fbf53fbd98d994485f801000100a0860100000000" + "00000000000000000000000000000000000000000000000000080000000000000000001701000152a6388c8bf5" + "4e8fcd73cc824813bfef0912299b01000100a08601000000000000000000000000000000000000000000000000" + "0000000000080000000000000000000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b701000100a0" + "860100000000000000000000000000000000000000000000000000000000000000000000000000d22102764370" + "693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022040b5820b93fd5e272f2a00" + "518af45a722571834934ba20d9a866de8e6d6409ab022003c610c647648444c1e2193634b2c51817a5a6ac3fe7" + "81da1a9ea773506afd8221025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff46" + "30440220140e46b260942a8475f38df39bf54a2eea56c77199fc7ba775aa4b7f147d0d2102206c82705cba509f" + "37ba0e35520a97bccb71a9e35cadcb8d95dd7fde5c8aa9e428"); +} + +TEST(NULSSigner, SignTokenWithFeePayer) { + auto privateKey = + parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); + + auto feePayerPrivateKey = + parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); + auto amount = store(uint256_t(100000)); + auto balance = store(uint256_t(900000)); + auto feePayerBalance = store(uint256_t(1000000)); + std::string asset_nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac"); + input.set_to("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(9); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1660636748); + input.set_nonce(asset_nonce.data(), asset_nonce.size()); + + // fee payer + input.set_fee_payer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA"); + input.set_fee_payer_nonce("e05d03df6ede0e22"); + input.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + input.set_fee_payer_balance(feePayerBalance.data(), feePayerBalance.size()); + + Proto::SigningOutput output; + output = Signer::sign(input); + + EXPECT_EQ( + hex(output.encoded()), + "02004c4efb620000d2021701000152a6388c8bf54e8fcd73cc824813bfef0912299b09000100a0860100000000" + "0000000000000000000000000000000000000000000000000008000000000000000000170100014f019a4227bf" + "f89d51590fbf53fbd98d994485f801000100a08601000000000000000000000000000000000000000000000000" + "000000000008e05d03df6ede0e22000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b709000100a0" + "860100000000000000000000000000000000000000000000000000000000000000000000000000d32102764370" + "693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022025cb5b40bda4e6fc0ba719" + "bb0c1a907b2a0faa91316ef2c836310d49f906b6a802207d530a56a6c56d974036c86125da06d6e47f9d8ba154" + "4ac3e620cebd883a707821025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff47" + "3045022100ff6f45a1c3856f9ea954baca6b2988295bbb22c958f87f0d3baf9989930549530220426ecb152051" + "3710b99ab50e1f6c7e21b0175adef08aa05070bb9bfca8a001d8"); +} + +TEST(NULSSigner, InsufficientBalance) { + auto privateKey = + parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); + auto feePayerPrivateKey = + parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); + auto amount = store(uint256_t(10000000)); + auto balance = store(uint256_t(100)); + auto feePayerBalance = store(uint256_t(1000000)); + std::string nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac"); + input.set_to("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(1); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1660632991); + input.set_nonce(nonce.data(), nonce.size()); + + // fee payer + input.set_fee_payer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA"); + input.set_fee_payer_nonce("0000000000000000"); + input.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + input.set_fee_payer_balance(feePayerBalance.data(), feePayerBalance.size()); + + Proto::SigningOutput output; + output = Signer::sign(input); + + EXPECT_EQ(output.error(), Common::Proto::Error_general); + EXPECT_EQ(output.error_message(), "User account balance not sufficient"); +} + +TEST(NULSSigner, InsufficientBalanceWithFeePayer) { + auto privateKey = + parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); + auto feePayerPrivateKey = + parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); + auto amount = store(uint256_t(10000000)); + auto balance = store(uint256_t(100000000)); + auto feePayerBalance = store(uint256_t(100)); + std::string nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac"); + input.set_to("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(1); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1660632991); + input.set_nonce(nonce.data(), nonce.size()); + + // fee payer + input.set_fee_payer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA"); + input.set_fee_payer_nonce("0000000000000000"); + input.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + input.set_fee_payer_balance(feePayerBalance.data(), feePayerBalance.size()); + + Proto::SigningOutput output; + output = Signer::sign(input); + + EXPECT_EQ(output.error(), Common::Proto::Error_general); + EXPECT_EQ(output.error_message(), "fee payer balance not sufficient"); +} + +TEST(NULSSigner, TokenInsufficientBalance) { + auto privateKey = + parse_hex("0x9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b"); + auto amount = store(uint256_t(10000000)); + auto balance = store(uint256_t(100000000)); + auto feePayerBalance = store(uint256_t(100)); + std::string nonce = "0000000000000000"; + std::string asset_nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H"); + input.set_to("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(9); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1569228280); + input.set_nonce(asset_nonce.data(), asset_nonce.size()); + input.set_fee_payer("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H"); + input.set_fee_payer_nonce(nonce.data(), nonce.size()); + input.set_fee_payer_balance(feePayerBalance.data(), feePayerBalance.size()); + + Proto::SigningOutput output; + output = Signer::sign(input); + + EXPECT_EQ(output.error(), Common::Proto::Error_general); + EXPECT_EQ(output.error_message(), "fee payer balance not sufficient"); +} + +} // namespace TW::NULS::tests diff --git a/tests/chains/NULS/TWAnySignerTests.cpp b/tests/chains/NULS/TWAnySignerTests.cpp new file mode 100644 index 00000000000..d82048ada7a --- /dev/null +++ b/tests/chains/NULS/TWAnySignerTests.cpp @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "NULS/Address.h" +#include "PrivateKey.h" +#include "proto/NULS.pb.h" +#include "uint256.h" +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::NULS::tests { + +TEST(TWAnySignerNULS, Sign) { + auto privateKey = + parse_hex("0x9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b"); + auto amount = store(uint256_t(10000000)); + auto balance = store(uint256_t(100000000)); + std::string nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H"); + input.set_to("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(1); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1569228280); + input.set_nonce(nonce.data(), nonce.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNULS); + + EXPECT_EQ( + hex(output.encoded()), + "0200f885885d00008c0117010001f7ec6473df12e751d64cf20a8baa7edd50810f8101000100201d9a00000000" + "00000000000000000000000000000000000000000000000000080000000000000000000117010001f05e787897" + "1f3374515eabb6f16d75219d887312010001008096980000000000000000000000000000000000000000000000" + "0000000000000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae1" + "43c1d7e3463044022028019c0099e2233c7adb84bb03a9a5666ece4a5b65a026a090fa460f3679654702204df0" + "fcb8762b5944b3aba033fa1a287ccb098150035dd8b66f52dc58d3d0843a"); +} + +TEST(TWAnySignerNULS, SignToken) { + auto privateKey = + parse_hex("0x9ce21dad67e0f0af2599b41b515a7f7018059418bab892a7b68f283d489abc4b"); + auto amount = store(uint256_t(10000000)); + auto balance = store(uint256_t(100000000)); + auto assetBalance = store(uint256_t(100000000)); + std::string nonce = "0000000000000000"; + std::string asset_nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H"); + input.set_to("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(9); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(assetBalance.data(), assetBalance.size()); + input.set_timestamp(1569228280); + input.set_nonce(asset_nonce.data(), asset_nonce.size()); + input.set_fee_payer("NULSd6Hgj7ZoVgsPN9ybB4C1N2TbvkgLc8Z9H"); + input.set_fee_payer_balance(balance.data(), balance.size()); + input.set_fee_payer_nonce(nonce.data(), nonce.size()); + + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNULS); + + EXPECT_EQ(hex(output.encoded()), + "0200f885885d0000d20217010001f7ec6473df12e751d64cf20a8baa7edd50810f810900010080969800" + "000000000000000000000000000000000000000000000000000000000800000000000000000017010001" + "f7ec6473df12e751d64cf20a8baa7edd50810f8101000100a08601000000000000000000000000000000" + "0000000000000000000000000000080000000000000000000117010001f05e7878971f3374515eabb6f1" + "6d75219d8873120900010080969800000000000000000000000000000000000000000000000000000000" + "000000000000000000692103958b790c331954ed367d37bac901de5c2f06ac8368b37d7bd6cd5ae143c1" + "d7e346304402204629e7e39708468a405f8d8dd208d0133a686beb5d3ae829b7a2f5867c74480902203d" + "0dffac8189b1caa9087e480fd57581e8c382cc4e17034b492dd2178dac851d"); +} + +// Tx: +// https://nulscan.io/transaction/info?hash=58694394d2e28e18f66cf61697c791774c44d5275dce60d5e05d03df6ede0e22 +TEST(TWAnySigner, SignWithFeePayer) { + auto privateKey = + parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); + auto feePayerPrivateKey = + parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); + auto pk = PrivateKey(privateKey); + auto amount = store(uint256_t(100000)); + auto balance = store(uint256_t(1000000)); + auto feePayerBalance = store(uint256_t(1000000)); + std::string nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac"); + input.set_to("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(1); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1660632991); + input.set_nonce(nonce.data(), nonce.size()); + + // fee payer + input.set_fee_payer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA"); + input.set_fee_payer_nonce("0000000000000000"); + input.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + input.set_fee_payer_balance(feePayerBalance.data(), feePayerBalance.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNULS); + + EXPECT_EQ( + hex(output.encoded()), + "02009f3ffb620000d202170100014f019a4227bff89d51590fbf53fbd98d994485f801000100a0860100000000" + "00000000000000000000000000000000000000000000000000080000000000000000001701000152a6388c8bf5" + "4e8fcd73cc824813bfef0912299b01000100a08601000000000000000000000000000000000000000000000000" + "0000000000080000000000000000000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b701000100a0" + "860100000000000000000000000000000000000000000000000000000000000000000000000000d22102764370" + "693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022040b5820b93fd5e272f2a00" + "518af45a722571834934ba20d9a866de8e6d6409ab022003c610c647648444c1e2193634b2c51817a5a6ac3fe7" + "81da1a9ea773506afd8221025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff46" + "30440220140e46b260942a8475f38df39bf54a2eea56c77199fc7ba775aa4b7f147d0d2102206c82705cba509f" + "37ba0e35520a97bccb71a9e35cadcb8d95dd7fde5c8aa9e428"); +} + +// Tx: +// https://nulscan.io/transaction/info?hash=210731f7b4a96884a2e04925e758586fb2edb07627621ba1be4de2c54b0868fc +TEST(TWAnySigner, SignTokenWithFeePayer) { + auto privateKey = + parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); + auto feePayerPrivateKey = + parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); + auto amount = store(uint256_t(100000)); + auto balance = store(uint256_t(900000)); + auto feePayerBalance = store(uint256_t(1000000)); + std::string asset_nonce = "0000000000000000"; + Proto::SigningInput input; + + input.set_from("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac"); + input.set_to("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV"); + input.set_amount(amount.data(), amount.size()); + input.set_chain_id(9); + input.set_idassets_id(1); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_balance(balance.data(), balance.size()); + input.set_timestamp(1660636748); + input.set_nonce(asset_nonce.data(), asset_nonce.size()); + + // fee payer + input.set_fee_payer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA"); + input.set_fee_payer_nonce("e05d03df6ede0e22"); + input.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + input.set_fee_payer_balance(feePayerBalance.data(), feePayerBalance.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNULS); + + EXPECT_EQ( + hex(output.encoded()), + "02004c4efb620000d2021701000152a6388c8bf54e8fcd73cc824813bfef0912299b09000100a0860100000000" + "0000000000000000000000000000000000000000000000000008000000000000000000170100014f019a4227bf" + "f89d51590fbf53fbd98d994485f801000100a08601000000000000000000000000000000000000000000000000" + "000000000008e05d03df6ede0e22000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b709000100a0" + "860100000000000000000000000000000000000000000000000000000000000000000000000000d32102764370" + "693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022025cb5b40bda4e6fc0ba719" + "bb0c1a907b2a0faa91316ef2c836310d49f906b6a802207d530a56a6c56d974036c86125da06d6e47f9d8ba154" + "4ac3e620cebd883a707821025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff47" + "3045022100ff6f45a1c3856f9ea954baca6b2988295bbb22c958f87f0d3baf9989930549530220426ecb152051" + "3710b99ab50e1f6c7e21b0175adef08aa05070bb9bfca8a001d8"); +} +} // namespace TW::NULS::tests diff --git a/tests/chains/NULS/TWCoinTypeTests.cpp b/tests/chains/NULS/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..f2592aa1d2d --- /dev/null +++ b/tests/chains/NULS/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWNULSCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNULS)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("303e0e42c28acc37ba952a1effd43daa1caec79928054f7abefb21c32e6fdc02")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNULS, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("NULSd6HgdSjUZy7jKMZfvQ5QU6Z97oufGTGcF")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNULS, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNULS)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNULS)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNULS), 8); + ASSERT_EQ(TWBlockchainNULS, TWCoinTypeBlockchain(TWCoinTypeNULS)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeNULS)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeNULS)); + assertStringsEqual(symbol, "NULS"); + assertStringsEqual(txUrl, "https://nulscan.io/transaction/info?hash=303e0e42c28acc37ba952a1effd43daa1caec79928054f7abefb21c32e6fdc02"); + assertStringsEqual(accUrl, "https://nulscan.io/address/info?address=NULSd6HgdSjUZy7jKMZfvQ5QU6Z97oufGTGcF"); + assertStringsEqual(id, "nuls"); + assertStringsEqual(name, "NULS"); +} diff --git a/tests/chains/NULS/TransactionCompilerTests.cpp b/tests/chains/NULS/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..cbc2def155d --- /dev/null +++ b/tests/chains/NULS/TransactionCompilerTests.cpp @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "uint256.h" + +#include "proto/NULS.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::NULS::tests { + +TEST(NULSCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeNULS; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::NULS::Proto::SigningInput(); + auto from = std::string("NULSd6HgWabfcG6H7NDK2TJvtoU3wxY1YLKwJ"); + auto to = std::string("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + uint32_t chainId = 1; + uint32_t idassetsId = 1; + auto amount = TW::store((TW::uint256_t)10000000); + auto amountStr = std::string(amount.begin(), amount.end()); + auto balance = TW::store((TW::uint256_t)100000000); + auto balanceStr = std::string(balance.begin(), balance.end()); + auto nonce = std::string("0000000000000000"); + input.set_from(from); + input.set_to(to); + input.set_amount(amountStr); + input.set_chain_id(chainId); + input.set_idassets_id(idassetsId); + input.set_nonce(nonce); + input.set_balance(balanceStr); + input.set_timestamp((uint32_t)1569228280); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + auto preImage = preSigningOutput.data(); + EXPECT_EQ(hex(preImage), + "0200f885885d00008c01170100012c177a01a7afbe98e094007b99476534fb7926b701000100201d9a00" + "000000000000000000000000000000000000000000000000000000000800000000000000000001170100" + "01f05e7878971f3374515eabb6f16d75219d887312010001008096980000000000000000000000000000" + "0000000000000000000000000000000000000000000000"); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "8746b37cb4b443424d3093e8107c5dfd6c5318010bbffcc8e8ba7c1da60877fd"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = + parse_hex("033c87a3d9b812556b3034b6471cad5131a01e210c1d7ca06dd53b7d0aff0ee045"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = + parse_hex("a5234269eab6fe8a1510dd0cb36070a03464b48856e1ef2681dbb79a5ec656f92961ac01d401a6f8" + "49acc958c6c9653f49282f5a0916df036ea8766918bac19500"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + const auto ExpectedEncoded = parse_hex( + "0200f885885d00008c01170100012c177a01a7afbe98e094007b99476534fb7926b701000100201d9a00000000" + "00000000000000000000000000000000000000000000000000080000000000000000000117010001f05e787897" + "1f3374515eabb6f16d75219d887312010001008096980000000000000000000000000000000000000000000000" + "00000000000000000000000000006a21033c87a3d9b812556b3034b6471cad5131a01e210c1d7ca06dd53b7d0a" + "ff0ee045473045022100a5234269eab6fe8a1510dd0cb36070a03464b48856e1ef2681dbb79a5ec656f9022029" + "61ac01d401a6f849acc958c6c9653f49282f5a0916df036ea8766918bac195"); + const auto ExpectedTx = std::string(ExpectedEncoded.begin(), ExpectedEncoded.end()); + EXPECT_EQ(outputData.size(), 259ul); + { + TW::NULS::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded(), ExpectedTx); + EXPECT_EQ(output.encoded().size(), 256ul); + } + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::NULS::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + auto key = parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1"); + signingInput.set_private_key(key.data(), key.size()); + + TW::NULS::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKeyData}); + NULS::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); + } +} + +TEST(NULSCompiler, TokenCompileWithSignatures) { + const auto coin = TWCoinTypeNULS; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::NULS::Proto::SigningInput(); + auto from = std::string("NULSd6HgWabfcG6H7NDK2TJvtoU3wxY1YLKwJ"); + auto to = std::string("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + uint32_t chainId = 9; + uint32_t idassetsId = 1; + auto amount = TW::store((TW::uint256_t)10000000); + auto amountStr = std::string(amount.begin(), amount.end()); + auto balance = TW::store((TW::uint256_t)100000000); + auto balanceStr = std::string(balance.begin(), balance.end()); + auto feePayerBalance = TW::store((TW::uint256_t)100000000); + auto feePayerBalanceStr = std::string(feePayerBalance.begin(), feePayerBalance.end()); + auto nonce = std::string("0000000000000000"); + auto asset_nonce = std::string("0000000000000000"); + input.set_from(from); + input.set_to(to); + input.set_amount(amountStr); + input.set_chain_id(chainId); + input.set_idassets_id(idassetsId); + input.set_nonce(asset_nonce.data(), asset_nonce.size()); + input.set_balance(balanceStr); + input.set_timestamp((uint32_t)1569228280); + input.set_fee_payer(from); + input.set_fee_payer_balance(feePayerBalanceStr); + input.set_fee_payer_nonce(nonce.data(), nonce.size()); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + auto preImage = preSigningOutput.data(); + EXPECT_EQ( + hex(preImage), + "0200f885885d0000d202170100012c177a01a7afbe98e094007b99476534fb7926b70900010080969800000000" + "0000000000000000000000000000000000000000000000000008000000000000000000170100012c177a01a7af" + "be98e094007b99476534fb7926b701000100a08601000000000000000000000000000000000000000000000000" + "0000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120900010080" + "969800000000000000000000000000000000000000000000000000000000000000000000000000"); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "9040642ce845b320453b2ccd6f80efc38fdf61ec8f0c12e0c16f6244ec2e0496"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = + parse_hex("033c87a3d9b812556b3034b6471cad5131a01e210c1d7ca06dd53b7d0aff0ee045"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = + parse_hex("5ddea604c6cdfcf6cbe32f5873937641676ee5f9aee3c40aa9857c59aefedff25b77429cf62307d4" + "3a6a79b4c106123e6232e3981032573770fe2726bf9fc07c00"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + /// Step 3: Compile transaction info + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + const auto ExpectedEncoded = parse_hex( + "0200f885885d0000d202170100012c177a01a7afbe98e094007b99476534fb7926b70900010080969800000000" + "0000000000000000000000000000000000000000000000000008000000000000000000170100012c177a01a7af" + "be98e094007b99476534fb7926b701000100a08601000000000000000000000000000000000000000000000000" + "0000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120900010080" + "9698000000000000000000000000000000000000000000000000000000000000000000000000006921033c87a3" + "d9b812556b3034b6471cad5131a01e210c1d7ca06dd53b7d0aff0ee04546304402205ddea604c6cdfcf6cbe32f" + "5873937641676ee5f9aee3c40aa9857c59aefedff202205b77429cf62307d43a6a79b4c106123e6232e3981032" + "573770fe2726bf9fc07c"); + const auto ExpectedTx = std::string(ExpectedEncoded.begin(), ExpectedEncoded.end()); + EXPECT_EQ(outputData.size(), 328ul); + + { + TW::NULS::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded(), ExpectedTx); + EXPECT_EQ(output.encoded().size(), 325ul); + } + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::NULS::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + auto key = parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1"); + signingInput.set_private_key(key.data(), key.size()); + + TW::NULS::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } +} + +// Tx: +// https://nulscan.io/transaction/info?hash=58694394d2e28e18f66cf61697c791774c44d5275dce60d5e05d03df6ede0e22 +TEST(NULSCompiler, CompileWithSignaturesFeePayer) { + const auto coin = TWCoinTypeNULS; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::NULS::Proto::SigningInput(); + auto from = std::string("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac"); + auto to = std::string("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV"); + uint32_t chainId = 1; + uint32_t idassetsId = 1; + auto amount = TW::store((TW::uint256_t)100000); + auto amountStr = std::string(amount.begin(), amount.end()); + auto balance = TW::store((TW::uint256_t)1000000); + auto balanceStr = std::string(balance.begin(), balance.end()); + auto feePayerBalance = TW::store((TW::uint256_t)1000000); + auto feePayerBalanceStr = std::string(feePayerBalance.begin(), feePayerBalance.end()); + auto nonce = std::string("0000000000000000"); + input.set_from(from); + input.set_to(to); + input.set_amount(amountStr); + input.set_chain_id(chainId); + input.set_idassets_id(idassetsId); + input.set_nonce(nonce); + input.set_balance(balanceStr); + input.set_timestamp((uint32_t)1660632991); + // fee payer + input.set_fee_payer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA"); + input.set_fee_payer_nonce("0000000000000000"); + input.set_fee_payer_balance(feePayerBalanceStr); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + auto preImage = preSigningOutput.data(); + EXPECT_EQ( + hex(preImage), + "02009f3ffb620000d202170100014f019a4227bff89d51590fbf53fbd98d994485f801000100a0860100000000" + "00000000000000000000000000000000000000000000000000080000000000000000001701000152a6388c8bf5" + "4e8fcd73cc824813bfef0912299b01000100a08601000000000000000000000000000000000000000000000000" + "0000000000080000000000000000000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b701000100a0" + "860100000000000000000000000000000000000000000000000000000000000000000000000000"); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "58694394d2e28e18f66cf61697c791774c44d5275dce60d5e05d03df6ede0e22"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = + parse_hex("02764370693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const Data feePayerPublicKeyData = + parse_hex("025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff"); + const PublicKey feePayerPublicKey = PublicKey(feePayerPublicKeyData, TWPublicKeyTypeSECP256k1); + + const auto signature = + parse_hex("40b5820b93fd5e272f2a00518af45a722571834934ba20d9a866de8e6d6409ab03c610c647648444" + "c1e2193634b2c51817a5a6ac3fe781da1a9ea773506afd8201"); + const auto feePayerSignature = + parse_hex("140e46b260942a8475f38df39bf54a2eea56c77199fc7ba775aa4b7f147d0d216c82705cba509f37" + "ba0e35520a97bccb71a9e35cadcb8d95dd7fde5c8aa9e42800"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + EXPECT_TRUE(feePayerPublicKey.verify(feePayerSignature, TW::data(preImageHash))); + /// Step 3: Compile transaction info + Data outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, feePayerSignature}, {publicKeyData, feePayerPublicKeyData}); + const auto ExpectedEncoded = parse_hex( + "02009f3ffb620000d202170100014f019a4227bff89d51590fbf53fbd98d994485f801000100a0860100000000" + "00000000000000000000000000000000000000000000000000080000000000000000001701000152a6388c8bf5" + "4e8fcd73cc824813bfef0912299b01000100a08601000000000000000000000000000000000000000000000000" + "0000000000080000000000000000000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b701000100a0" + "860100000000000000000000000000000000000000000000000000000000000000000000000000d22102764370" + "693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022040b5820b93fd5e272f2a00" + "518af45a722571834934ba20d9a866de8e6d6409ab022003c610c647648444c1e2193634b2c51817a5a6ac3fe7" + "81da1a9ea773506afd8221025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff46" + "30440220140e46b260942a8475f38df39bf54a2eea56c77199fc7ba775aa4b7f147d0d2102206c82705cba509f" + "37ba0e35520a97bccb71a9e35cadcb8d95dd7fde5c8aa9e428"); + const auto ExpectedTx = std::string(ExpectedEncoded.begin(), ExpectedEncoded.end()); + EXPECT_EQ(outputData.size(), 433ul); + TW::NULS::Proto::SigningOutput signingOutput; + ASSERT_TRUE(signingOutput.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(signingOutput.encoded(), ExpectedTx); + EXPECT_EQ(signingOutput.encoded().size(), 430ul); + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::NULS::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + auto key = parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); + signingInput.set_private_key(key.data(), key.size()); + auto feePayerPrivateKey = + parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); + signingInput.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + + TW::NULS::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKeyData}); + NULS::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); + } +} + +// Tx: +// https://nulscan.io/transaction/info?hash=210731f7b4a96884a2e04925e758586fb2edb07627621ba1be4de2c54b0868fc +TEST(NULSCompiler, TokenCompileWithSignaturesFeePayer) { + const auto coin = TWCoinTypeNULS; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::NULS::Proto::SigningInput(); + auto from = std::string("NULSd6HgYx7bdWWv7PxYhYeTRBhD6kZs1o5Ac"); + auto to = std::string("NULSd6HgaHjzhMEUjd4T5DFnLz9EvV4TntrdV"); + uint32_t chainId = 9; + uint32_t idassetsId = 1; + auto amount = TW::store((TW::uint256_t)100000); + auto amountStr = std::string(amount.begin(), amount.end()); + auto balance = TW::store((TW::uint256_t)900000); + auto balanceStr = std::string(balance.begin(), balance.end()); + auto feePayerBalance = TW::store((TW::uint256_t)1000000); + auto feePayerBalanceStr = std::string(feePayerBalance.begin(), feePayerBalance.end()); + auto nonce = std::string("0000000000000000"); + input.set_from(from); + input.set_to(to); + input.set_amount(amountStr); + input.set_chain_id(chainId); + input.set_idassets_id(idassetsId); + input.set_nonce(nonce); + input.set_balance(balanceStr); + input.set_timestamp((uint32_t)1660636748); + // fee payer + input.set_fee_payer("NULSd6HgYj81NrQBFZYXvyQhHCJCkGYWDTNeA"); + input.set_fee_payer_nonce("e05d03df6ede0e22"); + input.set_fee_payer_balance(feePayerBalanceStr); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + auto preImage = preSigningOutput.data(); + EXPECT_EQ( + hex(preImage), + "02004c4efb620000d2021701000152a6388c8bf54e8fcd73cc824813bfef0912299b09000100a0860100000000" + "0000000000000000000000000000000000000000000000000008000000000000000000170100014f019a4227bf" + "f89d51590fbf53fbd98d994485f801000100a08601000000000000000000000000000000000000000000000000" + "000000000008e05d03df6ede0e22000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b709000100a0" + "860100000000000000000000000000000000000000000000000000000000000000000000000000"); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "210731f7b4a96884a2e04925e758586fb2edb07627621ba1be4de2c54b0868fc"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = + parse_hex("02764370693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const Data feePayerPublicKeyData = + parse_hex("025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff"); + const PublicKey feePayerPublicKey = PublicKey(feePayerPublicKeyData, TWPublicKeyTypeSECP256k1); + + const auto signature = + parse_hex("25cb5b40bda4e6fc0ba719bb0c1a907b2a0faa91316ef2c836310d49f906b6a87d530a56a6c56d97" + "4036c86125da06d6e47f9d8ba1544ac3e620cebd883a707800"); + const auto feePayerSignature = + parse_hex("ff6f45a1c3856f9ea954baca6b2988295bbb22c958f87f0d3baf998993054953426ecb1520513710" + "b99ab50e1f6c7e21b0175adef08aa05070bb9bfca8a001d801"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + EXPECT_TRUE(feePayerPublicKey.verify(feePayerSignature, TW::data(preImageHash))); + /// Step 3: Compile transaction info + Data outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, feePayerSignature}, {publicKeyData, feePayerPublicKeyData}); + const auto ExpectedEncoded = parse_hex( + "02004c4efb620000d2021701000152a6388c8bf54e8fcd73cc824813bfef0912299b09000100a0860100000000" + "0000000000000000000000000000000000000000000000000008000000000000000000170100014f019a4227bf" + "f89d51590fbf53fbd98d994485f801000100a08601000000000000000000000000000000000000000000000000" + "000000000008e05d03df6ede0e22000117010001686a3c9cd2ac45aee0ef825b0443d1eb209368b709000100a0" + "860100000000000000000000000000000000000000000000000000000000000000000000000000d32102764370" + "693450d654d6fc061d1d4dbfbe0c95715fd3cde7e15df073ab0983b8c8463044022025cb5b40bda4e6fc0ba719" + "bb0c1a907b2a0faa91316ef2c836310d49f906b6a802207d530a56a6c56d974036c86125da06d6e47f9d8ba154" + "4ac3e620cebd883a707821025910df09ca768909cce9224d182401044c7b5bd44b0adee2ec5f2e64446573ff47" + "3045022100ff6f45a1c3856f9ea954baca6b2988295bbb22c958f87f0d3baf9989930549530220426ecb152051" + "3710b99ab50e1f6c7e21b0175adef08aa05070bb9bfca8a001d8"); + const auto ExpectedTx = std::string(ExpectedEncoded.begin(), ExpectedEncoded.end()); + EXPECT_EQ(outputData.size(), 434ul); + TW::NULS::Proto::SigningOutput signingOutput; + ASSERT_TRUE(signingOutput.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(signingOutput.encoded(), ExpectedTx); + EXPECT_EQ(signingOutput.encoded().size(), 431ul); + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::NULS::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + auto key = parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); + signingInput.set_private_key(key.data(), key.size()); + auto feePayerPrivateKey = + parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); + signingInput.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + + TW::NULS::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKeyData}); + NULS::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); + } +} + +} // namespace TW::NULS::tests diff --git a/tests/chains/Nano/AddressTests.cpp b/tests/chains/Nano/AddressTests.cpp new file mode 100644 index 00000000000..fdf30780912 --- /dev/null +++ b/tests/chains/Nano/AddressTests.cpp @@ -0,0 +1,56 @@ +// Copyright © 2019 Mart Roosmaa. +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Nano/Address.h" + +#include + +using namespace std; +using namespace TW; + +namespace TW::Nano::tests { + +TEST(NanoAddress, FromPublicKey) { + { + const auto publicKey = PublicKey(parse_hex("5114aad86a390897d2a91b33b931b3a59a7df9e63eb3694f9430122f5622ae50"), TWPublicKeyTypeED25519Blake2b); + const auto address = Address(publicKey); + ASSERT_EQ(string("nano_1nanode8ngaakzbck8smq6ru9bethqwyehomf79sae1k7xd47dkidjqzffeg"), address.string()); + } + + { + const auto publicKey = PublicKey(parse_hex("03e20ec6b4a39a629815ae02c0a1393b9225e3b890cae45b59f42fa29be9668d"), TWPublicKeyTypeED25519); + ASSERT_THROW(Address address(publicKey), std::invalid_argument); + } +} + +TEST(NanoAddress, FromString) { + { + string nanoAddress = "nano_1nanode8ngaakzbck8smq6ru9bethqwyehomf79sae1k7xd47dkidjqzffeg"; + const auto address = Address(nanoAddress); + ASSERT_EQ(address.string(), nanoAddress); + ASSERT_EQ(hex(address.bytes), "5114aad86a390897d2a91b33b931b3a59a7df9e63eb3694f9430122f5622ae50"); + } + + { + string xrbAddress = "xrb_1111111111111111111111111111111111111111111111111111hifc8npp"; + string nanoAddress = "nano_1111111111111111111111111111111111111111111111111111hifc8npp"; + const auto address = Address(xrbAddress); + ASSERT_EQ(address.string(), nanoAddress); + ASSERT_EQ(hex(address.bytes), "0000000000000000000000000000000000000000000000000000000000000000"); + } +} + +TEST(NanoAddress, isValid) { + string nanodeAddress = "nano_1nanode8ngaakzbck8smq6ru9bethqwyehomf79sae1k7xd47dkidjqzffeg"; + string faultyChecksumAddress = "xrb_1111111111111111111111111111111111111111111111111111hi111111"; + string bitcoinAddress = "1Ma2DrB78K7jmAwaomqZNRMCvgQrNjE2QC"; + + ASSERT_TRUE(Address::isValid(nanodeAddress)); + ASSERT_FALSE(Address::isValid(faultyChecksumAddress)); + ASSERT_FALSE(Address::isValid(bitcoinAddress)); +} + +} // namespace TW::Nano::tests diff --git a/tests/chains/Nano/SignerTests.cpp b/tests/chains/Nano/SignerTests.cpp new file mode 100644 index 00000000000..c28645d0794 --- /dev/null +++ b/tests/chains/Nano/SignerTests.cpp @@ -0,0 +1,248 @@ +// Copyright © 2019 Mart Roosmaa. +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Nano/Signer.h" + +#include "TestAccounts.h" +#include + +using namespace TW; + +namespace TW::Nano::tests { + +TEST(NanoSigner, sign1) { + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); + + auto input = Proto::SigningInput(); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_link_block(linkBlock.data(), linkBlock.size()); + input.set_representative(kRepOfficial1); + input.set_balance("96242336390000000000000000000"); + + // https://www.nanode.co/block/f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696 + const auto signer = Signer(input); + ASSERT_EQ(hex(signer.blockHash), "f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"); + const auto signature = signer.sign(); + ASSERT_EQ(hex(signature), "d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09"); + const Proto::SigningOutput out = signer.build(); + EXPECT_EQ(hex(out.signature()), "d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09"); + EXPECT_EQ(hex(out.block_hash()), "f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"); + EXPECT_EQ( + "{\"account\":\"nano_1bhbsc9yuh15anq3owu1izw1nk7bhhqefrkhfo954fyt8dk1q911buk1kk4c\"," + "\"balance\":\"96242336390000000000000000000\"," + "\"link\":\"491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507\"," + "\"link_as_account\":\"nano_1kazsap8mc481zbqbcqjytpf9mmigj87qr5k5fhf97579t4k8fa94octjx6d\"," + "\"previous\":\"0000000000000000000000000000000000000000000000000000000000000000\"," + "\"representative\":\"nano_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4\"," + "\"signature\":\"d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09\"," + "\"type\":\"state\"}", + out.json()); +} + +TEST(NanoSigner, sign2) { + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto parentBlock = parse_hex("f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"); + + auto input = Proto::SigningInput(); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_parent_block(parentBlock.data(), parentBlock.size()); + input.set_representative(kRepNanode); + input.set_balance("96242336390000000000000000000"); + + // https://www.nanode.co/block/2568bf76336f7a415ca236dab97c1df9de951ca057a2e79df1322e647a259e7b + const auto signer = Signer(input); + ASSERT_EQ(hex(signer.blockHash), "2568bf76336f7a415ca236dab97c1df9de951ca057a2e79df1322e647a259e7b"); + const auto signature = signer.sign(); + ASSERT_EQ(hex(signature), "3a0687542405163d5623808052042b3482360a82cc003d178a0c0d8bfbca86450975d0faec60ae5ac37feba9a8e2205c8540317b26f2c589c2a6578b03870403"); +} + +TEST(NanoSigner, sign3) { + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto parentBlock = parse_hex("2568bf76336f7a415ca236dab97c1df9de951ca057a2e79df1322e647a259e7b"); + const auto linkBlock = parse_hex("d7384845d2ae530b45a5dd50ee50757f988329f652781767af3f1bc2322f52b9"); + + auto input = Proto::SigningInput(); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_parent_block(parentBlock.data(), parentBlock.size()); + input.set_link_block(linkBlock.data(), linkBlock.size()); + input.set_representative(kRepNanode); + input.set_balance("196242336390000000000000000000"); + input.set_work("123456789"); + + // https://www.nanode.co/block/1ca240212838d053ecaa9dceee598c52a6080067edecaeede3319eb0b7db6525 + const auto signer = Signer(input); + ASSERT_EQ(hex(signer.blockHash), "1ca240212838d053ecaa9dceee598c52a6080067edecaeede3319eb0b7db6525"); + const auto signature = signer.sign(); + ASSERT_EQ(hex(signature), "e980d45365ae2fb291950019f7c19a3d5fa5df2736ca7e7ca1984338b4686976cb7efdda2894ddcea480f82645b50f2340c9d0fc69a05621bdc355783a21820d"); + const Proto::SigningOutput out = signer.build(); + EXPECT_EQ( + "{\"account\":\"nano_1bhbsc9yuh15anq3owu1izw1nk7bhhqefrkhfo954fyt8dk1q911buk1kk4c\"," + "\"balance\":\"196242336390000000000000000000\"," + "\"link\":\"d7384845d2ae530b45a5dd50ee50757f988329f652781767af3f1bc2322f52b9\"," + "\"link_as_account\":\"nano_3osrb34x7dkm3f4tdqcixsa9czwrienzenmr4xmtyhruras4ynosarg1sdiq\"," + "\"previous\":\"2568bf76336f7a415ca236dab97c1df9de951ca057a2e79df1322e647a259e7b\"," + "\"representative\":\"nano_1nanode8ngaakzbck8smq6ru9bethqwyehomf79sae1k7xd47dkidjqzffeg\"," + "\"signature\":\"e980d45365ae2fb291950019f7c19a3d5fa5df2736ca7e7ca1984338b4686976cb7efdda2894ddcea480f82645b50f2340c9d0fc69a05621bdc355783a21820d\"," + "\"type\":\"state\",\"work\":\"123456789\"}", + out.json()); +} + +TEST(NanoSigner, sign4) { + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto parentBlock = parse_hex("1ca240212838d053ecaa9dceee598c52a6080067edecaeede3319eb0b7db6525"); + + auto input = Proto::SigningInput(); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_parent_block(parentBlock.data(), parentBlock.size()); + input.set_link_recipient("xrb_3wm37qz19zhei7nzscjcopbrbnnachs4p1gnwo5oroi3qonw6inwgoeuufdp"); + input.set_representative(kRepNanode); + input.set_balance("126242336390000000000000000000"); + + // https://www.nanode.co/block/32ac7d8f5a16a498abf203b8dfee623c9e111ff25e7339f8cd69ec7492b23edd + const auto signer = Signer(input); + ASSERT_EQ(hex(signer.blockHash), "32ac7d8f5a16a498abf203b8dfee623c9e111ff25e7339f8cd69ec7492b23edd"); + const auto signature = signer.sign(); + ASSERT_EQ(hex(signature), "bcb806e140c9e2bc71c51ebbd941b4d99cee3d97fd50e3006eabc5e325c712662e2dc163ee32660875d67815ce4721e122389d2e64f1c9ad4555a9d3d8c33802"); +} + +TEST(NanoSigner, signInvalid1) { + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + + // Missing link_block + auto input = Proto::SigningInput(); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_representative(kRepOfficial1); + input.set_balance("96242336390000000000000000000"); + + ASSERT_THROW(Signer signer(input), std::invalid_argument); +} + +TEST(NanoSigner, signInvalid2) { + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); + + // Missing representative + auto input = Proto::SigningInput(); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_link_block(linkBlock.data(), linkBlock.size()); + input.set_balance("96242336390000000000000000000"); + + ASSERT_THROW(Signer signer(input), std::invalid_argument); +} + +TEST(NanoSigner, signInvalid3) { + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); + + // Missing balance + auto input = Proto::SigningInput(); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_link_block(linkBlock.data(), linkBlock.size()); + input.set_representative(kRepOfficial1); + + ASSERT_THROW(Signer signer(input), std::invalid_argument); +} + +TEST(NanoSigner, signInvalid4) { + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); + + // Account first block cannot be 0 balance + auto input = Proto::SigningInput(); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_link_block(linkBlock.data(), linkBlock.size()); + input.set_representative(kRepOfficial1); + input.set_balance("0"); + + ASSERT_THROW(Signer signer(input), std::invalid_argument); +} + +TEST(NanoSigner, signInvalid5) { + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + + // First block must use link_block not link_recipient + auto input = Proto::SigningInput(); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_link_recipient("xrb_3wm37qz19zhei7nzscjcopbrbnnachs4p1gnwo5oroi3qonw6inwgoeuufdp"); + input.set_representative(kRepOfficial1); + input.set_balance("96242336390000000000000000000"); + + ASSERT_THROW(Signer signer(input), std::invalid_argument); +} + +TEST(NanoSigner, signInvalid6) { + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); + + // Invalid representative value + auto input = Proto::SigningInput(); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_link_block(linkBlock.data(), linkBlock.size()); + input.set_representative("xrb_4wm37qz19zhei7nzscjcopbrbnnachs4p1gnwo5oroi3qonw6inwgoeuufdp"); + input.set_balance("96242336390000000000000000000"); + + ASSERT_THROW(Signer signer(input), std::invalid_argument); +} + +TEST(NanoSigner, signInvalid7) { + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto parentBlock = parse_hex("f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"); + + auto input = Proto::SigningInput(); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_parent_block(parentBlock.data(), parentBlock.size()); + input.set_link_recipient("xrb_4wm37qz19zhei7nzscjcopbrbnnachs4p1gnwo5oroi3qonw6inwgoeuufdp"); + input.set_representative(kRepOfficial1); + input.set_balance("1.2.3"); + + ASSERT_THROW(Signer signer(input), std::invalid_argument); +} + +TEST(NanoSigner, buildUnsignedTxBytes) { + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); + const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); + + auto input = Proto::SigningInput(); + input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + input.set_link_block(linkBlock.data(), linkBlock.size()); + input.set_representative(kRepOfficial1); + input.set_balance("96242336390000000000000000000"); + + const auto unsignedTxBytes = Signer::buildUnsignedTxBytes(input); + ASSERT_EQ(hex(unsignedTxBytes), "f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"); +} + +TEST(NanoSigner, buildSigningOutput) { + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); + const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); + + auto input = Proto::SigningInput(); + input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + input.set_link_block(linkBlock.data(), linkBlock.size()); + input.set_representative(kRepOfficial1); + input.set_balance("96242336390000000000000000000"); + + const auto signature = parse_hex("d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09"); + + const auto out = Signer::buildSigningOutput(input, signature); + EXPECT_EQ(hex(out.signature()), "d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09"); + EXPECT_EQ(hex(out.block_hash()), "f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"); + EXPECT_EQ( + "{\"account\":\"nano_1bhbsc9yuh15anq3owu1izw1nk7bhhqefrkhfo954fyt8dk1q911buk1kk4c\"," + "\"balance\":\"96242336390000000000000000000\"," + "\"link\":\"491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507\"," + "\"link_as_account\":\"nano_1kazsap8mc481zbqbcqjytpf9mmigj87qr5k5fhf97579t4k8fa94octjx6d\"," + "\"previous\":\"0000000000000000000000000000000000000000000000000000000000000000\"," + "\"representative\":\"nano_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4\"," + "\"signature\":\"d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09\"," + "\"type\":\"state\"}", + out.json()); +} + +} // namespace TW::Nano::tests diff --git a/tests/chains/Nano/TWAnySignerTests.cpp b/tests/chains/Nano/TWAnySignerTests.cpp new file mode 100644 index 00000000000..767b9ce0f6c --- /dev/null +++ b/tests/chains/Nano/TWAnySignerTests.cpp @@ -0,0 +1,52 @@ +// Copyright © 2019 Mart Roosmaa. +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/Nano.pb.h" +#include "TestUtilities.h" +#include +#include + +using namespace TW; + +namespace TW::Nano::tests { + +TEST(TWAnySignerNano, sign) { + const auto privateKey = parse_hex("173c40e97fe2afcd24187e74f6b603cb949a5365e72fbdd065a6b165e2189e34"); + const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); + + auto input = Proto::SigningInput(); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_link_block(linkBlock.data(), linkBlock.size()); + input.set_representative("xrb_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4"); + input.set_balance("96242336390000000000000000000"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNano); + + EXPECT_EQ( + "{\"account\":\"nano_1bhbsc9yuh15anq3owu1izw1nk7bhhqefrkhfo954fyt8dk1q911buk1kk4c\"," + "\"balance\":\"96242336390000000000000000000\"," + "\"link\":\"491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507\"," + "\"link_as_account\":\"nano_1kazsap8mc481zbqbcqjytpf9mmigj87qr5k5fhf97579t4k8fa94octjx6d\"," + "\"previous\":\"0000000000000000000000000000000000000000000000000000000000000000\"," + "\"representative\":\"nano_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4\"," + "\"signature\":" + "\"d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5" + "ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09\"," + "\"type\":\"state\"}", + output.json()); +} + +TEST(TWAnySignerNano, SignJSON) { + auto json = STRING(R"({"link_block":"SR/KLGmoRgfTdKrx9qzTznB0TFvgchte05RlPoUjNQc=","representative":"nano_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4","balance":"96242336390000000000000000000"})"); + auto key = DATA("173c40e97fe2afcd24187e74f6b603cb949a5365e72fbdd065a6b165e2189e34"); + auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeNano)); + + ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeNano)); + assertStringsEqual(result, R"({"account":"nano_1bhbsc9yuh15anq3owu1izw1nk7bhhqefrkhfo954fyt8dk1q911buk1kk4c","balance":"96242336390000000000000000000","link":"491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507","link_as_account":"nano_1kazsap8mc481zbqbcqjytpf9mmigj87qr5k5fhf97579t4k8fa94octjx6d","previous":"0000000000000000000000000000000000000000000000000000000000000000","representative":"nano_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4","signature":"d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09","type":"state"})"); +} + +} // namespace TW::Nano::tests diff --git a/tests/chains/Nano/TWCoinTypeTests.cpp b/tests/chains/Nano/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..a0b033eeade --- /dev/null +++ b/tests/chains/Nano/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWNanoCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNano)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("C264DB7BF40738F0CEFF19B606746CB925B713E4B8699A055699E0DC8ABBC70F")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNano, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("nano_1wpj616kwhe1y38y1mspd8aub8i334cwybqco511iyuxm55zx8d67ptf1tsf")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNano, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNano)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNano)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNano), 30); + ASSERT_EQ(TWBlockchainNano, TWCoinTypeBlockchain(TWCoinTypeNano)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeNano)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeNano)); + assertStringsEqual(symbol, "XNO"); + assertStringsEqual(txUrl, "https://www.nanolooker.com/block/C264DB7BF40738F0CEFF19B606746CB925B713E4B8699A055699E0DC8ABBC70F"); + assertStringsEqual(accUrl, "https://www.nanolooker.com/account/nano_1wpj616kwhe1y38y1mspd8aub8i334cwybqco511iyuxm55zx8d67ptf1tsf"); + assertStringsEqual(id, "nano"); + assertStringsEqual(name, "Nano"); +} diff --git a/tests/Nano/TWNanoAddressTests.cpp b/tests/chains/Nano/TWNanoAddressTests.cpp similarity index 82% rename from tests/Nano/TWNanoAddressTests.cpp rename to tests/chains/Nano/TWNanoAddressTests.cpp index afcd1587cf4..b01dfc2447a 100644 --- a/tests/Nano/TWNanoAddressTests.cpp +++ b/tests/chains/Nano/TWNanoAddressTests.cpp @@ -1,11 +1,9 @@ // Copyright © 2019 Mart Roosmaa. -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "HexCoding.h" #include "PrivateKey.h" #include "PublicKey.h" diff --git a/tests/chains/Nano/TestAccounts.h b/tests/chains/Nano/TestAccounts.h new file mode 100644 index 00000000000..636c643e9d0 --- /dev/null +++ b/tests/chains/Nano/TestAccounts.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +namespace TW::Nano::tests { + +const auto kPrivateKey = "173c40e97fe2afcd24187e74f6b603cb949a5365e72fbdd065a6b165e2189e34"; +const auto kRepOfficial1 = "xrb_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4"; +const auto kRepNanode = "xrb_1nanode8ngaakzbck8smq6ru9bethqwyehomf79sae1k7xd47dkidjqzffeg"; + +} // namespace TW::Nano::tests diff --git a/tests/chains/Nano/TransactionCompilerTests.cpp b/tests/chains/Nano/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..88ec1cda1ca --- /dev/null +++ b/tests/chains/Nano/TransactionCompilerTests.cpp @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "TransactionCompiler.h" +#include "proto/Nano.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include +#include + +#include "HexCoding.h" +#include "Nano/Signer.h" +#include "PrivateKey.h" +#include "PublicKey.h" + +#include "TestAccounts.h" +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::Nano::tests { + +TEST(NanoCompiler, CompileWithSignatures) { + /// Step 1 : Prepare transaction input(protobuf) + auto coin = TWCoinTypeNano; + const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); + const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); + + auto input = Proto::SigningInput(); + input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + input.set_link_block(linkBlock.data(), linkBlock.size()); + input.set_representative(kRepOfficial1); + input.set_balance("96242336390000000000000000000"); + auto inputString = input.SerializeAsString(); + auto localInputData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, localInputData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(Common::Proto::OK, preSigningOutput.error()); + + auto preImage = data(preSigningOutput.data()); + auto preImageHash = data(preSigningOutput.data_hash()); + const auto ExpectedPreImageHash = "f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"; + ASSERT_EQ(ExpectedPreImageHash, hex(preImageHash)); + // Verify signature (pubkey & hash & signature) + Data signature = parse_hex("d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09"); + EXPECT_TRUE(publicKey.verify(signature, preImageHash)); + /// Step 3 : Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, localInputData, {signature}, {publicKey.bytes}); + const auto ExpectedOutJson = "{\"account\":\"nano_1bhbsc9yuh15anq3owu1izw1nk7bhhqefrkhfo954fyt8dk1q911buk1kk4c\"," + "\"balance\":\"96242336390000000000000000000\"," + "\"link\":\"491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507\"," + "\"link_as_account\":\"nano_1kazsap8mc481zbqbcqjytpf9mmigj87qr5k5fhf97579t4k8fa94octjx6d\"," + "\"previous\":\"0000000000000000000000000000000000000000000000000000000000000000\"," + "\"representative\":\"nano_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4\"," + "\"signature\":\"d247f6b90383b24e612569c75a12f11242f6e03b4914eadc7d941577dcf54a3a7cb7f0a4aba4246a40d9ebb5ee1e00b4a0a834ad5a1e7bef24e11f62b95a9e09\"," + "\"type\":\"state\"}"; + { + TW::Nano::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(signature), hex(output.signature())); + EXPECT_EQ(ExpectedPreImageHash, hex(output.block_hash())); + EXPECT_EQ(ExpectedOutJson, output.json()); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::Nano::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(localInputData.data(), (int)localInputData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::Nano::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + EXPECT_EQ(ExpectedOutJson, output.json()); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, localInputData, {signature, signature}, {publicKey.bytes}); + TW::Nano::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(0ul, output.json().size()); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, localInputData, {}, {}); + TW::Nano::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(0ul, output.json().size()); + EXPECT_EQ(Common::Proto::Error_invalid_params, output.error()); + } +} + +} // namespace TW::Nano::tests diff --git a/tests/chains/NativeZetaChain/TWCoinTypeTests.cpp b/tests/chains/NativeZetaChain/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..89e2cdafc2d --- /dev/null +++ b/tests/chains/NativeZetaChain/TWCoinTypeTests.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +TEST(TWNativeZetaChainCoinType, TWCoinType) { + const auto coin = TWCoinTypeNativeZetaChain; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("2DBB071DDD47985F4470A21E5943CE95D371AE4BDE2267E201D3553FB2BDCFDE")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "zetachain"); + assertStringsEqual(name, "NativeZetaChain"); + assertStringsEqual(symbol, "ZETA"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainCosmos); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0); + assertStringsEqual(txUrl, "https://explorer.zetachain.com/cosmos/tx/2DBB071DDD47985F4470A21E5943CE95D371AE4BDE2267E201D3553FB2BDCFDE"); + assertStringsEqual(accUrl, "https://explorer.zetachain.com/address/zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne"); +} diff --git a/tests/chains/Nebl/AddressTests.cpp b/tests/chains/Nebl/AddressTests.cpp new file mode 100644 index 00000000000..932f9dfd9b4 --- /dev/null +++ b/tests/chains/Nebl/AddressTests.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "Bitcoin/Address.h" +#include "PublicKey.h" +#include "PrivateKey.h" +#include +#include + +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(NeblAddress, Valid) { + ASSERT_TRUE(Address::isValid(std::string("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"))); + ASSERT_TRUE(TW::validateAddress(TWCoinTypeNebl, "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc")); +} + +TEST(NeblAddress, Invalid) { + ASSERT_FALSE(Address::isValid(std::string("TZzJsL7VSUVeTcLb12LHr5tUW9bcx1T4G3"))); +} + +TEST(NeblAddress, FromPrivateKey) { + auto privateKey = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto address = Address(publicKey, TWCoinTypeP2pkhPrefix(TWCoinTypeNebl)); + ASSERT_EQ(address.string(), "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); +} + +TEST(NeblAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("03787a4c5ff72dce6d97f9b6360dc302b2d8a833e8c570dcc124a96e5f564bb524"), TWPublicKeyTypeSECP256k1); + auto address = Address(publicKey, TWCoinTypeP2pkhPrefix(TWCoinTypeNebl)); + ASSERT_EQ(address.string(), "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + + auto addr = TW::deriveAddress(TWCoinTypeNebl, publicKey, TWDerivationBitcoinLegacy); + ASSERT_EQ(addr, "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); +} + +TEST(NeblAddress, FromString) { + auto address = Address("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + ASSERT_EQ(address.string(), "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + + auto data = TW::addressToData(TWCoinTypeNebl, "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + EXPECT_EQ(hex(data), "ae40b2142aba5ddd10f74d9440bfda8a36cbad5b"); + + // invalid address + data = TW::addressToData(TWCoinTypeNebl, "xZW297yHHzSdiiwqE1eN4bfm5tJULjVZ"); + EXPECT_EQ(data.size(), 0ul); +} diff --git a/tests/chains/Nebl/SignerTests.cpp b/tests/chains/Nebl/SignerTests.cpp new file mode 100644 index 00000000000..eb8cad61749 --- /dev/null +++ b/tests/chains/Nebl/SignerTests.cpp @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Verge/Signer.h" +#include "Verge/Transaction.h" +#include "Verge/TransactionBuilder.h" +#include "Bitcoin/Address.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" + +#include + +#include +#include + +using namespace TW; +using namespace TW::Bitcoin; + + +TEST(NeblSigner, Sign) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "NRrKgiZfT7jUdS3geoEBproA7hzZnDQAdr"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + input.set_coin_type(TWCoinTypeNebl); + input.set_time(1656474580); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto script0 = Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); + ASSERT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 980000000; + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = Verge::Signer::sign(input); + + ASSERT_EQ(hex(result.encoded()), + "01000000d4cbbb62017d40b2c80adbb0f44cbec466ae30f0316e0c1bf98110fd6d3f1b0fda47e1a6a5000000006a47304402207f77f7ed50ec56447fd108b2a9a693b2ac9f62e99b59dfa914f242510943187602204618fd9195050c763eb93644e51344f6c00e4dd93aa41bb42bce42c9e4cc53b6012103787a4c5ff72dce6d97f9b6360dc302b2d8a833e8c570dcc124a96e5f564bb524feffffff02002f6859000000001976a914412033ed457c72ca70bab5fbfdc03256bd2ce07d88ac009d693a000000001976a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac00000000" + ); +} + +TEST(NeblSigner, SignAnyoneCanPay) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "NRrKgiZfT7jUdS3geoEBproA7hzZnDQAdr"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAnyoneCanPay|TWBitcoinSigHashTypeSingle); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + input.set_coin_type(TWCoinTypeNebl); + input.set_time(1656474580); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto script0 = Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); + EXPECT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + + auto txHash1 = parse_hex("29bd442521ea303afb09ad2583f589a6527c9218c050882b6b8527bbe4d11766"); + std::reverse(txHash1.begin(), txHash1.end()); + + auto utxo1 = input.add_utxo(); + utxo1->mutable_out_point()->set_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_index(0); + utxo1->mutable_out_point()->set_sequence(4294967294); + utxo1->set_amount(200000000); + + auto script1 = Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); + ASSERT_EQ(hex(script1.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); + utxo1->set_script(script1.bytes.data(), script1.bytes.size()); + + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 1000000000; + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = Bitcoin::TransactionSigner::sign(input); + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + ASSERT_EQ(hex(serialized), + "01000000d4cbbb62017d40b2c80adbb0f44cbec466ae30f0316e0c1bf98110fd6d3f1b0fda47e1a6a5000000006b483045022100827715814a853164cf04b7106ef508eeadc5d868616756d5f598eab977c3ea2d022024ab244bfda0d31a4454ec70bedc07391fd956c94bb80f6164dc45188e0507d6832103787a4c5ff72dce6d97f9b6360dc302b2d8a833e8c570dcc124a96e5f564bb524feffffff02002f6859000000001976a914412033ed457c72ca70bab5fbfdc03256bd2ce07d88ac00ca9a3b000000001976a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac00000000" + ); +} + +TEST(NeblSigner, SignWithError) { + const int64_t amount = 1500000000; + const std::string toAddress = "NRrKgiZfT7jUdS3geoEBproA7hzZnDQAdr"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + input.set_coin_type(TWCoinTypeNebl); + input.set_time(1656474580); + + // Sign + auto result = Verge::Signer::sign(input); + + ASSERT_NE(result.error(), Common::Proto::OK); + + // PreImage + auto preResult = Verge::Signer::preImageHashes(input); + ASSERT_NE(preResult.error(), Common::Proto::OK); +} + diff --git a/tests/chains/Nebl/TWAnySignerTests.cpp b/tests/chains/Nebl/TWAnySignerTests.cpp new file mode 100644 index 00000000000..745d6ed792c --- /dev/null +++ b/tests/chains/Nebl/TWAnySignerTests.cpp @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include + +#include "Bitcoin/Signer.h" +#include "Bitcoin/Address.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(TWAnySignerNebl, Sign) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "NRrKgiZfT7jUdS3geoEBproA7hzZnDQAdr"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + input.set_coin_type(TWCoinTypeNebl); + input.set_time(1656474580); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto script0 = Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); + ASSERT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + Bitcoin::Proto::TransactionPlan plan; + { + ANY_PLAN(input, plan, TWCoinTypeNebl); + + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(980000000); + + *input.mutable_plan() = plan; + } + + Bitcoin::Proto::SigningOutput output; + { + ANY_SIGN(input, TWCoinTypeNebl); + ASSERT_EQ(output.error(), Common::Proto::OK); + } + + // Sign + ASSERT_EQ(hex(output.encoded()), + "01000000d4cbbb62017d40b2c80adbb0f44cbec466ae30f0316e0c1bf98110fd6d3f1b0fda47e1a6a5000000006a47304402207f77f7ed50ec56447fd108b2a9a693b2ac9f62e99b59dfa914f242510943187602204618fd9195050c763eb93644e51344f6c00e4dd93aa41bb42bce42c9e4cc53b6012103787a4c5ff72dce6d97f9b6360dc302b2d8a833e8c570dcc124a96e5f564bb524feffffff02002f6859000000001976a914412033ed457c72ca70bab5fbfdc03256bd2ce07d88ac009d693a000000001976a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac00000000" + ); +} diff --git a/tests/chains/Nebl/TWCoinTypeTests.cpp b/tests/chains/Nebl/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..817ad95c8cc --- /dev/null +++ b/tests/chains/Nebl/TWCoinTypeTests.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// +#include "TestUtilities.h" +#include +#include + + +TEST(TWNeblCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNebl)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("53713d5208e2a1bc68ef8bd5c851f113cf5e1675d22ecf3406f38819ab9187b3")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNebl, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNebl, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNebl)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNebl)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNebl), 8); + ASSERT_EQ(TWBlockchainVerge, TWCoinTypeBlockchain(TWCoinTypeNebl)); + ASSERT_EQ(0x70, TWCoinTypeP2shPrefix(TWCoinTypeNebl)); + ASSERT_EQ(0x35, TWCoinTypeP2pkhPrefix(TWCoinTypeNebl)); + assertStringsEqual(symbol, "NEBL"); + assertStringsEqual(txUrl, "https://explorer.nebl.io/tx/53713d5208e2a1bc68ef8bd5c851f113cf5e1675d22ecf3406f38819ab9187b3"); + assertStringsEqual(accUrl, "https://explorer.nebl.io/address/NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + assertStringsEqual(id, "Nebl"); + assertStringsEqual(name, "Nebl"); +} \ No newline at end of file diff --git a/tests/chains/Nebl/TWNeblAddressTests.cpp b/tests/chains/Nebl/TWNeblAddressTests.cpp new file mode 100644 index 00000000000..085680ffaaa --- /dev/null +++ b/tests/chains/Nebl/TWNeblAddressTests.cpp @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWNebl, Address) { + auto string = STRING("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeNebl)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "ae40b2142aba5ddd10f74d9440bfda8a36cbad5b"); +} diff --git a/tests/chains/Nebl/TransactionBuilderTests.cpp b/tests/chains/Nebl/TransactionBuilderTests.cpp new file mode 100644 index 00000000000..09bdcb8218d --- /dev/null +++ b/tests/chains/Nebl/TransactionBuilderTests.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Verge/Transaction.h" +#include "Verge/TransactionBuilder.h" +#include "Bitcoin/TransactionPlan.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" + +#include + +#include +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(NeblTransactionBuilder, BuildWithTime) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "NRrKgiZfT7jUdS3geoEBproA7hzZnDQAdr"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + input.set_coin_type(TWCoinTypeNebl); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto script0 = Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); + ASSERT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 980000000; + + auto tx = Verge::TransactionBuilder::build(plan, input).payload(); + ASSERT_NE(tx.time, 0ul); +} \ No newline at end of file diff --git a/tests/chains/Nebl/TransactionCompilerTests.cpp b/tests/chains/Nebl/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..30669f9ad36 --- /dev/null +++ b/tests/chains/Nebl/TransactionCompilerTests.cpp @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" +#include "Bitcoin/Script.h" + +#include +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + + + +TEST(NeblCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeNebl; + + // tx on mainnet + // https://Nebl-blockchain.info/tx/21314157b60ddacb842d2a749429c4112724b7a078adb9e77ba502ea2dd7c230 + + const int64_t amount = 100000000; + const int64_t fee = 226; + const std::string toAddress = "NRrKgiZfT7jUdS3geoEBproA7hzZnDQAdr"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); + input.set_coin_type(coin); + input.set_time(1584059579); + + auto txHash0 = parse_hex("ee839754c8e93d620cbec9a1c51e7b69016d00839741b03af2c039852d941212"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(1); + utxo0->mutable_out_point()->set_sequence(4294967295); + utxo0->set_amount(20000000000000); + + auto script0 = TW::Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); + ASSERT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + EXPECT_EQ(input.utxo_size(), 1); + // Plan + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(input, plan, coin); + + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(19999899999774); + + // Extend input with accepted plan + *input.mutable_plan() = plan; + + // Serialize input + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + /// Step 2: Obtain preimage hashes + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + TW::Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), (int)preImageHashes.size())); + + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].data_hash()), + "e7d7c67a125a506d46fa75f86a575360239d301a19621f9f231c46d889fe1a3b"); + + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].public_key_hash()), "ae40b2142aba5ddd10f74d9440bfda8a36cbad5b"); + + + + auto publicKeyHex = "03787a4c5ff72dce6d97f9b6360dc302b2d8a833e8c570dcc124a96e5f564bb524"; + auto publicKey = PublicKey(parse_hex(publicKeyHex), TWPublicKeyTypeSECP256k1); + + auto preImageHash = preSigningOutput.hash_public_keys()[0].data_hash(); + auto signature = parse_hex("3045022100a2dd4cbc8516a7b19516bce90fde7a3c4836c1277ddc61ca80d27d5711bcefc302200e049a3c2bdfd7ebacb7be48914a7cad8ea0db0695fb28ab645acdb12c413cb3"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE( + publicKey.verifyAsDER(signature, TW::Data(preImageHash.begin(), preImageHash.end()))); + + // Simulate signatures, normally obtained from signature server. + std::vector signatureVec; + std::vector pubkeyVec; + signatureVec.push_back(signature); + pubkeyVec.push_back(publicKey.bytes); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, pubkeyVec); + + const auto ExpectedTx = + "01000000bbd46a5e011212942d8539c0f23ab0419783006d01697b1ec5a1c9be0c623de9c8549783ee010000006b483045022100a2dd4cbc8516a7b19516bce90fde7a3c4836c1277ddc61ca80d27d5711bcefc302200e049a3c2bdfd7ebacb7be48914a7cad8ea0db0695fb28ab645acdb12c413cb3012103787a4c5ff72dce6d97f9b6360dc302b2d8a833e8c570dcc124a96e5f564bb524ffffffff0200e1f505000000001976a914412033ed457c72ca70bab5fbfdc03256bd2ce07d88ac1e5eef96301200001976a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac00000000"; + { + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: not enough signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, pubkeyVec); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/Nebulas/AddressTests.cpp b/tests/chains/Nebulas/AddressTests.cpp new file mode 100644 index 00000000000..8efe6395086 --- /dev/null +++ b/tests/chains/Nebulas/AddressTests.cpp @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Nebulas/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include + +namespace TW::Nebulas::tests { + +using namespace std; + +TEST(NebulasAddress, Invalid) { + ASSERT_FALSE(Address::isValid("abc")); + ASSERT_FALSE(Address::isValid("a1TgpFZWCMmFd2sphb6RKsCvsEyMCNa2Yyv")); + ASSERT_FALSE(Address::isValid("n2TgpFZWCMmFd2sphb6RKsCvsEyMCNa2Yyv")); + // normal address test + ASSERT_TRUE(Address::isValid("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY")); + // contract address test + ASSERT_TRUE(Address::isValid("n1zUNqeBPvsyrw5zxp9mKcDdLTjuaEL7s39")); +} + +TEST(NebulasAddress, String) { + ASSERT_THROW(Address("abc"), std::invalid_argument); + ASSERT_EQ(Address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY").string(), + "n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); + ASSERT_EQ(Address(Base58::decode("n1TgpFZWCMmFd2sphb6RKsCvsEyMCNa2Yyv")).string(), + "n1TgpFZWCMmFd2sphb6RKsCvsEyMCNa2Yyv"); + + const auto address = Address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); + ASSERT_EQ(address.string(), "n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); +} + +TEST(NebulasAddress, Data) { + Data data; + EXPECT_THROW(Address(data).string(), std::invalid_argument); + ASSERT_EQ(Address(Base58::decode("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY")).string(), + "n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); +} + +TEST(NebulasAddress, FromPrivateKey) { + const auto privateKey = PrivateKey(parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); + + EXPECT_THROW(Address(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)), std::invalid_argument); +} + +} // namespace TW::Nebulas::tests diff --git a/tests/chains/Nebulas/SignerTests.cpp b/tests/chains/Nebulas/SignerTests.cpp new file mode 100644 index 00000000000..c904a4a2fe4 --- /dev/null +++ b/tests/chains/Nebulas/SignerTests.cpp @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Nebulas/Address.h" +#include "Nebulas/Signer.h" +#include + +#include + +namespace TW::Nebulas { + +class SignerExposed : public Signer { + public: + SignerExposed(boost::multiprecision::uint256_t chainID) : Signer(chainID) {} + using Signer::hash; +}; + +TEST(NebulasSigner, Hash) { + auto from = Address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); + auto to = Address("n1SAeQRVn33bamxN4ehWUT7JGdxipwn8b17"); + auto transaction = Transaction( + /* to: */ from, + /* nonce: */ 7, + /* gasPrice: */ 1000000, + /* gasLimit: */ 200000, + /* to: */ to, + /* amount: */ 11000000000000000000ULL, + /* timestamp: */ 1560052938, + /* payload: */ std::string()); + auto signer = SignerExposed(1); + auto hash = signer.hash(transaction); + + ASSERT_EQ(hex(hash), "505dd4769de32a9c4bb6d6afd4f8e1ea6474815fd43484d8917cbd9e0993b885"); +} + +} // namespace TW::Nebulas diff --git a/tests/chains/Nebulas/TWAnySignerTests.cpp b/tests/chains/Nebulas/TWAnySignerTests.cpp new file mode 100644 index 00000000000..21ed9d0c0de --- /dev/null +++ b/tests/chains/Nebulas/TWAnySignerTests.cpp @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include "HexCoding.h" +#include "uint256.h" +#include "proto/Nebulas.pb.h" + +#include + +namespace TW::Nebulas::tests { + +TEST(TWAnySignerNebulas, Sign) { + Proto::SigningInput input; + input.set_from_address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); + input.set_to_address("n1SAeQRVn33bamxN4ehWUT7JGdxipwn8b17"); + auto value = store(uint256_t(7)); + input.set_nonce(value.data(), value.size()); + value = store(uint256_t(1000000)); + input.set_gas_price(value.data(), value.size()); + value = store(uint256_t(200000)); + input.set_gas_limit(value.data(), value.size()); + value = store(uint256_t(11000000000000000000ULL)); + input.set_amount(value.data(), value.size()); + input.set_payload(""); + value = store(uint256_t(1560052938)); + input.set_timestamp(value.data(), value.size()); + + const auto privateKey = parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b"); + input.set_private_key(privateKey.data(), privateKey.size()); + auto chainid = store(uint256_t(1)); + input.set_chain_id(chainid.data(), chainid.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNebulas); + + EXPECT_EQ(hex(output.signature()), "f53f4a9141ff8e462b094138eccd8c3a5d7865f9e9ab509626c78460a9e0b0fc35f7ed5ba1795ceb81a5e46b7580a6f7fb431d44fdba92515399cf6a8e47e71500"); + EXPECT_EQ(output.raw(), "CiBQXdR2neMqnEu21q/U+OHqZHSBX9Q0hNiRfL2eCZO4hRIaGVefwtw23wEobqA40/7aIwQHghETxH4r+50aGhlXf89CeLWgHFjKu9/6tn4KNbelsMDAIIi2IhAAAAAAAAAAAJin2bgxTAAAKAcwyony5wU6CAoGYmluYXJ5QAFKEAAAAAAAAAAAAAAAAAAPQkBSEAAAAAAAAAAAAAAAAAADDUBYAWJB9T9KkUH/jkYrCUE47M2MOl14Zfnpq1CWJseEYKngsPw19+1boXlc64Gl5Gt1gKb3+0MdRP26klFTmc9qjkfnFQA="); +} + +} // namespace TW::Nebulas::tests diff --git a/tests/chains/Nebulas/TWCoinTypeTests.cpp b/tests/chains/Nebulas/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..2d302a0aa52 --- /dev/null +++ b/tests/chains/Nebulas/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWNebulasCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNebulas)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNebulas, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNebulas, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNebulas)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNebulas)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNebulas), 18); + ASSERT_EQ(TWBlockchainNebulas, TWCoinTypeBlockchain(TWCoinTypeNebulas)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeNebulas)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeNebulas)); + assertStringsEqual(symbol, "NAS"); + assertStringsEqual(txUrl, "https://explorer.nebulas.io/#/tx/t123"); + assertStringsEqual(accUrl, "https://explorer.nebulas.io/#/address/a12"); + assertStringsEqual(id, "nebulas"); + assertStringsEqual(name, "Nebulas"); +} diff --git a/tests/Nebulas/TWNebulasAddressTests.cpp b/tests/chains/Nebulas/TWNebulasAddressTests.cpp similarity index 89% rename from tests/Nebulas/TWNebulasAddressTests.cpp rename to tests/chains/Nebulas/TWNebulasAddressTests.cpp index 32e7ae08fa4..e41a4e54e09 100644 --- a/tests/Nebulas/TWNebulasAddressTests.cpp +++ b/tests/chains/Nebulas/TWNebulasAddressTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/Nebulas/TransactionTests.cpp b/tests/chains/Nebulas/TransactionTests.cpp new file mode 100644 index 00000000000..d937cb73734 --- /dev/null +++ b/tests/chains/Nebulas/TransactionTests.cpp @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Nebulas/Signer.h" +#include "HexCoding.h" +#include "Base64.h" +#include "PrivateKey.h" + +#include + +extern std::string htmlescape(const std::string& str); + +namespace TW::Nebulas::tests { + +using namespace std; + +TEST(NebulasTransaction, serialize) { + auto from = Address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); + auto to = Address("n1SAeQRVn33bamxN4ehWUT7JGdxipwn8b17"); + auto transaction = Transaction( + /* to: */ from, + /* nonce: */ 7, + /* gasPrice: */ 1000000, + /* gasLimit: */ 200000, + /* to: */ to, + /* amount: */ 11000000000000000000ULL, + /* timestamp: */ 1560052938, + /* payload: */ std::string()); + + const auto privateKey = PrivateKey(parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b")); + auto signer = Signer(1); + signer.sign(privateKey, transaction); + transaction.serializeToRaw(); + + ASSERT_EQ(TW::Base64::encode(transaction.raw), "CiBQXdR2neMqnEu21q/U+OHqZHSBX9Q0hNiRfL2eCZO4hRIaGVefwtw23wEobqA40/7aIwQHghETxH4r+50aGhlXf89CeLWgHFjKu9/6tn4KNbelsMDAIIi2IhAAAAAAAAAAAJin2bgxTAAAKAcwyony5wU6CAoGYmluYXJ5QAFKEAAAAAAAAAAAAAAAAAAPQkBSEAAAAAAAAAAAAAAAAAADDUBYAWJB9T9KkUH/jkYrCUE47M2MOl14Zfnpq1CWJseEYKngsPw19+1boXlc64Gl5Gt1gKb3+0MdRP26klFTmc9qjkfnFQA="); +} + +TEST(NebulasTransaction, binaryPayload) { + auto from = Address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); + auto to = Address("n1SAeQRVn33bamxN4ehWUT7JGdxipwn8b17"); + auto transaction = Transaction( + /* to: */ from, + /* nonce: */ 7, + /* gasPrice: */ 1000000, + /* gasLimit: */ 200000, + /* to: */ to, + /* amount: */ 11000000000000000000ULL, + /* timestamp: */ 1560052938, + /* payload: */ std::string("{\"binary\":\"test\"}")); + + const auto privateKey = PrivateKey(parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b")); + auto signer = Signer(1); + signer.sign(privateKey, transaction); + ASSERT_EQ(TW::Base64::encode(transaction.raw), "CiB1Oqj7bxLQMHEoNyg/vFHmsTrGdkpTf/5qFDkYPB3bkxIaGVefwtw23wEobqA40/7aIwQHghETxH4r+50aGhlXf89CeLWgHFjKu9/6tn4KNbelsMDAIIi2IhAAAAAAAAAAAJin2bgxTAAAKAcwyony5wU6PQoGYmluYXJ5EjN7IkRhdGEiOnsiZGF0YSI6WzExNiwxMDEsMTE1LDExNl0sInR5cGUiOiJCdWZmZXIifX1AAUoQAAAAAAAAAAAAAAAAAA9CQFIQAAAAAAAAAAAAAAAAAAMNQFgBYkGHXq+JWPaEyeB19bqL3QB5jyM961WLq7PMTpnGM4iLtBjCkngjS81kgPM2TE4qKDcpzqjum/NccrZtUPQLGk0MAQ=="); +} + +TEST(NebulasTransaction, htmlescape) { + // test for escaped label + auto test = ("test&<>\x20\x28\x20\x29"); + auto result = htmlescape(test); + ASSERT_EQ(result, "test\\u0026\\u003c\\u003e\\u2028\\u2029"); +} + +TEST(NebulasTransaction, serializeUnsigned) { + auto from = Address("n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); + auto to = Address("n1SAeQRVn33bamxN4ehWUT7JGdxipwn8b17"); + auto transaction = Transaction( + /* to: */ from, + /* nonce: */ 7, + /* gasPrice: */ 1000000, + /* gasLimit: */ 200000, + /* to: */ to, + /* amount: */ 11000000000000000000ULL, + /* timestamp: */ 1560052938, + /* payload: */ std::string()); + + ASSERT_THROW(transaction.serializeToRaw(), std::logic_error); +} + +} // namespace TW::Nebulas::tests diff --git a/tests/chains/Neon/TWCoinTypeTests.cpp b/tests/chains/Neon/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..d0a506e4119 --- /dev/null +++ b/tests/chains/Neon/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWNeonCoinType, TWCoinType) { + const auto coin = TWCoinTypeNeon; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x77d86af2c6f02f14ef13ca52bf54864d92fcc4b32d8e884e225061c006738ed6")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xfa4a8650e7bebb918859c280a86f9661bed29877")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "neon"); + assertStringsEqual(name, "Neon"); + assertStringsEqual(symbol, "NEON"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "245022934"); + assertStringsEqual(txUrl, "https://neonscan.org/tx/0x77d86af2c6f02f14ef13ca52bf54864d92fcc4b32d8e884e225061c006738ed6"); + assertStringsEqual(accUrl, "https://neonscan.org/address/0xfa4a8650e7bebb918859c280a86f9661bed29877"); +} diff --git a/tests/chains/Nervos/AddressTests.cpp b/tests/chains/Nervos/AddressTests.cpp new file mode 100644 index 00000000000..4ff4d1246d5 --- /dev/null +++ b/tests/chains/Nervos/AddressTests.cpp @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HDWallet.h" +#include "HexCoding.h" +#include "Nervos/Address.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include + +namespace TW::Nervos::tests { + +TEST(NervosAddress, Valid) { + ASSERT_TRUE(Address::isValid("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9e" + "rg8furras980hksatlslfaktks7epf25")); + ASSERT_TRUE(Address::isValid("ckb1qyqvfdgvtjxswncx8mq2wl0dp6hlp7nmvhdqcecnt6")); + ASSERT_TRUE(Address::isValid("ckb1qjda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xw3394p3wg6" + "p60qclvpfmaa582lu860dja5h0fk0v")); +} + +TEST(NervosAddress, Invalid) { + ASSERT_FALSE(Address::isValid("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9" + "erg8furras980hksatlslfaktks7epf26")); + ASSERT_FALSE(Address::isValid("ckb1qyqvfdgvtjxswncx8mq2wl0dp6hlp7nmvhdqcecnt7")); + ASSERT_FALSE(Address::isValid("ckb1qjda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xw3394p3wg" + "6p60qclvpfmaa582lu860dja5h0fk0w")); +} + +TEST(NervosAddress, FromPrivateKey) { + auto privateKey = + PrivateKey(parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb")); + auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); + ASSERT_EQ(address.string(), "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9er" + "g8furras980hksatlslfaktks7epf25"); +} + +TEST(NervosAddress, FromPublicKey) { + auto publicKey = + PublicKey(parse_hex("026c9e4cbb95d4b3a123c1fc80795feacc38029683a1b3e16bccf49bba25fb2858"), + TWPublicKeyTypeSECP256k1); + auto address = Address(publicKey); + ASSERT_EQ(address.string(), "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9er" + "g8furras980hksatlslfaktks7epf25"); +} + +TEST(NervosAddress, FromString) { + auto address1 = Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8fu" + "rras980hksatlslfaktks7epf25"); + ASSERT_EQ(address1.string(), "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9e" + "rg8furras980hksatlslfaktks7epf25"); + auto address2 = Address("ckb1qyqvfdgvtjxswncx8mq2wl0dp6hlp7nmvhdqcecnt6"); + ASSERT_EQ(address2.string(), "ckb1qyqvfdgvtjxswncx8mq2wl0dp6hlp7nmvhdqcecnt6"); + auto address3 = Address("ckb1qjda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xw3394p3wg6p60qc" + "lvpfmaa582lu860dja5h0fk0v"); + ASSERT_EQ(address3.string(), "ckb1qjda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xw3394p3wg6" + "p60qclvpfmaa582lu860dja5h0fk0v"); +} + +TEST(TWNervosAddress, AddressFromPublicKey) { + auto privateKey = + PrivateKey(parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + ASSERT_EQ(publicKey.bytes.size(), 33ul); + auto address = Address(publicKey); + ASSERT_EQ(address.string(), "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9er" + "g8furras980hksatlslfaktks7epf25"); +} + +TEST(NervosAddress, AddressFromString) { + auto address = Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8fur" + "ras980hksatlslfaktks7epf25"); + ASSERT_EQ(address.string(), "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9er" + "g8furras980hksatlslfaktks7epf25"); +} + +TEST(NervosAddress, AddressFromWallet) { + auto hdWallet = HDWallet( + "alpha draw toss question picnic endless recycle wrong enable roast success palm", ""); + auto addressString = hdWallet.deriveAddress(TWCoinTypeNervos); + ASSERT_EQ(addressString, "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8f" + "urras980hksatlslfaktks7epf25"); +} + +} // namespace TW::Nervos::tests diff --git a/tests/chains/Nervos/SignerTests.cpp b/tests/chains/Nervos/SignerTests.cpp new file mode 100644 index 00000000000..af862b5f84a --- /dev/null +++ b/tests/chains/Nervos/SignerTests.cpp @@ -0,0 +1,927 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Nervos/Address.h" +#include "Nervos/Cell.h" +#include "Nervos/Serialization.h" +#include "Nervos/Signer.h" +#include "Nervos/Transaction.h" +#include "Nervos/TransactionPlan.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "uint256.h" +#include "TestUtilities.h" +#include + +#include + +namespace TW::Nervos::tests { + +std::vector getPrivateKeys(Proto::SigningInput& input) { + std::vector privateKeys; + privateKeys.reserve(input.private_key_size()); + for (auto&& privateKey : input.private_key()) { + privateKeys.emplace_back(privateKey); + } + return privateKeys; +} + +Proto::SigningInput getInput1() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_native_transfer(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02" + "wectaumxn0664yw2jd53lqk4mxg3"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78" + "yze6eyfyvd537z66ur22c9mmrgz82ama"); + operation.set_amount(10000000000); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(100000000000); + *cell1.mutable_out_point() = + OutPoint(parse_hex("71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3"), 1) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(20000000000); + *cell2.mutable_out_point() = + OutPoint(parse_hex("71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3"), 0) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto privateKey = parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey.begin(), privateKey.end())); + + return input; +} + +void checkOutput1(Transaction& tx) { + // https://explorer.nervos.org/transaction/0xf2c32afde7e72011985583873bc16c0a3c01fc01fc161eb4b914fcf84c53cdf8 + ASSERT_EQ(tx.hash(), + parse_hex("f2c32afde7e72011985583873bc16c0a3c01fc01fc161eb4b914fcf84c53cdf8")); + + ASSERT_EQ(tx.cellDeps.size(), 1ul); + + ASSERT_EQ(tx.cellDeps[0].outPoint.txHash, + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")); + ASSERT_EQ(tx.cellDeps[0].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[0].depType, DepType::DepGroup); + + ASSERT_EQ(tx.headerDeps.size(), 0ul); + + ASSERT_EQ(tx.inputs.size(), 1ul); + + ASSERT_EQ(tx.inputs[0].previousOutput.txHash, + parse_hex("71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3")); + ASSERT_EQ(tx.inputs[0].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[0].since, 0ul); + + ASSERT_EQ(tx.outputs.size(), 2ul); + ASSERT_EQ(tx.outputsData.size(), 2ul); + + ASSERT_EQ(tx.outputs[0].capacity, 10000000000ul); + ASSERT_EQ(tx.outputs[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[0].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].lock.args, parse_hex("ab201f55b02f53b385f79b34dfad548e549b48fc")); + ASSERT_EQ(tx.outputs[0].type.codeHash.size(), 0ul); + ASSERT_EQ(tx.outputs[0].type.args.size(), 0ul); + ASSERT_EQ(tx.outputsData[0].size(), 0ul); + + ASSERT_EQ(tx.outputs[1].capacity, 9999999536ul); + ASSERT_EQ(tx.outputs[1].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[1].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[1].lock.args, parse_hex("b0d65be39059d6489231b48f85ad706a560bbd8d")); + ASSERT_EQ(tx.outputs[1].type.codeHash.size(), 0ul); + ASSERT_EQ(tx.outputs[1].type.args.size(), 0ul); + ASSERT_EQ(tx.outputsData[1].size(), 0ul); + + ASSERT_EQ(tx.serializedWitnesses.size(), 1ul); + ASSERT_EQ( + hex(tx.serializedWitnesses[0]), + "55000000100000005500000055000000410000002a9ef2ad7829e5ea0c7a32735d29a0cb2ec20434f6fd5bf6e2" + "9cda56b28e08140156191cbbf80313d3c9cae4b74607acce7b28eb21d52ef058ed8491cdde70b700"); +} + +void checkPlan1(TransactionPlan& txPlan) { + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + + ASSERT_EQ(txPlan.cellDeps.size(), 1ul); + + ASSERT_EQ(txPlan.cellDeps[0].outPoint.txHash, + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")); + ASSERT_EQ(txPlan.cellDeps[0].outPoint.index, 0ul); + ASSERT_EQ(txPlan.cellDeps[0].depType, DepType::DepGroup); + + ASSERT_EQ(txPlan.headerDeps.size(), 0ul); + + ASSERT_EQ(txPlan.selectedCells.size(), 1ul); + + ASSERT_EQ(txPlan.selectedCells[0].outPoint.txHash, + parse_hex("71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3")); + ASSERT_EQ(txPlan.selectedCells[0].outPoint.index, 0ul); + ASSERT_EQ(txPlan.selectedCells[0].capacity, 20000000000ul); + ASSERT_EQ(txPlan.selectedCells[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(txPlan.selectedCells[0].lock.hashType, HashType::Type1); + ASSERT_EQ(txPlan.selectedCells[0].lock.args, + parse_hex("c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da")); + ASSERT_EQ(txPlan.selectedCells[0].type.codeHash.size(), 0ul); + ASSERT_EQ(txPlan.selectedCells[0].type.args.size(), 0ul); + ASSERT_EQ(txPlan.selectedCells[0].data.size(), 0ul); + + ASSERT_EQ(txPlan.outputs.size(), 2ul); + ASSERT_EQ(txPlan.outputsData.size(), 2ul); + + ASSERT_EQ(txPlan.outputs[0].capacity, 10000000000ul); + ASSERT_EQ(txPlan.outputs[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(txPlan.outputs[0].lock.hashType, HashType::Type1); + ASSERT_EQ(txPlan.outputs[0].lock.args, parse_hex("ab201f55b02f53b385f79b34dfad548e549b48fc")); + ASSERT_EQ(txPlan.outputs[0].type.codeHash.size(), 0ul); + ASSERT_EQ(txPlan.outputs[0].type.args.size(), 0ul); + ASSERT_EQ(txPlan.outputsData[0].size(), 0ul); + + ASSERT_EQ(txPlan.outputs[1].capacity, 9999999536ul); + ASSERT_EQ(txPlan.outputs[1].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(txPlan.outputs[1].lock.hashType, HashType::Type1); + ASSERT_EQ(txPlan.outputs[1].lock.args, parse_hex("b0d65be39059d6489231b48f85ad706a560bbd8d")); + ASSERT_EQ(txPlan.outputs[1].type.codeHash.size(), 0ul); + ASSERT_EQ(txPlan.outputs[1].type.args.size(), 0ul); + ASSERT_EQ(txPlan.outputsData[1].size(), 0ul); +} + +TEST(NervosSigner, PlanAndSign_Native_Simple) { + auto input = getInput1(); + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + checkPlan1(txPlan); + Transaction tx; + tx.build(txPlan); + auto error = tx.sign(getPrivateKeys(input)); + ASSERT_EQ(error, Common::Proto::SigningError::OK); + checkOutput1(tx); +} + +TEST(NervosSigner, Sign_NegativeMissingKey) { + auto input = getInput1(); + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + Transaction tx; + tx.build(txPlan); + auto privateKey = parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61fec"); + *input.mutable_private_key(0) = std::string(privateKey.begin(), privateKey.end()); + auto error = tx.sign(getPrivateKeys(input)); + ASSERT_EQ(error, Common::Proto::Error_missing_private_key); +} + +TEST(NervosSigner, Sign_NegativeNotEnoughUtxos) { + auto input = getInput1(); + auto& operation = *input.mutable_native_transfer(); + operation.set_amount(1000000000000); + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::Error_not_enough_utxos); +} + +Proto::SigningInput getInput2() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_native_transfer(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02" + "wectaumxn0664yw2jd53lqk4mxg3"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78" + "yze6eyfyvd537z66ur22c9mmrgz82ama"); + operation.set_use_max_amount(true); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(11410040620); + *cell1.mutable_out_point() = + OutPoint(parse_hex("c75567c80dc9b97aaf4e5c23f4c7f37b077f2b33a50dd7abd952abfbd5beb247"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto privateKey = parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey.begin(), privateKey.end())); + + return input; +} + +TEST(NervosSigner, Sign_Native_SendMaximum) { + auto input = getInput2(); + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + Transaction tx; + tx.build(txPlan); + auto error = tx.sign(getPrivateKeys(input)); + + ASSERT_EQ(error, Common::Proto::SigningError::OK); + + // https://explorer.nervos.org/transaction/0x298f5e04b6900796614b89062eb96cec63c3b2c460d01058736a793b567bc5c8 + ASSERT_EQ(tx.hash(), + parse_hex("298f5e04b6900796614b89062eb96cec63c3b2c460d01058736a793b567bc5c8")); + + ASSERT_EQ(tx.cellDeps.size(), 1ul); + + ASSERT_EQ(tx.cellDeps[0].outPoint.txHash, + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")); + ASSERT_EQ(tx.cellDeps[0].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[0].depType, DepType::DepGroup); + + ASSERT_EQ(tx.headerDeps.size(), 0ul); + + ASSERT_EQ(tx.inputs.size(), 1ul); + + ASSERT_EQ(tx.inputs[0].previousOutput.txHash, + parse_hex("c75567c80dc9b97aaf4e5c23f4c7f37b077f2b33a50dd7abd952abfbd5beb247")); + ASSERT_EQ(tx.inputs[0].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[0].since, 0ul); + + ASSERT_EQ(tx.outputs.size(), 1ul); + ASSERT_EQ(tx.outputsData.size(), 1ul); + + ASSERT_EQ(tx.outputs[0].capacity, 11410040265ul); + ASSERT_EQ(tx.outputs[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[0].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].lock.args, parse_hex("ab201f55b02f53b385f79b34dfad548e549b48fc")); + ASSERT_EQ(tx.outputs[0].type.codeHash.size(), 0ul); + ASSERT_EQ(tx.outputs[0].type.args.size(), 0ul); + ASSERT_EQ(tx.outputsData[0].size(), 0ul); + + ASSERT_EQ(tx.serializedWitnesses.size(), 1ul); + ASSERT_EQ( + hex(tx.serializedWitnesses[0]), + "5500000010000000550000005500000041000000daf6e65e5a1fe447a4feb7199886b6635c44738e04ea594576" + "08fb1c447e068026529d57b02014ddc144622f886153df426853f22083f8891461eeb50b5ce97d01"); +} + +Proto::SigningInput getInput3() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_sudt_transfer(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02" + "wectaumxn0664yw2jd53lqk4mxg3"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78" + "yze6eyfyvd537z66ur22c9mmrgz82ama"); + uint256_t amount = 1000000000000000; + operation.set_amount(toString(amount)); + input.set_byte_fee(1); + auto sudtAddress = + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8"); + *operation.mutable_sudt_address() = std::string(sudtAddress.begin(), sudtAddress.end()); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(14399998906); + *cell1.mutable_out_point() = + OutPoint(parse_hex("5b12911e7413e011f251c1fb5fae4e76fd5fcae4f0d4c6412dcc5b0bfcece823"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(14400000000); + *cell2.mutable_out_point() = + OutPoint(parse_hex("e118bd11a73d381daf288381ce182d92b6cf2f52d25886bbda9e1a61525c7c4a"), 0) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + *cell2.mutable_type() = + Script(parse_hex("5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5"), + HashType::Type1, + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8")) + .proto(); + auto cell2Data = parse_hex("00e0e4c9b9f84f000000000000000000"); + *cell2.mutable_data() = std::string(cell2Data.begin(), cell2Data.end()); + + auto& cell3 = *input.add_cell(); + cell3.set_capacity(8210025567); + *cell3.mutable_out_point() = + OutPoint(parse_hex("09a45a15e48f985b554a0b6e5f0857913cc492ec061cc9b0b2befa4b24609a4a"), 1) + .proto(); + *cell3.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqds6ed78yze6eyfyvd537z66ur22c9mmrgz82ama")) + .proto(); + + auto privateKey1 = + parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey1.begin(), privateKey1.end())); + auto privateKey2 = + parse_hex("0c8859a9d9084a8c2b55963268b352e258756f9240f2a1f4645c610ed191dae9"); + input.add_private_key(std::string(privateKey2.begin(), privateKey2.end())); + + return input; +} + +TEST(NervosSigner, Sign_SUDT_Simple) { + auto input = getInput3(); + + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + Transaction tx; + tx.build(txPlan); + auto error = tx.sign(getPrivateKeys(input)); + + ASSERT_EQ(error, Common::Proto::SigningError::OK); + + // https://explorer.nervos.org/transaction/0x9b15f2bea26b98201540d8e20e8b1c21d96dd77ad246520b405c6aabb7173371 + ASSERT_EQ(tx.hash(), + parse_hex("9b15f2bea26b98201540d8e20e8b1c21d96dd77ad246520b405c6aabb7173371")); + + ASSERT_EQ(tx.cellDeps.size(), 2ul); + + ASSERT_EQ(tx.cellDeps[0].outPoint.txHash, + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")); + ASSERT_EQ(tx.cellDeps[0].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[0].depType, DepType::DepGroup); + + ASSERT_EQ(tx.cellDeps[1].outPoint.txHash, + parse_hex("c7813f6a415144643970c2e88e0bb6ca6a8edc5dd7c1022746f628284a9936d5")); + ASSERT_EQ(tx.cellDeps[1].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[1].depType, DepType::Code); + + ASSERT_EQ(tx.headerDeps.size(), 0ul); + + ASSERT_EQ(tx.inputs.size(), 3ul); + + ASSERT_EQ(tx.inputs[0].previousOutput.txHash, + parse_hex("e118bd11a73d381daf288381ce182d92b6cf2f52d25886bbda9e1a61525c7c4a")); + ASSERT_EQ(tx.inputs[0].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[0].since, 0ul); + + ASSERT_EQ(tx.inputs[1].previousOutput.txHash, + parse_hex("09a45a15e48f985b554a0b6e5f0857913cc492ec061cc9b0b2befa4b24609a4a")); + ASSERT_EQ(tx.inputs[1].previousOutput.index, 1ul); + ASSERT_EQ(tx.inputs[1].since, 0ul); + + ASSERT_EQ(tx.inputs[2].previousOutput.txHash, + parse_hex("5b12911e7413e011f251c1fb5fae4e76fd5fcae4f0d4c6412dcc5b0bfcece823")); + ASSERT_EQ(tx.inputs[2].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[2].since, 0ul); + + ASSERT_EQ(tx.outputs.size(), 3ul); + ASSERT_EQ(tx.outputsData.size(), 3ul); + + ASSERT_EQ(tx.outputs[0].capacity, 14400000000ul); + ASSERT_EQ(tx.outputs[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[0].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].lock.args, parse_hex("ab201f55b02f53b385f79b34dfad548e549b48fc")); + ASSERT_EQ(tx.outputs[0].type.codeHash, + parse_hex("5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5")); + ASSERT_EQ(tx.outputs[0].type.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].type.args, + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8")); + ASSERT_EQ(hex(tx.outputsData[0]), "0080c6a47e8d03000000000000000000"); + + ASSERT_EQ(tx.outputs[1].capacity, 14400000000ul); + ASSERT_EQ(tx.outputs[1].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[1].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[1].lock.args, parse_hex("b0d65be39059d6489231b48f85ad706a560bbd8d")); + ASSERT_EQ(tx.outputs[1].type.codeHash, + parse_hex("5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5")); + ASSERT_EQ(tx.outputs[1].type.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[1].type.args, + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8")); + ASSERT_EQ(hex(tx.outputsData[1]), "00601e253b6b4c000000000000000000"); + + ASSERT_EQ(tx.outputs[2].capacity, 8210023387ul); + ASSERT_EQ(tx.outputs[2].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[2].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[2].lock.args, parse_hex("b0d65be39059d6489231b48f85ad706a560bbd8d")); + ASSERT_EQ(tx.outputs[2].type.codeHash.size(), 0ul); + ASSERT_EQ(tx.outputs[2].type.args.size(), 0ul); + ASSERT_EQ(tx.outputsData[2].size(), 0ul); + + ASSERT_EQ(tx.serializedWitnesses.size(), 3ul); + ASSERT_EQ( + hex(tx.serializedWitnesses[0]), + "550000001000000055000000550000004100000035d55fd46316f248552eb6af7ac9589c9ec533c4e5b71896b0" + "5cdf697e2d18551ceff54d7b860ebb2f4dd5f6c5bb4af1da15460a7621f5aa4bc7d5585a0504de00"); + ASSERT_EQ( + hex(tx.serializedWitnesses[1]), + "5500000010000000550000005500000041000000eaa4bf69126d3016ab786610f2f0668b2ef353915d623d0b01" + "84fc25cec3dcad6dc08a1504a2d7dd9faced17b041d79d4c21f1977e57859713360f5e3609583501"); + ASSERT_EQ(hex(tx.serializedWitnesses[2]), ""); +} + +Proto::SigningInput getInput4() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_sudt_transfer(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02" + "wectaumxn0664yw2jd53lqk4mxg3"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78" + "yze6eyfyvd537z66ur22c9mmrgz82ama"); + operation.set_use_max_amount(true); + input.set_byte_fee(1); + auto sudtAddress = + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8"); + *operation.mutable_sudt_address() = std::string(sudtAddress.begin(), sudtAddress.end()); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(8210026306); + *cell1.mutable_out_point() = + OutPoint(parse_hex("430cb60ee816e2631d6d9605659c18fec8eb3de94526f5fd4ad51feaad6f1664"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(14400000000); + *cell2.mutable_out_point() = + OutPoint(parse_hex("378b6bd2f7fc2b1599ee55be7e8fa17fdd6e0d25e2e146d5f46006e0292d6564"), 0) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + *cell2.mutable_type() = + Script(parse_hex("5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5"), + HashType::Type1, + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8")) + .proto(); + auto cell2Data = parse_hex("00601e253b6b4c000000000000000000"); + *cell2.mutable_data() = std::string(cell2Data.begin(), cell2Data.end()); + + auto privateKey = parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey.begin(), privateKey.end())); + + return input; +} + +TEST(NervosSigner, Sign_SUDT_SendMaximum) { + auto input = getInput4(); + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + Transaction tx; + tx.build(txPlan); + auto error = tx.sign(getPrivateKeys(input)); + + ASSERT_EQ(error, Common::Proto::SigningError::OK); + + // https://explorer.nervos.org/transaction/0x09a45a15e48f985b554a0b6e5f0857913cc492ec061cc9b0b2befa4b24609a4a + ASSERT_EQ(tx.hash(), + parse_hex("09a45a15e48f985b554a0b6e5f0857913cc492ec061cc9b0b2befa4b24609a4a")); + + ASSERT_EQ(tx.cellDeps.size(), 2ul); + + ASSERT_EQ(tx.cellDeps[0].outPoint.txHash, + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")); + ASSERT_EQ(tx.cellDeps[0].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[0].depType, DepType::DepGroup); + + ASSERT_EQ(tx.cellDeps[1].outPoint.txHash, + parse_hex("c7813f6a415144643970c2e88e0bb6ca6a8edc5dd7c1022746f628284a9936d5")); + ASSERT_EQ(tx.cellDeps[1].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[1].depType, DepType::Code); + + ASSERT_EQ(tx.headerDeps.size(), 0ul); + + ASSERT_EQ(tx.inputs.size(), 2ul); + + ASSERT_EQ(tx.inputs[0].previousOutput.txHash, + parse_hex("378b6bd2f7fc2b1599ee55be7e8fa17fdd6e0d25e2e146d5f46006e0292d6564")); + ASSERT_EQ(tx.inputs[0].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[0].since, 0ul); + + ASSERT_EQ(tx.inputs[1].previousOutput.txHash, + parse_hex("430cb60ee816e2631d6d9605659c18fec8eb3de94526f5fd4ad51feaad6f1664")); + ASSERT_EQ(tx.inputs[1].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[1].since, 0ul); + + ASSERT_EQ(tx.outputs.size(), 2ul); + ASSERT_EQ(tx.outputsData.size(), 2ul); + + ASSERT_EQ(tx.outputs[0].capacity, 14400000000ul); + ASSERT_EQ(tx.outputs[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[0].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].lock.args, parse_hex("ab201f55b02f53b385f79b34dfad548e549b48fc")); + ASSERT_EQ(tx.outputs[0].type.codeHash, + parse_hex("5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5")); + ASSERT_EQ(tx.outputs[0].type.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].type.args, + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8")); + ASSERT_EQ(hex(tx.outputsData[0]), "00601e253b6b4c000000000000000000"); + + ASSERT_EQ(tx.outputs[1].capacity, 8210025567ul); + ASSERT_EQ(tx.outputs[1].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[1].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[1].lock.args, parse_hex("b0d65be39059d6489231b48f85ad706a560bbd8d")); + ASSERT_EQ(tx.outputs[1].type.codeHash.size(), 0ul); + ASSERT_EQ(tx.outputs[1].type.args.size(), 0ul); + ASSERT_EQ(tx.outputsData[1].size(), 0ul); + + ASSERT_EQ(tx.serializedWitnesses.size(), 2ul); + ASSERT_EQ( + hex(tx.serializedWitnesses[0]), + "5500000010000000550000005500000041000000da7c908bdf2cb091b7ff9bb682b762d1323c5e1ecf9b2ce0eb" + "edb9d55f6625c52ab14910ae401833112f2ea516ab11bc9ef691c3dff7886e3238c9348c3d73a701"); + ASSERT_EQ(hex(tx.serializedWitnesses[1]), ""); +} + +Proto::SigningInput getInput5() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_dao_deposit(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8f" + "urras980hksatlslfaktks7epf25"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9e" + "rg8furras980hksatlslfaktks7epf25"); + + operation.set_amount(10200000000); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(8210021909); + *cell1.mutable_out_point() = + OutPoint(parse_hex("c7dacd4aab49f5f9643e87752428cebde38eeb49c7726781c4d8b526822004a1"), 1) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqds6ed78yze6eyfyvd537z66ur22c9mmrgz82ama")) + .proto(); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(14399998167); + *cell2.mutable_out_point() = + OutPoint(parse_hex("d3c3263170815b326779e2fd8d548f846ae13eff9d9a82833c7071069a1d32bf"), 0) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto privateKey1 = + parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey1.begin(), privateKey1.end())); + auto privateKey2 = + parse_hex("0c8859a9d9084a8c2b55963268b352e258756f9240f2a1f4645c610ed191dae9"); + input.add_private_key(std::string(privateKey2.begin(), privateKey2.end())); + + return input; +} + +TEST(NervosSigner, Sign_DAO_Deposit) { + auto input = getInput5(); + + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + Transaction tx; + tx.build(txPlan); + auto error = tx.sign(getPrivateKeys(input)); + + ASSERT_EQ(error, Common::Proto::SigningError::OK); + + // https://explorer.nervos.org/transaction/0x583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683 + ASSERT_EQ(tx.hash(), + parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683")); + + ASSERT_EQ(tx.cellDeps.size(), 2ul); + + ASSERT_EQ(tx.cellDeps[0].outPoint.txHash, + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")); + ASSERT_EQ(tx.cellDeps[0].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[0].depType, DepType::DepGroup); + + ASSERT_EQ(tx.cellDeps[1].outPoint.txHash, + parse_hex("e2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c")); + ASSERT_EQ(tx.cellDeps[1].outPoint.index, 2ul); + ASSERT_EQ(tx.cellDeps[1].depType, DepType::Code); + + ASSERT_EQ(tx.headerDeps.size(), 0ul); + + ASSERT_EQ(tx.inputs.size(), 2ul); + + ASSERT_EQ(tx.inputs[0].previousOutput.txHash, + parse_hex("c7dacd4aab49f5f9643e87752428cebde38eeb49c7726781c4d8b526822004a1")); + ASSERT_EQ(tx.inputs[0].previousOutput.index, 1ul); + ASSERT_EQ(tx.inputs[0].since, 0ul); + + ASSERT_EQ(tx.inputs[1].previousOutput.txHash, + parse_hex("d3c3263170815b326779e2fd8d548f846ae13eff9d9a82833c7071069a1d32bf")); + ASSERT_EQ(tx.inputs[1].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[1].since, 0ul); + + ASSERT_EQ(tx.outputs.size(), 2ul); + ASSERT_EQ(tx.outputsData.size(), 2ul); + + ASSERT_EQ(tx.outputs[0].capacity, 10200000000ul); + ASSERT_EQ(tx.outputs[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[0].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].lock.args, parse_hex("c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da")); + ASSERT_EQ(tx.outputs[0].type.codeHash, + parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e")); + ASSERT_EQ(tx.outputs[0].type.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].type.args, parse_hex("")); + ASSERT_EQ(hex(tx.outputsData[0]), "0000000000000000"); + + ASSERT_EQ(tx.outputs[1].capacity, 12410019377ul); + ASSERT_EQ(tx.outputs[1].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[1].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[1].lock.args, parse_hex("c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da")); + ASSERT_EQ(tx.outputs[1].type.codeHash.size(), 0ul); + ASSERT_EQ(tx.outputs[1].type.args.size(), 0ul); + ASSERT_EQ(tx.outputsData[1].size(), 0ul); + + ASSERT_EQ(tx.serializedWitnesses.size(), 2ul); + ASSERT_EQ( + hex(tx.serializedWitnesses[0]), + "5500000010000000550000005500000041000000305d09c7de3f34a4d53bc4e0031ee59c95b9abc4fc3ff5548e" + "1a17ca726c069a232012c9c4be6ec4d4ffbe88613ca5e686e3e4b7d0b9bbd7038003e23ffdcdd601"); + ASSERT_EQ( + hex(tx.serializedWitnesses[1]), + "55000000100000005500000055000000410000007c514c77482dd1e1086f41a6d17364c9b5ed16364d61df6f7f" + "d8540f8bf7c131275c877943786b1b72fbf4f9d817ee5dd554a689808b7919543c691b5068e5be01"); +} + +Proto::SigningInput getInput6() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_dao_withdraw_phase1(); + + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9e" + "rg8furras980hksatlslfaktks7epf25"); + auto& depositCell = *operation.mutable_deposit_cell(); + depositCell.set_capacity(10200000000); + *depositCell.mutable_out_point() = + OutPoint(parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"), 0) + .proto(); + *depositCell.mutable_lock() = + Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras9" + "80hksatlslfaktks7epf25")) + .proto(); + *depositCell.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto depositCellData = parse_hex("0000000000000000"); + *depositCell.mutable_data() = std::string(depositCellData.begin(), depositCellData.end()); + depositCell.set_block_number(7575466); + auto blockHashData = + parse_hex("3dfdb4b702a355a5593315016f8af0537d5a2f3292811b79420ded78a092be6a"); + *depositCell.mutable_block_hash() = std::string(blockHashData.begin(), blockHashData.end()); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(10200000000); + *cell1.mutable_out_point() = + OutPoint(parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + *cell1.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto cell1Data = parse_hex("0000000000000000"); + *cell1.mutable_data() = std::string(cell1Data.begin(), cell1Data.end()); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(12410019377); + *cell2.mutable_out_point() = + OutPoint(parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"), 1) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto privateKey1 = + parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey1.begin(), privateKey1.end())); + + return input; +} + +TEST(NervosSigner, Sign_DAO_Withdraw_Phase1) { + auto input = getInput6(); + + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + Transaction tx; + tx.build(txPlan); + auto error = tx.sign(getPrivateKeys(input)); + + ASSERT_EQ(error, Common::Proto::SigningError::OK); + + // https://explorer.nervos.org/transaction/0xb4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca + ASSERT_EQ(tx.hash(), + parse_hex("b4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca")); + + ASSERT_EQ(tx.cellDeps.size(), 2ul); + + ASSERT_EQ(tx.cellDeps[0].outPoint.txHash, + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")); + ASSERT_EQ(tx.cellDeps[0].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[0].depType, DepType::DepGroup); + + ASSERT_EQ(tx.cellDeps[1].outPoint.txHash, + parse_hex("e2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c")); + ASSERT_EQ(tx.cellDeps[1].outPoint.index, 2ul); + ASSERT_EQ(tx.cellDeps[1].depType, DepType::Code); + + ASSERT_EQ(tx.headerDeps.size(), 1ul); + ASSERT_EQ(tx.headerDeps[0], + parse_hex("3dfdb4b702a355a5593315016f8af0537d5a2f3292811b79420ded78a092be6a")); + + ASSERT_EQ(tx.inputs.size(), 2ul); + + ASSERT_EQ(tx.inputs[0].previousOutput.txHash, + parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683")); + ASSERT_EQ(tx.inputs[0].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[0].since, 0ul); + + ASSERT_EQ(tx.inputs[1].previousOutput.txHash, + parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683")); + ASSERT_EQ(tx.inputs[1].previousOutput.index, 1ul); + ASSERT_EQ(tx.inputs[1].since, 0ul); + + ASSERT_EQ(tx.outputs.size(), 2ul); + ASSERT_EQ(tx.outputsData.size(), 2ul); + + ASSERT_EQ(tx.outputs[0].capacity, 10200000000ul); + ASSERT_EQ(tx.outputs[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[0].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].lock.args, parse_hex("c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da")); + ASSERT_EQ(tx.outputs[0].type.codeHash, + parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e")); + ASSERT_EQ(tx.outputs[0].type.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].type.args, parse_hex("")); + ASSERT_EQ(hex(tx.outputsData[0]), "aa97730000000000"); + + ASSERT_EQ(tx.outputs[1].capacity, 12410018646ul); + ASSERT_EQ(tx.outputs[1].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[1].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[1].lock.args, parse_hex("c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da")); + ASSERT_EQ(tx.outputs[1].type.codeHash.size(), 0ul); + ASSERT_EQ(tx.outputs[1].type.args.size(), 0ul); + ASSERT_EQ(tx.outputsData[1].size(), 0ul); + + ASSERT_EQ(tx.serializedWitnesses.size(), 2ul); + ASSERT_EQ( + hex(tx.serializedWitnesses[0]), + "5500000010000000550000005500000041000000d5131c1a6b8eca11e2c280b72c5db09ea00bb788fd3262eace" + "d861c39db2aad04a36f9d174b6f167a9c98b85d2bccf537a163c44459d23467dfa86408f48dd5f01"); + ASSERT_EQ(tx.serializedWitnesses[1].size(), 0ul); +} + +Proto::SigningInput getInput7() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_dao_withdraw_phase2(); + + auto& depositCell = *operation.mutable_deposit_cell(); + depositCell.set_capacity(10200000000); + *depositCell.mutable_out_point() = + OutPoint(parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"), 0) + .proto(); + *depositCell.mutable_lock() = + Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras9" + "80hksatlslfaktks7epf25")) + .proto(); + *depositCell.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto depositCellData = parse_hex("0000000000000000"); + *depositCell.mutable_data() = std::string(depositCellData.begin(), depositCellData.end()); + depositCell.set_block_number(7575466); + auto blockHashData1 = + parse_hex("3dfdb4b702a355a5593315016f8af0537d5a2f3292811b79420ded78a092be6a"); + *depositCell.mutable_block_hash() = std::string(blockHashData1.begin(), blockHashData1.end()); + + auto& withdrawingCell = *operation.mutable_withdrawing_cell(); + withdrawingCell.set_capacity(10200000000); + *withdrawingCell.mutable_out_point() = + OutPoint(parse_hex("b4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca"), 0) + .proto(); + *withdrawingCell.mutable_lock() = + Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras9" + "80hksatlslfaktks7epf25")) + .proto(); + *withdrawingCell.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto withdrawingCellData = parse_hex("aa97730000000000"); + *withdrawingCell.mutable_data() = + std::string(withdrawingCellData.begin(), withdrawingCellData.end()); + withdrawingCell.set_block_number(7575534); + auto blockHashData2 = + parse_hex("b070d5364afd47c23fe267077d454009d6f665f200a915e68af1616e46f4aa52"); + *withdrawingCell.mutable_block_hash() = + std::string(blockHashData2.begin(), blockHashData2.end()); + withdrawingCell.set_since(0x20037c0000001731); + + operation.set_amount(10200000000); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(10200000000); + *cell1.mutable_out_point() = + OutPoint(parse_hex("b4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + *cell1.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto cell1Data = parse_hex("aa97730000000000"); + *cell1.mutable_data() = std::string(cell1Data.begin(), cell1Data.end()); + + auto privateKey1 = + parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey1.begin(), privateKey1.end())); + + return input; +} + +TEST(NervosSigner, Sign_DAO_Withdraw_Phase2) { + auto input = getInput7(); + + TransactionPlan txPlan; + txPlan.plan(input); + ASSERT_EQ(txPlan.error, Common::Proto::SigningError::OK); + Transaction tx; + tx.build(txPlan); + auto error = tx.sign(getPrivateKeys(input)); + + ASSERT_EQ(error, Common::Proto::SigningError::OK); + + ASSERT_EQ(tx.hash(), + parse_hex("4fde13c93fc5d24ab7f660070aaa0b1725809d585f6258605e595cdbd856ea1c")); + + ASSERT_EQ(tx.cellDeps.size(), 2ul); + + ASSERT_EQ(tx.cellDeps[0].outPoint.txHash, + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")); + ASSERT_EQ(tx.cellDeps[0].outPoint.index, 0ul); + ASSERT_EQ(tx.cellDeps[0].depType, DepType::DepGroup); + + ASSERT_EQ(tx.cellDeps[1].outPoint.txHash, + parse_hex("e2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c")); + ASSERT_EQ(tx.cellDeps[1].outPoint.index, 2ul); + ASSERT_EQ(tx.cellDeps[1].depType, DepType::Code); + + ASSERT_EQ(tx.headerDeps.size(), 2ul); + ASSERT_EQ(tx.headerDeps[0], + parse_hex("3dfdb4b702a355a5593315016f8af0537d5a2f3292811b79420ded78a092be6a")); + ASSERT_EQ(tx.headerDeps[1], + parse_hex("b070d5364afd47c23fe267077d454009d6f665f200a915e68af1616e46f4aa52")); + + ASSERT_EQ(tx.inputs.size(), 1ul); + + ASSERT_EQ(tx.inputs[0].previousOutput.txHash, + parse_hex("b4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca")); + ASSERT_EQ(tx.inputs[0].previousOutput.index, 0ul); + ASSERT_EQ(tx.inputs[0].since, 0x20037c0000001731ul); + + ASSERT_EQ(tx.outputs.size(), 1ul); + ASSERT_EQ(tx.outputsData.size(), 1ul); + + ASSERT_EQ(tx.outputs[0].capacity, 10199999532ul); + ASSERT_EQ(tx.outputs[0].lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(tx.outputs[0].lock.hashType, HashType::Type1); + ASSERT_EQ(tx.outputs[0].lock.args, parse_hex("c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da")); + ASSERT_EQ(tx.outputs[0].type.codeHash.size(), 0ul); + ASSERT_EQ(tx.outputs[0].type.args.size(), 0ul); + ASSERT_EQ(tx.outputsData[0].size(), 0ul); + + ASSERT_EQ(tx.serializedWitnesses.size(), 1ul); + ASSERT_EQ(hex(tx.serializedWitnesses[0]), + "6100000010000000550000006100000041000000743f86c5557f4e2d3327f4d17e7bad27209b29c1e9cd" + "bab42ab03f7094af917b4b203ddd7f2e87102e09ae579f2fe7f6adb7900b7386b58c1183ba0011b7c421" + "00080000000000000000000000"); +} + +} // namespace TW::Nervos::tests diff --git a/tests/chains/Nervos/TWAnyAddressTests.cpp b/tests/chains/Nervos/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..be1c5f92e01 --- /dev/null +++ b/tests/chains/Nervos/TWAnyAddressTests.cpp @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "PrivateKey.h" +#include +#include +#include +#include +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWAnyAddressNervos, AddressFromPublicKey) { + auto privateKey = + WRAP(TWPrivateKey, + TWPrivateKeyCreateWithData( + DATA("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb").get())); + ASSERT_NE(nullptr, privateKey.get()); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + ASSERT_NE(nullptr, publicKey.get()); + ASSERT_EQ(33ul, publicKey.get()->impl.bytes.size()); + auto address = + WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeNervos)); + auto addressString = WRAPS(TWAnyAddressDescription(address.get())); + assertStringsEqual(addressString, "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwy" + "k5x9erg8furras980hksatlslfaktks7epf25"); +} + +TEST(TWAnyAddressNervos, AddressFromString) { + auto address = + WRAP(TWAnyAddress, + TWAnyAddressCreateWithString(STRING("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthy" + "waa50xwsqwyk5x9erg8furras980hksatlslfaktks7epf25") + .get(), + TWCoinTypeNervos)); + ASSERT_NE(nullptr, address.get()); + auto addressString = WRAPS(TWAnyAddressDescription(address.get())); + assertStringsEqual(addressString, "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwy" + "k5x9erg8furras980hksatlslfaktks7epf25"); +} + +TEST(TWAnyAddressNervos, AddressFromWallet) { + auto wallet = WRAP( + TWHDWallet, + TWHDWalletCreateWithMnemonic( + STRING( + "alpha draw toss question picnic endless recycle wrong enable roast success palm") + .get(), + STRING("").get())); + auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeNervos)); + auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); + EXPECT_EQ(TWDataSize(privateKeyData.get()), 32ul); + + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); + EXPECT_EQ(TWDataSize(publicKeyData.get()), 33ul); + assertHexEqual(publicKeyData, + "026c9e4cbb95d4b3a123c1fc80795feacc38029683a1b3e16bccf49bba25fb2858"); + + auto address = WRAPS(TWCoinTypeDeriveAddress(TWCoinTypeNervos, privateKey.get())); + assertStringsEqual(address, "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9er" + "g8furras980hksatlslfaktks7epf25"); +} diff --git a/tests/chains/Nervos/TWAnySignerTests.cpp b/tests/chains/Nervos/TWAnySignerTests.cpp new file mode 100644 index 00000000000..75b42c032f1 --- /dev/null +++ b/tests/chains/Nervos/TWAnySignerTests.cpp @@ -0,0 +1,663 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Nervos/Address.h" +#include "Nervos/Serialization.h" +#include "Nervos/TransactionPlan.h" +#include "PublicKey.h" +#include "proto/Nervos.pb.h" +#include "uint256.h" +#include "TestUtilities.h" +#include + +#include + +namespace TW::Nervos::tests { + +Proto::SigningInput getAnySignerInput1() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_native_transfer(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02" + "wectaumxn0664yw2jd53lqk4mxg3"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78" + "yze6eyfyvd537z66ur22c9mmrgz82ama"); + operation.set_amount(10000000000); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(100000000000); + *cell1.mutable_out_point() = + OutPoint(parse_hex("71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3"), 1) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(20000000000); + *cell2.mutable_out_point() = + OutPoint(parse_hex("71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3"), 0) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto privateKey = parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey.begin(), privateKey.end())); + + return input; +} + +void checkAnySignerOutput1(Proto::SigningOutput& output) { + ASSERT_EQ(output.error(), Common::Proto::OK); + // https://explorer.nervos.org/transaction/0xf2c32afde7e72011985583873bc16c0a3c01fc01fc161eb4b914fcf84c53cdf8 + ASSERT_EQ(output.transaction_id(), + "0xf2c32afde7e72011985583873bc16c0a3c01fc01fc161eb4b914fcf84c53cdf8"); + ASSERT_EQ( + output.transaction_json(), + "{\"cell_deps\":[{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c\"}}],\"header_deps\":" + "[],\"inputs\":[{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3\"},\"since\":\"0x0\"}" + "],\"outputs\":[{\"capacity\":\"0x2540be400\",\"lock\":{\"args\":" + "\"0xab201f55b02f53b385f79b34dfad548e549b48fc\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":null},{\"capacity\":\"0x2540be230\",\"lock\":{\"args\":" + "\"0xb0d65be39059d6489231b48f85ad706a560bbd8d\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":null}],\"outputs_data\":[\"0x\",\"0x\"],\"version\":\"0x0\"," + "\"witnesses\":[" + "\"0x55000000100000005500000055000000410000002a9ef2ad7829e5ea0c7a32735d29a0cb2ec20434f6fd5b" + "f6e29cda56b28e08140156191cbbf80313d3c9cae4b74607acce7b28eb21d52ef058ed8491cdde70b700\"]}"); +} + +void checkPlan1(Proto::TransactionPlan& txPlanProto) { + ASSERT_EQ(txPlanProto.error(), Common::Proto::OK); + + ASSERT_EQ(txPlanProto.cell_deps_size(), 1); + + const auto cellDep1 = txPlanProto.cell_deps(0); + const auto cellDep1TxHash = + parse_hex("71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c"); + ASSERT_EQ(cellDep1.out_point().tx_hash(), + std::string(cellDep1TxHash.begin(), cellDep1TxHash.end())); + ASSERT_EQ(cellDep1.out_point().index(), 0ul); + ASSERT_EQ(cellDep1.dep_type(), "dep_group"); + + ASSERT_EQ(txPlanProto.header_deps_size(), 0); + + ASSERT_EQ(txPlanProto.selected_cells_size(), 1); + + auto cell1 = Cell(txPlanProto.selected_cells(0)); + ASSERT_EQ(cell1.outPoint.txHash, + parse_hex("71caea2d3ac9e3ea899643e3e67dd11eb587e7fe0d8c6e67255d0959fa0a1fa3")); + ASSERT_EQ(cell1.outPoint.index, 0ul); + ASSERT_EQ(cell1.capacity, 20000000000ul); + ASSERT_EQ(cell1.lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(cell1.lock.hashType, HashType::Type1); + ASSERT_EQ(cell1.lock.args, parse_hex("c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da")); + ASSERT_EQ(cell1.type.codeHash.size(), 0ul); + ASSERT_EQ(cell1.type.args.size(), 0ul); + ASSERT_EQ(cell1.data.size(), 0ul); + + ASSERT_EQ(txPlanProto.outputs_size(), 2); + ASSERT_EQ(txPlanProto.outputs_data_size(), 2); + + auto cellOutput1 = CellOutput(txPlanProto.outputs(0)); + ASSERT_EQ(cellOutput1.capacity, 10000000000ul); + ASSERT_EQ(cellOutput1.lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(cellOutput1.lock.hashType, HashType::Type1); + ASSERT_EQ(cellOutput1.lock.args, parse_hex("ab201f55b02f53b385f79b34dfad548e549b48fc")); + ASSERT_EQ(cellOutput1.type.codeHash.size(), 0ul); + ASSERT_EQ(cellOutput1.type.args.size(), 0ul); + ASSERT_EQ(txPlanProto.outputs_data(0).length(), 0ul); + + auto cellOutput2 = CellOutput(txPlanProto.outputs(1)); + ASSERT_EQ(cellOutput2.capacity, 9999999536ul); + ASSERT_EQ(cellOutput2.lock.codeHash, + parse_hex("9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8")); + ASSERT_EQ(cellOutput2.lock.hashType, HashType::Type1); + ASSERT_EQ(cellOutput2.lock.args, parse_hex("b0d65be39059d6489231b48f85ad706a560bbd8d")); + ASSERT_EQ(cellOutput2.type.codeHash.size(), 0ul); + ASSERT_EQ(cellOutput2.type.args.size(), 0ul); + ASSERT_EQ(txPlanProto.outputs_data(1).length(), 0ul); +} + +TEST(TWAnySignerNervos, Sign_Native_Simple) { + auto input = getAnySignerInput1(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNervos); + checkAnySignerOutput1(output); +} + +TEST(TWAnySignerNervos, PlanAndSign_Native_Simple) { + auto input = getAnySignerInput1(); + Proto::TransactionPlan txPlanProto; + ANY_PLAN(input, txPlanProto, TWCoinTypeNervos); + checkPlan1(txPlanProto); + *input.mutable_plan() = txPlanProto; + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNervos); + checkAnySignerOutput1(output); +} + +TEST(TWAnySignerNervos, Sign_NegativeMissingKey) { + auto input = getAnySignerInput1(); + input.clear_private_key(); + auto privateKey = parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61fec"); + input.add_private_key(std::string(privateKey.begin(), privateKey.end())); + Proto::SigningOutput output; + + ANY_SIGN(input, TWCoinTypeNervos); + + ASSERT_EQ(output.error(), Common::Proto::Error_missing_private_key); +} + +TEST(TWAnySignerNervos, Sign_NegativeNotEnoughUtxos) { + auto input = getAnySignerInput1(); + auto& operation = *input.mutable_native_transfer(); + operation.set_amount(1000000000000); + Proto::SigningOutput output; + + ANY_SIGN(input, TWCoinTypeNervos); + + ASSERT_EQ(output.error(), Common::Proto::Error_not_enough_utxos); +} + +Proto::SigningInput getAnySignerInput2() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_native_transfer(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02" + "wectaumxn0664yw2jd53lqk4mxg3"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78" + "yze6eyfyvd537z66ur22c9mmrgz82ama"); + operation.set_use_max_amount(true); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(11410040620); + *cell1.mutable_out_point() = + OutPoint(parse_hex("c75567c80dc9b97aaf4e5c23f4c7f37b077f2b33a50dd7abd952abfbd5beb247"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto privateKey = parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey.begin(), privateKey.end())); + + return input; +} + +TEST(TWAnySignerNervos, Sign_Native_SendMaximum) { + auto input = getAnySignerInput2(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNervos); + ASSERT_EQ(output.error(), Common::Proto::OK); + // https://explorer.nervos.org/transaction/0x298f5e04b6900796614b89062eb96cec63c3b2c460d01058736a793b567bc5c8 + ASSERT_EQ(output.transaction_id(), + "0x298f5e04b6900796614b89062eb96cec63c3b2c460d01058736a793b567bc5c8"); + ASSERT_EQ( + output.transaction_json(), + "{\"cell_deps\":[{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c\"}}],\"header_deps\":" + "[],\"inputs\":[{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0xc75567c80dc9b97aaf4e5c23f4c7f37b077f2b33a50dd7abd952abfbd5beb247\"},\"since\":\"0x0\"}" + "],\"outputs\":[{\"capacity\":\"0x2a81765c9\",\"lock\":{\"args\":" + "\"0xab201f55b02f53b385f79b34dfad548e549b48fc\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":null}],\"outputs_data\":[\"0x\"],\"version\":\"0x0\",\"witnesses\":[" + "\"0x5500000010000000550000005500000041000000daf6e65e5a1fe447a4feb7199886b6635c44738e04ea59" + "457608fb1c447e068026529d57b02014ddc144622f886153df426853f22083f8891461eeb50b5ce97d01\"]}"); +} + +Proto::SigningInput getAnySignerInput3() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_sudt_transfer(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02" + "wectaumxn0664yw2jd53lqk4mxg3"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78" + "yze6eyfyvd537z66ur22c9mmrgz82ama"); + uint256_t amount = 1000000000000000; + operation.set_amount(toString(amount)); + input.set_byte_fee(1); + auto sudtAddress = + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8"); + *operation.mutable_sudt_address() = std::string(sudtAddress.begin(), sudtAddress.end()); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(14399998906); + *cell1.mutable_out_point() = + OutPoint(parse_hex("5b12911e7413e011f251c1fb5fae4e76fd5fcae4f0d4c6412dcc5b0bfcece823"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(14400000000); + *cell2.mutable_out_point() = + OutPoint(parse_hex("e118bd11a73d381daf288381ce182d92b6cf2f52d25886bbda9e1a61525c7c4a"), 0) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + *cell2.mutable_type() = + Script(parse_hex("5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5"), + HashType::Type1, + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8")) + .proto(); + auto cell2Data = parse_hex("00e0e4c9b9f84f000000000000000000"); + *cell2.mutable_data() = std::string(cell2Data.begin(), cell2Data.end()); + + auto& cell3 = *input.add_cell(); + cell3.set_capacity(8210025567); + *cell3.mutable_out_point() = + OutPoint(parse_hex("09a45a15e48f985b554a0b6e5f0857913cc492ec061cc9b0b2befa4b24609a4a"), 1) + .proto(); + *cell3.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqds6ed78yze6eyfyvd537z66ur22c9mmrgz82ama")) + .proto(); + + auto privateKey1 = + parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey1.begin(), privateKey1.end())); + auto privateKey2 = + parse_hex("0c8859a9d9084a8c2b55963268b352e258756f9240f2a1f4645c610ed191dae9"); + input.add_private_key(std::string(privateKey2.begin(), privateKey2.end())); + + return input; +} + +TEST(TWAnySignerNervos, Sign_SUDT_Simple) { + auto input = getAnySignerInput3(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNervos); + ASSERT_EQ(output.error(), Common::Proto::OK); + // https://explorer.nervos.org/transaction/0x9b15f2bea26b98201540d8e20e8b1c21d96dd77ad246520b405c6aabb7173371 + ASSERT_EQ(output.transaction_id(), + "0x9b15f2bea26b98201540d8e20e8b1c21d96dd77ad246520b405c6aabb7173371"); + ASSERT_EQ( + output.transaction_json(), + "{\"cell_deps\":[{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c\"}},{\"dep_type\":" + "\"code\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0xc7813f6a415144643970c2e88e0bb6ca6a8edc5dd7c1022746f628284a9936d5\"}}],\"header_deps\":" + "[],\"inputs\":[{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0xe118bd11a73d381daf288381ce182d92b6cf2f52d25886bbda9e1a61525c7c4a\"},\"since\":\"0x0\"}" + ",{\"previous_output\":{\"index\":\"0x1\",\"tx_hash\":" + "\"0x09a45a15e48f985b554a0b6e5f0857913cc492ec061cc9b0b2befa4b24609a4a\"},\"since\":\"0x0\"}" + ",{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x5b12911e7413e011f251c1fb5fae4e76fd5fcae4f0d4c6412dcc5b0bfcece823\"},\"since\":\"0x0\"}" + "],\"outputs\":[{\"capacity\":\"0x35a4e9000\",\"lock\":{\"args\":" + "\"0xab201f55b02f53b385f79b34dfad548e549b48fc\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":{\"args\":" + "\"0x9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8\",\"code_hash\":" + "\"0x5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5\",\"hash_type\":" + "\"type\"}},{\"capacity\":\"0x35a4e9000\",\"lock\":{\"args\":" + "\"0xb0d65be39059d6489231b48f85ad706a560bbd8d\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":{\"args\":" + "\"0x9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8\",\"code_hash\":" + "\"0x5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5\",\"hash_type\":" + "\"type\"}},{\"capacity\":\"0x1e95b03db\",\"lock\":{\"args\":" + "\"0xb0d65be39059d6489231b48f85ad706a560bbd8d\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":null}],\"outputs_data\":[\"0x0080c6a47e8d03000000000000000000\"," + "\"0x00601e253b6b4c000000000000000000\",\"0x\"],\"version\":\"0x0\",\"witnesses\":[" + "\"0x550000001000000055000000550000004100000035d55fd46316f248552eb6af7ac9589c9ec533c4e5b718" + "96b05cdf697e2d18551ceff54d7b860ebb2f4dd5f6c5bb4af1da15460a7621f5aa4bc7d5585a0504de00\"," + "\"0x5500000010000000550000005500000041000000eaa4bf69126d3016ab786610f2f0668b2ef353915d623d" + "0b0184fc25cec3dcad6dc08a1504a2d7dd9faced17b041d79d4c21f1977e57859713360f5e3609583501\"," + "\"0x\"]}"); +} + +Proto::SigningInput getAnySignerInput4() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_sudt_transfer(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02" + "wectaumxn0664yw2jd53lqk4mxg3"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqds6ed78" + "yze6eyfyvd537z66ur22c9mmrgz82ama"); + operation.set_use_max_amount(true); + input.set_byte_fee(1); + auto sudtAddress = + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8"); + *operation.mutable_sudt_address() = std::string(sudtAddress.begin(), sudtAddress.end()); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(8210026306); + *cell1.mutable_out_point() = + OutPoint(parse_hex("430cb60ee816e2631d6d9605659c18fec8eb3de94526f5fd4ad51feaad6f1664"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(14400000000); + *cell2.mutable_out_point() = + OutPoint(parse_hex("378b6bd2f7fc2b1599ee55be7e8fa17fdd6e0d25e2e146d5f46006e0292d6564"), 0) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + *cell2.mutable_type() = + Script(parse_hex("5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5"), + HashType::Type1, + parse_hex("9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8")) + .proto(); + auto cell2Data = parse_hex("00601e253b6b4c000000000000000000"); + *cell2.mutable_data() = std::string(cell2Data.begin(), cell2Data.end()); + + auto privateKey = parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey.begin(), privateKey.end())); + + return input; +} + +TEST(TWAnySignerNervos, Sign_SUDT_SendMaximum) { + auto input = getAnySignerInput4(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNervos); + ASSERT_EQ(output.error(), Common::Proto::OK); + // https://explorer.nervos.org/transaction/0x09a45a15e48f985b554a0b6e5f0857913cc492ec061cc9b0b2befa4b24609a4a + ASSERT_EQ(output.transaction_id(), + "0x09a45a15e48f985b554a0b6e5f0857913cc492ec061cc9b0b2befa4b24609a4a"); + ASSERT_EQ(output.transaction_json(), + "{\"cell_deps\":[{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_" + "hash\":\"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c\"}},{" + "\"dep_type\":\"code\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0xc7813f6a415144643970c2e88e0bb6ca6a8edc5dd7c1022746f628284a9936d5\"}}],\"header_" + "deps\":[],\"inputs\":[{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x378b6bd2f7fc2b1599ee55be7e8fa17fdd6e0d25e2e146d5f46006e0292d6564\"},\"since\":" + "\"0x0\"},{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x430cb60ee816e2631d6d9605659c18fec8eb3de94526f5fd4ad51feaad6f1664\"},\"since\":" + "\"0x0\"}],\"outputs\":[{\"capacity\":\"0x35a4e9000\",\"lock\":{\"args\":" + "\"0xab201f55b02f53b385f79b34dfad548e549b48fc\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_" + "type\":\"type\"},\"type\":{\"args\":" + "\"0x9657b32fcdc463e13ec9205914fd91c443822a949937ae94add9869e7f2e1de8\",\"code_" + "hash\":\"0x5e7a36a77e68eecc013dfa2fe6a23f3b6c344b04005808694ae6dd45eea4cfd5\"," + "\"hash_type\":\"type\"}},{\"capacity\":\"0x1e95b0c5f\",\"lock\":{\"args\":" + "\"0xb0d65be39059d6489231b48f85ad706a560bbd8d\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_" + "type\":\"type\"},\"type\":null}],\"outputs_data\":[" + "\"0x00601e253b6b4c000000000000000000\",\"0x\"],\"version\":\"0x0\",\"witnesses\":[" + "\"0x5500000010000000550000005500000041000000da7c908bdf2cb091b7ff9bb682b762d1323c5e1e" + "cf9b2ce0ebedb9d55f6625c52ab14910ae401833112f2ea516ab11bc9ef691c3dff7886e3238c9348c3d" + "73a701\",\"0x\"]}"); +} + +Proto::SigningInput getAnySignerInput5() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_dao_deposit(); + + operation.set_to_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8f" + "urras980hksatlslfaktks7epf25"); + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9e" + "rg8furras980hksatlslfaktks7epf25"); + operation.set_amount(10200000000); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(8210021909); + *cell1.mutable_out_point() = + OutPoint(parse_hex("c7dacd4aab49f5f9643e87752428cebde38eeb49c7726781c4d8b526822004a1"), 1) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqds6ed78yze6eyfyvd537z66ur22c9mmrgz82ama")) + .proto(); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(14399998167); + *cell2.mutable_out_point() = + OutPoint(parse_hex("d3c3263170815b326779e2fd8d548f846ae13eff9d9a82833c7071069a1d32bf"), 0) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto privateKey1 = + parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey1.begin(), privateKey1.end())); + auto privateKey2 = + parse_hex("0c8859a9d9084a8c2b55963268b352e258756f9240f2a1f4645c610ed191dae9"); + input.add_private_key(std::string(privateKey2.begin(), privateKey2.end())); + + return input; +} + +TEST(TWAnySignerNervos, Sign_DAO_Deposit) { + auto input = getAnySignerInput5(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNervos); + ASSERT_EQ(output.error(), Common::Proto::OK); + // https://explorer.nervos.org/transaction/0x583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683 + ASSERT_EQ(output.transaction_id(), + "0x583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"); + ASSERT_EQ( + output.transaction_json(), + "{\"cell_deps\":[{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c\"}},{\"dep_type\":" + "\"code\",\"out_point\":{\"index\":\"0x2\",\"tx_hash\":" + "\"0xe2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c\"}}],\"header_deps\":" + "[],\"inputs\":[{\"previous_output\":{\"index\":\"0x1\",\"tx_hash\":" + "\"0xc7dacd4aab49f5f9643e87752428cebde38eeb49c7726781c4d8b526822004a1\"},\"since\":\"0x0\"}" + ",{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0xd3c3263170815b326779e2fd8d548f846ae13eff9d9a82833c7071069a1d32bf\"},\"since\":\"0x0\"}" + "],\"outputs\":[{\"capacity\":\"0x25ff7a600\",\"lock\":{\"args\":" + "\"0xc4b50c5c8d074f063ec0a77ded0eaff0fa7b65da\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":{\"args\":\"0x\",\"code_hash\":" + "\"0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e\",\"hash_type\":" + "\"type\"}},{\"capacity\":\"0x2e3b1de31\",\"lock\":{\"args\":" + "\"0xc4b50c5c8d074f063ec0a77ded0eaff0fa7b65da\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":null}],\"outputs_data\":[\"0x0000000000000000\",\"0x\"],\"version\":" + "\"0x0\",\"witnesses\":[" + "\"0x5500000010000000550000005500000041000000305d09c7de3f34a4d53bc4e0031ee59c95b9abc4fc3ff5" + "548e1a17ca726c069a232012c9c4be6ec4d4ffbe88613ca5e686e3e4b7d0b9bbd7038003e23ffdcdd601\"," + "\"0x55000000100000005500000055000000410000007c514c77482dd1e1086f41a6d17364c9b5ed16364d61df" + "6f7fd8540f8bf7c131275c877943786b1b72fbf4f9d817ee5dd554a689808b7919543c691b5068e5be01\"]}"); +} + +Proto::SigningInput getAnySignerInput6() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_dao_withdraw_phase1(); + + operation.set_change_address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9e" + "rg8furras980hksatlslfaktks7epf25"); + auto& depositCell = *operation.mutable_deposit_cell(); + depositCell.set_capacity(10200000000); + *depositCell.mutable_out_point() = + OutPoint(parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"), 0) + .proto(); + *depositCell.mutable_lock() = + Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras9" + "80hksatlslfaktks7epf25")) + .proto(); + *depositCell.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto depositCellData = parse_hex("0000000000000000"); + *depositCell.mutable_data() = std::string(depositCellData.begin(), depositCellData.end()); + depositCell.set_block_number(7575466); + auto blockHashData = + parse_hex("3dfdb4b702a355a5593315016f8af0537d5a2f3292811b79420ded78a092be6a"); + *depositCell.mutable_block_hash() = std::string(blockHashData.begin(), blockHashData.end()); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(10200000000); + *cell1.mutable_out_point() = + OutPoint(parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + *cell1.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto cell1Data = parse_hex("0000000000000000"); + *cell1.mutable_data() = std::string(cell1Data.begin(), cell1Data.end()); + + auto& cell2 = *input.add_cell(); + cell2.set_capacity(12410019377); + *cell2.mutable_out_point() = + OutPoint(parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"), 1) + .proto(); + *cell2.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + + auto privateKey1 = + parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey1.begin(), privateKey1.end())); + + return input; +} + +TEST(TWAnySignerNervos, Sign_DAO_Withdraw_Phase1) { + auto input = getAnySignerInput6(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNervos); + ASSERT_EQ(output.error(), Common::Proto::OK); + // https://explorer.nervos.org/transaction/0xb4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca + ASSERT_EQ(output.transaction_id(), + "0xb4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca"); + ASSERT_EQ(output.transaction_json(), + "{\"cell_deps\":[{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_" + "hash\":\"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c\"}},{" + "\"dep_type\":\"code\",\"out_point\":{\"index\":\"0x2\",\"tx_hash\":" + "\"0xe2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c\"}}],\"header_" + "deps\":[\"0x3dfdb4b702a355a5593315016f8af0537d5a2f3292811b79420ded78a092be6a\"]," + "\"inputs\":[{\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683\"},\"since\":" + "\"0x0\"},{\"previous_output\":{\"index\":\"0x1\",\"tx_hash\":" + "\"0x583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683\"},\"since\":" + "\"0x0\"}],\"outputs\":[{\"capacity\":\"0x25ff7a600\",\"lock\":{\"args\":" + "\"0xc4b50c5c8d074f063ec0a77ded0eaff0fa7b65da\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_" + "type\":\"type\"},\"type\":{\"args\":\"0x\",\"code_hash\":" + "\"0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e\",\"hash_" + "type\":\"type\"}},{\"capacity\":\"0x2e3b1db56\",\"lock\":{\"args\":" + "\"0xc4b50c5c8d074f063ec0a77ded0eaff0fa7b65da\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_" + "type\":\"type\"},\"type\":null}],\"outputs_data\":[\"0xaa97730000000000\",\"0x\"]," + "\"version\":\"0x0\",\"witnesses\":[" + "\"0x5500000010000000550000005500000041000000d5131c1a6b8eca11e2c280b72c5db09ea00bb788" + "fd3262eaced861c39db2aad04a36f9d174b6f167a9c98b85d2bccf537a163c44459d23467dfa86408f48" + "dd5f01\",\"0x\"]}"); +} + +Proto::SigningInput getAnySignerInput7() { + auto input = Proto::SigningInput(); + auto& operation = *input.mutable_dao_withdraw_phase2(); + + auto& depositCell = *operation.mutable_deposit_cell(); + depositCell.set_capacity(10200000000); + *depositCell.mutable_out_point() = + OutPoint(parse_hex("583d77a037e86155b7ab79ac59fc9bb01640e2427e859467ea10c4a6f222b683"), 0) + .proto(); + *depositCell.mutable_lock() = + Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras9" + "80hksatlslfaktks7epf25")) + .proto(); + *depositCell.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto depositCellData = parse_hex("0000000000000000"); + *depositCell.mutable_data() = std::string(depositCellData.begin(), depositCellData.end()); + depositCell.set_block_number(7575466); + auto blockHashData1 = + parse_hex("3dfdb4b702a355a5593315016f8af0537d5a2f3292811b79420ded78a092be6a"); + *depositCell.mutable_block_hash() = std::string(blockHashData1.begin(), blockHashData1.end()); + + auto& withdrawingCell = *operation.mutable_withdrawing_cell(); + withdrawingCell.set_capacity(10200000000); + *withdrawingCell.mutable_out_point() = + OutPoint(parse_hex("b4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca"), 0) + .proto(); + *withdrawingCell.mutable_lock() = + Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras9" + "80hksatlslfaktks7epf25")) + .proto(); + *withdrawingCell.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto withdrawingCellData = parse_hex("aa97730000000000"); + *withdrawingCell.mutable_data() = + std::string(withdrawingCellData.begin(), withdrawingCellData.end()); + withdrawingCell.set_block_number(7575534); + auto blockHashData2 = + parse_hex("b070d5364afd47c23fe267077d454009d6f665f200a915e68af1616e46f4aa52"); + *withdrawingCell.mutable_block_hash() = + std::string(blockHashData2.begin(), blockHashData2.end()); + withdrawingCell.set_since(0x20037c0000001731); + + operation.set_amount(10200000000); + input.set_byte_fee(1); + + auto& cell1 = *input.add_cell(); + cell1.set_capacity(10200000000); + *cell1.mutable_out_point() = + OutPoint(parse_hex("b4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca"), 0) + .proto(); + *cell1.mutable_lock() = Script(Address("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50x" + "wsqwyk5x9erg8furras980hksatlslfaktks7epf25")) + .proto(); + *cell1.mutable_type() = + Script(parse_hex("82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e"), + HashType::Type1, Data()) + .proto(); + auto cell1Data = parse_hex("aa97730000000000"); + *cell1.mutable_data() = std::string(cell1Data.begin(), cell1Data.end()); + + auto privateKey1 = + parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"); + input.add_private_key(std::string(privateKey1.begin(), privateKey1.end())); + + return input; +} + +TEST(TWAnySignerNervos, Sign_DAO_Withdraw_Phase2) { + auto input = getAnySignerInput7(); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNervos); + ASSERT_EQ(output.error(), Common::Proto::OK); + ASSERT_EQ(output.transaction_id(), + "0x4fde13c93fc5d24ab7f660070aaa0b1725809d585f6258605e595cdbd856ea1c"); + ASSERT_EQ( + output.transaction_json(), + "{\"cell_deps\":[{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c\"}},{\"dep_type\":" + "\"code\",\"out_point\":{\"index\":\"0x2\",\"tx_hash\":" + "\"0xe2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c\"}}],\"header_deps\":" + "[\"0x3dfdb4b702a355a5593315016f8af0537d5a2f3292811b79420ded78a092be6a\"," + "\"0xb070d5364afd47c23fe267077d454009d6f665f200a915e68af1616e46f4aa52\"],\"inputs\":[{" + "\"previous_output\":{\"index\":\"0x0\",\"tx_hash\":" + "\"0xb4e62bc5f5108275b0ef3da8f8cc3fb0172843c4a2a9cdfef3b04d6c65e9acca\"},\"since\":" + "\"0x20037c0000001731\"}],\"outputs\":[{\"capacity\":\"0x25ff7a42c\",\"lock\":{\"args\":" + "\"0xc4b50c5c8d074f063ec0a77ded0eaff0fa7b65da\",\"code_hash\":" + "\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":" + "\"type\"},\"type\":null}],\"outputs_data\":[\"0x\"],\"version\":\"0x0\",\"witnesses\":[" + "\"0x6100000010000000550000006100000041000000743f86c5557f4e2d3327f4d17e7bad27209b29c1e9cdba" + "b42ab03f7094af917b4b203ddd7f2e87102e09ae579f2fe7f6adb7900b7386b58c1183ba0011b7c42100080000" + "000000000000000000\"]}"); +} + +} // namespace TW::Nervos::tests \ No newline at end of file diff --git a/tests/chains/Nervos/TWCoinTypeTests.cpp b/tests/chains/Nervos/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..d2abfee8137 --- /dev/null +++ b/tests/chains/Nervos/TWCoinTypeTests.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWNervosCoinType, TWCoinType) { + const auto coin = TWCoinTypeNervos; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "nervos"); + assertStringsEqual(name, "Nervos"); + assertStringsEqual(symbol, "CKB"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 8); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainNervos); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(txUrl, "https://explorer.nervos.org/transaction/t123"); + assertStringsEqual(accUrl, "https://explorer.nervos.org/address/a12"); +} diff --git a/tests/chains/Nervos/TWNervosAddressTests.cpp b/tests/chains/Nervos/TWNervosAddressTests.cpp new file mode 100644 index 00000000000..f98cff90bae --- /dev/null +++ b/tests/chains/Nervos/TWNervosAddressTests.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::Nervos::tests { + +TEST(TWNervosAddress, Create) { + const auto ckbAddress = "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras980hksatlslfaktks7epf25"; + const auto addr = WRAP(TWNervosAddress, TWNervosAddressCreateWithString(STRING(ckbAddress).get())); + + const auto codeCash = WRAPD(TWNervosAddressCodeHash(addr.get())); + const auto args = WRAPD(TWNervosAddressArgs(addr.get())); + const auto hashType = WRAPS(TWNervosAddressHashType(addr.get())); + + EXPECT_TRUE(TWNervosAddressIsValidString(STRING(ckbAddress).get())); + assertHexEqual(codeCash, "9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"); + assertHexEqual(args, "c4b50c5c8d074f063ec0a77ded0eaff0fa7b65da"); + assertStringsEqual(hashType, "type"); +} + +} // namespace TW::Nervos::tests diff --git a/tests/chains/Nimiq/AddressTests.cpp b/tests/chains/Nimiq/AddressTests.cpp new file mode 100644 index 00000000000..169c18359a9 --- /dev/null +++ b/tests/chains/Nimiq/AddressTests.cpp @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Nimiq/Address.h" +#include "Nimiq/Signer.h" + +#include +#include + +using namespace TW; + +namespace TW::Nimiq::tests { + +TEST(NimiqAddress, Create) { + EXPECT_ANY_THROW(new Address("")); + EXPECT_ANY_THROW(new Address(Data{})); +} + +TEST(NimiqAddress, IsValid) { + // No address + ASSERT_FALSE(Address::isValid("")); + // Invalid country code + ASSERT_FALSE(Address::isValid("DE86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA")); + // Invalid checksum + ASSERT_FALSE(Address::isValid("NQ42 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA")); + // Too short + ASSERT_FALSE(Address::isValid("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0ML")); + // Too long + ASSERT_FALSE(Address::isValid("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA 0MLA")); + // Is not Base32 + ASSERT_FALSE(Address::isValid("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR ####")); + // Invalid checksum + ASSERT_FALSE(Address::isValid("NQXX 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA")); + // Valid, without spaces + ASSERT_TRUE(Address::isValid("NQ862H8FYGU5RM77QSN9LYLHC56ACYYR0MLA")); + // Valid, normal format + ASSERT_TRUE(Address::isValid("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA")); +} + +TEST(NimiqAddress, String) { + // Address to string + ASSERT_EQ( + Address(parse_hex("5b3e9e5f32b89abafc3708765dc8f00216cefbb1")).string(), + "NQ61 BCY9 UPRJ P2DB MY1P 11T5 TJ7G 08BC VXVH"); + // Without spaces + ASSERT_EQ( + Address("NQ862H8FYGU5RM77QSN9LYLHC56ACYYR0MLA").string(), + "NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"); + // With spaces + ASSERT_EQ( + Address("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA").string(), + "NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"); +} + +TEST(NimiqAddress, FromPublicKey) { + const auto publicKey = Signer::publicKeyFromBytes( + parse_hex("70c7492aaa9c9ac7a05bc0d9c5db2dae9372029654f71f0c7f95deed5099b702")); + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "NQ27 GBAY EVHP HK5X 6JHV JGFJ 5M3H BF4Y G7GD"); +} + +} // namespace TW::Nimiq::tests diff --git a/tests/chains/Nimiq/SignerTests.cpp b/tests/chains/Nimiq/SignerTests.cpp new file mode 100644 index 00000000000..c22441f2f39 --- /dev/null +++ b/tests/chains/Nimiq/SignerTests.cpp @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "Nimiq/Address.h" +#include "Nimiq/Signer.h" +#include "Nimiq/Transaction.h" + +#include + +namespace TW::Nimiq { + +TEST(NimiqSigner, DerivePublicKey) { + const PrivateKey privateKey(parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756")); + const PublicKey publicKey((privateKey.getPublicKey(TWPublicKeyTypeED25519))); + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "NQ27 GBAY EVHP HK5X 6JHV JGFJ 5M3H BF4Y G7GD"); +} + +TEST(NimiqSigner, Sign) { + const PrivateKey privateKey(parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756")); + const auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + std::array pubkeyBytes; + std::copy(pubkey.bytes.begin(), pubkey.bytes.end(), pubkeyBytes.data()); + + Transaction tx( + pubkeyBytes, + Address("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"), + 42042042, + 1000, + 314159 + ); + + Signer signer; + signer.sign(privateKey, tx); + + ASSERT_EQ(hex(tx.signature), + "74dc7f6e0ab58a0bf52cc6e8801b0cca132dd4229d9a3e3a3d2f90e4d8f045d981b771bf5fc3851a98f3c617b1a943228f963e910e061808a721cfa0e3cad50b"); +} + +} // namespace TW::Nimiq diff --git a/tests/chains/Nimiq/TWAnySignerTests.cpp b/tests/chains/Nimiq/TWAnySignerTests.cpp new file mode 100644 index 00000000000..a618b1ac04f --- /dev/null +++ b/tests/chains/Nimiq/TWAnySignerTests.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/Nimiq.pb.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::Nimiq::tests { + +TEST(TWAnySignerNimiq, Sign) { + auto privateKey = parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756"); + + Proto::SigningInput input; + + input.set_destination("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"); + input.set_fee(1000); + input.set_value(42042042); + input.set_validity_start_height(314159); + input.set_private_key(privateKey.data(), privateKey.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeNimiq); + + EXPECT_EQ(hex(output.encoded()), "0070c7492aaa9c9ac7a05bc0d9c5db2dae9372029654f71f0c7f95deed5099b7021450ffc385cd4e7c6ac9a7e91614ca67ff90568a00000000028182ba00000000000003e80004cb2f2a74dc7f6e0ab58a0bf52cc6e8801b0cca132dd4229d9a3e3a3d2f90e4d8f045d981b771bf5fc3851a98f3c617b1a943228f963e910e061808a721cfa0e3cad50b"); +} + +} // namespace TW::Nimiq::tests diff --git a/tests/chains/Nimiq/TWCoinTypeTests.cpp b/tests/chains/Nimiq/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..65439d5fb46 --- /dev/null +++ b/tests/chains/Nimiq/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWNimiqCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeNimiq)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeNimiq, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeNimiq, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeNimiq)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeNimiq)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeNimiq), 5); + ASSERT_EQ(TWBlockchainNimiq, TWCoinTypeBlockchain(TWCoinTypeNimiq)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeNimiq)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeNimiq)); + assertStringsEqual(symbol, "NIM"); + assertStringsEqual(txUrl, "https://nimiq.watch/#t123"); + assertStringsEqual(accUrl, "https://nimiq.watch/#a12"); + assertStringsEqual(id, "nimiq"); + assertStringsEqual(name, "Nimiq"); +} diff --git a/tests/chains/Nimiq/TransactionTests.cpp b/tests/chains/Nimiq/TransactionTests.cpp new file mode 100644 index 00000000000..679f955f4ca --- /dev/null +++ b/tests/chains/Nimiq/TransactionTests.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "Nimiq/Address.h" +#include "Nimiq/Transaction.h" + +#include + +namespace TW::Nimiq { + +TEST(NimiqTransaction, PreImage) { + const PrivateKey privateKey(parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756")); + const auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + std::array pubkeyBytes; + std::copy(pubkey.bytes.begin(), pubkey.bytes.end(), pubkeyBytes.data()); + + Transaction tx( + pubkeyBytes, + Address("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"), + 42042042, + 1000, + 314159 + ); + ASSERT_EQ(hex(tx.getPreImage()), + "000082d5f776378ccbe34a3d941f22d4715bc9f81e0d001450ffc385cd4e7c6ac9a7e91614ca67ff90568a0000000000028182ba00000000000003e80004cb2f2a00"); +} + +TEST(NimiqTransaction, Serialize) { + const PrivateKey privateKey(parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756")); + const auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + std::array pubkeyBytes; + std::copy(pubkey.bytes.begin(), pubkey.bytes.end(), pubkeyBytes.data()); + + Transaction tx( + pubkeyBytes, + Address("NQ86 2H8F YGU5 RM77 QSN9 LYLH C56A CYYR 0MLA"), + 42042042, + 1000, + 314159 + ); + + const auto signature = parse_hex("74dc7f6e0ab58a0bf52cc6e8801b0cca132dd4229d9a3e3a3d2f90e4d8f045d981b771bf5fc3851a98f3c617b1a943228f963e910e061808a721cfa0e3cad50b"); + std::copy(signature.begin(), signature.end(), tx.signature.begin()); + + ASSERT_EQ(hex(tx.serialize()), + "0070c7492aaa9c9ac7a05bc0d9c5db2dae9372029654f71f0c7f95deed5099b7021450ffc385cd4e7c6ac9a7e91614ca67ff90568a00000000028182ba00000000000003e80004cb2f2a74dc7f6e0ab58a0bf52cc6e8801b0cca132dd4229d9a3e3a3d2f90e4d8f045d981b771bf5fc3851a98f3c617b1a943228f963e910e061808a721cfa0e3cad50b"); +} + +} // namespace TW::Nimiq diff --git a/tests/chains/OKXChain/TWCoinTypeTests.cpp b/tests/chains/OKXChain/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..28db8f84c53 --- /dev/null +++ b/tests/chains/OKXChain/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWCoinTypeOKXChain, TWCoinType) { + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeOKXChain)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x46C3A947E8248570FBD28E4FE456CC8F80DFD90716533878FB67857B95FA3D37")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeOKXChain, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x074faafd0b20fad2efa115b8ed7e75993e580b85")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeOKXChain, accId.get())); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeOKXChain)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeOKXChain)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeOKXChain), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeOKXChain)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeOKXChain)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeOKXChain)); + assertStringsEqual(symbol, "OKT"); + assertStringsEqual(txUrl, "https://www.oklink.com/oktc/tx/0x46C3A947E8248570FBD28E4FE456CC8F80DFD90716533878FB67857B95FA3D37"); + assertStringsEqual(accUrl, "https://www.oklink.com/oktc/address/0x074faafd0b20fad2efa115b8ed7e75993e580b85"); + assertStringsEqual(id, "okc"); + assertStringsEqual(name, "OKX Chain"); +} diff --git a/tests/chains/Oasis/AddressTests.cpp b/tests/chains/Oasis/AddressTests.cpp new file mode 100644 index 00000000000..a85dd59c24f --- /dev/null +++ b/tests/chains/Oasis/AddressTests.cpp @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Oasis/Address.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include + +using namespace TW; + +namespace TW::Oasis::tests { + +TEST(OasisAddress, Valid) { + ASSERT_TRUE(Address::isValid("oasis1qp0cnmkjl22gky6p6qeghjytt4v7dkxsrsmueweh")); +} + +TEST(OasisAddress, Invalid) { + ASSERT_FALSE(Address::isValid("oasis1qp0cnmkjl22gky6p6qeghjytt4v7dkxsrsmuewehj")); + ASSERT_FALSE(Address::isValid("oasi1qp0cnmkjl22gky6p6qeghjytt4v7dkxsrsmueweh")); +} + +TEST(OasisAddress, ForceInvalid) { + try { + auto addressString = "oasis1qp0cnmkjl22gky6p6qeghjytt4v7dkxsrsmuewehj"; + auto address = Address(addressString); + } catch (std::invalid_argument& e1) { + return; + } + FAIL() << "This test should generate an exception as it an invalid address"; +} + +TEST(OasisAddress, FromWrongData) { + try { + auto dataString = "asdadfasdfsdfwrwrsadasdasdsad"; + auto address = Address(data(dataString)); + } catch (std::invalid_argument& e1) { + return; + } + FAIL() << "This test should generate an exception as it an invalid data"; +} + +TEST(OasisAddress, FromPrivateKey) { + auto privateKey = PrivateKey(parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0")); + auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519)); + ASSERT_EQ(address.string(), "oasis1qzawzy5kaa2xgphenf3r0f5enpr3mx5dps559yxm"); +} + +TEST(OasisAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("aba52c0dcb80c2fe96ed4c3741af40c573a0500c0d73acda22795c37cb0f1739"), TWPublicKeyTypeED25519); + auto address = Address(publicKey); + ASSERT_EQ(address.string(), "oasis1qphdkldpttpsj2j3l9sde9h26cwpfwqwwuhvruyu"); +} + +TEST(OasisAddress, WrongPublicKeyType) { + try { + auto publicKey = PublicKey(parse_hex("aba52c0dcb80c2fe96ed4c3741af40c573a0500c0d73acda22795c37cb0f1739"), TWPublicKeyTypeED25519Cardano); + auto address = Address(publicKey); + } catch (std::invalid_argument& e1) { + return; + } + FAIL() << "TWPublicKeyTypeED25519Cardano should generate an exception as it an invalid publicKey type"; +} + +TEST(OasisAddress, FromString) { + Address address; + ASSERT_TRUE(Address::decode("oasis1hts399h023jqd7v6vgm6dxvcguwe4rgvqqgvq38n", address)); + ASSERT_EQ(address.string(), "oasis1hts399h023jqd7v6vgm6dxvcguwe4rgvqqgvq38n"); + + ASSERT_FALSE(Address::decode("oasis1hts399h023jqd7v6vgm6dxvcguwe4rgvqqgvq38ng", address)); +} + +} // namespace TW::Oasis::tests diff --git a/tests/chains/Oasis/SignerTests.cpp b/tests/chains/Oasis/SignerTests.cpp new file mode 100644 index 00000000000..23a26f587bf --- /dev/null +++ b/tests/chains/Oasis/SignerTests.cpp @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Oasis/Address.h" +#include "Oasis/Signer.h" +#include "PrivateKey.h" +#include "PublicKey.h" + +#include + +using namespace TW; + +namespace TW::Oasis::tests { + +TEST(OasisSigner, Sign) { + auto input = Proto::SigningInput(); + auto& transfer = *input.mutable_transfer(); + + transfer.set_gas_price(0); + transfer.set_gas_amount("0"); + transfer.set_nonce(0); + transfer.set_to("oasis1qrrnesqpgc6rfy2m50eew5d7klqfqk69avhv4ak5"); + transfer.set_amount("10000000"); + + // The use of this context thing is explained here --> https://docs.oasis.dev/oasis-core/common-functionality/crypto#domain-separation + transfer.set_context("oasis-core/consensus: tx for chain a245619497e580dd3bc1aa3256c07f68b8dcc13f92da115eadc3b231b083d3c4"); + + auto key = parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output = Signer::sign(input); + + ASSERT_EQ(hex(output.encoded()), "a2697369676e6174757265a2697369676e617475726558406e51c18c9b2015c9b49414b3307336597f51ff331873d214ce2db81c9651a34d99529ccaa294a39ccd01c6b0bc2c2239d87c624e5ba4840cf99ac8f9283e240c6a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b73756e747275737465645f7261775f76616c7565585ea463666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680656e6f6e636500666d6574686f64707374616b696e672e5472616e73666572"); +} + +} // namespace TW::Oasis::tests diff --git a/tests/chains/Oasis/TWAnySignerTests.cpp b/tests/chains/Oasis/TWAnySignerTests.cpp new file mode 100644 index 00000000000..63f24e03fe7 --- /dev/null +++ b/tests/chains/Oasis/TWAnySignerTests.cpp @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "HexCoding.h" +#include "proto/Oasis.pb.h" + +#include "TestUtilities.h" +#include + +using namespace TW; +namespace TW::Oasis::tests { + +TEST(TWAnySignerOasis, Sign) { + auto input = Proto::SigningInput(); + auto output = Proto::SigningOutput(); + auto& transfer = *input.mutable_transfer(); + + transfer.set_gas_price(0); + transfer.set_gas_amount("0"); + transfer.set_nonce(0); + transfer.set_to("oasis1qrrnesqpgc6rfy2m50eew5d7klqfqk69avhv4ak5"); + transfer.set_amount("10000000"); + transfer.set_context("oasis-core/consensus: tx for chain a245619497e580dd3bc1aa3256c07f68b8dcc13f92da115eadc3b231b083d3c4"); + + auto key = parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0"); + input.set_private_key(key.data(), key.size()); + + ANY_SIGN(input, TWCoinTypeOasis); + + EXPECT_EQ("a2697369676e6174757265a2697369676e617475726558406e51c18c9b2015c9b49414b3307336597f51ff331873d214ce2db81c9651a34d99529ccaa294a39ccd01c6b0bc2c2239d87c624e5ba4840cf99ac8f9283e240c6a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b73756e747275737465645f7261775f76616c7565585ea463666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680656e6f6e636500666d6574686f64707374616b696e672e5472616e73666572", + hex(output.encoded())); +} + +TEST(TWAnySignerOasisEscrow, Sign) { + auto input = Proto::SigningInput(); + auto output = Proto::SigningOutput(); + auto& transfer = *input.mutable_escrow(); + + transfer.set_gas_price(0); + transfer.set_gas_amount("0"); + transfer.set_nonce(0); + transfer.set_account("oasis1qrrnesqpgc6rfy2m50eew5d7klqfqk69avhv4ak5"); + transfer.set_amount("10000000"); + transfer.set_context("oasis-core/consensus: tx for chain a245619497e580dd3bc1aa3256c07f68b8dcc13f92da115eadc3b231b083d3c4"); + + auto key = parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0"); + input.set_private_key(key.data(), key.size()); + + ANY_SIGN(input, TWCoinTypeOasis); + + EXPECT_EQ("a2697369676e6174757265a2697369676e61747572655840f22235e307a45dbeb0c4201bee58b920c791d80356dc17ebe7fb878dbfe35a5e8e05ac8842c7f6cfaaf7a2b8898528f4cea7f9d501be1ce1275191c3333b00076a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b73756e747275737465645f7261775f76616c75655864a463666565a2636761730066616d6f756e74410064626f6479a266616d6f756e744400989680676163636f756e745500c73cc001463434915ba3f39751beb7c0905b45eb656e6f6e636500666d6574686f64717374616b696e672e416464457363726f77", + hex(output.encoded())); +} + +TEST(TWAnySignerOasisReclaimEscrow, Sign) { + auto input = Proto::SigningInput(); + auto output = Proto::SigningOutput(); + auto& transfer = *input.mutable_reclaimescrow(); + + transfer.set_gas_price(0); + transfer.set_gas_amount("0"); + transfer.set_nonce(0); + transfer.set_account("oasis1qrrnesqpgc6rfy2m50eew5d7klqfqk69avhv4ak5"); + transfer.set_shares("10000000"); + transfer.set_context("oasis-core/consensus: tx for chain a245619497e580dd3bc1aa3256c07f68b8dcc13f92da115eadc3b231b083d3c4"); + + auto key = parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0"); + input.set_private_key(key.data(), key.size()); + + ANY_SIGN(input, TWCoinTypeOasis); + + EXPECT_EQ("a2697369676e6174757265a2697369676e6174757265584048a3218b453a130dec2a1392556b5e03d54c6dab29600c50944e9bd0e5325b76f98ffe4e9f8b07590cd964480ce76b50d134035e73b03cba1adb7631ab67eb006a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b73756e747275737465645f7261775f76616c75655868a463666565a2636761730066616d6f756e74410064626f6479a2667368617265734400989680676163636f756e745500c73cc001463434915ba3f39751beb7c0905b45eb656e6f6e636500666d6574686f64757374616b696e672e5265636c61696d457363726f77", + hex(output.encoded())); +} + +} // namespace TW::Oasis::tests diff --git a/tests/chains/Oasis/TWCoinTypeTests.cpp b/tests/chains/Oasis/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..15085e876c0 --- /dev/null +++ b/tests/chains/Oasis/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWOasisCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeOasis)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("73dc977fdd8596d4a57e6feb891b21f5da3652d26815dc94f15f7420c298e29e")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeOasis, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("oasis1qrx376dmwuckmruzn9vq64n49clw72lywctvxdf4")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeOasis, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeOasis)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeOasis)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeOasis), 9); + ASSERT_EQ(TWBlockchainOasisNetwork, TWCoinTypeBlockchain(TWCoinTypeOasis)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeOasis)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeOasis)); + assertStringsEqual(symbol, "ROSE"); + assertStringsEqual(txUrl, "https://oasisscan.com/transactions/73dc977fdd8596d4a57e6feb891b21f5da3652d26815dc94f15f7420c298e29e"); + assertStringsEqual(accUrl, "https://oasisscan.com/accounts/detail/oasis1qrx376dmwuckmruzn9vq64n49clw72lywctvxdf4"); + assertStringsEqual(id, "oasis"); + assertStringsEqual(name, "Oasis"); +} diff --git a/tests/chains/Oasis/TransactionCompilerTests.cpp b/tests/chains/Oasis/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..18edd6b58e9 --- /dev/null +++ b/tests/chains/Oasis/TransactionCompilerTests.cpp @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Oasis.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Oasis::tests { + +TEST(OasisCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeOasis; + /// Step 1: Prepare transaction input (protobuf) + auto privateKey = PrivateKey(parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0")); + auto publicKey = privateKey.getPublicKey(publicKeyType(coin)); + + auto input = TW::Oasis::Proto::SigningInput(); + auto& transfer = *input.mutable_transfer(); + transfer.set_gas_price(0); + transfer.set_gas_amount("0"); + transfer.set_nonce(0); + transfer.set_to("oasis1qrrnesqpgc6rfy2m50eew5d7klqfqk69avhv4ak5"); + transfer.set_amount("10000000"); + transfer.set_context("oasis-core/consensus: tx for chain a245619497e580dd3bc1aa3256c07f68b8dcc13f92da115eadc3b231b083d3c4"); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), "373976e01fa0634a40ce8898a869f1056d862e3a0f26d8ae22ebeb5fdbcde9b3"); + + auto signature = parse_hex("6e51c18c9b2015c9b49414b3307336597f51ff331873d214ce2db81c9651a34d99529ccaa294a39ccd01c6b0bc2c2239d87c624e5ba4840cf99ac8f9283e240c"); + + /// Step 3: Compile transaction info + const auto expectedTx = "a2697369676e6174757265a2697369676e617475726558406e51c18c9b2015c9b49414b3307336597f51ff331873d214ce2db81c9651a34d99529ccaa294a39ccd01c6b0bc2c2239d87c624e5ba4840cf99ac8f9283e240c6a7075626c69635f6b6579582093d8f8a455f50527976a8aa87ebde38d5606efa86cb985d3fb466aff37000e3b73756e747275737465645f7261775f76616c7565585ea463666565a2636761730066616d6f756e74410064626f6479a262746f5500c73cc001463434915ba3f39751beb7c0905b45eb66616d6f756e744400989680656e6f6e636500666d6574686f64707374616b696e672e5472616e73666572"; + auto outputData = TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKey.bytes}); + + { + TW::Oasis::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), expectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::Oasis::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::Oasis::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), expectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKey.bytes}); + Oasis::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {}, {}); + Oasis::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +} // namespace TW::Oasis::tests diff --git a/tests/chains/Ontology/AccountTests.cpp b/tests/chains/Ontology/AccountTests.cpp new file mode 100644 index 00000000000..1917eea454f --- /dev/null +++ b/tests/chains/Ontology/AccountTests.cpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Hash.h" +#include "HexCoding.h" +#include "PrivateKey.h" + +#include "Ontology/Signer.h" + +#include + +using namespace TW; +namespace TW::Ontology::tests { + +TEST(OntologyAccount, validity) { + auto hexPrvKey = "4646464646464646464646464646464646464646464646464646464646464646"; + auto hexPubKey = "031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"; + auto signer = Signer(PrivateKey(parse_hex(hexPrvKey))); + auto prvKey = signer.getPrivateKey(); + auto pubKey = signer.getPublicKey(); + EXPECT_EQ(hexPrvKey, hex(prvKey.bytes)); + EXPECT_EQ(hexPubKey, hex(pubKey.bytes)); +} + +} // namespace TW::Ontology::tests diff --git a/tests/chains/Ontology/AddressTests.cpp b/tests/chains/Ontology/AddressTests.cpp new file mode 100644 index 00000000000..559c5ebe5b0 --- /dev/null +++ b/tests/chains/Ontology/AddressTests.cpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PublicKey.h" + +#include "Ontology/Address.h" +#include "Ontology/Signer.h" + +#include + +namespace TW::Ontology::tests { + +TEST(OntologyAddress, validation) { + ASSERT_FALSE(Address::isValid("abc")); + ASSERT_FALSE(Address::isValid("abeb60f3e94c1b9a09f33669435e7ef12eacd")); + ASSERT_FALSE(Address::isValid("abcb60f3e94c9b9a09f33669435e7ef1beaedads")); + ASSERT_TRUE(Address::isValid("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD")); +} + +TEST(OntologyAddress, fromPubKey) { + auto address = Address( + PublicKey(parse_hex("031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"), TWPublicKeyTypeSECP256k1)); + EXPECT_EQ("AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5", address.string()); +} + +TEST(OntologyAddress, fromString) { + auto b58Str = "AYTxeseHT5khTWhtWX1pFFP1mbQrd4q1zz"; + auto address = Address(b58Str); + EXPECT_EQ(b58Str, address.string()); + auto errB58Str = "AATxeseHT5khTWhtWX1pFFP1mbQrd4q1zz"; + ASSERT_THROW(new Address(errB58Str), std::runtime_error); +} + +TEST(OntologyAddress, fromMultiPubKeys) { + auto signer1 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); + auto signer2 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); + auto signer3 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464658"))); + std::vector pubKeys{signer1.getPublicKey().bytes, signer2.getPublicKey().bytes, signer3.getPublicKey().bytes}; + uint8_t m = 2; + auto multiAddress = Address(m, pubKeys); + EXPECT_EQ("AYGWgijVZnrUa2tRoCcydsHUXR1111DgdW", multiAddress.string()); +} + +TEST(OntologyAddress, fromBytes) { + auto address = Address( + PublicKey(parse_hex("031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"), TWPublicKeyTypeSECP256k1)); + EXPECT_EQ("AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5", address.string()); + + std::vector v(20); + + for (auto i = 0ul; i < address._data.size(); ++i) { + v[i] = address._data[i]; + } + auto address2 = Address(v); + EXPECT_EQ("AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5", address2.string()); + + v.pop_back(); + EXPECT_ANY_THROW(new Address(v)); +} + +} // namespace TW::Ontology::tests diff --git a/tests/chains/Ontology/Oep4Tests.cpp b/tests/chains/Ontology/Oep4Tests.cpp new file mode 100644 index 00000000000..6c43b61be89 --- /dev/null +++ b/tests/chains/Ontology/Oep4Tests.cpp @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" + +#include "Ontology/Oep4.h" +#include "Ontology/Signer.h" +#include +#include + +namespace TW::Ontology::tests { + +TEST(OntologyOep4, name) { + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex)); + Oep4 wing(wing_addr); + + uint32_t nonce = 0x1234; + auto tx = wing.name(nonce); + auto rawTx = hex(tx.serialize()); + EXPECT_EQ("00d1341200000000000000000000000000000000000000000000000000000000000000000000000000001c00c1046e616d656733def739225d0f93dd2aed457d7b1fd074ec31ff0000", rawTx); +} + +TEST(OntologyOep4, symbol) { + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex)); + Oep4 wing(wing_addr); + + uint32_t nonce = 0x1234; + auto tx = wing.symbol(nonce); + auto rawTx = hex(tx.serialize()); + EXPECT_EQ("00d1341200000000000000000000000000000000000000000000000000000000000000000000000000001e00c10673796d626f6c6733def739225d0f93dd2aed457d7b1fd074ec31ff0000", rawTx); +} + +TEST(OntologyOep4, decimals) { + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex)); + Oep4 wing(wing_addr); + + uint32_t nonce = 0x1234; + auto tx = wing.decimals(nonce); + auto rawTx = hex(tx.serialize()); + EXPECT_EQ("00d1341200000000000000000000000000000000000000000000000000000000000000000000000000002000c108646563696d616c736733def739225d0f93dd2aed457d7b1fd074ec31ff0000", rawTx); +} + +TEST(OntologyOep4, totalSupply) { + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex)); + Oep4 wing(wing_addr); + + uint32_t nonce = 0x1234; + auto tx = wing.totalSupply(nonce); + auto rawTx = hex(tx.serialize()); + EXPECT_EQ("00d1341200000000000000000000000000000000000000000000000000000000000000000000000000002300c10b746f74616c537570706c796733def739225d0f93dd2aed457d7b1fd074ec31ff0000", rawTx); +} + +TEST(OntologyOep4, balanceOf) { + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex)); + Oep4 wing(wing_addr); + auto user = Address("AeaThtPwh5kAYnjHavzwmvxPd725nVTvbM"); + + uint32_t nonce = 0x1234; + auto tx = wing.balanceOf(user, nonce); + auto rawTx = hex(tx.serialize()); + EXPECT_EQ("00d1341200000000000000000000000000000000000000000000000000000000000000000000000000003614fa2254ffaee3c3e1172e8e98f800e4105c74988e51c10962616c616e63654f666733def739225d0f93dd2aed457d7b1fd074ec31ff0000", rawTx); +} + +TEST(OntologyOep4, addressHack) { + auto ownerbin = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + auto payerbin = parse_hex("4646464646464646464646464646464646464646464646464646464646464652"); + + PrivateKey owner(ownerbin); + PrivateKey payer(payerbin); + + auto pubKey = owner.getPublicKey(TWPublicKeyTypeNIST256p1); + Address addr(pubKey); + + EXPECT_EQ("AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5", addr.string()); + + auto payerpub_key = payer.getPublicKey(TWPublicKeyTypeNIST256p1); + Address payer_addr(payerpub_key); + EXPECT_EQ("APniYDGozkhUh8Tk7pe35aah2HGJ4fJfVd", payer_addr.string()); +} + +TEST(OntologyOep4, transfer) { + PrivateKey fromPrivate(parse_hex("4646464646464646464646464646464646464646464646464646464646464652")); + Signer from(fromPrivate); + + PrivateKey payerPrivate(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + Signer payer(payerPrivate); + + auto toAddress = Address("AVY6LfvxauVQAVHDV9hC3ZCv7cQqzfDotH"); + + uint32_t nonce = 0x1234; + uint64_t amount = 233; + uint64_t gasPrice = 2500; + uint64_t gasLimit = 50000; + + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex)); + Oep4 wing(wing_addr); + + auto tx = wing.transfer(from, toAddress, amount, payer, gasPrice, gasLimit, nonce); + auto rawTxBytes = tx.serialize(); + auto rawTx = hex(rawTxBytes); + // Transaction Hex tab + // https://explorer.ont.io/testnet/tx/710266b8d497e794ecd47e01e269e4aeb6f4ff2b01eaeafc4cd371e062b13757 + EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa22871cfb819923fe01e9cb1e9ed16baa2b05c2feb76bcbe2ec125f72701c5e965232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9da4bb5dacd23dafba868cb31bacb38b4a6ff2607682a426c1dc09b05a1e158d6cd2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", rawTx); + // TODO uncomment when nist256p1 Rust implementation is enabled. + // EXPECT_EQ(rawTx, "00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa2d78e3046e66dc020e1634e1612e9455d0c8acac2305ae0563293d39bfa9d3bec232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9dab44a2531dc2504589734ce4534c74b58bdc0f3457cd53267331ec5211b0a4e842321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac"); +} + +TEST(OntologyOep4, transferMainnet) { + auto from = Signer( + PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); + + auto payer = Signer( + PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); + + auto toAddress = Address("AUJJhwRNi4RsNfvuexLETxXEb6szu9D5Ad"); + + uint32_t nonce = 0x1234; + uint64_t amount = 233; + uint64_t gasPrice = 2500; + uint64_t gasLimit = 50000; + + // wing oep4 mainnet address + std::string wing_hex{"00c59fcd27a562d6397883eab1f2fff56e58ef80"}; + auto wing_addr = Address(parse_hex(wing_hex)); + Oep4 wing(wing_addr); + + auto tx = wing.transfer(from, toAddress, amount, payer, gasPrice, gasLimit, nonce); + auto rawTx = hex(tx.serialize()); + // Transaction Hex tab + // https://explorer.ont.io/tx/70b276aaeb6b4578237390ec339b6a196f4620bdef8df1717032d32576ccef4a + EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e900148962e81f62cb76068b5f204ea5425d64d57147191457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726780ef586ef5fff2b1ea837839d662a527cd9fc500000241403c3a5e738f99e8f98ac4f59e225e549e2483bb60aee1771ef8ef189255e1670825d6a4c401f2e103348877393d8355c4d295b21fdfaf3dc4fea9b0459f1e1507232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41409501ccaab299dc660da9084dd6e8f22658f7687e77319b17b97149c3f023806d04b300baa52874eae57ccde935bb64e2c16c59e00e0efe7086ae93c1153b80722321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", rawTx); +} + +} // namespace TW::Ontology::tests diff --git a/tests/chains/Ontology/OngTests.cpp b/tests/chains/Ontology/OngTests.cpp new file mode 100644 index 00000000000..1aa603bebf9 --- /dev/null +++ b/tests/chains/Ontology/OngTests.cpp @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" + +#include "Ontology/Ong.h" + +#include +#include + +namespace TW::Ontology::tests { + +TEST(OntologyOng, decimals) { + uint32_t nonce = 0; + auto tx = Ong().decimals(nonce); + auto rawTx = hex(tx.serialize()); + EXPECT_EQ("00d100000000000000000000000000000000000000000000000000000000000000000000000000000000" + "380008646563696d616c731400000000000000000000000000000000000000020068164f6e746f6c6f67" + "792e4e61746976652e496e766f6b650000", + rawTx); +} + +TEST(OntologyOng, balanceOf) { + uint32_t nonce = 0; + auto address = Address("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD"); + auto tx = Ong().balanceOf(address, nonce); + auto rawTx = hex(tx.serialize()); + EXPECT_EQ("00d100000000000000000000000000000000000000000000000000000000000000000000000000000000" + "4d1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b70962616c616e63654f6614000000000000000000" + "00000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b650000", + rawTx); +} + +TEST(OntologyOng, transfer) { + PrivateKey privateKey1(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + Signer signer1(privateKey1); + + PrivateKey privateKey2(parse_hex("4646464646464646464646464646464646464646464646464646464646464652")); + Signer signer2(privateKey2); + + Address toAddress("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn"); + uint32_t nonce = 0; + uint64_t amount = 1, gasPrice = 500, gasLimit = 20000; + + auto tx = Ong().transfer(signer1, toAddress, amount, signer2, gasPrice, gasLimit, nonce); + auto rawTx = tx.serialize(); + auto rawTxHex = hex(rawTx); + + EXPECT_EQ("00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" + "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" + "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" + "00000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140ac3edf2d00540f9c" + "2f3b24878936b409c995c425ab5edf247c5b0d812a50df293ff63e173bac71a6cd0772ff78415c46ac64" + "f625cbc06fe90ccdecf9a94319c42321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" + "47125f927b7486ac41406fea9f12b125d7f65a94774e765a796428b3c6c4c46b0470624b9a1cef4ff420" + "488828f308c263b35287363e51add8cd49136eb57a397c6ade95df80d9a16282232103d9fd62df332403" + "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + rawTxHex); + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // EXPECT_EQ("00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" + // "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" + // "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" + // "00000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140ac3edf2d00540f9c" + // "2f3b24878936b409c995c425ab5edf247c5b0d812a50df29c009c1e7c4538e5a32f88d0087bea3b91082" + // "0487db572e9be6ebddc953200b8d2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" + // "47125f927b7486ac41406fea9f12b125d7f65a94774e765a796428b3c6c4c46b0470624b9a1cef4ff420" + // "b777d70bf73d9c4dad78c9c1ae52273273d38bf82cde221a1523eb4222c1c2cf232103d9fd62df332403" + // "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + // rawTxHex); +} + +TEST(OntologyOng, withdraw) { + PrivateKey privateKey1(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + Signer signer1(privateKey1); + + PrivateKey privateKey2(parse_hex("4646464646464646464646464646464646464646464646464646464646464652")); + Signer signer2(privateKey2); + + uint32_t nonce = 0; + uint64_t amount = 1, gasPrice = 500, gasLimit = 20000; + auto tx = + Ong().withdraw(signer1, signer1.getAddress(), amount, signer2, gasPrice, gasLimit, nonce); + auto rawTx = tx.serialize(); + auto rawTxHex = hex(rawTx); + + EXPECT_EQ( + "00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df68b00c6" + "6b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc81400000000000000000000000000000000000000" + "016a7cc814fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc8516a7cc86c0c7472616e7366657246726f" + "6d1400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f" + "6b6500024140b8b859055c744a89ef4d4f6ae7a58e0a99fef2eb0f6cf09d740b56cf4c7c14ab64e00c28de9b1f" + "28921cbd62e6bcd6d452ab9871f8f5d2288812ff322ee2f4af2321031bec1250aa8f78275f99a6663688f31085" + "848d0ed92f1203e447125f927b7486ac41406413b060329e133cd13709c361ccd90b3944477cf3937f1459313f" + "0ea6435f6f2b1335192a5d1b346fd431e8af912bfa4e1a23ad7d0ab7fc5b808655af5c9043232103d9fd62df33" + "2403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + rawTxHex); + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // EXPECT_EQ( + // "00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df68b00c6" + // "6b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc81400000000000000000000000000000000000000" + // "016a7cc814fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc8516a7cc86c0c7472616e7366657246726f" + // "6d1400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f" + // "6b6500024140b8b859055c744a89ef4d4f6ae7a58e0a99fef2eb0f6cf09d740b56cf4c7c14ab9b1ff3d62164e0" + // "d86de3429d1943292b6a3b623bae21cc5c6ba6cb90cd8030a22321031bec1250aa8f78275f99a6663688f31085" + // "848d0ed92f1203e447125f927b7486ac41406413b060329e133cd13709c361ccd90b3944477cf3937f1459313f" + // "0ea6435f6f2b1335192a5d1b346fd431e8af912bfa4e1a23ad7d0ab7fc5b808655af5c9043232103d9fd62df33" + // "2403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + // rawTxHex); +} + +} // namespace TW::Ontology::tests diff --git a/tests/chains/Ontology/OntTests.cpp b/tests/chains/Ontology/OntTests.cpp new file mode 100644 index 00000000000..4d499ddbdcc --- /dev/null +++ b/tests/chains/Ontology/OntTests.cpp @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Ontology/Ont.h" + +#include +#include + +namespace TW::Ontology::tests { + +TEST(OntologyOnt, decimals) { + uint32_t nonce = 0; + auto tx = Ont().decimals(nonce); + auto rawTx = hex(tx.serialize()); + EXPECT_EQ("00d100000000000000000000000000000000000000000000000000000000000000000000000000000000" + "380008646563696d616c731400000000000000000000000000000000000000010068164f6e746f6c6f67" + "792e4e61746976652e496e766f6b650000", + rawTx); +} + +TEST(OntologyOnt, queryBalance) { + uint32_t nonce = 0; + auto address = Address("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD"); + auto tx = Ont().balanceOf(address, nonce); + auto rawTx = hex(tx.serialize()); + EXPECT_EQ("00d100000000000000000000000000000000000000000000000000000000000000000000000000000000" + "4d1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b70962616c616e63654f6614000000000000000000" + "00000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b650000", + rawTx); +} + +TEST(OntologyOnt, transfer) { + PrivateKey privateKey1(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + Signer signer1(privateKey1); + + PrivateKey privateKey2(parse_hex("4646464646464646464646464646464646464646464646464646464646464652")); + Signer signer2(privateKey2); + + auto toAddress = Address("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn"); + uint32_t nonce = 0; + uint64_t amount = 1, gasPrice = 500, gasLimit = 20000; + auto tx = Ont().transfer(signer1, toAddress, amount, signer2, gasPrice, gasLimit, nonce); + auto rawTx = tx.serialize(); + auto rawTxHex = hex(rawTx); + + EXPECT_EQ("00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" + "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" + "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" + "00000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b65000241407531e7d5bb9ae138" + "862585a65c26d624f1a7a61011298809d9ed9cf60d10a4504067dee9d549a836b480c4e48904e28f9b42" + "dd5fa14376cbb1ef27d931eaea552321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" + "47125f927b7486ac4140bcc6df81d7f2f3143f152c446643ac5bf7910ef90046be8c89818264a11d360d" + "0576d7b092fabafd0913a67ccf8b2f8e3d2bd708f768c2bb67e2d2f759805608232103d9fd62df332403" + "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + rawTxHex); + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // EXPECT_EQ("00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" + // "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" + // "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" + // "00000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b65000241407531e7d5bb9ae138" + // "862585a65c26d624f1a7a61011298809d9ed9cf60d10a450bf9821152ab657ca4b7f3b1b76fb1d7021a4" + // "1d4e05d427b941caa2e9ca783afc2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" + // "47125f927b7486ac4140bcc6df81d7f2f3143f152c446643ac5bf7910ef90046be8c89818264a11d360d" + // "fa89284e6d054503f6ec59833074d0717fbb23a4afaedbc98bd6f7cba2e2cf49232103d9fd62df332403" + // "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + // rawTxHex); +} + +// Successfully broadcasted: https://explorer.ont.io/tx/785af64758886099b995e2aed914c56b15ab63c6e0c5acf42b66f3bbc3e95f98 +// TEST(OntologyOnt, transferUpdatedSign) { +// PrivateKey privateKey1(parse_hex("3b2bca95860af1baf5ef55f60167ef59db098b5871617c889f42dee4ffcb0c6f")); +// Signer signer1(privateKey1); +// +// PrivateKey privateKey2(parse_hex("3b2bca95860af1baf5ef55f60167ef59db098b5871617c889f42dee4ffcb0c6f")); +// Signer signer2(privateKey2); +// +// auto toAddress = Address("AUyL4TZ1zFEcSKDJrjFnD7vsq5iFZMZqT7"); +// uint32_t nonce = 2760697417; +// +// uint64_t amount = 1, gasPrice = 2500, gasLimit = 20000; +// auto tx = Ont().transfer(signer1, toAddress, amount, signer2, gasPrice, gasLimit, nonce); +// auto rawTx = tx.serialize(); +// auto rawTxHex = hex(rawTx); +// +// // The transaction hex signed by using Rust implementation: +// EXPECT_EQ("00d149e68ca4c409000000000000204e0000000000007ae1b6c6e44a518f8bcffc44e75236c7a2a8f3c2" +// "7100c66b147ae1b6c6e44a518f8bcffc44e75236c7a2a8f3c26a7cc81490c44262e0c9740f7b3c0e3d04" +// "6c106901c72cc46a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" +// "00000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140362bc7dd7d005b47" +// "464a2a2f84edd146e993267ceefb4174ef30d0cba0a1aa42bcd043b2349ba7030c2a3d7700d6653cc921" +// "d17a9f8942d46a93e84ae7968ed223210332aa8304797de2817ca65ce25aede0b176c3c5a61a20caffaf" +// "2df7fb6bb0b1b4ac4140362bc7dd7d005b47464a2a2f84edd146e993267ceefb4174ef30d0cba0a1aa42" +// "bcd043b2349ba7030c2a3d7700d6653cc921d17a9f8942d46a93e84ae7968ed223210332aa8304797de2" +// "817ca65ce25aede0b176c3c5a61a20caffaf2df7fb6bb0b1b4ac", +// rawTxHex); +// +// // The transaction hex signed by using `trezor-crypto`: +// EXPECT_NE("00d149e68ca4c409000000000000204e0000000000007ae1b6c6e44a518f8bcffc44e75236c7a2a8f3c2" +// "7100c66b147ae1b6c6e44a518f8bcffc44e75236c7a2a8f3c26a7cc81490c44262e0c9740f7b3c0e3d04" +// "6c106901c72cc46a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" +// "00000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140362bc7dd7d005b47" +// "464a2a2f84edd146e993267ceefb4174ef30d0cba0a1aa42432fbc4ccb6458fdf3d5c288ff299ac2f3c5" +// "2933078e5bb08925e27814cc967f23210332aa8304797de2817ca65ce25aede0b176c3c5a61a20caffaf" +// "2df7fb6bb0b1b4ac4140362bc7dd7d005b47464a2a2f84edd146e993267ceefb4174ef30d0cba0a1aa42" +// "432fbc4ccb6458fdf3d5c288ff299ac2f3c52933078e5bb08925e27814cc967f23210332aa8304797de2" +// "817ca65ce25aede0b176c3c5a61a20caffaf2df7fb6bb0b1b4ac", +// rawTxHex); +// } + +} // namespace TW::Ontology::tests diff --git a/tests/chains/Ontology/ParamsBuilderTests.cpp b/tests/chains/Ontology/ParamsBuilderTests.cpp new file mode 100644 index 00000000000..e9262dc59a7 --- /dev/null +++ b/tests/chains/Ontology/ParamsBuilderTests.cpp @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PublicKey.h" + +#include "Ontology/Address.h" +#include "Ontology/Ont.h" +#include "Ontology/ParamsBuilder.h" + +#include +#include +#include + +namespace TW::Ontology::tests { + +TEST(ParamsBuilder, pushInt) { + std::vector numVector{0, + 1, + 2, + 127, + 128, + 129, + 65534, + 65535, + 65536, + 65537, + 4294967294, + 4294967295, + 4294967296, + 68719476735, + 68719476736, + 281474976710655, + 72057594037927935, + 1152921504606846975}; + std::vector codeVector{"00", + "51", + "52", + "017f", + "028000", + "028100", + "03feff00", + "03ffff00", + "03000001", + "03010001", + "05feffffff00", + "05ffffffff00", + "050000000001", + "05ffffffff0f", + "050000000010", + "07ffffffffffff00", + "08ffffffffffffff00", + "08ffffffffffffff0f"}; + for (auto index = 0ul; index < numVector.size(); index++) { + auto builder = ParamsBuilder(); + builder.push(numVector[index]); + EXPECT_EQ(codeVector[index], hex(builder.getBytes())); + } +} + +TEST(ParamsBuilder, balanceInvokeCode) { + auto balanceParam = Address("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD")._data; + auto invokeCode = ParamsBuilder::buildNativeInvokeCode(Ont().contractAddress(), 0x00, + "balanceOf", {balanceParam}); + auto hexInvokeCode = + "1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b70962616c616e63654f661400000000000000000000000000" + "000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b65"; + EXPECT_EQ(hexInvokeCode, hex(invokeCode)); +} + +TEST(ParamsBuilder, transferInvokeCode) { + auto fromAddress = Address("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD")._data; + auto toAddress = Address("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn")._data; + uint64_t amount = 1; + NeoVmParamValue::ParamList transferParam{fromAddress, toAddress, amount}; + NeoVmParamValue::ParamArray args{transferParam}; + auto invokeCode = + ParamsBuilder::buildNativeInvokeCode(Ont().contractAddress(), 0x00, "transfer", {args}); + auto hexInvokeCode = + "00c66b1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b76a7cc814feec06b79ed299ea06fcb94abac41aaf3e" + "ad76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068" + "164f6e746f6c6f67792e4e61746976652e496e766f6b65"; + EXPECT_EQ(hexInvokeCode, hex(invokeCode)); +} + +TEST(ParamsBuilder, invokeOep4Code) { + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex)); + + NeoVmParamValue::ParamArray args{}; + std::string method{"name"}; + auto invokeCode = ParamsBuilder::buildOep4InvokeCode(wing_addr, method, {args}); + + auto expectCode = "00c1046e616d656733def739225d0f93dd2aed457d7b1fd074ec31ff"; + EXPECT_EQ(expectCode, hex(invokeCode)); +} + +TEST(ParamsBuilder, invokeOep4CodeBalanceOf) { + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex)); + auto user_addr = Address("AeaThtPwh5kAYnjHavzwmvxPd725nVTvbM"); + Data d(std::begin(user_addr._data), std::end(user_addr._data)); + + NeoVmParamValue::ParamArray args{d}; + std::string method{"balanceOf"}; + auto invokeCode = ParamsBuilder::buildOep4InvokeCode(wing_addr, method, {args}); + + auto expectCode = "14fa2254ffaee3c3e1172e8e98f800e4105c74988e51c10962616c616e63654f666733def739225d0f93dd2aed457d7b1fd074ec31ff"; + EXPECT_EQ(expectCode, hex(invokeCode)); +} + +TEST(OntologyOep4, invokeOep4CodeTransfer) { + std::string wing_hex{"ff31ec74d01f7b7d45ed2add930f5d2239f7de33"}; + auto wing_addr = Address(parse_hex(wing_hex)); + auto from = Address("APniYDGozkhUh8Tk7pe35aah2HGJ4fJfVd"); + auto to = Address("AVY6LfvxauVQAVHDV9hC3ZCv7cQqzfDotH"); + uint64_t amount = 253; + + NeoVmParamValue::ParamArray args{from._data, to._data, amount}; + std::reverse(args.begin(), args.end()); + std::string method{"transfer"}; + auto invokeCode = ParamsBuilder::buildOep4InvokeCode(wing_addr, method, {args}); + + auto expectCode = "02fd001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff"; + EXPECT_EQ(expectCode, hex(invokeCode)); +} + +} // namespace TW::Ontology::tests diff --git a/tests/chains/Ontology/TWAnySignerTests.cpp b/tests/chains/Ontology/TWAnySignerTests.cpp new file mode 100644 index 00000000000..3d9754490aa --- /dev/null +++ b/tests/chains/Ontology/TWAnySignerTests.cpp @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "TestUtilities.h" + +#include "Ontology/Oep4TxBuilder.h" +#include "Ontology/OngTxBuilder.h" +#include "Ontology/OntTxBuilder.h" + +#include + +#include + +using namespace TW; + +namespace TW::Ontology::tests { + +TEST(TWAnySingerOntology, OntBalanceOf) { + // curl -H "Content-Type: application/json" -X POST -d '{"Action":"sendrawtransaction", + // "Version":"1.0.0","00d1885602ec0000000000000000000000000000000000000000000000000000000000000000000000004d1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b70962616c616e63654f661400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b650000"}' + // http://polaris2.ont.io:20334/api/v1/transaction?preExec=1 + // + // {"Action":"sendrawtransaction","Desc":"SUCCESS","Error":0,"Result":{"State":1,"Gas":20000,"Result":"00","Notify":[]},"Version":"1.0.0"} + auto input = Proto::SigningInput(); + input.set_contract("ONT"); + input.set_method("balanceOf"); + input.set_query_address("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD"); + input.set_nonce(3959576200); + auto data = OntTxBuilder::build(input); + auto rawTx = hex(data); + EXPECT_EQ("00d1885602ec000000000000000000000000000000000000000000000000000000000000000000000000" + "4d1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b70962616c616e63654f6614000000000000000000" + "00000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b650000", + rawTx); +} + +TEST(TWAnySingerOntology, OntDecimals) { + // curl -H "Content-Type: application/json" -X POST -d '{"Action":"sendrawtransaction", + // "Version":"1.0.0","Data":"00d1bdc12a48000000000000000000000000000000000000000000000000000000000000000000000000380008646563696d616c731400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b650000"}' + // http://polaris2.ont.io:20334/api/v1/transaction?preExec=1 + // + //{"Action":"sendrawtransaction","Desc":"SUCCESS","Error":0,"Result":{"State":1,"Gas":20000,"Result":"","Notify":[]},"Version":"1.0.0"} + auto input = Proto::SigningInput(); + input.set_contract("ONT"); + input.set_method("decimals"); + input.set_nonce(1210761661); + auto data = OntTxBuilder::build(input); + auto rawTx = hex(data); + EXPECT_EQ("00d1bdc12a48000000000000000000000000000000000000000000000000000000000000000000000000" + "380008646563696d616c731400000000000000000000000000000000000000010068164f6e746f6c6f67" + "792e4e61746976652e496e766f6b650000", + rawTx); +} + +TEST(TWAnySingerOntology, OntTransfer) { + // tx on polaris test net. + // https://explorer.ont.io/transaction/4a672ce813d3fac9042e9472cf9b470f8a5e59a2deb41fd7b23a1f7479a155d5/testnet + auto ownerPrivateKey = + parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + auto payerPrivateKey = + parse_hex("4646464646464646464646464646464646464646464646464646464646464652"); + auto input = Proto::SigningInput(); + input.set_contract("ONT"); + input.set_method("transfer"); + input.set_nonce(2338116610); + input.set_owner_private_key(ownerPrivateKey.data(), ownerPrivateKey.size()); + input.set_payer_private_key(payerPrivateKey.data(), payerPrivateKey.size()); + input.set_to_address("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn"); + input.set_amount(1); + input.set_gas_price(500); + input.set_gas_limit(20000); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeOntology); + + EXPECT_EQ("00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" + "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" + "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" + "00000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6e" + "bb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb7cff28ddaf7f1048822c0ca21a0c4926323a" + "2497875b963f3b8cbd3717aa6e7c2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" + "47125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860" + "305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403" + "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + hex(output.encoded())); + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // EXPECT_EQ("00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" + // "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" + // "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" + // "00000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6e" + // "bb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb8300d7215080efb87dd3f35de5f3b6d98aac" + // "d6161fbc0845b82d0d8be4b8b6d52321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" + // "47125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860" + // "305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403" + // "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + // hex(output.encoded())); +} + +TEST(TWAnySingerOntology, OngDecimals) { + // curl -H "Content-Type: application/json" -X POST -d '{"Action":"sendrawtransaction", + // "Version":"1.0.0","Data":"00d1e3f2e679000000000000000000000000000000000000000000000000000000000000000000000000380008646563696d616c731400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b650000"}' + // http://polaris2.ont.io:20334/api/v1/transaction?preExec=1 + // + // {"Action":"sendrawtransaction","Desc":"SUCCESS","Error":0,"Result":{"State":1,"Gas":20000,"Result":"09","Notify":[]},"Version":"1.0.0"} + auto input = Proto::SigningInput(); + input.set_contract("ONG"); + input.set_method("decimals"); + input.set_nonce(2045178595); + auto data = OngTxBuilder::build(input); + auto rawTx = hex(data); + EXPECT_EQ("00d1e3f2e679000000000000000000000000000000000000000000000000000000000000000000000000" + "380008646563696d616c731400000000000000000000000000000000000000020068164f6e746f6c6f67" + "792e4e61746976652e496e766f6b650000", + rawTx); +} + +TEST(TWAnySingerOntology, OngBalanceOf) { + // curl -H "Content-Type: application/json" -X POST -d '{"Action":"sendrawtransaction", + // "Version":"1.0.0","Data":"00d1ab1ad0cf0000000000000000000000000000000000000000000000000000000000000000000000004d1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b70962616c616e63654f661400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b650000"}' + // http://polaris2.ont.io:20334/api/v1/transaction?preExec=1 + // + //{"Action":"sendrawtransaction","Desc":"SUCCESS","Error":0,"Result":{"State":1,"Gas":20000,"Result":"27e74d240609","Notify":[]},"Version":"1.0.0"} + auto input = Proto::SigningInput(); + input.set_contract("ONG"); + input.set_method("balanceOf"); + input.set_query_address("ANDfjwrUroaVtvBguDtrWKRMyxFwvVwnZD"); + input.set_nonce(3486522027); + auto data = OngTxBuilder::build(input); + auto rawTx = hex(data); + EXPECT_EQ("00d1ab1ad0cf000000000000000000000000000000000000000000000000000000000000000000000000" + "4d1446b1a18af6b7c9f8a4602f9f73eeb3030f0c29b70962616c616e63654f6614000000000000000000" + "00000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b650000", + rawTx); +} + +TEST(TWAnySingerOntology, OngTransfer) { + // tx on polaris test net. + // https://explorer.ont.io/transaction/8a1e59396dcb72d9095088f50d1023294bf9c7b79ba693bd641578f748cbd4e6/testnet + auto ownerPrivateKey = + parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + auto payerPrivateKey = + parse_hex("4646464646464646464646464646464646464646464646464646464646464652"); + auto input = Proto::SigningInput(); + input.set_contract("ONG"); + input.set_method("transfer"); + input.set_owner_private_key(ownerPrivateKey.data(), ownerPrivateKey.size()); + input.set_payer_private_key(payerPrivateKey.data(), payerPrivateKey.size()); + input.set_to_address("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn"); + input.set_amount(1); + input.set_gas_price(500); + input.set_gas_limit(20000); + input.set_nonce(2827104669); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeOntology); + + EXPECT_EQ("00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" + "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" + "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" + "00000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efa" + "d62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f73b8ab199a4d757b4c7b9ed46c4ff8cfa8ae" + "faa90b7fb6485e358034448cba752321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" + "47125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d74" + "3b078bd4e21bb4404c0182a32ee05260e22454dffb34dacccf458dfbee6d32db232103d9fd62df332403" + "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + hex(output.encoded())); + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // EXPECT_EQ("00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" + // "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" + // "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" + // "00000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efa" + // "d62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f8c4754e565b28a85b384612b93b007301438" + // "00049b97e83c95844a8eb7d66adc2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" + // "47125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d74" + // "c4f8742a1de44bc0b3fe7d5cd11fad9edac2a5cdabe2c3b824743cc70df5f276232103d9fd62df332403" + // "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + // hex(output.encoded())); +} + +TEST(TWAnySingerOntology, OngWithdraw) { + // tx on polaris test net. + // https://explorer.ont.io/transaction/433cb7ed4dec32d55be0db104aaa7ade4c7dbe0f62ef94f7b17829f7ac7cd75b/testnet + auto ownerPrivateKey = + parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + auto payerPrivateKey = + parse_hex("4646464646464646464646464646464646464646464646464646464646464652"); + auto input = Proto::SigningInput(); + input.set_contract("ONG"); + input.set_method("withdraw"); + input.set_owner_private_key(ownerPrivateKey.data(), ownerPrivateKey.size()); + input.set_payer_private_key(payerPrivateKey.data(), payerPrivateKey.size()); + input.set_to_address("AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5"); + input.set_amount(1); + input.set_gas_price(500); + input.set_gas_limit(20000); + input.set_nonce(3784713724); + auto data = OngTxBuilder::build(input); + auto rawTx = hex(data); + EXPECT_EQ( + "00d1fc2596e1f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df68b00c6" + "6b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc81400000000000000000000000000000000000000" + "016a7cc814fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc8516a7cc86c0c7472616e7366657246726f" + "6d1400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f" + "6b65000241400ef868766eeafce71b6ff2a4332aa4363980e66c55ef70aea80e3baee1daf02b43ae6d4c7c8a17" + "8b92f523602426eaa4205ab0ae5944b0fdae0abcbabaefbc4c2321031bec1250aa8f78275f99a6663688f31085" + "848d0ed92f1203e447125f927b7486ac4140c49c23092cd9003247a55792211d816010c7d6204c6e07a6e017da" + "70007b25ee2ab3665103f846300cd03512040275b78ae46812d40cd611058decdff5551e1f232103d9fd62df33" + "2403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + rawTx); +} + +TEST(TWAnySingerOntology, Oep4Decimal) { + auto input = Proto::SigningInput(); + input.set_contract("ff31ec74d01f7b7d45ed2add930f5d2239f7de33"); + input.set_method("decimals"); + input.set_nonce(0x1234); + auto data = Oep4TxBuilder::build(input); + auto rawTx = hex(data); + EXPECT_EQ("00d1341200000000000000000000000000000000000000000000000000000000000000000000000000002000c108646563696d616c736733def739225d0f93dd2aed457d7b1fd074ec31ff0000", rawTx); +} + +TEST(TWAnySingerOntology, Oep4BalanceOf) { + // read only method don't need signer + auto input = Proto::SigningInput(); + input.set_contract("ff31ec74d01f7b7d45ed2add930f5d2239f7de33"); + input.set_method("balanceOf"); + input.set_query_address("AeaThtPwh5kAYnjHavzwmvxPd725nVTvbM"); + input.set_nonce(0x1234); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeOntology); + EXPECT_EQ("00d1341200000000000000000000000000000000000000000000000000000000000000000000000000003614fa2254ffaee3c3e1172e8e98f800e4105c74988e51c10962616c616e63654f666733def739225d0f93dd2aed457d7b1fd074ec31ff0000", hex(output.encoded())); +} + +TEST(TWAnySingerOntology, Oep4Transfer) { + // https://explorer.ont.io/testnet/tx/710266b8d497e794ecd47e01e269e4aeb6f4ff2b01eaeafc4cd371e062b13757 + auto ownerPrivateKey = + parse_hex("4646464646464646464646464646464646464646464646464646464646464652"); + auto payerPrivateKey = + parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + auto input = Proto::SigningInput(); + input.set_contract("ff31ec74d01f7b7d45ed2add930f5d2239f7de33"); + input.set_method("transfer"); + input.set_owner_private_key(ownerPrivateKey.data(), ownerPrivateKey.size()); + input.set_payer_private_key(payerPrivateKey.data(), payerPrivateKey.size()); + input.set_to_address("AVY6LfvxauVQAVHDV9hC3ZCv7cQqzfDotH"); + input.set_amount(233); + input.set_gas_price(2500); + input.set_gas_limit(50000); + input.set_nonce(0x1234); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeOntology); + + auto rawTx = data(output.encoded()); + auto rawTxHex = hex(rawTx); + + EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa22871cfb819923fe01e9cb1e9ed16baa2b05c2feb76bcbe2ec125f72701c5e965232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9da4bb5dacd23dafba868cb31bacb38b4a6ff2607682a426c1dc09b05a1e158d6cd2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", hex(output.encoded())); + + // TODO uncomment when nist256p1 Rust implementation is enabled. + // EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa2d78e3046e66dc020e1634e1612e9455d0c8acac2305ae0563293d39bfa9d3bec232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9dab44a2531dc2504589734ce4534c74b58bdc0f3457cd53267331ec5211b0a4e842321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", rawTxHex); +} + +TEST(TWAnySingerOntology, Oep4TokenBalanceOf) { + // curl -H "Content-Type: application/json" -X POST -d '{"Action":"sendrawtransaction", + // "Version":"1.0.0","00d1a119d4f700000000000000000000000000000000000000000000000000000000000000000000000036144a03aaf03d12fd4d46bfcc260bda73ecef33b83b51c10962616c616e63654f6667e77fb36f54874c29f503d301d91d8ab98eb2342f0000"}' + // http://polaris2.ont.io:20334/api/v1/transaction?preExec=1 + // + // {"Action":"sendrawtransaction","Desc":"SUCCESS","Error":0,"Result":{"State":1,"Gas":20000,"Result":"40922df506","Notify":[]},"Version":"1.0.0"} + auto input = Proto::SigningInput(); + input.set_contract("2f34b28eb98a1dd901d303f5294c87546fb37fe7"); + input.set_method("balanceOf"); + input.set_query_address("ANXE3XovCwBH1ckQnPc6vKYiTwRXyrVToD"); + input.set_nonce(4157872545); + auto data = Oep4TxBuilder::build(input); + auto rawTx = hex(data); + EXPECT_EQ("00d1a119d4f700000000000000000000000000000000000000000000000000000000000000000000000036144a03aaf03d12fd4d46bfcc260bda73ecef33b83b51c10962616c616e63654f6667e77fb36f54874c29f503d301d91d8ab98eb2342f0000", + rawTx); +} + +TEST(TWAnySingerOntology, Oep4TokenDecimals) { + // curl -H "Content-Type: application/json" -X POST -d '{"Action":"sendrawtransaction", + // "Version":"1.0.0","Data":"00d1b1050fb40000000000000000000000000000000000000000000000000000000000000000000000002000c108646563696d616c7367e77fb36f54874c29f503d301d91d8ab98eb2342f0000"}' + // http://polaris2.ont.io:20334/api/v1/transaction?preExec=1 + // + //{"Action":"sendrawtransaction","Desc":"SUCCESS","Error":0,"Result":{"State":1,"Gas":20000,"Result":"08","Notify":[]},"Version":"1.0.0"} + auto input = Proto::SigningInput(); + input.set_contract("2f34b28eb98a1dd901d303f5294c87546fb37fe7"); + input.set_method("decimals"); + input.set_nonce(3020883377); + auto data = Oep4TxBuilder::build(input); + auto rawTx = hex(data); + EXPECT_EQ("00d1b1050fb40000000000000000000000000000000000000000000000000000000000000000000000002000c108646563696d616c7367e77fb36f54874c29f503d301d91d8ab98eb2342f0000", + rawTx); +} + +TEST(TWAnySingerOntology, Oep4TokenTransfer) { + auto ownerPrivateKey = + parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); + auto payerPrivateKey = + parse_hex("4646464646464646464646464646464646464646464646464646464646464652"); + auto input = Proto::SigningInput(); + input.set_contract("2f34b28eb98a1dd901d303f5294c87546fb37fe7"); + input.set_method("transfer"); + input.set_nonce(2232822985); + input.set_owner_private_key(ownerPrivateKey.data(), ownerPrivateKey.size()); + input.set_payer_private_key(payerPrivateKey.data(), payerPrivateKey.size()); + input.set_to_address("ARR6PsaBwRttzCmyxCMhL7NmFk1LqExD7L"); + input.set_amount(1000); + input.set_gas_price(2500); + input.set_gas_limit(200); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeOntology); + + EXPECT_EQ("00d1c92c1685c409000000000000c80000000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" + "4d02e8031469c329fbb30a490979ea1a6f0b6a3a91235f6bd714fbacc8214765d457c8e3f2b5a1d3c498" + "1a2e9d2a53c1087472616e7366657267e77fb36f54874c29f503d301d91d8ab98eb2342f000241402b62" + "b4c6bc07667019e5c9a1fa1b83ca71ee23ddb763446406b1b03706bf50a6180b13e255a08ade7da376df" + "d34faee7f51c4f0056325fa79aaf7de0ef25d64e2321031bec1250aa8f78275f99a6663688f31085848d" + "0ed92f1203e447125f927b7486ac41408aa88ae92ea30a9e5059de8594f462af7dfa7545fffa6654e94e" + "edfb910bcd5452a26d1554d5d980db84d00dd330aab2fc68316660c8ae5af2c806085157e8ce232103d9" + "fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + hex(output.encoded())); +} + +} // namespace TW::Ontology::tests diff --git a/tests/chains/Ontology/TWCoinTypeTests.cpp b/tests/chains/Ontology/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..026dd2a2fc3 --- /dev/null +++ b/tests/chains/Ontology/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWOntologyCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeOntology)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeOntology, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeOntology, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeOntology)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeOntology)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeOntology), 0); + ASSERT_EQ(TWBlockchainOntology, TWCoinTypeBlockchain(TWCoinTypeOntology)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeOntology)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeOntology)); + assertStringsEqual(symbol, "ONT"); + assertStringsEqual(txUrl, "https://explorer.ont.io/transaction/t123"); + assertStringsEqual(accUrl, "https://explorer.ont.io/address/a12"); + assertStringsEqual(id, "ontology"); + assertStringsEqual(name, "Ontology"); +} diff --git a/tests/chains/Ontology/TransactionCompilerTests.cpp b/tests/chains/Ontology/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..8315ae6180d --- /dev/null +++ b/tests/chains/Ontology/TransactionCompilerTests.cpp @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Ontology.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Ontology::tests { + +TEST(OntologyCompiler, CompileWithSignatures) { + /// Prepare transaction input (protobuf) + const auto coin = TWCoinTypeOntology; + auto input = Ontology::Proto::SigningInput(); + input.set_method("transfer"); + input.set_owner_address("AaCTzuhEr6essEEKnSTuxD2GJkmGc4nuJp"); + input.set_to_address("AWBzyqpXcSpgrWyzR6qzUGWc9ZoYT3Bsvk"); + input.set_payer_address("AaCTzuhEr6essEEKnSTuxD2GJkmGc4nuJp"); + input.set_amount(1); + input.set_gas_price(3500); + input.set_gas_limit(20000); + input.set_nonce(1); + + /// Obtain preimage hash and check it + input.set_contract("ONT"); + auto ontTxInputData = data(input.SerializeAsString()); + const auto ontPreImageHashes = TransactionCompiler::preImageHashes(coin, ontTxInputData); + auto ontPreOutput = TxCompiler::Proto::PreSigningOutput(); + ontPreOutput.ParseFromArray(ontPreImageHashes.data(), (int)ontPreImageHashes.size()); + auto ontPreImageHash = ontPreOutput.data_hash(); + + input.set_contract("ONG"); + auto ongTxInputData = data(input.SerializeAsString()); + const auto ongPreImageHashes = TransactionCompiler::preImageHashes(coin, ongTxInputData); + auto ongPreOutput = TxCompiler::Proto::PreSigningOutput(); + ongPreOutput.ParseFromArray(ongPreImageHashes.data(), (int)ongPreImageHashes.size()); + auto ongPreImageHash = ongPreOutput.data_hash(); + + { + EXPECT_EQ(hex(ontPreImageHash), + "d3770eb50f1fcddc17ac9d59f1b7e69c4916dbbe4c484cc6ba27dd0792aeb943"); + EXPECT_EQ(hex(ongPreImageHash), + "788066583071cfd05a4a10e5b897b9b81d2363c16fd98128ddc81891535567af"); + } + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = + parse_hex("038ea73c590f48c7d5a8ba544a928a0c8fb206aab60688793a054db9823602765a"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeNIST256p1); + const auto ontSignature = + parse_hex("b1678dfcda9b9b468d9a97a5b3021a680814180ca08cd17d9e3a9cf512b05a3b286fed9b8f635718" + "c0aabddc9fc1acfbc48561577e35ef92ee97d7fa86e14f47"); + const auto ongSignature = + parse_hex("d90c4d76e9d07d3e5c00e4a8768ce09ca66be05cfb7f48ad02632b4f08fcaa6f4e3f6f52eb4278c1" + "579065e54ea5e696b7711f071298576fa7050b21ae614bbe"); + + // Verify signature (pubkey & hash & signature) + { + EXPECT_TRUE(publicKey.verify(ontSignature, TW::data(ontPreImageHash))); + EXPECT_TRUE(publicKey.verify(ongSignature, TW::data(ongPreImageHash))); + } + + /// Compile transaction info + const Data ontOutputData = TransactionCompiler::compileWithSignatures( + coin, ontTxInputData, {ontSignature}, {publicKeyData}); + const Data ongOutputData = TransactionCompiler::compileWithSignatures( + coin, ongTxInputData, {ongSignature}, {publicKeyData}); + auto ontOutput = Ontology::Proto::SigningOutput(); + auto ongOutput = Ontology::Proto::SigningOutput(); + ontOutput.ParseFromArray(ontOutputData.data(), (int)ontOutputData.size()); + ongOutput.ParseFromArray(ongOutputData.data(), (int)ongOutputData.size()); + const auto ontExpectedTx = + "00d101000000ac0d000000000000204e000000000000ca18ec37ac94f19588926a5302ded54cd909a76e7100c6" + "6b14ca18ec37ac94f19588926a5302ded54cd909a76e6a7cc8149e21dda3257e18eb033d9451dda1d9ac8bcfa4" + "776a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f" + "6e746f6c6f67792e4e61746976652e496e766f6b6500014140b1678dfcda9b9b468d9a97a5b3021a680814180c" + "a08cd17d9e3a9cf512b05a3b286fed9b8f635718c0aabddc9fc1acfbc48561577e35ef92ee97d7fa86e14f4723" + "21038ea73c590f48c7d5a8ba544a928a0c8fb206aab60688793a054db9823602765aac"; + const auto ongExpectedTx = + "00d101000000ac0d000000000000204e000000000000ca18ec37ac94f19588926a5302ded54cd909a76e7100c6" + "6b14ca18ec37ac94f19588926a5302ded54cd909a76e6a7cc8149e21dda3257e18eb033d9451dda1d9ac8bcfa4" + "776a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000020068164f" + "6e746f6c6f67792e4e61746976652e496e766f6b6500014140d90c4d76e9d07d3e5c00e4a8768ce09ca66be05c" + "fb7f48ad02632b4f08fcaa6f4e3f6f52eb4278c1579065e54ea5e696b7711f071298576fa7050b21ae614bbe23" + "21038ea73c590f48c7d5a8ba544a928a0c8fb206aab60688793a054db9823602765aac"; + + { + EXPECT_EQ(hex(ontOutput.encoded()), ontExpectedTx); + EXPECT_EQ(hex(ongOutput.encoded()), ongExpectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, ontTxInputData, {ongSignature, ongSignature}, {publicKey.bytes}); + Ontology::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); + } + + { // Negative: empty signatures + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, ontTxInputData, {}, {}); + Ontology::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + // OEP4Token transfer test case + input.set_method("transfer"); + input.set_contract("2f34b28eb98a1dd901d303f5294c87546fb37fe7"); + input.set_owner_address("Aa8QcHJ8tbRXyjpG6FHo7TysjKXxkd1Yf2"); + input.set_to_address("ARR6PsaBwRttzCmyxCMhL7NmFk1LqExD7L"); + input.set_payer_address("Aa8QcHJ8tbRXyjpG6FHo7TysjKXxkd1Yf2"); + input.set_amount(1000); + input.set_gas_price(2500); + input.set_gas_limit(20000); + input.set_nonce(1); + + auto oepTxInputData = data(input.SerializeAsString()); + const auto oepPreImageHashes = TransactionCompiler::preImageHashes(coin, oepTxInputData); + auto oepPreOutput = TxCompiler::Proto::PreSigningOutput(); + oepPreOutput.ParseFromArray(oepPreImageHashes.data(), (int)oepPreImageHashes.size()); + auto oepPreImageHash = oepPreOutput.data_hash(); + EXPECT_EQ(hex(oepPreImageHash), + "5be4a3be92a49ce2af800c94c7c44eeb8cd345c25541f63e545edc06bd72c0ed"); + + const auto oepPublicKeyData = + parse_hex("03932a08085b4bd7adcf8915f805ab35ad51f58ebbd09783b01bb4c44e503444f9"); + const PublicKey opePublicKey = PublicKey(oepPublicKeyData, TWPublicKeyTypeNIST256p1); + const auto oepSignature = + parse_hex("55aff2726c5e17dd6a6bbdaf5200442f4c9890a0cc044fb13d4a09918893dce261bb14eec2f578b590ed5c925f66bcfeddf794bee6a014c65e049f544953cb09"); + EXPECT_TRUE(opePublicKey.verify(oepSignature, TW::data(oepPreImageHash))); + + const Data oepOutputData = TransactionCompiler::compileWithSignatures( + coin, oepTxInputData, {oepSignature}, {oepPublicKeyData}); + auto oepOutput = Ontology::Proto::SigningOutput(); + oepOutput.ParseFromArray(oepOutputData.data(), (int)oepOutputData.size()); + const auto oepExpectedTx = + "00d101000000c409000000000000204e000000000000c9546dcef4038ce3b64e79d079b3c97a8931c7174d02e8" + "031469c329fbb30a490979ea1a6f0b6a3a91235f6bd714c9546dcef4038ce3b64e79d079b3c97a8931c71753c1" + "087472616e7366657267e77fb36f54874c29f503d301d91d8ab98eb2342f0001414055aff2726c5e17dd6a6bbd" + "af5200442f4c9890a0cc044fb13d4a09918893dce261bb14eec2f578b590ed5c925f66bcfeddf794bee6a014c6" + "5e049f544953cb09232103932a08085b4bd7adcf8915f805ab35ad51f58ebbd09783b01bb4c44e503444f9ac"; + EXPECT_EQ(hex(oepOutput.encoded()), oepExpectedTx); +} + +} // namespace TW::Ontology::tests diff --git a/tests/chains/Ontology/TransactionTests.cpp b/tests/chains/Ontology/TransactionTests.cpp new file mode 100644 index 00000000000..43def521309 --- /dev/null +++ b/tests/chains/Ontology/TransactionTests.cpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" + +#include "Ontology/ParamsBuilder.h" +#include "Ontology/Signer.h" +#include "Ontology/Transaction.h" + +#include + +#include + +namespace TW::Ontology::tests { + +TEST(OntologyTransaction, validity) { + std::vector ontContract{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; + auto fromAddress = Address("AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5"); + auto toAddress = Address("APniYDGozkhUh8Tk7pe35aah2HGJ4fJfVd"); + uint64_t amount = 1; + NeoVmParamValue::ParamList transferParam{fromAddress._data, toAddress._data, amount}; + NeoVmParamValue::ParamArray args{transferParam}; + auto invokeCode = ParamsBuilder::buildNativeInvokeCode(ontContract, 0x00, "transfer", {args}); + uint8_t version = 0; + uint8_t txType = 0xd1; + uint32_t nonce = 1552759011; + uint64_t gasPrice = 600; + uint64_t gasLimit = 300000; + auto tx = + Transaction(version, txType, nonce, gasPrice, gasLimit, toAddress.string(), invokeCode); + std::string hexTx = + "00d1e3388d5c5802000000000000e09304000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c6" + "6b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc81457e9d1a61f9aafa798b6c7fbeae35639681d7d" + "f66a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f" + "6e746f6c6f67792e4e61746976652e496e766f6b650000"; + EXPECT_EQ(hexTx, hex(tx.serialize())); + auto signer1 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); + signer1.sign(tx); + hexTx = + "00d1e3388d5c5802000000000000e09304000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c6" + "6b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc81457e9d1a61f9aafa798b6c7fbeae35639681d7d" + "f66a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f" + "6e746f6c6f67792e4e61746976652e496e766f6b6500014140e03a09d85f56d2ceb5817a1f3a430bab9bf0f469" + "da38afe4a5b33de258a06236d8e0a59d25918a49825455c99f91de9caf8071e38a589a530519705af9081eca23" + "21031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac"; + EXPECT_EQ(520ul, hex(tx.serialize()).length()); + EXPECT_EQ(hexTx.substr(0, 20), hex(tx.serialize()).substr(0, 20)); + auto signer2 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); + signer2.addSign(tx); + auto result = tx.serialize(); + auto verifyPosition1 = + hex(result).find("21031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac"); + auto verifyPosition2 = + hex(result).find("2103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac"); + EXPECT_EQ(450ul, verifyPosition1); + EXPECT_EQ(654ul, verifyPosition2); + EXPECT_EQ(724ul, hex(result).length()); +} + +} // namespace TW::Ontology::tests diff --git a/tests/chains/OpBNBtestnet/TWCoinTypeTests.cpp b/tests/chains/OpBNBtestnet/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..aca675e68fb --- /dev/null +++ b/tests/chains/OpBNBtestnet/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWOpBNBtestnetCoinType, TWCoinType) { + const auto coin = TWCoinTypeOpBNB; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x788ea8fb4a82dae957f1d3b18af3cd0bbde55a276e66bd17af8c869f24c03a0f")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x4eaf936c172b5e5511959167e8ab4f7031113ca3")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "opbnb"); + assertStringsEqual(name, "OpBNB"); + assertStringsEqual(symbol, "BNB"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "204"); + assertStringsEqual(txUrl, "https://opbnbscan.com/tx/0x788ea8fb4a82dae957f1d3b18af3cd0bbde55a276e66bd17af8c869f24c03a0f"); + assertStringsEqual(accUrl, "https://opbnbscan.com/address/0x4eaf936c172b5e5511959167e8ab4f7031113ca3"); +} diff --git a/tests/chains/Optimism/TWCoinTypeTests.cpp b/tests/chains/Optimism/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..97be122198d --- /dev/null +++ b/tests/chains/Optimism/TWCoinTypeTests.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +TEST(TWOptimismCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeOptimism)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x6fd99288be9bf71eb002bb31da10a4fb0fbbb3c45ae73693b212f49c9db7df8f")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeOptimism, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x1f932361e31d206b4f6b2478123a9d0f8c761031")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeOptimism, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeOptimism)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeOptimism)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeOptimism), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeOptimism)); + + assertStringsEqual(symbol, "ETH"); + assertStringsEqual(txUrl, "https://optimistic.etherscan.io/tx/0x6fd99288be9bf71eb002bb31da10a4fb0fbbb3c45ae73693b212f49c9db7df8f"); + assertStringsEqual(accUrl, "https://optimistic.etherscan.io/address/0x1f932361e31d206b4f6b2478123a9d0f8c761031"); + assertStringsEqual(id, "optimism"); + assertStringsEqual(name, "OP Mainnet"); +} diff --git a/tests/chains/POANetwork/TWCoinTypeTests.cpp b/tests/chains/POANetwork/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..b43bedb7d0f --- /dev/null +++ b/tests/chains/POANetwork/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWPOANetworkCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypePOANetwork)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypePOANetwork, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypePOANetwork, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypePOANetwork)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypePOANetwork)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypePOANetwork), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypePOANetwork)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypePOANetwork)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypePOANetwork)); + assertStringsEqual(symbol, "POA"); + assertStringsEqual(txUrl, "https://blockscout.com/poa/core/tx/t123"); + assertStringsEqual(accUrl, "https://blockscout.com/poa/core/address/a12"); + assertStringsEqual(id, "poa"); + assertStringsEqual(name, "POA Network"); +} diff --git a/tests/chains/Pivx/AddressTests.cpp b/tests/chains/Pivx/AddressTests.cpp new file mode 100644 index 00000000000..4032e352241 --- /dev/null +++ b/tests/chains/Pivx/AddressTests.cpp @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include "Coin.h" +#include "HexCoding.h" +#include "HDWallet.h" +#include "Bitcoin/Address.h" +#include "PublicKey.h" +#include +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(PivxAddress, Valid) { + ASSERT_TRUE(Address::isValid(std::string("DLCk9wuF3r8CRbawUhrDpDHXAGfkHAC7if"))); + ASSERT_TRUE(Address::isValid(std::string("DSHhD6WxT5RfG2deUzVfuC1jDuRfdDpsSo"))); +} + +TEST(PivxAddress, FromPublicKey) { + const auto publicKey = PublicKey(parse_hex("0273132ffeafdc0eec3aaef5684a320a677cc77ba43df81de944af4a7fc6de66c9"), TWPublicKeyTypeSECP256k1); + const auto address = Address(publicKey, TWCoinTypeP2pkhPrefix(TWCoinTypePivx)); + EXPECT_EQ(address.string(), "D6E4vLacYhKc6RVxLNQg5j8rtWbAH8fybH"); + + auto wallet = HDWallet("shoot island position soft burden budget tooth cruel issue economy destroy above", ""); + auto addr = wallet.deriveAddress(TWCoinTypePivx); + EXPECT_EQ(addr, "D81AqC8zKma3Cht4TbVuh4jyVVyLkZULCm"); +} + +TEST(PivxAddress, FromString) { + auto address = Address("D6E4vLacYhKc6RVxLNQg5j8rtWbAH8fybH"); + ASSERT_EQ(address.string(), "D6E4vLacYhKc6RVxLNQg5j8rtWbAH8fybH"); + + address = Address("DSHhD6WxT5RfG2deUzVfuC1jDuRfdDpsSo"); + ASSERT_EQ(address.string(), "DSHhD6WxT5RfG2deUzVfuC1jDuRfdDpsSo"); +} + +TEST(PivxAddress, AddressData) { + const auto publicKey = PublicKey(parse_hex("0273132ffeafdc0eec3aaef5684a320a677cc77ba43df81de944af4a7fc6de66c9"), TWPublicKeyTypeSECP256k1); + auto address = TW::deriveAddress(TWCoinTypePivx, publicKey); + + auto data = TW::addressToData(TWCoinTypePivx, "D6E4vLacYhKc6RVxLNQg5j8rtWbAH8fybH"); + ASSERT_EQ(hex(data), "0be8da968f9bc6c1c16b8c635544e757aade7013"); + ASSERT_EQ(data, TW::addressToData(TWCoinTypePivx, address)); + + data = TW::addressToData(TWCoinTypePivx, "1G15VvshDxwFTnahZZECJfFwEkq9fP79"); // invalid address + ASSERT_EQ(data.size(), 0ul); +} \ No newline at end of file diff --git a/tests/chains/Pivx/TWAnyAddressTests.cpp b/tests/chains/Pivx/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..9185d79531a --- /dev/null +++ b/tests/chains/Pivx/TWAnyAddressTests.cpp @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWPivx, Address) { + auto string = STRING("DSHhD6WxT5RfG2deUzVfuC1jDuRfdDpsSo"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypePivx)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "e7fae8ee6ecabaab1252f3b27679cb34f2406169"); + + string = STRING("D6E4vLacYhKc6RVxLNQg5j8rtWbAH8fybH"); + addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypePivx)); + string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "0be8da968f9bc6c1c16b8c635544e757aade7013"); +} diff --git a/tests/chains/Pivx/TWCoinTypeTests.cpp b/tests/chains/Pivx/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..dc73c23eba3 --- /dev/null +++ b/tests/chains/Pivx/TWCoinTypeTests.cpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWPivxCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypePivx)); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypePivx)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypePivx)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypePivx), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypePivx)); + ASSERT_EQ(13, TWCoinTypeP2shPrefix(TWCoinTypePivx)); + ASSERT_EQ(30, TWCoinTypeP2pkhPrefix(TWCoinTypePivx)); + assertStringsEqual(symbol, "PIVX"); + assertStringsEqual(id, "pivx"); + assertStringsEqual(name, "Pivx"); +} diff --git a/tests/chains/Pivx/TransactionCompilerTests.cpp b/tests/chains/Pivx/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..b654b874273 --- /dev/null +++ b/tests/chains/Pivx/TransactionCompilerTests.cpp @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include + +#include "Bitcoin/Script.h" +#include "Bitcoin/TransactionInput.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(PivxCompiler, CompileWithSignatures) { + // tx on mainnet + // https://pivx.ccore.online/transaction/e5376c954c748926c373eb08df50ad72b3869be230c659689f9d83c150efd6be + + const auto coin = TWCoinTypePivx; + const int64_t amount = 87090; + const int64_t fee = 2910; + const std::string toAddress = "D6MrY5B9oZaCYNaXCbt2uvmjKC1nPgrgrq"; + + auto toScript = Bitcoin::Script::lockScriptForAddress(toAddress, coin); + ASSERT_EQ(hex(toScript.bytes), "76a9140d61d810a1ae2a9c4638808dd73b64e3ea54caf488ac"); + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("DFsBL73ZaDAJkzv9DeBLEzX8Jh6kkmkfzV"); + input.set_coin_type(coin); + + auto txHash0 = parse_hex("1eda07bd98ea04d322d65facaed830024e264e356810e55111cf6d7c26dff3de"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(1); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(26910000); + + auto utxoAddr0 = "D6E4vLacYhKc6RVxLNQg5j8rtWbAH8fybH"; + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, coin); + ASSERT_EQ(hex(script0.bytes), "76a9140be8da968f9bc6c1c16b8c635544e757aade701388ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + EXPECT_EQ(input.utxo_size(), 1); + + // Plan + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(input, plan, coin); + + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(26820000); + + // Extend input with accepted plan + *input.mutable_plan() = plan; + + // Serialize input + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT((int)txInputData.size(), 0); + + /// Step 2: Obtain preimage hashes + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + TW::Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), (int)preImageHashes.size())); + + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].data_hash()), + "7d1af92dd981db6512142aa84c38385664d6751cc858a57e4adee34e6cae1449"); + + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].public_key_hash()), "0be8da968f9bc6c1c16b8c635544e757aade7013"); + + auto publicKeyHex = "0273132ffeafdc0eec3aaef5684a320a677cc77ba43df81de944af4a7fc6de66c9"; + auto publicKey = PublicKey(parse_hex(publicKeyHex), TWPublicKeyTypeSECP256k1); + auto preImageHash = preSigningOutput.hash_public_keys()[0].data_hash(); + auto signature = parse_hex("304402202bd34158770290fb304ec85d8a92671e003681e20fb64a346ac5bd8d3686571402207c64b662d85367a949cc275a15aa10713f91815c37cf2979408dc1aa9ddbf4ab"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE( + publicKey.verifyAsDER(signature, TW::Data(preImageHash.begin(), preImageHash.end()))); + + // Simulate signatures, normally obtained from signature server. + std::vector signatureVec; + std::vector pubkeyVec; + signatureVec.push_back(signature); + pubkeyVec.push_back(publicKey.bytes); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, pubkeyVec); + + const auto ExpectedTx = + "0100000001def3df267c6dcf1151e51068354e264e0230d8aeac5fd622d304ea98bd07da1e010000006a47304402202bd34158770290fb304ec85d8a92671e003681e20fb64a346ac5bd8d3686571402207c64b662d85367a949cc275a15aa10713f91815c37cf2979408dc1aa9ddbf4ab01210273132ffeafdc0eec3aaef5684a320a677cc77ba43df81de944af4a7fc6de66c9ffffffff0232540100000000001976a9140d61d810a1ae2a9c4638808dd73b64e3ea54caf488aca03d9901000000001976a91475a6ba23a1faaceed874538fe42362b72a1a156588ac00000000"; + { + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: not enough signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, pubkeyVec); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + { // Negative: invalid public key + const auto publicKeyBlake = + parse_hex("b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"); + EXPECT_EXCEPTION( + TransactionCompiler::compileWithSignatures( + coin, txInputData, signatureVec, {publicKeyBlake}), + "Invalid public key"); + } + { // Negative: wrong signature (formally valid) + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, + {parse_hex("415502201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b349022" + "00a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f3b51")}, + pubkeyVec); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signing); + } +} diff --git a/tests/chains/Polkadot/AddressTests.cpp b/tests/chains/Polkadot/AddressTests.cpp new file mode 100644 index 00000000000..47fb3a5d8ca --- /dev/null +++ b/tests/chains/Polkadot/AddressTests.cpp @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Polkadot/Address.h" +#include "PublicKey.h" +#include "PrivateKey.h" +#include +#include + +namespace TW::Polkadot::tests { + +TEST(PolkadotAddress, Validation) { + // Substrate ed25519 + ASSERT_FALSE(Address::isValid("5FqqU2rytGPhcwQosKRtW1E3ha6BJKAjHgtcodh71dSyXhoZ")); + // Bitcoin + ASSERT_FALSE(Address::isValid("1ES14c7qLb5CYhLMUekctxLgc1FV2Ti9DA")); + // Kusama ed25519 + ASSERT_FALSE(Address::isValid("FHKAe66mnbk8ke8zVWE9hFVFrJN1mprFPVmD5rrevotkcDZ")); + // Kusama secp256k1 + ASSERT_FALSE(Address::isValid("FxQFyTorsjVsjjMyjdgq8w5vGx8LiA1qhWbRYcFijxKKchx")); + // Kusama sr25519 + ASSERT_FALSE(Address::isValid("EJ5UJ12GShfh7EWrcNZFLiYU79oogdtXFUuDDZzk7Wb2vCe")); + + // Polkadot ed25519 + ASSERT_TRUE(Address::isValid("15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu")); + // Polkadot sr25519 + ASSERT_TRUE(Address::isValid("15AeCjMpcSt3Fwa47jJBd7JzQ395Kr2cuyF5Zp4UBf1g9ony")); + + ASSERT_TRUE(Address::isValid("cEYtw6AVMB27hFUs4gVukajLM7GqxwxUfJkbPY3rNToHMcCgb", 64)); + ASSERT_FALSE(Address::isValid("JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A", 64)); + + // Polymesh + ASSERT_TRUE(Address::isValid("2DxwekgWwK7sqVeuXGmaXLZUvwnewLTs2rvU2CFKLgvvYwCG", 12)); + ASSERT_FALSE(Address::isValid("JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A", 12)); +} + +TEST(PolkadotAddress, FromPrivateKey) { + // subkey phrase `chief menu kingdom stereo hope hazard into island bag trick egg route` + auto privateKey = PrivateKey(parse_hex("0x612d82bc053d1b4729057688ecb1ebf62745d817ddd9b595bc822f5f2ba0e41a")); + auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519)); + ASSERT_EQ(address.string(), "15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu"); +} + +TEST(PolkadotAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("0xbeff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519); + auto address = Address(publicKey); + ASSERT_EQ(address.string(), "15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu"); +} + +TEST(PolkadotAddress, FromPublicKeyWithPrefix) { + auto publicKey = PublicKey(parse_hex("0x92fd9c237030356e26cfcc4568dc71055d5ec92dfe0ff903767e00611971bad3"), TWPublicKeyTypeED25519); + auto addressPolkadot = Address(publicKey, 0); + ASSERT_EQ(addressPolkadot.string(), "14KjL5vGAYJCbKgZJmFKDSjewtBpvaxx9YvRZvi7qmb5s8CC"); + + auto addressAstar = Address(publicKey, 5); + ASSERT_EQ(addressAstar.string(), "ZG2d3dH5zfqNchsqReS6x4nBJuJCW7Z6Fh5eLvdA3ZXGkPd"); + + auto addressParallel = Address(publicKey, 172); + ASSERT_EQ(addressParallel.string(), "p8EGHjWt7e1MYoD7V6WXvbPZWK9GSJiiK85kv2R7Ur7FisPUL"); + + // polymesh + publicKey = PublicKey(parse_hex("849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f83"), TWPublicKeyTypeED25519); + auto addressPolymesh = Address(publicKey, 12); + ASSERT_EQ(addressPolymesh.string(), "2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); +} + +TEST(PolkadotAddress, FromString) { + auto address = Address("15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu"); + ASSERT_EQ(address.string(), "15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu"); +} + +TEST(PolkadotAddress, FromStringWithPrefix) { + auto addressKusama = Address("Fu3r514w83euSVV7q1MyFGWErUR2xDzXS2goHzimUn4S12D", 2); + ASSERT_EQ(addressKusama.string(), "Fu3r514w83euSVV7q1MyFGWErUR2xDzXS2goHzimUn4S12D"); + + auto addressParallel = Address("p8EGHjWt7e1MYoD7V6WXvbPZWK9GSJiiK85kv2R7Ur7FisPUL", 172); + ASSERT_EQ(addressParallel.string(), "p8EGHjWt7e1MYoD7V6WXvbPZWK9GSJiiK85kv2R7Ur7FisPUL"); + + // polymesh + auto addressPolymesh = Address("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW", 12); + ASSERT_EQ(addressPolymesh.string(), "2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); +} + +} // namespace TW::Polkadot::tests diff --git a/tests/chains/Polkadot/ExtrinsicTests.cpp b/tests/chains/Polkadot/ExtrinsicTests.cpp new file mode 100644 index 00000000000..4f5c969722a --- /dev/null +++ b/tests/chains/Polkadot/ExtrinsicTests.cpp @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Data.h" +#include "HexCoding.h" +#include "Polkadot/Extrinsic.h" +#include "proto/Polkadot.pb.h" +#include "uint256.h" + +#include + +using namespace std; +using namespace TW; + +namespace TW::Polkadot::tests { + +TEST(PolkadotExtrinsic, Polymesh_encodeTransferWithMemo) { + Polkadot::Proto::SigningInput input; + input.set_network(12); + input.set_multi_address(true); + + auto* transfer = input.mutable_balance_call()->mutable_transfer(); + transfer->set_to_address("2EB7wW2fYfFskkSx2d65ivn34ewpuEjcowfJYBL79ty5FsZF"); + auto* callIndices = transfer->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x05); + callIndices->set_method_index(0x01); + + auto value = store(1); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_memo("MEMO PADDED WITH SPACES"); + + auto result = Polkadot::Extrinsic(input).encodeCall(input); + EXPECT_EQ(hex(result), "0501004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e0204014d454d4f20504144444544205749544820535041434553000000000000000000"); +} + +TEST(PolkadotExtrinsic, Polymesh_encodeAuthorizationJoinIdentity) { + Polkadot::Proto::SigningInput input; + input.set_network(12); + input.set_multi_address(true); + + auto* identity = input.mutable_polymesh_call()->mutable_identity_call()->mutable_add_authorization(); + identity->set_target("2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc"); + auto* authCallIndices = identity->mutable_call_indices()->mutable_custom(); + authCallIndices->set_module_index(0x07); + authCallIndices->set_method_index(0x0d); + + auto result = Polkadot::Extrinsic(input).encodeCall(input); + EXPECT_EQ(hex(result), "070d0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320501000100010000"); + + auto* authData = identity->mutable_data(); + authData->mutable_asset()->set_data({0x00}); + authData->mutable_extrinsic()->set_data({0x00}); + authData->mutable_portfolio()->set_data({0x00}); + + EXPECT_EQ(hex(result), hex(Polkadot::Extrinsic(input).encodeCall(input))); + + // clear data + authData->clear_asset(); + authData->clear_extrinsic(); + authData->clear_portfolio(); + + EXPECT_EQ(hex(Polkadot::Extrinsic(input).encodeCall(input)), "070d0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000"); +} + +TEST(PolkadotExtrinsic, Polymesh_encodeIdentity) { + Polkadot::Proto::SigningInput input; + input.set_network(12); + input.set_multi_address(true); + + auto* key = input.mutable_polymesh_call()->mutable_identity_call()->mutable_join_identity_as_key(); + key->set_auth_id(4875); + auto* callIndices = key->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x07); + callIndices->set_method_index(0x05); + + auto result = Polkadot::Extrinsic(input).encodeCall(input); + EXPECT_EQ(hex(result), "07050b13000000000000"); +} + +TEST(PolkadotExtrinsic, Statemint_encodeAssetTransfer) { + // tx on mainnet + // https://statemint.subscan.io/extrinsic/2619512-2 + + Polkadot::Proto::SigningInput input; + input.set_network(0); + input.set_multi_address(true); + + auto* transfer = input.mutable_balance_call()->mutable_asset_transfer(); + transfer->set_to_address("14ixj163bkk2UEKLEXsEWosuFNuijpqEWZbX5JzN4yMHbUVD"); + auto* callIndices = transfer->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x32); + callIndices->set_method_index(0x05); + + auto value = store(999500000); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_asset_id(1984); + + auto result = Polkadot::Extrinsic(input).encodeCall(input); + // clang-format off + EXPECT_EQ(hex(result), "3205" + "011f" + "00" + "a4b558a0342ae6e379a7ed00d23ff505f1101646cb279844496ad608943eda0d" + "82a34cee"); + // clang-format on +} + +TEST(PolkadotExtrinsic, Statemint_encodeBatchAssetTransfer) { + // tx on mainnet + // https://statemint.subscan.io/extrinsic/2571849-2 + + Polkadot::Proto::SigningInput input; + input.set_network(0); + input.set_multi_address(true); + + auto* transfer = input.mutable_balance_call()->mutable_batch_asset_transfer(); + auto* batchCallIndices = transfer->mutable_call_indices()->mutable_custom(); + batchCallIndices->set_module_index(0x28); + batchCallIndices->set_method_index(0x00); + transfer->set_fee_asset_id(0x00); + + auto* t = transfer->add_transfers(); + t->set_to_address("13wQDQTMM6E9g5WD27e6UsWWTwHLaW763FQxnkbVaoKmsBQy"); + auto value = store(808081); + t->set_value(std::string(value.begin(), value.end())); + t->set_asset_id(1984); + + auto* transferCallIndices = t->mutable_call_indices()->mutable_custom(); + transferCallIndices->set_module_index(0x32); + transferCallIndices->set_method_index(0x06); + + auto result = Polkadot::Extrinsic(input).encodeCall(input); + // clang-format off + EXPECT_EQ(hex(result), "2800" + "04" + "3206" + "011f" + "00" + "81f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c91" + "46523100"); + // clang-format on +} + +TEST(PolkadotExtrinsic, Kusama_encodeAssetTransferNoCallIndices) { + Polkadot::Proto::SigningInput input; + input.set_network(0); + input.set_multi_address(true); + + auto* transfer = input.mutable_balance_call()->mutable_batch_asset_transfer(); + transfer->set_fee_asset_id(0x00); + + auto* t = transfer->add_transfers(); + t->set_to_address("13wQDQTMM6E9g5WD27e6UsWWTwHLaW763FQxnkbVaoKmsBQy"); + auto value = store(808081); + t->set_value(std::string(value.begin(), value.end())); + t->set_asset_id(1984); + + EXPECT_THROW(Polkadot::Extrinsic(input).encodeCall(input), std::invalid_argument); +} + +TEST(PolkadotExtrinsic, Polkadot_EncodePayloadWithNewSpec) { + Polkadot::Proto::SigningInput input; + input.set_network(0); + input.set_multi_address(true); + + auto* transfer = input.mutable_balance_call()->mutable_asset_transfer(); + transfer->set_to_address("14ixj163bkk2UEKLEXsEWosuFNuijpqEWZbX5JzN4yMHbUVD"); + auto* callIndices = transfer->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x32); + callIndices->set_method_index(0x05); + + auto value = store(999500000); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_asset_id(1984); + + input.set_spec_version(1002000); // breaking change happens at version 1002005 + auto result = Polkadot::Extrinsic(input).encodePayload(); + EXPECT_EQ(hex(result), "3205011f00a4b558a0342ae6e379a7ed00d23ff505f1101646cb279844496ad608943eda0d82a34cee00000000104a0f0000000000"); + + input.set_spec_version(1002005); // >= 1002005 + result = Polkadot::Extrinsic(input).encodePayload(); + EXPECT_EQ(hex(result), "3205011f00a4b558a0342ae6e379a7ed00d23ff505f1101646cb279844496ad608943eda0d82a34cee0000000000154a0f000000000000"); + + input.set_spec_version(1002006); // >= 1002005 + result = Polkadot::Extrinsic(input).encodePayload(); + EXPECT_EQ(hex(result), "3205011f00a4b558a0342ae6e379a7ed00d23ff505f1101646cb279844496ad608943eda0d82a34cee0000000000164a0f000000000000"); +} + + +} // namespace TW::Polkadot::tests diff --git a/tests/Polkadot/SS58AddressTests.cpp b/tests/chains/Polkadot/SS58AddressTests.cpp similarity index 94% rename from tests/Polkadot/SS58AddressTests.cpp rename to tests/chains/Polkadot/SS58AddressTests.cpp index a65ce6c965d..6482c70f1f0 100644 --- a/tests/Polkadot/SS58AddressTests.cpp +++ b/tests/chains/Polkadot/SS58AddressTests.cpp @@ -1,19 +1,19 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Polkadot/SS58Address.h" #include "HexCoding.h" #include "PublicKey.h" -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include using namespace TW; +namespace TW::Polkadot::tests { + TEST(SS58Address, IsValid) { EXPECT_TRUE(SS58Address::isValid("15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu", 0)); @@ -140,3 +140,5 @@ TEST(SS58Address, EncodeNetwork) { EXPECT_FALSE(SS58Address::encodeNetwork(0x4000, data)); EXPECT_FALSE(SS58Address::encodeNetwork(0x8000, data)); } + +} // namespace TW::Polkadot::tests diff --git a/tests/Polkadot/ScaleCodecTests.cpp b/tests/chains/Polkadot/ScaleCodecTests.cpp similarity index 76% rename from tests/Polkadot/ScaleCodecTests.cpp rename to tests/chains/Polkadot/ScaleCodecTests.cpp index efdd07515c8..7202e84037d 100644 --- a/tests/Polkadot/ScaleCodecTests.cpp +++ b/tests/chains/Polkadot/ScaleCodecTests.cpp @@ -1,19 +1,17 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "HexCoding.h" #include "Polkadot/ScaleCodec.h" #include "Kusama/Address.h" +#include "uint256.h" #include using namespace TW; -using namespace TW::Polkadot; - +namespace TW::Polkadot::tests { TEST(PolkadotCodec, EncodeCompact) { ASSERT_EQ(hex(encodeCompact(0)), "00"); @@ -36,7 +34,7 @@ TEST(PolkadotCodec, EncodeCompact) { ASSERT_EQ(hex(encodeCompact(72057594037927935)), "0fffffffffffffff"); ASSERT_EQ(hex(encodeCompact(72057594037927936)), "130000000000000001"); - + ASSERT_EQ(hex(encodeCompact(18446744073709551615u)), "13ffffffffffffffff"); } @@ -70,9 +68,27 @@ TEST(PolkadotCodec, EncodeVectorAccountIds) { ASSERT_EQ(hex(encoded), "08008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc72"); } +TEST(PolkadotCodec, EncodeVectorAccountIdsKusama) { + auto addresses = std::vector{ + SS58Address("FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP", TWSS58AddressTypeKusama), + SS58Address("CtwdfrhECFs3FpvCGoiE4hwRC4UsSiM8WL899HjRdQbfYZY", TWSS58AddressTypeKusama)}; + auto encoded = encodeAccountIds(addresses, false); + ASSERT_EQ(hex(encoded), "08008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000e33fdfb980e4499e5c3576e742a563b6a4fc0f6f598b1917fd7a6fe393ffc72"); +} + TEST(PolkadotCodec, EncodeEra) { auto era1 = encodeEra(429119, 8); auto era2 = encodeEra(428861, 4); + auto era3 = encodeEra(4246319, 64); ASSERT_EQ(hex(era1), "7200"); ASSERT_EQ(hex(era2), "1100"); + EXPECT_EQ(hex(era3), "f502"); } + +TEST(PolkadotCodec, CountBytes) { + EXPECT_EQ(size_t(1), countBytes(uint256_t(0))); + EXPECT_EQ(size_t(1), countBytes(uint256_t(1))); + EXPECT_EQ(size_t(2), countBytes(uint256_t("0x1ff"))); +} + +} // namespace TW::Polkadot::tests diff --git a/tests/chains/Polkadot/SignerTests.cpp b/tests/chains/Polkadot/SignerTests.cpp new file mode 100644 index 00000000000..7e712160f51 --- /dev/null +++ b/tests/chains/Polkadot/SignerTests.cpp @@ -0,0 +1,696 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Polkadot/Address.h" +#include "Polkadot/Extrinsic.h" +#include "Polkadot/SS58Address.h" +#include "Polkadot/Signer.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "proto/Polkadot.pb.h" +#include "Coin.h" +#include "uint256.h" + +#include +#include + + +namespace TW::Polkadot::tests { + auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); + auto privateKeyIOS = PrivateKey(parse_hex("37932b086586a6675e66e562fe68bd3eeea4177d066619c602fe3efc290ada62")); + auto privateKeyThrow2 = PrivateKey(parse_hex("70a794d4f1019c3ce002f33062f45029c4f930a56b3d20ec477f7668c6bbc37f")); + auto privateKeyPolkadot = PrivateKey(parse_hex("298fcced2b497ed48367261d8340f647b3fca2d9415d57c2e3c5ef90482a2266")); + auto addressThrow2 = "14Ztd3KJDaB9xyJtRkREtSZDdhLSbm7UUKt8Z7AwSv7q85G2"; + auto toPublicKey = PublicKey(parse_hex("0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"), TWPublicKeyTypeED25519); + auto genesisHash = parse_hex("91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"); + auto controller1 = "14xKzzU1ZYDnzFj7FgdtDAYSMJNARjDc2gNw4XAFDgr4uXgp"; + +TEST(PolkadotSigner, SignTransfer_9fd062) { + auto toAddress = Address("13ZLCqJNPsRZYEbwjtZZFpWt9GyFzg5WahXCVWKpWdUJqrQ5"); + + auto input = Proto::SigningInput(); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + auto blockHash = parse_hex("0x5d2143bb808626d63ad7e1cda70fa8697059d670a992e82cd440fbb95ea40351"); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(3); + input.set_spec_version(26); + { + PublicKey publicKey = privateKeyThrow2.getPublicKey(TWPublicKeyTypeED25519); + Address address = Address(publicKey); + EXPECT_EQ(address.string(), addressThrow2); + } + input.set_private_key(privateKeyThrow2.bytes.data(), privateKeyThrow2.bytes.size()); + input.set_network(ss58Prefix(TWCoinTypePolkadot)); + input.set_transaction_version(5); + + // era: for blockhash and block number, use curl -H "Content-Type: application/json" -H "Accept: text/plain" https:///transaction/material?noMeta=true + auto era = input.mutable_era(); + era->set_block_number(3541050); + era->set_period(64); + + auto balanceCall = input.mutable_balance_call(); + auto transfer = balanceCall->mutable_transfer(); + auto value = store(uint256_t(2000000000)); // 0.2 + transfer->set_to_address(toAddress.string()); + transfer->set_value(value.data(), value.size()); + + auto extrinsic = Extrinsic(input); + auto preimage = extrinsic.encodePayload(); + EXPECT_EQ(hex(preimage), "05007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0300943577a5030c001a0000000500000091b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c35d2143bb808626d63ad7e1cda70fa8697059d670a992e82cd440fbb95ea40351"); + + auto output = Signer::sign(input); + // https://polkadot.subscan.io/extrinsic/0x9fd06208a6023e489147d8d93f0182b0cb7e45a40165247319b87278e08362d8 + EXPECT_EQ(hex(output.encoded()), "3502849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f7830073e59cef381aedf56d7af076bafff9857ffc1e3bd7d1d7484176ff5b58b73f1211a518e1ed1fd2ea201bd31869c0798bba4ffe753998c409d098b65d25dff801a5030c0005007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0300943577"); +} + +TEST(PolkadotSigner, SignTransferDOT) { + + auto blockHash = parse_hex("0x343a3f4258fd92f5ca6ca5abdf473d86a78b0bcd0dc09c568ca594245cc8c642"); + auto toAddress = SS58Address(toPublicKey, TWSS58AddressTypePolkadot); + + auto input = Proto::SigningInput(); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + + input.set_nonce(0); + input.set_spec_version(17); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_network(ss58Prefix(TWCoinTypePolkadot)); + input.set_transaction_version(3); + + auto& era = *input.mutable_era(); + era.set_block_number(927699); + era.set_period(8); + + auto balanceCall = input.mutable_balance_call(); + auto& transfer = *balanceCall->mutable_transfer(); + auto value = store(uint256_t(12345)); + transfer.set_to_address(toAddress.string()); + transfer.set_value(value.data(), value.size()); + + auto extrinsic = Extrinsic(input); + auto preimage = extrinsic.encodePayload(); + auto output = Signer::sign(input); + + ASSERT_EQ(hex(preimage), "05008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c032000000110000000300000091b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3343a3f4258fd92f5ca6ca5abdf473d86a78b0bcd0dc09c568ca594245cc8c642"); + ASSERT_EQ(hex(output.encoded()), "29028488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee003d91a06263956d8ce3ce5c55455baefff299d9cb2bb3f76866b6828ee4083770b6c03b05d7b6eb510ac78d047002c1fe5c6ee4b37c9c5a8b09ea07677f12e50d3200000005008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"); +} + +TEST(PolkadotSigner, SignTransfer_72dd5b) { + + auto blockHash = parse_hex("7d5fa17b70251d0806f26156b1b698dfd09e040642fa092595ce0a78e9e84fcd"); + + auto input = Proto::SigningInput(); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + + input.set_nonce(1); + input.set_spec_version(28); + input.set_private_key(privateKeyIOS.bytes.data(), privateKeyIOS.bytes.size()); + input.set_network(ss58Prefix(TWCoinTypePolkadot)); + input.set_transaction_version(6); + + auto& era = *input.mutable_era(); + era.set_block_number(3910736); + era.set_period(64); + + auto balanceCall = input.mutable_balance_call(); + auto& transfer = *balanceCall->mutable_transfer(); + auto value = store(uint256_t(10000000000)); + transfer.set_to_address("13ZLCqJNPsRZYEbwjtZZFpWt9GyFzg5WahXCVWKpWdUJqrQ5"); + transfer.set_value(value.data(), value.size()); + + auto extrinsic = Extrinsic(input); + auto preimage = extrinsic.encodePayload(); + auto output = Signer::sign(input); + + ASSERT_EQ(hex(preimage), "0500007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0700e40b5402050104001c0000000600000091b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c37d5fa17b70251d0806f26156b1b698dfd09e040642fa092595ce0a78e9e84fcd"); + ASSERT_EQ(hex(output.encoded()), "410284008d96660f14babe708b5e61853c9f5929bc90dd9874485bf4d6dc32d3e6f22eaa0038ec4973ab9773dfcbf170b8d27d36d89b85c3145e038d68914de83cf1f7aca24af64c55ec51ba9f45c5a4d74a9917dee380e9171108921c3e5546e05be15206050104000500007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0700e40b5402"); +} + +TEST(PolkadotSigner, SignBond_8da66d) { + auto input = Proto::SigningInput(); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + auto blockHash = parse_hex("0xf1eee612825f29abd3299b486e401299df2faa55b7ce1e34bf2243bd591905fc"); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(0); + input.set_spec_version(26); + { + PublicKey publicKey = privateKeyThrow2.getPublicKey(TWPublicKeyTypeED25519); + Address address = Address(publicKey); + EXPECT_EQ(address.string(), addressThrow2); + } + input.set_private_key(privateKeyThrow2.bytes.data(), privateKeyThrow2.bytes.size()); + input.set_network(ss58Prefix(TWCoinTypePolkadot)); + input.set_transaction_version(5); + + // era: for blockhash and block number, use curl -H "Content-Type: application/json" -H "Accept: text/plain" https:///transaction/material?noMeta=true + auto era = input.mutable_era(); + era->set_block_number(3540912); + era->set_period(64); + + auto stakingCall = input.mutable_staking_call(); + auto bond = stakingCall->mutable_bond(); + auto value = store(uint256_t(11000000000)); // 1.1 + bond->set_controller(addressThrow2); // myself + bond->set_value(value.data(), value.size()); + bond->set_reward_destination(Proto::RewardDestination::STASH); + + auto output = Signer::sign(input); + // https://polkadot.subscan.io/extrinsic/0x8da66d3fe0f592cff714ec107289370365117a1abdb72a19ac91181fdcf62bba + ASSERT_EQ(hex(output.encoded()), "3d02849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f783009025843bc49c1c4fbc99dbbd290c92f9879665d55b02f110abfb4800f0e7630877d2cffd853deae7466c22fbc8616a609e1b92615bb365ea8adccba5ef7624050503000007009dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f7830700aea68f0201"); +} + +TEST(PolkadotSigner, SignBondAndNominate_4955314_2) { + + auto key = parse_hex("7f44b19b391a8015ca4c7d94097b3695867a448d1391e7f3243f06987bdb6858"); + auto input = Proto::SigningInput(); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(genesisHash.data(), genesisHash.size()); + input.set_nonce(4); + input.set_spec_version(30); + input.set_private_key(key.data(), key.size()); + input.set_network(ss58Prefix(TWCoinTypePolkadot)); + input.set_transaction_version(7); + + auto stakingCall = input.mutable_staking_call(); + auto bondnom = stakingCall->mutable_bond_and_nominate(); + auto value = store(uint256_t(10000000000)); // 1 DOT + bondnom->set_controller("13ZLCqJNPsRZYEbwjtZZFpWt9GyFzg5WahXCVWKpWdUJqrQ5"); + bondnom->set_value(value.data(), value.size()); + bondnom->set_reward_destination(Proto::RewardDestination::STASH); + bondnom->add_nominators("1zugcavYA9yCuYwiEYeMHNJm9gXznYjNfXQjZsZukF1Mpow"); + bondnom->add_nominators("15oKi7HoBQbwwdQc47k71q4sJJWnu5opn1pqoGx4NAEYZSHs"); + + auto output = Signer::sign(input); + // https://polkadot.subscan.io/extrinsic/4955314-2 + ASSERT_EQ(hex(output.encoded()), "6103840036092fac541e0e5feda19e537c679b487566d7101141c203ac8322c27e5f076a00a8b1f859d788f11a958e98b731358f89cf3fdd41a667ea992522e8d4f46915f4c03a1896f2ac54bdc5f16e2ce8a2a3bf233d02aad8192332afd2113ed6688e0d0010001a02080700007120f76076bcb0efdf94c7219e116899d0163ea61cb428183d71324eb33b2bce0700e40b540201070508002c2a55b5ffdca266bd0207df97565b03255f70783ca1a349be5ed9f44589c36000d44533a4d21fd9d6f5d57c8cd05c61a6f23f9131cec8ae386b6b437db399ec3d"); +} + +TEST(PolkadotSigner, SignNominate_452522) { + auto input = Proto::SigningInput(); + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + auto blockHash = parse_hex("0x211787d016e39007ac054547737a10542620013e73648b3134541d536cb44e2c"); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(1); + input.set_spec_version(26); + input.set_private_key(privateKeyThrow2.bytes.data(), privateKeyThrow2.bytes.size()); + input.set_network(ss58Prefix(TWCoinTypePolkadot)); + input.set_transaction_version(5); + + // era: for blockhash and block number, use curl -H "Content-Type: application/json" -H "Accept: text/plain" https:///transaction/material?noMeta=true + auto era = input.mutable_era(); + era->set_block_number(3540945); + era->set_period(64); + + auto stakingCall = input.mutable_staking_call(); + auto nominate = stakingCall->mutable_nominate(); + + nominate->add_nominators(controller1); + nominate->add_nominators("1REAJ1k691g5Eqqg9gL7vvZCBG7FCCZ8zgQkZWd4va5ESih"); + + auto output = Signer::sign(input); + // https://polkadot.subscan.io/extrinsic/0x4525224b7d8f3e58de3a54a9fbfd071401c2b737f314c972a2bb087a0ff508a6 + ASSERT_EQ(hex(output.encoded()), "a502849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f78300d73ff0dc456704743f70173a56e6c13e88a6e1dddb38a23552a066e44fb64e2c9d8a5e9a76afb9489b8540365f668bddd34b7d9c8dbdc4600e6316080e55a30315010400070508aee72821ca00e62304e4f0d858122a65b87c8df4f0eae224ae064b951d39f610127a30e486492921e58f2564b36ab1ca21ff630672f0e76920edd601f8f2b89a"); +} + +TEST(PolkadotSigner, SignNominate2) { + auto blockHash = parse_hex("d22a6b2e3e61325050718bd04a14da9efca1f41c9f0a525c375d36106e25af68"); + auto input = Proto::SigningInput(); + + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(0); + input.set_spec_version(17); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_network(ss58Prefix(TWCoinTypePolkadot)); + input.set_transaction_version(3); + + auto stakingCall = input.mutable_staking_call(); + auto& nominate = *stakingCall->mutable_nominate(); + // payload size larger than 256, will be hashed + nominate.add_nominators("1zugcabYjgfQdMLC3cAzQ8tJZMo45tMnGpivpAzpxB4CZyK"); + nominate.add_nominators("1REAJ1k691g5Eqqg9gL7vvZCBG7FCCZ8zgQkZWd4va5ESih"); + nominate.add_nominators("1WG3jyNqniQMRZGQUc7QD2kVLT8hkRPGMSqAb5XYQM1UDxN"); + nominate.add_nominators("16QFrtU6kDdBjxY8qEKz5EEfuDkHxqG8pix3wSGKQzRcuWHo"); + nominate.add_nominators("14ShUZUYUR35RBZW6uVVt1zXDxmSQddkeDdXf1JkMA6P721N"); + nominate.add_nominators("15MUBwP6dyVw5CXF9PjSSv7SdXQuDSwjX86v1kBodCSWVR7c"); + + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.encoded()), "a1048488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee00135bbc68b67fffadaf7e98b6402c4fc60382765f543225083a024b0e0ff8071d4ec4ddd67a65828113cc76f3208765608be010d2fcfdcd47e8fe342872704c000000000705182c2a55b5a116a4c88aff57e8f2b70ba72dda72dda4b78630e16ad0ca69006f18127a30e486492921e58f2564b36ab1ca21ff630672f0e76920edd601f8f2b89a1650c532ed1a8641e8922aa24ade0ff411d03edd9ed1c6b7fe42f1a801cee37ceee9d5d071a418b51c02b456d5f5cefd6231041ad59b0e8379c59c11ba4a2439984e16482c99cfad1436111e321a86d87d0fac203bf64538f888e45d793b5413c08d5de7a5d97bea2c7ddf516d0635bddc43f326ae2f80e2595b49d4a08c4619"); +} + +TEST(PolkadotSigner, SignChill) { + auto blockHash = parse_hex("1d4a1ecc8b1c37bf0ba5d3e0bf14ec5402fbb035eeaf6d8042c07ca5f8c57429"); + auto input = Proto::SigningInput(); + + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(0); + input.set_spec_version(17); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_network(ss58Prefix(TWCoinTypePolkadot)); + input.set_transaction_version(3); + + auto stakingCall = input.mutable_staking_call(); + auto __attribute__((unused))& chill = *stakingCall->mutable_chill(); + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.encoded()), "9d018488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0088b5e1cd93ba74b82e329f95e1b22660385970182172b2ae280801fdd1ee5652cf7bf319e5e176ccc299dd8eb1e7fccb0ea7717efaf4aacd7640789dd09c1e070000000706"); +} + +TEST(PolkadotSigner, SignWithdraw) { + auto blockHash = parse_hex("7b4d1d1e2573eabcc90a3e96058eb0d8d21d7a0b636e8030d152d9179a345dda"); + auto input = Proto::SigningInput(); + + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(0); + input.set_spec_version(17); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_network(ss58Prefix(TWCoinTypePolkadot)); + input.set_transaction_version(3); + + auto stakingCall = input.mutable_staking_call(); + auto& withdraw = *stakingCall->mutable_withdraw_unbonded(); + withdraw.set_slashing_spans(10); + + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.encoded()), "ad018488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee002e49bf0dec9bef01dd3bd25419e2147dc983613d0860108f889f9ff2d062c5e3267e309e2dbc35dd2fc2b877b57d86a5f12cbeb8217485be32be3c34d2507d0e00000007030a000000"); +} + +TEST(PolkadotSigner, SignUnbond_070957) { + auto input = Proto::SigningInput(); + + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + auto blockHash = parse_hex("0x53040c71c6061bd256346b81fcb3545c13b5c34c7cd0c2c25f00aa6e564b16d5"); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(2); + input.set_spec_version(26); + input.set_private_key(privateKeyThrow2.bytes.data(), privateKeyThrow2.bytes.size()); + input.set_network(ss58Prefix(TWCoinTypePolkadot)); + input.set_transaction_version(5); + + auto era = input.mutable_era(); + era->set_block_number(3540983); + era->set_period(64); + + auto stakingCall = input.mutable_staking_call(); + auto unbond = stakingCall->mutable_unbond(); + auto value = store(uint256_t(4000000000)); + unbond->set_value(value.data(), value.size()); + + auto output = Signer::sign(input); + // https://polkadot.subscan.io/extrinsic/0x070957ab697adbe11f7d72a1314d0a81d272a747d2e6880818073317125f980a + ASSERT_EQ(hex(output.encoded()), "b501849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f783003a762d9dc3f2aba8922c4babf7e6622ca1d74da17ab3f152d8f29b0ffee53c7e5e150915912a9dfd98ef115d272e096543eef9f513207dd606eea97d023a64087503080007020300286bee"); +} + +TEST(PolkadotSigner, SignChillAndUnbond) { + auto blockHash = parse_hex("0x35ba668bb19453e8da6334cadcef2a27c8d4141bfc8b49e78e853c3d73e1ecd0"); + auto input = Proto::SigningInput(); + + input.set_genesis_hash(genesisHash.data(), genesisHash.size()); + input.set_block_hash(blockHash.data(), blockHash.size()); + input.set_nonce(6); + input.set_spec_version(9200); + input.set_private_key(privateKeyPolkadot.bytes.data(), privateKeyPolkadot.bytes.size()); + input.set_network(ss58Prefix(TWCoinTypePolkadot)); + input.set_transaction_version(12); + + auto era = input.mutable_era(); + era->set_block_number(10541373); + era->set_period(64); + + auto stakingCall = input.mutable_staking_call(); + auto chillBond = stakingCall->mutable_chill_and_unbond(); + auto value = store(uint256_t(100500000000)); // 10.05 DOT + chillBond->set_value(value.data(), value.size()); + + auto output = Signer::sign(input); + // https://polkadot.subscan.io/extrinsic/10541383-2 + ASSERT_EQ(hex(output.encoded()), "d10184008361bd08ddca5fda28b5e2aa84dc2621de566e23e089e555a42194c3eaf2da7900c891ba102db672e378945d74cf7f399226a76b43cab502436971599255451597fc2599902e4b62c7ce85ecc3f653c693fef3232be620984b5bb5bcecbbd7b209d50318001a02080706070207004d446617"); +} + +TEST(PolkadotSigner, PolymeshEncodeAndSign) { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x9a4283cc38f7e769c53ad2d1c5cf292fc85a740ec1c1aa80c180847e51928650 + + Polkadot::Proto::SigningInput input; + input.set_network(12); + input.set_multi_address(true); + auto blockHash = parse_hex("898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102"); + auto vGenesisHash = parse_hex("6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(vGenesisHash.begin(), vGenesisHash.end())); + input.set_nonce(1UL); + input.set_spec_version(3010u); + input.set_transaction_version(2u); + + auto* era = input.mutable_era(); + era->set_block_number(4298130UL); + era->set_period(64UL); + + auto* transfer = input.mutable_balance_call()->mutable_transfer(); + transfer->set_to_address("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); + auto value = store(1000000); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_memo("MEMO PADDED WITH SPACES"); + + auto* callIndices = transfer->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x05); + callIndices->set_method_index(0x01); + + auto preImage = Signer::signaturePreImage(input); + ASSERT_EQ(hex(preImage), "050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f2050414444454420574954482053504143455300000000000000000025010400c20b0000020000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102"); + + auto publicKey = parse_hex("4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"); + auto signature = parse_hex("0791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f"); + auto encoded = Signer::encodeTransaction(input, publicKey, signature); + ASSERT_EQ(hex(encoded), "bd0284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee000791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f25010400050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f20504144444544205749544820535041434553000000000000000000"); +} + +TEST(PolkadotSigner, Statemint_encodeTransaction_transfer) { + // tx on mainnet + // https://statemint.subscan.io/extrinsic/2686030-2 + + Polkadot::Proto::SigningInput input; + input.set_network(0); + input.set_multi_address(true); + auto blockHash = parse_hex("68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f"); + auto vGenesisHash = parse_hex("68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(vGenesisHash.begin(), vGenesisHash.end())); + input.set_nonce(0UL); + input.set_spec_version(9320U); + input.set_transaction_version(9U); + + auto* transfer = input.mutable_balance_call()->mutable_asset_transfer(); + transfer->set_to_address("12q4hq1dgqHZVGzHbwZmqq1cFwatN15Visfd7YmUiMB5ZWkH"); + + auto value = store(100000); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_asset_id(1984); + transfer->set_fee_asset_id(0x00); // native token + + auto* callIndices = transfer->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x32); + callIndices->set_method_index(0x05); + + auto publicKey = parse_hex("81f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c91"); + auto signature = parse_hex("e0ae36a5ceaaa7ff53fadfecc8a285a436b15e39c43ea09e8897f34fa3fe55133028eb7d8a9ea2cd42ff1c786e945cd47a02243454ecb39c81acc3409d96f903"); + auto encoded = Signer::encodeTransaction(input, publicKey, signature); + ASSERT_EQ(hex(encoded), "4102840081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c9100e0ae36a5ceaaa7ff53fadfecc8a285a436b15e39c43ea09e8897f34fa3fe55133028eb7d8a9ea2cd42ff1c786e945cd47a02243454ecb39c81acc3409d96f903000000003205011f0050e47b3c8aef60bc4fc744d8d979cb0eb2d45fa25c2e9da74e1e5ebd9e117518821a0600"); + + // 3d 02840081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c9100e0ae36a5ceaaa7ff53fadfecc8a285a436b15e39c43ea09e8897f34fa3fe55133028eb7d8a9ea2cd42ff1c786e945cd47a02243454ecb39c81acc3409d96f903000000 3205011f0050e47b3c8aef60bc4fc744d8d979cb0eb2d45fa25c2e9da74e1e5ebd9e117518821a0600 + // 41 02840081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c9100e0ae36a5ceaaa7ff53fadfecc8a285a436b15e39c43ea09e8897f34fa3fe55133028eb7d8a9ea2cd42ff1c786e945cd47a02243454ecb39c81acc3409d96f903000000 00 3205011f0050e47b3c8aef60bc4fc744d8d979cb0eb2d45fa25c2e9da74e1e5ebd9e117518821a0600 +} + +TEST(PolkadotSigner, Statemint_encodeTransaction_transfer_keep_alive) { + // tx on mainnet + // https://statemint.subscan.io/extrinsic/2686081-2 + + Polkadot::Proto::SigningInput input; + input.set_network(0); + input.set_multi_address(true); + auto blockHash = parse_hex("e8f10f9a841dc73578148c763afa17638670c8655542172a80af2e03bf3cbe62"); + auto vGenesisHash = parse_hex("68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(vGenesisHash.begin(), vGenesisHash.end())); + input.set_nonce(2UL); + input.set_spec_version(9320U); + input.set_transaction_version(9U); + + auto* era = input.mutable_era(); + era->set_block_number(2686056UL); + era->set_period(64UL); + + auto* transfer = input.mutable_balance_call()->mutable_asset_transfer(); + transfer->set_to_address("12q4hq1dgqHZVGzHbwZmqq1cFwatN15Visfd7YmUiMB5ZWkH"); + + auto value = store(100000); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_asset_id(1984); + transfer->set_fee_asset_id(0x00); // native token + + auto* callIndices = transfer->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x32); + callIndices->set_method_index(0x06); + + auto publicKey = parse_hex("81f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c91"); + auto signature = parse_hex("68c40526bd9e56e340bfc9385ea463afce34e5c49be75b5946974d9ef6a357f90842036cd1b811b60882ae7183aa23545ef5825aafc8aaa6274d71a03414dc0a"); + auto encoded = Signer::encodeTransaction(input, publicKey, signature); + ASSERT_EQ(hex(encoded), "4502840081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c910068c40526bd9e56e340bfc9385ea463afce34e5c49be75b5946974d9ef6a357f90842036cd1b811b60882ae7183aa23545ef5825aafc8aaa6274d71a03414dc0a85020800003206011f0050e47b3c8aef60bc4fc744d8d979cb0eb2d45fa25c2e9da74e1e5ebd9e117518821a0600"); +} + +TEST(PolkadotSigner, Statemint_encodeTransaction_batch_transfer_keep_alive) { + // tx on mainnet + // https://statemint.subscan.io/extrinsic/2711054-2 + + Polkadot::Proto::SigningInput input; + input.set_network(0); + input.set_multi_address(true); + auto blockHash = parse_hex("c8a2e9492f822f8c07f3717a00e36f68a3090a878b07998724ec1f178f4cf514"); + auto vGenesisHash = parse_hex("68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(vGenesisHash.begin(), vGenesisHash.end())); + input.set_nonce(3UL); + input.set_spec_version(9320U); + input.set_transaction_version(9U); + + auto* era = input.mutable_era(); + era->set_block_number(2711016UL); + era->set_period(64UL); + + auto* transfer = input.mutable_balance_call()->mutable_batch_asset_transfer(); + transfer->set_fee_asset_id(0x00); + auto* batchCallIndices = transfer->mutable_call_indices()->mutable_custom(); + batchCallIndices->set_module_index(0x28); + batchCallIndices->set_method_index(0x00); + + auto* t1 = transfer->add_transfers(); + t1->set_to_address("12q4hq1dgqHZVGzHbwZmqq1cFwatN15Visfd7YmUiMB5ZWkH"); + auto value = store(100000); + t1->set_value(std::string(value.begin(), value.end())); + t1->set_asset_id(1984); + t1->set_fee_asset_id(0x00); // native token + auto* t1CallIndices = t1->mutable_call_indices()->mutable_custom(); + t1CallIndices->set_module_index(0x32); + t1CallIndices->set_method_index(0x06); + + auto* t2 = transfer->add_transfers(); + t2->set_to_address("12q4hq1dgqHZVGzHbwZmqq1cFwatN15Visfd7YmUiMB5ZWkH"); + t2->set_value(std::string(value.begin(), value.end())); + t2->set_asset_id(1984); + t2->set_fee_asset_id(0x00); // native token + auto* t2CallIndices = t2->mutable_call_indices()->mutable_custom(); + t2CallIndices->set_module_index(0x32); + t2CallIndices->set_method_index(0x06); + + auto publicKey = parse_hex("81f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c91"); + auto signature = parse_hex("e1d541271965858ff2ba1a1296f0b4d28c8cbcaddf0ea06a9866869caeca3d16eff1265591d11b46d66882493079fde9e425cd941f166260135e9d81f7daf60c"); + auto encoded = Signer::encodeTransaction(input, publicKey, signature); + ASSERT_EQ(hex(encoded), "f502840081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c9100e1d541271965858ff2ba1a1296f0b4d28c8cbcaddf0ea06a9866869caeca3d16eff1265591d11b46d66882493079fde9e425cd941f166260135e9d81f7daf60c85020c00002800083206011f0050e47b3c8aef60bc4fc744d8d979cb0eb2d45fa25c2e9da74e1e5ebd9e117518821a06003206011f0050e47b3c8aef60bc4fc744d8d979cb0eb2d45fa25c2e9da74e1e5ebd9e117518821a0600"); +} + +TEST(PolkadotSigner, Statemint_encodeTransaction_dot_transfer_keep_alive) { + // tx on mainnet + // https://statemint.subscan.io/extrinsic/2789245-2 + + Polkadot::Proto::SigningInput input; + input.set_network(0); + input.set_multi_address(true); + auto blockHash = parse_hex("68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f"); + auto vGenesisHash = parse_hex("68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(vGenesisHash.begin(), vGenesisHash.end())); + input.set_nonce(7UL); + input.set_spec_version(9320U); + input.set_transaction_version(9U); + + auto* transfer = input.mutable_balance_call()->mutable_asset_transfer(); + transfer->set_to_address("12q4hq1dgqHZVGzHbwZmqq1cFwatN15Visfd7YmUiMB5ZWkH"); + + auto value = store(100000); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_asset_id(0x00); + transfer->set_fee_asset_id(0x00); // native token + + auto* callIndices = transfer->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x0a); + callIndices->set_method_index(0x03); + + auto publicKey = parse_hex("81f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c91"); + auto signature = parse_hex("c4f7cb46605986ff6dd1a192736feddd8ae468a10b1b458eadfa855ed6b59ad442a96c18e7109ad594d11ba2fd52920545f8a450234e9b03ee3e8f59a8f06f00"); + auto encoded = Signer::encodeTransaction(input, publicKey, signature); + ASSERT_EQ(hex(encoded), "3902840081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c9100c4f7cb46605986ff6dd1a192736feddd8ae468a10b1b458eadfa855ed6b59ad442a96c18e7109ad594d11ba2fd52920545f8a450234e9b03ee3e8f59a8f06f00001c00000a030050e47b3c8aef60bc4fc744d8d979cb0eb2d45fa25c2e9da74e1e5ebd9e117518821a0600"); +} + +TEST(PolkadotSigner, Statemint_encodeTransaction_usdt_transfer_keep_alive) { + // tx on mainnet + // https://statemint.subscan.io/extrinsic/2789377-2 + + Polkadot::Proto::SigningInput input; + input.set_network(0); + input.set_multi_address(true); + auto blockHash = parse_hex("68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f"); + auto vGenesisHash = parse_hex("68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(vGenesisHash.begin(), vGenesisHash.end())); + input.set_nonce(8UL); + input.set_spec_version(9320U); + input.set_transaction_version(9U); + + auto* transfer = input.mutable_balance_call()->mutable_asset_transfer(); + transfer->set_to_address("12q4hq1dgqHZVGzHbwZmqq1cFwatN15Visfd7YmUiMB5ZWkH"); + + auto value = store(100000); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_asset_id(1984); + transfer->set_fee_asset_id(1984); + + auto* callIndices = transfer->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x32); + callIndices->set_method_index(0x06); + + auto publicKey = parse_hex("81f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c91"); + auto signature = parse_hex("d22583408806c005a24caf16f2084691f4c6dcb6015e6645adc86fc1474369b0e0b7dbcc0ef25b17eae43844aff6fb42a0b279a19e822c76043cac015be5e40a"); + auto encoded = Signer::encodeTransaction(input, publicKey, signature); + ASSERT_EQ(hex(encoded), "5102840081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c9100d22583408806c005a24caf16f2084691f4c6dcb6015e6645adc86fc1474369b0e0b7dbcc0ef25b17eae43844aff6fb42a0b279a19e822c76043cac015be5e40a00200001c00700003206011f0050e47b3c8aef60bc4fc744d8d979cb0eb2d45fa25c2e9da74e1e5ebd9e117518821a0600"); +} + +TEST(PolkadotSigner, encodeTransaction_Add_authorization) { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x7d9b9109027b36b72d37ba0648cb70e5254524d3d6752cc6b41601f4bdfb1af0 + + Polkadot::Proto::SigningInput input; + input.set_network(12); + input.set_multi_address(true); + auto blockHash = parse_hex("ce0c2109db498e45abf8fd447580dcfa7b7a07ffc2bfb1a0fbdd1af3e8816d2b"); + auto vGenesisHash = parse_hex("6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(vGenesisHash.begin(), vGenesisHash.end())); + input.set_nonce(5UL); + input.set_spec_version(3010U); + input.set_transaction_version(2U); + + auto* era = input.mutable_era(); + era->set_block_number(4395451UL); + era->set_period(64UL); + + auto* addAuthorization = input.mutable_polymesh_call()->mutable_identity_call()->mutable_add_authorization(); + addAuthorization->set_target("2HEVN4PHYKj7B1krQ9bctAQXZxHQQkANVNCcfbdYk2gZ4cBR"); + auto* callIndices = addAuthorization->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x07); + callIndices->set_method_index(0x0d); + + auto publicKey = parse_hex("4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"); + auto signature = parse_hex("81e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01"); + auto encoded = Signer::encodeTransaction(input, publicKey, signature); + ASSERT_EQ(hex(encoded), "490284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee0081e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01b5031400070d01d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d0610540501000100010000"); +} + +TEST(PolkadotSigner, encodeTransaction_JoinIdentityAsKey) { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x9d7297d8b38af5668861996cb115f321ed681989e87024fda64eae748c2dc542 + + Polkadot::Proto::SigningInput input; + input.set_network(12); + input.set_multi_address(true); + auto blockHash = parse_hex("45c80153c47f5d16acc7a66d473870e8d4574437a7d8c813f47da74cae3812c2"); + auto vGenesisHash = parse_hex("6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(vGenesisHash.begin(), vGenesisHash.end())); + input.set_nonce(0UL); + input.set_spec_version(3010U); + input.set_transaction_version(2U); + + auto* era = input.mutable_era(); + era->set_block_number(4395527UL); + era->set_period(64UL); + + auto* key = input.mutable_polymesh_call()->mutable_identity_call()->mutable_join_identity_as_key(); + key->set_auth_id(21435); + auto* callIndices = key->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x07); + callIndices->set_method_index(0x05); + + auto publicKey = parse_hex("d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054"); + auto signature = parse_hex("7f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006"); + auto encoded = Signer::encodeTransaction(input, publicKey, signature); + ASSERT_EQ(hex(encoded), "c5018400d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054007f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006750000000705bb53000000000000"); +} + +TEST(PolkadotSigner, Kusama_SignBond_NoController) { + // tx on mainnet + // https://kusama.subscan.io/extrinsic/0x4e52e59b63910cbdb8c5430c2d100908934f473363c8994cddfd6d1501b017f5 + + Polkadot::Proto::SigningInput input; + input.set_network(ss58Prefix(TWCoinTypeKusama)); + auto blockHash = parse_hex("beb02a3ee782f4bd60ffcfc3de473e3c5a00b2cf124dd302c559b0e77b4331eb"); + auto vGenesisHash = parse_hex("b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(vGenesisHash.begin(), vGenesisHash.end())); + input.set_nonce(3UL); + input.set_spec_version(9430U); + input.set_transaction_version(23U); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto* era = input.mutable_era(); + era->set_block_number(18672490UL); + era->set_period(64UL); + + // Ignore `controller` as it was removed from the `Staking::bond` function at `spec_version = 9430` + // https://kusama.subscan.io/runtime/Staking?version=9430 + auto* bond = input.mutable_staking_call()->mutable_bond(); + auto value = store(uint256_t(120'000'000'000)); // 0.12 + bond->set_value(value.data(), value.size()); + bond->set_reward_destination(Proto::RewardDestination::CONTROLLER); + + auto output = Signer::sign(input); + ASSERT_EQ(hex(output.encoded()), "c101840088dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee00bc4d7a166bd1e7e2bfe9b53e81239c9e340d5a326f17c0a3d2768fcc127f20f4f85d888ecb90aa3ed9a0943f8ae8116b9a19747e563c8d8151dfe3b1b5deb40ca5020c0006000700b08ef01b02"); +} + +TEST(PolkadotSigner, SignTransfer_KusamaNewSpec) { + auto toAddress = Address("DAbYHrSQTULYZsuA1kvH2cQ33oBsCxxSRPM1XkhzGLeJuHG", ss58Prefix(TWCoinTypeKusama)); + + auto input = Proto::SigningInput(); + auto genesisHashData = parse_hex("0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe"); + input.set_genesis_hash(genesisHashData.data(), genesisHashData.size()); + auto blockHashData = parse_hex("0x0c731c2b7f5332749432eae61cd5a919592965b28181cf9b73b0a1258ea73303"); + input.set_block_hash(blockHashData.data(), blockHashData.size()); + input.set_nonce(150); + input.set_spec_version(1002005); + { + PublicKey publicKey = privateKeyThrow2.getPublicKey(TWPublicKeyTypeED25519); + Address address = Address(publicKey); + EXPECT_EQ(address.string(), addressThrow2); + } + input.set_private_key(privateKeyThrow2.bytes.data(), privateKeyThrow2.bytes.size()); + input.set_network(ss58Prefix(TWCoinTypeKusama)); + input.set_transaction_version(26); + + // era: for blockhash and block number, use curl -H "Content-Type: application/json" -H "Accept: text/plain" https:///transaction/material?noMeta=true + auto era = input.mutable_era(); + era->set_block_number(23610713); + era->set_period(64); + + auto balanceCall = input.mutable_balance_call(); + auto transfer = balanceCall->mutable_transfer(); + auto value = store(uint256_t(2000000000)); // 0.2 + transfer->set_to_address(toAddress.string()); + transfer->set_value(value.data(), value.size()); + + auto extrinsic = Extrinsic(input); + auto preimage = extrinsic.encodePayload(); + EXPECT_EQ(hex(preimage), "0400001a2447c661c9b168bba4a2a178baef7d79eee006c1d145ffc832be76ff6ee9ce0300943577950159020000154a0f001a000000b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe0c731c2b7f5332749432eae61cd5a919592965b28181cf9b73b0a1258ea7330300"); + + auto output = Signer::sign(input); + EXPECT_EQ(hex(output.encoded()), "450284009dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f78300fc5a463d3b6972ac7e0b701110f9d95d377be5b6a2f356765553104c04765fc0066c235c11dabde650d487760dc310003d607abceaf85a0a0f47f1a90e3680029501590200000400001a2447c661c9b168bba4a2a178baef7d79eee006c1d145ffc832be76ff6ee9ce0300943577"); +} + + +} // namespace TW::Polkadot::tests diff --git a/tests/chains/Polkadot/TWCoinTypeTests.cpp b/tests/chains/Polkadot/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..f59d9208226 --- /dev/null +++ b/tests/chains/Polkadot/TWCoinTypeTests.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +TEST(TWPolkadotCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypePolkadot)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xb96f97d8ee508f420e606e1a6dcc74b88844713ddec2bd7cf4e3aa6b1d6beef4")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypePolkadot, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("13hJFqnkqQbmgnGQteGntjMjTdmTBRE8Z93JqxsrpgT7Yjd2")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypePolkadot, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypePolkadot)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypePolkadot)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypePolkadot), 10); + ASSERT_EQ(TWBlockchainPolkadot, TWCoinTypeBlockchain(TWCoinTypePolkadot)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypePolkadot)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypePolkadot)); + assertStringsEqual(symbol, "DOT"); + assertStringsEqual(txUrl, "https://polkadot.subscan.io/extrinsic/0xb96f97d8ee508f420e606e1a6dcc74b88844713ddec2bd7cf4e3aa6b1d6beef4"); + assertStringsEqual(accUrl, "https://polkadot.subscan.io/account/13hJFqnkqQbmgnGQteGntjMjTdmTBRE8Z93JqxsrpgT7Yjd2"); + assertStringsEqual(id, "polkadot"); + assertStringsEqual(name, "Polkadot"); +} diff --git a/tests/chains/Polkadot/TransactionCompilerTests.cpp b/tests/chains/Polkadot/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..2476fe54ab8 --- /dev/null +++ b/tests/chains/Polkadot/TransactionCompilerTests.cpp @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Polkadot.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Polkadot::tests { + +TEST(PolkadotCompiler, CompileWithSignatures) { + /// Prepare transaction input (protobuf) + const auto coin = TWCoinTypePolkadot; + auto input = Polkadot::Proto::SigningInput(); + auto block_hash = parse_hex("40cee3c3b7f8422f4c512e9ebebdeeff1c28e81cc678ee4864d945d641e05f9b"); + auto genesis_hash = parse_hex("91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"); + input.set_block_hash(block_hash.data(), block_hash.size()); + input.set_genesis_hash(genesis_hash.data(), genesis_hash.size()); + input.set_nonce(0); + input.set_spec_version(25); + input.set_transaction_version(5); + input.set_network(ss58Prefix(TWCoinTypePolkadot)); + + auto era = input.mutable_era(); + era->set_block_number(5898150); + era->set_period(10000); + + auto call = input.mutable_balance_call(); + auto tx = call->mutable_transfer(); + auto value = parse_hex("210fdc0c00"); + tx->set_to_address("15JWiQUmczAFU3hrZrD2gDyuJdL2BbFaX9yngivb1UWiBJWA"); + tx->set_value(value.data(), value.size()); + + auto txInputData = data(input.SerializeAsString()); + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + auto preOutput = TxCompiler::Proto::PreSigningOutput(); + preOutput.ParseFromArray(preImageHashes.data(), (int)preImageHashes.size()); + auto preImageHash = preOutput.data_hash(); + + { + EXPECT_EQ(hex(preImageHash), "0500be4c21aa92dcba057e9b719ce1de970f774f064c09b13a3ea3009affb8cb5ec707000cdc0f219dfe0000190000000500000091b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c340cee3c3b7f8422f4c512e9ebebdeeff1c28e81cc678ee4864d945d641e05f9b"); + } + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = parse_hex("d84accbb64934815506288fafbfc7d275e64aa4e3cd9c5392db6e83b13256bf3"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeED25519); + const auto signature = parse_hex("fb43727477caaa12542b9060856816d42eedef6ebf2e98e4f8dff4355fe384751925833c4a26b2fed1707aebe655cb3317504a61ee59697c086f7baa6ca06a09"); + + // Verify signature (pubkey & hash & signature) + { + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + } + + const auto expectedTx = "390284d84accbb64934815506288fafbfc7d275e64aa4e3cd9c5392db6e83b13256bf300fb43727477caaa12542b9060856816d42eedef6ebf2e98e4f8dff4355fe384751925833c4a26b2fed1707aebe655cb3317504a61ee59697c086f7baa6ca06a099dfe00000500be4c21aa92dcba057e9b719ce1de970f774f064c09b13a3ea3009affb8cb5ec707000cdc0f21"; + { + /// Compile transaction info + const Data outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKeyData}); + auto output = Polkadot::Proto::SigningOutput(); + output.ParseFromArray(outputData.data(), (int)outputData.size()); + EXPECT_EQ(hex(output.encoded()), expectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, {publicKey.bytes}); + Polkadot::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Polkadot::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +} // namespace TW::Polkadot::tests diff --git a/tests/chains/Polygon/TWCoinTypeTests.cpp b/tests/chains/Polygon/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..e9360551de0 --- /dev/null +++ b/tests/chains/Polygon/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWPolygonCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypePolygon)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xe26ed1470d5bf99a53d687843e7acdf7e4ba6620af93b4d672e714de90476e8e")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypePolygon, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x720E1fa107A1Df39Db4E78A3633121ac36Bec132")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypePolygon, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypePolygon)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypePolygon)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypePolygon), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypePolygon)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypePolygon)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypePolygon)); + assertStringsEqual(symbol, "POL"); + assertStringsEqual(txUrl, "https://polygonscan.com/tx/0xe26ed1470d5bf99a53d687843e7acdf7e4ba6620af93b4d672e714de90476e8e"); + assertStringsEqual(accUrl, "https://polygonscan.com/address/0x720E1fa107A1Df39Db4E78A3633121ac36Bec132"); + assertStringsEqual(id, "polygon"); + assertStringsEqual(name, "Polygon"); +} diff --git a/tests/chains/PolygonZkEvm/TWCoinTypeTests.cpp b/tests/chains/PolygonZkEvm/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..3329a57d97d --- /dev/null +++ b/tests/chains/PolygonZkEvm/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +namespace TW::TWPolygonZkEvm::tests { + +TEST(TWPolygonZkEVMCoinType, TWCoinType) { + const auto coin = TWCoinTypePolygonzkEVM; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xc70fd1a45b3130f5515a27d96f01a7f508099fb0b8af52ef432d5e4b2373dccd")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x81d98c8fda0410ee3e9d7586cb949cd19fa4cf38")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "polygonzkevm"); + assertStringsEqual(name, "Polygon zkEVM"); + assertStringsEqual(symbol, "ETH"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + ASSERT_EQ(coin, 10001101ull); + assertStringsEqual(chainId, "1101"); + assertStringsEqual(txUrl, "https://zkevm.polygonscan.com/tx/0xc70fd1a45b3130f5515a27d96f01a7f508099fb0b8af52ef432d5e4b2373dccd"); + assertStringsEqual(accUrl, "https://zkevm.polygonscan.com/address/0x81d98c8fda0410ee3e9d7586cb949cd19fa4cf38"); +} + +} // namespace TW::TWZksync::tests diff --git a/tests/chains/Qtum/TWCoinTypeTests.cpp b/tests/chains/Qtum/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..f32c75e50b4 --- /dev/null +++ b/tests/chains/Qtum/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWQtumCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeQtum)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeQtum, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeQtum, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeQtum)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeQtum)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeQtum), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeQtum)); + ASSERT_EQ(0x32, TWCoinTypeP2shPrefix(TWCoinTypeQtum)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeQtum)); + assertStringsEqual(symbol, "QTUM"); + assertStringsEqual(txUrl, "https://qtum.info/tx/t123"); + assertStringsEqual(accUrl, "https://qtum.info/address/a12"); + assertStringsEqual(id, "qtum"); + assertStringsEqual(name, "Qtum"); +} diff --git a/tests/Qtum/TWQtumAddressTests.cpp b/tests/chains/Qtum/TWQtumAddressTests.cpp similarity index 95% rename from tests/Qtum/TWQtumAddressTests.cpp rename to tests/chains/Qtum/TWQtumAddressTests.cpp index 8d658cf096c..cf95e3bc12a 100644 --- a/tests/Qtum/TWQtumAddressTests.cpp +++ b/tests/chains/Qtum/TWQtumAddressTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/Ravencoin/TWCoinTypeTests.cpp b/tests/chains/Ravencoin/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..38304931caa --- /dev/null +++ b/tests/chains/Ravencoin/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWRavencoinCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeRavencoin)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeRavencoin, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeRavencoin, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeRavencoin)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeRavencoin)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeRavencoin), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeRavencoin)); + ASSERT_EQ(0x7a, TWCoinTypeP2shPrefix(TWCoinTypeRavencoin)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeRavencoin)); + assertStringsEqual(symbol, "RVN"); + assertStringsEqual(txUrl, "https://blockbook.ravencoin.org/tx/t123"); + assertStringsEqual(accUrl, "https://blockbook.ravencoin.org/address/a12"); + assertStringsEqual(id, "ravencoin"); + assertStringsEqual(name, "Ravencoin"); +} diff --git a/tests/Ravencoin/TWRavencoinTransactionTests.cpp b/tests/chains/Ravencoin/TWRavencoinTransactionTests.cpp similarity index 88% rename from tests/Ravencoin/TWRavencoinTransactionTests.cpp rename to tests/chains/Ravencoin/TWRavencoinTransactionTests.cpp index b9c220fdb0d..7c3c7da223e 100644 --- a/tests/Ravencoin/TWRavencoinTransactionTests.cpp +++ b/tests/chains/Ravencoin/TWRavencoinTransactionTests.cpp @@ -1,11 +1,8 @@ - -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "Bitcoin/OutPoint.h" #include "Bitcoin/TransactionBuilder.h" @@ -18,8 +15,7 @@ #include -using namespace TW; -using namespace TW::Bitcoin; +namespace TW::Bitcoin { TEST(RavencoinTransaction, SignTransaction) { /* @@ -73,22 +69,23 @@ TEST(RavencoinTransaction, SignTransaction) { signedTx.encode(serialized, Transaction::SegwitFormatMode::NonSegwit); ASSERT_EQ( hex(serialized), - "0100000001445560237d8093da3487eb90bc7ff826fab43cdbe213c034d671ec4eb4827e0c000000006b483045022100d790bdaa3c44eb5e3a422365ca5fc009c4512625222e3378f2f16e7e6ef1732a0220688c1bb995b7ff2f12729e101d7c24b6314430317e7717911fdc35c0d84f2f0d012102138724e702d25b0fdce73372ccea9734f9349442d5a9681a5f4d831036cd9429ffffffff0280f0fa02000000001976a9149451f4546e09fc2e49ef9b5303924712ec2b038e88ac006cdc02000000001976a9145d6e33f3a108bbcc586cbbe90994d5baf5a9cce488ac00000000" - ); + "0100000001445560237d8093da3487eb90bc7ff826fab43cdbe213c034d671ec4eb4827e0c000000006b483045022100d790bdaa3c44eb5e3a422365ca5fc009c4512625222e3378f2f16e7e6ef1732a0220688c1bb995b7ff2f12729e101d7c24b6314430317e7717911fdc35c0d84f2f0d012102138724e702d25b0fdce73372ccea9734f9349442d5a9681a5f4d831036cd9429ffffffff0280f0fa02000000001976a9149451f4546e09fc2e49ef9b5303924712ec2b038e88ac006cdc02000000001976a9145d6e33f3a108bbcc586cbbe90994d5baf5a9cce488ac00000000"); } TEST(RavencoinTransaction, LockScripts) { - // P2PKH + // P2PKH // https://blockbook.ravencoin.org/tx/3717b528eb4925461d9de5a596d2eefe175985740b4fda153255e10135f236a6 - + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("RNoSGCX8SPFscj8epDaJjqEpuZa2B5in88").get(), TWCoinTypeRavencoin)); auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); assertHexEqual(scriptData, "76a9149451f4546e09fc2e49ef9b5303924712ec2b038e88ac"); // P2SH - // https://ravencoin.network/api/tx/f600d07814677d1f60545c8f7f71260238595c4928d6fb87caa0f9dd732e9bb5 - + // https://blockbook.ravencoin.org/tx/f600d07814677d1f60545c8f7f71260238595c4928d6fb87caa0f9dd732e9bb5 + auto script2 = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("rPWwn5h4QFZNaz1XmY39rc73sdYGGDdmq1").get(), TWCoinTypeRavencoin)); auto scriptData2 = WRAPD(TWBitcoinScriptData(script2.get())); assertHexEqual(scriptData2, "a914bd92088bb7e82d611a9b94fbb74a0908152b784f87"); } + +} // namespace TW::Bitcoin diff --git a/tests/chains/Ronin/TWAnyAddressTests.cpp b/tests/chains/Ronin/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..b1d162c2974 --- /dev/null +++ b/tests/chains/Ronin/TWAnyAddressTests.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include +#include + +#include "Ronin/Entry.h" + +#include + +const auto roninPrefixChecksummed = "ronin:EC49280228b0D05Aa8e8b756503254e1eE7835ab"; + +const auto gTests = { + roninPrefixChecksummed, + "ronin:ec49280228b0d05aa8e8b756503254e1ee7835ab", + "0xEC49280228b0D05Aa8e8b756503254e1eE7835ab", + "ronin:0xEC49280228b0D05Aa8e8b756503254e1eE7835ab", +}; + +TEST(RoninAnyAddress, Validate) { + for (const auto& t: gTests) { + EXPECT_TRUE(TWAnyAddressIsValid(STRING(t).get(), TWCoinTypeRonin)); + } +} + +TEST(RoninAnyAddress, Normalize) { + for (const auto& t: gTests) { + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(STRING(t).get(), TWCoinTypeRonin)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string2.get(), STRING(roninPrefixChecksummed).get())); + + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "ec49280228b0d05aa8e8b756503254e1ee7835ab"); + } +} + +TEST(RoninAnyAddress, Invalid) { + const auto tests = { + "EC49280228b0D05Aa8e8b756503254e1eE7835ab", // no prefix + "ec49280228b0d05aa8e8b756503254e1ee7835ab", // no prefix + "roni:EC49280228b0D05Aa8e8b756503254e1eE7835ab", // wrong prefix + "ronin=EC49280228b0D05Aa8e8b756503254e1eE7835ab", // wrong prefix + "0xronin:EC49280228b0D05Aa8e8b756503254e1eE7835ab", // wrong prefix + "EC49280228b0D05Aa8e8b756503254e1eE7835", // too short + "ronin:EC49280228b0D05Aa8e8b756503254e1eE7835", // too short + "ronin:ec49280228b0d05aa8e8b756503254e1ee7835", // too short + }; + + for (const auto& t: tests) { + EXPECT_FALSE(TWAnyAddressIsValid(STRING(t).get(), TWCoinTypeRonin)); + } +} diff --git a/tests/chains/Ronin/TWAnySignerTests.cpp b/tests/chains/Ronin/TWAnySignerTests.cpp new file mode 100644 index 00000000000..7e73d988335 --- /dev/null +++ b/tests/chains/Ronin/TWAnySignerTests.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/Ethereum.pb.h" +#include "uint256.h" +#include "TestUtilities.h" +#include + +#include + +namespace TW::Ronin::tests { + +TEST(TWAnySignerRonin, Sign) { + // https://explorer.roninchain.com/tx/0xf13a2c4421700f8782ca73eaf16bb8baf82bcf093e23570a1ff062cdd8dbf6c3 + Ethereum::Proto::SigningInput input; + auto chainId = store(uint256_t(2020)); + auto nonce = store(uint256_t(0)); + auto gasPrice = store(uint256_t(1000000000)); + auto gasLimit = store(uint256_t(21000)); + auto key = parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_private_key(key.data(), key.size()); + input.set_to_address("ronin:c36edf48e21cf395b206352a1819de658fd7f988"); + + auto& transfer = *input.mutable_transaction()->mutable_transfer(); + auto amount = store(uint256_t(276447)); + transfer.set_amount(amount.data(), amount.size()); + + std::string expected = "f86880843b9aca0082520894c36edf48e21cf395b206352a1819de658fd7f988830437df80820feca0442aa06b0d0465bfecf84b28e2ce614a32a1ccc12735dc03a5799517d6659d7aa004e1bf2efa30743f1b6d49dbec2671e9fb5ead1e7da15e352ca1df6fb86a8ba7"; + + // sign test + Ethereum::Proto::SigningOutput output; + + ANY_SIGN(input, TWCoinTypeRonin); + + ASSERT_EQ(hex(output.encoded()), expected); + EXPECT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeRonin)); +} + +} // namespace TW::Ronin::tests diff --git a/tests/chains/Ronin/TWCoinTypeTests.cpp b/tests/chains/Ronin/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..3efdb99911f --- /dev/null +++ b/tests/chains/Ronin/TWCoinTypeTests.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWRoninCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeRonin)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xc09835aaf9c1cacea8ce322865583c791d3a4dfbd9a3b72f79539db88d3697ab")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeRonin, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xdc05ecd5fbdb64058d94f3182d66f44342b9adcb")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeRonin, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeRonin)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeRonin)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeRonin), 18); + ASSERT_EQ(TWBlockchainRonin, TWCoinTypeBlockchain(TWCoinTypeRonin)); + + assertStringsEqual(symbol, "RON"); + assertStringsEqual(txUrl, "https://explorer.roninchain.com/tx/0xc09835aaf9c1cacea8ce322865583c791d3a4dfbd9a3b72f79539db88d3697ab"); + assertStringsEqual(accUrl, "https://explorer.roninchain.com/address/0xdc05ecd5fbdb64058d94f3182d66f44342b9adcb"); + assertStringsEqual(id, "ronin"); + assertStringsEqual(name, "Ronin"); +} diff --git a/tests/chains/Rootstock/TWCoinTypeTests.cpp b/tests/chains/Rootstock/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..04795f50dae --- /dev/null +++ b/tests/chains/Rootstock/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWRootstockCoinType, TWCoinType) { + const auto coin = TWCoinTypeRootstock; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xeb8fa0488a655f8dc975153bffd066800bcaae5f21cf372356365b2a1d6d2288")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x4e5dabc28e4a0f5e5b19fcb56b28c5a1989352c1")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "rootstock"); + assertStringsEqual(name, "Rootstock"); + assertStringsEqual(symbol, "RBTC"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "30"); + assertStringsEqual(txUrl, "https://explorer.rsk.co/tx/0xeb8fa0488a655f8dc975153bffd066800bcaae5f21cf372356365b2a1d6d2288"); + assertStringsEqual(accUrl, "https://explorer.rsk.co/address/0x4e5dabc28e4a0f5e5b19fcb56b28c5a1989352c1"); +} diff --git a/tests/chains/Scroll/TWAnySignerTests.cpp b/tests/chains/Scroll/TWAnySignerTests.cpp new file mode 100644 index 00000000000..b719ff58bf9 --- /dev/null +++ b/tests/chains/Scroll/TWAnySignerTests.cpp @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/Ethereum.pb.h" +#include "uint256.h" +#include "TestUtilities.h" +#include + +#include + +namespace TW::Scroll::tests { + +/// Successfully broadcasted: +/// https://blockscout.scroll.io/tx/0x5a7ba291e0490079bddda54ca5592e5990d6b0eb49f8d239202941e3f63d32bc +TEST(TWAnySignerScroll, Sign) { + Ethereum::Proto::SigningInput input; + auto chainId = store(uint256_t(534352)); + auto nonce = store(uint256_t(1)); + auto gasPrice = store(uint256_t(1000000)); + auto gasLimit = store(uint256_t(200000)); + auto key = parse_hex("ba4c9bceece0677d2c92be11c2338652e34b10675dc4cec5546a20a314fe7a73"); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_private_key(key.data(), key.size()); + input.set_to_address("0xa6BC5EE0B1e904DD0773c5555D2F6833fE937A68"); + + auto& transfer = *input.mutable_transaction()->mutable_transfer(); + auto amount = store(uint256_t(200000000000000)); + transfer.set_amount(amount.data(), amount.size()); + + std::string expected = "f86c01830f424083030d4094a6bc5ee0b1e904dd0773c5555d2f6833fe937a6886b5e620f480008083104ec3a0c43ee3d34f7758e05e2f54df227eb7780ad97d06e91e03ef6a3c91d4bea6e42fa07d075f20776f7f485faca6f057110fd2745a5cdd6cf121682ef7791619a03ade"; + + // sign test + Ethereum::Proto::SigningOutput output; + + ANY_SIGN(input, TWCoinTypeScroll); + + ASSERT_EQ(hex(output.encoded()), expected); + EXPECT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeScroll)); +} + +} // namespace TW::Scroll::tests diff --git a/tests/chains/Scroll/TWCoinTypeTests.cpp b/tests/chains/Scroll/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..afe6e3c63b0 --- /dev/null +++ b/tests/chains/Scroll/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWScrollCoinType, TWCoinType) { + const auto coin = TWCoinTypeScroll; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xa2062a4530b194a438bb9f9e87cdce59f70775a52e8336892364445847c43ca2")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xf9062b8a30e0d7722960e305049fa50b86ba6253")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "scroll"); + assertStringsEqual(name, "Scroll"); + assertStringsEqual(symbol, "ETH"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "534352"); + assertStringsEqual(txUrl, "https://scrollscan.com/tx/0xa2062a4530b194a438bb9f9e87cdce59f70775a52e8336892364445847c43ca2"); + assertStringsEqual(accUrl, "https://scrollscan.com/address/0xf9062b8a30e0d7722960e305049fa50b86ba6253"); +} diff --git a/tests/chains/SmartBitcoinCash/TWCoinTypeTests.cpp b/tests/chains/SmartBitcoinCash/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..30c048093bc --- /dev/null +++ b/tests/chains/SmartBitcoinCash/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWSmartBitcoinCashCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeSmartBitcoinCash)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x6413466b455b17d03c7a8ce2d7f99fec34bcd338628bdd2d0580a21e3197a4d9")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeSmartBitcoinCash, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xFeEc227410E1DF9f3b4e6e2E284DC83051ae468F")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeSmartBitcoinCash, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeSmartBitcoinCash)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeSmartBitcoinCash)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeSmartBitcoinCash), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeSmartBitcoinCash)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeSmartBitcoinCash)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeSmartBitcoinCash)); + assertStringsEqual(symbol, "BCH"); + assertStringsEqual(txUrl, "https://www.smartscan.cash/tx/0x6413466b455b17d03c7a8ce2d7f99fec34bcd338628bdd2d0580a21e3197a4d9"); + assertStringsEqual(accUrl, "https://www.smartscan.cash/address/0xFeEc227410E1DF9f3b4e6e2E284DC83051ae468F"); + assertStringsEqual(id, "smartbch"); + assertStringsEqual(name, "Smart Bitcoin Cash"); +} diff --git a/tests/chains/Solana/AddressTests.cpp b/tests/chains/Solana/AddressTests.cpp new file mode 100644 index 00000000000..6e040826979 --- /dev/null +++ b/tests/chains/Solana/AddressTests.cpp @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base58.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "Solana/Address.h" + +#include + +using namespace std; +using namespace TW; + +namespace TW::Solana::tests { + +TEST(SolanaAddress, FromPublicKey) { + { + const auto addressString = "2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"; + const auto publicKey = PublicKey(Base58::decode(addressString), TWPublicKeyTypeED25519); + const auto address = Address(publicKey); + ASSERT_EQ(addressString, address.string()); + } + { + const auto privateKey = PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")); + const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); + EXPECT_ANY_THROW(new Address(publicKey)); + } +} + +TEST(SolanaAddress, FromData) { + { + const auto address = Address(parse_hex("18f9d8d877393bbbe8d697a8a2e52879cc7e84f467656d1cce6bab5a8d2637ec")); + ASSERT_EQ("2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST", address.string()); + } + { + EXPECT_ANY_THROW(new Address(Data{})); + } +} + +TEST(SolanaAddress, FromString) { + string addressString = "2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"; + const auto address = Address(addressString); + ASSERT_EQ(address.string(), addressString); + + EXPECT_ANY_THROW(new Address("4h4bzCLCV8")); +} + +TEST(SolanaAddress, isValid) { + ASSERT_TRUE(Address::isValid("2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST")); + ASSERT_FALSE(Address::isValid( + "2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdSl")); // Contains invalid base-58 character + ASSERT_FALSE( + Address::isValid("2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpd")); // Is invalid length +} + +TEST(SolanaAddress, isValidOnCurve) { + EXPECT_TRUE(PublicKey(Base58::decode("HzqnaMjWFbK2io6WgV2Z5uBguCBU21RMUS16wsDUHkon"), TWPublicKeyTypeED25519).isValidED25519()); + EXPECT_TRUE(PublicKey(Base58::decode("68io7dTfyeWua1wD1YcCMka4y5iiChceaFRCBjqCM5PK"), TWPublicKeyTypeED25519).isValidED25519()); + EXPECT_TRUE(PublicKey(Base58::decode("Dra34QLFCjxnk8tUNcBwxs6pgb5spF4oseQYF2xn7ABZ"), TWPublicKeyTypeED25519).isValidED25519()); + // negative case + EXPECT_FALSE(PublicKey(Base58::decode("6X4X1Ae24mkoWeCEpktevySVG9jzeCufut5vtUW3wFrD"), TWPublicKeyTypeED25519).isValidED25519()); + EXPECT_FALSE(PublicKey(Base58::decode("EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"), TWPublicKeyTypeED25519).isValidED25519()); + EXPECT_FALSE(PublicKey(Base58::decode("ANVCrmRw7Ww7rTFfMbrjApSPXEEcZpBa6YEiBdf98pAf"), TWPublicKeyTypeED25519).isValidED25519()); + EXPECT_FALSE(PublicKey(Base58::decode("AbygL37RheNZv327cMvZPqKYLLkZ6wqWYexRxgNiZyeP"), TWPublicKeyTypeED25519).isValidED25519()); +} + +} // namespace TW::Solana::tests diff --git a/tests/chains/Solana/TWAnySignerTests.cpp b/tests/chains/Solana/TWAnySignerTests.cpp new file mode 100644 index 00000000000..8537199f0e6 --- /dev/null +++ b/tests/chains/Solana/TWAnySignerTests.cpp @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base58.h" +#include "PrivateKey.h" +#include "proto/Solana.pb.h" +#include "TestUtilities.h" +#include + +#include + +using namespace TW; +namespace TW::Solana::tests { + +const auto expectedString1 = + "3p2kzZ1DvquqC6LApPuxpTg5CCDVPqJFokGSnGhnBHrta4uq7S2EyehV1XNUVXp51D69GxGzQZU" + "jikfDzbWBG2aFtG3gHT1QfLzyFKHM4HQtMQMNXqay1NAeiiYZjNhx9UvMX4uAQZ4Q6rx6m2AYfQ" + "7aoMUrejq298q1wBFdtS9XVB5QTiStnzC7zs97FUEK2T4XapjF1519EyFBViTfHpGpnf5bfizDz" + "sW9kYUtRDW1UC2LgHr7npgq5W9TBmHf9hSmRgM9XXucjXLqubNWE7HUMhbKjuBqkirRM"; + +TEST(TWAnySignerSolana, SignTransfer) { + auto privateKey = Base58::decode("A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr"); + auto input = Proto::SigningInput(); + + auto& message = *input.mutable_transfer_transaction(); + message.set_recipient("EN2sCsJ1WDV8UFqsiTXHcUPUxQ4juE71eCknHYYMifkd"); + message.set_value((uint64_t)42L); + input.set_private_key(privateKey.data(), privateKey.size()); + input.set_recent_blockhash("11111111111111111111111111111111"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeSolana); + + ASSERT_EQ(output.encoded(), expectedString1); + ASSERT_EQ(output.unsigned_tx(), "87PYsiS4MUU1UqXrsDoCBmD5FcKsXhwEBD8hc4zbq78yePu7bLENmbnmjmVbsj4VvaxnZhy4bERndPFzjSRH5WpwKwMLSCKvn9eSDmPESNcdkqne2UdMfWiFoq8ZeQBnF9h98dP8GM9kfzWPjvLmhjwuwA1E2k5WCtfii7LKQ34v6AtmFQGZqgdKiNqygP7ZKusHWGT8ZkTZ"); +} + +} // namespace TW::Solana::tests diff --git a/tests/chains/Solana/TWCoinTypeTests.cpp b/tests/chains/Solana/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..4fe7feb42af --- /dev/null +++ b/tests/chains/Solana/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWSolanaCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeSolana)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("5LmxrEKGchhMuYfw6Qut6CbsvE9pVfb8YvwZKvWssSesDVjHioBCmWKSJQh1WhvcM6CpemhpHNmEMA2a36rzwTa8")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeSolana, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("Bxp8yhH9zNwxyE4UqxP7a7hgJ5xTZfxNNft7YJJ2VRjT")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeSolana, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeSolana)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeSolana)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeSolana), 9); + ASSERT_EQ(TWBlockchainSolana, TWCoinTypeBlockchain(TWCoinTypeSolana)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeSolana)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeSolana)); + assertStringsEqual(symbol, "SOL"); + assertStringsEqual(txUrl, "https://solscan.io/tx/5LmxrEKGchhMuYfw6Qut6CbsvE9pVfb8YvwZKvWssSesDVjHioBCmWKSJQh1WhvcM6CpemhpHNmEMA2a36rzwTa8"); + assertStringsEqual(accUrl, "https://solscan.io/account/Bxp8yhH9zNwxyE4UqxP7a7hgJ5xTZfxNNft7YJJ2VRjT"); + assertStringsEqual(id, "solana"); + assertStringsEqual(name, "Solana"); +} diff --git a/tests/chains/Solana/TWSolanaAddressTests.cpp b/tests/chains/Solana/TWSolanaAddressTests.cpp new file mode 100644 index 00000000000..5607877186b --- /dev/null +++ b/tests/chains/Solana/TWSolanaAddressTests.cpp @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include +#include +#include + +#include + +TEST(TWSolanaAddress, HDWallet) { + auto mnemonic = + "shoot island position soft burden budget tooth cruel issue economy destroy above"; + auto passphrase = ""; + + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(STRING(mnemonic).get(), STRING(passphrase).get())); + + auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypeSolana, WRAPS(TWCoinTypeDerivationPath(TWCoinTypeSolana)).get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey.get())); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeSolana)); + auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + + assertStringsEqual(addressStr, "2bUBiBNZyD29gP1oV6de7nxowMLoDBtopMMTGgMvjG5m"); +} + +TEST(TWSolanaProgram, defaultTokenAddress) { + const auto solAddress = STRING("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + const auto serumToken = STRING("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); + + auto solanaAddress = WRAP(TWSolanaAddress, TWSolanaAddressCreateWithString(solAddress.get())); + auto description = WRAPS(TWSolanaAddressDescription(solanaAddress.get())); + auto tokenAddress = WRAPS(TWSolanaAddressDefaultTokenAddress(solanaAddress.get(), serumToken.get())); + + assertStringsEqual(tokenAddress, "EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"); + assertStringsEqual(description, "B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); +} + +TEST(TWSolanaProgram, defaultTokenAddressError) { + const auto solAddress = STRING("B1iGmDJdvmxyUiYM8UEo2Uw2D58EmUrw4KyLYMmrhf8V"); + // Invalid token mint address. + const auto serumToken = STRING("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKW"); + + auto solanaAddress = WRAP(TWSolanaAddress, TWSolanaAddressCreateWithString(solAddress.get())); + auto description = WRAPS(TWSolanaAddressDescription(solanaAddress.get())); + + EXPECT_EQ(TWSolanaAddressDefaultTokenAddress(solanaAddress.get(), serumToken.get()), nullptr); +} + +TEST(TWSolanaProgram, token2022Address) { + const auto solAddress = STRING("68dzdXkni9BrAwU1asAwurMEdQhXUJq6MNY8niDAny8t"); + const auto catwifhatToken = STRING("7atgF8KQo4wJrD5ATGX7t1V2zVvykPJbFfNeVf1icFv1"); + + auto solanaAddress = WRAP(TWSolanaAddress, TWSolanaAddressCreateWithString(solAddress.get())); + auto description = WRAPS(TWSolanaAddressDescription(solanaAddress.get())); + auto tokenAddress = WRAPS(TWSolanaAddressToken2022Address(solanaAddress.get(), catwifhatToken.get())); + + assertStringsEqual(tokenAddress, "3PaFQnebQMHBgthRScup2B932cMxA1GBP7m9roCkomHq"); + assertStringsEqual(description, "68dzdXkni9BrAwU1asAwurMEdQhXUJq6MNY8niDAny8t"); +} + +TEST(TWSolanaProgram, token2022AddressError) { + const auto solAddress = STRING("68dzdXkni9BrAwU1asAwurMEdQhXUJq6MNY8niDAny8t"); + // Invalid token mint address. + const auto catwifhatToken = STRING("7atgF8KQo4wJrD5ATGX7t1V2zVvykPJbFfNeVf1icF"); + + auto solanaAddress = WRAP(TWSolanaAddress, TWSolanaAddressCreateWithString(solAddress.get())); + auto description = WRAPS(TWSolanaAddressDescription(solanaAddress.get())); + + EXPECT_EQ(TWSolanaAddressToken2022Address(solanaAddress.get(), catwifhatToken.get()), nullptr); +} diff --git a/tests/chains/Solana/TWSolanaTransaction.cpp b/tests/chains/Solana/TWSolanaTransaction.cpp new file mode 100644 index 00000000000..6850ce721c5 --- /dev/null +++ b/tests/chains/Solana/TWSolanaTransaction.cpp @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TrustWalletCore/TWSolanaTransaction.h" +#include "TrustWalletCore/TWTransactionDecoder.h" +#include "TrustWalletCore/TWAnySigner.h" +#include "proto/Solana.pb.h" +#include "TestUtilities.h" +#include "Base64.h" +#include "Base58.h" + +#include + +using namespace TW; +namespace TW::Solana::tests { + +TEST(TWSolanaTransaction, UpdateBlockhashAndSign) { + // base64 encoded + // https://explorer.solana.com/tx/3KbvREZUat76wgWMtnJfWbJL74Vzh4U2eabVJa3Z3bb2fPtW8AREP5pbmRwUrxZCESbTomWpL41PeKDcPGbojsej?cluster=devnet + auto encodedTx = STRING("AnQTYwZpkm3fs4SdLxnV6gQj3hSLsyacpxDdLMALYWObm722f79IfYFTbZeFK9xHtMumiDOWAM2hHQP4r/GtbARpncaXgOVFv7OgbRLMbuCEJHO1qwcdCbtH72VzyzU8yw9sqqHIAaCUE8xaQTgT6Z5IyZfeyMe2QGJIfOjz65UPAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8Aqe6sdLXiXSDILEtzckCjkjchiSf6zVGpMYiAE5BE2IqHAQUEAgQDAQoMoA8AAAAAAAAG"); + // base58 encoded + auto newBlockhash = STRING("CyPYVsYWrsJNfVpi8aazu7WsrswNFuDd385z6GNoBGUg"); + + auto myPrivateKey = DATA("7f0932159226ddec9e1a4b0b8fe7cdc135049f9e549a867d722aa720dd64f32e"); + auto feePayerPrivateKey = DATA("4b9d6f57d28b06cbfa1d4cc710953e62d653caf853415c56ffd9d150acdeb7f7"); + auto privateKeysVec = WRAP(TWDataVector, TWDataVectorCreateWithData(myPrivateKey.get())); + TWDataVectorAdd(privateKeysVec.get(), feePayerPrivateKey.get()); + + auto outputData = WRAPD(TWSolanaTransactionUpdateBlockhashAndSign(encodedTx.get(), newBlockhash.get(), privateKeysVec.get())); + + Proto::SigningOutput output; + output.ParseFromArray(TWDataBytes(outputData.get()), static_cast(TWDataSize(outputData.get()))); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + EXPECT_EQ(output.encoded(), "Ajzc/Tke0CG8Cew5qFa6xZI/7Ya3DN0M8Ige6tKPsGzhg8Bw9DqL18KUrEZZ1F4YqZBo4Rv+FsDT8A7Nss7p4A6BNVZzzGprCJqYQeNg0EVIbmPc6mDitNniHXGeKgPZ6QZbM4FElw9O7IOFTpOBPvQFeqy0vZf/aayncL8EK/UEAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqbHiki6ThNH3auuyZPQpJntnN0mA//56nMpK/6HIuu8xAQUEAgQDAQoMoA8AAAAAAAAG"); +} + +TEST(TWSolanaTransaction, DecodeUpdateBlockhashAndSign) { + // base64 encoded + // https://explorer.solana.com/tx/3KbvREZUat76wgWMtnJfWbJL74Vzh4U2eabVJa3Z3bb2fPtW8AREP5pbmRwUrxZCESbTomWpL41PeKDcPGbojsej?cluster=devnet + auto encodedTx = Base64::decode("AnQTYwZpkm3fs4SdLxnV6gQj3hSLsyacpxDdLMALYWObm722f79IfYFTbZeFK9xHtMumiDOWAM2hHQP4r/GtbARpncaXgOVFv7OgbRLMbuCEJHO1qwcdCbtH72VzyzU8yw9sqqHIAaCUE8xaQTgT6Z5IyZfeyMe2QGJIfOjz65UPAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8Aqe6sdLXiXSDILEtzckCjkjchiSf6zVGpMYiAE5BE2IqHAQUEAgQDAQoMoA8AAAAAAAAG"); + auto newBlockhash = "CyPYVsYWrsJNfVpi8aazu7WsrswNFuDd385z6GNoBGUg"; + auto senderPrivateKey = Base58::decode("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"); + auto feePayerPrivateKey = Base58::decode("66ApBuKpo2uSzpjGBraHq7HP8UZMUJzp3um8FdEjkC9c"); + + // Step 1: Decode the transaction. + + auto encodedTxPtr = WRAPD(TWDataCreateWithBytes(encodedTx.data(), encodedTx.size())); + auto decodeOutputPtr = WRAPD(TWTransactionDecoderDecode(TWCoinTypeSolana, encodedTxPtr.get())); + + Proto::DecodingTransactionOutput decodeOutput; + decodeOutput.ParseFromArray( + TWDataBytes(decodeOutputPtr.get()), + static_cast(TWDataSize(decodeOutputPtr.get())) + ); + + EXPECT_EQ(decodeOutput.error(), Common::Proto::SigningError::OK); + ASSERT_TRUE(decodeOutput.transaction().has_legacy()); + // Previous fee payer signature. + EXPECT_EQ( + decodeOutput.transaction().signatures(0).signature(), + "3KbvREZUat76wgWMtnJfWbJL74Vzh4U2eabVJa3Z3bb2fPtW8AREP5pbmRwUrxZCESbTomWpL41PeKDcPGbojsej" + ); + // Previous sender signature. + EXPECT_EQ( + decodeOutput.transaction().signatures(1).signature(), + "37UT8U6DdqaWqV6yQuHcRN3JpMefDgVna8LJJPmmDNKYMwmefEgvcreSg9AqSsT7cU2rMBNyDktEtDU19ZqqZvML" + ); + + // Step 2. Prepare a signing input and update the recent-blockhash. + + Proto::SigningInput signingInput; + *signingInput.mutable_raw_message() = decodeOutput.transaction(); + signingInput.mutable_raw_message()->mutable_legacy()->set_recent_blockhash(newBlockhash); + signingInput.set_private_key(senderPrivateKey.data(), senderPrivateKey.size()); + signingInput.set_fee_payer_private_key(feePayerPrivateKey.data(), feePayerPrivateKey.size()); + signingInput.set_tx_encoding(Proto::Encoding::Base64); + + // Step 3. Re-sign the updated transaction. + + Proto::SigningOutput output; + ANY_SIGN(signingInput, TWCoinTypeSolana); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + EXPECT_EQ(output.encoded(), "Ajzc/Tke0CG8Cew5qFa6xZI/7Ya3DN0M8Ige6tKPsGzhg8Bw9DqL18KUrEZZ1F4YqZBo4Rv+FsDT8A7Nss7p4A6BNVZzzGprCJqYQeNg0EVIbmPc6mDitNniHXGeKgPZ6QZbM4FElw9O7IOFTpOBPvQFeqy0vZf/aayncL8EK/UEAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqbHiki6ThNH3auuyZPQpJntnN0mA//56nMpK/6HIuu8xAQUEAgQDAQoMoA8AAAAAAAAG"); +} + +} // TW::Solana::tests diff --git a/tests/chains/Solana/TWWalletConnectSolana.cpp b/tests/chains/Solana/TWWalletConnectSolana.cpp new file mode 100644 index 00000000000..f8c23d0c80e --- /dev/null +++ b/tests/chains/Solana/TWWalletConnectSolana.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base58.h" +#include "proto/Solana.pb.h" +#include "proto/WalletConnect.pb.h" +#include "Coin.h" +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Solana { + +TEST(TWWalletConnectSolana, Transfer) { + auto private_key = Base58::decode("A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr"); + const auto payload = R"({"transaction":"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDZsL1CMnFVcrMn7JtiOiN1U4hC7WovOVof2DX51xM0H/GizyJTHgrBanCf8bGbrFNTn0x3pCGq30hKbywSTr6AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgIAAQwCAAAAKgAAAAAAAAA="})"; + + WalletConnect::Proto::ParseRequestInput parsingInput; + parsingInput.set_method(WalletConnect::Proto::Method::SolanaSignTransaction); + parsingInput.set_payload(payload); + + const auto parsinginputData = parsingInput.SerializeAsString(); + const auto parsingInputDataPtr = WRAPD(TWDataCreateWithBytes(reinterpret_cast(parsinginputData.c_str()), parsinginputData.size())); + + const auto outputDataPtr = WRAPD(TWWalletConnectRequestParse(TWCoinTypeSolana, parsingInputDataPtr.get())); + + WalletConnect::Proto::ParseRequestOutput parsingOutput; + parsingOutput.ParseFromArray( + TWDataBytes(outputDataPtr.get()), + static_cast(TWDataSize(outputDataPtr.get())) + ); + + EXPECT_EQ(parsingOutput.error(), Common::Proto::SigningError::OK); + + // Step 2: Set missing fields. + ASSERT_TRUE(parsingOutput.has_solana()); + Proto::SigningInput signingInput = parsingOutput.solana(); + + signingInput.set_private_key(private_key.data(), private_key.size()); + signingInput.set_tx_encoding(Proto::Encoding::Base64); + + Proto::SigningOutput output; + ANY_SIGN(signingInput, TWCoinTypeSolana); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + EXPECT_EQ(output.encoded(), "AQPWaOi7dMdmQpXi8HyQQKwiqIftrg1igGQxGtZeT50ksn4wAnyH4DtDrkkuE0fqgx80LTp4LwNN9a440SrmoA8BAAEDZsL1CMnFVcrMn7JtiOiN1U4hC7WovOVof2DX51xM0H/GizyJTHgrBanCf8bGbrFNTn0x3pCGq30hKbywSTr6AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgIAAQwCAAAAKgAAAAAAAAA="); +} + +} // namespace TW::Binance diff --git a/tests/chains/Solana/TransactionCompilerTests.cpp b/tests/chains/Solana/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..7130966e3b8 --- /dev/null +++ b/tests/chains/Solana/TransactionCompilerTests.cpp @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base58.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Solana.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Solana::tests { + +TEST(SolanaCompiler, CompileTransferWithSignatures) { + const auto coin = TWCoinTypeSolana; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::Solana::Proto::SigningInput(); + auto& message = *input.mutable_transfer_transaction(); + auto recipient = std::string("3UVYmECPPMZSCqWKfENfuoTv51fTDTWicX9xmBD2euKe"); + auto sender = std::string("sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH"); + input.set_sender(sender); + input.set_recent_blockhash(std::string("TPJFTN4CjBn12HiBfAbGUhpD9zGvRSm2RcheFRA4Fyv")); + message.set_recipient(recipient); + message.set_value((uint64_t)1000); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::Solana::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + ASSERT_EQ(preSigningOutput.signers_size(), 1); + auto signer = preSigningOutput.signers(0); + EXPECT_EQ(signer, sender); + auto preImageHash = preSigningOutput.data(); + EXPECT_EQ(hex(preImageHash), + "010001030d044a62d0a4dfe5a037a15b59fa4d4d0d3ab81103a2c10a6da08a4d058611c024c255a8bc3e" + "8496217a2cd2a1894b9b9dcace04fcd9c0d599acdaaea40a1b6100000000000000000000000000000000" + "0000000000000000000000000000000006c25012cc11a599a45b3b2f7f8a7c65b0547fa0bb67170d7a0c" + "d1eda4e2c9e501020200010c02000000e803000000000000"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = + parse_hex("0d044a62d0a4dfe5a037a15b59fa4d4d0d3ab81103a2c10a6da08a4d058611c0"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeED25519); + const auto signature = + parse_hex("a8c610697087eaf8a34b3facbe06f8e9bb9603bb03270dad021ffcd2fc37b6e9efcdcb78b227401f" + "000eb9231c67685240890962e44a17fd27fc2ff7b971df03"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + const auto ExpectedTx = + "5bWxVCP5fuzkKSGby9hnsLranszQJR2evJGTfBrpDQ4rJceW1WxKNrWqVPBsN2QCAGmE6W7VaYkyWjv39HhGrr1Ne2" + "QSUuHZdyyn7hK4pxzLPMgPG8fY1XvXdppWMaKMLmhriLkckzGKJMaE3pWBRFBKzigXY28714uUNndb7S9hVakxa59h" + "rLph39CMgAkcj6b8KYvJEkb1YdYytHSZNGi4kVVTNqiicNgPdf1gmG6qz9zVtnqj9JtaD2efdS8qxsKnvNWSgb8Xxb" + "T6dwyp7msUUi7d27cYaPTpK"; + { + Solana::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded(), ExpectedTx); + EXPECT_EQ(output.encoded().size(), 293ul); + } + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Solana::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + auto key = parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1"); + signingInput.set_private_key(key.data(), key.size()); + + Solana::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } +} + +TEST(SolanaCompiler, CompileTransferWithPriorityFee) { + // tx on mainnet + // https://explorer.solana.com/tx/5asW13PSGvbZAeiGe8YFo7jt3UTqb8KUfFhXh5DXpDVfpVup1ZP41tp7PmBJH43gK5xT9U4VDVChDynmC7PJp9fa + + const auto coin = TWCoinTypeSolana; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::Solana::Proto::SigningInput(); + auto& message = *input.mutable_transfer_transaction(); + auto recipient = std::string("6NdFCrugyZVRhFbHvJT3dFBrGE9ZYFbfc8dBS2q4d2a9"); + auto sender = std::string("EQ37VYUVcqUSzYgvaguDB4yVRg5m3Xg7qQoKF8zFiJxe"); + input.set_sender(sender); + input.set_recent_blockhash(std::string("8oFmGuWUpsy8h8WP8AXTp3yhcLt4gQJRG5GxNcK7jfhX")); + input.set_nonce_account("ubKTCz9avQt3twiC9TbRjfoCiJnggH1abjgj9FjZJJm"); + input.mutable_priority_fee_price()->set_price(40000); + input.mutable_priority_fee_limit()->set_limit(480000); + message.set_recipient(recipient); + message.set_value((uint64_t)10000); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::Solana::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + ASSERT_EQ(preSigningOutput.signers_size(), 1); + auto signer = preSigningOutput.signers(0); + EXPECT_EQ(signer, sender); + auto preImageHash = preSigningOutput.data(); + EXPECT_EQ(hex(preImageHash), "01000306c70ead37125b5e142838eb59a6883ef915474f9b0a52494f698a4d23f4827ca70d79017647148829ddee52e687a840b42ce55a808367ff3cb0dc67444f5361ca4fd49d2664e7ac3016160e0b943a9222a21bee6510248dc2ae341f58195a2a3606a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000000000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000073db37fafe5d370e4605395dfc4661d886170c751ecfd7d76a744ea43c9f16f6040403010300040400000005000903409c0000000000000500050200530700040200020c020000001027000000000000"); + + // Simulate signature, normally obtained from signature server + const Data publicKeyData = parse_hex("c70ead37125b5e142838eb59a6883ef915474f9b0a52494f698a4d23f4827ca7"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeED25519); + const auto signature = parse_hex("e546dbc2b896ff53abe5d3f090abdea84fb3862c6dcab4a6878e4d0dc803a53a2b077ef14014737e049815ff4df5daa92dc3e11b55770466d5feab6bdfccf005"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + auto outputData = TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + const auto ExpectedTx = "84RnGAbza5DstiCPgBwtyuGnB2TPYsRGFr4L9tvzc71tBjyPS5aK9xGfMPdKA16gm8dJSxdAwMQX22zF3bQAHtgNHSApaL9Hs2B4Rz1HjazPmbNYLJKkSZ4gq2MWbY6DSKkg3NUf4L9HpZVFbrUw7TNgFYbEYiA1wTJ4aVwVAh9NQLDaQBgANnMvjFTYy2rDjgHL8nZU6omjK2uvDaLdqp2L4hXjGTKQ1mMFVRLXnrMUX8dijBPty3NDcgJ3G442ccGguez7DwYooirYuZ5ajiJwkKtqu8tW4namRdvAC7YmL1tCqZpWXyDBhqMapoyf1bVCvUbnuz64RZwhd7nj6DyULtiMXCUXFayeShe2nvJGTkzWZxEEeHPyvLtTmSNrmRWqE2ZEDCFGH3bopBKG2QJVeEomh7rKFSZ6WTDmG2V7L6zPFexAhuen9ynEBu8JJWRM3nx5dj4BSDeQf"; + { + Solana::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.encoded(), ExpectedTx); + } +} + +} // namespace TW::Solana::tests diff --git a/tests/chains/StarkEx/MessageSignerTests.cpp b/tests/chains/StarkEx/MessageSignerTests.cpp new file mode 100644 index 00000000000..d8140ecead3 --- /dev/null +++ b/tests/chains/StarkEx/MessageSignerTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include +#include +#include "TestUtilities.h" +#include + +namespace TW::StarkEx::tests { + +TEST(StarkExMessageSigner, SignAndVerify) { + PrivateKey starkPrivKey(parse_hex("04be51a04e718c202e4dca60c2b72958252024cfc1070c090dd0f170298249de", true)); + auto starkPubKey = starkPrivKey.getPublicKey(TWPublicKeyTypeStarkex); + auto starkMsg = "463a2240432264a3aa71a5713f2a4e4c1b9e12bbb56083cd56af6d878217cf"; + auto starkSignature = StarkEx::MessageSigner::signMessage(starkPrivKey, starkMsg); + ASSERT_EQ(starkSignature, "04cf5f21333dd189ada3c0f2a51430d733501a9b1d5e07905273c1938cfb261e05b6013d74adde403e8953743a338c8d414bb96bf69d2ca1a91a85ed2700a528"); + ASSERT_TRUE(StarkEx::MessageSigner::verifyMessage(starkPubKey, starkMsg, starkSignature)); +} + +TEST(TWStarkExMessageSigner, SignAndVerify) { + const auto privKeyData = "04be51a04e718c202e4dca60c2b72958252024cfc1070c090dd0f170298249de"; + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto message = STRING("463a2240432264a3aa71a5713f2a4e4c1b9e12bbb56083cd56af6d878217cf"); + + const auto pubKey = TWPrivateKeyGetPublicKeyByType(privateKey.get(), TWPublicKeyTypeStarkex); + const auto signature = WRAPS(TWStarkExMessageSignerSignMessage(privateKey.get(), message.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature.get())), "04cf5f21333dd189ada3c0f2a51430d733501a9b1d5e07905273c1938cfb261e05b6013d74adde403e8953743a338c8d414bb96bf69d2ca1a91a85ed2700a528"); + EXPECT_TRUE(TWStarkExMessageSignerVerifyMessage(pubKey, message.get(), signature.get())); + delete pubKey; +} + +} diff --git a/tests/chains/Stellar/AddressTests.cpp b/tests/chains/Stellar/AddressTests.cpp new file mode 100644 index 00000000000..b4c84772273 --- /dev/null +++ b/tests/chains/Stellar/AddressTests.cpp @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bitcoin/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "Stellar/Address.h" + +#include + +using namespace std; +using namespace TW; + +namespace TW::Stellar::tests { + +TEST(StellarAddress, FromPublicKey) { + const auto publicKey = PublicKey(parse_hex("0103E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D"), TWPublicKeyTypeED25519); + const auto address = Address(publicKey); + auto str = hex(address.bytes); + ASSERT_EQ(string("GAB6EDWGWSRZUYUYCWXAFQFBHE5ZEJPDXCIMVZC3LH2C7IU35FTI2NOQ"), address.string()); + + const auto privateKey = PrivateKey(parse_hex("94d1a980d5e528067d44bf8a60d646f556e40ca71e17cd4ead2d56f89e4bd20f")); + const auto publicKey2 = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); + EXPECT_ANY_THROW(new Address(publicKey2)); +} + +TEST(StellarAddress, FromString) { + string stellarAddress = "GAB6EDWGWSRZUYUYCWXAFQFBHE5ZEJPDXCIMVZC3LH2C7IU35FTI2NOQ"; + const auto address = Address(stellarAddress); + ASSERT_EQ(address.string(), stellarAddress); + + EXPECT_ANY_THROW(new Address("")); +} + +TEST(StellarAddress, isValid) { + string stellarAddress = "GABQHYQOY22KHGTCTAK24AWAUE4TXERF4O4JBSXELNM7IL5CTPUWM3SC"; + string bitcoinAddress = "1Ma2DrB78K7jmAwaomqZNRMCvgQrNjE2QC"; + + ASSERT_TRUE(Address::isValid(stellarAddress)); + ASSERT_FALSE(Address::isValid(bitcoinAddress)); + ASSERT_FALSE(Address::isValid("qc64537q3cvjmc2cgkz10y58waj4294967296r10ccchmrmrdzq03783")); +} + +} // namespace TW::Stellar::tests diff --git a/tests/chains/Stellar/TWAnySignerTests.cpp b/tests/chains/Stellar/TWAnySignerTests.cpp new file mode 100644 index 00000000000..fe8728b1de4 --- /dev/null +++ b/tests/chains/Stellar/TWAnySignerTests.cpp @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "Stellar/Address.h" +#include "proto/Stellar.pb.h" +#include +#include +#include + +using namespace TW; + +namespace TW::Stellar::tests { + +TEST(TWAnySingerStellar, Sign_Payment) { + auto key = parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722"); + Proto::SigningInput input; + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); + input.set_fee(1000); + input.set_sequence(2); + input.mutable_op_payment()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); + input.mutable_op_payment()->set_amount(10000000); + input.set_private_key(key.data(), key.size()); + auto& memoText = *input.mutable_memo_text(); + memoText.set_text("Hello, world!"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeStellar); + + EXPECT_EQ(output.signature(), "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAEAAAANSGVsbG8sIHdvcmxkIQAAAAAAAAEAAAAAAAAAAQAAAADFgLYxeg6zm/f81Po8Gf2rS4m7q79hCV7kUFr27O16rgAAAAAAAAAAAJiWgAAAAAAAAAABGd9cogAAAEBQQldEkYJ6rMvOHilkwFCYyroGGUvrNeWVqr/sn3iFFqgz91XxgUT0ou7bMSPRgPROfBYDfQCFfFxbcDPrrCwB"); +} + +TEST(TWAnySingerStellar, Sign_Payment_66b5) { + auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); + PrivateKey privKey = PrivateKey(key); + PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); + Address addr = Address(pubKey); + EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); + + Proto::SigningInput input; + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); + input.set_fee(1000); + input.set_sequence(144098454883270657); + input.mutable_op_payment()->set_destination("GA3ISGYIE2ZTH3UAKEKBVHBPKUSL3LT4UQ6C5CUGP2IM5F467O267KI7"); + input.mutable_op_payment()->set_amount(1000000); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeStellar); + + // curl "https://horizon.stellar.org/transactions/66b5bca4b4293bdd85a6a559b08918482774b76bcc170b4533411f1d6422ce24" + EXPECT_EQ(output.signature(), "AAAAAMpFJQVVMv16RJUPlzQUTlgZOHVurhw3igGacP1305F1AAAD6AH/8MgAAAABAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAANokbCCazM+6AURQanC9VJL2ufKQ8LoqGfpDOl577te8AAAAAAAAAAAAPQkAAAAAAAAAAAXfTkXUAAABAM9Nhzr8iWKzqnHknrxSVoa4b2qzbTzgyE2+WWxg6XHH50xiFfmvtRKVhzp0Jg8PfhatOb6KNheKRWEw4OvqEDw=="); +} + +TEST(TWAnySingerStellar, Sign_Payment_Asset_ea50) { + auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); + PrivateKey privKey = PrivateKey(key); + PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); + Address addr = Address(pubKey); + EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); + + Proto::SigningInput input; + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); + input.set_fee(1000); + input.set_sequence(144098454883270661); + input.mutable_op_payment()->set_destination("GA3ISGYIE2ZTH3UAKEKBVHBPKUSL3LT4UQ6C5CUGP2IM5F467O267KI7"); + input.mutable_op_payment()->mutable_asset()->set_issuer("GA6HCMBLTZS5VYYBCATRBRZ3BZJMAFUDKYYF6AH6MVCMGWMRDNSWJPIH"); + input.mutable_op_payment()->mutable_asset()->set_alphanum4("MOBI"); + input.mutable_op_payment()->set_amount(12000000); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeStellar); + + // curl "https://horizon.stellar.org/transactions/ea50884cd1288d2d5420065995d13d750d812258e0e79280c4033a434e625c99 + EXPECT_EQ(output.signature(), "AAAAAMpFJQVVMv16RJUPlzQUTlgZOHVurhw3igGacP1305F1AAAD6AH/8MgAAAAFAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAANokbCCazM+6AURQanC9VJL2ufKQ8LoqGfpDOl577te8AAAABTU9CSQAAAAA8cTArnmXa4wEQJxDHOw5SwBaDVjBfAP5lRMNZkRtlZAAAAAAAtxsAAAAAAAAAAAF305F1AAAAQEuWZZvKZuF6SMuSGIyfLqx5sn5O55+Kd489uP4g9jZH4UE7zZ4ME0+74I0BU8YDsYOmmxcfp/vdwTd+n3oGCQw="); +} + +TEST(TWAnySingerStellar, Sign_Change_Trust_ad9c) { + auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); + PrivateKey privKey = PrivateKey(key); + PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); + Address addr = Address(pubKey); + EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); + + Proto::SigningInput input; + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); + input.set_fee(10000); + input.set_sequence(144098454883270659); + input.mutable_op_change_trust()->mutable_asset()->set_issuer("GA6HCMBLTZS5VYYBCATRBRZ3BZJMAFUDKYYF6AH6MVCMGWMRDNSWJPIH"); + input.mutable_op_change_trust()->mutable_asset()->set_alphanum4("MOBI"); + input.mutable_op_change_trust()->set_valid_before(1613336576); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeStellar); + + // curl "https://horizon.stellar.org/transactions/ad9cd0f3d636096b6502ccae07adbcf2cd3c0da5393fc2b07813dbe90ecc0d7b" + EXPECT_EQ(output.signature(), "AAAAAMpFJQVVMv16RJUPlzQUTlgZOHVurhw3igGacP1305F1AAAnEAH/8MgAAAADAAAAAQAAAAAAAAAAAAAAAGApkAAAAAAAAAAAAQAAAAAAAAAGAAAAAU1PQkkAAAAAPHEwK55l2uMBECcQxzsOUsAWg1YwXwD+ZUTDWZEbZWR//////////wAAAAAAAAABd9ORdQAAAEAnfyXyaNQX5Bq3AEQVBIaYd+cLib+y2sNY7DF/NYVSE51dZ6swGGElz094ObsPefmVmeRrkGsSc/fF5pmth+wJ"); +} + +TEST(TWAnySingerStellar, Sign_Change_Trust_2) { + auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); + PrivateKey privKey = PrivateKey(key); + PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); + Address addr = Address(pubKey); + EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); + + Proto::SigningInput input; + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); + input.set_fee(10000); + input.set_sequence(144098454883270659); + input.mutable_op_change_trust()->mutable_asset()->set_issuer("GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX"); + input.mutable_op_change_trust()->mutable_asset()->set_alphanum4("USD"); + input.mutable_op_change_trust()->set_valid_before(1613336576); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeStellar); + + EXPECT_EQ(output.signature(), "AAAAAMpFJQVVMv16RJUPlzQUTlgZOHVurhw3igGacP1305F1AAAnEAH/8MgAAAADAAAAAQAAAAAAAAAAAAAAAGApkAAAAAAAAAAAAQAAAAAAAAAGAAAAAVVTRAAAAAAA6KYahh5gr2D4B3PgY0blxyy+Wdyt2jdgjVjvQlEdn9x//////////wAAAAAAAAABd9ORdQAAAEDMZtN05ZsZB4OKOZSFkQvuRqDIvMME3PYMTAGJPQlO6Ee0nOtaRn2q0uf0IhETSSfqcsK5asAZzNj07tG0SPwM"); +} + +TEST(TWAnySingerStellar, Sign_Create_Claimable_Balance_1f1f84) { + auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); + PrivateKey privKey = PrivateKey(key); + PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); + Address addr = Address(pubKey); + EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); + + Proto::SigningInput input; + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); + input.set_fee(10000); + input.set_sequence(144098454883270687); + input.mutable_op_create_claimable_balance()->set_amount(90000000); + input.mutable_op_create_claimable_balance()->add_claimants(); + input.mutable_op_create_claimable_balance()->mutable_claimants(0)->set_account("GC6CJDAY54D3O4RHEH33LUTBKDZGVOTR6NHBOTL4PIWI2CDKVRSZZJGJ"); + input.mutable_op_create_claimable_balance()->mutable_claimants(0)->set_predicate(Proto::Predicate_unconditional); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeStellar); + + // https://stellar.expert/explorer/public/tx/1f1f849ff2560901c91226f2fc866ef4ed1c67d672262c1f5829abe2348ac638 + // curl -X POST -F "tx=AAAAAMpF..Bg==" "https://horizon.stellar.org/transactions" + EXPECT_EQ(output.signature(), "AAAAAMpFJQVVMv16RJUPlzQUTlgZOHVurhw3igGacP1305F1AAAnEAH/8MgAAAAfAAAAAAAAAAAAAAABAAAAAAAAAA4AAAAAAAAAAAVdSoAAAAABAAAAAAAAAAC8JIwY7we3cich97XSYVDyarpx804XTXx6LI0IaqxlnAAAAAAAAAAAAAAAAXfTkXUAAABAgms/HPhEP/EYtVr5aWwhKJsn3pIVEZGFnTD2Xd/VPVsn8qogI7RYyjyBxSFPiLAljgGsPaUMfU3WFvyJCWNwBg=="); +} + +TEST(TWAnySingerStellar, Sign_Claim_Claimable_Balance_c1fb3c) { + auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); + PrivateKey privKey = PrivateKey(key); + PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); + Address addr = Address(pubKey); + EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); + + Proto::SigningInput input; + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); + input.set_fee(10000); + input.set_sequence(144098454883270689); + const Data balanceIdHash = parse_hex("9c7b794b7b150f3e4c6dcfa260672bbe0c248b360129112e927e0f7ee2f9faf8"); + input.mutable_op_claim_claimable_balance()->set_balance_id(balanceIdHash.data(), balanceIdHash.size()); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeStellar); + + // https://stellar.expert/explorer/public/tx/c1fb3cf348aeb72bb2e1030c1d7f7f9c6c6d1bbab071b3e7c7c1cadafa795e8e + // curl -X POST -F "tx=AAAAAMpF..DQ==" "https://horizon.stellar.org/transactions" + EXPECT_EQ(output.signature(), "AAAAAMpFJQVVMv16RJUPlzQUTlgZOHVurhw3igGacP1305F1AAAnEAH/8MgAAAAhAAAAAAAAAAAAAAABAAAAAAAAAA8AAAAAnHt5S3sVDz5Mbc+iYGcrvgwkizYBKREukn4PfuL5+vgAAAAAAAAAAXfTkXUAAABAWL7dKkR1JuPZGFbDTRDgGBHW/vLPMWNRkAew+wPfGiCnZhpJJDcyX197EDDZMsJ7ungPUyhczRaeQOwZKx4DDQ=="); + + { // negative test: hash wrong size + const Data invalidBalanceIdHash = parse_hex("010203"); + input.mutable_op_claim_claimable_balance()->set_balance_id(invalidBalanceIdHash.data(), invalidBalanceIdHash.size()); + ANY_SIGN(input, TWCoinTypeStellar); + EXPECT_EQ(output.signature(), "AAAAAXfTkXUAAABAFCywEfLs3q5Tv9eZCIcjhkJR0s8J4Us9G5YjVKUSaMoUz/AadC8dM2oQSLhpC5wjrNBi7hevg7jlkPx5/4AJCQ=="); + } +} + +} // namespace TW::Stellar::tests diff --git a/tests/chains/Stellar/TWCoinTypeTests.cpp b/tests/chains/Stellar/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..95e6cbd55f1 --- /dev/null +++ b/tests/chains/Stellar/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWStellarCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeStellar)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("8a7ff7261e8b3f31af7f6ed257c2e9fe7c47afcd9b1ce1be1bfc1bc5f6a3ad9e")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeStellar, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("GCILJZQ3CKBKBUJWW4TAM6Q37LJA5MQX6GMSFSQN75BPLWIZ33OPRG52")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeStellar, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeStellar)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeStellar)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeStellar), 7); + ASSERT_EQ(TWBlockchainStellar, TWCoinTypeBlockchain(TWCoinTypeStellar)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeStellar)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeStellar)); + assertStringsEqual(symbol, "XLM"); + assertStringsEqual(txUrl, "https://blockchair.com/stellar/transaction/8a7ff7261e8b3f31af7f6ed257c2e9fe7c47afcd9b1ce1be1bfc1bc5f6a3ad9e"); + assertStringsEqual(accUrl, "https://blockchair.com/stellar/account/GCILJZQ3CKBKBUJWW4TAM6Q37LJA5MQX6GMSFSQN75BPLWIZ33OPRG52"); + assertStringsEqual(id, "stellar"); + assertStringsEqual(name, "Stellar"); +} diff --git a/tests/Stellar/TWStellarAddressTests.cpp b/tests/chains/Stellar/TWStellarAddressTests.cpp similarity index 75% rename from tests/Stellar/TWStellarAddressTests.cpp rename to tests/chains/Stellar/TWStellarAddressTests.cpp index 47d1f759c76..aeb02ed3a06 100644 --- a/tests/Stellar/TWStellarAddressTests.cpp +++ b/tests/chains/Stellar/TWStellarAddressTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/Stellar/TransactionCompilerTests.cpp b/tests/chains/Stellar/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..578877ba49c --- /dev/null +++ b/tests/chains/Stellar/TransactionCompilerTests.cpp @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Stellar.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(StellarCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeStellar; + /// Step 1: Prepare transaction input (protobuf) + TW::Stellar::Proto::SigningInput input; + auto privateKey = + PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); + auto publicKey = privateKey.getPublicKey(::publicKeyType(coin)); + + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); + input.set_fee(1000); + input.set_sequence(2); + input.mutable_op_payment()->set_destination( + "GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); + input.mutable_op_payment()->set_amount(10000000); + auto &memoText = *input.mutable_memo_text(); + memoText.set_text("Hello, world!"); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "1e8786a0162630b2393e0f6c51f16a2d7860715023cb19bf25cad14490b1f8f3"); + + auto signature = parse_hex("5042574491827aaccbce1e2964c05098caba06194beb35e595aabfec9f788516a83" + "3f755f18144f4a2eedb3123d180f44e7c16037d00857c5c5b7033ebac2c01"); + + /// Step 3: Compile transaction info + const auto tx = "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/" + "DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAEAAAANSGVsbG8sIHdvcmxkIQAAAAAAAAEAAAAAAAAA" + "AQAAAADFgLYxeg6zm/" + "f81Po8Gf2rS4m7q79hCV7kUFr27O16rgAAAAAAAAAAAJiWgAAAAAAAAAABGd9cogAAAEBQQldEkYJ6" + "rMvOHilkwFCYyroGGUvrNeWVqr/sn3iFFqgz91XxgUT0ou7bMSPRgPROfBYDfQCFfFxbcDPrrCwB"; + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {}); + { + TW::Stellar::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.signature(), tx); + } + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::Stellar::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::Stellar::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.signature(), tx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {}); + Stellar::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.signature().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); + } +} diff --git a/tests/chains/Stellar/TransactionTests.cpp b/tests/chains/Stellar/TransactionTests.cpp new file mode 100644 index 00000000000..1803cb1605f --- /dev/null +++ b/tests/chains/Stellar/TransactionTests.cpp @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include "HexCoding.h" +#include "Stellar/Signer.h" +#include +#include + +#include + +namespace TW::Stellar::tests { + +using namespace std; + +TEST(StellarTransaction, sign) { + auto words = STRING("indicate rival expand cave giant same grocery burden ugly rose tuna blood"); + auto passphrase = STRING(""); + + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeStellar)); + auto input = TW::Stellar::Proto::SigningInput(); + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); + input.set_fee(1000); + input.set_sequence(2); + input.mutable_op_payment()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); + input.mutable_op_payment()->set_amount(10000000); + input.set_private_key(privateKey.get()->impl.bytes.data(), privateKey.get()->impl.bytes.size()); + + const auto signer = TW::Stellar::Signer(input); + + const auto signature = signer.sign(); + ASSERT_EQ(signature, "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAxYC2MXoOs5v3/NT6PBn9q0uJu6u/YQle5FBa9uzteq4AAAAAAAAAAACYloAAAAAAAAAAARnfXKIAAABAocQZwTnVvGMQlpdGacWvgenxN5ku8YB8yhEGrDfEV48yDqcj6QaePAitDj/N2gxfYD9Q2pJ+ZpkQMsZZG4ACAg=="); +} + +TEST(StellarTransaction, signWithMemoText) { + auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); + auto input = TW::Stellar::Proto::SigningInput(); + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); + input.set_fee(1000); + input.set_sequence(2); + auto memoText = TW::Stellar::Proto::MemoText(); + memoText.set_text("Hello, world!"); + *input.mutable_memo_text() = memoText; + input.mutable_op_payment()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); + input.mutable_op_payment()->set_amount(10000000); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto signer = Signer(input); + + const auto signature = signer.sign(); + ASSERT_EQ(signature, "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAEAAAANSGVsbG8sIHdvcmxkIQAAAAAAAAEAAAAAAAAAAQAAAADFgLYxeg6zm/f81Po8Gf2rS4m7q79hCV7kUFr27O16rgAAAAAAAAAAAJiWgAAAAAAAAAABGd9cogAAAEBQQldEkYJ6rMvOHilkwFCYyroGGUvrNeWVqr/sn3iFFqgz91XxgUT0ou7bMSPRgPROfBYDfQCFfFxbcDPrrCwB"); +} + +TEST(StellarTransaction, signWithMemoHash) { + auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); + auto input = TW::Stellar::Proto::SigningInput(); + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); + input.set_fee(1000); + input.set_sequence(2); + auto memoHash = TW::Stellar::Proto::MemoHash(); + auto fromHex = parse_hex("315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3"); + memoHash.set_hash(fromHex.data(), fromHex.size()); + *input.mutable_memo_hash() = memoHash; + input.mutable_op_payment()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); + input.mutable_op_payment()->set_amount(10000000); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto signer = Signer(input); + + const auto signature = signer.sign(); + ASSERT_EQ(signature, "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAMxX1vbdtB4xDuKwAZOSgFkYSsfznfIaTRb/JTHWJTt0wAAAAEAAAAAAAAAAQAAAADFgLYxeg6zm/f81Po8Gf2rS4m7q79hCV7kUFr27O16rgAAAAAAAAAAAJiWgAAAAAAAAAABGd9cogAAAECIyh1BG+hER5W+dgHDKe49X6VEYRWIjajM4Ufq3DUG/yw7Xv1MMF4eax3U0TRi7Qwj2fio/DRD3+/Ljtvip2MD"); +} + +TEST(StellarTransaction, signWithMemoReturn) { + auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); + auto input = TW::Stellar::Proto::SigningInput(); + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); + input.set_fee(1000); + input.set_sequence(2); + auto memoHash = TW::Stellar::Proto::MemoHash(); + auto fromHex = parse_hex("315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3"); + memoHash.set_hash(fromHex.data(), fromHex.size()); + *input.mutable_memo_return_hash() = memoHash; + input.mutable_op_payment()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); + input.mutable_op_payment()->set_amount(10000000); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto signer = Signer(input); + + const auto signature = signer.sign(); + ASSERT_EQ(signature, "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAQxX1vbdtB4xDuKwAZOSgFkYSsfznfIaTRb/JTHWJTt0wAAAAEAAAAAAAAAAQAAAADFgLYxeg6zm/f81Po8Gf2rS4m7q79hCV7kUFr27O16rgAAAAAAAAAAAJiWgAAAAAAAAAABGd9cogAAAEBd77iui04quoaoWMfeJO06nRfn3Z9bptbAj7Ol44j3ApU8c9dJwVhJbQ7La4mKgIkYviEhGx3AIulFYCkokb8M"); +} + +TEST(StellarTransaction, signWithMemoID) { + auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); + auto input = TW::Stellar::Proto::SigningInput(); + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); + input.set_fee(1000); + input.set_sequence(2); + auto memoId = TW::Stellar::Proto::MemoId(); + memoId.set_id(1234567890); + *input.mutable_memo_id() = memoId; + input.mutable_op_payment()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); + input.mutable_op_payment()->set_amount(10000000); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto signer = Signer(input); + + const auto signature = signer.sign(); + ASSERT_EQ(signature, "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAIAAAAASZYC0gAAAAEAAAAAAAAAAQAAAADFgLYxeg6zm/f81Po8Gf2rS4m7q79hCV7kUFr27O16rgAAAAAAAAAAAJiWgAAAAAAAAAABGd9cogAAAEAOJ8wwCizQPf6JmkCsCNZolQeqet2qN7fgLUUQlwx3TNzM0+/GJ6Qc2faTybjKy111rE60IlnfaPeMl/nyxKIB"); +} + +TEST(StellarTransaction, signAcreateAccount) { + auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); + auto input = TW::Stellar::Proto::SigningInput(); + input.set_passphrase(TWStellarPassphrase_Stellar); + input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); + input.set_fee(1000); + input.set_sequence(2); + auto memoId = TW::Stellar::Proto::MemoId(); + memoId.set_id(1234567890); + *input.mutable_memo_id() = memoId; + input.mutable_op_create_account()->set_destination("GDCYBNRRPIHLHG7X7TKPUPAZ7WVUXCN3VO7WCCK64RIFV5XM5V5K4A52"); + input.mutable_op_create_account()->set_amount(10000000); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto signer = Signer(input); + + const auto signature = signer.sign(); + ASSERT_EQ(signature, "AAAAAAmpZryqzBA+OIlrquP4wvBsIf1H3U+GT/DTP5gZ31yiAAAD6AAAAAAAAAACAAAAAAAAAAIAAAAASZYC0gAAAAEAAAAAAAAAAAAAAADFgLYxeg6zm/f81Po8Gf2rS4m7q79hCV7kUFr27O16rgAAAAAAmJaAAAAAAAAAAAEZ31yiAAAAQNgqNDqbe0X60gyH+1xf2Tv2RndFiJmyfbrvVjsTfjZAVRrS2zE9hHlqPQKpZkGKEFka7+1ElOS+/m/1JDnauQg="); +} + +} // namespace TW::Stellar::tests diff --git a/tests/chains/Stratis/TWCoinTypeTests.cpp b/tests/chains/Stratis/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..978e12df6b1 --- /dev/null +++ b/tests/chains/Stratis/TWCoinTypeTests.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWStratisCoinType, TWCoinType) { + const auto coin = TWCoinTypeStratis; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("3923df87e83859dec8b87a414cbb1529113788c512a4d0c283e1394c274f0bfb")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("XWqnSWzQA5kDAS727UTYtXkdcbKc8mEvyN")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "stratis"); + assertStringsEqual(name, "Stratis"); + assertStringsEqual(symbol, "STRAX"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 8); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainBitcoin); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x8c); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(txUrl, "https://explorer.rutanio.com/strax/explorer/transaction/3923df87e83859dec8b87a414cbb1529113788c512a4d0c283e1394c274f0bfb"); + assertStringsEqual(accUrl, "https://explorer.rutanio.com/strax/explorer/address/XWqnSWzQA5kDAS727UTYtXkdcbKc8mEvyN"); +} diff --git a/tests/chains/Stratis/TWStratisTests.cpp b/tests/chains/Stratis/TWStratisTests.cpp new file mode 100644 index 00000000000..6d8dbf1f95b --- /dev/null +++ b/tests/chains/Stratis/TWStratisTests.cpp @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include +#include +#include +#include +#include +#include + +#include + +TEST(Stratis, LegacyAddress) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeStratis))); + auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); + assertStringsEqual(addressString, "XMEd53bqmNitpFX1cUd1tV6LRME4pcuaPe"); +} + +TEST(Stratis, Address) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625").get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeStratis)); + auto string = WRAPS(TWAnyAddressDescription(address.get())); + + assertStringsEqual(string, "strax1qytnqzjknvv03jwfgrsmzt0ycmwqgl0as6zdq3n"); +} + +TEST(Stratis, LockScriptForLegacyAddress) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("XWqnSWzQA5kDAS727UTYtXkdcbKc8mEvyN").get(), TWCoinTypeStratis)); + auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); + assertHexEqual(scriptData, "76a914d5d068b60f3b63a5a59cc7b8609ac85b76b1896388ac"); +} + +TEST(Stratis, LockScriptForAddress) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("strax1qqktrryxg23qjxmnhmz9xsp8w4kkfqv7c2xl6t7").get(), TWCoinTypeStratis)); + auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); + assertHexEqual(scriptData, "001405963190c85441236e77d88a6804eeadac9033d8"); +} + +TEST(Stratis, ExtendedKeys) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( + STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), + STRING("TREZOR").get() + )); + + // .bip44 + auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeStratis, TWHDVersionXPRV)); + auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeStratis, TWHDVersionXPUB)); + + assertStringsEqual(xprv, "xprv9xyQ71PNhXBFdiY9xAs76X1Y4YzejPv9qe6tKBQ4pEemnmk6b4iFnV1BpJThm2en26emssc558vqHPujDyBKDdSkrNtQiHwbzpQNobWyvh9"); + assertStringsEqual(xpub, "xpub6BxkWWvGXtjYrCcd4CQ7TexGcaq98re1Cs2V7ZogNaBkfa5F8c2WLHKffYrwmJdNQztsd3oJvdmHuhN79c8qKpASRtavBsbcUq1R5SxeQtq"); +} + +TEST(Stratis, DeriveFromXpub) { + auto xpub = STRING("xpub6BxkWWvGXtjYrCcd4CQ7TexGcaq98re1Cs2V7ZogNaBkfa5F8c2WLHKffYrwmJdNQztsd3oJvdmHuhN79c8qKpASRtavBsbcUq1R5SxeQtq"); + auto pubKey2 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeStratis, STRING("m/44'/105105'/0'/0/2").get())); + auto pubKey9 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeStratis, STRING("m/44'/105105'/0'/0/9").get())); + + auto address2 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey2.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeStratis))); + auto address2String = WRAPS(TWBitcoinAddressDescription(address2.get())); + + auto address9 = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(pubKey9.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeStratis))); + auto address9String = WRAPS(TWBitcoinAddressDescription(address9.get())); + + assertStringsEqual(address2String, "XC4QM1nSbHrLb8sWMf4qXcphocqSAMNLng"); + assertStringsEqual(address9String, "XM4ixdCpyqF86RhKwWRyUXFxXHNypRXiyL"); +} \ No newline at end of file diff --git a/tests/chains/Sui/CompilerTests.cpp b/tests/chains/Sui/CompilerTests.cpp new file mode 100644 index 00000000000..b496546ff43 --- /dev/null +++ b/tests/chains/Sui/CompilerTests.cpp @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "proto/Sui.pb.h" +#include "proto/TransactionCompiler.pb.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include + +namespace TW::Sui::tests { + +TEST(SuiCompiler, PreHashAndCompile) { + // Successfully broadcasted https://explorer.sui.io/txblock/HkPo6rYPyDY53x1MBszvSZVZyixVN7CHvCJGX381czAh?network=devnet + auto expectedData = parse_hex("000000000002000810270000000000000020259ff8074ab425cbb489f236e18e08f03f1a7856bdf7c7a2877bd64f738b50150202000101000001010300000000010100d575ad7f18e948462a5cf698f564ef394a752a71fec62493af8a055c012c0d500106f2c2c8c1d8964df1019d6616e9705719bebabd931da2755cb948ceb7e68964ec020000000000002060456ec667f5cd10467680ebf950ed329205175dacd946bb236aeed57c8617cfd575ad7f18e948462a5cf698f564ef394a752a71fec62493af8a055c012c0d500100000000000000d00700000000000000"); + auto expectedHash = TW::Hash::blake2b(expectedData, 32); + + Proto::SigningInput input; + auto txMsg = "AAACAAgQJwAAAAAAAAAgJZ/4B0q0Jcu0ifI24Y4I8D8aeFa998eih3vWT3OLUBUCAgABAQAAAQEDAAAAAAEBANV1rX8Y6UhGKlz2mPVk7zlKdSpx/sYkk6+KBVwBLA1QAQbywsjB2JZN8QGdZhbpcFcZvrq9kx2idVy5SM635olk7AIAAAAAAAAgYEVuxmf1zRBGdoDr+VDtMpIFF12s2Ua7I2ru1XyGF8/Vda1/GOlIRipc9pj1ZO85SnUqcf7GJJOvigVcASwNUAEAAAAAAAAA0AcAAAAAAAAA"; + input.mutable_sign_direct_message()->set_unsigned_tx_msg(txMsg); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + auto preImageHashesData = TransactionCompiler::preImageHashes(TWCoinTypeSui, inputStrData); + TxCompiler::Proto::PreSigningOutput preSigningOutput; + preSigningOutput.ParseFromArray(preImageHashesData.data(), static_cast(preImageHashesData.size())); + + EXPECT_EQ(data(preSigningOutput.data()), expectedData); + EXPECT_EQ(data(preSigningOutput.data_hash()), expectedHash); + + auto privateKey = PrivateKey(parse_hex("3823dce5288ab55dd1c00d97e91933c613417fdb282a0b8b01a7f5f5a533b266")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; + auto signature = privateKey.sign(expectedHash, TWCurveED25519); + + auto outputData = TransactionCompiler::compileWithSignatures(TWCoinTypeSui, inputStrData, {signature}, {publicKey}); + Proto::SigningOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(output.signature(), "APxPduNVvHj2CcRcHOtiP2aBR9qP3vO2Cb0g12PI64QofDB6ks33oqe/i/iCTLcop2rBrkczwrayZuJOdi7gvwNqfN7sFqdcD/Z4e8I1YQlGkDMCK7EOgmydRDqfH8C9jg=="); +} + +} diff --git a/tests/chains/Sui/SignerTests.cpp b/tests/chains/Sui/SignerTests.cpp new file mode 100644 index 00000000000..ce801ca9113 --- /dev/null +++ b/tests/chains/Sui/SignerTests.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "proto/Sui.pb.h" +#include "PublicKey.h" +#include "TestUtilities.h" + +#include + +namespace TW::Sui::tests { + +TEST(SuiSigner, Transfer) { + // Successfully broadcasted https://explorer.sui.io/txblock/HkPo6rYPyDY53x1MBszvSZVZyixVN7CHvCJGX381czAh?network=devnet + Proto::SigningInput input; + auto txMsg = "AAACAAgQJwAAAAAAAAAgJZ/4B0q0Jcu0ifI24Y4I8D8aeFa998eih3vWT3OLUBUCAgABAQAAAQEDAAAAAAEBANV1rX8Y6UhGKlz2mPVk7zlKdSpx/sYkk6+KBVwBLA1QAQbywsjB2JZN8QGdZhbpcFcZvrq9kx2idVy5SM635olk7AIAAAAAAAAgYEVuxmf1zRBGdoDr+VDtMpIFF12s2Ua7I2ru1XyGF8/Vda1/GOlIRipc9pj1ZO85SnUqcf7GJJOvigVcASwNUAEAAAAAAAAA0AcAAAAAAAAA"; + input.mutable_sign_direct_message()->set_unsigned_tx_msg(txMsg); + auto privateKey = PrivateKey(parse_hex("3823dce5288ab55dd1c00d97e91933c613417fdb282a0b8b01a7f5f5a533b266")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeSui); + ASSERT_EQ(output.unsigned_tx(), "AAACAAgQJwAAAAAAAAAgJZ/4B0q0Jcu0ifI24Y4I8D8aeFa998eih3vWT3OLUBUCAgABAQAAAQEDAAAAAAEBANV1rX8Y6UhGKlz2mPVk7zlKdSpx/sYkk6+KBVwBLA1QAQbywsjB2JZN8QGdZhbpcFcZvrq9kx2idVy5SM635olk7AIAAAAAAAAgYEVuxmf1zRBGdoDr+VDtMpIFF12s2Ua7I2ru1XyGF8/Vda1/GOlIRipc9pj1ZO85SnUqcf7GJJOvigVcASwNUAEAAAAAAAAA0AcAAAAAAAAA"); + ASSERT_EQ(output.signature(), "APxPduNVvHj2CcRcHOtiP2aBR9qP3vO2Cb0g12PI64QofDB6ks33oqe/i/iCTLcop2rBrkczwrayZuJOdi7gvwNqfN7sFqdcD/Z4e8I1YQlGkDMCK7EOgmydRDqfH8C9jg=="); +} + +} // namespace TW::Sui::tests diff --git a/tests/chains/Sui/TWCoinTypeTests.cpp b/tests/chains/Sui/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..459a08e9ff5 --- /dev/null +++ b/tests/chains/Sui/TWCoinTypeTests.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWSuiCoinType, TWCoinType) { + const auto coin = TWCoinTypeSui; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("5i8fbSL6r8yw2xcKmXxwkzHu3wpiyMLsyf2htCvDH8Ao")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x259ff8074ab425cbb489f236e18e08f03f1a7856bdf7c7a2877bd64f738b5015")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "sui"); + assertStringsEqual(name, "Sui"); + assertStringsEqual(symbol, "SUI"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 9); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainSui); + assertStringsEqual(txUrl, "https://explorer.sui.io//txblock/5i8fbSL6r8yw2xcKmXxwkzHu3wpiyMLsyf2htCvDH8Ao"); + assertStringsEqual(accUrl, "https://explorer.sui.io//address/0x259ff8074ab425cbb489f236e18e08f03f1a7856bdf7c7a2877bd64f738b5015"); +} diff --git a/tests/chains/Syscoin/TWCoinTypeTests.cpp b/tests/chains/Syscoin/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..f33efe2beb6 --- /dev/null +++ b/tests/chains/Syscoin/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWSyscoinCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeSyscoin)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("19e043f76f6ffc960f5fe93ecec37bc37a58ae7525d7e9cd6ed40f71f0da60eb")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeSyscoin, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("sys1qh3gvhnzq2ch7w8g04x8zksr2mz7r90x7ksmu40")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeSyscoin, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeSyscoin)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeSyscoin)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeSyscoin), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeSyscoin)); + ASSERT_EQ(0x5, TWCoinTypeP2shPrefix(TWCoinTypeSyscoin)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeSyscoin)); + assertStringsEqual(symbol, "SYS"); + assertStringsEqual(txUrl, "https://sys1.bcfn.ca/tx/19e043f76f6ffc960f5fe93ecec37bc37a58ae7525d7e9cd6ed40f71f0da60eb"); + assertStringsEqual(accUrl, "https://sys1.bcfn.ca/address/sys1qh3gvhnzq2ch7w8g04x8zksr2mz7r90x7ksmu40"); + assertStringsEqual(id, "syscoin"); + assertStringsEqual(name, "Syscoin"); +} diff --git a/tests/chains/Syscoin/TWSyscoinTests.cpp b/tests/chains/Syscoin/TWSyscoinTests.cpp new file mode 100644 index 00000000000..77cc8390314 --- /dev/null +++ b/tests/chains/Syscoin/TWSyscoinTests.cpp @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include +#include +#include +#include +#include +#include + +#include + +TEST(Syscoin, LegacyAddress) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeSyscoin))); + auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); + assertStringsEqual(addressString, "SXBPFk2PFDAP13qyKSdC4yptsJ8kJRAT3T"); +} + +TEST(Syscoin, Address) { + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625").get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeSyscoin)); + auto string = WRAPS(TWAnyAddressDescription(address.get())); + + assertStringsEqual(string, "sys1qytnqzjknvv03jwfgrsmzt0ycmwqgl0as7lkcf7"); +} + +TEST(Syscoin, LockScriptForLegacyAddress) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("SXBPFk2PFDAP13qyKSdC4yptsJ8kJRAT3T").get(), TWCoinTypeSyscoin)); + auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); + assertHexEqual(scriptData, "76a9146c70e57df7b18eeb0198be9e254737ecd336ed8888ac"); +} + +TEST(Syscoin, LockScriptForAddress) { + auto script = WRAP(TWBitcoinScript, TWBitcoinScriptLockScriptForAddress(STRING("sys1qytnqzjknvv03jwfgrsmzt0ycmwqgl0as7lkcf7").get(), TWCoinTypeSyscoin)); + auto scriptData = WRAPD(TWBitcoinScriptData(script.get())); + assertHexEqual(scriptData, "001422e6014ad3631f1939281c3625bc98db808fbfb0"); +} + +TEST(Syscoin, ExtendedKeys) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( + STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), + STRING("TREZOR").get() + )); + + // .bip44 + auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeSyscoin, TWHDVersionXPRV)); + auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeSyscoin, TWHDVersionXPUB)); + + assertStringsEqual(xprv, "xprv9yFNgN7z81uG6QtwFt7gvbmLeDGeGfS2ar3DunwEkZcC7uLBXyy4eaaV3ir769zMLe3eHuTaGUtWVXwp6dkunLsfmA7bf3XqEFpTjHxSijx"); + assertStringsEqual(xpub, "xpub6CEj5sesxPTZJtyQMuehHji5CF78g89sx4xpiBLrJu9AzhfL5XHKCNtxtzPzyGxqb6jMbZfmbHeSGZZArL4hLttmdC6KEMuiWy7VocTYjzR"); + + // .bip49 + auto yprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP49, TWCoinTypeSyscoin, TWHDVersionYPRV)); + auto ypub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP49, TWCoinTypeSyscoin, TWHDVersionYPUB)); + + assertStringsEqual(yprv, "yprvAJAofBFEEQ1DLJJVMkPr4pufHLUKZ2VSbtHqPpphEgwgfvG8exgadM8vtW8AW52N7tqU4qM8JHk5xZkq3icnzoph5QA5kRVHBnhXuRMGw2b"); + assertStringsEqual(ypub, "ypub6XAA4gn84mZWYnNxTmvrRxrPqNJoxVDHy7DSCDEJo2UfYibHCVzqB9TQjmL2TKSEZVFmTNcmdJXunEu6oV2AFQNeiszjzcnX4nbG27s4SgS"); + + // .bip84 + auto zprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP84, TWCoinTypeSyscoin, TWHDVersionZPRV)); + auto zpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP84, TWCoinTypeSyscoin, TWHDVersionZPUB)); + assertStringsEqual(zprv, "zprvAcdCiLx9ooAFnC1hXh7stnobLnnu7u25rqfLeJ9v632xdCXJrc8KvgNk2eZeQQbPQHvcUpsfJzgyDkRdfnkT6vjpYqkxFv1LsPxQ7uFwLGy"); + assertStringsEqual(zpub, "zpub6qcZ7rV3eAiYzg6AdietFvkKtpdPXMjwE4awSgZXeNZwVzrTQ9SaUUhDswmdA4A5riyimx322es7niQvJ1Fbi3mJNSVz3d3f9GBsYBb8Wky"); +} + +TEST(Syscoin, DeriveFromZpub) { + auto zpub = STRING("zpub6sCFp8chadVDXVt7GRmQFpq8B7W8wMLdFDto1hXu2jLZtvkFhRnwScXARNfrGSeyhR8DBLJnaUUkBbkmB2GwUYkecEAMUcbUpFQV4v7PXcs"); + auto pubKey4 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeSyscoin, STRING("m/44'/2'/0'/0/4").get())); + auto pubKey11 = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(zpub.get(), TWCoinTypeSyscoin, STRING("m/44'/2'/0'/0/11").get())); + + auto address4 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey4.get(), TWCoinTypeSyscoin)); + auto address4String = WRAPS(TWAnyAddressDescription(address4.get())); + + auto address11 = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubKey11.get(), TWCoinTypeSyscoin)); + auto address11String = WRAPS(TWAnyAddressDescription(address11.get())); + + assertStringsEqual(address4String, "sys1qcgnevr9rp7aazy62m4gen0tfzlssa52a2z04vc"); + assertStringsEqual(address11String, "sys1qy072y8968nzp6mz3j292h8lp72d678fchhmyta"); +} diff --git a/tests/chains/TBinance/TWAnyAddressTests.cpp b/tests/chains/TBinance/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..196cc13bc86 --- /dev/null +++ b/tests/chains/TBinance/TWAnyAddressTests.cpp @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWTBinance, Address) { + auto string = STRING("tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9f8"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeTBinance)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "3ed78029e7f5303787dfaf03b7f282354659064a"); +} diff --git a/tests/chains/TBinance/TWCoinTypeTests.cpp b/tests/chains/TBinance/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..6c55888e754 --- /dev/null +++ b/tests/chains/TBinance/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWTBinanceCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTBinance)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("92E9DA1B6D603667E2DE83C0AC0C1D9E6D65405AD642DA794421C64A82A078D0")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTBinance, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("tbnb1c2cxgv3cklswxlvqr9anm6mlp6536qnd36txgr")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTBinance, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTBinance)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTBinance)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTBinance), 8); + ASSERT_EQ(TWBlockchainBinance, TWCoinTypeBlockchain(TWCoinTypeTBinance)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTBinance)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTBinance)); + assertStringsEqual(symbol, "BNB"); + assertStringsEqual(txUrl, "https://testnet-explorer.binance.org/tx/92E9DA1B6D603667E2DE83C0AC0C1D9E6D65405AD642DA794421C64A82A078D0"); + assertStringsEqual(accUrl, "https://testnet-explorer.binance.org/address/tbnb1c2cxgv3cklswxlvqr9anm6mlp6536qnd36txgr"); + assertStringsEqual(id, "tbinance"); + assertStringsEqual(name, "TBNB"); +} diff --git a/tests/chains/Tezos/AddressTests.cpp b/tests/chains/Tezos/AddressTests.cpp new file mode 100644 index 00000000000..0fca1e431cd --- /dev/null +++ b/tests/chains/Tezos/AddressTests.cpp @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HDWallet.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "Tezos/Address.h" +#include "Tezos/Forging.h" + +#include + +#include +#include +#include + +using namespace TW; + +namespace TW::Tezos::tests { + +TEST(TezosAddress, forge_tz1) { + auto input = Address("tz1eZwq8b5cvE2bPKokatLkVMzkxz24z3Don"); + auto expected = "0000cfa4aae60f5d9389752d41e320da224d43287fe2"; + + ASSERT_EQ(input.forge(), parse_hex(expected)); +} + +TEST(TezosAddress, forge_tz2) { + auto input = Address("tz2Rh3NYeLxrqTuvaZJmaMiVMqCajeXMWtYo"); + auto expected = "0001be99dd914e38388ec80432818b517759e3524f16"; + + ASSERT_EQ(input.forge(), parse_hex(expected)); +} + +TEST(TezosAddress, forge_tz3) { + auto input = Address("tz3RDC3Jdn4j15J7bBHZd29EUee9gVB1CxD9"); + auto expected = "0002358cbffa97149631cfb999fa47f0035fb1ea8636"; + + ASSERT_EQ(input.forge(), parse_hex(expected)); +} + +TEST(TezosAddress, forge_kt1) { + auto input = Address("KT1XnTn74bUtxHfDtBmm2bGZAQfhPbvKWR8o"); + auto expected = "01fe810959c3d6127a41cbd471e7cb4e91a61b780b00"; + + ASSERT_EQ(input.forge(), parse_hex(expected)); +} + +TEST(TezosAddress, isInvalid) { + std::array invalidAddresses{ + "NmH7tmeJUmHcncBDvpr7aJNEBk7rp5zYsB1qt", // Invalid prefix, valid checksum + "tz1eZwq8b5cvE2bPKokatLkVMzkxz24z3AAAA", // Valid prefix, invalid checksum + "1tzeZwq8b5cvE2bPKokatLkVMzkxz24zAAAAA" // Invalid prefix, invalid checksum + }; + + for (auto& address : invalidAddresses) { + ASSERT_FALSE(Address::isValid(address)); + } +} + +TEST(TezosAddress, isValid) { + std::array validAddresses { + "tz1Yju7jmmsaUiG9qQLoYv35v5pHgnWoLWbt", + "tz2PdGc7U5tiyqPgTSgqCDct94qd6ovQwP6u", + "tz3VEZ4k6a4Wx42iyev6i2aVAptTRLEAivNN", + "KT1XnTn74bUtxHfDtBmm2bGZAQfhPbvKWR8o", + }; + + for (auto& address : validAddresses) { + ASSERT_TRUE(Address::isValid(address)); + } +} + +TEST(TezosAddress, string) { + auto addressString = "tz1d1qQL3mYVuiH4JPFvuikEpFwaDm85oabM"; + auto address = Address(addressString); + ASSERT_EQ(address.string(), addressString); +} + +TEST(TezosAddress, deriveOriginatedAddress) { + auto operationHash = "oo7VeTEPjEusPKnsHtKcGYbYa7i4RWpcEhUVo3Suugbbs6K62Ro"; + auto operationIndex = 0; + auto expected = "KT1WrtjtAYQSrUVvSNJPTZTebiUWoopQL5hw"; + + ASSERT_EQ(Address::deriveOriginatedAddress(operationHash, operationIndex), expected); +} + +TEST(TezosAddress, PublicKeyInit) { + Data bytes = parse_hex("01fe157cc8011727936c592f856c9071d39cf4acdadfa6d76435e4619c9dc56f63"); + const auto publicKey = PublicKey(bytes, TWPublicKeyTypeED25519); + auto address = Address(publicKey); + + auto expected = "tz1cG2jx3W4bZFeVGBjsTxUAG8tdpTXtE8PT"; + ASSERT_EQ(address.string(), expected); +} + +} // namespace TW::Tezos::tests diff --git a/tests/chains/Tezos/ForgingTests.cpp b/tests/chains/Tezos/ForgingTests.cpp new file mode 100644 index 00000000000..95ffe14dd78 --- /dev/null +++ b/tests/chains/Tezos/ForgingTests.cpp @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HDWallet.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "Tezos/Address.h" +#include "Tezos/BinaryCoding.h" +#include "Tezos/Forging.h" +#include "proto/Tezos.pb.h" +#include +#include +#include +#include + +namespace TW::Tezos::tests { + +TEST(Forging, ForgeBoolTrue) { + auto expected = "ff"; + + auto output = forgeBool(true); + + ASSERT_EQ(output, parse_hex(expected)); +} + +TEST(Forging, ForgeBoolFalse) { + auto expected = "00"; + + auto output = forgeBool(false); + + ASSERT_EQ(output, parse_hex(expected)); +} + +TEST(Forging, ForgeZarithZero) { + auto expected = "00"; + + auto output = forgeZarith(0); + + ASSERT_EQ(hex(output), hex(parse_hex(expected))); +} + +TEST(Forging, ForgeZarithTen) { + auto expected = "0a"; + + auto output = forgeZarith(10); + + ASSERT_EQ(output, parse_hex(expected)); +} + +TEST(Forging, ForgeZarithTwenty) { + auto expected = "14"; + + auto output = forgeZarith(20); + + ASSERT_EQ(output, parse_hex(expected)); +} + +TEST(Forging, ForgeZarithOneHundredFifty) { + auto expected = "9601"; + + auto output = forgeZarith(150); + + ASSERT_EQ(output, parse_hex(expected)); +} + +TEST(Forging, ForgeZarithLarge) { + auto expected = "bbd08001"; + + auto output = forgeZarith(2107451); + + ASSERT_EQ(hex(output), expected); +} + +TEST(Forging, forge_tz1) { + auto expected = "00cfa4aae60f5d9389752d41e320da224d43287fe2"; + + auto output = forgePublicKeyHash("tz1eZwq8b5cvE2bPKokatLkVMzkxz24z3Don"); + + ASSERT_EQ(output, parse_hex(expected)); +} + +TEST(Forging, forge_tz2) { + auto expected = "01be99dd914e38388ec80432818b517759e3524f16"; + + auto output = forgePublicKeyHash("tz2Rh3NYeLxrqTuvaZJmaMiVMqCajeXMWtYo"); + + ASSERT_EQ(output, parse_hex(expected)); +} + +TEST(Forging, forge_tz3) { + auto expected = "02358cbffa97149631cfb999fa47f0035fb1ea8636"; + + auto output = forgePublicKeyHash("tz3RDC3Jdn4j15J7bBHZd29EUee9gVB1CxD9"); + + ASSERT_EQ(output, parse_hex(expected)); +} + +TEST(Forging, ForgeED25519PublicKey) { + auto expected = "00311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10906f511cff95"; + + auto privateKey = PrivateKey(parse_hex("c6377a4cc490dc913fc3f0d9cf67d293a32df4547c46cb7e9e33c3b7b97c64d8")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + auto output = forgePublicKey(publicKey); + + ASSERT_EQ(hex(output), expected); +} + +TEST(Forging, ForgeInt32) { + auto expected = "01"; + ASSERT_EQ(hex(forgeInt32(1, 1)), expected); +} + +TEST(Forging, ForgeString) { + auto expected = "087472616e73666572"; + ASSERT_EQ(hex(forgeString("transfer", 1)), expected); +} + +TEST(Forging, ForgeEntrypoint) { + auto expected = "ff087472616e73666572"; + ASSERT_EQ(hex(forgeEntrypoint("transfer")), expected); +} + +TEST(Forging, ForgeMichelsonFA12) { + Tezos::Proto::FA12Parameters data; + data.set_entrypoint("transfer"); + data.set_from("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + data.set_to("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + data.set_value("123"); + auto v = FA12ParameterToMichelson(data); + ASSERT_EQ(hex(forgeMichelson(v)), "07070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555000bb01"); +} +TEST(Forging, ForgeSECP256k1PublicKey) { + auto expected = "0102b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfe"; + + auto privateKey = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto output = forgePublicKey(publicKey); + + ASSERT_EQ(hex(output), expected); +} + +TEST(TezosTransaction, forgeTransaction) { + auto transactionOperationData = new TW::Tezos::Proto::TransactionOperationData(); + transactionOperationData->set_amount(1); + transactionOperationData->set_destination("tz1Yju7jmmsaUiG9qQLoYv35v5pHgnWoLWbt"); + + auto transactionOperation = TW::Tezos::Proto::Operation(); + transactionOperation.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + transactionOperation.set_fee(1272); + transactionOperation.set_counter(30738); + transactionOperation.set_gas_limit(10100); + transactionOperation.set_storage_limit(257); + transactionOperation.set_kind(TW::Tezos::Proto::Operation::TRANSACTION); + transactionOperation.set_allocated_transaction_operation_data(transactionOperationData); + + auto expected = "6c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e81020100008fb5cea62d147c696afd9a93dbce962f4c8a9c9100"; + auto serialized = forgeOperation(transactionOperation); + + ASSERT_EQ(hex(serialized), expected); +} + +TEST(TezosTransaction, forgeTransactionFA12) { + auto transactionOperationData = new TW::Tezos::Proto::TransactionOperationData(); + transactionOperationData->set_amount(0); + transactionOperationData->set_destination("KT1EwXFWoG9bYebmF4pYw72aGjwEnBWefgW5"); + transactionOperationData->mutable_parameters()->mutable_fa12_parameters()->set_from("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + transactionOperationData->mutable_parameters()->mutable_fa12_parameters()->set_to("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + transactionOperationData->mutable_parameters()->mutable_fa12_parameters()->set_entrypoint("transfer"); + transactionOperationData->mutable_parameters()->mutable_fa12_parameters()->set_value("123"); + + auto transactionOperation = TW::Tezos::Proto::Operation(); + transactionOperation.set_source("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + transactionOperation.set_fee(100000); + transactionOperation.set_counter(2993172); + transactionOperation.set_gas_limit(100000); + transactionOperation.set_storage_limit(0); + transactionOperation.set_kind(TW::Tezos::Proto::Operation::TRANSACTION); + transactionOperation.set_allocated_transaction_operation_data(transactionOperationData); + + auto expected = "6c00fe2ce0cccc0214af521ad60c140c5589b4039247a08d0694d8b601a08d0600000145bd8a65cc48159d8ea60a55df735b7c5ad45f0e00ffff087472616e736665720000005907070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555000bb01"; + auto serialized = forgeOperation(transactionOperation); + + ASSERT_EQ(hex(serialized), expected); +} + +TEST(TezosTransaction, forgeTransactionFA2) { + auto transactionOperationData = new TW::Tezos::Proto::TransactionOperationData(); + transactionOperationData->set_amount(0); + transactionOperationData->set_destination("KT1DYk1XDzHredJq1EyNkDindiWDqZyekXGj"); + auto& fa2 = *transactionOperationData->mutable_parameters()->mutable_fa2_parameters(); + fa2.set_entrypoint("transfer"); + auto& txObject = *fa2.add_txs_object(); + txObject.set_from("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + auto& tx = *txObject.add_txs(); + tx.set_amount("10"); + tx.set_token_id("0"); + tx.set_to("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + + auto transactionOperation = TW::Tezos::Proto::Operation(); + transactionOperation.set_source("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + transactionOperation.set_fee(100000); + transactionOperation.set_counter(2993173); + transactionOperation.set_gas_limit(100000); + transactionOperation.set_storage_limit(0); + transactionOperation.set_kind(TW::Tezos::Proto::Operation::TRANSACTION); + transactionOperation.set_allocated_transaction_operation_data(transactionOperationData); + auto serialized = forgeOperation(transactionOperation); + auto expected = "6c00fe2ce0cccc0214af521ad60c140c5589b4039247a08d0695d8b601a08d0600000136767f88850bae28bfb9f46b73c5e87ede4de12700ffff087472616e7366657200000066020000006107070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b5550020000003107070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070000000a"; + ASSERT_EQ(hex(serialized), expected); +} + +TEST(TezosTransaction, forgeReveal) { + PublicKey publicKey = parsePublicKey("edpku9ZF6UUAEo1AL3NWy1oxHLL6AfQcGYwA5hFKrEKVHMT3Xx889A"); + + auto revealOperationData = new TW::Tezos::Proto::RevealOperationData(); + revealOperationData->set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + + auto revealOperation = TW::Tezos::Proto::Operation(); + revealOperation.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + revealOperation.set_fee(1272); + revealOperation.set_counter(30738); + revealOperation.set_gas_limit(10100); + revealOperation.set_storage_limit(257); + revealOperation.set_kind(TW::Tezos::Proto::Operation::REVEAL); + revealOperation.set_allocated_reveal_operation_data(revealOperationData); + + auto expected = "6b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200429a986c8072a40a1f3a3e2ab5a5819bb1b2fb69993c5004837815b9dc55923e"; + auto serialized = forgeOperation(revealOperation); + + ASSERT_EQ(hex(serialized), expected); +} + +TEST(TezosTransaction, forgeDelegate) { + auto delegateOperationData = new TW::Tezos::Proto::DelegationOperationData(); + delegateOperationData->set_delegate("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); + + auto delegateOperation = TW::Tezos::Proto::Operation(); + delegateOperation.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + delegateOperation.set_fee(1272); + delegateOperation.set_counter(30738); + delegateOperation.set_gas_limit(10100); + delegateOperation.set_storage_limit(257); + delegateOperation.set_kind(TW::Tezos::Proto::Operation::DELEGATION); + delegateOperation.set_allocated_delegation_operation_data(delegateOperationData); + + auto expected = "6e0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e8102ff003e47f837f0467b4acde406ed5842f35e2414b1a8"; + auto serialized = forgeOperation(delegateOperation); + + ASSERT_EQ(hex(serialized), expected); +} + +TEST(TezosTransaction, forgeUndelegate) { + auto delegateOperationData = new TW::Tezos::Proto::DelegationOperationData(); + delegateOperationData->set_delegate(""); + + auto delegateOperation = TW::Tezos::Proto::Operation(); + delegateOperation.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + delegateOperation.set_fee(1272); + delegateOperation.set_counter(30738); + delegateOperation.set_gas_limit(10100); + delegateOperation.set_storage_limit(257); + delegateOperation.set_kind(TW::Tezos::Proto::Operation::DELEGATION); + delegateOperation.set_allocated_delegation_operation_data(delegateOperationData); + + auto expected = "6e0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200"; + auto serialized = forgeOperation(delegateOperation); + + ASSERT_EQ(hex(serialized), expected); +} + +} // namespace TW::Tezos::tests diff --git a/tests/chains/Tezos/MessageSignerTests.cpp b/tests/chains/Tezos/MessageSignerTests.cpp new file mode 100644 index 00000000000..3f38133d55a --- /dev/null +++ b/tests/chains/Tezos/MessageSignerTests.cpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include + +#include "HexCoding.h" +#include "TestUtilities.h" +#include +#include + +namespace TW::Tezos::tests { +TEST(TezosMessageSigner, inputToPayload) { + auto payload = Tezos::MessageSigner::inputToPayload("Tezos Signed Message: testUrl 2023-02-08T10:36:18.454Z Hello World"); + ASSERT_EQ(payload, "05010000004254657a6f73205369676e6564204d6573736167653a207465737455726c20323032332d30322d30385431303a33363a31382e3435345a2048656c6c6f20576f726c64"); +} + +TEST(TezosMessageSigner, formatMessage) { + auto formatMessage = Tezos::MessageSigner::formatMessage("Hello World", "testUrl"); + std::regex regex("Tezos Signed Message: \\S+ \\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z .+"); + ASSERT_TRUE(std::regex_match(formatMessage, regex)); +} + +TEST(TezosMessageSigner, SignMessage) { + auto payload = Tezos::MessageSigner::inputToPayload("Tezos Signed Message: testUrl 2023-02-08T10:36:18.454Z Hello World"); + PrivateKey privKey(parse_hex("91b4fb8d7348db2e7de2693f58ce1cceb966fa960739adac1d9dba2cbaa0940a")); + auto result = Tezos::MessageSigner::signMessage(privKey, payload); + auto expected = "edsigu3se2fcEJUCm1aqxjzbHdf7Wsugr4mLaA9YM2UVZ9Yy5meGv87VqHN3mmDeRwApTj1JKDaYjqmLZifSFdWCqBoghqaowwJ"; + ASSERT_EQ(result, expected); + ASSERT_TRUE(Tezos::MessageSigner::verifyMessage(privKey.getPublicKey(TWPublicKeyTypeED25519), payload, result)); +} + +TEST(TWTezosMessageSigner, formatMessage) { + const auto message = STRING("Hello World"); + const auto dappUrl = STRING("testUrl"); + auto formattedMsg = WRAPS(TWTezosMessageSignerFormatMessage(message.get(), dappUrl.get())); + std::regex regex("Tezos Signed Message: \\S+ \\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z .+"); + ASSERT_TRUE(std::regex_match(std::string(TWStringUTF8Bytes(formattedMsg.get())), regex)); +} + +TEST(TWTezosMessageSigner, inputToPayload) { + const auto message = STRING("Tezos Signed Message: testUrl 2023-02-08T10:36:18.454Z Hello World"); + const auto expected = "05010000004254657a6f73205369676e6564204d6573736167653a207465737455726c20323032332d30322d30385431303a33363a31382e3435345a2048656c6c6f20576f726c64"; + auto payload = WRAPS(TWTezosMessageSignerInputToPayload(message.get())); + ASSERT_EQ(std::string(TWStringUTF8Bytes(payload.get())), expected); +} + +TEST(TWTezosMessageSigner, SignAndVerify) { + const auto privKeyData = "91b4fb8d7348db2e7de2693f58ce1cceb966fa960739adac1d9dba2cbaa0940a"; + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto message = STRING("05010000004254657a6f73205369676e6564204d6573736167653a207465737455726c20323032332d30322d30385431303a33363a31382e3435345a2048656c6c6f20576f726c64"); + + const auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeTezos)); + const auto signature = WRAPS(TWTezosMessageSignerSignMessage(privateKey.get(), message.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature.get())), "edsigu3se2fcEJUCm1aqxjzbHdf7Wsugr4mLaA9YM2UVZ9Yy5meGv87VqHN3mmDeRwApTj1JKDaYjqmLZifSFdWCqBoghqaowwJ"); + EXPECT_TRUE(TWTezosMessageSignerVerifyMessage(pubKey.get(), message.get(), signature.get())); +} +} // namespace TW::Tezos::tests diff --git a/tests/chains/Tezos/OperationListTests.cpp b/tests/chains/Tezos/OperationListTests.cpp new file mode 100644 index 00000000000..4218e01c6e1 --- /dev/null +++ b/tests/chains/Tezos/OperationListTests.cpp @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Tezos/Address.h" +#include "Tezos/BinaryCoding.h" +#include "Tezos/OperationList.h" +#include "proto/Tezos.pb.h" +#include "HexCoding.h" + +#include + +namespace TW::Tezos::tests { + +TEST(TezosOperationList, ForgeBranch) { + auto input = TW::Tezos::OperationList("BMNY6Jkas7BzKb7wDLCFoQ4YxfYoieU7Xmo1ED3Y9Lo3ZvVGdgW"); + auto expected = "da8eb4f57f98a647588b47d29483d1edfdbec1428c11609cee0da6e0f27cfc38"; + + ASSERT_EQ(input.forgeBranch(), parse_hex(expected)); +} + +TEST(TezosOperationList, ForgeOperationList_TransactionOnly) { + auto branch = "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"; + auto op_list = TW::Tezos::OperationList(branch); + auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); + + auto transactionOperationData = new Proto::TransactionOperationData(); + transactionOperationData->set_amount(1); + transactionOperationData->set_destination("tz1Yju7jmmsaUiG9qQLoYv35v5pHgnWoLWbt"); + + auto transactionOperation = Proto::Operation(); + transactionOperation.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + transactionOperation.set_fee(1272); + transactionOperation.set_counter(30738); + transactionOperation.set_gas_limit(10100); + transactionOperation.set_storage_limit(257); + transactionOperation.set_kind(Proto::Operation::TRANSACTION); + transactionOperation.set_allocated_transaction_operation_data(transactionOperationData); + + op_list.addOperation(transactionOperation); + + auto expected = "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e81020100008fb5cea62d147c696afd9a93dbce962f4c8a9c9100"; + auto forged = op_list.forge(key); + ASSERT_EQ(hex(forged), expected); +} + +TEST(TezosOperationList, ForgeOperationList_RevealOnly) { + auto branch = "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"; + auto op_list = TW::Tezos::OperationList(branch); + auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); + PublicKey publicKey = parsePublicKey("edpku9ZF6UUAEo1AL3NWy1oxHLL6AfQcGYwA5hFKrEKVHMT3Xx889A"); + + auto revealOperationData = new Proto::RevealOperationData(); + revealOperationData->set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + + auto revealOperation = Proto::Operation(); + revealOperation.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + revealOperation.set_fee(1272); + revealOperation.set_counter(30738); + revealOperation.set_gas_limit(10100); + revealOperation.set_storage_limit(257); + revealOperation.set_kind(TW::Tezos::Proto::Operation::REVEAL); + revealOperation.set_allocated_reveal_operation_data(revealOperationData); + + op_list.addOperation(revealOperation); + auto expected = "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200429a986c8072a40a1f3a3e2ab5a5819bb1b2fb69993c5004837815b9dc55923e"; + auto forged = op_list.forge(key); + + ASSERT_EQ(hex(forged), expected); +} + +TEST(TezosOperationList, ForgeOperationList_Delegation_ClearDelegate) { + auto branch = "BLGJfQDFEYZBRLj5GSHskj8NPaRYhk7Kx5WAfdcDucD3q98WdeW"; + auto op_list = OperationList(branch); + auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); + auto delegationOperationData = new Proto::DelegationOperationData(); + delegationOperationData->set_delegate("tz1gSM6yiwr85jEASZ1q3UekgHEoxYt7wg2M"); + + auto delegationOperation = Proto::Operation(); + delegationOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); + delegationOperation.set_fee(1257); + delegationOperation.set_counter(67); + delegationOperation.set_gas_limit(10000); + delegationOperation.set_storage_limit(0); + delegationOperation.set_kind(Proto::Operation::DELEGATION); + delegationOperation.set_allocated_delegation_operation_data(delegationOperationData); + + op_list.addOperation(delegationOperation); + + auto expected = "48b63d801fa824013a195f7885ba522503c59e0580f7663e15c52f03ccc935e66e003e47f837f0467b4acde406ed5842f35e2414b1a8e90943904e00ff00e42504da69a7c8d5baeaaeebe157a02db6b22ed8"; + ASSERT_EQ(hex(op_list.forge(key)), expected); +} + +TEST(TezosOperationList, ForgeOperationList_Delegation_AddDelegate) { + auto branch = "BLa4GrVQTxUgQWbHv6cF7RXWSGzHGPbgecpQ795R3cLzw4cGfpD"; + auto op_list = OperationList(branch); + auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); + auto delegationOperationData = new Proto::DelegationOperationData(); + delegationOperationData->set_delegate("tz1dYUCcrorfCoaQCtZaxi1ynGrP3prTZcxS"); + + auto delegationOperation = Proto::Operation(); + delegationOperation.set_source("KT1D5jmrBD7bDa3jCpgzo32FMYmRDdK2ihka"); + delegationOperation.set_fee(1257); + delegationOperation.set_counter(68); + delegationOperation.set_gas_limit(10000); + delegationOperation.set_storage_limit(0); + delegationOperation.set_kind(Proto::Operation::DELEGATION); + delegationOperation.set_allocated_delegation_operation_data(delegationOperationData); + + op_list.addOperation(delegationOperation); + auto expected = "7105102c032807994dd9b5edf219261896a559876ca16cbf9d31dbe3612b89f26e00315b1206ec00b1b1e64cc3b8b93059f58fa2fc39e90944904e00ff00c4650fd609f88c67356e5fe01e37cd3ff654b18c"; + auto forged = op_list.forge(key); + ASSERT_EQ(hex(forged), expected); +} + +TEST(TezosOperationList, ForgeOperationList_TransactionAndReveal) { + auto branch = "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"; + auto op_list = OperationList(branch); + auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); + auto publicKey = parsePublicKey("edpkuNb9N2UHtGeSc2BZCBHN8ETx7E4DwkSfz5Hw3m3tF3dLZTU8qp"); + + auto revealOperationData = new Proto::RevealOperationData(); + revealOperationData->set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + + auto revealOperation = Proto::Operation(); + revealOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); + revealOperation.set_fee(1272); + revealOperation.set_counter(30738); + revealOperation.set_gas_limit(10100); + revealOperation.set_storage_limit(257); + revealOperation.set_kind(Proto::Operation::REVEAL); + revealOperation.set_allocated_reveal_operation_data(revealOperationData); + + auto transactionOperationData = new Proto::TransactionOperationData(); + transactionOperationData->set_amount(1); + transactionOperationData->set_destination("tz1gSM6yiwr85jEASZ1q3UekgHEoxYt7wg2M"); + + auto transactionOperation = Proto::Operation(); + transactionOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); + transactionOperation.set_fee(1272); + transactionOperation.set_counter(30739); + transactionOperation.set_gas_limit(10100); + transactionOperation.set_storage_limit(257); + transactionOperation.set_kind(Proto::Operation::TRANSACTION); + transactionOperation.set_allocated_transaction_operation_data(transactionOperationData); + + op_list.addOperation(revealOperation); + op_list.addOperation(transactionOperation); + + auto expected = "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b003e47f837f0467b4acde406ed5842f35e2414b1a8f80992f001f44e810200603247bbf52501498293686da89ad8b2aca85f83b90903d4521dd2aba66054eb6c003e47f837f0467b4acde406ed5842f35e2414b1a8f80993f001f44e8102010000e42504da69a7c8d5baeaaeebe157a02db6b22ed800"; + auto forged = op_list.forge(key); + + ASSERT_EQ(hex(forged), expected); +} + +TEST(TezosOperationList, ForgeOperationList_RevealWithoutPublicKey) { + auto branch = "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"; + auto op_list = OperationList(branch); + auto key = parsePrivateKey("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); + + auto revealOperationData = new Proto::RevealOperationData(); + + auto revealOperation = Proto::Operation(); + revealOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); + revealOperation.set_fee(1272); + revealOperation.set_counter(30738); + revealOperation.set_gas_limit(10100); + revealOperation.set_storage_limit(257); + revealOperation.set_kind(Proto::Operation::REVEAL); + revealOperation.set_allocated_reveal_operation_data(revealOperationData); + + op_list.addOperation(revealOperation); + + auto expected = "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b003e47f837f0467b4acde406ed5842f35e2414b1a8f80992f001f44e810200603247bbf52501498293686da89ad8b2aca85f83b90903d4521dd2aba66054eb"; + auto forged = op_list.forge(key); + + ASSERT_EQ(hex(forged), expected); +} + +} // namespace TW::Tezos::tests diff --git a/tests/chains/Tezos/PublicKeyTests.cpp b/tests/chains/Tezos/PublicKeyTests.cpp new file mode 100644 index 00000000000..c91383fcb86 --- /dev/null +++ b/tests/chains/Tezos/PublicKeyTests.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Tezos/BinaryCoding.h" +#include "Tezos/Forging.h" +#include "PublicKey.h" +#include "Data.h" +#include "HexCoding.h" + +#include + +namespace TW::Tezos::tests { + +TEST(TezosPublicKey, forge) { + auto input = parsePublicKey("edpkuAfEJCEatRgFpRGg3gn3FdWniLXBoubARreRwuVZPWufkgDBvR"); + auto expected = "00451bde832454ba73e6e0de313fcf5d1565ec51080edc73bb19287b8e0ab2122b"; + auto serialized = forgePublicKey(input); + ASSERT_EQ(hex(serialized), expected); +} + +TEST(TezosPublicKey, parse) { + auto input = "edpkuAfEJCEatRgFpRGg3gn3FdWniLXBoubARreRwuVZPWufkgDBvR"; + auto bytes = Data({1, 69, 27, 222, 131, 36, 84, 186, 115, 230, 224, 222, 49, 63, 207, 93, 21, 101, 236, 81, 8, 14, 220, 115, 187, 25, 40, 123, 142, 10, 178, 18, 43}); + auto output = parsePublicKey(input); + auto expected = PublicKey(bytes, TWPublicKeyTypeED25519); + ASSERT_EQ(output, expected); +} + +} // namespace TW::Tezos::tests diff --git a/tests/chains/Tezos/SignerTests.cpp b/tests/chains/Tezos/SignerTests.cpp new file mode 100644 index 00000000000..2f63e20c9e8 --- /dev/null +++ b/tests/chains/Tezos/SignerTests.cpp @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base58.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "Tezos/BinaryCoding.h" +#include "Tezos/OperationList.h" +#include "Tezos/Signer.h" + +#include + +using namespace TW; + +namespace TW::Tezos::tests { + +TEST(TezosSigner, SignString) { + Data bytesToSign = parse_hex("ffaa"); + Data expectedSignature = parse_hex("eaab7f4066217b072b79609a9f76cdfadd93f8dde41763887e131c02324f18c8e41b1009e334baf87f9d2e917bf4c0e73165622e5522409a0c5817234a48cc02"); + Data expected = Data(); + append(expected, bytesToSign); + append(expected, expectedSignature); + + auto key = PrivateKey(parse_hex("0x2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f")); + auto signedBytes = Signer().signData(key, bytesToSign); + + ASSERT_EQ(signedBytes, expected); +} + +TEST(TezosSigner, SignOperationList) { + auto branch = "BLDnkhhVgwdBAtmDNQc5HtEMsrxq8L3t7NQbjUbbdTdw5Ug1Mpe"; + auto op_list = Tezos::OperationList(branch); + + auto transactionOperationData = new Proto::TransactionOperationData(); + transactionOperationData->set_amount(11100000); + transactionOperationData->set_destination("tz1gSM6yiwr85jEASZ1q3UekgHEoxYt7wg2M"); + + auto transactionOperation = TW::Tezos::Proto::Operation(); + transactionOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); + transactionOperation.set_fee(1283); + transactionOperation.set_counter(1878); + transactionOperation.set_gas_limit(10307); + transactionOperation.set_storage_limit(0); + transactionOperation.set_kind(Proto::Operation::TRANSACTION); + transactionOperation.set_allocated_transaction_operation_data(transactionOperationData); + + op_list.addOperation(transactionOperation); + + PublicKey publicKey = parsePublicKey("edpkuNb9N2UHtGeSc2BZCBHN8ETx7E4DwkSfz5Hw3m3tF3dLZTU8qp"); + auto revealOperationData = new TW::Tezos::Proto::RevealOperationData(); + revealOperationData->set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + + auto revealOperation = Proto::Operation(); + revealOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); + revealOperation.set_fee(1268); + revealOperation.set_counter(1876); + revealOperation.set_gas_limit(10100); + revealOperation.set_storage_limit(0); + revealOperation.set_kind(Proto::Operation::REVEAL); + revealOperation.set_allocated_reveal_operation_data(revealOperationData); + + op_list.addOperation(revealOperation); + + auto delegateOperationData = new Tezos::Proto::DelegationOperationData(); + delegateOperationData->set_delegate("tz1gSM6yiwr85jEASZ1q3UekgHEoxYt7wg2M"); + + auto delegateOperation = Proto::Operation(); + delegateOperation.set_source("tz1RKLoYm4vtLzo7TAgGifMDAkiWhjfyXwP4"); + delegateOperation.set_fee(1257); + delegateOperation.set_counter(1879); + delegateOperation.set_gas_limit(10100); + delegateOperation.set_storage_limit(0); + delegateOperation.set_kind(Proto::Operation::DELEGATION); + delegateOperation.set_allocated_delegation_operation_data(delegateOperationData); + + op_list.addOperation(delegateOperation); + + auto decodedPrivateKey = Base58::decodeCheck("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); + auto key = PrivateKey(Data(decodedPrivateKey.begin() + 4, decodedPrivateKey.end())); + + std::string expectedForgedBytesToSign = hex(op_list.forge(key)); + std::string expectedSignature = "871693145f2dc72861ff6816e7ac3ce93c57611ac09a4c657a5a35270fa57153334c14cd8cae94ee228b6ef52f0e3f10948721e666318bc54b6c455404b11e03"; + std::string expectedSignedBytes = expectedForgedBytesToSign + expectedSignature; + + auto signedBytes = Signer().signOperationList(key, op_list); + auto signedBytesHex = hex(signedBytes); + + ASSERT_EQ(hex(signedBytes), expectedSignedBytes); +} + +} // namespace TW::Tezos::tests diff --git a/tests/chains/Tezos/TWAnySignerTests.cpp b/tests/chains/Tezos/TWAnySignerTests.cpp new file mode 100644 index 00000000000..30943e749c8 --- /dev/null +++ b/tests/chains/Tezos/TWAnySignerTests.cpp @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Tezos/BinaryCoding.h" +#include "proto/Tezos.pb.h" +#include "TestUtilities.h" +#include + +#include + +using namespace TW; +namespace TW::Tezos::tests { + +TEST(TWAnySignerTezos, SignFA12) { + // https://ghostnet.tzkt.io/ooTBu7DLbeC7DmVfXEsp896A6WTwimedbsM9QRqUVtqA8Vxt6D3/2993172 + auto key = parse_hex("363265a0b3f06661001cab8b4f3ca8fd97ae70608184979cf7300836f57ec2d6"); + + Proto::SigningInput input; + input.set_private_key(key.data(), key.size()); + auto& operations = *input.mutable_operation_list(); + operations.set_branch("BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"); + + auto& transaction = *operations.add_operations(); + auto& txData = *transaction.mutable_transaction_operation_data(); + txData.set_amount(0); + txData.set_destination("KT1EwXFWoG9bYebmF4pYw72aGjwEnBWefgW5"); + txData.mutable_parameters()->mutable_fa12_parameters()->set_entrypoint("transfer"); + txData.mutable_parameters()->mutable_fa12_parameters()->set_from("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + txData.mutable_parameters()->mutable_fa12_parameters()->set_to("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + txData.mutable_parameters()->mutable_fa12_parameters()->set_value("123"); + transaction.set_source("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + transaction.set_fee(100000); + transaction.set_counter(2993172); + transaction.set_gas_limit(100000); + transaction.set_storage_limit(0); + transaction.set_kind(Proto::Operation::TRANSACTION); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeTezos); + ASSERT_EQ(hex(output.encoded()), "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016c00fe2ce0cccc0214af521ad60c140c5589b4039247a08d0694d8b601a08d0600000145bd8a65cc48159d8ea60a55df735b7c5ad45f0e00ffff087472616e736665720000005907070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555000bb012914d768155fba2df319a81136e8e3e573b9cadb1676834490c90212615d271da029b6b0531e290e9063bcdb40bea43627af048b18e036f02be2b6b22fc8b307"); +} + +TEST(TWAnySignerTezos, SignFA2) { + // https://ghostnet.tzkt.io/onxLBoPaf23M3A8kHTwncSFG2GVXPfnGXUhkC8BhKj8QDdCEbng + auto key = parse_hex("363265a0b3f06661001cab8b4f3ca8fd97ae70608184979cf7300836f57ec2d6"); + + Proto::SigningInput input; + input.set_private_key(key.data(), key.size()); + auto& operations = *input.mutable_operation_list(); + operations.set_branch("BKvEAX9HXfJZWYfTQbR1C7B3ADoKY6a1aKVRF7qQqvc9hS8Rr3m"); + + auto& transaction = *operations.add_operations(); + + auto* transactionOperationData = transaction.mutable_transaction_operation_data(); + transactionOperationData->set_amount(0); + transactionOperationData->set_destination("KT1DYk1XDzHredJq1EyNkDindiWDqZyekXGj"); + + auto& fa2 = *transactionOperationData->mutable_parameters()->mutable_fa2_parameters(); + fa2.set_entrypoint("transfer"); + auto& txObject = *fa2.add_txs_object(); + txObject.set_from("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + auto& tx = *txObject.add_txs(); + tx.set_amount("10"); + tx.set_token_id("0"); + tx.set_to("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + + transaction.set_source("tz1ioz62kDw6Gm5HApeQtc1PGmN2wPBtJKUP"); + transaction.set_fee(100000); + transaction.set_counter(2993173); + transaction.set_gas_limit(100000); + transaction.set_storage_limit(0); + transaction.set_kind(Proto::Operation::TRANSACTION); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeTezos); + ASSERT_EQ(hex(output.encoded()), "1b1f9345dc9f77bd24b09034d1d2f9a28f02ac837f49db54b8d68341f53dc4b76c00fe2ce0cccc0214af521ad60c140c5589b4039247a08d0695d8b601a08d0600000136767f88850bae28bfb9f46b73c5e87ede4de12700ffff087472616e7366657200000066020000006107070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b5550020000003107070100000024747a31696f7a36326b447736476d35484170655174633150476d4e32775042744a4b555007070000000a552d24710d6c59383286700c6c2917b25a6c1fa8b587e593c289dd47704278796792f1e522c1623845ec991e292b0935445e6994850bd03f035a006c5ed93806"); +} + +TEST(TWAnySignerTezos, BlindSign) { + // Successfully broadcasted: https://ghostnet.tzkt.io/oobGgTkDNz9eqGVXiU4wShPZydkroCrmbKjoDcfSqhnM7GmcdEu/15229334 + auto key = parse_hex("3caf5afaed067890cd850efd1555df351aa482badb4a541c29261f1acf261bf5"); + auto bytes = parse_hex("64aa7792af40de41371a72b3342daa7bf3d2b5a84511e9074341fdd52148dd9d6c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542850f96c3a1079d780080ade2040155959998da7e79231e2be8ed8ff373ac1b1574b000ffff04737761700000009e070703060707020000000807070508030b000007070100000018323032332d30322d32345431333a34303a32322e3332385a07070100000024747a31625443473754415535523736356f4458694c4d63385a4537546a7376617868484807070100000024747a315377326d4641557a626b6d37646b47434472626542734a54547456374a4438457907070080dac409070700bdf892a1a291e196aa0503066c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542cd0497c3a107f10f180001543aa1803f0bbe2099809ab067dfa8a4cbc1c26a00ffff07617070726f76650000002d070701000000244b5431516f64676b5974754e79664a726a72673854515a586d64544643616d373268533900006c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542cd0498c3a107f70f090001543aa1803f0bbe2099809ab067dfa8a4cbc1c26a00ffff07617070726f766500000036070701000000244b5431516f64676b5974754e79664a726a72673854515a586d64544643616d373268533900bdf892a1a291e196aa056c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542e71599c3a107fabb01400001b1f0d7affc39861f7f5c75f917f683d2e9f55e3100ffff04737761700000009a070700000707000007070001070700bdf892a1a291e196aa05070700a3f683c2a6d80a07070100000018323032332d30322d32345431333a34303a32322e3332385a070705090100000024747a31625443473754415535523736356f4458694c4d63385a4537546a7376617868484805090100000024747a315377326d4641557a626b6d37646b47434472626542734a54547456374a443845796c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542cd049ac3a107f50f1b000193d22b59c496c94504729be1c671ec1d1d7a9cf000ffff107570646174655f6f70657261746f72730000005f020000005a050507070100000024747a31625443473754415535523736356f4458694c4d63385a4537546a73766178684848070701000000244b543147504a44546638475a73704363616e6147324b684d764775334e4a52717572617400006c00ad756cb46ba6f59efa8bd10ff544ba9d20d0954285109bc3a107a0820100000155959998da7e79231e2be8ed8ff373ac1b1574b000ffff0473776170000000a1070703060707020000000807070508030b000807070100000018323032332d30322d32345431333a34303a32322e3332385a07070100000024747a31625443473754415535523736356f4458694c4d63385a4537546a7376617868484807070100000024747a315377326d4641557a626b6d37646b47434472626542734a54547456374a44384579070700a3f683c2a6d80a070700a4f096bfbe9df6f0e00603066c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542cd049cc3a107ed0f00000193d22b59c496c94504729be1c671ec1d1d7a9cf000ffff107570646174655f6f70657261746f72730000005f020000005a050807070100000024747a31625443473754415535523736356f4458694c4d63385a4537546a73766178684848070701000000244b543147504a44546638475a73704363616e6147324b684d764775334e4a5271757261740000"); + + Proto::SigningInput input; + input.set_private_key(key.data(), key.size()); + input.set_encoded_operations(bytes.data(), bytes.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeTezos); + + EXPECT_EQ(hex(output.encoded()), "64aa7792af40de41371a72b3342daa7bf3d2b5a84511e9074341fdd52148dd9d6c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542850f96c3a1079d780080ade2040155959998da7e79231e2be8ed8ff373ac1b1574b000ffff04737761700000009e070703060707020000000807070508030b000007070100000018323032332d30322d32345431333a34303a32322e3332385a07070100000024747a31625443473754415535523736356f4458694c4d63385a4537546a7376617868484807070100000024747a315377326d4641557a626b6d37646b47434472626542734a54547456374a4438457907070080dac409070700bdf892a1a291e196aa0503066c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542cd0497c3a107f10f180001543aa1803f0bbe2099809ab067dfa8a4cbc1c26a00ffff07617070726f76650000002d070701000000244b5431516f64676b5974754e79664a726a72673854515a586d64544643616d373268533900006c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542cd0498c3a107f70f090001543aa1803f0bbe2099809ab067dfa8a4cbc1c26a00ffff07617070726f766500000036070701000000244b5431516f64676b5974754e79664a726a72673854515a586d64544643616d373268533900bdf892a1a291e196aa056c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542e71599c3a107fabb01400001b1f0d7affc39861f7f5c75f917f683d2e9f55e3100ffff04737761700000009a070700000707000007070001070700bdf892a1a291e196aa05070700a3f683c2a6d80a07070100000018323032332d30322d32345431333a34303a32322e3332385a070705090100000024747a31625443473754415535523736356f4458694c4d63385a4537546a7376617868484805090100000024747a315377326d4641557a626b6d37646b47434472626542734a54547456374a443845796c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542cd049ac3a107f50f1b000193d22b59c496c94504729be1c671ec1d1d7a9cf000ffff107570646174655f6f70657261746f72730000005f020000005a050507070100000024747a31625443473754415535523736356f4458694c4d63385a4537546a73766178684848070701000000244b543147504a44546638475a73704363616e6147324b684d764775334e4a52717572617400006c00ad756cb46ba6f59efa8bd10ff544ba9d20d0954285109bc3a107a0820100000155959998da7e79231e2be8ed8ff373ac1b1574b000ffff0473776170000000a1070703060707020000000807070508030b000807070100000018323032332d30322d32345431333a34303a32322e3332385a07070100000024747a31625443473754415535523736356f4458694c4d63385a4537546a7376617868484807070100000024747a315377326d4641557a626b6d37646b47434472626542734a54547456374a44384579070700a3f683c2a6d80a070700a4f096bfbe9df6f0e00603066c00ad756cb46ba6f59efa8bd10ff544ba9d20d09542cd049cc3a107ed0f00000193d22b59c496c94504729be1c671ec1d1d7a9cf000ffff107570646174655f6f70657261746f72730000005f020000005a050807070100000024747a31625443473754415535523736356f4458694c4d63385a4537546a73766178684848070701000000244b543147504a44546638475a73704363616e6147324b684d764775334e4a5271757261740000e10077fc3068aaaf1c7779e1dc2c396b3b40d73ddda04648bf4b16ac2e747c89b461771488e80da3aa30fc18c90de99fd358bfb76683f3c3ec250b1ee09b6d07"); +} + +TEST(TWAnySignerTezos, Sign) { + auto key = parse_hex("2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f"); + auto revealKey = parse_hex("311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10906f511cff95"); + + Proto::SigningInput input; + input.set_private_key(key.data(), key.size()); + auto& operations = *input.mutable_operation_list(); + operations.set_branch("BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"); + + auto& reveal = *operations.add_operations(); + auto& revealData = *reveal.mutable_reveal_operation_data(); + revealData.set_public_key(revealKey.data(), revealKey.size()); + reveal.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + reveal.set_fee(1272); + reveal.set_counter(30738); + reveal.set_gas_limit(10100); + reveal.set_storage_limit(257); + reveal.set_kind(Proto::Operation::REVEAL); + + auto& transaction = *operations.add_operations(); + auto& txData = *transaction.mutable_transaction_operation_data(); + txData.set_amount(1); + txData.set_destination("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + transaction.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + transaction.set_fee(1272); + transaction.set_counter(30739); + transaction.set_gas_limit(10100); + transaction.set_storage_limit(257); + transaction.set_kind(Proto::Operation::TRANSACTION); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeTezos); + + EXPECT_EQ(hex(output.encoded()), "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10906f511cff956c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80993f001f44e810201000081faa75f741ef614b0e35fcc8c90dfa3b0b95721000217034271b815e5f0c0a881342838ce49d7b48cdf507c72b1568c69a10db70c98774cdad1a74df760763e25f760ff13afcbbf3a1f2c833a0beeb9576a579c05"); +} + +TEST(TWAnySignerTezos, SignJSON) { + auto json = STRING(R"({"operationList": {"branch": "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp","operations": [{"source": "tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW","fee": 1272,"counter": 30738,"gasLimit": 10100,"storageLimit": 257,"kind": 107,"revealOperationData": {"publicKey": "QpqYbIBypAofOj4qtaWBm7Gy+2mZPFAEg3gVudxVkj4="}},{"source": "tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW","fee": 1272,"counter": 30739,"gasLimit": 10100,"storageLimit": 257,"kind": 108,"transactionOperationData": {"destination": "tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW","amount": 1}}]}})"); + auto key = DATA("2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f"); + auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeTezos)); + + ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeTezos)); + assertStringsEqual(result, "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80992f001f44e810200429a986c8072a40a1f3a3e2ab5a5819bb1b2fb69993c5004837815b9dc55923e6c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80993f001f44e810201000081faa75f741ef614b0e35fcc8c90dfa3b0b957210001b86398d5b9be737dca8e4106ea18d70e69b75e92f892fb283546a99152b8d7794b919c0fbf1c31de386069a60014491c0e7505adef5781cead1cfe6608030b"); +} + +} // namespace TW::Tezos::tests diff --git a/tests/chains/Tezos/TWCoinTypeTests.cpp b/tests/chains/Tezos/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..50e2e1baea1 --- /dev/null +++ b/tests/chains/Tezos/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWTezosCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTezos)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("onk3Z6V4StyfiXTPSHwZFvTKVAaws37cHmZacmULPr3VbVHpKrg")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTezos, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("tz1SiPXX4MYGNJNDsRc7n8hkvUqFzg8xqF9m")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTezos, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTezos)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTezos)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTezos), 6); + ASSERT_EQ(TWBlockchainTezos, TWCoinTypeBlockchain(TWCoinTypeTezos)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTezos)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTezos)); + assertStringsEqual(symbol, "XTZ"); + assertStringsEqual(txUrl, "https://tzstats.com/onk3Z6V4StyfiXTPSHwZFvTKVAaws37cHmZacmULPr3VbVHpKrg"); + assertStringsEqual(accUrl, "https://tzstats.com/tz1SiPXX4MYGNJNDsRc7n8hkvUqFzg8xqF9m"); + assertStringsEqual(id, "tezos"); + assertStringsEqual(name, "Tezos"); +} diff --git a/tests/chains/Tezos/TransactionCompilerTests.cpp b/tests/chains/Tezos/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..56f3ec7a008 --- /dev/null +++ b/tests/chains/Tezos/TransactionCompilerTests.cpp @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Tezos.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TezosCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeTezos; + + /// Step 1: Prepare transaction input (protobuf) + auto privateKey = + PrivateKey(parse_hex("2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f")); + auto publicKey = privateKey.getPublicKey(::publicKeyType(coin)); + auto revealKey = parse_hex("311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10906f511cff95"); + + TW::Tezos::Proto::SigningInput input; + auto& operations = *input.mutable_operation_list(); + operations.set_branch("BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp"); + + auto& reveal = *operations.add_operations(); + auto& revealData = *reveal.mutable_reveal_operation_data(); + revealData.set_public_key(revealKey.data(), revealKey.size()); + reveal.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + reveal.set_fee(1272); + reveal.set_counter(30738); + reveal.set_gas_limit(10100); + reveal.set_storage_limit(257); + reveal.set_kind(Tezos::Proto::Operation::REVEAL); + + auto& transaction = *operations.add_operations(); + auto& txData = *transaction.mutable_transaction_operation_data(); + txData.set_amount(1); + txData.set_destination("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + transaction.set_source("tz1XVJ8bZUXs7r5NV8dHvuiBhzECvLRLR3jW"); + transaction.set_fee(1272); + transaction.set_counter(30739); + transaction.set_gas_limit(10100); + transaction.set_storage_limit(257); + transaction.set_kind(Tezos::Proto::Operation::TRANSACTION); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "12e4f8b17ad3b316a5a56960db76c7d6505dbf2fff66106be75c8d6753daac0e"); + + auto signature = parse_hex("0217034271b815e5f0c0a881342838ce49d7b48cdf507c72b1568c69a10db70c987" + "74cdad1a74df760763e25f760ff13afcbbf3a1f2c833a0beeb9576a579c05"); + + /// Step 3: Compile transaction info + const auto tx = + "3756ef37b1be849e3114643f0aa5847cabf9a896d3bfe4dd51448de68e91da016b0081faa75f741ef614b0e35f" + "cc8c90dfa3b0b95721f80992f001f44e810200311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10" + "906f511cff956c0081faa75f741ef614b0e35fcc8c90dfa3b0b95721f80993f001f44e810201000081faa75f74" + "1ef614b0e35fcc8c90dfa3b0b95721000217034271b815e5f0c0a881342838ce49d7b48cdf507c72b1568c69a1" + "0db70c98774cdad1a74df760763e25f760ff13afcbbf3a1f2c833a0beeb9576a579c05"; + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {}); + + { + TW::Tezos::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), tx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::Tezos::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + TW::Tezos::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), tx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {}); + Tezos::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); + } +} \ No newline at end of file diff --git a/tests/chains/TheOpenNetwork/TWAnyAddressTests.cpp b/tests/chains/TheOpenNetwork/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..6a7fc5a7a5d --- /dev/null +++ b/tests/chains/TheOpenNetwork/TWAnyAddressTests.cpp @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Data.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::TheOpenNetwork::tests { + +TEST(TWTheOpenNetwork, Address) { + const auto mnemonic = STRING("stuff diamond cycle federal scan spread pigeon people engage teach snack grain"); + const auto passphrase = STRING(""); + + const auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(mnemonic.get(), passphrase.get())); + + const auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypeTON, WRAPS(TWCoinTypeDerivationPath(TWCoinTypeTON)).get())); + const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey.get())); + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeTON)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + + assertStringsEqual(addressStr, "UQDYW_1eScJVxtitoBRksvoV9cCYo4uKGWLVNIHB1JqRRyQx"); +} + +TEST(TWTheOpenNetwork, AddressValidate) { + auto string = STRING("EQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorkdl"); + + ASSERT_TRUE(TWAnyAddressIsValid(string.get(), TWCoinTypeTON)); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeTON)); + + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "8a8627861a5dd96c9db3ce0807b122da5ed473934ce7568a5b4b1c361cbb28ae"); + + auto normalized = WRAPS(TWAnyAddressDescription(addr.get())); + assertStringsEqual(normalized, "UQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorhqg"); +} + +} // namespace TW::TheOpenNetwork::tests diff --git a/tests/chains/TheOpenNetwork/TWAnySignerTests.cpp b/tests/chains/TheOpenNetwork/TWAnySignerTests.cpp new file mode 100644 index 00000000000..ffd5d276a78 --- /dev/null +++ b/tests/chains/TheOpenNetwork/TWAnySignerTests.cpp @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" + +#include "proto/TheOpenNetwork.pb.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::TheOpenNetwork::tests { + +TEST(TWAnySignerTheOpenNetwork, SignMessageToTransferAndDeployWalletV4R2) { + Proto::SigningInput input; + + auto& transfer = *input.add_messages(); + transfer.set_dest("EQDYW_1eScJVxtitoBRksvoV9cCYo4uKGWLVNIHB1JqRR3n0"); + transfer.set_amount(10); + transfer.set_mode(Proto::SendMode::PAY_FEES_SEPARATELY | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS); + transfer.set_bounceable(true); + + const auto privateKey = parse_hex("63474e5fe9511f1526a50567ce142befc343e71a49b865ac3908f58667319cb8"); + input.set_private_key(privateKey.data(), privateKey.size()); + + input.set_expire_at(1671135440); + + input.set_wallet_version(Proto::WALLET_V4_R2); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeTON); + + // The same Cell can be BoC encoded differently. + // This encoded BoC equals to: + // te6ccgICABoAAQAAA8sAAAJFiADN98eLgHfrkE8l8gmT8X5REpTVR6QnqDhArTbKlVvbZh4ABAABAZznxvGBhoRXhPogxNY8QmHlihJWxg5t6KptqcAIZlVks1r+Z+r1avCWNCeqeLC/oaiVN4mDx/E1+Zhi33G25rcIKamjF/////8AAAAAAAMAAgFiYgBsLf6vJOEq42xW0AoyWX0K+uBMUcXFDLFqmkDg6k1Io4hQAAAAAAAAAAAAAAAAAQADAAACATQABgAFAFEAAAAAKamjF/Qsd/kxvqIOxdAVBzEna7suKGCUdmEkWyMZ74Ez7o1BQAEU/wD0pBP0vPLICwAHAgEgAA0ACAT48oMI1xgg0x/TH9MfAvgju/Jk7UTQ0x/TH9P/9ATRUUO68qFRUbryogX5AVQQZPkQ8qP4ACSkyMsfUkDLH1Iwy/9SEPQAye1U+A8B0wchwACfbFGTINdKltMH1AL7AOgw4CHAAeMAIcAC4wABwAORMOMNA6TIyx8Syx/L/wAMAAsACgAJAAr0AMntVABsgQEI1xj6ANM/MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAHCBAQjXGPoA0z/IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBu0gf6ANTUIvkABcjKBxXL/8nQd3SAGMjLBcsCIs8WUAX6AhTLaxLMzMlz+wDIQBSBAQj0UfKnAgIBSAAXAA4CASAAEAAPAFm9JCtvaiaECAoGuQ+gIYRw1AgIR6STfSmRDOaQPp/5g3gSgBt4EBSJhxWfMYQCASAAEgARABG4yX7UTQ1wsfgCAVgAFgATAgEgABUAFAAZrx32omhAEGuQ64WPwAAZrc52omhAIGuQ64X/wAA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYALm0AHQ0wMhcbCSXwTgItdJwSCSXwTgAtMfIYIQcGx1Z70ighBkc3RyvbCSXwXgA/pAMCD6RAHIygfL/8nQ7UTQgQFA1yH0BDBcgQEI9ApvoTGzkl8H4AXTP8glghBwbHVnupI4MOMNA4IQZHN0crqSXwbjDQAZABgAilAEgQEI9Fkw7UTQgQFA1yDIAc8W9ADJ7VQBcrCOI4IQZHN0coMesXCAGFAFywVQA88WI/oCE8tqyx/LP8mAQPsAkl8D4gB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+gIZ9ADLaRfLH1Jgyz8gyYBA+wAG + ASSERT_EQ(output.encoded(), "te6cckECGgEAA7IAAkWIAM33x4uAd+uQTyXyCZPxflESlNVHpCeoOECtNsqVW9tmHgECAgE0AwQBnOfG8YGGhFeE+iDE1jxCYeWKElbGDm3oqm2pwAhmVWSzWv5n6vVq8JY0J6p4sL+hqJU3iYPH8TX5mGLfcbbmtwgpqaMX/////wAAAAAAAwUBFP8A9KQT9LzyyAsGAFEAAAAAKamjF/Qsd/kxvqIOxdAVBzEna7suKGCUdmEkWyMZ74Ez7o1BQAFiYgBsLf6vJOEq42xW0AoyWX0K+uBMUcXFDLFqmkDg6k1Io4hQAAAAAAAAAAAAAAAAAQcCASAICQAAAgFICgsE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8MDQ4PAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNEBECASASEwBu0gf6ANTUIvkABcjKBxXL/8nQd3SAGMjLBcsCIs8WUAX6AhTLaxLMzMlz+wDIQBSBAQj0UfKnAgBwgQEI1xj6ANM/yFQgR4EBCPRR8qeCEG5vdGVwdIAYyMsFywJQBs8WUAT6AhTLahLLH8s/yXP7AAIAbIEBCNcY+gDTPzBSJIEBCPRZ8qeCEGRzdHJwdIAYyMsFywJQBc8WUAP6AhPLassfEss/yXP7AAAK9ADJ7VQAeAH6APQEMPgnbyIwUAqhIb7y4FCCEHBsdWeDHrFwgBhQBMsFJs8WWPoCGfQAy2kXyx9SYMs/IMmAQPsABgCKUASBAQj0WTDtRNCBAUDXIMgBzxb0AMntVAFysI4jghBkc3Rygx6xcIAYUAXLBVADzxYj+gITy2rLH8s/yYBA+wCSXwPiAgEgFBUAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAIBWBYXABG4yX7UTQ1wsfgAPbKd+1E0IEBQNch9AQwAsjKB8v/ydABgQEI9ApvoTGACASAYGQAZrc52omhAIGuQ64X/wAAZrx32omhAEGuQ64WPwJiaP4Q="); +} + +TEST(TWAnySignerTheOpenNetwork, SignMessageToTransferAndDeployWalletV5R1) { + Proto::SigningInput input; + + auto& transfer = *input.add_messages(); + transfer.set_dest("EQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQfBu"); + transfer.set_amount(10); + transfer.set_mode(Proto::SendMode::PAY_FEES_SEPARATELY | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS); + transfer.set_bounceable(true); + + const auto privateKey = parse_hex("3570e35f54cfb843f2cfaf2b8cae7ceeb7b32225d7dbbd86f611056d74d9073e"); + input.set_private_key(privateKey.data(), privateKey.size()); + + input.set_expire_at(0xffffffff); + + input.set_wallet_version(Proto::WALLET_V5_R1); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeTON); + + // Successfully broadcasted: https://tonviewer.com/transaction/Q32uRBqVprzNzc6iVgwxPeJPE92Fx21dfsqrHnCh5Ss= + ASSERT_EQ(hex(output.hash()), "437dae441a95a6bccdcdcea2560c313de24f13dd85c76d5d7ecaab1e70a1e52b"); + ASSERT_EQ(output.encoded(), "te6cckECGwEAA2sAAkWIACm9HPyVOpjCNOG6nbf+EwCONRHHpeMQsIlCoWNKhUaaHgECAgE0AwQBoXNpZ25///8R/////wAAAACACOfqY7L3l3aKc58eNxuJTaeH/fgBw2aG0coM+hjDpjWhJKbYKmsAD8v054HYSuO6vN3bQnV5U19BhsGfe1MDoAUBFP8A9KQT9LzyyAsGAFGAAAAAP///iKrcG+d35KaMMtuxik4jqNofFL51Mu1f8Qf19onqsDlyIAIKDsPIbQMWBwIBIAgJAWJiAC90HaFSSy94Zxb85WYu97uTKIxAlLvolycpcYkWc+8giFAAAAAAAAAAAAAAAAABFgIBSAoLAQLyDALc0CDXScEgkVuPYyDXCx8gghBleHRuvSGCEHNpbnS9sJJfA+CCEGV4dG66jrSAINchAdB01yH6QDD6RPgo+kQwWL2RW+DtRNCBAUHXIfQFgwf0Dm+hMZEw4YBA1yFwf9s84DEg10mBAoC5kTDgcOIXDQIBIA4PAR4g1wsfghBzaWduuvLgin8NAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYFwIBIBARABm+Xw9qJoQICg65D6AsAgFuEhMCAUgUFQAZrc52omhAIOuQ64X/wAAZrx32omhAEOuQ64WPwAAXsyX7UTQcdch1wsfgABGyYvtRNDXCgCAAAAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKGBkaAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNB8Ui06"); +} + +} // namespace TW::TheOpenNetwork::tests + diff --git a/tests/chains/TheOpenNetwork/TWCoinTypeTests.cpp b/tests/chains/TheOpenNetwork/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..c35df437493 --- /dev/null +++ b/tests/chains/TheOpenNetwork/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include + +#include "TestUtilities.h" +#include + + +TEST(TWTONCoinType, TWCoinType) { + const auto coin = TWCoinTypeTON; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("fJXfn0EVhV09HFuEgUHu4Cchb24nUQtIMwSzmzk2tLs=")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "ton"); + assertStringsEqual(name, "TON"); + assertStringsEqual(symbol, "TON"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 9); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainTheOpenNetwork); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(txUrl, "https://tonviewer.com/transaction/fJXfn0EVhV09HFuEgUHu4Cchb24nUQtIMwSzmzk2tLs="); + assertStringsEqual(accUrl, "https://tonviewer.com/EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N"); +} diff --git a/tests/chains/TheOpenNetwork/TWTONAddressConverterTests.cpp b/tests/chains/TheOpenNetwork/TWTONAddressConverterTests.cpp new file mode 100644 index 00000000000..01113313ce8 --- /dev/null +++ b/tests/chains/TheOpenNetwork/TWTONAddressConverterTests.cpp @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include "TrustWalletCore/TWTONAddressConverter.h" + +namespace TW::TheOpenNetwork::tests { + +TEST(TWTONAddressConverter, GetJettonNotcoinAddress) { + auto mainAddress = STRING("UQBjKqthWBE6GEcqb_epTRFrQ1niS6Z1Z1MHMwR-mnAYRoYr"); + auto addressBocEncoded = WRAPS(TWTONAddressConverterToBoc(mainAddress.get())); + assertStringsEqual(addressBocEncoded, "te6cckEBAQEAJAAAQ4AMZVVsKwInQwjlTf71KaItaGs8SXTOrOpg5mCP004DCNAptHQU"); + + // curl --location 'https://toncenter.com/api/v2/runGetMethod' --header 'Content-Type: application/json' --data \ + // '{"address":"EQAvlWFDxGF2lXm67y4yzC17wYKD9A0guwPkMs1gOsM__NOT","method":"get_wallet_address","method":"get_wallet_address","stack":[["tvm.Slice","te6ccgICAAEAAQAAACQAAABDgAxlVWwrAidDCOVN/vUpoi1oazxJdM6s6mDmYI/TTgMI0A=="]]}' + + // `get_wallet_address` response: + auto jettonAddressBocEncoded = STRING("te6cckEBAQEAJAAAQ4AFvT5rqwxcbKfITqnkwL+go4Zi9bulRHAtLt4cjjFdK7B8L+Cq"); + auto jettonAddress = WRAPS(TWTONAddressConverterFromBoc(jettonAddressBocEncoded.get())); + assertStringsEqual(jettonAddress, "UQAt6fNdWGLjZT5CdU8mBf0FHDMXrd0qI4FpdvDkcYrpXV5H"); +} + +TEST(TWTONAddressConverter, GetJettonUSDTAddress) { + auto mainAddress = STRING("UQBjKqthWBE6GEcqb_epTRFrQ1niS6Z1Z1MHMwR-mnAYRoYr"); + auto addressBocEncoded = WRAPS(TWTONAddressConverterToBoc(mainAddress.get())); + assertStringsEqual(addressBocEncoded, "te6cckEBAQEAJAAAQ4AMZVVsKwInQwjlTf71KaItaGs8SXTOrOpg5mCP004DCNAptHQU"); + + // curl --location 'https://toncenter.com/api/v2/runGetMethod' --header 'Content-Type: application/json' --data \ + // '{"address":"EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs","method":"get_wallet_address","method":"get_wallet_address","stack":[["tvm.Slice","te6ccgICAAEAAQAAACQAAABDgAxlVWwrAidDCOVN/vUpoi1oazxJdM6s6mDmYI/TTgMI0A=="]]}' + + // `get_wallet_address` response: + auto jettonAddressBocEncoded = STRING("te6cckEBAQEAJAAAQ4Aed71FEI46jdFXghsGUIG2GIR8wpbQaLzrKNj7BtHOEHBSO5Mf"); + auto jettonAddress = WRAPS(TWTONAddressConverterFromBoc(jettonAddressBocEncoded.get())); + assertStringsEqual(jettonAddress, "UQDzveoohHHUboq8ENgyhA2wxCPmFLaDRedZRsfYNo5wg4TL"); +} + +TEST(TWTONAddressConverter, GetJettonStonAddress) { + auto mainAddress = STRING("EQATQPeCwtMzQ9u54nTjUNcK4n_0VRSxPOOROLf_IE0OU3XK"); + auto addressBocEncoded = WRAPS(TWTONAddressConverterToBoc(mainAddress.get())); + assertStringsEqual(addressBocEncoded, "te6cckEBAQEAJAAAQ4ACaB7wWFpmaHt3PE6cahrhXE/+iqKWJ5xyJxb/5AmhynDu6Ygj"); + + // curl --location 'https://toncenter.com/api/v2/runGetMethod' --header 'Content-Type: application/json' --data \ + // '{"address":"EQA2kCVNwVsil2EM2mB0SkXytxCqQjS4mttjDpnXmwG9T6bO","method":"get_wallet_address","method":"get_wallet_address","stack":[["tvm.Slice","te6ccgICAAEAAQAAACQAAABDgAxlVWwrAidDCOVN/vUpoi1oazxJdM6s6mDmYI/TTgMI0A=="]]}' + + // `get_wallet_address` response: + auto jettonAddressBocEncoded = STRING("te6cckEBAQEAJAAAQ4ALPu0dyA1gHd3r7J1rxlvhXSvT5y3rokMDMiCQ86TsUJDnt69H"); + auto jettonAddress = WRAPS(TWTONAddressConverterFromBoc(jettonAddressBocEncoded.get())); + assertStringsEqual(jettonAddress, "UQBZ92juQGsA7u9fZOteMt8K6V6fOW9dEhgZkQSHnSdihHPH"); +} + +TEST(TWTONAddressConverter, FromBocNullAddress) { + auto jettonAddressBocEncoded = STRING("te6cckEBAQEAAwAAASCUQYZV"); + auto jettonAddress = WRAPS(TWTONAddressConverterFromBoc(jettonAddressBocEncoded.get())); + assertStringsEqual(jettonAddress, "UQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJKZ"); +} + +TEST(TWTONAddressConverter, FromBocError) { + // No type bit. + auto boc1 = STRING("te6cckEBAQEAAwAAAcCO6ba2"); + ASSERT_EQ(TWTONAddressConverterFromBoc(boc1.get()), nullptr); + + // No res1 and workchain bits. + auto boc2 = STRING("te6cckEBAQEAAwAAAaDsenDX"); + ASSERT_EQ(TWTONAddressConverterFromBoc(boc2.get()), nullptr); + + // Incomplete hash (31 bytes instead of 32). + auto boc3 = STRING("te6cckEBAQEAIwAAQYAgQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAUGJnJWk="); + ASSERT_EQ(TWTONAddressConverterFromBoc(boc3.get()), nullptr); + + // Expected 267 bits, found 268. + auto boc4 = STRING("te6cckEBAQEAJAAAQ4AgQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEgGG0Gq"); + ASSERT_EQ(TWTONAddressConverterFromBoc(boc4.get()), nullptr); +} + +TEST(TWTONAddressConverter, ToUserFriendly) { + auto rawAddress = "0:8a8627861a5dd96c9db3ce0807b122da5ed473934ce7568a5b4b1c361cbb28ae"; + auto bounceable = "EQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorkdl"; + auto nonBounceable = "UQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorhqg"; + auto bounceableTestnet = "kQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorvzv"; + auto nonBounceableTestnet = "0QCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorqEq"; + + // Raw to user friendly. + assertStringsEqual( + WRAPS(TWTONAddressConverterToUserFriendly(STRING(rawAddress).get(), true, false)), + bounceable + ); + assertStringsEqual( + WRAPS(TWTONAddressConverterToUserFriendly(STRING(rawAddress).get(), false, false)), + nonBounceable + ); + assertStringsEqual( + WRAPS(TWTONAddressConverterToUserFriendly(STRING(rawAddress).get(), true, true)), + bounceableTestnet + ); + assertStringsEqual( + WRAPS(TWTONAddressConverterToUserFriendly(STRING(rawAddress).get(), false, true)), + nonBounceableTestnet + ); + + // Bounceable to non-bounceable. + assertStringsEqual( + WRAPS(TWTONAddressConverterToUserFriendly(STRING(bounceable).get(), false, false)), + nonBounceable + ); + + // Non-bounceable to bounceable. + assertStringsEqual( + WRAPS(TWTONAddressConverterToUserFriendly(STRING(nonBounceable).get(), true, false)), + bounceable + ); + + // Non-bounceable to non-bounceable. + assertStringsEqual( + WRAPS(TWTONAddressConverterToUserFriendly(STRING(nonBounceable).get(), false, false)), + nonBounceable + ); +} + +TEST(TWTONAddressConverter, ToUserFriendlyError) { + // No "0:" prefix. + auto invalid1 = STRING("8a8627861a5dd96c9db3ce0807b122da5ed473934ce7568a5b4b1c361cbb28ae"); + ASSERT_EQ(TWTONAddressConverterToUserFriendly(invalid1.get(), true, false), nullptr); + + // Too short. + auto invalid2 = STRING("EQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsor"); + ASSERT_EQ(TWTONAddressConverterToUserFriendly(invalid1.get(), false, false), nullptr); +} + +} // namespace TW::TheOpenNetwork::tests diff --git a/tests/chains/TheOpenNetwork/TWTONMessageSignerTests.cpp b/tests/chains/TheOpenNetwork/TWTONMessageSignerTests.cpp new file mode 100644 index 00000000000..fd9367dffd7 --- /dev/null +++ b/tests/chains/TheOpenNetwork/TWTONMessageSignerTests.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include "HexCoding.h" +#include "TrustWalletCore/TWTONMessageSigner.h" + +namespace TW::TheOpenNetwork::tests { + +TEST(TWTONMessageSigner, SignMessage) { + const auto privateKeyBytes = DATA("112d4e2e700a468f1eae699329202f1ee671d6b665caa2d92dea038cf3868c18"); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(privateKeyBytes.get())); + const auto message = STRING("Hello world"); + + const auto signature = WRAPS(TWTONMessageSignerSignMessage(privateKey.get(), message.get())); + assertStringsEqual(signature, "2490fbaa72aec0b77b19162bbbe0b0e3f7afd42cc9ef469f0494cd4a366a4bf76643300cd5991f66bce6006336742b8d1d435d541d244dcc013d428472e89504"); +} + +} // namespace TW::TheOpenNetwork::tests diff --git a/tests/chains/TheOpenNetwork/TWTONWalletTests.cpp b/tests/chains/TheOpenNetwork/TWTONWalletTests.cpp new file mode 100644 index 00000000000..b346ee5568b --- /dev/null +++ b/tests/chains/TheOpenNetwork/TWTONWalletTests.cpp @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include "HexCoding.h" +#include "TrustWalletCore/TWTONWallet.h" + +namespace TW::TheOpenNetwork::tests { + +TEST(TWTONWallet, BuildV4R2StateInit) { + auto publicKeyBytes = DATA("f229a9371fa7c2108b3d90ea22c9be705ff5d0cfeaee9cbb9366ff0171579357"); + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(publicKeyBytes.get(), TWPublicKeyTypeED25519)); + + const int32_t baseWorkchain = 0; + const int32_t defaultWalletId = 0x29a9a317; + const auto stateInit = WRAPS(TWTONWalletBuildV4R2StateInit(publicKey.get(), baseWorkchain, defaultWalletId)); + assertStringsEqual(stateInit, "te6cckECFgEAAwQAAgE0AQIBFP8A9KQT9LzyyAsDAFEAAAAAKamjF/IpqTcfp8IQiz2Q6iLJvnBf9dDP6u6cu5Nm/wFxV5NXQAIBIAQFAgFIBgcE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8ICQoLAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNDA0CASAODwBu0gf6ANTUIvkABcjKBxXL/8nQd3SAGMjLBcsCIs8WUAX6AhTLaxLMzMlz+wDIQBSBAQj0UfKnAgBwgQEI1xj6ANM/yFQgR4EBCPRR8qeCEG5vdGVwdIAYyMsFywJQBs8WUAT6AhTLahLLH8s/yXP7AAIAbIEBCNcY+gDTPzBSJIEBCPRZ8qeCEGRzdHJwdIAYyMsFywJQBc8WUAP6AhPLassfEss/yXP7AAAK9ADJ7VQAeAH6APQEMPgnbyIwUAqhIb7y4FCCEHBsdWeDHrFwgBhQBMsFJs8WWPoCGfQAy2kXyx9SYMs/IMmAQPsABgCKUASBAQj0WTDtRNCBAUDXIMgBzxb0AMntVAFysI4jghBkc3Rygx6xcIAYUAXLBVADzxYj+gITy2rLH8s/yYBA+wCSXwPiAgEgEBEAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAIBWBITABG4yX7UTQ1wsfgAPbKd+1E0IEBQNch9AQwAsjKB8v/ydABgQEI9ApvoTGACASAUFQAZrc52omhAIGuQ64X/wAAZrx32omhAEGuQ64WPwEXtMkg="); +} + +} // namespace TW::TheOpenNetwork::tests diff --git a/tests/chains/Theta/SignerTests.cpp b/tests/chains/Theta/SignerTests.cpp new file mode 100644 index 00000000000..295ee5fc98d --- /dev/null +++ b/tests/chains/Theta/SignerTests.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Theta/Signer.h" + +#include + +namespace TW::Theta { + +using boost::multiprecision::uint256_t; + +TEST(Signer, Sign) { + const auto pkFrom = + PrivateKey(parse_hex("0x93a90ea508331dfdf27fb79757d4250b4e84954927ba0073cd67454ac432c737")); + const auto from = Ethereum::Address("0x2E833968E5bB786Ae419c4d13189fB081Cc43bab"); + const auto to = Ethereum::Address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); + auto transaction = Transaction(from, to, 10, 20, 1); + + auto signer = Signer("privatenet"); + auto signature = signer.sign(pkFrom, transaction); + transaction.setSignature(from, signature); + + ASSERT_EQ(hex(signature), "5190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8" + "fe9134ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501"); + ASSERT_EQ(hex(transaction.encodePayload()), + "02f887c78085e8d4a51000f863f861942e833968e5bb786ae419c4d13189fb081cc43babc70a85e8d4a5" + "101401b8415190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134" + "ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501d9d8949f1233798e905e17356007" + "1255140b4a8abd3ec6c20a14"); +} + +} // namespace TW::Theta diff --git a/tests/chains/Theta/TWAnySignerTests.cpp b/tests/chains/Theta/TWAnySignerTests.cpp new file mode 100644 index 00000000000..07d544c0a50 --- /dev/null +++ b/tests/chains/Theta/TWAnySignerTests.cpp @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/Theta.pb.h" +#include "uint256.h" +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::Theta::tests { + +TEST(TWAnySignerTheta, Sign) { + auto privateKey = parse_hex("93a90ea508331dfdf27fb79757d4250b4e84954927ba0073cd67454ac432c737"); + + Proto::SigningInput input; + input.set_chain_id("privatenet"); + input.set_to_address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); + auto amount = store(uint256_t(10)); + input.set_theta_amount(amount.data(), amount.size()); + auto tfuelAmount = store(uint256_t(20)); + input.set_tfuel_amount(tfuelAmount.data(), tfuelAmount.size()); + auto fee = store(uint256_t(1000000000000)); + input.set_fee(fee.data(), fee.size()); + input.set_sequence(1); + input.set_private_key(privateKey.data(), privateKey.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeTheta); + + ASSERT_EQ(hex(output.encoded()), "02f887c78085e8d4a51000f863f861942e833968e5bb786ae419c4d13189fb081cc43babc70a85e8d4a5101401b8415190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501d9d8949f1233798e905e173560071255140b4a8abd3ec6c20a14"); +} + +} // namespace TW::Thetha::tests diff --git a/tests/chains/Theta/TWCoinTypeTests.cpp b/tests/chains/Theta/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..595dfe4172e --- /dev/null +++ b/tests/chains/Theta/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWThetaCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTheta)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTheta, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTheta, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTheta)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTheta)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTheta), 18); + ASSERT_EQ(TWBlockchainTheta, TWCoinTypeBlockchain(TWCoinTypeTheta)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTheta)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTheta)); + assertStringsEqual(symbol, "THETA"); + assertStringsEqual(txUrl, "https://explorer.thetatoken.org/txs/t123"); + assertStringsEqual(accUrl, "https://explorer.thetatoken.org/account/a12"); + assertStringsEqual(id, "theta"); + assertStringsEqual(name, "Theta"); +} diff --git a/tests/chains/Theta/TransactionCompilerTests.cpp b/tests/chains/Theta/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..fd35e22f406 --- /dev/null +++ b/tests/chains/Theta/TransactionCompilerTests.cpp @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" +#include "uint256.h" + +#include "proto/Theta.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(ThetaCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeTheta; + /// Step 1: Prepare transaction input (protobuf) + const auto pkFrom = + PrivateKey(parse_hex("0x93a90ea508331dfdf27fb79757d4250b4e84954927ba0073cd67454ac432c737")); + const auto publicKey = pkFrom.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + TW::Theta::Proto::SigningInput input; + input.set_chain_id("privatenet"); + input.set_to_address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); + auto amount = store(uint256_t(10)); + input.set_theta_amount(amount.data(), amount.size()); + auto tfuelAmount = store(uint256_t(20)); + input.set_tfuel_amount(tfuelAmount.data(), tfuelAmount.size()); + auto fee = store(uint256_t(1000000000000)); + input.set_fee(fee.data(), fee.size()); + input.set_sequence(1); + std::string pubkeyStr(publicKey.bytes.begin(), publicKey.bytes.end()); + input.set_public_key(pubkeyStr); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, inputStrData); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + EXPECT_EQ(hex(preSigningOutput.data_hash()), + "2dc419e9919e65f129453419dc72a6bee99b2281dfddf754807a5c212ae35678"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = publicKey.bytes; + const auto signature = + parse_hex("5190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134ccdef0" + "53ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preSigningOutput.data_hash()))); + + /// Step 3: Compile transaction info + auto expectedTx = "02f887c78085e8d4a51000f863f861942e833968e5bb786ae419c4d13189fb081cc43babc70a" + "85e8d4a5101401b8415190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb" + "7fff267cb8fe9134ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501d9d8" + "949f1233798e905e173560071255140b4a8abd3ec6c20a14"; + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); + + { + Theta::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), expectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Theta::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(pkFrom.bytes.data(), pkFrom.bytes.size()); + + Theta::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), expectedTx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKeyData}); + Theta::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } +} diff --git a/tests/chains/Theta/TransactionTests.cpp b/tests/chains/Theta/TransactionTests.cpp new file mode 100644 index 00000000000..7cebeb254b2 --- /dev/null +++ b/tests/chains/Theta/TransactionTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Theta/Transaction.h" + +#include "HexCoding.h" + +#include + +namespace TW::Theta::tests { + +TEST(ThetaTransaction, EncodePayload) { + const auto from = Ethereum::Address("0x2E833968E5bB786Ae419c4d13189fB081Cc43bab"); + const auto to = Ethereum::Address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); + auto transaction = Transaction(from, to, 10, 20, 1); + ASSERT_EQ(hex(transaction.encodePayload()), + "02f843c78085e8d4a51000e0df942e833968e5bb786ae419c4d13189fb081cc43babc70a85e8d4a51014" + "0180d9d8949f1233798e905e173560071255140b4a8abd3ec6c20a14"); +} + +TEST(ThetaTransaction, EncodePayloadWithSignature) { + const auto from = Ethereum::Address("0x2E833968E5bB786Ae419c4d13189fB081Cc43bab"); + const auto to = Ethereum::Address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); + auto transaction = Transaction(from, to, 10, 20, 1); + transaction.setSignature( + from, parse_hex("5190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134" + "ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501")); + ASSERT_EQ(hex(transaction.encodePayload()), + "02f887c78085e8d4a51000f863f861942e833968e5bb786ae419c4d13189fb081cc43babc70a85e8d4a5" + "101401b8415190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134" + "ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501d9d8949f1233798e905e17356007" + "1255140b4a8abd3ec6c20a14"); +} + +} // namespace TW::Theta::tests diff --git a/tests/chains/ThetaFuel/TWAnySignerTests.cpp b/tests/chains/ThetaFuel/TWAnySignerTests.cpp new file mode 100644 index 00000000000..6650ab26722 --- /dev/null +++ b/tests/chains/ThetaFuel/TWAnySignerTests.cpp @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include "HexCoding.h" +#include "uint256.h" +#include "proto/Ethereum.pb.h" + +#include + +namespace TW::ThetaFuel::tests { + +/// Successfully broadcasted: +/// https://explorer.thetatoken.org/txs/0x0e7b0642f89855bf591d094cb7648c325fcef669add66dd273c4e16170fbca01 +TEST(TWAnySignerThetaFuel, TfuelTransfer) { + auto chainId = store(uint256_t(361)); + auto nonce = store(uint256_t(5)); + auto gasLimit = store(uint256_t(79883)); + auto gasPrice = store(uint256_t(4000000000000)); // 0.000004 + auto toAddress = "0x8dbD6c7Ede90646a61Bbc649831b7c298BFd37A0"; + auto amount = uint256_t(100000000000000000); // 0.1 + auto amountData = store(amount); + auto key = parse_hex("0xc99dd0045dff0c1594c383658c07b4b75f39b90af7f8b592d1a7b461e03cc34b"); + + Ethereum::Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_to_address(toAddress); + input.set_private_key(key.data(), key.size()); + auto &transfer = *input.mutable_transaction()->mutable_transfer(); + transfer.set_amount(amountData.data(), amountData.size()); + + // sign test + Ethereum::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeThetaFuel); + + ASSERT_EQ(hex(output.encoded()), + "f870058603a3529440008301380b948dbd6c7ede90646a61bbc649831b7c298bfd37a088016345785d8a0000808202f5a0b1857121d66a484798ad0cd0fed0e205ee2e1f7f7f60b45cf84a2dbeb25c8c9fa06ffedd5df33a38f7de958c2800482432b6a8546913fc145f2615cc93f7a7647d"); +} + +/// Successfully broadcasted: +/// https://explorer.thetatoken.org/txs/0x2c38163d84f031d4276dedc4e4424a6443208f7b22e1bfe6fd2ba0f607af5100 +TEST(TWAnySignerThetaFuel, TdropTokenTransfer) { + auto chainId = store(uint256_t(361)); + auto nonce = store(uint256_t(4)); + auto gasLimit = store(uint256_t(79883)); + auto gasPrice = store(uint256_t(4000000000000)); // 0.000004 + auto toAddress = "0x8dbD6c7Ede90646a61Bbc649831b7c298BFd37A0"; + auto token = "0x1336739b05c7ab8a526d40dcc0d04a826b5f8b03"; // TDROP + auto amount = uint256_t(4000000000000000000); // 4 TDROP + auto amountData = store(amount); + auto key = parse_hex("0xc99dd0045dff0c1594c383658c07b4b75f39b90af7f8b592d1a7b461e03cc34b"); + + Ethereum::Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_to_address(token); + input.set_private_key(key.data(), key.size()); + auto &erc20 = *input.mutable_transaction()->mutable_erc20_transfer(); + erc20.set_to(toAddress); + erc20.set_amount(amountData.data(), amountData.size()); + + // sign test + Ethereum::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeThetaFuel); + + ASSERT_EQ(hex(output.encoded()), + "f8ad048603a3529440008301380b941336739b05c7ab8a526d40dcc0d04a826b5f8b0380b844a9059cbb0000000000000000000000008dbd6c7ede90646a61bbc649831b7c298bfd37a00000000000000000000000000000000000000000000000003782dace9d9000008202f6a03c1d37f5fc6adaa018c4ba41e13b9983e91500e7cfa8bc3731bb6365dd28d61ba07500748e46febcb781d6f37dad2479e1bd172479d108614c986122e1c6a4441e"); +} + +} // namespace TW::ThetaFuel diff --git a/tests/chains/ThetaFuel/TWCoinTypeTests.cpp b/tests/chains/ThetaFuel/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..c00d89bc9e6 --- /dev/null +++ b/tests/chains/ThetaFuel/TWCoinTypeTests.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + +TEST(TWThetaFuelCoinType, TWCoinType) { + const auto coin = TWCoinTypeThetaFuel; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xdb1c1c4e06289a4fc71b98ced218242d4f4a54a09987791a6a53a5260c053555")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xa144e6a98b967e585b214bfa7f6692af81987e5b")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "tfuelevm"); + assertStringsEqual(name, "Theta Fuel"); + assertStringsEqual(symbol, "TFUEL"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "361"); + assertStringsEqual(txUrl, "https://explorer.thetatoken.org/tx/0xdb1c1c4e06289a4fc71b98ced218242d4f4a54a09987791a6a53a5260c053555"); + assertStringsEqual(accUrl, "https://explorer.thetatoken.org/account/0xa144e6a98b967e585b214bfa7f6692af81987e5b"); +} diff --git a/tests/chains/ThunderToken/TWCoinTypeTests.cpp b/tests/chains/ThunderToken/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..7534dd9a20f --- /dev/null +++ b/tests/chains/ThunderToken/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWThunderTokenCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeThunderCore)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeThunderCore, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeThunderCore, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeThunderCore)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeThunderCore)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeThunderCore), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeThunderCore)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeThunderCore)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeThunderCore)); + assertStringsEqual(symbol, "TT"); + assertStringsEqual(txUrl, "https://scan.thundercore.com/transactions/t123"); + assertStringsEqual(accUrl, "https://scan.thundercore.com/address/a12"); + assertStringsEqual(id, "thundertoken"); + assertStringsEqual(name, "ThunderCore"); +} diff --git a/tests/chains/Tron/AddressTests.cpp b/tests/chains/Tron/AddressTests.cpp new file mode 100644 index 00000000000..7489812ddca --- /dev/null +++ b/tests/chains/Tron/AddressTests.cpp @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "Tron/Address.h" + +#include + +namespace TW::Tron { + +TEST(TronAddress, FromPublicKey) { + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + const auto address = Address(publicKey); + ASSERT_EQ(address.string(), "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + + const auto privateKey2 = PrivateKey(parse_hex("BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835")); + const auto publicKey2 = privateKey2.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + const auto address2 = Address(publicKey2); + ASSERT_EQ(address2.string(), "THRF3GuPnvvPzKoaT8pJex5XHmo8NNbCb3"); + + const auto privateKey3 = PrivateKey(parse_hex("BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835")); + const auto publicKey3 = privateKey3.getPublicKey(TWPublicKeyTypeED25519); + EXPECT_ANY_THROW(new Address(publicKey3)); +} + +TEST(TronAddress, Invalid) { + ASSERT_FALSE(Address::isValid(std::string("abc"))); + ASSERT_FALSE(Address::isValid(std::string("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"))); + ASSERT_FALSE(Address::isValid(std::string("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W"))); + ASSERT_FALSE(Address::isValid(std::string("2MegQ6oqSda2tTagdEzBA"))); + ASSERT_TRUE(Address::isValid(std::string("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"))); +} + +TEST(TronAddress, InitWithString) { + const auto address = Address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + + ASSERT_EQ(address.string(), "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); +} + +} // namespace TW::Tron diff --git a/tests/chains/Tron/SerializationTests.cpp b/tests/chains/Tron/SerializationTests.cpp new file mode 100644 index 00000000000..ef321093f73 --- /dev/null +++ b/tests/chains/Tron/SerializationTests.cpp @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "proto/Tron.pb.h" +#include "Tron/Signer.h" +#include "PrivateKey.h" +#include "HexCoding.h" +#include "uint256.h" + +#include + +namespace TW::Tron { + TEST(TronSerialization, TransferAsset) { + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& transfer = *transaction.mutable_transfer_asset(); + transfer.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + transfer.set_to_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + transfer.set_amount(4); + transfer.set_asset_name("1000959"); + + transaction.set_timestamp(1539295479000); + transaction.set_expiration(1541890116000 + 10 * 60 * 60 * 1000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1541890116000); + const auto txTrieRoot = parse_hex("845ab51bf63c2c21ee71a4dc0ac3781619f07a7cd05e1e0bd8ba828979332ffa"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000003cb800a7e69e9144e3d16f0cf33f33a95c7ce274097822c67243c1"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3979265); + const auto witnessAddress = parse_hex("41b487cdc02de90f15ac89a68c82f44cbfe3d915ea"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(output.json(), R"({"raw_data":{"contract":[{"parameter":{"type_url":"type.googleapis.com/protocol.TransferAssetContract","value":{"amount":4,"asset_name":"31303030393539","owner_address":"415cd0fb0ab3ce40f3051414c604b27756e69e43db","to_address":"41521ea197907927725ef36d70f25f850d1659c7c7"}},"type":"TransferAssetContract"}],"expiration":1541926116000,"ref_block_bytes":"b801","ref_block_hash":"0e2bc08d550f5f58","timestamp":1539295479000},"signature":["77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00"],"txID":"546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb"})"); + } + + TEST(TronSerialization, SignVoteAsset) { + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& vote = *transaction.mutable_vote_asset(); + vote.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + vote.add_vote_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + vote.set_support(true); + vote.set_count(1); + + transaction.set_timestamp(1539295479000); + transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1539295479000); + const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3111739); + const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(output.json(), R"({"raw_data":{"contract":[{"parameter":{"type_url":"type.googleapis.com/protocol.VoteAssetContract","value":{"count":1,"owner_address":"415cd0fb0ab3ce40f3051414c604b27756e69e43db","support":true,"vote_address":["41521ea197907927725ef36d70f25f850d1659c7c7"]}},"type":"VoteAssetContract"}],"expiration":1539331479000,"ref_block_bytes":"7b3b","ref_block_hash":"b21ace8d6ac20e7e","timestamp":1539295479000},"signature":["501e04b08f359116a26d9ec784abc50830f92a9dc05d2c1aceefe0eba79466d2730b63b6739edf0f1f1972181618b201ce0b4167d14a66abf40eba4097c39ec400"],"txID":"59b5736fb9756124f9470e4fadbcdafdc8c970da7157fa0ad34a41559418bf0a"})"); + } + + TEST(TronSerialization, SignVoteWitness) { + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& vote_witness = *transaction.mutable_vote_witness(); + vote_witness.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + vote_witness.set_support(true); + + auto& vote = *vote_witness.add_votes(); + vote.set_vote_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + vote.set_vote_count(3); + + transaction.set_timestamp(1539295479000); + transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1539295479000); + const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3111739); + const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(output.json(), R"({"raw_data":{"contract":[{"parameter":{"type_url":"type.googleapis.com/protocol.VoteWitnessContract","value":{"owner_address":"415cd0fb0ab3ce40f3051414c604b27756e69e43db","support":true,"votes":[{"vote_address":"41521ea197907927725ef36d70f25f850d1659c7c7","vote_count":3}]}},"type":"VoteWitnessContract"}],"expiration":1539331479000,"ref_block_bytes":"7b3b","ref_block_hash":"b21ace8d6ac20e7e","timestamp":1539295479000},"signature":["79ec1073ae1319ef9303a2f5a515876cfd67f8f0e155bdbde1115d391c05358a3c32f148bfafacf07e1619aaed728d9ffbc2c7e4a5046003c7b74feb86fc68e400"],"txID":"3f923e9dd9571a66624fafeda27baa3e00aba1709d3fdc5c97c77b81fda18c1f"})"); + } + + TEST(TronSerialization, SignTriggerSmartContract) { + auto input = Proto::SigningInput(); + auto data = parse_hex("736f6d652064617461"); + auto& transaction = *input.mutable_transaction(); + auto& trigger_contract = *transaction.mutable_trigger_smart_contract(); + trigger_contract.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + trigger_contract.set_contract_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + trigger_contract.set_call_value(0); + trigger_contract.set_call_token_value(10000); + trigger_contract.set_token_id(1); + trigger_contract.set_data(data.data(), data.size()); + + transaction.set_timestamp(1539295479000); + transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1539295479000); + const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3111739); + const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(output.json(), R"({"raw_data":{"contract":[{"parameter":{"type_url":"type.googleapis.com/protocol.TriggerSmartContract","value":{"call_token_value":10000,"contract_address":"41521ea197907927725ef36d70f25f850d1659c7c7","data":"736f6d652064617461","owner_address":"415cd0fb0ab3ce40f3051414c604b27756e69e43db","token_id":1}},"type":"TriggerSmartContract"}],"expiration":1539331479000,"ref_block_bytes":"7b3b","ref_block_hash":"b21ace8d6ac20e7e","timestamp":1539295479000},"signature":["21a99aafeabdddfdfae86538df048d120a83eb36bbcf5656595919ba6afddacd0a07d0ba051ae80337613174b109f36cb583b6e46ee5aecf6ffe3392fdbb8a2a01"],"txID":"9927d3daae10ad001b25ef3c1bb03073c928cc0e0823f6f3ce404c2b03ce3570"})"); + } + + TEST(TronSerialization, SignTransferTrc20Contract) { + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + auto& transfer_contract = *transaction.mutable_transfer_trc20_contract(); + transfer_contract.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + transfer_contract.set_contract_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + transfer_contract.set_to_address("TW1dU4L3eNm7Lw8WvieLKEHpXWAussRG9Z"); + Data amount = store(uint256_t(1000)); + transfer_contract.set_amount(std::string(amount.begin(), amount.end())); + + transaction.set_timestamp(1539295479000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1539295479000); + const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3111739); + const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(output.json(), R"({"raw_data":{"contract":[{"parameter":{"type_url":"type.googleapis.com/protocol.TriggerSmartContract","value":{"contract_address":"41521ea197907927725ef36d70f25f850d1659c7c7","data":"a9059cbb000000000000000000000041dbd7c53729b3310e1843083000fa84abad99696100000000000000000000000000000000000000000000000000000000000003e8","owner_address":"415cd0fb0ab3ce40f3051414c604b27756e69e43db"}},"type":"TriggerSmartContract"}],"expiration":1539331479000,"ref_block_bytes":"7b3b","ref_block_hash":"b21ace8d6ac20e7e","timestamp":1539295479000},"signature":["bec790877b3a008640781e3948b070740b1f6023c29ecb3f7b5835433c13fc5835e5cad3bd44360ff2ddad5ed7dc9d7dee6878f90e86a40355b7697f5954b88c01"],"txID":"0d644290e3cf554f6219c7747f5287589b6e7e30e1b02793b48ba362da6a5058"})"); + } + + TEST(TronSerialization, SignTransferTrc20Contract_LargeAmount) { + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + auto& transfer_contract = *transaction.mutable_transfer_trc20_contract(); + transfer_contract.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + transfer_contract.set_contract_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + transfer_contract.set_to_address("TW1dU4L3eNm7Lw8WvieLKEHpXWAussRG9Z"); + Data amount = store(uint256_t("10000000000000000000000")); // over 64 bits, corresponds to 10000 in case of 18 decimals + transfer_contract.set_amount(std::string(amount.begin(), amount.end())); + + transaction.set_timestamp(1539295479000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1539295479000); + const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3111739); + const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(output.json(), R"({"raw_data":{"contract":[{"parameter":{"type_url":"type.googleapis.com/protocol.TriggerSmartContract","value":{"contract_address":"41521ea197907927725ef36d70f25f850d1659c7c7","data":"a9059cbb000000000000000000000041dbd7c53729b3310e1843083000fa84abad99696100000000000000000000000000000000000000000000021e19e0c9bab2400000","owner_address":"415cd0fb0ab3ce40f3051414c604b27756e69e43db"}},"type":"TriggerSmartContract"}],"expiration":1539331479000,"ref_block_bytes":"7b3b","ref_block_hash":"b21ace8d6ac20e7e","timestamp":1539295479000},"signature":["8207cbae6aff799cfefa1ab4d8a0c52b6a59be43491bd25b4f03754f0e8115b006b5f1393a3934ec3489f5d3c272a7af42658bdc165dc632b36114bd3180da2e00"],"txID":"774422d8d205760876496f22b7d4395cfceda03f139b8362a3693f1f405f0c36"})"); + } +} diff --git a/tests/chains/Tron/SignerTests.cpp b/tests/chains/Tron/SignerTests.cpp new file mode 100644 index 00000000000..f27150bb585 --- /dev/null +++ b/tests/chains/Tron/SignerTests.cpp @@ -0,0 +1,517 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bitcoin/Address.h" +#include "Tron/Address.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "uint256.h" +#include "proto/Tron.pb.h" +#include "Tron/Signer.h" + +#include + +namespace TW::Tron { + +TEST(TronSigner, SignDirectTransferAsset) { + auto input = Proto::SigningInput(); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + input.set_txid("546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb"); + const auto output = Signer::sign(input); + ASSERT_EQ(hex(output.id()), "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb"); + ASSERT_EQ(hex(output.signature()), "77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00"); +} + +TEST(TronSigner, SignTransferAsset) { + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& transfer = *transaction.mutable_transfer_asset(); + transfer.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + transfer.set_to_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + transfer.set_amount(4); + transfer.set_asset_name("1000959"); + + transaction.set_timestamp(1539295479000); + transaction.set_expiration(1541890116000 + 10 * 60 * 60 * 1000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1541890116000); + const auto txTrieRoot = parse_hex("845ab51bf63c2c21ee71a4dc0ac3781619f07a7cd05e1e0bd8ba828979332ffa"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000003cb800a7e69e9144e3d16f0cf33f33a95c7ce274097822c67243c1"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3979265); + const auto witnessAddress = parse_hex("41b487cdc02de90f15ac89a68c82f44cbfe3d915ea"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.id()), "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb"); + ASSERT_EQ(hex(output.signature()), "77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00"); +} + +TEST(TronSigner, SignTransfer) { + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& transfer = *transaction.mutable_transfer(); + transfer.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + transfer.set_to_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + transfer.set_amount(2000000); + + transaction.set_timestamp(1539295479000); + transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1539295479000); + const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3111739); + const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.id()), "dc6f6d9325ee44ab3c00528472be16e1572ab076aa161ccd12515029869d0451"); + ASSERT_EQ(hex(output.signature()), "ede769f6df28aefe6a846be169958c155e23e7e5c9621d2e8dce1719b4d952b63e8a8bf9f00e41204ac1bf69b1a663dacdf764367e48e4a5afcd6b055a747fb200"); +} + +TEST(TronSigner, SignFreezeBalanceV2) { + // Successfully broadcasted https://nile.tronscan.org/#/transaction/3a46321487ce1fd115da38b3431006ea529f65ef2507f19233f5a23c05abd01d + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& freeze = *transaction.mutable_freeze_balance_v2(); + freeze.set_owner_address("TWWb9EjUWai17YEVB7FR8hreupYJKG9sMR"); + freeze.set_frozen_balance(10000000); + freeze.set_resource("ENERGY"); + + transaction.set_timestamp(1676983541337); + transaction.set_expiration(1676983599000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1676983485000); + const auto txTrieRoot = parse_hex("9b54db7f84bd19bbad9ff1fccef894c1aade6879450e9e9e2accec751eaa1f52"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000020cd4c13a67497a3a433a3105bc5a73a041ee3da98407d5a2a2bf1b"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(34395330); + const auto witnessAddress = parse_hex("4150d3765e4e670727ebac9d5b598f74b75a3d54a7"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(26); + + const auto privateKey = PrivateKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.id()), "3a46321487ce1fd115da38b3431006ea529f65ef2507f19233f5a23c05abd01d"); + ASSERT_EQ(hex(output.signature()), "d4b539a389f6721b4e9d0eb9f39b62a539069060e1af2a118f06b81737ad9cdb49d5b4fda85f10603012f8de3996da2a1234c21d74ac6ea5e60217d3c10b630900"); +} + +TEST(TronSigner, WithdrawExpireUnfreezeContract) { + // Successfully broadcasted https://nile.tronscan.org/#/transaction/65ff34192eebda9ba7013771ff2da1010615e348b70c046647f41afe865f00eb + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& freeze = *transaction.mutable_withdraw_expire_unfreeze(); + freeze.set_owner_address("TWWb9EjUWai17YEVB7FR8hreupYJKG9sMR"); + + transaction.set_timestamp(1677574466457); + transaction.set_expiration(1677574524000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1677574410000); + const auto txTrieRoot = parse_hex("0000000000000000000000000000000000000000000000000000000000000000"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000020fce45738ef00be07c350c03d027851308bc19d61c32312c673d3d"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(34590278); + const auto witnessAddress = parse_hex("41e7860196ad5b5718c1d6326babab039b70b8c1cd"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(27); + + const auto privateKey = PrivateKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.id()), "65ff34192eebda9ba7013771ff2da1010615e348b70c046647f41afe865f00eb"); + ASSERT_EQ(hex(output.signature()), "ef0361248c118b8afae9c4c8e6dfad1e63eec4fb6c182ae369fa3bbecc2ac29a292838949ad74300b2b7322a110ffd4458224e283181cf6d64df0324b068bb0001"); +} + +TEST(TronSigner, SignUnFreezeBalanceV2) { + // Successfully broadcasted https://nile.tronscan.org/#/transaction/3070adc1743e6fdd20e04a749cc2af691ca26d2ce70e40cc0886be03595f9eeb + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& freeze = *transaction.mutable_unfreeze_balance_v2(); + freeze.set_owner_address("TWWb9EjUWai17YEVB7FR8hreupYJKG9sMR"); + freeze.set_unfreeze_balance(510000000); + freeze.set_resource("ENERGY"); + + transaction.set_timestamp(1676992267490); + transaction.set_expiration(1676992326000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1676992212000); + const auto txTrieRoot = parse_hex("4b1edc58d14a5c60c083365d8b77771ba626394b445c7a7b8b5d67330bb6c92d"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000020ce000354fbb346d676de268b3f83124381f8496835afe88da4a01"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(34398209); + const auto witnessAddress = parse_hex("4194a21bec5d0e1dde2151475f72ed158a87eb4817"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(26); + + const auto privateKey = PrivateKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.id()), "3070adc1743e6fdd20e04a749cc2af691ca26d2ce70e40cc0886be03595f9eeb"); + ASSERT_EQ(hex(output.signature()), "10bc05c47102f1db1a3a4c0b4a6aba028d5a35dda4e505563c3f0ccf95a562cf18b53f7f7053c485299cfc599a432d1f0ee5554a56cd5981ccfff31d79b9868b00"); +} + +TEST(TronSigner, DelegateResourceContract) { + // Successfully broadcasted https://nile.tronscan.org/#/transaction/ceabcd0f105854c13aae12ba35c0766945713c29cee540be1239bb0f1f0cde2c + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& freeze = *transaction.mutable_delegate_resource(); + freeze.set_owner_address("TWWb9EjUWai17YEVB7FR8hreupYJKG9sMR"); + freeze.set_receiver_address("TPFfHr1CWfTcS9eugQXQmvqHNGufnjxjXP"); + freeze.set_balance(68000000); + freeze.set_resource("ENERGY"); + + transaction.set_timestamp(1676991607274); + transaction.set_expiration(1676991660000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1676991546000); + const auto txTrieRoot = parse_hex("0000000000000000000000000000000000000000000000000000000000000000"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000020cdf260ff2357d814141106c375c101913c933c2b5c31a390db7fc"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(34397991); + const auto witnessAddress = parse_hex("417d3601dbd9d033b034c154868acc2904d9c45565"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(26); + + const auto privateKey = PrivateKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.id()), "ceabcd0f105854c13aae12ba35c0766945713c29cee540be1239bb0f1f0cde2c"); + ASSERT_EQ(hex(output.signature()), "664500a76466497a442cecc0e9282a9234483f047c12a997b6206d7f6a9030c70b700c879d7948c4cbdfe339c2c81a29dea18e00e9916504196c1b20cf045ca300"); +} + +TEST(TronSigner, UnDelegateResourceContract) { + // Successfully broadcasted https://nile.tronscan.org/#/transaction/3609519cc700cf2446b5e048864abc4b45e2ba6b7f9f8890d471ba2876599d3b + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& freeze = *transaction.mutable_undelegate_resource(); + freeze.set_owner_address("TWWb9EjUWai17YEVB7FR8hreupYJKG9sMR"); + freeze.set_receiver_address("TPFfHr1CWfTcS9eugQXQmvqHNGufnjxjXP"); + freeze.set_balance(68000000); + freeze.set_resource("ENERGY"); + + transaction.set_timestamp(1676992063012); + transaction.set_expiration(1676992122000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1676992008000); + const auto txTrieRoot = parse_hex("85a47017a4380e92d09bac0f8991031e8de13b8b65767a6f5372d3f0992eabcd"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000020cdfbe4d7f36fcbb3d96dd634987b897eaf885001dd62fd92eb263"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(34398143); + const auto witnessAddress = parse_hex("4196409f85790883057edf03286d08e4aa608c0d0a"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(26); + + const auto privateKey = PrivateKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.id()), "3609519cc700cf2446b5e048864abc4b45e2ba6b7f9f8890d471ba2876599d3b"); + ASSERT_EQ(hex(output.signature()), "b08e32a704d5a366df499d283d407c428dd50e60665f54ecf967226b75bec37157e6bc23312af07fad9dd3551cd668ce027cc280932fd4772af89d6f0fecf11900"); +} + +TEST(TronSigner, SignFreezeBalance) { + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& freeze = *transaction.mutable_freeze_balance(); + freeze.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + freeze.set_receiver_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + freeze.set_frozen_duration(1000000); + freeze.set_frozen_duration(100); + freeze.set_resource("ENERGY"); + + transaction.set_timestamp(1539295479000); + transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1539295479000); + const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3111739); + const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.id()), "d314967bc1d153d649d9f54a1cc78033f0d696a58ff6922f490ddaec82558c83"); + ASSERT_EQ(hex(output.signature()), "aa7cf79fb1692ff432a1a3e520be3355c3e8168c5fa22f6e3b96c2a9f2e2827b49d67d5e6eea5c7e7cf872047d422ce5d4d149c4df752b176d13f8f48920271201"); +} + +TEST(TronSigner, SignUnFreezeBalance) { + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& unfreeze = *transaction.mutable_unfreeze_balance(); + unfreeze.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + unfreeze.set_receiver_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + unfreeze.set_resource("ENERGY"); + + transaction.set_timestamp(1539295479000); + transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1539295479000); + const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3111739); + const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.id()), "c5bd624bb53fed8ce4a7361475263b3a91ae71ef389630e0b3b8693c8c56d7a1"); + ASSERT_EQ(hex(output.signature()), "4b4b12b5fd091d5343335f14ac90bf23ea9a8167d648dd9d10d00c9c9b24731c484937bf133e5010f0338fb70a679a9a2eca8b945574005bc4015b419a68897300"); +} + +TEST(TronSigner, SignUnFreezeAsset) { + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& unfreeze = *transaction.mutable_unfreeze_asset(); + unfreeze.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + + transaction.set_timestamp(1539295479000); + transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1539295479000); + const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3111739); + const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.id()), "432bd5cf77ff134787712724709a672fc6e51763de00292438db02d23931e13d"); + ASSERT_EQ(hex(output.signature()), "f493d8f275538a50bb8a832d759df9cad535bb2c5cc73296b04983f551d8398b6d7a30fc0fdfd73e8a9cac77a1a6a9435dc6309bb98fbb219035e88809a0b65901"); +} + +TEST(TronSigner, SignWithdrawBalance) { + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& unfreeze = *transaction.mutable_withdraw_balance(); + unfreeze.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + + transaction.set_timestamp(1539295479000); + transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1539295479000); + const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3111739); + const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.id()), "69aaa954dcd61f28a6a73e979addece6e36541522e5b3374b18b4ef9bc3de4cb"); + ASSERT_EQ(hex(output.signature()), "cb7d23a5eb23284a25ba6deaa231de0f18d8d103592e3312bff101a4219a3e02167eca24b3f4ce78b34f0c1842b6f7fb8d813f530c4c54342cdedef9f8e1f85100"); +} + +TEST(TronSigner, SignVoteAsset) { + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& vote = *transaction.mutable_vote_asset(); + vote.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + vote.add_vote_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + vote.set_support(true); + vote.set_count(1); + + transaction.set_timestamp(1539295479000); + transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1539295479000); + const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3111739); + const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.id()), "59b5736fb9756124f9470e4fadbcdafdc8c970da7157fa0ad34a41559418bf0a"); + ASSERT_EQ(hex(output.signature()), "501e04b08f359116a26d9ec784abc50830f92a9dc05d2c1aceefe0eba79466d2730b63b6739edf0f1f1972181618b201ce0b4167d14a66abf40eba4097c39ec400"); +} + +TEST(TronSigner, SignVoteWitness) { + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& vote_witness = *transaction.mutable_vote_witness(); + vote_witness.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + vote_witness.set_support(true); + + auto& vote = *vote_witness.add_votes(); + vote.set_vote_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + vote.set_vote_count(3); + + transaction.set_timestamp(1539295479000); + transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1539295479000); + const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3111739); + const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.id()), "3f923e9dd9571a66624fafeda27baa3e00aba1709d3fdc5c97c77b81fda18c1f"); + ASSERT_EQ(hex(output.signature()), "79ec1073ae1319ef9303a2f5a515876cfd67f8f0e155bdbde1115d391c05358a3c32f148bfafacf07e1619aaed728d9ffbc2c7e4a5046003c7b74feb86fc68e400"); +} + +TEST(TronSigner, SignTriggerSmartContract) { + auto input = Proto::SigningInput(); + auto data = parse_hex("736f6d652064617461"); + auto& transaction = *input.mutable_transaction(); + auto& trigger_contract = *transaction.mutable_trigger_smart_contract(); + trigger_contract.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + trigger_contract.set_contract_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + trigger_contract.set_call_value(0); + trigger_contract.set_call_token_value(10000); + trigger_contract.set_token_id(1); + trigger_contract.set_data(data.data(), data.size()); + + transaction.set_timestamp(1539295479000); + transaction.set_expiration(1539295479000 + 10 * 60 * 60 * 1000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1539295479000); + const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3111739); + const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.id()), "9927d3daae10ad001b25ef3c1bb03073c928cc0e0823f6f3ce404c2b03ce3570"); + ASSERT_EQ(hex(output.signature()), "21a99aafeabdddfdfae86538df048d120a83eb36bbcf5656595919ba6afddacd0a07d0ba051ae80337613174b109f36cb583b6e46ee5aecf6ffe3392fdbb8a2a01"); +} + +TEST(TronSigner, SignTransferTrc20Contract) { + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + auto& transfer_contract = *transaction.mutable_transfer_trc20_contract(); + transfer_contract.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + transfer_contract.set_contract_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + transfer_contract.set_to_address("TW1dU4L3eNm7Lw8WvieLKEHpXWAussRG9Z"); + Data amount = store(uint256_t(1000)); + transfer_contract.set_amount(std::string(amount.begin(), amount.end())); + + transaction.set_timestamp(1539295479000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1539295479000); + const auto txTrieRoot = parse_hex("64288c2db0641316762a99dbb02ef7c90f968b60f9f2e410835980614332f86d"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000002f7b3af4f5f8b9e23a30c530f719f165b742e7358536b280eead2d"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3111739); + const auto witnessAddress = parse_hex("415863f6091b8e71766da808b1dd3159790f61de7d"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + const auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.id()), "0d644290e3cf554f6219c7747f5287589b6e7e30e1b02793b48ba362da6a5058"); + ASSERT_EQ(hex(output.signature()), "bec790877b3a008640781e3948b070740b1f6023c29ecb3f7b5835433c13fc5835e5cad3bd44360ff2ddad5ed7dc9d7dee6878f90e86a40355b7697f5954b88c01"); +} +} // namespace TW::Tron diff --git a/tests/chains/Tron/TWAnySignerTests.cpp b/tests/chains/Tron/TWAnySignerTests.cpp new file mode 100644 index 00000000000..bff0316d20a --- /dev/null +++ b/tests/chains/Tron/TWAnySignerTests.cpp @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/Tron.pb.h" +#include + +#include "TestUtilities.h" +#include + +namespace TW::Tron { + +TEST(TWAnySignerTron, SignTransferAsset) { + auto input = Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& transfer = *transaction.mutable_transfer_asset(); + transfer.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + transfer.set_to_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + transfer.set_amount(4); + transfer.set_asset_name("1000959"); + + transaction.set_timestamp(1539295479000); + transaction.set_expiration(1541890116000 + 10 * 60 * 60 * 1000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1541890116000); + const auto txTrieRoot = parse_hex("845ab51bf63c2c21ee71a4dc0ac3781619f07a7cd05e1e0bd8ba828979332ffa"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = parse_hex("00000000003cb800a7e69e9144e3d16f0cf33f33a95c7ce274097822c67243c1"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3979265); + const auto witnessAddress = parse_hex("41b487cdc02de90f15ac89a68c82f44cbfe3d915ea"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + + const auto privateKey = parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"); + input.set_private_key(privateKey.data(), privateKey.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeTron); + + ASSERT_EQ(hex(output.signature()), "77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00"); +} + +} diff --git a/tests/chains/Tron/TWCoinTypeTests.cpp b/tests/chains/Tron/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..9aae259d088 --- /dev/null +++ b/tests/chains/Tron/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWTronCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeTron)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeTron, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeTron, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeTron)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeTron)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeTron), 6); + ASSERT_EQ(TWBlockchainTron, TWCoinTypeBlockchain(TWCoinTypeTron)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeTron)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeTron)); + assertStringsEqual(symbol, "TRX"); + assertStringsEqual(txUrl, "https://tronscan.org/#/transaction/t123"); + assertStringsEqual(accUrl, "https://tronscan.org/#/address/a12"); + assertStringsEqual(id, "tron"); + assertStringsEqual(name, "Tron"); +} diff --git a/tests/chains/Tron/TransactionCompilerTests.cpp b/tests/chains/Tron/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..0962f4bda07 --- /dev/null +++ b/tests/chains/Tron/TransactionCompilerTests.cpp @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/TransactionCompiler.pb.h" +#include "proto/Tron.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TronCompiler, CompileWithSignatures) { + const auto privateKey = + PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + const auto coin = TWCoinTypeTron; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::Tron::Proto::SigningInput(); + auto& transaction = *input.mutable_transaction(); + + auto& transfer = *transaction.mutable_transfer_asset(); + transfer.set_owner_address("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); + transfer.set_to_address("THTR75o8xXAgCTQqpiot2AFRAjvW1tSbVV"); + transfer.set_amount(4); + transfer.set_asset_name("1000959"); + transaction.set_timestamp(1539295479000); + transaction.set_expiration(1541890116000 + 10 * 60 * 60 * 1000); + + auto& blockHeader = *transaction.mutable_block_header(); + blockHeader.set_timestamp(1541890116000); + const auto txTrieRoot = + parse_hex("845ab51bf63c2c21ee71a4dc0ac3781619f07a7cd05e1e0bd8ba828979332ffa"); + blockHeader.set_tx_trie_root(txTrieRoot.data(), txTrieRoot.size()); + const auto parentHash = + parse_hex("00000000003cb800a7e69e9144e3d16f0cf33f33a95c7ce274097822c67243c1"); + blockHeader.set_parent_hash(parentHash.data(), parentHash.size()); + blockHeader.set_number(3979265); + const auto witnessAddress = parse_hex("41b487cdc02de90f15ac89a68c82f44cbfe3d915ea"); + blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); + blockHeader.set_version(3); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), + "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb"); + auto signature = parse_hex("77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603" + "a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preSigningOutput.data_hash()))); + /// Step 3: Compile transaction info + const auto tx = + "{\"raw_data\":{\"contract\":[{\"parameter\":{\"type_url\":\"type.googleapis.com/" + "protocol.TransferAssetContract\",\"value\":{\"amount\":4,\"asset_name\":" + "\"31303030393539\",\"owner_address\":\"415cd0fb0ab3ce40f3051414c604b27756e69e43db\",\"to_" + "address\":\"41521ea197907927725ef36d70f25f850d1659c7c7\"}},\"type\":" + "\"TransferAssetContract\"}],\"expiration\":1541926116000,\"ref_block_bytes\":\"b801\"," + "\"ref_block_hash\":\"0e2bc08d550f5f58\",\"timestamp\":1539295479000},\"signature\":[" + "\"77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991b" + "f55acc8e488a6ca04fb393b1a8ac16610eeafdfc00\"],\"txID\":" + "\"546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb\"}"; + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKey.bytes}); + + { + TW::Tron::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(output.json(), tx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::Tron::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(privateKey.bytes.data(), 32); + + TW::Tron::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.json(), tx); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKey.bytes}); + Tron::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.json().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } +} diff --git a/tests/chains/Tron/TronMessageSignerTests.cpp b/tests/chains/Tron/TronMessageSignerTests.cpp new file mode 100644 index 00000000000..4b0940a5db6 --- /dev/null +++ b/tests/chains/Tron/TronMessageSignerTests.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include +#include +#include + +#include + +namespace TW::Tron { + TEST(TronMessageSigner, SignMessageAndVerify) { + PrivateKey tronKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")); + auto msg = "Hello World"; + auto signature = Tron::MessageSigner::signMessage(tronKey, msg); + ASSERT_EQ(signature, "9bb6d11ec8a6a3fb686a8f55b123e7ec4e9746a26157f6f9e854dd72f5683b450397a7b0a9653865658de8f9243f877539882891bad30c7286c3bf5622b900471b"); + auto pubKey = tronKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + ASSERT_TRUE(Tron::MessageSigner::verifyMessage(pubKey, msg, signature)); + } + + TEST(TWTronMessageSigner, SignAndVerifyLegacy) { + const auto privKeyData = "75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a"; + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto message = STRING("Hello World"); + + const auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeTron)); + const auto signature = WRAPS(TWTronMessageSignerSignMessage(privateKey.get(), message.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature.get())), "9bb6d11ec8a6a3fb686a8f55b123e7ec4e9746a26157f6f9e854dd72f5683b450397a7b0a9653865658de8f9243f877539882891bad30c7286c3bf5622b900471b"); + EXPECT_TRUE(TWTronMessageSignerVerifyMessage(pubKey.get(), message.get(), signature.get())); + } +} diff --git a/tests/chains/VeChain/SignerTests.cpp b/tests/chains/VeChain/SignerTests.cpp new file mode 100644 index 00000000000..1317cc17ee4 --- /dev/null +++ b/tests/chains/VeChain/SignerTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "VeChain/Signer.h" + +#include + +namespace TW::VeChain { + +using boost::multiprecision::uint256_t; + +TEST(Signer, Sign) { + auto transaction = Transaction(); + transaction.chainTag = 1; + transaction.blockRef = 1; + transaction.expiration = 1; + transaction.clauses.push_back( + Clause(Ethereum::Address("0x3535353535353535353535353535353535353535"), 1000, {}) + ); + transaction.gasPriceCoef = 0; + transaction.gas = 21000; + transaction.nonce = 1; + + auto key = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); + auto signature = Signer::sign(key, transaction); + + ASSERT_EQ(hex(signature), "3181b1094150f8e4f51f370b805cc9c5b107504145b9e316e846d5e5dbeedb5c1c2b5d217f197a105983dfaad6a198414d5731c7447493cb6b5169907d73dbe101"); +} + +} // namespace TW::VeChain diff --git a/tests/chains/VeChain/TWAnySignerTests.cpp b/tests/chains/VeChain/TWAnySignerTests.cpp new file mode 100644 index 00000000000..c2aab9d4d8c --- /dev/null +++ b/tests/chains/VeChain/TWAnySignerTests.cpp @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/VeChain.pb.h" +#include "TestUtilities.h" +#include +#include + +namespace TW::VeChain::tests { + +TEST(TWAnySignerVeChain, Sign) { + auto input = Proto::SigningInput(); + + input.set_chain_tag(1); + input.set_block_ref(1); + input.set_expiration(1); + input.set_gas_price_coef(0); + input.set_gas(21000); + input.set_nonce(1); + + auto key = parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); + input.set_private_key(key.data(), key.size()); + + auto& clause = *input.add_clauses(); + auto amount = parse_hex("31303030"); // 1000 + clause.set_to("0x3535353535353535353535353535353535353535"); + clause.set_value(amount.data(), amount.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeVeChain); + + ASSERT_EQ(hex(output.encoded()), "f86a010101dcdb943535353535353535353535353535353535353535843130303080808252088001c0b841bf8edf9600e645b5abd677cb52f585e7f655d1361075d511b37f707a9f31da6702d28739933b264527a1d05b046f5b74044b88c30c3f5a09d616bd7a4af4901601"); +} + +} // namespace TW::VeChain::tests diff --git a/tests/chains/VeChain/TWCoinTypeTests.cpp b/tests/chains/VeChain/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..325499d53a8 --- /dev/null +++ b/tests/chains/VeChain/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWVeChainCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeVeChain)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xa424053be0063555aee73a595ca69968c2e4d90d36f280753e503b92b11a655d")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeVeChain, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x8a0a035a33173601bfbec8b6ae7c4a6557a55103")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeVeChain, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeVeChain)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeVeChain)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeVeChain), 18); + ASSERT_EQ(TWBlockchainVechain, TWCoinTypeBlockchain(TWCoinTypeVeChain)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeVeChain)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeVeChain)); + assertStringsEqual(symbol, "VET"); + assertStringsEqual(txUrl, "https://explore.vechain.org/transactions/0xa424053be0063555aee73a595ca69968c2e4d90d36f280753e503b92b11a655d"); + assertStringsEqual(accUrl, "https://explore.vechain.org/accounts/0x8a0a035a33173601bfbec8b6ae7c4a6557a55103"); + assertStringsEqual(id, "vechain"); + assertStringsEqual(name, "VeChain"); +} diff --git a/tests/chains/VeChain/TransactionCompilerTests.cpp b/tests/chains/VeChain/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..6a9f9ab9c90 --- /dev/null +++ b/tests/chains/VeChain/TransactionCompilerTests.cpp @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/TransactionCompiler.pb.h" +#include "proto/VeChain.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(VechainCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeVeChain; + + /// Step 1: Prepare transaction input (protobuf) + TW::VeChain::Proto::SigningInput input; + PrivateKey privateKey = + PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto publicKey = privateKey.getPublicKey(publicKeyType(coin)); + + input.set_chain_tag(1); + input.set_block_ref(1); + input.set_expiration(1); + input.set_gas_price_coef(0); + input.set_gas(21000); + input.set_nonce(1); + + auto& clause = *input.add_clauses(); + auto amount = parse_hex("31303030"); // 1000 + clause.set_to("0x3535353535353535353535353535353535353535"); + clause.set_value(amount.data(), amount.size()); + + auto stringInput = input.SerializeAsString(); + auto dataInput = TW::Data(stringInput.begin(), stringInput.end()); + + /// Step 2: Obtain preimage hash + const auto preImageHashData = TransactionCompiler::preImageHashes(coin, dataInput); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = preSigningOutput.data(); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImage), + "e7010101dcdb943535353535353535353535353535353535353535843130303080808252088001c0"); + EXPECT_EQ(hex(preImageHash), + "a1b8ef3af3d8c74e97ac6cd732916a8f4c38c0905c8b70d2fa598edf1f62ea04"); + + /// Step 3: Sign + TW::Data signature; + { + TW::VeChain::Proto::SigningOutput output; + ANY_SIGN(input, coin); + ASSERT_EQ(hex(output.encoded()), + "f86a010101dcdb943535353535353535353535353535353535353535843130303080808252088001c0b8" + "41bf8edf9600e645b5abd677cb52f585e7f655d1361075d511b37f707a9f31da6702d28739933b264527" + "a1d05b046f5b74044b88c30c3f5a09d616bd7a4af4901601"); + + signature = data(output.signature()); + /// Step 4: Verify signature + ASSERT_TRUE(publicKey.verify(signature, data(preImageHash.data()))); + } + { + const Data outputData = + TransactionCompiler::compileWithSignatures(coin, dataInput, {signature}, {publicKey.bytes}); + + TW::VeChain::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + ASSERT_EQ(hex(output.encoded()), + "f86a010101dcdb943535353535353535353535353535353535353535843130303080808252088001c0b8" + "41bf8edf9600e645b5abd677cb52f585e7f655d1361075d511b37f707a9f31da6702d28739933b264527" + "a1d05b046f5b74044b88c30c3f5a09d616bd7a4af4901601"); + } + + { // Negative: more than one signatures + const Data outputData = TransactionCompiler::compileWithSignatures( + coin, dataInput, {signature, signature}, {publicKey.bytes}); + VeChain::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } +} diff --git a/tests/chains/Verge/AddressTests.cpp b/tests/chains/Verge/AddressTests.cpp new file mode 100644 index 00000000000..5185d3bd005 --- /dev/null +++ b/tests/chains/Verge/AddressTests.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "Bitcoin/Address.h" +#include "PublicKey.h" +#include "PrivateKey.h" +#include +#include + +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(VergeAddress, Valid) { + ASSERT_TRUE(Address::isValid(std::string("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E"))); + ASSERT_TRUE(TW::validateAddress(TWCoinTypeVerge, "DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E")); + ASSERT_TRUE(TW::validateAddress(TWCoinTypeVerge, "vg1qujpe553lzgyg95g7k0w6zwscuy0ae022h4q4zg")); +} + +TEST(VergeAddress, Invalid) { + ASSERT_FALSE(Address::isValid(std::string("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj234"))); +} + +TEST(VergeAddress, FromPrivateKey) { + auto privateKey = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto address = Address(publicKey, TWCoinTypeP2pkhPrefix(TWCoinTypeVerge)); + ASSERT_EQ(address.string(), "DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E"); + + auto addr = TW::deriveAddress(TWCoinTypeVerge, publicKey, TWDerivationBitcoinSegwit); + ASSERT_EQ(addr, "vg1qujpe553lzgyg95g7k0w6zwscuy0ae022h4q4zg"); +} + +TEST(VergeAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("034f3eb727ca1eba84a0d22839a483a1120ee6a1da0d5087dde527b5ff912c1694"), TWPublicKeyTypeSECP256k1); + auto address = Address(publicKey, TWCoinTypeP2pkhPrefix(TWCoinTypeVerge)); + ASSERT_EQ(address.string(), "D8rBdwBfz5wvLhmHvRkXnNzeeihQgxkLmL"); + + auto addr = TW::deriveAddress(TWCoinTypeVerge, publicKey, TWDerivationBitcoinLegacy); + ASSERT_EQ(addr, "D8rBdwBfz5wvLhmHvRkXnNzeeihQgxkLmL"); +} + +TEST(VergeAddress, FromString) { + auto address = Address("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E"); + ASSERT_EQ(address.string(), "DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E"); + + auto data = TW::addressToData(TWCoinTypeVerge, "DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E"); + EXPECT_EQ(hex(data), "e4839a523f120882d11eb3dda13a18e11fdcbd4a"); + + data = TW::addressToData(TWCoinTypeVerge, "vg1qujpe553lzgyg95g7k0w6zwscuy0ae022h4q4zg"); + EXPECT_EQ(hex(data), "e4839a523f120882d11eb3dda13a18e11fdcbd4a"); + + // invalid address + data = TW::addressToData(TWCoinTypeVerge, "vg1qujpe553lzgyg95g7k0w6zwscuy0ae022h4q4"); + EXPECT_EQ(data.size(), 0ul); +} diff --git a/tests/chains/Verge/SignerTests.cpp b/tests/chains/Verge/SignerTests.cpp new file mode 100644 index 00000000000..14ff7bb0720 --- /dev/null +++ b/tests/chains/Verge/SignerTests.cpp @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Verge/Signer.h" +#include "Verge/Transaction.h" +#include "Verge/TransactionBuilder.h" +#include "Bitcoin/Address.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" + +#include + +#include +#include + +using namespace TW; +using namespace TW::Bitcoin; + + +TEST(VergeSigner, Sign) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "DQYMMpqPrnWYZaikKGTQqk5ydUaQw8nkdD"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("DAkEo5pNELZav7MRwBfEwHRG1aChgSUw6c"); + input.set_coin_type(TWCoinTypeVerge); + input.set_time(1656474580); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + auto script0 = Bitcoin::Script::lockScriptForAddress("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E", TWCoinTypeVerge); + ASSERT_EQ(hex(script0.bytes), "76a914e4839a523f120882d11eb3dda13a18e11fdcbd4a88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 980000000; + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = Verge::Signer::sign(input); + + ASSERT_EQ(hex(result.encoded()), + "01000000d4cbbb62017d40b2c80adbb0f44cbec466ae30f0316e0c1bf98110fd6d3f1b0fda47e1a6a5000000006b483045022100bf1d0e5f84e70e699f45dd4822ecdbbfb1687e61ac749354a76f2afa2e13f76602202d4f5cda7177282b58f80163fead42300468670d03c5f4bb1db3b9596f2dcea301210220ee0423797a856fdd2e85876a60bf10f8696e6ae83e744f498f2173237fe23dfeffffff02002f6859000000001976a914d4d05406c3ca73cf887911f80c852a1c0773615088ac009d693a000000001976a9143d7e143a8b3c8a4aa2f51104da380edeb6c3fc2088ac00000000" + ); +} + +TEST(VergeSigner, SignAnyoneCanPay) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "DQYMMpqPrnWYZaikKGTQqk5ydUaQw8nkdD"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAnyoneCanPay|TWBitcoinSigHashTypeSingle); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("DAkEo5pNELZav7MRwBfEwHRG1aChgSUw6c"); + input.set_coin_type(TWCoinTypeVerge); + input.set_time(1656474580); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto script0 = Bitcoin::Script::lockScriptForAddress("vg1qujpe553lzgyg95g7k0w6zwscuy0ae022h4q4zg", TWCoinTypeVerge); + EXPECT_EQ(hex(script0.bytes), "0014e4839a523f120882d11eb3dda13a18e11fdcbd4a"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + + auto txHash1 = parse_hex("29bd442521ea303afb09ad2583f589a6527c9218c050882b6b8527bbe4d11766"); + std::reverse(txHash1.begin(), txHash1.end()); + + auto utxo1 = input.add_utxo(); + utxo1->mutable_out_point()->set_hash(txHash1.data(), txHash1.size()); + utxo1->mutable_out_point()->set_index(0); + utxo1->mutable_out_point()->set_sequence(4294967294); + utxo1->set_amount(200000000); + + auto script1 = Bitcoin::Script::lockScriptForAddress("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E", TWCoinTypeVerge); + ASSERT_EQ(hex(script1.bytes), "76a914e4839a523f120882d11eb3dda13a18e11fdcbd4a88ac"); + utxo1->set_script(script1.bytes.data(), script1.bytes.size()); + + auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 1000000000; + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = Bitcoin::TransactionSigner::sign(input); + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + ASSERT_EQ(hex(serialized), + "01000000d4cbbb620001017d40b2c80adbb0f44cbec466ae30f0316e0c1bf98110fd6d3f1b0fda47e1a6a50000000000feffffff02002f6859000000001976a914d4d05406c3ca73cf887911f80c852a1c0773615088ac00ca9a3b000000001976a9143d7e143a8b3c8a4aa2f51104da380edeb6c3fc2088ac02473044022016413b2d31c16d185cdf7c0ae343b14eee586124a8fa65bfaaec6a35eeb54e13022073e3d73d251d97fd951201ab184cdb101627317866e199ac0963b83b17e5f3bf83210220ee0423797a856fdd2e85876a60bf10f8696e6ae83e744f498f2173237fe23d00000000" + ); +} + +TEST(VergeSigner, SignSegwit) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "DQYMMpqPrnWYZaikKGTQqk5ydUaQw8nkdD"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("DAkEo5pNELZav7MRwBfEwHRG1aChgSUw6c"); + input.set_coin_type(TWCoinTypeVerge); + input.set_time(1656474580); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto script0 = Bitcoin::Script::lockScriptForAddress("vg1qujpe553lzgyg95g7k0w6zwscuy0ae022h4q4zg", TWCoinTypeVerge); + EXPECT_EQ(hex(script0.bytes), "0014e4839a523f120882d11eb3dda13a18e11fdcbd4a"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + + auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 980000000; + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = Verge::Signer::sign(input); + + ASSERT_EQ(hex(result.encoded()), + "01000000d4cbbb620001017d40b2c80adbb0f44cbec466ae30f0316e0c1bf98110fd6d3f1b0fda47e1a6a50000000000feffffff02002f6859000000001976a914d4d05406c3ca73cf887911f80c852a1c0773615088ac009d693a000000001976a9143d7e143a8b3c8a4aa2f51104da380edeb6c3fc2088ac024730440220657132a334ffbb15f6bbcd11da743756534c2c345195e19c007d67224f09703f022036cebc6442e212be80b74d5992cfd70355e603c1e538e84e02fecf49f82f2f8a01210220ee0423797a856fdd2e85876a60bf10f8696e6ae83e744f498f2173237fe23d00000000" + ); +} + +TEST(VergeSigner, SignWithError) { + const int64_t amount = 1500000000; + const std::string toAddress = "DQYMMpqPrnWYZaikKGTQqk5ydUaQw8nkdD"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("DAkEo5pNELZav7MRwBfEwHRG1aChgSUw6c"); + input.set_coin_type(TWCoinTypeVerge); + input.set_time(1656474580); + + // Sign + auto result = Verge::Signer::sign(input); + + ASSERT_NE(result.error(), Common::Proto::OK); + + // PreImage + auto preResult = Verge::Signer::preImageHashes(input); + ASSERT_NE(preResult.error(), Common::Proto::OK); +} \ No newline at end of file diff --git a/tests/chains/Verge/TWAnyAddressTests.cpp b/tests/chains/Verge/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..0476122548a --- /dev/null +++ b/tests/chains/Verge/TWAnyAddressTests.cpp @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWVerge, Address) { + auto string = STRING("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeVerge)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "e4839a523f120882d11eb3dda13a18e11fdcbd4a"); +} diff --git a/tests/chains/Verge/TWAnySignerTests.cpp b/tests/chains/Verge/TWAnySignerTests.cpp new file mode 100644 index 00000000000..7d65a5a792d --- /dev/null +++ b/tests/chains/Verge/TWAnySignerTests.cpp @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include + +#include "Bitcoin/Signer.h" +#include "Bitcoin/Address.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(TWAnySignerVerge, Sign) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "DQYMMpqPrnWYZaikKGTQqk5ydUaQw8nkdD"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("DAkEo5pNELZav7MRwBfEwHRG1aChgSUw6c"); + input.set_coin_type(TWCoinTypeVerge); + input.set_time(1656474580); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + auto script0 = Bitcoin::Script::lockScriptForAddress("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E", TWCoinTypeVerge); + ASSERT_EQ(hex(script0.bytes), "76a914e4839a523f120882d11eb3dda13a18e11fdcbd4a88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + Bitcoin::Proto::TransactionPlan plan; + { + ANY_PLAN(input, plan, TWCoinTypeVerge); + + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(980000000); + + *input.mutable_plan() = plan; + } + + Bitcoin::Proto::SigningOutput output; + { + ANY_SIGN(input, TWCoinTypeVerge); + ASSERT_EQ(output.error(), Common::Proto::OK); + } + + // Sign + ASSERT_EQ(hex(output.encoded()), + "01000000d4cbbb62017d40b2c80adbb0f44cbec466ae30f0316e0c1bf98110fd6d3f1b0fda47e1a6a5000000006b483045022100bf1d0e5f84e70e699f45dd4822ecdbbfb1687e61ac749354a76f2afa2e13f76602202d4f5cda7177282b58f80163fead42300468670d03c5f4bb1db3b9596f2dcea301210220ee0423797a856fdd2e85876a60bf10f8696e6ae83e744f498f2173237fe23dfeffffff02002f6859000000001976a914d4d05406c3ca73cf887911f80c852a1c0773615088ac009d693a000000001976a9143d7e143a8b3c8a4aa2f51104da380edeb6c3fc2088ac00000000" + ); +} diff --git a/tests/chains/Verge/TWCoinTypeTests.cpp b/tests/chains/Verge/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..47d2546607f --- /dev/null +++ b/tests/chains/Verge/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWVergeCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeVerge)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("8c99979a2b25a46659bff35b238aab1c3158f736f215d99526429c7c96203581")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeVerge, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("DFre88gd87bAZQdnS7dbBLwT6GWiGFMQB6")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeVerge, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeVerge)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeVerge)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeVerge), 6); + ASSERT_EQ(TWBlockchainVerge, TWCoinTypeBlockchain(TWCoinTypeVerge)); + ASSERT_EQ(0x21, TWCoinTypeP2shPrefix(TWCoinTypeVerge)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeVerge)); + assertStringsEqual(symbol, "XVG"); + assertStringsEqual(txUrl, "https://verge-blockchain.info/tx/8c99979a2b25a46659bff35b238aab1c3158f736f215d99526429c7c96203581"); + assertStringsEqual(accUrl, "https://verge-blockchain.info/address/DFre88gd87bAZQdnS7dbBLwT6GWiGFMQB6"); + assertStringsEqual(id, "verge"); + assertStringsEqual(name, "Verge"); +} diff --git a/tests/chains/Verge/TransactionBuilderTests.cpp b/tests/chains/Verge/TransactionBuilderTests.cpp new file mode 100644 index 00000000000..dd0b84e1c94 --- /dev/null +++ b/tests/chains/Verge/TransactionBuilderTests.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Verge/Transaction.h" +#include "Verge/TransactionBuilder.h" +#include "Bitcoin/TransactionPlan.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" + +#include + +#include +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(VergeTransactionBuilder, BuildWithTime) { + const int64_t amount = 1500000000; + const int64_t fee = 2000000; + const std::string toAddress = "DQYMMpqPrnWYZaikKGTQqk5ydUaQw8nkdD"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("DAkEo5pNELZav7MRwBfEwHRG1aChgSUw6c"); + input.set_coin_type(TWCoinTypeVerge); + + auto txHash0 = parse_hex("a5a6e147da0f1b3f6dfd1081f91b0c6e31f030ae66c4be4cf4b0db0ac8b2407d"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(2500000000); + + auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + auto script0 = Bitcoin::Script::lockScriptForAddress("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E", TWCoinTypeVerge); + ASSERT_EQ(hex(script0.bytes), "76a914e4839a523f120882d11eb3dda13a18e11fdcbd4a88ac"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 980000000; + + auto tx = Verge::TransactionBuilder::build(plan, input).payload(); + ASSERT_NE(tx.time, 0ul); +} \ No newline at end of file diff --git a/tests/chains/Verge/TransactionCompilerTests.cpp b/tests/chains/Verge/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..796676fa20f --- /dev/null +++ b/tests/chains/Verge/TransactionCompilerTests.cpp @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" + +#include +#include +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(VergeCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeVerge; + + // tx on mainnet + // https://verge-blockchain.info/tx/21314157b60ddacb842d2a749429c4112724b7a078adb9e77ba502ea2dd7c230 + + const int64_t amount = 9999995000000; + const int64_t fee = 120850; + const std::string toAddress = "DQZboqURLgrBzBz4Kfbs3yV6fZ3DrNFRjQ"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("DCUWt5ctZcPdPMYPV2o1xK1kqv7jNwxu4h"); + input.set_coin_type(coin); + input.set_time(1584059579); + + auto txHash0 = parse_hex("ee839754c8e93d620cbec9a1c51e7b69016d00839741b03af2c039852d941212"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(1); + utxo0->mutable_out_point()->set_sequence(4294967295); + utxo0->set_amount(20000000000000); + + auto script0 = parse_hex("76a91479471b92b3c94b37544fff430556043d9acd53b188ac"); + utxo0->set_script(script0.data(), script0.size()); + + EXPECT_EQ(input.utxo_size(), 1); + + // Plan + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(input, plan, coin); + + plan.set_amount(amount); + plan.set_fee(fee); + plan.set_change(10000004879150); + + // Extend input with accepted plan + *input.mutable_plan() = plan; + + // Serialize input + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + /// Step 2: Obtain preimage hashes + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + TW::Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), (int)preImageHashes.size())); + + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].data_hash()), + "f7498449e2b8d33d4ff00c72b05c820e5262f43360d9f38455dcfd8f6425c9b2"); + + EXPECT_EQ(hex(preSigningOutput.hash_public_keys()[0].public_key_hash()), "79471b92b3c94b37544fff430556043d9acd53b1"); + + auto publicKeyHex = "02b2655122379a375a47e7a204a9dc4572cec5dbe4db4c51fea0c9fa03061fdb0b"; + auto publicKey = PublicKey(parse_hex(publicKeyHex), TWPublicKeyTypeSECP256k1); + auto preImageHash = preSigningOutput.hash_public_keys()[0].data_hash(); + auto signature = parse_hex("3044022039e18d10ab4793d0564cfa675286d2ffd016b8f936c696fd3b72267b621dcd400220653d4761be6b12261629c4240033a08d9767a5f16851dc91a190c8a8d25ecbe0"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE( + publicKey.verifyAsDER(signature, TW::Data(preImageHash.begin(), preImageHash.end()))); + + // Simulate signatures, normally obtained from signature server. + std::vector signatureVec; + std::vector pubkeyVec; + signatureVec.push_back(signature); + pubkeyVec.push_back(publicKey.bytes); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, signatureVec, pubkeyVec); + + const auto ExpectedTx = + "01000000bbd46a5e011212942d8539c0f23ab0419783006d01697b1ec5a1c9be0c623de9c8549783ee010000006a473044022039e18d10ab4793d0564cfa675286d2ffd016b8f936c696fd3b72267b621dcd400220653d4761be6b12261629c4240033a08d9767a5f16851dc91a190c8a8d25ecbe0012102b2655122379a375a47e7a204a9dc4572cec5dbe4db4c51fea0c9fa03061fdb0bffffffff02c054264e180900001976a914d50cce1f1449ac5630a0a731cbfcf7d7208a6e7d88ac2e13bd4e180900001976a91450751a6dc46f7068ac3c6350f6a85f7c20fd5e2988ac00000000"; + { + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: not enough signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, pubkeyVec); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/Viacoin/TWCoinTypeTests.cpp b/tests/chains/Viacoin/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..1c416808887 --- /dev/null +++ b/tests/chains/Viacoin/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWViacoinCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeViacoin)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeViacoin, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeViacoin, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeViacoin)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeViacoin)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeViacoin), 8); + ASSERT_EQ(TWBlockchainBitcoin, TWCoinTypeBlockchain(TWCoinTypeViacoin)); + ASSERT_EQ(0x21, TWCoinTypeP2shPrefix(TWCoinTypeViacoin)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeViacoin)); + assertStringsEqual(symbol, "VIA"); + assertStringsEqual(txUrl, "https://explorer.viacoin.org/tx/t123"); + assertStringsEqual(accUrl, "https://explorer.viacoin.org/address/a12"); + assertStringsEqual(id, "viacoin"); + assertStringsEqual(name, "Viacoin"); +} diff --git a/tests/Viacoin/TWViacoinAddressTests.cpp b/tests/chains/Viacoin/TWViacoinAddressTests.cpp similarity index 95% rename from tests/Viacoin/TWViacoinAddressTests.cpp rename to tests/chains/Viacoin/TWViacoinAddressTests.cpp index 55ef8fe74a5..996afe1fe8b 100644 --- a/tests/Viacoin/TWViacoinAddressTests.cpp +++ b/tests/chains/Viacoin/TWViacoinAddressTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/Viction/TWCoinTypeTests.cpp b/tests/chains/Viction/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..8bb5b07ecb5 --- /dev/null +++ b/tests/chains/Viction/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWVictionType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeViction)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x35a8d3ab06c94d5b7d27221b7c9a24ba3f1710dd0fcfd75c5d59b3a885fd709b")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeViction, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x86cCbD9bfb371c355202086882bC644A7D0b024B")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeViction, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeViction)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeViction)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeViction), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeViction)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeViction)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeViction)); + assertStringsEqual(symbol, "VIC"); + assertStringsEqual(txUrl, "https://www.vicscan.xyz/tx/0x35a8d3ab06c94d5b7d27221b7c9a24ba3f1710dd0fcfd75c5d59b3a885fd709b"); + assertStringsEqual(accUrl, "https://www.vicscan.xyz/address/0x86cCbD9bfb371c355202086882bC644A7D0b024B"); + assertStringsEqual(id, "viction"); + assertStringsEqual(name, "Viction"); +} diff --git a/tests/chains/WAX/TWAnySignerTests.cpp b/tests/chains/WAX/TWAnySignerTests.cpp new file mode 100644 index 00000000000..f81d7663dec --- /dev/null +++ b/tests/chains/WAX/TWAnySignerTests.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include "HexCoding.h" +#include "PublicKey.h" +#include "PrivateKey.h" +#include "EOS/Address.h" +#include "Base58.h" +#include "proto/EOS.pb.h" + +#include + +namespace TW::EOS::tests { + +TEST(TWAnySignerWAX, Sign) { + Proto::SigningInput input; + const auto chainId = parse_hex("1064487b3cd1a897ce03ae5b6a865651747e2e152090f99c1d19d44e01aea5a4"); + const auto refBlock = parse_hex("0cffaeda15039f3468398c5b4295d220fcc217f7cf96030c3729773097c6bd76"); + const auto key = parse_hex("d30d185a296b9591d648cb92fe0aa8f8a42de30ed9d2a21da9e7f69c67e8e355"); + + const auto pubKey = PublicKey(PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1)); + const auto address = Address(pubKey); + EXPECT_EQ(address.string(), "EOS7rC6zYUjuxWkiokZTrwwHqwFvZ15Qdrn5WNxMKVXtHiDDmBWog"); + + auto& asset = *input.mutable_asset(); + asset.set_amount(100000000); + asset.set_decimals(4); + asset.set_symbol("WAX"); + + input.set_chain_id(chainId.data(), chainId.size()); + input.set_reference_block_id(refBlock.data(), refBlock.size()); + input.set_reference_block_time(1670507804); + input.set_currency("eosio.token"); + input.set_sender("k52o1qdeh.gm"); + input.set_recipient("c2lrpvzxb.gm"); + input.set_memo("sent from wallet-core"); + input.set_private_key(key.data(), key.size()); + input.set_private_key_type(Proto::KeyType::MODERNK1); + input.set_expiration(1670507804 + 30); + + // https://wax.bloks.io/transaction/4548f7b28ee608663caea61234049ac0018415e02dd0abcea1c215c8da00d10a + { + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEOS); + + EXPECT_EQ(output.error(), Common::Proto::OK); + auto expected = R"({"compression":"none","packed_context_free_data":"","packed_trx":"3aed9163daae68398c5b000000000100a6823403ea3055000000572d3ccdcd012019682ad940458100000000a8ed3232362019682ad9404581201938fdef7aa34000e1f5050000000004574158000000001573656e742066726f6d2077616c6c65742d636f726500","signatures":["SIG_K1_KAroa9t89dpujjfBgBMgDcZrVhML5yP7iFk5sGNnNqbT4SxTCLqjQwwLZDi1ryx4W7Hy9DE9p1MqUSFVKeY8NtKyiySFjE"]})"; + EXPECT_EQ(output.json_encoded(), expected); + } +} + +} // namespace TW::EOS::tests diff --git a/tests/chains/WAX/TWCoinTypeTests.cpp b/tests/chains/WAX/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..bb223717567 --- /dev/null +++ b/tests/chains/WAX/TWCoinTypeTests.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + + +TEST(TWWAXCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeWAX)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("4548f7b28ee608663caea61234049ac0018415e02dd0abcea1c215c8da00d10a")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeWAX, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("k52o1qdeh.gm")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeWAX, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeWAX)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeWAX)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeWAX), 4); + ASSERT_EQ(TWBlockchainEOS, TWCoinTypeBlockchain(TWCoinTypeWAX)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeWAX)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeWAX)); + assertStringsEqual(symbol, "WAXP"); + assertStringsEqual(txUrl, "https://wax.bloks.io/transaction/4548f7b28ee608663caea61234049ac0018415e02dd0abcea1c215c8da00d10a"); + assertStringsEqual(accUrl, "https://wax.bloks.io/account/k52o1qdeh.gm"); + assertStringsEqual(id, "wax"); + assertStringsEqual(name, "WAX"); +} diff --git a/tests/chains/Wanchain/TWCoinTypeTests.cpp b/tests/chains/Wanchain/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..9d2f2704238 --- /dev/null +++ b/tests/chains/Wanchain/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWWanchainCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeWanchain)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x180ea96a3218b82b9b35d796823266d8a425c182507adfe5eeffc96e6a14d856")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeWanchain, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x69B492D57bb777e97aa7044D0575228434e2E8B1")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeWanchain, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeWanchain)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeWanchain)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeWanchain), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeWanchain)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeWanchain)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeWanchain)); + assertStringsEqual(symbol, "WAN"); + assertStringsEqual(txUrl, "https://www.wanscan.org/tx/0x180ea96a3218b82b9b35d796823266d8a425c182507adfe5eeffc96e6a14d856"); + assertStringsEqual(accUrl, "https://www.wanscan.org/address/0x69B492D57bb777e97aa7044D0575228434e2E8B1"); + assertStringsEqual(id, "wanchain"); + assertStringsEqual(name, "Wanchain"); +} diff --git a/tests/chains/Waves/AddressTests.cpp b/tests/chains/Waves/AddressTests.cpp new file mode 100644 index 00000000000..22de9778b55 --- /dev/null +++ b/tests/chains/Waves/AddressTests.cpp @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "Waves/Address.h" + +#include +#include +#include + +using namespace std; +using namespace TW; + +namespace TW::Waves::tests { + +TEST(WavesAddress, SecureHash) { + const auto secureHash = + hex(Address::secureHash(parse_hex("0157c7fefc0c6acc54e9e4354a81ac1f038e01745731"))); + + ASSERT_EQ(secureHash, "a7978a753c6496866dc75ba3abcaaec796f2380037a1fa7c46cbf9762ee380df"); +} + +TEST(WavesAddress, FromPrivateKey) { + const auto privateKey = + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + const auto publicKeyEd25519 = privateKey.getPublicKey(TWPublicKeyTypeED25519); + ASSERT_EQ(hex(Data(publicKeyEd25519.bytes.begin(), publicKeyEd25519.bytes.end())), + "ff84c4bfc095df25b01e48807715856d95af93d88c5b57f30cb0ce567ca4ced6"); + const auto publicKeyCurve25519 = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); + ASSERT_EQ(hex(Data(publicKeyCurve25519.bytes.begin(), publicKeyCurve25519.bytes.end())), + "559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"); + const auto address = Address(publicKeyCurve25519); + + ASSERT_EQ(address.string(), "3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds"); + + const auto publicKeySECP256k1 = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_ANY_THROW(new Address(publicKeySECP256k1)); +} + +TEST(WavesAddress, FromPublicKey) { + const auto publicKey = + PublicKey(parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"), + TWPublicKeyTypeCURVE25519); + const auto address = Address(publicKey); + + ASSERT_EQ(address.string(), "3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds"); + + const auto address2 = Address(Data(address.bytes.begin(), address.bytes.end())); + ASSERT_EQ(address2.string(), "3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds"); +} + +TEST(WavesAddress, FromData) { + EXPECT_ANY_THROW(new Address(Data{})); +} + +TEST(WavesAddress, Invalid) { + ASSERT_FALSE(Address::isValid(std::string("abc"))); + ASSERT_FALSE(Address::isValid(std::string("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"))); + ASSERT_FALSE(Address::isValid(std::string("3PLANf4MgtNN5v5k4NNnyx2m4zKJiw1tF9v"))); + ASSERT_FALSE(Address::isValid(std::string("3PLANf4MgtNN5v6k4NNnyx2m4zKJiw1tF8v"))); +} + +TEST(WavesAddress, Valid) { + ASSERT_TRUE(Address::isValid(std::string("3PLANf4MgtNN5v6k4NNnyx2m4zKJiw1tF9v"))); + ASSERT_TRUE(Address::isValid(std::string("3PDjjLFDR5aWkKgufika7KSLnGmAe8ueDpC"))); + ASSERT_TRUE(Address::isValid(std::string("3PLjucTjqEfmgBF7fs2CER3fHQapCtknPeW"))); + ASSERT_TRUE(Address::isValid(std::string("3PB9ffP1YKQer3e7t283gPCLyjEfK8xrGp7"))); +} + +TEST(WavesAddress, InitWithString) { + const auto address = Address("3PQupTC1yRiHneotFt79LF2pkN6GrGMwEy3"); + ASSERT_EQ(address.string(), "3PQupTC1yRiHneotFt79LF2pkN6GrGMwEy3"); +} + +TEST(WavesAddress, InitWithInvalidString) { + EXPECT_THROW(Address("3PQupTC1yRiHneotFt79LF2pkN6GrGMwEy2"), invalid_argument); +} + +TEST(WavesAddress, Derive) { + const auto mnemonic = + "water process satisfy repeat flag avoid town badge sketch surge split between cabin sugar " + "ill special axis adjust pull useful craft peace flee physical"; + const auto wallet = HDWallet(mnemonic, ""); + const auto address1 = TW::deriveAddress( + TWCoinTypeWaves, wallet.getKey(TWCoinTypeWaves, DerivationPath("m/44'/5741564'/0'/0'/0'"))); + const auto address2 = TW::deriveAddress( + TWCoinTypeWaves, wallet.getKey(TWCoinTypeWaves, DerivationPath("m/44'/5741564'/0'/0'/1'"))); + + ASSERT_EQ(address1, "3PQupTC1yRiHneotFt79LF2pkN6GrGMwEy3"); + ASSERT_EQ(address2, "3PEXw52bkS9XuLhttWoKyykZjXqEY8zeLxf"); +} + +} // namespace TW::Waves::tests diff --git a/tests/chains/Waves/LeaseTests.cpp b/tests/chains/Waves/LeaseTests.cpp new file mode 100644 index 00000000000..588f7f99db7 --- /dev/null +++ b/tests/chains/Waves/LeaseTests.cpp @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "Waves/Address.h" +#include "Waves/Transaction.h" +#include "proto/Waves.pb.h" + +#include +#include + +using json = nlohmann::json; + +using namespace std; +using namespace TW; + +namespace TW::Waves::tests { + +TEST(WavesLease, serialize) { + const auto privateKey = + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + auto input = Proto::SigningInput(); + input.set_timestamp(int64_t(1526646497465)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto& message = *input.mutable_lease_message(); + message.set_amount(int64_t(100000000)); + message.set_fee(int64_t(100000)); + message.set_to("3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc"); + auto tx1 = Transaction( + input, + /* pub_key */ + parse_hex("425f57a8cb5439e4e912e66376f7041565d029ae4437dae1a3ebe15649d43461")); + auto serialized1 = tx1.serializeToSign(); + ASSERT_EQ(hex(serialized1), "080200425f57a8cb5439e4e912e66376f7041565d029ae4437dae1a3ebe15649d4346101574" + "fdfcd1bfb19114bd2ac369e32013c70c6d03a4627879cbf0000000005f5e100000000000001" + "86a0000001637338e0b9"); +} + +TEST(WavesLease, CancelSerialize) { + const auto privateKey = + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + auto input = Proto::SigningInput(); + input.set_timestamp(int64_t(1568831000826)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto& message = *input.mutable_cancel_lease_message(); + message.set_fee(int64_t(100000)); + message.set_lease_id("44re3UEDw1QwPFP8dKzfuGHVMNBejUW9NbhxG6b4KJ1T"); + auto tx1 = Transaction( + input, + /* pub_key */ + parse_hex("425f57a8cb5439e4e912e66376f7041565d029ae4437dae1a3ebe15649d43461")); + auto serialized1 = tx1.serializeToSign(); + ASSERT_EQ(hex(serialized1), "090257425f57a8cb5439e4e912e66376f7041565d029ae4437dae1a3ebe15649d" + "4346100000000000186a00000016d459d50fa2d8fee08efc97f79bcd97a4d977c" + "76183580d723909af2b50e72b02f1e36707e"); +} + +TEST(WavesLease, jsonSerialize) { + const auto privateKey = PrivateKey(parse_hex( + "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + const auto publicKeyCurve25519 = + privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); + auto input = Proto::SigningInput(); + input.set_timestamp(int64_t(1568973547102)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto& message = *input.mutable_lease_message(); + message.set_amount(int64_t(100000)); + message.set_fee(int64_t(100000)); + message.set_to("3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc"); + auto tx1 = Transaction(input, + /* pub_key */ + parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d")); + + auto signature = Signer::sign(privateKey, tx1); + auto json = tx1.buildJson(signature); + + ASSERT_EQ(json["type"], TransactionType::lease); + ASSERT_EQ(json["version"], TransactionVersion::V2); + ASSERT_EQ(json["fee"], int64_t(100000)); + ASSERT_EQ(json["senderPublicKey"], + "6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU"); + ASSERT_EQ(json["timestamp"], int64_t(1568973547102)); + ASSERT_EQ(json["proofs"].dump(), + "[\"4opce9e99827upK3m3D3NicnvBqbMLtAJ4Jc8ksTLiScqBgjdqzr9JyXG" + "C1NAGZUbkqJvix9bNrBokrxtGruwmu3\"]"); + ASSERT_EQ(json["recipient"], "3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc"); + ASSERT_EQ(json["amount"], int64_t(100000)); + ASSERT_EQ(json.dump(), + "{\"amount\":100000,\"fee\":100000,\"proofs\":[" + "\"4opce9e99827upK3m3D3NicnvBqbMLtAJ4Jc8ksTLiScqBgjdqzr9JyXGC1NAGZUbkqJ" + "vix9bNrBokrxtGruwmu3\"],\"recipient\":" + "\"3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc\",\"senderPublicKey\":" + "\"6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU\",\"timestamp\":" + "1568973547102,\"type\":8,\"version\":2}"); +} + +TEST(WavesLease, jsonCancelSerialize) { + const auto privateKey = PrivateKey(parse_hex( + "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + const auto publicKeyCurve25519 = + privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); + auto input = Proto::SigningInput(); + input.set_timestamp(int64_t(1568973547102)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto& message = *input.mutable_cancel_lease_message(); + message.set_lease_id("DKhmXrCsBwf6WVhGh8bYVBnjtAXGpk2K4Yd3CW4u1huG"); + message.set_fee(int64_t(100000)); + auto tx1 = Transaction(input, + /* pub_key */ + parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d")); + auto signature = Signer::sign(privateKey, tx1); + auto json = tx1.buildJson(signature); + + ASSERT_EQ(json["type"], TransactionType::cancelLease); + ASSERT_EQ(json["version"], TransactionVersion::V2); + ASSERT_EQ(json["fee"], int64_t(100000)); + ASSERT_EQ(json["senderPublicKey"], + "6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU"); + ASSERT_EQ(json["leaseId"], "DKhmXrCsBwf6WVhGh8bYVBnjtAXGpk2K4Yd3CW4u1huG"); + ASSERT_EQ(json["chainId"], 87); + ASSERT_EQ(json["timestamp"], int64_t(1568973547102)); + ASSERT_EQ(json["proofs"].dump(), + "[\"Mwhh7kdbhPv9vtnPh6pjEcHTFJ5h5JtAziwFpqH8Ykw1yWYie4Nquh" + "eYtAWPbRowgpDVBxvG1rTrv82LnFdByQY\"]"); + ASSERT_EQ(json.dump(), + "{\"chainId\":87,\"fee\":100000,\"leaseId\":\"DKhmXrCsBwf6WVhGh8bYVBnjtAXGpk2K4Yd3CW4u1huG\"," + "\"proofs\":[\"Mwhh7kdbhPv9vtnPh6pjEcHTFJ5h5JtAziwFpqH8Ykw1yWYie4NquheYtAWP" + "bRowgpDVBxvG1rTrv82LnFdByQY\"],\"senderPublicKey\":" + "\"6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU\",\"timestamp\":" + "1568973547102,\"type\":9,\"version\":2}"); +} + +} // namespace TW::Waves::tests diff --git a/tests/chains/Waves/SignerTests.cpp b/tests/chains/Waves/SignerTests.cpp new file mode 100644 index 00000000000..bbc63fad0c5 --- /dev/null +++ b/tests/chains/Waves/SignerTests.cpp @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PublicKey.h" +#include "Waves/Signer.h" +#include "Waves/Transaction.h" + +#include +#include + +using namespace TW; + +namespace TW::Waves::tests { + +TEST(WavesSigner, SignTransaction) { + const auto privateKey = + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + const auto publicKeyCurve25519 = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); + ASSERT_EQ(hex(Data(publicKeyCurve25519.bytes.begin(), publicKeyCurve25519.bytes.end())), + "559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"); + // 3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds + const auto address = Address(publicKeyCurve25519); + auto input = Proto::SigningInput(); + input.set_timestamp(int64_t(1526641218066)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto& message = *input.mutable_transfer_message(); + message.set_amount(int64_t(100000000)); + message.set_asset(Transaction::WAVES); + message.set_fee(int64_t(100000000)); + message.set_fee_asset(Transaction::WAVES); + message.set_to(address.string()); + message.set_attachment("falafel"); + auto tx1 = Transaction( + input, + /* pub_key */ + parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d")); + + auto signature = Signer::sign(privateKey, tx1); + + EXPECT_EQ(hex(tx1.serializeToSign()), + "0402559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d00000000016372e8" + "52120000000005f5e1000000000005f5e10001570acc4110b78a6d38b34d879b5bba38806202ecf1732f" + "8542000766616c6166656c"); + EXPECT_EQ(hex(signature), "af7989256f496e103ce95096b3f52196dd9132e044905fe486da3b829b5e403bcba9" + "5ab7e650a4a33948c2d05cfca2dce4d4df747e26402974490fb4c49fbe8f"); + + ASSERT_TRUE(publicKeyCurve25519.verify(signature, tx1.serializeToSign())); +} + +TEST(WavesSigner, curve25519_pk_to_ed25519) { + const auto publicKeyCurve25519 = + parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"); + auto r = Data(); + r.resize(32); + curve25519_pk_to_ed25519(r.data(), publicKeyCurve25519.data()); + EXPECT_EQ(hex(r), "ff84c4bfc095df25b01e48807715856d95af93d88c5b57f30cb0ce567ca4ce56"); +} + +} // namespace TW::Waves::tests diff --git a/tests/chains/Waves/TWAnySignerTests.cpp b/tests/chains/Waves/TWAnySignerTests.cpp new file mode 100644 index 00000000000..23e858b682c --- /dev/null +++ b/tests/chains/Waves/TWAnySignerTests.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base58.h" +#include "HexCoding.h" +#include "proto/Waves.pb.h" +#include "TestUtilities.h" +#include +#include + +namespace TW::Waves::tests { + +TEST(TWAnySignerWaves, Sign) { + auto input = Proto::SigningInput(); + const auto privateKey = Base58::decode("83mqJpmgB5Mko1567sVAdqZxVKsT6jccXt3eFSi4G1zE"); + + input.set_timestamp(int64_t(1559146613)); + input.set_private_key(privateKey.data(), privateKey.size()); + auto& message = *input.mutable_transfer_message(); + message.set_amount(int64_t(100000000)); + message.set_asset("DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq"); + message.set_fee(int64_t(100000)); + message.set_fee_asset("DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq"); + message.set_to("3PPCZQkvdMJpmx7Zrz1cnYsPe9Bt1XT2Ckx"); + message.set_attachment("hello"); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeWaves); + + ASSERT_EQ(hex(output.signature()), "5d6a77b1fd9b53d9735cd2543ba94215664f2b07d6c7befb081221fcd49f5b6ad6b9ac108582e8d3e74943bdf35fd80d985edf4b4de1fb1c5c427e84d0879f8f"); +} + +} // namespace TW::Waves::tests diff --git a/tests/chains/Waves/TWCoinTypeTests.cpp b/tests/chains/Waves/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..4e3fdc744e1 --- /dev/null +++ b/tests/chains/Waves/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWWavesCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeWaves)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeWaves, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeWaves, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeWaves)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeWaves)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeWaves), 8); + ASSERT_EQ(TWBlockchainWaves, TWCoinTypeBlockchain(TWCoinTypeWaves)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeWaves)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeWaves)); + assertStringsEqual(symbol, "WAVES"); + assertStringsEqual(txUrl, "https://wavesexplorer.com/tx/t123"); + assertStringsEqual(accUrl, "https://wavesexplorer.com/address/a12"); + assertStringsEqual(id, "waves"); + assertStringsEqual(name, "Waves"); +} diff --git a/tests/chains/Waves/TransactionTests.cpp b/tests/chains/Waves/TransactionTests.cpp new file mode 100644 index 00000000000..a71ca9c9069 --- /dev/null +++ b/tests/chains/Waves/TransactionTests.cpp @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "Waves/Address.h" +#include "proto/Waves.pb.h" +#include "Waves/Transaction.h" + +#include +#include + +namespace TW::Waves::tests { + +using json = nlohmann::json; +using namespace std; + +TEST(WavesTransaction, serialize) { + const auto privateKey = + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + auto input = Proto::SigningInput(); + input.set_timestamp(int64_t(1526641218066)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto& message = *input.mutable_transfer_message(); + message.set_amount(int64_t(100000000)); + message.set_asset(""); + message.set_fee(int64_t(100000000)); + message.set_fee_asset(Transaction::WAVES); + message.set_to("3PLgzJXQiN77G7KgnR1WVa8jBYhF2dmWndx"); + message.set_attachment("falafel"); + auto tx1 = Transaction( + input, + /* pub_key */ + parse_hex("d528aabec35ca100d87c7b7a128632faf19cd44531819457445113a32a21ef22")); + auto serialized1 = tx1.serializeToSign(); + ASSERT_EQ(hex(serialized1), "0402d528aabec35ca100d87c7b7a128632faf19cd44531819457445113a32a21ef" + "2200000000016372e852120000000005f5e1000000000005f5e1000157cdc9381c" + "071beb5abd27738d5cd36cf75f3cbfdd69e8e6bb000766616c6166656c"); + + auto input2 = Proto::SigningInput(); + input2.set_timestamp(int64_t(1)); + input2.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto& message2 = *input2.mutable_transfer_message(); + message2.set_amount(int64_t(1)); + message2.set_asset("DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq"); + message2.set_fee(int64_t(1)); + message2.set_fee_asset("DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq"); + message2.set_to("3PLgzJXQiN77G7KgnR1WVa8jBYhF2dmWndx"); + message2.set_attachment(""); + + auto tx2 = Transaction( + input2, + /* pub_key */ + parse_hex("d528aabec35ca100d87c7b7a128632faf19cd44531819457445113a32a21ef22")); + auto serialized2 = tx2.serializeToSign(); + ASSERT_EQ(hex(serialized2), + "0402d528aabec35ca100d87c7b7a128632faf19cd44531819457445113a32a21ef2201bae8ddc9955fa6" + "f69f8e7b155efcdb97bc3bb3a95db4c4604408cec245cd187201bae8ddc9955fa6f69f8e7b155efcdb97" + "bc3bb3a95db4c4604408cec245cd18720000000000000001000000000000000100000000000000010157" + "cdc9381c071beb5abd27738d5cd36cf75f3cbfdd69e8e6bb0000"); +} + +TEST(WavesTransaction, failedSerialize) { + // 141 bytes attachment + const auto privateKey = + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + auto input = Proto::SigningInput(); + input.set_timestamp(int64_t(1526641218066)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto& message = *input.mutable_transfer_message(); + message.set_amount(int64_t(100000000)); + message.set_asset(""); + message.set_fee(int64_t(100000000)); + message.set_fee_asset(""); + message.set_to("3PLgzJXQiN77G7KgnR1WVa8jBYhF2dmWndx"); + message.set_attachment("falafelfalafelfalafelfalafelfalafelfalafelfalafel" + "falafelfalafelfalafelfalafelfalafelfalafelfalafel" + "falafelfalafelfalafelfalafelfalafelfalafel"); + auto tx1 = Transaction( + input, + /* pub_key */ + parse_hex("d528aabec35ca100d87c7b7a128632faf19cd44531819457445113a32a21ef22")); + EXPECT_THROW(tx1.serializeToSign(), invalid_argument); +} + +TEST(WavesTransaction, jsonSerialize) { + + const auto privateKey = + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + const auto publicKeyCurve25519 = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); + ASSERT_EQ(hex(Data(publicKeyCurve25519.bytes.begin(), publicKeyCurve25519.bytes.end())), + "559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"); + const auto address = Address(publicKeyCurve25519); + + auto input = Proto::SigningInput(); + input.set_timestamp(int64_t(1526641218066)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto& message = *input.mutable_transfer_message(); + message.set_amount(int64_t(10000000)); + message.set_asset("DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq"); + message.set_fee(int64_t(100000000)); + message.set_fee_asset("DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD82zq"); + message.set_to(address.string()); + message.set_attachment("falafel"); + auto tx1 = Transaction( + input, + /* pub_key */ + parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d")); + + auto signature = Signer::sign(privateKey, tx1); + + auto json = tx1.buildJson(signature); + + ASSERT_EQ(json["type"], TransactionType::transfer); + ASSERT_EQ(json["version"], TransactionVersion::V2); + ASSERT_EQ(json["fee"], int64_t(100000000)); + ASSERT_EQ(json["senderPublicKey"], "6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU"); + ASSERT_EQ(json["timestamp"], int64_t(1526641218066)); + ASSERT_EQ(json["proofs"].dump(), "[\"5ynN2NUiFHkQzw9bK8R7dZcNfTWMAtcWRJsrMvFFM6dUT3fSnPCCX7CTajNU8bJCB" + "H69vU1mnwfx4zpDtF1SkzKg\"]"); + ASSERT_EQ(json["recipient"], "3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds"); + ASSERT_EQ(json["assetId"], "DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq"); + ASSERT_EQ(json["feeAssetId"], "DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD82zq"); + ASSERT_EQ(json["amount"], int64_t(10000000)); + ASSERT_EQ(json["attachment"], "4t2Xazb2SX"); + ASSERT_EQ(json.dump(), "{\"amount\":10000000,\"assetId\":\"DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD81zq\",\"attachment\":\"4t2Xazb2SX\",\"fee\":100000000,\"feeAssetId\":\"DacnEpaUVFRCYk8Fcd1F3cqUZuT4XG7qW9mRyoZD82zq\",\"proofs\":[\"5ynN2NUiFHkQzw9bK8R7dZcNfTWMAtcWRJsrMvFFM6dUT3fSnPCCX7CTajNU8bJCBH69vU1mnwfx4zpDtF1SkzKg\"],\"recipient\":\"3P2uzAzX9XTu1t32GkWw68YFFLwtapWvDds\",\"senderPublicKey\":\"6mA8eQjie53kd4jbZrwL3ZhMBqCX6nzit1k55tR2X7zU\",\"timestamp\":1526641218066,\"type\":4,\"version\":2}"); +} + +} // namespace TW::Waves::tests diff --git a/tests/chains/XRP/AddressTests.cpp b/tests/chains/XRP/AddressTests.cpp new file mode 100644 index 00000000000..cac0f014219 --- /dev/null +++ b/tests/chains/XRP/AddressTests.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "XRP/Address.h" +#include "XRP/XAddress.h" +#include "HexCoding.h" + +#include + +namespace TW::Ripple { + +TEST(RippleAddress, FromPublicKey) { + const auto publicKey = PublicKey(parse_hex("0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D"), TWPublicKeyTypeSECP256k1); + const auto address = Address(publicKey); + ASSERT_EQ(std::string("rnBFvgZphmN39GWzUJeUitaP22Fr9be75H"), address.string()); +} + +TEST(RippleAddress, FromString) { + std::string classic = "rnBFvgZphmN39GWzUJeUitaP22Fr9be75H"; + const auto address = Address(classic); + + ASSERT_EQ(address.string(), classic); +} + +TEST(RippleXAddress, FromPublicKey) { + const auto publicKey = PublicKey(parse_hex("0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D"), TWPublicKeyTypeSECP256k1); + const auto address = XAddress(publicKey, 12345); + ASSERT_EQ(std::string("X76UnYEMbQfEs3mUqgtjp4zFy9exgThRj7XVZ6UxsdrBptF"), address.string()); +} + +TEST(RippleXAddress, FromString) { + std::string xAddress = "X76UnYEMbQfEs3mUqgtjp4zFy9exgThRj7XVZ6UxsdrBptF"; + std::string xAddress2 = "X76UnYEMbQfEs3mUqgtjp4zFy9exgTsM93nriVZAPufrpE3"; + const auto address = XAddress(xAddress); + const auto address2 = XAddress(xAddress2); + + ASSERT_EQ(address.tag, 12345ul); + ASSERT_EQ(address.string(), xAddress); + + ASSERT_EQ(address2.tag, 0ul); + ASSERT_EQ(address2.string(), xAddress2); +} + +TEST(RippleAddress, isValid) { + std::string classicAddress = "r36yxStAh7qgTQNHTzjZvXybCTzUFhrfav"; + std::string bitcoinAddress = "1Ma2DrB78K7jmAwaomqZNRMCvgQrNjE2QC"; + std::string xAddress = "XVfvixWZQKkcenFRYApCjpTUyJ4BePTe3jJv7beatUZvQYh"; + + ASSERT_TRUE(Address::isValid(classicAddress)); + ASSERT_TRUE(XAddress::isValid(xAddress)); + ASSERT_FALSE(Address::isValid(bitcoinAddress)); + ASSERT_FALSE(XAddress::isValid(bitcoinAddress)); +} + +} // namespace TW::Ripple diff --git a/tests/chains/XRP/BinaryCodingTests.cpp b/tests/chains/XRP/BinaryCodingTests.cpp new file mode 100644 index 00000000000..a8081beb0fc --- /dev/null +++ b/tests/chains/XRP/BinaryCodingTests.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "XRP/BinaryCoding.h" + +#include "TestUtilities.h" +#include + +namespace TW::Ripple::tests { +using namespace std; + +TEST(RippleBinaryCoding, encodeVariableLength) { + Data data; + encodeVariableLength(180, data); + EXPECT_EQ(hex(data), "b4"); + + data.clear(); + encodeVariableLength(12080, data); + EXPECT_EQ(hex(data), "2e6f"); + + data.clear(); + encodeVariableLength(12580, data); + EXPECT_EQ(hex(data), "000063"); +} + +} // namespace TW::Ripple::tests diff --git a/tests/chains/XRP/TWAnySignerTests.cpp b/tests/chains/XRP/TWAnySignerTests.cpp new file mode 100644 index 00000000000..3dd17ea7d1b --- /dev/null +++ b/tests/chains/XRP/TWAnySignerTests.cpp @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/Common.pb.h" +#include "proto/Ripple.pb.h" +#include + +#include "TestUtilities.h" +#include + +using namespace TW; + +namespace TW::Ripple::tests { + +TEST(TWAnySignerRipple, SignXrpPayment) { + // https://testnet.xrpl.org/transactions/A202034796F37F38D1D20F2025DECECB1623FC801F041FC694199C0D0E49A739 + auto key = parse_hex("a5576c0f63da10e584568c8d134569ff44017b0a249eb70657127ae04f38cc77"); + Proto::SigningInput input; + + input.mutable_op_payment()->set_amount(10); + input.set_fee(10); + input.set_sequence(32268248); + input.set_last_ledger_sequence(32268269); + input.set_account("rfxdLwsZnoespnTDDb1Xhvbc8EFNdztaoq"); + input.mutable_op_payment()->set_destination("rU893viamSnsfP3zjzM2KPxjqZjXSXK6VF"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "12000022000000002401ec5fd8201b01ec5fed61400000000000000a68400000000000000a732103d13e1152965a51a4a9fd9a8b4ea3dd82a4eba6b25fcad5f460a2342bb650333f74463044022037d32835c9394f39b2cfd4eaf5b0a80e0db397ace06630fa2b099ff73e425dbc02205288f780330b7a88a1980fa83c647b5908502ad7de9a44500c08f0750b0d9e8481144c55f5a78067206507580be7bb2686c8460adff983148132e4e20aecf29090ac428a9c43f230a829220d"); + + // invalid tag + input.mutable_op_payment()->set_destination_tag(641505641505); + + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::Error_invalid_memo); + EXPECT_EQ(output.encoded(), ""); +} + +TEST(TWAnySignerRipple, SignXrpPaymentMain) { + // https://xrpscan.com/tx/4B9D022E8C77D798B7D11C41FDFDCF468F03A5564151C520EECA1E96FF1A1610 + auto key = parse_hex("acf1bbf6264e699da0cc65d17ac03fcca6ded1522d19529df7762db46097ff9f"); + Proto::SigningInput input; + + input.mutable_op_payment()->set_amount(1000000); + input.set_fee(10); + input.set_sequence(75674534); + input.set_last_ledger_sequence(75674797); + input.set_account("rGV1v1xw23PHcRn4Km4tF8R2mfh6yTZkcP"); + input.mutable_op_payment()->set_destination("rNLpgsBTCwiaZAnHe2ZViAN1GcXZtYW6rg"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "1200002200000000240482b3a6201b0482b4ad6140000000000f424068400000000000000a7321027efc5f15071d2ae5e73ee09a0c17456c5d9170a41d67e3297c554829199be80b74473045022100e1c746c3aeebc8278c627ee4c2ce5cae97e3856292c7fe5388f803920230a37b02207d2eccb76cd35dd379d6b24c2cabd786e62d34a564cf083e863176109c5b6bb48114aa000c09c692ef1f82787e51e22833149941ea2083149232ef60695add51f0f84534cc4084e4fdfc698e"); +} + +TEST(TWAnySignerRipple, SignTrustSetPayment) { + // https://testnet.xrpl.org/transactions/31042345374CFF785B3F7E2A3716E3BAB7E2CAA30D40F5E488E67ABA116655B9 + auto key = parse_hex("8753e78ee2963f301f82e5eeab2754f593fc242ce94273dd2fb0684e3b0f2b91"); + Proto::SigningInput input; + + input.mutable_op_trust_set()->mutable_limit_amount()->set_currency("USD"); + input.mutable_op_trust_set()->mutable_limit_amount()->set_value("10"); + input.mutable_op_trust_set()->mutable_limit_amount()->set_issuer("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"); + input.set_fee(10); + input.set_sequence(32268473); + input.set_last_ledger_sequence(32268494); + input.set_account("rnRkLPni2Q5yMxSqyJSJEkKUfQNFkaAspS"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "12001422000000002401ec60b9201b01ec60ce63d4c38d7ea4c6800000000000000000000000000055534400000000004b4e9c06f24296074f7bc48f92a97916c6dc5ea968400000000000000a732103dc4a0dae2d550de7cace9c26c1a331a114e3e7efee5577204b476d27e2dc683a7446304402206ebcc7a689845df373dd2566cd3789862d426d9ad4e6a09c2d2772b57e82696a022066b1f217a0f0d834d167613a313f74097423a9ccd11f1ae7f90ffab0d2fc26b58114308ea8e515b64f2e6616a33b42e1bbb9fa00bbd2"); +} + +TEST(TWAnySignerRipple, SignTokenPayment0) { + // https://testnet.xrpl.org/transactions/8F7820892294598B58CFA2E1101D15ED98C179B25A2BA6DAEB4F5B727CB00D4E + auto key = parse_hex("4ba5fd2ebf0f5d7e579b3c354c263ebb39cda4093845125786a280301af14e21"); + Proto::SigningInput input; + input.mutable_op_payment()->mutable_currency_amount()->set_currency("USD"); + input.mutable_op_payment()->mutable_currency_amount()->set_value("10"); + input.mutable_op_payment()->mutable_currency_amount()->set_issuer("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"); + input.set_fee(10); + input.set_sequence(32268645); + input.set_last_ledger_sequence(32268666); + input.set_account("raPAA61ca99bdwNiZs5JJukR5rvkHWvkBX"); + input.mutable_op_payment()->set_destination("rU893viamSnsfP3zjzM2KPxjqZjXSXK6VF"); + input.set_private_key(key.data(), key.size()); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + EXPECT_EQ(hex(output.encoded()), "12000022000000002401ec6165201b01ec617a61d4c38d7ea4c6800000000000000000000000000055534400000000004b4e9c06f24296074f7bc48f92a97916c6dc5ea968400000000000000a7321020652a477b0cca8b74d6e68a6a386a836b226101617481b95180eaffbe841b3227446304402203e925caeb05006afb135254e9ae4e46de2019db6c6f68614ef969885063a777602206af110fc29775256fcad8b14974c6a838141d82193192d3b57324fe1079afa1781143b2fa4f36553e5b7a4f54ff9e6883e44b4b0dbb383148132e4e20aecf29090ac428a9c43f230a829220d"); +} + +TEST(TWAnySignerRipple, SignTokenPayment1) { + // https://testnet.xrpl.org/transactions/14606DAAFA54DB29B738000DFC133312B341FFC1D22D57AE0C8D54C9C56E19D8 + auto key = parse_hex("4041882ce8c2ceea6f4cfe1a067b927c1e1eb2f5eb025eaf2f429479a7ec3738"); + Proto::SigningInput input; + + input.mutable_op_payment()->mutable_currency_amount()->set_currency("USD"); + input.mutable_op_payment()->mutable_currency_amount()->set_value("29.3e-1"); + input.mutable_op_payment()->mutable_currency_amount()->set_issuer("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"); + input.set_fee(10); + input.set_sequence(32268768); + input.set_last_ledger_sequence(32268789); + input.set_account("raJe5XVt99649qn5Pg7cKdmgEYdN3d4Mky"); + input.mutable_op_payment()->set_destination("rU893viamSnsfP3zjzM2KPxjqZjXSXK6VF"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "12000022000000002401ec61e0201b01ec61f561d48a68d1c931200000000000000000000000000055534400000000004b4e9c06f24296074f7bc48f92a97916c6dc5ea968400000000000000a73210348c331ab218ba964150490c83875b06ccad2100b1f5707f296764712738cf1ca74473045022100a938783258d33e2e3e6099d1ab68fd85c3fd21adfa00e136a67bed8fddec6c9a02206cc6784c1f212f19dc939207643d361ceaa8334eb366722cf33b24dc7669dd7a81143a2f2f189d05abb8519cc9dee0e2dbc6fa53924183148132e4e20aecf29090ac428a9c43f230a829220d"); +} + +TEST(TWAnySignerRipple, SignEscrowCreateMain) { + // https://xrpscan.com/tx/3576E5D413CBDC228D13F281BB66304C1EE9DDEAA5563F1783EDB1848266D739 + auto key = parse_hex("a3cf20a85b25be4c955f0814718cc7a02eae9195159bd72ede5dd5c4e60d22c4"); + Proto::SigningInput input; + + // with finish after and dest tag + input.mutable_op_escrow_create()->set_amount(21300); + input.mutable_op_escrow_create()->set_destination("rEeSXUWEYyEADhDHvi3mtahkFVn7dYNH2G"); + input.mutable_op_escrow_create()->set_destination_tag(67); + input.mutable_op_escrow_create()->set_cancel_after(755015907); + input.mutable_op_escrow_create()->set_finish_after(755015897); + input.mutable_op_escrow_create()->set_condition(""); + + input.set_fee(12); + input.set_sequence(84363229); + input.set_last_ledger_sequence(84363920); + input.set_account("rnXwGtLDXXcV63CnRoNaesSsJCZZkJwo9w"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120001220000000024050747dd2e00000043201b05074a9020242d00a0e320252d00a0d961400000000000533468400000000000000c7321029b557f4db390d68d39d3457204c225d4a68ed86854567a1da99d3e2cd640717374473045022100e62d5005401f1d2b1d9eaa42e0fdbb8b8a433d0cfe71455e782882aa6ab0656f02207b589489b4f344e87a956382e5ede6a55fbfc7e38701364c1fe7d056e9a3253a81143194b932f389b95922fba31662f3c8a606fedfd68314a0a67483ad4d51b2524eb304c0fcef6b2025b865"); +} + +TEST(TWAnySignerRipple, SignEscrowCreate) { + // https://testnet.xrpl.org/transactions/3F581927C742D5FAE65FB0759D0F04EF3B64B4A087911B07975816ECCB59915B + auto key = parse_hex("f157cf7951908b9a2b28d6c5817a3212c3971d8c05a1e964bbafaa5ad7529cb0"); + Proto::SigningInput input; + + // with finish after and dest tag + input.mutable_op_escrow_create()->set_amount(345941506); + input.mutable_op_escrow_create()->set_destination("rNS1tYfynXoKC3eX52gvVnSyU9mqWXvCgh"); + input.mutable_op_escrow_create()->set_destination_tag(2467); + input.mutable_op_escrow_create()->set_cancel_after(0); + input.mutable_op_escrow_create()->set_finish_after(750095491); + input.mutable_op_escrow_create()->set_condition(""); + + input.set_fee(12); + input.set_sequence(41874843); + input.set_last_ledger_sequence(41874865); + input.set_account("rL6iE1bbAHekMavpGot6gRxqkQKm6yfoQ6"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120001220000000024027ef59b2e000009a3201b027ef5b120252cb58c836140000000149ea60268400000000000000c7321021846a49ea81238d03dff5a89a9da82eb06b23a276af9a06b45d4aba39713311f744630440220176318f29d2b815f599072230690397f91262c1f801bafada9820d89c719359c0220756eb74d815e20e86f6748c6821d3204f93221a95b4481a572a10530f5776c698114d8242542e6108fccf75a7f5bb0059cfae6d155378314937e838cb1033342c72acfae58fe2e3875ce7693"); +} + +TEST(TWAnySignerRipple, SignEscrowCreate2) { + // https://testnet.xrpl.org/transactions/3F581927C742D5FAE65FB0759D0F04EF3B64B4A087911B07975816ECCB59915B + auto key = parse_hex("8b488ed9b9875174140a97cad53cd8c652789889612f94a9006b7ced18a1c6ef"); + Proto::SigningInput input; + + // with cancel after > 0x7fffffff + input.mutable_op_escrow_create()->set_amount(88941506); + input.mutable_op_escrow_create()->set_destination("rfC73DuBhDqF3Zw1K3uxaQNCkwT8pPKyf5"); + input.mutable_op_escrow_create()->set_destination_tag(0); + input.mutable_op_escrow_create()->set_cancel_after(2147483648); + input.mutable_op_escrow_create()->set_finish_after(750097108); + input.mutable_op_escrow_create()->set_condition(""); + + input.set_fee(12); + input.set_sequence(41875372); + input.set_last_ledger_sequence(41875394); + input.set_account("rEE4PdEYhEikJ1bvQjdE9HdjBV8yp8FsGC"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120001220000000024027ef7ac201b027ef7c220248000000020252cb592d46140000000054d23c268400000000000000c73210211cfeb81bc410e694e98c6a0f17c9c89d85e2b89bc17d2699063c0920217ab0574463044022038d27cd842422d8ee72d5cab11734ce128aef21d7cec17654d21c27d0556d23e0220059f913178a4c65a5d3289896876989e0fcaf3add9769459fb232ab94398368a81149c4970a2b763b9484e3b65d67f3d9b7b1698cb7f83144917342345fbe5cef1e22d3f1353fc468bf696ac"); +} + +TEST(TWAnySignerRipple, SignEscrowCreateWithConditionMain) { + // https://xrpscan.com/tx/77E01FD30A788BFC96F28960F099D4076255252F33FCD31EEBBCBB61E3318544 + auto key = parse_hex("a3cf20a85b25be4c955f0814718cc7a02eae9195159bd72ede5dd5c4e60d22c4"); + Proto::SigningInput input; + + // with cancel after and crypto condition + input.mutable_op_escrow_create()->set_amount(37000); + input.mutable_op_escrow_create()->set_destination("rEeSXUWEYyEADhDHvi3mtahkFVn7dYNH2G"); + input.mutable_op_escrow_create()->set_destination_tag(0); + input.mutable_op_escrow_create()->set_cancel_after(755014300); + input.mutable_op_escrow_create()->set_finish_after(0); + input.mutable_op_escrow_create()->set_condition("a0258020c26add2db64dd6d5700a5e2721c1e908d599901627b8dc82f25b3e035ec4004b810120"); // PREIMAGE-SHA-256 crypto-condition of secret 5d729ac237c4c7976403817b6409be7190efbfad49af2cf974b9582a854e8794 + + input.set_fee(12); + input.set_sequence(84363226); + input.set_last_ledger_sequence(84363509); + input.set_account("rnXwGtLDXXcV63CnRoNaesSsJCZZkJwo9w"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120001220000000024050747da201b050748f520242d009a9c61400000000000908868400000000000000c7321029b557f4db390d68d39d3457204c225d4a68ed86854567a1da99d3e2cd6407173744630440220307f4c91e91166db1428eb1ab8f65a84bd9b89542ed844045ffd040f5e13d12b022061120350b9685381e9941c7ec54ce154ca0ef0d01f630aeb3e78dd9fd087ff80701127a0258020c26add2db64dd6d5700a5e2721c1e908d599901627b8dc82f25b3e035ec4004b81012081143194b932f389b95922fba31662f3c8a606fedfd68314a0a67483ad4d51b2524eb304c0fcef6b2025b865"); +} + +TEST(TWAnySignerRipple, SignEscrowCreateWithCondition) { + // https://testnet.xrpl.org/transactions/A8EE35E26CD09E3D6A415DDEFEA6723CA5AFEB1838C5FE06835937FA49DEF3A0 + auto key = parse_hex("a3cf20a85b25be4c955f0814718cc7a02eae9195159bd72ede5dd5c4e60d22c4"); + Proto::SigningInput input; + + // with cancel after and crypto condition + input.mutable_op_escrow_create()->set_amount(30941506); + input.mutable_op_escrow_create()->set_destination("rEeSXUWEYyEADhDHvi3mtahkFVn7dYNH2G"); + input.mutable_op_escrow_create()->set_destination_tag(0); + input.mutable_op_escrow_create()->set_cancel_after(750090371); + input.mutable_op_escrow_create()->set_finish_after(0); + input.mutable_op_escrow_create()->set_condition("a0258020b3dda5c580919ce0fd6acdf013c337461951946e54b41446467961568cdd9e7b810120"); // PREIMAGE-SHA-256 crypto-condition of secret b3dda5c580919ce0fd6acdf013c337461951946e54b41446467961568cdd9e7b + + input.set_fee(12); + input.set_sequence(41872968); + input.set_last_ledger_sequence(41873012); + input.set_account("rnXwGtLDXXcV63CnRoNaesSsJCZZkJwo9w"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120001220000000024027eee48201b027eee7420242cb57883614000000001d8214268400000000000000c7321029b557f4db390d68d39d3457204c225d4a68ed86854567a1da99d3e2cd640717374473045022100931b3a6634471fa22f709417d7280b76564a8f3a700cf51a50a2c1b1e0162d570220217c0f2e3922e9bc5b2175712c0e244f2f05bf42ccd1e632b06476f66704203f701127a0258020b3dda5c580919ce0fd6acdf013c337461951946e54b41446467961568cdd9e7b81012081143194b932f389b95922fba31662f3c8a606fedfd68314a0a67483ad4d51b2524eb304c0fcef6b2025b865"); +} + +TEST(TWAnySignerRipple, SignEscrowCreateWithCondition2) { + // https://testnet.xrpl.org/transactions/25AE9F7CBC9944B140A4BE338A47DD8C2C29313B44694533D9D47CD758A60A8F + auto key = parse_hex("be60f33cbeb2b5ee688dcb1e93986f2522d8ad76b3c48398bf2be02a6699e781"); + Proto::SigningInput input; + + // with cancel after, crypto condition and dest tag + input.mutable_op_escrow_create()->set_amount(28941506); + input.mutable_op_escrow_create()->set_destination("r9YD31TAtbS8EPwEt2gzGDjsaMDyV1s5QE"); + input.mutable_op_escrow_create()->set_destination_tag(2467); + input.mutable_op_escrow_create()->set_cancel_after(750094604); + input.mutable_op_escrow_create()->set_finish_after(0); + input.mutable_op_escrow_create()->set_condition("a0258020ffecf1ae6182f10efebe0c0896cd6b044df7b27d33b05030033ef63d47e2b250810120"); // PREIMAGE-SHA-256 crypto-condition of secret ffecf1ae6182f10efebe0c0896cd6b044df7b27d33b05030033ef63d47e2b250 + + input.set_fee(12); + input.set_sequence(41874370); + input.set_last_ledger_sequence(41874392); + input.set_account("rpLGh11T9B6b4UjAU1WRCJowLw8uk7vS44"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120001220000000024027ef3c22e000009a3201b027ef3d820242cb5890c614000000001b99cc268400000000000000c7321035e6cd73289f9b1a796fba572f7a2732aae23b2a9ea6b0ec239d5b9feb388774074473045022100c4bb3b65acd5d30aa8f85ea2a0d2c0e18d2025a005a827722059a9a636eb1bca02207d73b4a64d679e605a6cb31881d7ea3642c1e54e3bf38d13d0dd4219c27d1420701127a0258020ffecf1ae6182f10efebe0c0896cd6b044df7b27d33b05030033ef63d47e2b25081012081140e9c9b31b826671aaa387555cdeccab82a78402083145da8080d21fecf98f24ea2223482e5d24f107799"); +} + +TEST(TWAnySignerRipple, SignEscrowCancelMain) { + // https://xrpscan.com/tx/949B3C3D8B4528C95D07654BBA10B08ABA65FFD339E31706BC93CB0824427F97 + auto key = parse_hex("a3cf20a85b25be4c955f0814718cc7a02eae9195159bd72ede5dd5c4e60d22c4"); + Proto::SigningInput input; + + input.mutable_op_escrow_cancel()->set_owner("rnXwGtLDXXcV63CnRoNaesSsJCZZkJwo9w"); + input.mutable_op_escrow_cancel()->set_offer_sequence(84363227); + + input.set_fee(12); + input.set_sequence(84363228); + input.set_last_ledger_sequence(84363740); + input.set_account("rnXwGtLDXXcV63CnRoNaesSsJCZZkJwo9w"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120004220000000024050747dc2019050747db201b050749dc68400000000000000c7321029b557f4db390d68d39d3457204c225d4a68ed86854567a1da99d3e2cd64071737446304402202c0416934dbf3a0c42d0b0da9e893cec69e42c81f41424f4a388c3ba8862e65a02201781e22ef85b251902e918f6d923769993757a79b865a62ecdebc1a015368f1f81143194b932f389b95922fba31662f3c8a606fedfd682143194b932f389b95922fba31662f3c8a606fedfd6"); +} + +TEST(TWAnySignerRipple, SignEscrowCancel) { + // https://testnet.xrpl.org/transactions/5B0F8766FFBDE7D3A9ACAA63361BF00FE0739DC8718507776EB2C1AD980BC965 + auto key = parse_hex("bf9810cc4f7cc5e6dea8a0c29f3389d9d511e795d467b402a870e71d93243705"); + Proto::SigningInput input; + + input.mutable_op_escrow_cancel()->set_owner("rE16pf2ZQUZBDLKAyTFF9Q1b3YY1nc7v2J"); + input.mutable_op_escrow_cancel()->set_offer_sequence(41875229); + + input.set_fee(12); + input.set_sequence(41875230); + input.set_last_ledger_sequence(41875263); + input.set_account("rE16pf2ZQUZBDLKAyTFF9Q1b3YY1nc7v2J"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120004220000000024027ef71e2019027ef71d201b027ef73f68400000000000000c73210277314966f72e9520199faa3941bd45b89e444f7eabf203e805527f880de80b8674473045022100ec04d05db5725ce154a511f93056fde0b825b7e0bb4a59b4d4264a008eafdcfe0220676f30f916c6ea0644c11c0bcafcfa8209a083041742d672a726a5c8d99230ea8114a327f724d30f2732f78a4ec6744db298e827ba2b8214a327f724d30f2732f78a4ec6744db298e827ba2b"); +} + +TEST(TWAnySignerRipple, SignEscrowFinishMain) { + // https://xrpscan.com/tx/F015FB9E893877289E3058F14DD2FAA93D7F1E44AC2C7F71E684BD65B94EEB59 + auto key = parse_hex("f60edca8e4bb25f9916017c9c7fe93e633b800550f86cf305a2e99271d7cede5"); + Proto::SigningInput input; + + input.mutable_op_escrow_finish()->set_owner("rnXwGtLDXXcV63CnRoNaesSsJCZZkJwo9w"); + input.mutable_op_escrow_finish()->set_offer_sequence(84363235); + + input.set_fee(12); + input.set_sequence(84363475); + input.set_last_ledger_sequence(84364395); + input.set_account("rEeSXUWEYyEADhDHvi3mtahkFVn7dYNH2G"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120002220000000024050748d32019050747e3201b05074c6b68400000000000000c7321025a4c754a3f836ebe18520e7d3861c6e38a4adfe466465d5db6cbb2d745d27ee574473045022100df5a22c475fa039d8fd7f1dd9a3b248e2f11232bf23ae0206b79b6ac3014a80e02202df2da6d98ed9ced91b9790a3d84f28c2aeb368e3cde84806926086d74d406c18114a0a67483ad4d51b2524eb304c0fcef6b2025b86582143194b932f389b95922fba31662f3c8a606fedfd6"); +} + +TEST(TWAnySignerRipple, SignEscrowFinish) { + // https://testnet.xrpl.org/transactions/690E04E97761E3E5F33A9FF3DA42C16E8E234043850DA294BB3FE38CAE551E71 + auto key = parse_hex("a6e306206d400dcc4a2d00e70b4a3925d511b2dabc1a85f4ffbf174a334e28e6"); + Proto::SigningInput input; + + input.mutable_op_escrow_finish()->set_owner("rL6iE1bbAHekMavpGot6gRxqkQKm6yfoQ6"); + input.mutable_op_escrow_finish()->set_offer_sequence(41874843); + + input.set_fee(12); + input.set_sequence(41874845); + input.set_last_ledger_sequence(41874877); + input.set_account("rNS1tYfynXoKC3eX52gvVnSyU9mqWXvCgh"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120002220000000024027ef59d2019027ef59b201b027ef5bd68400000000000000c7321026f8adad2b4071daa02916f8759ff148fad37c1562e48e71bb608d896d1c833cb74473045022100a7f06325574a9c4300725cb069029645b94d67217e5ae15a2e20bc0387e32aaf02206f9f1a7ae4aaccf2f4c2ab8d90f868e786a285aae677e0b01507eca5dd6823818114937e838cb1033342c72acfae58fe2e3875ce76938214d8242542e6108fccf75a7f5bb0059cfae6d15537"); +} + +TEST(TWAnySignerRipple, SignEscrowFinishWithConditionMain) { + // https://xrpscan.com/tx/7E9AC2C8286E3EC0410784920A0F8048C79257EDF19B392F98A31F62E3CF4FAD + auto key = parse_hex("f60edca8e4bb25f9916017c9c7fe93e633b800550f86cf305a2e99271d7cede5"); + Proto::SigningInput input; + + input.mutable_op_escrow_finish()->set_owner("rnXwGtLDXXcV63CnRoNaesSsJCZZkJwo9w"); + input.mutable_op_escrow_finish()->set_offer_sequence(84363226); + input.mutable_op_escrow_finish()->set_condition("a0258020c26add2db64dd6d5700a5e2721c1e908d599901627b8dc82f25b3e035ec4004b810120"); + input.mutable_op_escrow_finish()->set_fulfillment("a02280205d729ac237c4c7976403817b6409be7190efbfad49af2cf974b9582a854e8794"); + + input.set_fee(423); + input.set_sequence(84363473); + input.set_last_ledger_sequence(84363511); + input.set_account("rEeSXUWEYyEADhDHvi3mtahkFVn7dYNH2G"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120002220000000024050748d12019050747da201b050748f76840000000000001a77321025a4c754a3f836ebe18520e7d3861c6e38a4adfe466465d5db6cbb2d745d27ee574473045022100f06a6ac18efc1280f9d26cbb47c31f7ecd72ed200f9d05c6c762ffcf18b53534022049c6bb4ac8e79c478939a55dd1cb571d56ec929e5200332e410fd69c0fe1ef48701024a02280205d729ac237c4c7976403817b6409be7190efbfad49af2cf974b9582a854e8794701127a0258020c26add2db64dd6d5700a5e2721c1e908d599901627b8dc82f25b3e035ec4004b8101208114a0a67483ad4d51b2524eb304c0fcef6b2025b86582143194b932f389b95922fba31662f3c8a606fedfd6"); +} + +TEST(TWAnySignerRipple, SignEscrowFinishWithCondition) { + // https://testnet.xrpl.org/transactions/4A49D4AD05FBDC4A354E31C7453829509F59DD2B51CDE560C4350155F5DBFD86 + auto key = parse_hex("4bae9219cf9c58e5db0c395900085f07fc06709e1b2223ccac40191fbcbdab2a"); + Proto::SigningInput input; + + input.mutable_op_escrow_finish()->set_owner("rpLGh11T9B6b4UjAU1WRCJowLw8uk7vS44"); + input.mutable_op_escrow_finish()->set_offer_sequence(41874370); + input.mutable_op_escrow_finish()->set_condition("a0258020ffecf1ae6182f10efebe0c0896cd6b044df7b27d33b05030033ef63d47e2b250810120"); + input.mutable_op_escrow_finish()->set_fulfillment("a022802049b9ab20ca85b55d0c12b948ec7c524f843c77be1ef1561a42b7167dce174b7a"); + + input.set_fee(423); + input.set_sequence(41874372); + input.set_last_ledger_sequence(41874394); + input.set_account("r9YD31TAtbS8EPwEt2gzGDjsaMDyV1s5QE"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "120002220000000024027ef3c42019027ef3c2201b027ef3da6840000000000001a773210277c5d02c3c774c96017234a532dae12023ac8fb499c5d90a56488900ecc746d07446304402206698c1d296bf1493c97beb64945558724c6c88474cd3e0b90e9dc9e7313ac1970220175fef60c48646be934be28a964af0cc55843fb6e6ef17c886716a03af849f74701024a022802049b9ab20ca85b55d0c12b948ec7c524f843c77be1ef1561a42b7167dce174b7a701127a0258020ffecf1ae6182f10efebe0c0896cd6b044df7b27d33b05030033ef63d47e2b25081012081145da8080d21fecf98f24ea2223482e5d24f10779982140e9c9b31b826671aaa387555cdeccab82a784020"); +} + +TEST(TWAnySignerRipple, SignNfTokenBurn) { + // https://devnet.xrpl.org/transactions/37DA90BE3C30016B3A2C3D47D9677278A3F6D4141B318793CE6AA467A6530E2D + auto key = parse_hex("7c2ea5c7b1fd7dfc62d879918b7fc779cdff6bf6391d02ec99854297e916318e"); + Proto::SigningInput input; + + input.mutable_op_nftoken_burn()->set_nftoken_id("000b013a95f14b0044f78a264e41713c64b5f89242540ee208c3098e00000d65"); + input.set_fee(10); + input.set_sequence(22858395); + input.set_last_ledger_sequence(22858416); + input.set_account("rhR1mTXkg4iSGhz7zsEKBLbV3MkopivPVF"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "12001a220000000024015cca9b201b015ccab05a000b013a95f14b0044f78a264e41713c64b5f89242540ee208c3098e00000d6568400000000000000a73210254fc876043109af1ff11b832320be4436ef51dcc344da5970c9b6c6d1fbcddcf744730450221008b4d437bc92aa4643b275b17c0f88a1bef2c1c160ece5faf93b03e2d31b8278602207640e7e35426352deaafecf61e2b401a4ea1fc645839280370a72fa3c41aea7d8114259cbcf9635360bc302f27d0ce72c18d4dbe9c8d"); +} + +TEST(TWAnySignerRipple, SignNfTokenCreateOffer) { + // https://devnet.xrpl.org/transactions/E61D66E261DB89CEAAB4F54ECF792B329296CB524E8B40EA99D27CF5E16DD27D + auto key = parse_hex("1963884da4a4da79ad7681d106b2c55fb652c68ca7b288dd12bb86cd40b9d940"); + Proto::SigningInput input; + + input.mutable_op_nftoken_create_offer()->set_nftoken_id("000b013a95f14b0044f78a264e41713c64b5f89242540ee208c3098e00000d65"); + input.mutable_op_nftoken_create_offer()->set_destination("rDxTa8vhigDUCq9nmZY8jAkFne5XrcYbxG"); + input.set_fee(10); + input.set_sequence(22857522); + input.set_last_ledger_sequence(22857543); + input.set_account("rJdxtrVoL3Tak74EzN8SdMxFF6RP9smjJJ"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "12001b220000000124015cc732201b015cc7475a000b013a95f14b0044f78a264e41713c64b5f89242540ee208c3098e00000d6561400000000000000068400000000000000a7321022707066e4f8b87b749ef802338be064065dc978f0ea52ea9c8c8ea0a6145571974473045022100a148140469b8e9e2f9aa43631f3101e532d161d49a05e739cd3494ea208bd657022029a9752df3fc0d23b8fdb46d2274e69ab198ce6f373aeb7cdd0d81ab05aff6f48114c177c23ed1f5d175f42fd7970ece74ac18d61c4d83148e1e2ca343165bf30e96abead961f7a34510ad93"); +} + +TEST(TWAnySignerRipple, SignNfTokenAcceptOffer) { + // https://devnet.xrpl.org/transactions/6BB00A7BABB8797D60E3AB0E52DB64562524D014833977D87B04CA9FA3F56AD7 + auto key = parse_hex("3c01b3458d2b2a4b86a5699d11682d791b5c3136692c5594f7a8ca7f3967e7ae"); + Proto::SigningInput input; + + input.mutable_op_nftoken_accept_offer()->set_sell_offer("000b013a95f14b0044f78a264e41713c64b5f89242540ee208c3098e00000d65"); + input.set_fee(10); + input.set_sequence(22857743); + input.set_last_ledger_sequence(22857764); + input.set_account("rPa2KsEuSuZnmjosds99nhgsoiKtw85j6Z"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "12001d220000000024015cc80f201b015cc824501d000b013a95f14b0044f78a264e41713c64b5f89242540ee208c3098e00000d6568400000000000000a73210331c298cb86428b9126bd4af6a952870cfe3fe5065dc093cf97f3edbb27e9dd15744630440220797922caaa593c4e91fa6b63a38c92ef9f5e2183128918dda166f4292882e137022057702b668d7463ef1d01dad5ee6633bd36f0aa358dacc90d6b68d248672a400f8114f260a758132d3ed27e52d7f55ef0481606f090d4"); +} + +TEST(TWAnySignerRipple, SignNfTokenCancelOffer) { + // https://devnet.xrpl.org/transactions/CBA148308A0D1561E5E8CDF1F2E8D5562C320C221AC4053AA5F495CEF4B5D5D4 + auto key = parse_hex("3e50cc102d8c96abd55f047a536b6425154514ba8abdf5f09335a7c644176c5d"); + Proto::SigningInput input; + + input.mutable_op_nftoken_cancel_offer()->add_token_offers("000b013a95f14b0044f78a264e41713c64b5f89242540ee208c3098e00000d65"); + input.set_fee(10); + input.set_sequence(22857838); + input.set_last_ledger_sequence(22857859); + input.set_account("rPqczdU9bzow966hQKQejsXrMJspM7G4CC"); + input.set_private_key(key.data(), key.size()); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + + EXPECT_EQ(hex(output.encoded()), "12001c220000000024015cc86e201b015cc88368400000000000000a7321022250f103fd045edf2e552df2d20aca01a52dc6aedd522d68767f1c744fedb39d74463044022015fff495fc5d61cd71e5815e4d23845ec26f4dc94adb85207feba2c97e19856502207297ec84afc0bb74aa8a20d7254025a82d9b9f177f648845d8c72ee62884ff618114fa84c77f2a5245ef774845d40428d2a6f9603415041320000b013a95f14b0044f78a264e41713c64b5f89242540ee208c3098e00000d65"); +} + +} // namespace TW::Ripple::tests diff --git a/tests/chains/XRP/TWCoinTypeTests.cpp b/tests/chains/XRP/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..d9fcc697b53 --- /dev/null +++ b/tests/chains/XRP/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWXRPCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeXRP)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("E26AB8F3372D2FC02DEC1FD5674ADAB762D684BFFDBBDF5D674E9D7CF4A47054")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeXRP, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("rfkH7EuS1XcSkB9pocy1R6T8F4CsNYixYU")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeXRP, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeXRP)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeXRP)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeXRP), 6); + ASSERT_EQ(TWBlockchainRipple, TWCoinTypeBlockchain(TWCoinTypeXRP)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeXRP)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeXRP)); + assertStringsEqual(symbol, "XRP"); + assertStringsEqual(txUrl, "https://bithomp.com/explorer/E26AB8F3372D2FC02DEC1FD5674ADAB762D684BFFDBBDF5D674E9D7CF4A47054"); + assertStringsEqual(accUrl, "https://bithomp.com/explorer/rfkH7EuS1XcSkB9pocy1R6T8F4CsNYixYU"); + assertStringsEqual(id, "ripple"); + assertStringsEqual(name, "XRP"); +} diff --git a/tests/chains/XRP/TWRippleAddressTests.cpp b/tests/chains/XRP/TWRippleAddressTests.cpp new file mode 100644 index 00000000000..db9d8ec9382 --- /dev/null +++ b/tests/chains/XRP/TWRippleAddressTests.cpp @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include +#include + +#include + +namespace TW::Ripple::tests { + +const char* validAddrStr1 = "X76UnYEMbQfEs3mUqgtjp4zFy9exgThRj7XVZ6UxsdrBptF"; +const char* publicKeyDataStr1 = "0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D"; +const char* invalidAddrStr1 = "12345678"; + +TEST(TWRippleXAddress, ExtendedKeys) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic( + STRING("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal").get(), + STRING("TREZOR").get())); + + auto xpub = WRAPS(TWHDWalletGetExtendedPublicKey(wallet.get(), TWPurposeBIP44, TWCoinTypeXRP, TWHDVersionXPUB)); + auto xprv = WRAPS(TWHDWalletGetExtendedPrivateKey(wallet.get(), TWPurposeBIP44, TWCoinTypeXRP, TWHDVersionXPRV)); + + assertStringsEqual(xpub, "xpub6D9oDY4gqFBtsFEonh5GTDiUm6nmij373YWzmYdshcnM4AFzdhUf55iZD33vNU2ZqfQJU5wiCJUgisMt2RHKDzhi1PbZfh5Y2NiiYJAQqUn"); + assertStringsEqual(xprv, "xprv9zASp2XnzsdbemALgfYG65mkD4xHKGKFgKbPyAEG9HFNBMvr6AAQXHQ5MmqM66EnbJfe9TvYMy1bucz7hSQjG43NVizRZwJJYfLmeKo4nVB"); +} + +TEST(TWRipple, XAddress) { + const auto string = STRING("XVfvixWZQKkcenFRYApCjpTUyJ4BePTe3jJv7beatUZvQYh"); + const auto xAddress = WRAP(TWRippleXAddress, TWRippleXAddressCreateWithString(string.get())); + + EXPECT_TRUE(TWRippleXAddressIsValidString(string.get())); + EXPECT_EQ(TWRippleXAddressTag(xAddress.get()), 12345ul); + assertStringsEqual(WRAPS(TWRippleXAddressDescription(xAddress.get())), "XVfvixWZQKkcenFRYApCjpTUyJ4BePTe3jJv7beatUZvQYh"); +} + +TEST(TWRippleXAddress, CreateAndDelete) { + { + TWRippleXAddress* addr = TWRippleXAddressCreateWithString(STRING(validAddrStr1).get()); + EXPECT_TRUE(addr != nullptr); + TWRippleXAddressDelete(addr); + } + { + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyDataStr1).get(), TWPublicKeyTypeSECP256k1)); + TWRippleXAddress* addr = TWRippleXAddressCreateWithPublicKey(publicKey.get(), 12345); + EXPECT_TRUE(addr != nullptr); + TWRippleXAddressDelete(addr); + } +} + +TEST(TWRippleXAddress, AddressEqual) { + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyDataStr1).get(), TWPublicKeyTypeSECP256k1)); + auto addr1 = WRAP(TWRippleXAddress, TWRippleXAddressCreateWithPublicKey(publicKey.get(), 12345)); + EXPECT_TRUE(addr1.get() != nullptr); + + auto addr2 = WRAP(TWRippleXAddress, TWRippleXAddressCreateWithString(STRING(validAddrStr1).get())); + EXPECT_TRUE(addr2.get() != nullptr); + ASSERT_TRUE(TWRippleXAddressEqual(addr1.get(), addr2.get())); +} + +TEST(TWRippleXAddress, IsValidString) { + ASSERT_TRUE(TWRippleXAddressIsValidString(STRING(validAddrStr1).get())); + ASSERT_FALSE(TWRippleXAddressIsValidString(STRING(invalidAddrStr1).get())); +} + +TEST(TWRippleXAddress, AddressDescription) { + + auto addr1 = WRAP(TWRippleXAddress, TWRippleXAddressCreateWithString(STRING(validAddrStr1).get())); + EXPECT_TRUE(addr1.get() != nullptr); + auto addrStr1 = std::string(TWStringUTF8Bytes(WRAPS(TWRippleXAddressDescription(addr1.get())).get())); + EXPECT_TRUE(addrStr1 == validAddrStr1); +} + +} // namespace TW::Ripple::tests diff --git a/tests/chains/XRP/TransactionCompilerTests.cpp b/tests/chains/XRP/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..f5040e2cf41 --- /dev/null +++ b/tests/chains/XRP/TransactionCompilerTests.cpp @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Ripple.pb.h" +#include "proto/TransactionCompiler.pb.h" + +#include +#include + +#include "TestUtilities.h" +#include + +namespace TW::Ripple::tests { + +TEST(RippleCompiler, CompileRippleWithSignatures) { + const auto coin = TWCoinTypeXRP; + /// Step 1: Prepare transaction input (protobuf) + auto key = parse_hex("acf1bbf6264e699da0cc65d17ac03fcca6ded1522d19529df7762db46097ff9f"); + auto input = TW::Ripple::Proto::SigningInput(); + auto privateKey = TW::PrivateKey(key); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + + input.mutable_op_payment()->set_amount(1000000); + input.set_fee(10); + input.set_sequence(75674534); + input.set_last_ledger_sequence(75674797); + input.set_account("rGV1v1xw23PHcRn4Km4tF8R2mfh6yTZkcP"); + input.mutable_op_payment()->set_destination("rNLpgsBTCwiaZAnHe2ZViAN1GcXZtYW6rg"); + input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + /// Step 2: Obtain preimage hash + const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData); + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + preSigningOutput.ParseFromArray(preImageHashesData.data(), (int)preImageHashesData.size()); + auto preImage = preSigningOutput.data(); + EXPECT_EQ(hex(preImage), "535458001200002200000000240482b3a6201b0482b4ad6140000000000f424068400000000000000a7321027efc5f15071d2ae5e73ee09a0c17456c5d9170a41d67e3297c554829199be80b8114aa000c09c692ef1f82787e51e22833149941ea2083149232ef60695add51f0f84534cc4084e4fdfc698e"); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImageHash), "86ef78df7a4aad29e6b3730f7965c1bd5ccd2439426cb738d7c494a64cfaf4af"); + // Simulate signature, normally obtained from signature server + const auto signature = privateKey.signAsDER(TW::data(preImageHash)); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verifyAsDER(signature, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + auto outputData = + TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKey.bytes}); + const auto ExpectedTx = std::string("1200002200000000240482b3a6201b0482b4ad6140000000000f424068400000000000000a7321027efc5f15071d2ae5e73ee09a0c17456c5d9170a41d67e3297c554829199be80b74473045022100e1c746c3aeebc8278c627ee4c2ce5cae97e3856292c7fe5388f803920230a37b02207d2eccb76cd35dd379d6b24c2cabd786e62d34a564cf083e863176109c5b6bb48114aa000c09c692ef1f82787e51e22833149941ea2083149232ef60695add51f0f84534cc4084e4fdfc698e"); + + { + TW::Ripple::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + ASSERT_EQ(output.error(), TW::Common::Proto::SigningError::OK); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + TW::Ripple::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); + signingInput.set_private_key(key.data(), key.size()); + + TW::Ripple::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(hex(output.encoded()), ExpectedTx); + } + + { // Negative: not enough signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {signature, signature}, {publicKey.bytes}); + Ripple::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, inputStrData, {}, {}); + Ripple::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} + +} // namespace TW::Ripple::tests diff --git a/tests/chains/XRP/TransactionTests.cpp b/tests/chains/XRP/TransactionTests.cpp new file mode 100644 index 00000000000..eccde7ccaf4 --- /dev/null +++ b/tests/chains/XRP/TransactionTests.cpp @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "XRP/Address.h" +#include "XRP/Transaction.h" +#include "XRP/BinaryCoding.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" + +#include + +using namespace std; + +namespace TW::Ripple::tests { + +TEST(RippleTransaction, serializeAmount) { + /// From https://github.com/trezor/trezor-core/blob/master/tests/test_apps.ripple.serializer.py + auto data0 = Transaction::serializeAmount(0); + auto data1 = Transaction::serializeAmount(1); + auto data2 = Transaction::serializeAmount(93493429243); + auto data3 = Transaction::serializeAmount(25000000); + auto data4 = Transaction::serializeAmount(100000000000); + /// more than max supply + auto data5 = Transaction::serializeAmount(200000000000000000); + /// negative value + auto data6 = Transaction::serializeAmount(-1); + + ASSERT_EQ(hex(data0), "4000000000000000"); + ASSERT_EQ(hex(data1), "4000000000000001"); + ASSERT_EQ(hex(data2), "40000015c4a483fb"); + ASSERT_EQ(hex(data3), "40000000017d7840"); + ASSERT_EQ(hex(data4), "400000174876e800"); + ASSERT_EQ(hex(data5), "42c68af0bb140000"); + ASSERT_EQ(hex(data6), ""); +} + +TEST(RippleTransaction, serialize) { + /// From https://github.com/trezor/trezor-core/blob/master/tests/test_apps.ripple.serializer.py + auto account = Address("r9TeThyi5xiuUUrFjtPKZiHcDxs7K9H6Rb"); + auto destination = "r4BPgS7DHebQiU31xWELvZawwSG2fSPJ7C"; + auto tx1 = Transaction( + /* fee */10, + /* flags */0, + /* sequence */2, + /* last_ledger_sequence */0, + /* account */account + ); + tx1.createXrpPayment( + /* amount */25000000, + /* destination */destination, + /* destination_tag*/0 + ); + auto serialized1 = tx1.serialize(); + ASSERT_EQ(hex(serialized1), "120000220000000024000000026140000000017d784068400000000000000a81145ccb151f6e9d603f394ae778acf10d3bece874f68314e851bbbe79e328e43d68f43445368133df5fba5a"); + + auto tx2 = Transaction( + /* fee */15, + /* flags */0, + /* sequence */144, + /* last_ledger_sequence */0, + /* account */Address("rGWTUVmm1fB5QUjMYn8KfnyrFNgDiD9H9e") + ); + tx2.createXrpPayment( + /* amount */200000, + /* destination */"rw71Qs1UYQrSQ9hSgRohqNNQcyjCCfffkQ", + /* destination_tag*/0 + ); + auto serialized2 = tx2.serialize(); + ASSERT_EQ(hex(serialized2), "12000022000000002400000090614000000000030d4068400000000000000f8114aa1bd19d9e87be8069fdbf6843653c43837c03c6831467fe6ec28e0464dd24fb2d62a492aac697cfad02"); + + auto tx3 = Transaction( + /* fee */12, + /* flags */0, + /* sequence */1, + /* last_ledger_sequence */0, + /* account */Address("r4BPgS7DHebQiU31xWELvZawwSG2fSPJ7C") + ); + tx3.createXrpPayment( + /* amount */25000000, + /* destination */"rBqSFEFg2B6GBMobtxnU1eLA1zbNC9NDGM", + /* destination_tag*/4146942154 + ); + auto serialized3 = tx3.serialize(); + ASSERT_EQ(hex(serialized3), "120000220000000024000000012ef72d50ca6140000000017d784068400000000000000c8114e851bbbe79e328e43d68f43445368133df5fba5a831476dac5e814cd4aa74142c3ab45e69a900e637aa2"); + + auto tx4 = Transaction( + /* fee */12, + /* flags */0, + /* sequence */1, + /* last_ledger_sequence */0, + /* account */Address("r4BPgS7DHebQiU31xWELvZawwSG2fSPJ7C") + ); + tx4.createXrpPayment( + /* amount */25000000, + /* destination */"XVhidoXkozM5DTZFdDnJ5nYC8FPrTuJiyGh1VxSGS6RNJJ5", + /* ignore destination_tag*/12345 + ); + auto serialized4 = tx4.serialize(); + ASSERT_EQ(hex(serialized4), hex(serialized3)); +} + +TEST(RippleTransaction, preImage) { + auto account = Address("r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ"); + auto destination = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"; + auto tx1 = Transaction( + /* fee */10, + /* flags */2147483648, + /* sequence */1, + /* last_ledger_sequence */0, + /* account */account + ); + tx1.createXrpPayment( + /* amount */1000, + /* destination */destination, + /* destination_tag*/0 + ); + tx1.pub_key = parse_hex("ed5f5ac8b98974a3ca843326d9b88cebd0560177b973ee0b149f782cfaa06dc66a"); + auto unsignedTx = tx1.getPreImage(); + + ASSERT_EQ(hex(unsignedTx), + /* prefix */ "53545800" + /* tx type */ "120000" + /* flags */ "2280000000" + /* sequence */ "2400000001" + /* amount */ "6140000000000003e8" + /* fee */ "68400000000000000a" + /* pub key */ "7321ed5f5ac8b98974a3ca843326d9b88cebd0560177b973ee0b149f782cfaa06dc66a" + /* account */ "81145b812c9d57731e27a2da8b1830195f88ef32a3b6" + /* destination */ "8314b5f762798a53d543a014caf8b297cff8f2f937e8" + ); + ASSERT_EQ(unsignedTx.size(), 114ul); +} + +} // namespace TW::Ripple::tests diff --git a/tests/chains/Zcash/AddressTests.cpp b/tests/chains/Zcash/AddressTests.cpp new file mode 100644 index 00000000000..32ea339ed5f --- /dev/null +++ b/tests/chains/Zcash/AddressTests.cpp @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Zcash/TAddress.h" +#include "HexCoding.h" +#include "PrivateKey.h" + +#include + +namespace TW::Zcash { + +TEST(ZcashAddress, FromPrivateKey) { + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto address = TAddress(publicKey); + + EXPECT_EQ(address.string(), "t1Wg9uPPAfwhBWeRjtDPa5ZHNzyBx9rJVKY"); + EXPECT_EQ(address.bytes[0], 0x1c); + EXPECT_EQ(address.bytes[1], 0xb8); +} + +TEST(ZcashAddress, FromPublicKey) { + const auto privateKey = PrivateKey(parse_hex("BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto address = TAddress(publicKey); + + EXPECT_EQ(address.string(), "t1gaySCXCYtXE3ygP38YuWtVZczsEbdjG49"); + EXPECT_EQ(address.bytes[0], 0x1c); + EXPECT_EQ(address.bytes[1], 0xb8); +} + +TEST(ZcashAddress, Valid) { + EXPECT_TRUE(TAddress::isValid(std::string("t1RygJmrLdNGgi98gUgEJDTVaELTAYWoMBy"))); + EXPECT_TRUE(TAddress::isValid(std::string("t1TWk2mmvESDnE4dmCfT7MQ97ij6ZqLpNVU"))); + EXPECT_TRUE(TAddress::isValid(std::string("t3RD6RFKhWSotNbPEY4Vw7Ku9QCfKkzrbBL"))); +} + +TEST(ZcashAddress, Invalid) { + EXPECT_FALSE(TAddress::isValid(std::string("abc"))); + EXPECT_FALSE(TAddress::isValid(std::string("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"))); + EXPECT_FALSE(TAddress::isValid(std::string("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W"))); + EXPECT_FALSE(TAddress::isValid(std::string("t1RygJmrLdNGgi98+UgEJDTVaELTAYWoMBy"))); // Invalid Base58 + EXPECT_FALSE(TAddress::isValid(std::string("t1RygJmrLdNGgi98gUgEJDTVaELTAYW"))); // too short + EXPECT_FALSE(TAddress::isValid(std::string("t1RygJmrLdNGgi98gUgEJDTVaELTAYWoMBz"))); // bad checksum + EXPECT_FALSE(TAddress::isValid(std::string("TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"))); // too short + EXPECT_FALSE(TAddress::isValid(std::string("2NRbuP5YfzRNEa1RibT5kXay1VgvQHnydZY1"))); // invalid prefix +} + +TEST(ZcashAddress, InitWithString) { + { + const auto address = TAddress("t1Wg9uPPAfwhBWeRjtDPa5ZHNzyBx9rJVKY"); + EXPECT_EQ(address.string(), "t1Wg9uPPAfwhBWeRjtDPa5ZHNzyBx9rJVKY"); + EXPECT_EQ(address.bytes[0], 0x1c); + EXPECT_EQ(address.bytes[1], 0xb8); + } + { + const auto address = TAddress("t3RD6RFKhWSotNbPEY4Vw7Ku9QCfKkzrbBL"); + EXPECT_EQ(address.string(), "t3RD6RFKhWSotNbPEY4Vw7Ku9QCfKkzrbBL"); + EXPECT_EQ(address.bytes[0], 0x1c); + EXPECT_EQ(address.bytes[1], 0xbd); + } +} + +} // namespace TW::Zcash diff --git a/tests/chains/Zcash/TWCoinTypeTests.cpp b/tests/chains/Zcash/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..b85b7e8afd9 --- /dev/null +++ b/tests/chains/Zcash/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWZcashCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeZcash)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("f2438a93039faf08d39bd3df1f7b5f19a2c29ffe8753127e2956ab4461adab35")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeZcash, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("t1Yfrf1dssDLmaMBsq2LFKWPbS5vH3nGpa2")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeZcash, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeZcash)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeZcash)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeZcash), 8); + ASSERT_EQ(TWBlockchainZcash, TWCoinTypeBlockchain(TWCoinTypeZcash)); + ASSERT_EQ(0xbd, TWCoinTypeP2shPrefix(TWCoinTypeZcash)); + ASSERT_EQ(0x1c, TWCoinTypeStaticPrefix(TWCoinTypeZcash)); + assertStringsEqual(symbol, "ZEC"); + assertStringsEqual(txUrl, "https://blockchair.com/zcash/transaction/f2438a93039faf08d39bd3df1f7b5f19a2c29ffe8753127e2956ab4461adab35"); + assertStringsEqual(accUrl, "https://blockchair.com/zcash/address/t1Yfrf1dssDLmaMBsq2LFKWPbS5vH3nGpa2"); + assertStringsEqual(id, "zcash"); + assertStringsEqual(name, "Zcash"); +} diff --git a/tests/Zcash/TWZcashAddressTests.cpp b/tests/chains/Zcash/TWZcashAddressTests.cpp similarity index 93% rename from tests/Zcash/TWZcashAddressTests.cpp rename to tests/chains/Zcash/TWZcashAddressTests.cpp index bbaa4ad932e..4b1cfd3dee6 100644 --- a/tests/Zcash/TWZcashAddressTests.cpp +++ b/tests/chains/Zcash/TWZcashAddressTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "Zcash/TAddress.h" diff --git a/tests/chains/Zcash/TWZcashTransactionTests.cpp b/tests/chains/Zcash/TWZcashTransactionTests.cpp new file mode 100644 index 00000000000..cc20c735e5b --- /dev/null +++ b/tests/chains/Zcash/TWZcashTransactionTests.cpp @@ -0,0 +1,226 @@ + +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include "Bitcoin/OutPoint.h" +#include "Bitcoin/Script.h" +#include "Zcash/Signer.h" +#include "Zcash/TransactionBuilder.h" +#include "Bitcoin/TransactionSigner.h" +#include "HexCoding.h" +#include "PublicKey.h" +#include "Data.h" +#include "Coin.h" +#include "Zcash/Transaction.h" + +#include + +#include + +using namespace TW; + +TEST(TWZcashTransaction, Encode) { + // Test vector 3 https://github.com/zcash/zips/blob/master/zip-0243.rst + auto transaction = Zcash::Transaction(); + transaction.lockTime = 0x0004b029; + transaction.expiryHeight = 0x0004b048; + transaction.branchId = Zcash::SaplingBranchID; + + auto outpoint0 = Bitcoin::OutPoint(parse_hex("a8c685478265f4c14dada651969c45a65e1aeb8cd6791f2f5bb6a1d9952104d9"), 1); + transaction.inputs.emplace_back(outpoint0, Bitcoin::Script(parse_hex("483045022100a61e5d557568c2ddc1d9b03a7173c6ce7c996c4daecab007ac8f34bee01e6b9702204d38fdc0bcf2728a69fde78462a10fb45a9baa27873e6a5fc45fb5c76764202a01210365ffea3efa3908918a8b8627724af852fc9b86d7375b103ab0543cf418bcaa7f")), 0xfffffffe); + + auto script0 = Bitcoin::Script(parse_hex("76a9148132712c3ff19f3a151234616777420a6d7ef22688ac")); + transaction.outputs.emplace_back(0x02625a00, script0); + + auto script1 = Bitcoin::Script(parse_hex("76a9145453e4698f02a38abdaa521cd1ff2dee6fac187188ac")); + transaction.outputs.emplace_back(0x0098958b, script1); + + auto unsignedData = Data{}; + transaction.encode(unsignedData); + + ASSERT_EQ(hex(unsignedData), + /* header */ "04000080" + /* versionGroupId */ "85202f89" + /* vin */ "01""a8c685478265f4c14dada651969c45a65e1aeb8cd6791f2f5bb6a1d9952104d9""01000000""6b483045022100a61e5d557568c2ddc1d9b03a7173c6ce7c996c4daecab007ac8f34bee01e6b9702204d38fdc0bcf2728a69fde78462a10fb45a9baa27873e6a5fc45fb5c76764202a01210365ffea3efa3908918a8b8627724af852fc9b86d7375b103ab0543cf418bcaa7f""feffffff" + /* vout */ "02""005a620200000000""1976a9148132712c3ff19f3a151234616777420a6d7ef22688ac" + "8b95980000000000""1976a9145453e4698f02a38abdaa521cd1ff2dee6fac187188ac" + /* lockTime */ "29b00400" + /* expiryHeight */ "48b00400" + /* valueBalance */ "0000000000000000" + /* vShieldedSpend */ "00" + /* vShieldedOutput */ "00" + /* vJoinSplit */ "00" + ); + + auto scriptCode = Bitcoin::Script(parse_hex("76a914507173527b4c3318a2aecd793bf1cfed705950cf88ac")); + auto preImage = transaction.getPreImage(scriptCode, 0, TWBitcoinSigHashTypeAll, 0x02faf080); + ASSERT_EQ(hex(preImage), + /* header */ "04000080" + /* versionGroupId */ "85202f89" + /* hashPrevouts */ "fae31b8dec7b0b77e2c8d6b6eb0e7e4e55abc6574c26dd44464d9408a8e33f11" + /* hashSequence */ "6c80d37f12d89b6f17ff198723e7db1247c4811d1a695d74d930f99e98418790" + /* hashOutputs */ "d2b04118469b7810a0d1cc59568320aad25a84f407ecac40b4f605a4e6868454" + /* hashJoinSplits */ "0000000000000000000000000000000000000000000000000000000000000000" + /* hashShieldedSpends */ "0000000000000000000000000000000000000000000000000000000000000000" + /* hashShieldedOutputs */ "0000000000000000000000000000000000000000000000000000000000000000" + /* lockTime */ "29b00400" + /* expiryHeight */ "48b00400" + /* valueBalance */ "0000000000000000" + /* hashType */ "01000000" + /* prevout */ "a8c685478265f4c14dada651969c45a65e1aeb8cd6791f2f5bb6a1d9952104d9""01000000" + /* scriptCode */ "1976a914507173527b4c3318a2aecd793bf1cfed705950cf88ac" + /* amount */ "80f0fa0200000000" + /* sequence */ "feffffff" + ); + + auto sighash = transaction.getSignatureHash(scriptCode, 0, TWBitcoinSigHashTypeAll, 0x02faf080, Bitcoin::BASE); + ASSERT_EQ(hex(sighash), "f3148f80dfab5e573d5edfe7a850f5fd39234f80b5429d3a57edcc11e34c585b"); + + // AnyoneCanPay|none + preImage = transaction.getPreImage(scriptCode, 0, TWBitcoinSigHashType(TWBitcoinSigHashTypeAnyoneCanPay | TWBitcoinSigHashTypeNone), 0x02faf080); + EXPECT_EQ(hex(preImage), + "0400008085202f8900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000029b0040048b00400000000000000000082000000a8c685478265f4c14dada651969c45a65e1aeb8cd6791f2f5bb6a1d9952104d9010000001976a914507173527b4c3318a2aecd793bf1cfed705950cf88ac80f0fa0200000000feffffff" + ); + + sighash = transaction.getSignatureHash(scriptCode, 0, TWBitcoinSigHashTypeAnyoneCanPay, 0x02faf080, Bitcoin::BASE); + EXPECT_EQ(hex(sighash), "f0bde4facddbc11f5e9ed2f5d5038083bec4a61627a2715a5ee9be7fb3152e9b"); + + // AnyoneCanPay|Single + preImage = transaction.getPreImage(scriptCode, 0, TWBitcoinSigHashType(TWBitcoinSigHashTypeAnyoneCanPay | TWBitcoinSigHashTypeSingle), 0x02faf080); + EXPECT_EQ(hex(preImage), + "0400008085202f890000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000055986938e432f825904fe288aa4feca1fe7eafa24aecd1bd6a9a739536b50a5469be00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000029b0040048b00400000000000000000083000000a8c685478265f4c14dada651969c45a65e1aeb8cd6791f2f5bb6a1d9952104d9010000001976a914507173527b4c3318a2aecd793bf1cfed705950cf88ac80f0fa0200000000feffffff" + ); + + sighash = transaction.getSignatureHash(scriptCode, 0, TWBitcoinSigHashType(TWBitcoinSigHashTypeAnyoneCanPay | TWBitcoinSigHashTypeSingle), 0x02faf080, Bitcoin::BASE); + EXPECT_EQ(hex(sighash), "1e747b6a4a96aa9e7c1d7968221ec916bd30b514f8bca14b6f74d7c11c0742c2"); +} + +TEST(TWZcashTransaction, SaplingSigning) { + // tx on mainnet + // https://explorer.zcha.in/transactions/ec9033381c1cc53ada837ef9981c03ead1c7c41700ff3a954389cfaddc949256 + const int64_t amount = 488000; + const int64_t fee = 6000; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_coin_type(TWCoinTypeZcash); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address("t1QahNjDdibyE4EdYkawUSKBBcVTSqv64CS"); + + auto hash0 = DATA("53685b8809efc50dd7d5cb0906b307a1b8aa5157baa5fc1bd6fe2d0344dd193a"); + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(TWDataBytes(hash0.get()), TWDataSize(hash0.get())); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(494000); + auto script0 = parse_hex("76a914f84c7f4dd3c3dc311676444fdead6e6d290d50e388ac"); + utxo0->set_script(script0.data(), script0.size()); + + auto utxoKey0 = DATA("a9684f5bebd0e1208aae2e02bc9e9163bd1965ad23d8538644e1df8b99b99559"); + input.add_private_key(TWDataBytes(utxoKey0.get()), TWDataSize(utxoKey0.get())); + + auto plan = Zcash::TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 0; + plan.branchId = Data(Zcash::SaplingBranchID.begin(), Zcash::SaplingBranchID.end()); + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = Bitcoin::TransactionSigner::sign(input); + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + // txid = "ec9033381c1cc53ada837ef9981c03ead1c7c41700ff3a954389cfaddc949256" + + Data serialized; + signedTx.encode(serialized); + ASSERT_EQ(hex(serialized), + "04000080" + "85202f89" + "01" + "53685b8809efc50dd7d5cb0906b307a1b8aa5157baa5fc1bd6fe2d0344dd193a""00000000""6b483045022100ca0be9f37a4975432a52bb65b25e483f6f93d577955290bb7fb0060a93bfc92002203e0627dff004d3c72a957dc9f8e4e0e696e69d125e4d8e275d119001924d3b48012103b243171fae5516d1dc15f9178cfcc5fdc67b0a883055c117b01ba8af29b953f6""ffffffff" + "01" + "4072070000000000""1976a91449964a736f3713d64283fd0018626ba50091c7e988ac" + "00000000" + "00000000" + "0000000000000000" + "00" + "00" + "00" + ); +} + +TEST(TWZcashTransaction, BlossomSigning) { + // tx on mainnet + // https://explorer.zcha.in/transactions/387939ff8eb07dd264376eeef2e126394ab139802b1d80e92b21c1a2ae54fe92 + const int64_t amount = 17615; + const int64_t fee = 10000; + const std::string toAddress = "t1biXYN8wJahR76SqZTe1LBzTLf3JAsmT93"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_coin_type(TWCoinTypeZcash); + + auto txHash0 = parse_hex("2381825cd9069a200944996257e25b9403ba3e296bbc1dd98b01019cc7028cde"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(27615); + + // real key 1p "m/44'/133'/0'/0/14" + auto utxoKey0 = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); + auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZcash, utxoKey0); + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZcash); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = Zcash::TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 0; + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // Sign + auto result = Bitcoin::TransactionSigner::sign(input); + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + ASSERT_EQ(hex(serialized), "0400008085202f8901de8c02c79c01018bd91dbc6b293eba03945be25762994409209a06d95c828123000000006b483045022100e6e5071811c08d0c2e81cb8682ee36a8c6b645f5c08747acd3e828de2a4d8a9602200b13b36a838c7e8af81f2d6e7e694ede28833a480cfbaaa68a47187655298a7f0121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ffffffff01cf440000000000001976a914c3bacb129d85288a3deb5890ca9b711f7f71392688ac00000000000000000000000000000000000000"); +} + +TEST(TWZcashTransaction, SigningWithError) { + const int64_t amount = 17615; + const std::string toAddress = "t1biXYN8wJahR76SqZTe1LBzTLf3JAsmT93"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_coin_type(TWCoinTypeZcash); + + // Sign + auto result = Zcash::Signer::sign(input); + ASSERT_NE(result.error(), Common::Proto::OK); + + // PreImageHashes + auto preResult = Zcash::Signer::preImageHashes(input); + ASSERT_NE(preResult.error(), Common::Proto::OK); +} \ No newline at end of file diff --git a/tests/chains/Zcash/TransactionCompilerTests.cpp b/tests/chains/Zcash/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..6e92a26d50d --- /dev/null +++ b/tests/chains/Zcash/TransactionCompilerTests.cpp @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" + +#include +#include + +#include "Zcash/Signer.h" +#include "Zcash/Transaction.h" +#include "Zcash/TransactionBuilder.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(ZcashCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeZcash; + + // tx on mainnet + // https://explorer.zcha.in/transactions/387939ff8eb07dd264376eeef2e126394ab139802b1d80e92b21c1a2ae54fe92 + const int64_t amount = 17615; + const int64_t fee = 10000; + const std::string toAddress = "t1biXYN8wJahR76SqZTe1LBzTLf3JAsmT93"; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_coin_type(TWCoinTypeZcash); + + auto txHash0 = parse_hex("2381825cd9069a200944996257e25b9403ba3e296bbc1dd98b01019cc7028cde"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(27615); + + // real key 1p "m/44'/133'/0'/0/14" + auto utxoKey0 = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); + auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZcash, utxoKey0); + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZcash); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = Zcash::TransactionBuilder::plan(input); + plan.amount = amount; + plan.fee = fee; + plan.change = 0; + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // build preimage + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + ASSERT_EQ(preSigningOutput.hash_public_keys().size(), 1); + + auto preImageHash = data(preSigningOutput.hash_public_keys()[0].data_hash()); + EXPECT_EQ(hex(preImageHash), + "1472faba6529ac6d88f87f6ab881e438c3c8a17482b4a82ef13212333868258a"); + + // compile + auto publicKey = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); + TW::Data signature = parse_hex("3045022100e6e5071811c08d0c2e81cb8682ee36a8c6b645f5c08747acd3e828de2a4d8a9602200b13b36a838c7e8af81f2d6e7e694ede28833a480cfbaaa68a47187655298a7f"); + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKey.bytes}); + Bitcoin::Proto::SigningOutput signingOutput; + ASSERT_TRUE(signingOutput.ParseFromArray(outputData.data(), (int)outputData.size())); + ASSERT_EQ(hex(signingOutput.encoded()), "0400008085202f8901de8c02c79c01018bd91dbc6b293eba03945be25762994409209a06d95c828123000000006b483045022100e6e5071811c08d0c2e81cb8682ee36a8c6b645f5c08747acd3e828de2a4d8a9602200b13b36a838c7e8af81f2d6e7e694ede28833a480cfbaaa68a47187655298a7f0121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ffffffff01cf440000000000001976a914c3bacb129d85288a3deb5890ca9b711f7f71392688ac00000000000000000000000000000000000000"); + + { + auto result = Zcash::Signer::sign(input); + ASSERT_EQ(hex(result.encoded()), hex(signingOutput.encoded())); + } + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, {publicKey.bytes}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/Zelcash/TWCoinTypeTests.cpp b/tests/chains/Zelcash/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..ce646194bfb --- /dev/null +++ b/tests/chains/Zelcash/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWZelcashCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeZelcash)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeZelcash, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeZelcash, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeZelcash)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeZelcash)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeZelcash), 8); + ASSERT_EQ(TWBlockchainZcash, TWCoinTypeBlockchain(TWCoinTypeZelcash)); + ASSERT_EQ(0xbd, TWCoinTypeP2shPrefix(TWCoinTypeZelcash)); + ASSERT_EQ(0x1c, TWCoinTypeStaticPrefix(TWCoinTypeZelcash)); + assertStringsEqual(symbol, "FLUX"); + assertStringsEqual(txUrl, "https://explorer.runonflux.io/tx/t123"); + assertStringsEqual(accUrl, "https://explorer.runonflux.io/address/a12"); + assertStringsEqual(id, "zelcash"); + assertStringsEqual(name, "Flux"); +} diff --git a/tests/Zelcash/TWZelcashAddressTests.cpp b/tests/chains/Zelcash/TWZelcashAddressTests.cpp similarity index 92% rename from tests/Zelcash/TWZelcashAddressTests.cpp rename to tests/chains/Zelcash/TWZelcashAddressTests.cpp index 6d2b5807778..f247625c93d 100644 --- a/tests/Zelcash/TWZelcashAddressTests.cpp +++ b/tests/chains/Zelcash/TWZelcashAddressTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/Zelcash/TWZelcashTransactionTests.cpp b/tests/chains/Zelcash/TWZelcashTransactionTests.cpp similarity index 91% rename from tests/Zelcash/TWZelcashTransactionTests.cpp rename to tests/chains/Zelcash/TWZelcashTransactionTests.cpp index 90d2b09cb41..9a61ddcdd10 100644 --- a/tests/Zelcash/TWZelcashTransactionTests.cpp +++ b/tests/chains/Zelcash/TWZelcashTransactionTests.cpp @@ -1,11 +1,9 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include "Bitcoin/OutPoint.h" #include "HexCoding.h" @@ -40,7 +38,7 @@ TEST(TWZelcashTransaction, Encode) { auto unsignedData = Data{}; transaction.encode(unsignedData); - ASSERT_EQ(hex(unsignedData.begin(), unsignedData.end()), + ASSERT_EQ(hex(unsignedData), /* header */ "04000080" /* versionGroupId */ "85202f89" /* vin */ "01""a8c685478265f4c14dada651969c45a65e1aeb8cd6791f2f5bb6a1d9952104d9""01000000""6b483045022100a61e5d557568c2ddc1d9b03a7173c6ce7c996c4daecab007ac8f34bee01e6b9702204d38fdc0bcf2728a69fde78462a10fb45a9baa27873e6a5fc45fb5c76764202a01210365ffea3efa3908918a8b8627724af852fc9b86d7375b103ab0543cf418bcaa7f""feffffff" @@ -56,7 +54,7 @@ TEST(TWZelcashTransaction, Encode) { auto scriptCode = Bitcoin::Script(parse_hex("76a914507173527b4c3318a2aecd793bf1cfed705950cf88ac")); auto preImage = transaction.getPreImage(scriptCode, 0, TWBitcoinSigHashTypeAll, 0x02faf080); - ASSERT_EQ(hex(preImage.begin(), preImage.end()), + ASSERT_EQ(hex(preImage), /* header */ "04000080" /* versionGroupId */ "85202f89" /* hashPrevouts */ "fae31b8dec7b0b77e2c8d6b6eb0e7e4e55abc6574c26dd44464d9408a8e33f11" @@ -76,7 +74,7 @@ TEST(TWZelcashTransaction, Encode) { ); auto sighash = transaction.getSignatureHash(scriptCode, 0, TWBitcoinSigHashTypeAll, 0x02faf080, Bitcoin::BASE); - ASSERT_EQ(hex(sighash.begin(), sighash.end()), "f3148f80dfab5e573d5edfe7a850f5fd39234f80b5429d3a57edcc11e34c585b"); + ASSERT_EQ(hex(sighash), "f3148f80dfab5e573d5edfe7a850f5fd39234f80b5429d3a57edcc11e34c585b"); } TEST(TWZelcashTransaction, Signing) { @@ -86,6 +84,7 @@ TEST(TWZelcashTransaction, Signing) { const int64_t fee = 2260; auto input = Bitcoin::Proto::SigningInput(); + input.set_coin_type(TWCoinTypeZelcash); input.set_hash_type(TWBitcoinSigHashTypeAll); input.set_amount(amount); input.set_byte_fee(1); diff --git a/tests/chains/Zen/AddressTests.cpp b/tests/chains/Zen/AddressTests.cpp new file mode 100644 index 00000000000..bef8a87317a --- /dev/null +++ b/tests/chains/Zen/AddressTests.cpp @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "HDWallet.h" +#include "Zen/Address.h" +#include "PublicKey.h" +#include "PrivateKey.h" +#include +#include + +using namespace TW; +using namespace TW::Zen; + +TEST(ZenAddress, Valid) { + ASSERT_TRUE(Address::isValid("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg")); + ASSERT_TRUE(Address::isValid("zshX5BAgUvNgM1VoBVKZyFVVozTDjjJvRxJ")); +} + +TEST(ZenAddress, Invalid) { + ASSERT_FALSE(Address::isValid("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5abs")); +} + +TEST(ZenAddress, FromPrivateKey) { + auto privateKey = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto address = Address(pubKey); + ASSERT_EQ(address.string(), "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); +} + +TEST(ZenAddress, FromPublicKey) { + auto publicKey = PublicKey(parse_hex("02b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfe"), TWPublicKeyTypeSECP256k1); + auto address = Address(publicKey); + ASSERT_EQ(address.string(), "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); +} + +TEST(ZenAddress, FromString) { + auto address = Address("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + ASSERT_EQ(address.string(), "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + + address = Address("zshX5BAgUvNgM1VoBVKZyFVVozTDjjJvRxJ"); + ASSERT_EQ(address.string(), "zshX5BAgUvNgM1VoBVKZyFVVozTDjjJvRxJ"); +} diff --git a/tests/chains/Zen/SignerTests.cpp b/tests/chains/Zen/SignerTests.cpp new file mode 100644 index 00000000000..8b054461429 --- /dev/null +++ b/tests/chains/Zen/SignerTests.cpp @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Zen/Signer.h" +#include "Zen/Address.h" +#include "Zen/TransactionBuilder.h" + +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "proto/Bitcoin.pb.h" + +#include + +#include + +using namespace TW; +using namespace TW::Zen; + +TEST(ZenSigner, Sign) { + const int64_t amount = 10000; + const std::string toAddress = "zngBGZGKaaBamofSuFw5WMnvU2HQAtwGeb5"; + + auto blockHash = parse_hex("0000000004561422697a29d424d925334db5ef2e80232306a1ad3fd35f72dc81"); + std::reverse(blockHash.begin(), blockHash.end()); + auto blockHeight = 1147624; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + input.set_coin_type(TWCoinTypeZen); + + auto txHash0 = parse_hex("62dea4b87fd66ca8e75a199c93131827ed40fb96cd8412e3476540abb5139ea3"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(17600); + + auto utxoKey0 = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZen, utxoKey0); + ASSERT_EQ(utxoAddr0, "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZen, blockHash, blockHeight); + ASSERT_EQ(hex(script0.bytes), "76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = Signer::plan(input); + ASSERT_EQ(plan.fee(), 226); + plan.set_preblockhash(blockHash.data(), (int)blockHash.size()); + plan.set_preblockheight(blockHeight); + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan; + + // Sign + auto result = Bitcoin::TransactionSigner::sign(input); + ASSERT_TRUE(result) << std::to_string(result.error()); + auto signedTx = result.payload(); + + Data serialized; + signedTx.encode(serialized); + ASSERT_EQ(hex(serialized), + "0100000001a39e13b5ab406547e31284cd96fb40ed271813939c195ae7a86cd67fb8a4de62000000006a473044022014d687c0bee0b7b584db2eecbbf73b545ee255c42b8edf0944665df3fa882cfe02203bce2412d93c5a56cb4806ddd8297ff05f8fc121306e870bae33377a58a02f05012102b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfeffffffff0210270000000000003f76a914a58d22659b1082d1fa8698fc51996b43281bfce788ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4ce1c0000000000003f76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b400000000" + ); +} + +TEST(ZenSigner, SignWithError) { + const int64_t amount = 10000; + const std::string toAddress = "zngBGZGKaaBamofSuFw5WMnvU2HQAtwGeb5"; + + auto blockHash = parse_hex("0000000004561422697a29d424d925334db5ef2e80232306a1ad3fd35f72dc81"); + std::reverse(blockHash.begin(), blockHash.end()); + auto blockHeight = 1147624; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + input.set_coin_type(TWCoinTypeZen); + + auto plan = Signer::plan(input); + plan.set_preblockhash(blockHash.data(), (int)blockHash.size()); + plan.set_preblockheight(blockHeight); + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan; + + // Sign + auto result = Zen::Signer::sign(input); + + ASSERT_NE(result.error(), Common::Proto::OK); + + // PreImageHash + auto preResult = Zen::Signer::preImageHashes(input); + + ASSERT_NE(preResult.error(), Common::Proto::OK); +} \ No newline at end of file diff --git a/tests/chains/Zen/TWAnyAddressTests.cpp b/tests/chains/Zen/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..d6e2a86b245 --- /dev/null +++ b/tests/chains/Zen/TWAnyAddressTests.cpp @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWZen, Address) { + auto string = STRING("znfexeyosWvMG93AjJx6CkRzKtS2aBdDgAx"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeZen)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "9fd1b64dad29d82b151206f66057bab1dae2f517"); +} diff --git a/tests/chains/Zen/TWAnySignerTests.cpp b/tests/chains/Zen/TWAnySignerTests.cpp new file mode 100644 index 00000000000..55a83993273 --- /dev/null +++ b/tests/chains/Zen/TWAnySignerTests.cpp @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include + +#include "Bitcoin/Signer.h" +#include "Bitcoin/Address.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(TWAnySignerZen, Sign) { + const int64_t amount = 10000; + const std::string toAddress = "zngBGZGKaaBamofSuFw5WMnvU2HQAtwGeb5"; + + auto blockHash = parse_hex("0000000004561422697a29d424d925334db5ef2e80232306a1ad3fd35f72dc81"); + std::reverse(blockHash.begin(), blockHash.end()); + auto blockHeight = 1147624; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + input.set_coin_type(TWCoinTypeZen); + + auto txHash0 = parse_hex("62dea4b87fd66ca8e75a199c93131827ed40fb96cd8412e3476540abb5139ea3"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(17600); + + auto utxoKey0 = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZen, utxoKey0); + ASSERT_EQ(utxoAddr0, "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZen, blockHash, blockHeight); + ASSERT_EQ(hex(script0.bytes), "76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + Bitcoin::Proto::TransactionPlan plan; + { + ANY_PLAN(input, plan, TWCoinTypeZen); + + ASSERT_EQ(plan.fee(), 226); + + plan.set_preblockhash(blockHash.data(), (int)blockHash.size()); + plan.set_preblockheight(blockHeight); + + *input.mutable_plan() = plan; + } + + Bitcoin::Proto::SigningOutput output; + { + ANY_SIGN(input, TWCoinTypeZen); + ASSERT_EQ(output.error(), Common::Proto::OK); + } + + // Sign + ASSERT_EQ(hex(output.encoded()), + "0100000001a39e13b5ab406547e31284cd96fb40ed271813939c195ae7a86cd67fb8a4de62000000006a473044022014d687c0bee0b7b584db2eecbbf73b545ee255c42b8edf0944665df3fa882cfe02203bce2412d93c5a56cb4806ddd8297ff05f8fc121306e870bae33377a58a02f05012102b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfeffffffff0210270000000000003f76a914a58d22659b1082d1fa8698fc51996b43281bfce788ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4ce1c0000000000003f76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b400000000" + ); +} diff --git a/tests/chains/Zen/TWCoinTypeTests.cpp b/tests/chains/Zen/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..3cd9d0e75a8 --- /dev/null +++ b/tests/chains/Zen/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWZenCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeZen)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("b7f548640766fb024247accf4e01bec37d88d49c4900357edc84d49a09ff4430")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeZen, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("znRchPtvEyJJUwGbCALqyjwHJb1Gx6z4H4j")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeZen, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeZen)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeZen)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeZen), 8); + ASSERT_EQ(TWBlockchainZen, TWCoinTypeBlockchain(TWCoinTypeZen)); + ASSERT_EQ(0x96, TWCoinTypeP2shPrefix(TWCoinTypeZen)); + ASSERT_EQ(0x20, TWCoinTypeStaticPrefix(TWCoinTypeZen)); + assertStringsEqual(symbol, "ZEN"); + assertStringsEqual(txUrl, "https://explorer.horizen.io/tx/b7f548640766fb024247accf4e01bec37d88d49c4900357edc84d49a09ff4430"); + assertStringsEqual(accUrl, "https://explorer.horizen.io/address/znRchPtvEyJJUwGbCALqyjwHJb1Gx6z4H4j"); + assertStringsEqual(id, "zen"); + assertStringsEqual(name, "Zen"); +} diff --git a/tests/chains/Zen/TransactionBuilderTests.cpp b/tests/chains/Zen/TransactionBuilderTests.cpp new file mode 100644 index 00000000000..8ba691be1b6 --- /dev/null +++ b/tests/chains/Zen/TransactionBuilderTests.cpp @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Bitcoin/Transaction.h" +#include "Bitcoin/TransactionPlan.h" +#include "Bitcoin/TransactionSigner.h" +#include "Zen/Address.h" +#include "Zen/Signer.h" +#include "Zen/TransactionBuilder.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" + +#include + +#include +#include + +using namespace TW; +using namespace TW::Bitcoin; + +TEST(ZenTransactionBuilder, Build) { + const int64_t amount = 10000; + const std::string toAddress = "zngBGZGKaaBamofSuFw5WMnvU2HQAtwGeb5"; + + auto blockHash = parse_hex("0000000004561422697a29d424d925334db5ef2e80232306a1ad3fd35f72dc81"); + std::reverse(blockHash.begin(), blockHash.end()); + auto blockHeight = 1147624; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + input.set_coin_type(TWCoinTypeZen); + + Data opScript = parse_hex("00010203"); + input.set_output_op_return(std::string(opScript.begin(), opScript.end())); + + auto eo = input.add_extra_outputs(); + eo->set_to_address("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + eo->set_amount(7000); + + auto txHash0 = parse_hex("62dea4b87fd66ca8e75a199c93131827ed40fb96cd8412e3476540abb5139ea3"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(0); + utxo0->mutable_out_point()->set_sequence(UINT32_MAX); + utxo0->set_amount(17600); + + auto utxoKey0 = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZen, utxoKey0); + ASSERT_EQ(utxoAddr0, "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZen, blockHash, blockHeight); + ASSERT_EQ(hex(script0.bytes), "76a914cf83669620de8bbdf2cefcdc5b5113195603c56588ac2081dc725fd33fada1062323802eefb54d3325d924d4297a69221456040000000003e88211b4"); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); + + auto plan = Bitcoin::TransactionSigner::plan(input); + ASSERT_EQ(plan.fee, 294); + plan.preBlockHash = blockHash; + plan.preBlockHeight = blockHeight; + + // plan1 + auto result = Zen::TransactionBuilder::build(plan, input).payload(); + + ASSERT_GT(result.outputs.size(), 0ul); + ASSERT_EQ(result.outputs[0].value, plan.amount); + + // plan2 + result = Zen::TransactionBuilder::build(plan, input).payload(); + + ASSERT_EQ(result.outputs.size(), 4ul); + ASSERT_EQ(result.outputs[3].value, 7000); +} + +TEST(ZenTransactionBuilder, BuildScript) { + auto blockHash = parse_hex("0000000004561422697a29d424d925334db5ef2e80232306a1ad3fd35f72dc81"); + std::reverse(blockHash.begin(), blockHash.end()); + auto blockHeight = 1147624; + + // invalid address + auto result = Zen::TransactionBuilder::prepareOutputWithScript( + "DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E", + 10000, TWCoinTypeZen, blockHash, blockHeight); + ASSERT_FALSE(result.has_value()); +} \ No newline at end of file diff --git a/tests/chains/Zen/TransactionCompilerTests.cpp b/tests/chains/Zen/TransactionCompilerTests.cpp new file mode 100644 index 00000000000..006b9539811 --- /dev/null +++ b/tests/chains/Zen/TransactionCompilerTests.cpp @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include "proto/Bitcoin.pb.h" + +#include +#include + +#include "Zen/Signer.h" +#include "Zen/TransactionBuilder.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +TEST(ZenCompiler, CompileWithSignatures) { + const auto coin = TWCoinTypeZen; + + const int64_t amount = 200000; + const std::string toAddress = "znma8BydGx1p7SZ17g5JMMWXqSoRSE7BNdQ"; + + auto blockHash = parse_hex("000000000396ef95695b498168964e1733aca9fe47bb4f9b2851dcd0ec0edad0"); + std::reverse(blockHash.begin(), blockHash.end()); + auto blockHeight = 1163482; + + auto sblockHash = parse_hex("0000000002906dc9ef21c60d08cd03d192cba94de66095c63082d8e7e9436d40"); + std::reverse(sblockHash.begin(), sblockHash.end()); + auto sblockHeight = 1163438; + + auto input = Bitcoin::Proto::SigningInput(); + input.set_hash_type(TWBitcoinSigHashTypeAll); + input.set_amount(amount); + input.set_byte_fee(1); + input.set_to_address(toAddress); + input.set_change_address("zncug4MEDrunR5WgdWfGB1t9Bjp8RCpKxA6"); + input.set_coin_type(coin); + input.set_lock_time(1163772); + + auto txHash0 = parse_hex("89f799d7aaf17dbc619f5c68aa5a5ae55ceec779f9009203a87359217405f8d8"); + std::reverse(txHash0.begin(), txHash0.end()); + + auto utxo0 = input.add_utxo(); + utxo0->mutable_out_point()->set_hash(txHash0.data(), txHash0.size()); + utxo0->mutable_out_point()->set_index(1); + utxo0->mutable_out_point()->set_sequence(4294967294); + utxo0->set_amount(1249057); + + auto utxoAddr0 = "znj6M9EbCmU7UKN2zgAQ8j1GwUnr4QbZBYt"; + // build utxo scriptPubKey + // check 89f799d7aaf17dbc619f5c68aa5a5ae55ceec779f9009203a87359217405f8d8,1 + auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, coin, sblockHash, sblockHeight); + utxo0->set_script(script0.bytes.data(), script0.bytes.size()); + + auto plan = Zen::TransactionBuilder::plan(input); + ASSERT_EQ(plan.fee, 226); + plan.preBlockHash = blockHash; + plan.preBlockHeight = blockHeight; + plan.fee = 302; + plan.change = 1249057 - plan.amount - plan.fee; + + auto& protoPlan = *input.mutable_plan(); + protoPlan = plan.proto(); + + // build preimage + const auto txInputData = data(input.SerializeAsString()); + EXPECT_GT(txInputData.size(), 0ul); + + const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); + ASSERT_GT(preImageHashes.size(), 0ul); + + Bitcoin::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHashes.data(), int(preImageHashes.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + ASSERT_EQ(preSigningOutput.hash_public_keys().size(), 1); + + auto preImageHash = data(preSigningOutput.hash_public_keys()[0].data_hash()); + EXPECT_EQ(hex(preImageHash), + "882e2e61e740ff3d5889995679bf3dcda1b872e0d93be23c89a4fd4e3837f200"); + + // compile + auto publicKey = PublicKey(parse_hex("02806408d2f6d5095bb73e89f9edbe02fe81853f25c541d33da4422c6916c1d0e1"), TWPublicKeyTypeSECP256k1); + TW::Data signature = parse_hex("3045022100b27a4d10a4c5e758c4a379ccf7050eae6d8d4dacf5c65894d024de5ab947d4640220194ffccb29c95fe0ae3fb91a40276536494102891c6c5a9aee6063106fa55d30"); + auto outputData = + TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKey.bytes}); + Bitcoin::Proto::SigningOutput signingOutput; + ASSERT_TRUE(signingOutput.ParseFromArray(outputData.data(), (int)outputData.size())); + // txid: 0fc555f8e205e66576f760d99270eaa6d60480c0e816209b2058387b65c2a000 + ASSERT_EQ(hex(signingOutput.encoded()), "0100000001d8f80574215973a8039200f979c7ee5ce55a5aaa685c9f61bc7df1aad799f789010000006b483045022100b27a4d10a4c5e758c4a379ccf7050eae6d8d4dacf5c65894d024de5ab947d4640220194ffccb29c95fe0ae3fb91a40276536494102891c6c5a9aee6063106fa55d30012102806408d2f6d5095bb73e89f9edbe02fe81853f25c541d33da4422c6916c1d0e1feffffff02400d0300000000003f76a914e0b858909b6b2c14996658085ed907abd880d32d88ac20d0da0eecd0dc51289b4fbb47fea9ac33174e966881495b6995ef96030000000003dac011b4b3001000000000003f76a91481b1b83b2ae8a4cddd72750dc5252c4bddd4e57e88ac20d0da0eecd0dc51289b4fbb47fea9ac33174e966881495b6995ef96030000000003dac011b4fcc11100"); + + { // Negative: inconsistent signatures & publicKeys + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {signature, signature}, {publicKey.bytes}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } + + { // Negative: empty signatures + outputData = TransactionCompiler::compileWithSignatures( + coin, txInputData, {}, {}); + Bitcoin::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); + EXPECT_EQ(output.encoded().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +} diff --git a/tests/chains/ZenEON/TWCoinTypeTests.cpp b/tests/chains/ZenEON/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..a0571a15621 --- /dev/null +++ b/tests/chains/ZenEON/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWZenEONCoinType, TWCoinType) { + const auto coin = TWCoinTypeZenEON; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xb462e3dac8eef21957d3b6cff3c184d083434367a726dd871e98a774f4d037a5")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x09bCfC348101B1179BCF3837aC996cF09357215f")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "zeneon"); + assertStringsEqual(name, "Zen EON"); + assertStringsEqual(symbol, "ZEN"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "7332"); + assertStringsEqual(txUrl, "https://eon-explorer.horizenlabs.io/tx/0xb462e3dac8eef21957d3b6cff3c184d083434367a726dd871e98a774f4d037a5"); + assertStringsEqual(accUrl, "https://eon-explorer.horizenlabs.io/address/0x09bCfC348101B1179BCF3837aC996cF09357215f"); +} diff --git a/tests/chains/ZetaEVM/TWCoinTypeTests.cpp b/tests/chains/ZetaEVM/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..b8e9eaf9ef9 --- /dev/null +++ b/tests/chains/ZetaEVM/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWZetaEVMCoinType, TWCoinType) { + const auto coin = TWCoinTypeZetaEVM; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x04cb1201857de29af97b755e51c888454fb96c1f3bb3c1329bb94d5353d5c19e")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x85539A58F9c88DdDccBaBBfc660968323Fd1e167")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "zetaevm"); + assertStringsEqual(name, "Zeta EVM"); + assertStringsEqual(symbol, "ZETA"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + assertStringsEqual(chainId, "7000"); + assertStringsEqual(txUrl, "https://explorer.zetachain.com/evm/tx/0x04cb1201857de29af97b755e51c888454fb96c1f3bb3c1329bb94d5353d5c19e"); + assertStringsEqual(accUrl, "https://explorer.zetachain.com/address/0x85539A58F9c88DdDccBaBBfc660968323Fd1e167"); +} diff --git a/tests/chains/Zilliqa/AddressTests.cpp b/tests/chains/Zilliqa/AddressTests.cpp new file mode 100644 index 00000000000..db26af859b7 --- /dev/null +++ b/tests/chains/Zilliqa/AddressTests.cpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "Zilliqa/Address.h" +#include "Zilliqa/AddressChecksum.h" + +#include + +#include + +namespace TW::Zilliqa::tests { + +TEST(ZilliqaAddress, FromPrivateKey) { + const auto privateKey = + PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); + const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); + const auto address = Address(publicKey); + auto expectedAddress = "zil1j8xae6lggm8y63m3y2r7aefu797ze7mhzulnqg"; + + ASSERT_EQ(address.getHrp(), stringForHRP(TWHRPZilliqa)); + ASSERT_EQ(address.string(), expectedAddress); +} + +TEST(ZilliqaAddress, Validation) { + ASSERT_FALSE(Zilliqa::Address::isValid("0x91cddcebe846ce4d47712287eee53cf17c2cfb7")); + ASSERT_FALSE(Zilliqa::Address::isValid("")); + ASSERT_FALSE(Zilliqa::Address::isValid("0x")); + ASSERT_FALSE(Zilliqa::Address::isValid("91cddcebe846ce4d47712287eee53cf17c2cfb7")); + + ASSERT_TRUE(Zilliqa::Address::isValid("zil1fwh4ltdguhde9s7nysnp33d5wye6uqpugufkz7")); +} + +TEST(ZilliqaAddress, Checksum) { + ASSERT_EQ( + checksum(parse_hex("4BAF5FADA8E5DB92C3D3242618C5B47133AE003C")), + "4BAF5faDA8e5Db92C3d3242618c5B47133AE003C"); + ASSERT_EQ( + checksum(parse_hex("448261915A80CDE9BDE7C7A791685200D3A0BF4E")), + "448261915a80cdE9BDE7C7a791685200D3A0bf4E"); + ASSERT_EQ( + checksum(parse_hex("0xDED02FD979FC2E55C0243BD2F52DF022C40ADA1E")), + "Ded02fD979fC2e55c0243bd2F52df022c40ADa1E"); + ASSERT_EQ( + checksum(parse_hex("0x13F06E60297BEA6A3C402F6F64C416A6B31E586E")), + "13F06E60297bea6A3c402F6f64c416A6b31e586e"); + ASSERT_EQ( + checksum(parse_hex("0x1A90C25307C3CC71958A83FA213A2362D859CF33")), + "1a90C25307C3Cc71958A83fa213A2362D859CF33"); +} + +} // namespace TW::Zilliqa::tests diff --git a/tests/chains/Zilliqa/SignatureTests.cpp b/tests/chains/Zilliqa/SignatureTests.cpp new file mode 100644 index 00000000000..bea022cd485 --- /dev/null +++ b/tests/chains/Zilliqa/SignatureTests.cpp @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include "HexCoding.h" +#include "Data.h" +#include +#include + +#include + +using namespace TW; + +TEST(ZilliqaSignature, Signing) { + auto keyData = WRAPD(TWDataCreateWithHexString(STRING("0xafeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(keyData.get())); + auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + + auto message = "hello schnorr"; + auto messageData = WRAPD(TWDataCreateWithBytes((uint8_t *)message, strnlen(message, 13))); + auto signatureData = WRAPD(TWPrivateKeySignZilliqaSchnorr(privateKey.get(), messageData.get())); + auto signature = data(TWDataBytes(signatureData.get()), TWDataSize(signatureData.get())); + + ASSERT_TRUE(TWPublicKeyVerifyZilliqaSchnorr(pubKey.get(), signatureData.get(), messageData.get())); + EXPECT_EQ(hex(signature), "d166b1ae7892c5ef541461dc12a50214d0681b63d8037cda29a3fe6af8bb973e4ea94624d85bc0010bdc1b38d05198328fae21254adc2bf5feaf2804d54dba55"); +} diff --git a/tests/chains/Zilliqa/SignerTests.cpp b/tests/chains/Zilliqa/SignerTests.cpp new file mode 100644 index 00000000000..f8e0440307d --- /dev/null +++ b/tests/chains/Zilliqa/SignerTests.cpp @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "PrivateKey.h" +#include "Zilliqa/Address.h" +#include "Zilliqa/Signer.h" +#include "proto/Zilliqa.pb.h" +#include "uint256.h" + +#include + +namespace TW::Zilliqa::tests { + +TEST(ZilliqaSigner, PreImage) { + auto privateKey = PrivateKey(parse_hex("0E891B9DFF485000C7D1DC22ECF3A583CC50328684321D61947A86E57CF6C638")); + auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + ASSERT_EQ(hex(pubKey.bytes), "034ae47910d58b9bde819c3cffa8de4441955508db00aa2540db8e6bf6e99abc1b"); + + auto amount = uint256_t(15000000000000); + auto gasPrice = uint256_t(1000000000); + auto amountData = store(amount); + auto gasData = store(gasPrice); + auto toAddress = Address(parse_hex("0x9Ca91EB535Fb92Fda5094110FDaEB752eDb9B039")); + + auto input = Proto::SigningInput(); + auto& tx = *input.mutable_transaction(); + auto& transfer = *tx.mutable_transfer(); + transfer.set_amount(amountData.data(), amountData.size()); + + input.set_version(65537); + input.set_nonce(4); + input.set_to(toAddress.string()); + input.set_gas_price(gasData.data(), gasData.size()); + input.set_gas_limit(uint64_t(1)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + Address address; + auto preImage = Signer::getPreImage(input, address); + auto signature = Signer::sign(input).signature(); + + ASSERT_EQ(hex(preImage), "0881800410041a149ca91eb535fb92fda5094110fdaeb752edb9b03922230a21034ae47910d58b9bde819c3cffa8de4441955508db00aa2540db8e6bf6e99abc1b2a120a10000000000000000000000da475abf00032120a100000000000000000000000003b9aca003801"); + + ASSERT_TRUE(pubKey.verifyZilliqa(Data(signature.begin(), signature.end()), preImage)); +} + +TEST(ZilliqaSigner, Signing) { + auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")); + auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + + // 1 ZIL + auto amount = uint256_t(1000000000000); + auto gasPrice = uint256_t(1000000000); + auto amountData = store(amount); + auto gasData = store(gasPrice); + auto toAddress = Address(parse_hex("0x7FCcaCf066a5F26Ee3AFfc2ED1FA9810Deaa632C")); + + auto input = Proto::SigningInput(); + auto& tx = *input.mutable_transaction(); + auto& transfer = *tx.mutable_transfer(); + transfer.set_amount(amountData.data(), amountData.size()); + + input.set_version(65537); + input.set_nonce(2); + input.set_to(toAddress.string()); + input.set_gas_price(gasData.data(), gasData.size()); + input.set_gas_limit(uint64_t(1)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto output = Signer::sign(input); + + ASSERT_EQ(hex(output.signature()), "001fa4df08c11a4a79e96e69399ee48eeecc78231a78b0355a8ca783c77c139436e37934fecc2252ed8dac00e235e22d18410461fb896685c4270642738ed268"); + ASSERT_EQ(output.json(), R"({"amount":"1000000000000","code":"","data":"","gasLimit":"1","gasPrice":"1000000000","nonce":2,"pubKey":"03fb30b196ce3e976593ecc2da220dca9cdea8c84d2373770042a930b892ac0f5c","signature":"001fa4df08c11a4a79e96e69399ee48eeecc78231a78b0355a8ca783c77c139436e37934fecc2252ed8dac00e235e22d18410461fb896685c4270642738ed268","toAddr":"7FCcaCf066a5F26Ee3AFfc2ED1FA9810Deaa632C","version":65537})"); +} + +TEST(ZilliqaSigner, SigningData) { + // https://viewblock.io/zilliqa/tx/0x6228b3d7e69fc3481b84fd00e892cec359a41654f58948ff7b1b932396b00ad9 + auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")); + auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + + // 10 ZIL + auto amount = uint256_t(10000000000000); + auto gasPrice = uint256_t(2000000000); + auto amountData = store(amount); + auto gasData = store(gasPrice); + + std::string json = "{\"_tag\":\"DelegateStake\",\"params\":[{\"type\":\"ByStr20\",\"value\":\"0x122219cCeAb410901e96c3A0e55E46231480341b\",\"vname\":\"ssnaddr\"}]}"; + auto jsonData = Data(json.begin(), json.end()); + + auto input = Proto::SigningInput(); + auto& tx = *input.mutable_transaction(); + auto& raw = *tx.mutable_raw_transaction(); + raw.set_amount(amountData.data(), amountData.size()); + raw.set_data(jsonData.data(), jsonData.size()); + + input.set_version(65537); + input.set_nonce(56); + input.set_to("zil1g029nmzsf36r99vupp4s43lhs40fsscx3jjpuy"); + input.set_gas_price(gasData.data(), gasData.size()); + input.set_gas_limit(uint64_t(5000)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + auto output = Signer::sign(input); + ASSERT_EQ(output.json(), R"({"amount":"10000000000000","code":"","data":"{\"_tag\":\"DelegateStake\",\"params\":[{\"type\":\"ByStr20\",\"value\":\"0x122219cCeAb410901e96c3A0e55E46231480341b\",\"vname\":\"ssnaddr\"}]}","gasLimit":"5000","gasPrice":"2000000000","nonce":56,"pubKey":"03fb30b196ce3e976593ecc2da220dca9cdea8c84d2373770042a930b892ac0f5c","signature":"437fb5c3ce2c6b01f9d490f670539fae4533c82a21fa7edfe6b23df70d732937e8c578c8d6ed24be9150f5126f7b7c977a467af8947ef92a720908a761a6eb0d","toAddr":"43D459eC504C7432959c086B0ac7F7855E984306","version":65537})"); + ASSERT_EQ(hex(output.signature()), "437fb5c3ce2c6b01f9d490f670539fae4533c82a21fa7edfe6b23df70d732937e8c578c8d6ed24be9150f5126f7b7c977a467af8947ef92a720908a761a6eb0d"); +} + +} // namespace TW::Zilliqa::tests diff --git a/tests/chains/Zilliqa/TWAnySignerTests.cpp b/tests/chains/Zilliqa/TWAnySignerTests.cpp new file mode 100644 index 00000000000..1b420161496 --- /dev/null +++ b/tests/chains/Zilliqa/TWAnySignerTests.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "proto/Zilliqa.pb.h" +#include "uint256.h" +#include "TestUtilities.h" +#include +#include + +namespace TW::Zilliqa::tests { + +TEST(TWAnySignerZilliqa, Sign) { + auto input = TW::Zilliqa::Proto::SigningInput(); + auto& tx = *input.mutable_transaction(); + auto& transfer = *tx.mutable_transfer(); + auto key = parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748"); + auto amount = store(uint256_t(1000000000000)); + auto gasPrice = store(uint256_t(1000000000)); + + input.set_version(65537); + input.set_nonce(2); + input.set_to("zil10lx2eurx5hexaca0lshdr75czr025cevqu83uz"); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(1); + input.set_private_key(key.data(), key.size()); + transfer.set_amount(amount.data(), amount.size()); + + TW::Zilliqa::Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeZilliqa); + + EXPECT_EQ(hex(output.signature()), "001fa4df08c11a4a79e96e69399ee48eeecc78231a78b0355a8ca783c77c139436e37934fecc2252ed8dac00e235e22d18410461fb896685c4270642738ed268"); + EXPECT_EQ(hex(output.json()), "7b22616d6f756e74223a2231303030303030303030303030222c22636f6465223a22222c2264617461223a22222c226761734c696d6974223a2231222c226761735072696365223a2231303030303030303030222c226e6f6e6365223a322c227075624b6579223a22303366623330623139366365336539373635393365636332646132323064636139636465613863383464323337333737303034326139333062383932616330663563222c227369676e6174757265223a223030316661346466303863313161346137396539366536393339396565343865656563633738323331613738623033353561386361373833633737633133393433366533373933346665636332323532656438646163303065323335653232643138343130343631666238393636383563343237303634323733386564323638222c22746f41646472223a2237464363614366303636613546323645653341466663324544314641393831304465616136333243222c2276657273696f6e223a36353533377d"); +} + +TEST(TWAnySignerZilliqa, SignJSON) { + auto json = STRING(R"({"version":65537,"nonce":"2","to":"zil10lx2eurx5hexaca0lshdr75czr025cevqu83uz","gasPrice":"O5rKAA==","gasLimit":"1","transaction":{"transfer":{"amount":"6NSlEAA="}}})"); + auto key = DATA("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748"); + auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeZilliqa)); + + ASSERT_TRUE(TWAnySignerSupportsJSON(TWCoinTypeZilliqa)); + assertStringsEqual(result, "7b22616d6f756e74223a2231303030303030303030303030222c22636f6465223a22222c2264617461223a22222c226761734c696d6974223a2231222c226761735072696365223a2231303030303030303030222c226e6f6e6365223a322c227075624b6579223a22303366623330623139366365336539373635393365636332646132323064636139636465613863383464323337333737303034326139333062383932616330663563222c227369676e6174757265223a223030316661346466303863313161346137396539366536393339396565343865656563633738323331613738623033353561386361373833633737633133393433366533373933346665636332323532656438646163303065323335653232643138343130343631666238393636383563343237303634323733386564323638222c22746f41646472223a2237464363614366303636613546323645653341466663324544314641393831304465616136333243222c2276657273696f6e223a36353533377d"); +} + +} // namespace TW::Zilliqa::tests \ No newline at end of file diff --git a/tests/chains/Zilliqa/TWCoinTypeTests.cpp b/tests/chains/Zilliqa/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..f1c5545b0d2 --- /dev/null +++ b/tests/chains/Zilliqa/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWZilliqaCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeZilliqa)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("t123")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeZilliqa, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("a12")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeZilliqa, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeZilliqa)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeZilliqa)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeZilliqa), 12); + ASSERT_EQ(TWBlockchainZilliqa, TWCoinTypeBlockchain(TWCoinTypeZilliqa)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeZilliqa)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeZilliqa)); + assertStringsEqual(symbol, "ZIL"); + assertStringsEqual(txUrl, "https://viewblock.io/zilliqa/tx/t123"); + assertStringsEqual(accUrl, "https://viewblock.io/zilliqa/address/a12"); + assertStringsEqual(id, "zilliqa"); + assertStringsEqual(name, "Zilliqa"); +} diff --git a/tests/Zilliqa/TWZilliqaAddressTests.cpp b/tests/chains/Zilliqa/TWZilliqaAddressTests.cpp similarity index 80% rename from tests/Zilliqa/TWZilliqaAddressTests.cpp rename to tests/chains/Zilliqa/TWZilliqaAddressTests.cpp index e2e19630cbe..63b7b515816 100644 --- a/tests/Zilliqa/TWZilliqaAddressTests.cpp +++ b/tests/chains/Zilliqa/TWZilliqaAddressTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/chains/ZkLinkNova/TWCoinTypeTests.cpp b/tests/chains/ZkLinkNova/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..933ab752af8 --- /dev/null +++ b/tests/chains/ZkLinkNova/TWCoinTypeTests.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +TEST(TWZkLinkNovaCoinType, TWCoinType) { + const auto coin = TWCoinTypeZkLinkNova; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xeb5eb8710369c89115a83f3e744c15c9d388030cfce2fd3a653dbd18f2947400")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xF95115BaD9a4585B3C5e2bfB50579f17163A45aA")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "zklinknova"); + assertStringsEqual(name, "zkLink Nova Mainnet"); + assertStringsEqual(symbol, "ETH"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0); + assertStringsEqual(txUrl, "https://explorer.zklink.io/tx/0xeb5eb8710369c89115a83f3e744c15c9d388030cfce2fd3a653dbd18f2947400"); + assertStringsEqual(accUrl, "https://explorer.zklink.io/address/0xF95115BaD9a4585B3C5e2bfB50579f17163A45aA"); +} diff --git a/tests/chains/ZkSyncV2/TWCoinTypeTests.cpp b/tests/chains/ZkSyncV2/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..b72bb030ab0 --- /dev/null +++ b/tests/chains/ZkSyncV2/TWCoinTypeTests.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +namespace TW::TWZksync::tests { + +TEST(TWZksyncCoinType, TWCoinType) { + const auto coin = TWCoinTypeZksync; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto chainId = WRAPS(TWCoinTypeChainId(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0xae38d3ede1104d088b474da261d0eb4847952c3db24c21e820502f4c1b0c01f5")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0xeF86b2c8740518548ae449c4C3892B4be0475d8c")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "zksync"); + assertStringsEqual(name, "zkSync Era"); + assertStringsEqual(symbol, "ETH"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 18); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainEthereum); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); + ASSERT_EQ(TWCoinTypeZksync, 10000324ull); + assertStringsEqual(chainId, "324"); + assertStringsEqual(txUrl, "https://explorer.zksync.io/tx/0xae38d3ede1104d088b474da261d0eb4847952c3db24c21e820502f4c1b0c01f5"); + assertStringsEqual(accUrl, "https://explorer.zksync.io/address/0xeF86b2c8740518548ae449c4C3892B4be0475d8c"); +} + +} // namespace TW::TWZksync::tests diff --git a/tests/chains/xDai/TWCoinTypeTests.cpp b/tests/chains/xDai/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..68ebf0e6ee6 --- /dev/null +++ b/tests/chains/xDai/TWCoinTypeTests.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. +// +// This is a GENERATED FILE, changes made here MAY BE LOST. +// Generated one-time (codegen/bin/cointests) +// + +#include "TestUtilities.h" +#include +#include + + +TEST(TWxDaiCoinType, TWCoinType) { + auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeXDai)); + auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x936798a1ef607c9e856d7861b15999c770c06f0887c4fc1f6acbf3bef09899c1")); + auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeXDai, txId.get())); + auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x12d61a95CF55e18D267C2F1AA67d8e42ae1368f8")); + auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeXDai, accId.get())); + auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeXDai)); + auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeXDai)); + + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeXDai), 18); + ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeXDai)); + ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeXDai)); + ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeXDai)); + assertStringsEqual(symbol, "xDAI"); + assertStringsEqual(txUrl, "https://blockscout.com/xdai/mainnet/tx/0x936798a1ef607c9e856d7861b15999c770c06f0887c4fc1f6acbf3bef09899c1"); + assertStringsEqual(accUrl, "https://blockscout.com/xdai/mainnet/address/0x12d61a95CF55e18D267C2F1AA67d8e42ae1368f8"); + assertStringsEqual(id, "xdai"); + assertStringsEqual(name, "Gnosis Chain"); +} diff --git a/tests/common/AnyAddressTests.cpp b/tests/common/AnyAddressTests.cpp new file mode 100644 index 00000000000..fc765c6f548 --- /dev/null +++ b/tests/common/AnyAddressTests.cpp @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "AnyAddress.h" +#include "HexCoding.h" + +#include + +namespace TW::tests { + +constexpr auto ANY_ADDRESS_TEST_ADDRESS = "bc1qcj2vfjec3c3luf9fx9vddnglhh9gawmncmgxhz"; +constexpr auto ANY_ADDRESS_TEST_PUBKEY = "02753f5c275e1847ba4d2fd3df36ad00af2e165650b35fe3991e9c9c46f68b12bc"; + +TEST(AnyAddress, createFromString) { + std::unique_ptr addr(AnyAddress::createAddress(ANY_ADDRESS_TEST_ADDRESS, TWCoinTypeBitcoin)); + EXPECT_EQ(ANY_ADDRESS_TEST_ADDRESS, addr->address); +} + +TEST(AnyAddress, createFromPubKey) { + const Data key = parse_hex(ANY_ADDRESS_TEST_PUBKEY); + PublicKey publicKey(key, TWPublicKeyTypeSECP256k1); + std::unique_ptr addr(AnyAddress::createAddress(publicKey, TWCoinTypeBitcoin)); + EXPECT_EQ(ANY_ADDRESS_TEST_ADDRESS, addr->address); +} + +TEST(AnyAddress, createFromPubKeyDerivation) { + const Data key = parse_hex(ANY_ADDRESS_TEST_PUBKEY); + PublicKey publicKey(key, TWPublicKeyTypeSECP256k1); + { + std::unique_ptr addr(AnyAddress::createAddress(publicKey, TWCoinTypeBitcoin, TWDerivationDefault, std::monostate())); + EXPECT_EQ(addr->address, ANY_ADDRESS_TEST_ADDRESS); + } + { + std::unique_ptr addr(AnyAddress::createAddress(publicKey, TWCoinTypeBitcoin, TWDerivationBitcoinLegacy, std::monostate())); + EXPECT_EQ(addr->address, "1JvRfEQFv5q5qy9uTSAezH7kVQf4hqnHXx"); + } +} + +TEST(AnyAddress, createFromWrongString) { + std::unique_ptr addr(AnyAddress::createAddress("1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax", TWCoinTypeBitcoin)); + EXPECT_EQ(nullptr, addr); +} + +} // namespace TW::tests diff --git a/tests/common/Base64Tests.cpp b/tests/common/Base64Tests.cpp new file mode 100644 index 00000000000..b5d01b4ccba --- /dev/null +++ b/tests/common/Base64Tests.cpp @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base64.h" +#include "Data.h" +#include "HexCoding.h" + +#include + +namespace TW::Base64::tests { + +TEST(Base64, encode) { + auto encoded = encode(data("Hello, world!")); + EXPECT_EQ("SGVsbG8sIHdvcmxkIQ==", encoded); + encoded = encode(data("1")); + EXPECT_EQ("MQ==", encoded); + encoded = encode(data("12")); + EXPECT_EQ("MTI=", encoded); + encoded = encode(data("123")); + EXPECT_EQ("MTIz", encoded); + encoded = encode(data("1234")); + EXPECT_EQ("MTIzNA==", encoded); + encoded = encode(data("")); + EXPECT_EQ("", encoded); + encoded = encode(data("Lorem ipsum dolor sit amet, consectetur adipiscing elit")); + EXPECT_EQ("TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdA==", encoded); + encoded = encode(parse_hex("11ff8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d291b")); + EXPECT_EQ("Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb", encoded); +} + +TEST(Base64, decode) { + auto decoded = decode("SGVsbG8sIHdvcmxkIQ=="); + EXPECT_EQ(hex(data("Hello, world!")), hex(decoded)); + decoded = decode("MQ=="); + EXPECT_EQ(hex(data("1")), hex(decoded)); + decoded = decode("MTI="); + EXPECT_EQ(hex(data("12")), hex(decoded)); + decoded = decode("MTIz"); + EXPECT_EQ(hex(data("123")), hex(decoded)); + decoded = decode(""); + EXPECT_EQ("", hex(decoded)); + decoded = decode("Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb"); + EXPECT_EQ("11ff8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d291b", hex(decoded)); +} + + + +TEST(Base64, EncodeDecodeSui) { + auto v = "AAIAAAAAAAAAAAAAAAAAAAAAAAAAAgEAAAAAAAAAINaXMihjlCd4CQVFRPjcNb7QfYP4wGgQyl1xbplvEKUCA3N1aQh0cmFuc2ZlcgACAQCDlY9/fBVEt0yclyDF8RrjSRBfRRsAAAAAAAAAIJttZrU/26Bim7ku4dwY8d3fdabngn0B6dY/hLKgb6+xABQv0f6HrJCZ/1cuDVuxh1BL12XMeC21AKyRnN3jUaw243EdgyxtuXZpG62iKzFvYdk6RMGXxnoWd8RcfwkUAQAAAAAAACDi9GYNIZ0FXpPPi+zdDUuzHfs6MDoxzPuXGPZJq8ZfOAEAAAAAAAAA0AcAAAAAAAA="; + auto decoded = decode(v); + auto encoded = encode(decoded); + ASSERT_EQ(encoded, v); +} + +TEST(Base64, UrlFormat) { + const std::string const1 = "11003faa8556289975ec991ac9994dfb613abec4ea000d5094e6379080f594e559b330b8"; + + // Encoded string has both special characters + auto encoded = encode(parse_hex(const1)); + EXPECT_EQ("EQA/qoVWKJl17JkayZlN+2E6vsTqAA1QlOY3kID1lOVZszC4", encoded); + encoded = encodeBase64Url(parse_hex(const1)); + EXPECT_EQ("EQA_qoVWKJl17JkayZlN-2E6vsTqAA1QlOY3kID1lOVZszC4", encoded); + + auto decoded = decode("EQA/qoVWKJl17JkayZlN+2E6vsTqAA1QlOY3kID1lOVZszC4"); + EXPECT_EQ(const1, hex(decoded)); + decoded = decodeBase64Url("EQA_qoVWKJl17JkayZlN-2E6vsTqAA1QlOY3kID1lOVZszC4"); + EXPECT_EQ(const1, hex(decoded)); +} + +TEST(Base64, isBase64) { + EXPECT_TRUE(isBase64orBase64Url("Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb")); + EXPECT_TRUE(isBase64orBase64Url("Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSk=")); + EXPECT_TRUE(isBase64orBase64Url("Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODS==")); + EXPECT_TRUE(isBase64orBase64Url("EQA_qoVWKJl17JkayZlN-2E6vsTqAA1QlOY3kID1lOVZszC4")); + EXPECT_FALSE(isBase64orBase64Url("Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcOD===")); + EXPECT_FALSE(isBase64orBase64Url("Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb=")); + EXPECT_FALSE(isBase64orBase64Url("Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSk")); + EXPECT_FALSE(isBase64orBase64Url("MwCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsors=#")); +} + +} // namespace TW::Base64::tests diff --git a/tests/BaseEncoding.cpp b/tests/common/BaseEncoding.cpp similarity index 79% rename from tests/BaseEncoding.cpp rename to tests/common/BaseEncoding.cpp index 65763eb404f..69d655c94f9 100644 --- a/tests/BaseEncoding.cpp +++ b/tests/common/BaseEncoding.cpp @@ -1,26 +1,21 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Base32.h" #include "HexCoding.h" #include -using namespace TW; -using namespace TW::Base32; +namespace TW::Base32::tests { -void TestBase32Encode(const char* decoded_hex, const char* expected_encoded_in, const char* alphabet_in = nullptr) -{ +void TestBase32Encode(const char* decoded_hex, const char* expected_encoded_in, const char* alphabet_in = nullptr) { auto decoded = parse_hex(std::string(decoded_hex)); auto encoded = encode(decoded, alphabet_in); ASSERT_EQ(std::string(expected_encoded_in), encoded); } -void TestBase32Decode(const char* encoded_in, const char* expected_decoded_hex, const char* alphabet_in = nullptr) -{ +void TestBase32Decode(const char* encoded_in, const char* expected_decoded_hex, const char* alphabet_in = nullptr) { Data decoded; bool res = decode(std::string(encoded_in), decoded, alphabet_in); ASSERT_TRUE(res); @@ -33,7 +28,7 @@ TEST(Base32, Encode) { TestBase32Encode("010203", "AEBAG"); TestBase32Encode("", ""); TestBase32Encode( - "48450c2745890def7da06fc2551f912a14f9fc581c12db6e4d6f73f2fd0b2ad50df3d396", + "48450c2745890def7da06fc2551f912a14f9fc581c12db6e4d6f73f2fd0b2ad50df3d396", "JBCQYJ2FREG667NAN7BFKH4RFIKPT7CYDQJNW3SNN5Z7F7ILFLKQ346TSY"); TestBase32Encode( "3dd160d60673bd9b13adc25dad5d988d0d9f4ccdbe95a2122f9ef28b3ce4e89693074620", @@ -49,7 +44,7 @@ TEST(Base32, Decode) { TestBase32Decode("", ""); TestBase32Decode( "JBCQYJ2FREG667NAN7BFKH4RFIKPT7CYDQJNW3SNN5Z7F7ILFLKQ346TSY", - "48450c2745890def7da06fc2551f912a14f9fc581c12db6e4d6f73f2fd0b2ad50df3d396"); + "48450c2745890def7da06fc2551f912a14f9fc581c12db6e4d6f73f2fd0b2ad50df3d396"); TestBase32Decode( "HXIWBVQGOO6ZWE5NYJO22XMYRUGZ6TGNX2K2EERPT3ZIWPHE5CLJGB2GEA", "3dd160d60673bd9b13adc25dad5d988d0d9f4ccdbe95a2122f9ef28b3ce4e89693074620"); @@ -66,7 +61,9 @@ TEST(Base32, EncodeNimiq) { TEST(Base32, DecodeInvalid) { Data decoded; - ASSERT_FALSE(decode("+-", decoded)); // invalid characters - ASSERT_FALSE(decode("A", decoded)); // invalid odd length + ASSERT_FALSE(decode("+-", decoded)); // invalid characters + ASSERT_FALSE(decode("A", decoded)); // invalid odd length ASSERT_FALSE(decode("ABC", decoded)); // invalid odd length } + +} // namespace TW::Base32::tests diff --git a/tests/Bech32AddressTests.cpp b/tests/common/Bech32AddressTests.cpp similarity index 90% rename from tests/Bech32AddressTests.cpp rename to tests/common/Bech32AddressTests.cpp index 7152fd9ce78..11d8ec18b81 100644 --- a/tests/Bech32AddressTests.cpp +++ b/tests/common/Bech32AddressTests.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Bech32Address.h" #include "HexCoding.h" @@ -103,14 +101,14 @@ TEST(Bech32Address, FromPublicKey) { { auto privateKey = PrivateKey(parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832")); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - ASSERT_EQ(hex(publicKey.bytes.begin(), publicKey.bytes.end()), "026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502"); + ASSERT_EQ(hex(publicKey.bytes), "026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502"); auto address = Bech32Address("bnb", Hash::HasherSha256ripemd, publicKey); ASSERT_EQ("bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", address.string()); } { auto privateKey = PrivateKey(parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005")); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - ASSERT_EQ(hex(publicKey.bytes.begin(), publicKey.bytes.end()), "0257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc5"); + ASSERT_EQ(hex(publicKey.bytes), "0257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc5"); auto address = Bech32Address("cosmos", Hash::HasherSha256ripemd, publicKey); ASSERT_EQ(address.string(), "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); } @@ -129,7 +127,7 @@ TEST(Bech32Address, FromPublicKey) { { const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - ASSERT_EQ(hex(publicKey.bytes.begin(), publicKey.bytes.end()), "02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d"); + ASSERT_EQ(hex(publicKey.bytes), "02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d"); const auto address = Bech32Address("zil", Hash::HasherSha256, publicKey); ASSERT_EQ("zil", address.getHrp()); ASSERT_EQ("zil1j8xae6lggm8y63m3y2r7aefu797ze7mhzulnqg", address.string()); @@ -141,7 +139,7 @@ TEST(Bech32Address, Hashes) { const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); auto publicKey1 = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - ASSERT_EQ("02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d", hex(publicKey1.bytes.begin(), publicKey1.bytes.end())); + ASSERT_EQ("02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d", hex(publicKey1.bytes)); const auto address1 = Bech32Address("hrp", Hash::HasherSha256ripemd, publicKey1); ASSERT_EQ("hrp186zwn9h0z9fyvwfqs4jl92cw3kexusm4xw6ptp", address1.string()); @@ -152,7 +150,7 @@ TEST(Bech32Address, Hashes) { auto publicKey2 = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); ASSERT_EQ( "04b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d83c307736082c09f1f22328e0fbeab40ddd198cf0f70fcdaa1e5969ca400c098", - hex(publicKey2.bytes.begin(), publicKey2.bytes.end())); + hex(publicKey2.bytes)); const auto address3 = Bech32Address("hrp", Hash::HasherKeccak256, publicKey2); ASSERT_EQ("hrp17hff3s97m5uxpjcdq3nzqxxatt8cmumnsf03su", address3.string()); @@ -163,7 +161,7 @@ TEST(Bech32Address, Prefixes) { const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - ASSERT_EQ("02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d", hex(publicKey.bytes.begin(), publicKey.bytes.end())); + ASSERT_EQ("02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d", hex(publicKey.bytes)); const auto address1 = Bech32Address("hrpone", Hash::HasherSha256ripemd, publicKey); ASSERT_EQ("hrpone186zwn9h0z9fyvwfqs4jl92cw3kexusm47das6p", address1.string()); diff --git a/tests/Bech32Tests.cpp b/tests/common/Bech32Tests.cpp similarity index 95% rename from tests/Bech32Tests.cpp rename to tests/common/Bech32Tests.cpp index adb569ec333..c081fd9cf6c 100644 --- a/tests/Bech32Tests.cpp +++ b/tests/common/Bech32Tests.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Bech32.h" #include "HexCoding.h" @@ -82,7 +80,7 @@ TEST(Bech32, decode) { auto res = Bech32::decode(td.encoded); if (!td.isValid && !td.isValidM) { EXPECT_EQ(std::get<0>(res), ""); - EXPECT_EQ(std::get<1>(res).size(), 0); + EXPECT_EQ(std::get<1>(res).size(), 0ul); } else { if (td.isValid) { EXPECT_EQ(std::get<2>(res), Bech32::ChecksumVariant::Bech32); diff --git a/tests/common/BinaryCodingTests.cpp b/tests/common/BinaryCodingTests.cpp new file mode 100644 index 00000000000..5ce20884b9a --- /dev/null +++ b/tests/common/BinaryCodingTests.cpp @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "BinaryCoding.h" +#include "HexCoding.h" + +#include + +#include + +using namespace std; +using namespace TW; + +TEST(BinaryCodingTests, varIntSize) { + vector> tests = { + {0, 1}, + {1, 1}, + {10, 1}, + {100, 1}, + {0xfb, 1}, + {0xfc, 1}, + {0xfd, 3}, + {0xfe, 3}, + {0xff, 3}, + {0x100, 3}, + {0x200, 3}, + {0x1000, 3}, + {0xffff, 3}, + {0x10000, 5}, + {0x20000, 5}, + {0xffffffff, 5}, + {0x100000000, 9}, + {0x200000000, 9}, + {0x1000000000, 9}, + {0x10000000000, 9}, + {0x100000000000, 9}, + {0x1000000000000, 9}, + {0x10000000000000, 9}, + {0x100000000000000, 9}, + {0xffffffffffffffff, 9}, + }; + for (auto& test : tests) { + EXPECT_EQ(varIntSize(get<0>(test)), get<1>(test)); + } +} + +TEST(BinaryCodingTests, encodeAndDecodeVarInt) { + vector> tests = { + {0, "00"}, + {1, "01"}, + {10, "0a"}, + {100, "64"}, + {0xfb, "fb"}, + {0xfc, "fc"}, + {0xfd, "fdfd00"}, + {0xfe, "fdfe00"}, + {0xff, "fdff00"}, + {0x100, "fd0001"}, + {0x200, "fd0002"}, + {0x1000, "fd0010"}, + {0xffff, "fdffff"}, + {0x10000, "fe00000100"}, + {0x20000, "fe00000200"}, + {0xffffffff, "feffffffff"}, + {0x100000000, "ff0000000001000000"}, + {0x200000000, "ff0000000002000000"}, + {0x1000000000, "ff0000000010000000"}, + {0x10000000000, "ff0000000000010000"}, + {0x100000000000, "ff0000000000100000"}, + {0x1000000000000, "ff0000000000000100"}, + {0x10000000000000, "ff0000000000001000"}, + {0x100000000000000, "ff0000000000000001"}, + {0xffffffffffffffff, "ffffffffffffffffff"}, + }; + for (auto& test : tests) { + const auto input = get<0>(test); + Data encoded; + uint8_t resultEnc = encodeVarInt(input, encoded); + EXPECT_EQ(hex(encoded), get<1>(test)); + EXPECT_EQ(resultEnc, varIntSize(input)); + // decode back + size_t index = 0; + const auto resultDec = decodeVarInt(encoded, index); + EXPECT_EQ(get<0>(resultDec), true); + EXPECT_EQ(get<1>(resultDec), input); + } +} + +TEST(BinaryCodingTests, decodeVarIntTooShort) { + { + Data encoded = parse_hex("fe000000"); // one byte missing + size_t index = 0; + const auto result = decodeVarInt(encoded, index); + EXPECT_EQ(get<0>(result), false); + } + { + Data encoded = parse_hex("fe00000000"); + size_t index = 0; + const auto result = decodeVarInt(encoded, index); + EXPECT_EQ(get<0>(result), true); + } +} + +TEST(BinaryCodingTests, encodeAndDecodeString) { + vector> tests = { + {"", "00"}, + {"A", "0141"}, + {"AB", "024142"}, + {"abcdefghij", "0a6162636465666768696a"}, + {"abcdefghIj", "0a6162636465666768496a"}, + {"12345678901234567890123456789012345678901234567890", "323132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930"}, + { + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" + , "fd2c01" + "3132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930" + "3132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930" + "3132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930" + "3132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930" + "3132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930" + "3132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930" + }, + }; + for (auto& test : tests) { + const auto input = get<0>(test); + Data encoded; + encodeString(input, encoded); + EXPECT_EQ(hex(encoded), get<1>(test)); + // decode back + size_t index = 0; + const auto resultDec = decodeString(encoded, index); + EXPECT_EQ(get<0>(resultDec), true); + EXPECT_EQ(get<1>(resultDec), get<0>(test)); + } +} + +TEST(BinaryCodingTests, decodeStringTooShort) { + { + Data encoded = parse_hex("0a616263646566676849"); // one byte missing + size_t index = 0; + const auto result = decodeString(encoded, index); + EXPECT_EQ(get<0>(result), false); + } + { + Data encoded = parse_hex("0a6162636465666768496a"); + size_t index = 0; + const auto result = decodeString(encoded, index); + EXPECT_EQ(get<0>(result), true); + } +} diff --git a/tests/common/CborTests.cpp b/tests/common/CborTests.cpp new file mode 100644 index 00000000000..a941565dd39 --- /dev/null +++ b/tests/common/CborTests.cpp @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Cbor.h" + +#include "HexCoding.h" + +#include + +namespace TW::Cbor::tests { + +using namespace std; + +// clang-format off + +TEST(Cbor, EncSample1) { + EXPECT_EQ( + "8205a26178186461793831", + hex(Encode::array({ + Encode::uint(5), + Encode::map({ + make_pair(Encode::string("x"), Encode::uint(100)), + make_pair(Encode::string("y"), Encode::negInt(50)), + }), + }) + .encoded()) + ); +} + +TEST(Cbor, EncUInt) { + EXPECT_EQ("00", hex(Encode::uint(0).encoded())); + EXPECT_EQ("01", hex(Encode::uint(1).encoded())); + EXPECT_EQ("0a", hex(Encode::uint(10).encoded())); + EXPECT_EQ("17", hex(Encode::uint(23).encoded())); + EXPECT_EQ("1818", hex(Encode::uint(24).encoded())); + EXPECT_EQ("1819", hex(Encode::uint(25).encoded())); + EXPECT_EQ("181a", hex(Encode::uint(26).encoded())); + EXPECT_EQ("181b", hex(Encode::uint(27).encoded())); + EXPECT_EQ("181c", hex(Encode::uint(28).encoded())); + EXPECT_EQ("181d", hex(Encode::uint(29).encoded())); + EXPECT_EQ("181e", hex(Encode::uint(30).encoded())); + EXPECT_EQ("181f", hex(Encode::uint(31).encoded())); + EXPECT_EQ("1820", hex(Encode::uint(32).encoded())); + EXPECT_EQ("183f", hex(Encode::uint(0x3f).encoded())); + EXPECT_EQ("1840", hex(Encode::uint(0x40).encoded())); + EXPECT_EQ("1864", hex(Encode::uint(100).encoded())); + EXPECT_EQ("187f", hex(Encode::uint(0x7f).encoded())); + EXPECT_EQ("1880", hex(Encode::uint(0x80).encoded())); + EXPECT_EQ("18ff", hex(Encode::uint(0xff).encoded())); + EXPECT_EQ("190100", hex(Encode::uint(0x0100).encoded())); + EXPECT_EQ("1903e8", hex(Encode::uint(1000).encoded())); + EXPECT_EQ("198765", hex(Encode::uint(0x8765).encoded())); + EXPECT_EQ("19ffff", hex(Encode::uint(0xffff).encoded())); + EXPECT_EQ("1a00010000", hex(Encode::uint(0x00010000).encoded())); + EXPECT_EQ("1a000f4240", hex(Encode::uint(1000000).encoded())); + EXPECT_EQ("1a00800000", hex(Encode::uint(0x00800000).encoded())); + EXPECT_EQ("1a87654321", hex(Encode::uint(0x87654321).encoded())); + EXPECT_EQ("1affffffff", hex(Encode::uint(0xffffffff).encoded())); + EXPECT_EQ("1b0000000100000000", hex(Encode::uint(0x0000000100000000).encoded())); + EXPECT_EQ("1b000000e8d4a51000", hex(Encode::uint(1000000000000).encoded())); + EXPECT_EQ("1b876543210fedcba9", hex(Encode::uint(0x876543210fedcba9).encoded())); + EXPECT_EQ("1bffffffffffffffff", hex(Encode::uint(0xffffffffffffffff).encoded())); +} + +TEST(Cbor, EncNegInt) { + EXPECT_EQ("20", hex(Encode::negInt(1).encoded())); // -1 + EXPECT_EQ("00", hex(Encode::negInt(0).encoded())); // 0 + EXPECT_EQ("21", hex(Encode::negInt(2).encoded())); + EXPECT_EQ("28", hex(Encode::negInt(9).encoded())); + EXPECT_EQ("37", hex(Encode::negInt(24).encoded())); + EXPECT_EQ("3818", hex(Encode::negInt(25).encoded())); + EXPECT_EQ("38ff", hex(Encode::negInt(0x0100).encoded())); + EXPECT_EQ("390100", hex(Encode::negInt(0x0101).encoded())); + EXPECT_EQ("39ffff", hex(Encode::negInt(0x10000).encoded())); + EXPECT_EQ("3a00010000", hex(Encode::negInt(0x00010001).encoded())); + EXPECT_EQ("3a00800000", hex(Encode::negInt(0x00800001).encoded())); + EXPECT_EQ("3a87654321", hex(Encode::negInt(0x87654322).encoded())); + EXPECT_EQ("3affffffff", hex(Encode::negInt(0x100000000).encoded())); + EXPECT_EQ("3b0000000100000000", hex(Encode::negInt(0x0000000100000001).encoded())); + EXPECT_EQ("3b876543210fedcba9", hex(Encode::negInt(0x876543210fedcbaa).encoded())); + EXPECT_EQ("3bfffffffffffffffe", hex(Encode::negInt(0xffffffffffffffff).encoded())); + + EXPECT_EQ("-9", Decode(Encode::negInt(9).encoded()).dumpToString()); +} + +TEST(Cbor, EncString) { + EXPECT_EQ("60", hex(Encode::string("").encoded())); + EXPECT_EQ("6141", hex(Encode::string("A").encoded())); + EXPECT_EQ("656162636465", hex(Encode::string("abcde").encoded())); + Data long258(258); + EXPECT_EQ( + "590102000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + hex(Encode::bytes(long258).encoded())); + + EXPECT_EQ("\"abcde\"", Decode(Encode::string("abcde").encoded()).dumpToString()); + EXPECT_EQ("h\"6162636465\"", Decode(Encode::bytes(parse_hex("6162636465")).encoded()).dumpToString()); +} + +TEST(Cbor, EncTag) { + { + Data cbor = Encode::tag(5, Encode::uint(6)).encoded(); + EXPECT_EQ("c506", hex(cbor)); + EXPECT_TRUE(Decode(cbor).isValid()); + EXPECT_EQ("tag 5 6", Decode(cbor).dumpToString()); + } + EXPECT_EQ("d94321191234", hex(Encode::tag(0x4321, Encode::uint(0x1234)).encoded())); +} + +TEST(Cbor, EncNull) { + { + Data cbor = Encode::null().encoded(); + EXPECT_EQ("f6", hex(cbor)); + EXPECT_TRUE(Decode(cbor).isValid()); + EXPECT_EQ("null", Decode(cbor).dumpToString()); + } +} + +TEST(Cbor, EncInvalid) { + Data invalid = parse_hex("5b99999999999999991234"); // invalid very looong string + EXPECT_FALSE(Decode(invalid).isValid()); + + try { + Encode::fromRaw(invalid); + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} + +TEST(Cbor, DecInt) { + EXPECT_EQ(0ul, Decode(parse_hex("00")).getValue()); + EXPECT_EQ(1ul, Decode(parse_hex("01")).getValue()); + EXPECT_EQ(10ul, Decode(parse_hex("0a")).getValue()); + EXPECT_EQ(23ul, Decode(parse_hex("17")).getValue()); + EXPECT_EQ(24ul, Decode(parse_hex("1818")).getValue()); + EXPECT_EQ(25ul, Decode(parse_hex("1819")).getValue()); + EXPECT_EQ(26ul, Decode(parse_hex("181a")).getValue()); + EXPECT_EQ(27ul, Decode(parse_hex("181b")).getValue()); + EXPECT_EQ(28ul, Decode(parse_hex("181c")).getValue()); + EXPECT_EQ(29ul, Decode(parse_hex("181d")).getValue()); + EXPECT_EQ(30ul, Decode(parse_hex("181e")).getValue()); + EXPECT_EQ(31ul, Decode(parse_hex("181f")).getValue()); + EXPECT_EQ(32ul, Decode(parse_hex("1820")).getValue()); + EXPECT_EQ(0x3ful, Decode(parse_hex("183f")).getValue()); + EXPECT_EQ(0x40ul, Decode(parse_hex("1840")).getValue()); + EXPECT_EQ(100ul, Decode(parse_hex("1864")).getValue()); + EXPECT_EQ(0x7ful, Decode(parse_hex("187f")).getValue()); + EXPECT_EQ(0x80ul, Decode(parse_hex("1880")).getValue()); + EXPECT_EQ(0xfful, Decode(parse_hex("18ff")).getValue()); + EXPECT_EQ(0x100ul, Decode(parse_hex("190100")).getValue()); + EXPECT_EQ(1000ul, Decode(parse_hex("1903e8")).getValue()); + EXPECT_EQ(0x8765ul, Decode(parse_hex("198765")).getValue()); + EXPECT_EQ(0xfffful, Decode(parse_hex("19ffff")).getValue()); + EXPECT_EQ(0x00010000ul, Decode(parse_hex("1a00010000")).getValue()); + EXPECT_EQ(1000000ul, Decode(parse_hex("1a000f4240")).getValue()); + EXPECT_EQ(0x00800000ul, Decode(parse_hex("1a00800000")).getValue()); + EXPECT_EQ(0x87654321, Decode(parse_hex("1a87654321")).getValue()); + EXPECT_EQ(0xffffffff, Decode(parse_hex("1affffffff")).getValue()); + EXPECT_EQ(0x0000000100000000ul, Decode(parse_hex("1b0000000100000000")).getValue()); + EXPECT_EQ(1000000000000ul, Decode(parse_hex("1b000000e8d4a51000")).getValue()); + EXPECT_EQ(0x876543210fedcba9, Decode(parse_hex("1b876543210fedcba9")).getValue()); + EXPECT_EQ(0xffffffffffffffff, Decode(parse_hex("1bffffffffffffffff")).getValue()); +} + +TEST(Cbor, DecMinortypeInvalid) { + EXPECT_FALSE(Decode(parse_hex("1c")).isValid()); // 28 unused + EXPECT_FALSE(Decode(parse_hex("1d")).isValid()); // 29 unused + EXPECT_FALSE(Decode(parse_hex("1e")).isValid()); // 30 unused + EXPECT_TRUE(Decode(parse_hex("1b0000000000000000")).isValid()); +} + +TEST(Cbor, DecArray3) { + Decode cbor = Decode(parse_hex("83010203")); + EXPECT_EQ(3ul, cbor.getArrayElements().size()); +} + +TEST(Cbor, DecArrayNested) { + Data d1 = parse_hex("8301820203820405"); + Decode cbor = Decode(d1); + EXPECT_EQ(3ul, cbor.getArrayElements().size()); + + EXPECT_EQ(1ul, cbor.getArrayElements()[0].getValue()); + EXPECT_EQ(2ul, cbor.getArrayElements()[1].getArrayElements().size()); + EXPECT_EQ(2ul, cbor.getArrayElements()[1].getArrayElements()[0].getValue()); + EXPECT_EQ(3ul, cbor.getArrayElements()[1].getArrayElements()[1].getValue()); + EXPECT_EQ(2ul, cbor.getArrayElements()[2].getArrayElements().size()); + EXPECT_EQ(4ul, cbor.getArrayElements()[2].getArrayElements()[0].getValue()); + EXPECT_EQ(5ul, cbor.getArrayElements()[2].getArrayElements()[1].getValue()); +} + +TEST(Cbor, DecEncoded) { + // sometimes getting the encoded version is useful during decoding too + Decode cbor = Decode(parse_hex("8301820203820405")); + EXPECT_EQ(3ul, cbor.getArrayElements().size()); + EXPECT_EQ("820203", hex(cbor.getArrayElements()[1].encoded())); + EXPECT_EQ("820405", hex(cbor.getArrayElements()[2].encoded())); +} + +TEST(Cbor, DecMemoryref) { + // make sure reference to data is valid even if parent object has been destroyed + Decode* cbor = new Decode(parse_hex("828301020383010203")); + auto elems = cbor->getArrayElements(); + // delete parent + delete cbor; + // also do some new allocation + Decode* dummy = new Decode(parse_hex("5555555555555555")); + // work with the child references + EXPECT_EQ(2ul, elems.size()); + EXPECT_EQ(3ul, elems[0].getArrayElements().size()); + EXPECT_EQ(3ul, elems[1].getArrayElements().size()); + delete dummy; +} + +TEST(Cbor, GetValue) { + EXPECT_EQ(5ul, Decode(parse_hex("05")).getValue()); +} + +TEST(Cbor, GetValueInvalid) { + try { + Decode(parse_hex("83010203")).getValue(); // array + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} + +TEST(Cbor, GetString) { + // bytes/string and getString/getBytes work in all combinations + EXPECT_EQ("abcde", Decode(parse_hex("656162636465")).getString()); + EXPECT_EQ("abcde", Decode(parse_hex("456162636465")).getString()); + EXPECT_EQ("6162636465", hex(Decode(parse_hex("656162636465")).getBytes())); + EXPECT_EQ("6162636465", hex(Decode(parse_hex("456162636465")).getBytes())); +} + +TEST(Cbor, GetStringInvalidType) { + try { + Decode cbor = Decode(Encode::uint(5).encoded()); + cbor.getBytes(); + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} + +TEST(Cbor, GetStringInvalidTooShort) { + try { + Decode cbor = Decode(parse_hex("65616263")); // too short + cbor.getBytes(); + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} + +TEST(Cbor, ArrayEmpty) { + Data cbor = Encode::array({}).encoded(); + + EXPECT_EQ("80", hex(cbor)); + EXPECT_TRUE(Decode(cbor).isValid()); + EXPECT_EQ("[]", Decode(cbor).dumpToString()); + + Decode decode(cbor); + EXPECT_EQ(0ul, decode.getArrayElements().size()); +} + +TEST(Cbor, Array3) { + Data cbor = Encode::array({ + Encode::uint(1), + Encode::uint(2), + Encode::uint(3), + }).encoded(); + + EXPECT_EQ("83010203", hex(cbor)); + EXPECT_TRUE(Decode(cbor).isValid()); + EXPECT_EQ("[1, 2, 3]", Decode(cbor).dumpToString()); + + Decode decode(cbor); + EXPECT_EQ(3ul, decode.getArrayElements().size()); + EXPECT_EQ(1ul, decode.getArrayElements()[0].getValue()); + EXPECT_EQ(2ul, decode.getArrayElements()[1].getValue()); + EXPECT_EQ(3ul, decode.getArrayElements()[2].getValue()); +} + +TEST(Cbor, ArrayNested) { + Data cbor = Encode::array({ + Encode::uint(1), + Encode::array({ + Encode::uint(2), + Encode::uint(3), + }), + Encode::array({ + Encode::uint(4), + Encode::uint(5), + }), + }).encoded(); + + EXPECT_EQ("8301820203820405", hex(cbor)); + EXPECT_TRUE(Decode(cbor).isValid()); + EXPECT_EQ("[1, [2, 3], [4, 5]]", Decode(cbor).dumpToString()); + + Decode decode(cbor); + EXPECT_EQ(3ul, decode.getArrayElements().size()); + EXPECT_EQ(1ul, decode.getArrayElements()[0].getValue()); + EXPECT_EQ(2ul, decode.getArrayElements()[1].getArrayElements().size()); + EXPECT_EQ(2ul, decode.getArrayElements()[1].getArrayElements()[0].getValue()); + EXPECT_EQ(3ul, decode.getArrayElements()[1].getArrayElements()[1].getValue()); + EXPECT_EQ(2ul, decode.getArrayElements()[2].getArrayElements().size()); + EXPECT_EQ(4ul, decode.getArrayElements()[2].getArrayElements()[0].getValue()); + EXPECT_EQ(5ul, decode.getArrayElements()[2].getArrayElements()[1].getValue()); +} + +TEST(Cbor, Array25) { + auto elem = vector(); + for (int i = 1; i <= 25; ++i) { + elem.push_back(Encode::uint(i)); + } + Data cbor = Encode::array(elem).encoded(); + + EXPECT_EQ("98190102030405060708090a0b0c0d0e0f101112131415161718181819", hex(cbor)); + EXPECT_TRUE(Decode(cbor).isValid()); + EXPECT_EQ("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]", + Decode(cbor).dumpToString()); + + Decode decode(cbor); + EXPECT_EQ(25ul, decode.getArrayElements().size()); + for (auto i = 1ul; i <= 25; ++i) { + EXPECT_EQ(i, decode.getArrayElements()[i - 1].getValue()); + } +} + +TEST(Cbor, MapEmpty) { + Data cbor = Encode::map({}).encoded(); + + EXPECT_EQ("a0", hex(cbor)); + EXPECT_TRUE(Decode(cbor).isValid()); + EXPECT_EQ("{}", Decode(cbor).dumpToString()); + + Decode decode(cbor); + EXPECT_EQ(0ul, decode.getMapElements().size()); +} + +TEST(Cbor, Map2Num) { + Data cbor = Encode::map({ + make_pair(Encode::uint(1), Encode::uint(2)), + make_pair(Encode::uint(3), Encode::uint(4)), + }).encoded(); + + EXPECT_EQ("a201020304", hex(cbor)); + EXPECT_TRUE(Decode(cbor).isValid()); + EXPECT_EQ("{1: 2, 3: 4}", Decode(cbor).dumpToString()); + + Decode decode(cbor); + EXPECT_EQ(2ul, decode.getMapElements().size()); + EXPECT_EQ(1ul, decode.getMapElements()[0].first.getValue()); + EXPECT_EQ(2ul, decode.getMapElements()[0].second.getValue()); +} + +TEST(Cbor, Map2WithArr) { + Data cbor = Encode::map({ + make_pair(Encode::string("a"), Encode::uint(1)), + make_pair(Encode::string("b"), Encode::array({ + Encode::uint(2), + Encode::uint(3), + })), + }).encoded(); + + EXPECT_EQ("a26161016162820203", hex(cbor)); + EXPECT_TRUE(Decode(cbor).isValid()); + EXPECT_EQ("{\"a\": 1, \"b\": [2, 3]}", + Decode(cbor).dumpToString()); + + Decode decode(cbor); + EXPECT_EQ(2ul, decode.getMapElements().size()); + EXPECT_EQ("a", decode.getMapElements()[0].first.getString()); + EXPECT_EQ(1ul, decode.getMapElements()[0].second.getValue()); + EXPECT_EQ("b", decode.getMapElements()[1].first.getString()); + EXPECT_EQ(2ul, decode.getMapElements()[1].second.getArrayElements().size()); + EXPECT_EQ(2ul, decode.getMapElements()[1].second.getArrayElements()[0].getValue()); + EXPECT_EQ(3ul, decode.getMapElements()[1].second.getArrayElements()[1].getValue()); +} + +TEST(Cbor, MapNested) { + Data cbor = Encode::map({ + make_pair(Encode::string("a"), Encode::map({ + make_pair(Encode::string("b"), Encode::string("c")), + })), + }).encoded(); + + EXPECT_EQ("a16161a161626163", hex(cbor)); + EXPECT_TRUE(Decode(cbor).isValid()); + EXPECT_EQ("{\"a\": {\"b\": \"c\"}}", + Decode(cbor).dumpToString()); + + Decode decode(cbor); + EXPECT_EQ(1ul, decode.getMapElements().size()); + EXPECT_EQ("a", decode.getMapElements()[0].first.getString()); + EXPECT_EQ(1ul, decode.getMapElements()[0].second.getMapElements().size()); + EXPECT_EQ("b", decode.getMapElements()[0].second.getMapElements()[0].first.getString()); + EXPECT_EQ("c", decode.getMapElements()[0].second.getMapElements()[0].second.getString()); +} + +TEST(Cbor, MapIndef) { + Decode cbor = Decode(parse_hex("bf01020304ff")); + EXPECT_EQ("{_ 1: 2, 3: 4}", cbor.dumpToString()); + EXPECT_EQ(2ul, cbor.getMapElements().size()); + EXPECT_EQ(1ul, cbor.getMapElements()[0].first.getValue()); + EXPECT_EQ(2ul, cbor.getMapElements()[0].second.getValue()); +} + +TEST(Cbor, MapIsValidInvalidTooShort) { + { + Decode cbor = Decode(parse_hex("a301020304")); // too short + EXPECT_FALSE(cbor.isValid()); + } + { + Decode cbor = Decode(parse_hex("a3010203")); // too short, partial element + EXPECT_FALSE(cbor.isValid()); + } +} + +TEST(Cbor, MapGetInvalidTooShort1) { + try { + Decode cbor = Decode(parse_hex("a301020304")); // too short + auto elems = cbor.getMapElements(); + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} + +TEST(Cbor, MapGetInvalidTooShort2) { + try { + Decode cbor = Decode(parse_hex("a3010203")); // too short, partial element + auto elems = cbor.getMapElements(); + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} + +TEST(Cbor, ArrayIndef) { + Data cbor = Encode::indefArray() + .addIndefArrayElem(Encode::uint(1)) + .addIndefArrayElem(Encode::uint(2)) + .closeIndefArray() + .encoded(); + + EXPECT_EQ("9f0102ff", hex(cbor)); + EXPECT_TRUE(Decode(cbor).isValid()); + EXPECT_EQ("[_ 1, 2]", + Decode(cbor).dumpToString()); + + Decode decode(cbor); + EXPECT_EQ(2ul, decode.getArrayElements().size()); + EXPECT_EQ(1ul, decode.getArrayElements()[0].getValue()); + EXPECT_EQ(2ul, decode.getArrayElements()[1].getValue()); + + EXPECT_EQ("[_ 1, 2]", Decode(parse_hex("9f0102ff")).dumpToString()); + EXPECT_EQ("", Decode(parse_hex("ff")).dumpToString()); + EXPECT_EQ("spec 1", Decode(parse_hex("e1")).dumpToString()); +} + +TEST(Cbor, ArrayInfefErrorAddNostart) { + try { + Data cbor = Encode::uint(0).addIndefArrayElem(Encode::uint(1)).encoded(); + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} + +TEST(Cbor, ArrayInfefErrorCloseNostart) { + try { + Data cbor = Encode::uint(0).closeIndefArray().encoded(); + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} + +TEST(Cbor, ArrayInfefErrorResultNoclose) { + try { + Data cbor = Encode::indefArray() + .addIndefArrayElem(Encode::uint(1)) + .addIndefArrayElem(Encode::uint(2)) + // close is missing, break command not written + .encoded(); + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} + +TEST(Cbor, ArrayInfefErrorNoBreak) { + EXPECT_TRUE(Decode(parse_hex("9f0102ff")).isValid()); + // without break it's invalid + EXPECT_FALSE(Decode(parse_hex("9f0102")).isValid()); +} + +TEST(Cbor, GetTagValueNotTag) { + try { + Decode cbor = Decode(Encode::string("abc").encoded()); + cbor.getTagValue(); + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} + +TEST(Cbor, GetTagElementNotTag) { + try { + Decode cbor = Decode(Encode::string("abc").encoded()); + Decode tagElement = cbor.getTagElement(); + } catch (exception& ex) { + return; + } + FAIL() << "Expected exception"; +} +// clang-format on +} // namespace TW::Cbor::tests diff --git a/tests/common/CoinAddressDerivationTests.cpp b/tests/common/CoinAddressDerivationTests.cpp new file mode 100644 index 00000000000..1273ea427eb --- /dev/null +++ b/tests/common/CoinAddressDerivationTests.cpp @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" + +#include + +#include +#include + +namespace TW { + +TEST(Coin, DeriveAddress) { + auto dummyKeyData = parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); + const auto privateKey = PrivateKey(dummyKeyData); + const auto privateKeyExt = PrivateKey(dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData); + + const auto coins = TW::getCoinTypes(); + for (auto& c : coins) { + std::string address; + switch (c) { + default: + address = TW::deriveAddress(c, privateKey); + break; + + case TWCoinTypeCardano: + case TWCoinTypeNEO: + address = TW::deriveAddress(c, privateKeyExt); + break; + } + + switch (c) { + // Ethereum and ... + case TWCoinTypeEthereum: + // ... clones: + case TWCoinTypeAcalaEVM: + case TWCoinTypeArbitrum: + case TWCoinTypeArbitrumNova: + case TWCoinTypeAurora: + case TWCoinTypeAvalancheCChain: + case TWCoinTypeBoba: + case TWCoinTypeCallisto: + case TWCoinTypeCelo: + case TWCoinTypeConfluxeSpace: + case TWCoinTypeCronosChain: + case TWCoinTypeECOChain: + case TWCoinTypeEthereumClassic: + case TWCoinTypeEvmos: + case TWCoinTypeFantom: + case TWCoinTypeGoChain: + case TWCoinTypeKavaEvm: + case TWCoinTypeKaia: + case TWCoinTypeKuCoinCommunityChain: + case TWCoinTypeMeter: + case TWCoinTypeMetis: + case TWCoinTypeMoonbeam: + case TWCoinTypeMoonriver: + case TWCoinTypeOptimism: + case TWCoinTypeZksync: + case TWCoinTypePolygonzkEVM: + case TWCoinTypeOKXChain: + case TWCoinTypePOANetwork: + case TWCoinTypePolygon: + case TWCoinTypeSmartBitcoinCash: + case TWCoinTypeSmartChain: + case TWCoinTypeSmartChainLegacy: + case TWCoinTypeTheta: + case TWCoinTypeThetaFuel: + case TWCoinTypeThunderCore: + case TWCoinTypeViction: + case TWCoinTypeVeChain: + case TWCoinTypeWanchain: + case TWCoinTypeXDai: + case TWCoinTypeIoTeXEVM: + case TWCoinTypeScroll: + case TWCoinTypeOpBNB: + case TWCoinTypeNeon: + case TWCoinTypeBase: + case TWCoinTypeLinea: + case TWCoinTypeGreenfield: + case TWCoinTypeMantle: + case TWCoinTypeZenEON: + case TWCoinTypeMantaPacific: + case TWCoinTypeZetaEVM: + case TWCoinTypeMerlin: + case TWCoinTypeLightlink: + case TWCoinTypeBlast: + case TWCoinTypeBounceBit: + case TWCoinTypeZkLinkNova: + // end_of_evm_address_derivation_tests_marker_do_not_modify + EXPECT_EQ(address, "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); + break; + + case TWCoinTypeKin: + case TWCoinTypeStellar: + EXPECT_EQ(address, "GDXJHJHWN6GRNOAZXON6XH74ZX6NYFAS5B7642RSJQVJTIPA4ZYUQLEB"); + break; + + case TWCoinTypeNEO: + case TWCoinTypeOntology: + EXPECT_EQ(address, "AeicEjZyiXKgUeSBbYQHxsU1X3V5Buori5"); + break; + + case TWCoinTypeTerra: + case TWCoinTypeTerraV2: + EXPECT_EQ(address, "terra1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0ll9rwp"); + break; + + case TWCoinTypeZcash: + case TWCoinTypeZelcash: + EXPECT_EQ(address, "t1b9xfAk3kZp5Qk3rinDPq7zzLkJGHTChDS"); + break; + + case TWCoinTypeKomodo: + EXPECT_EQ(address, "RSZYjMDCP4q3t7NAFXPPnqEGrMZn971pdB"); + break; + case TWCoinTypeAcala: + EXPECT_EQ(address, "26GQqmwt3154cQbG2fyBsh3cGuCBoRFtrwuCD6WcVJdFReA4"); + break; + case TWCoinTypeAeternity: + EXPECT_EQ(address, "ak_2p5878zbFhxnrm7meL7TmqwtvBaqcBddyp5eGzZbovZ5FeVfcw"); + break; + case TWCoinTypeAion: + EXPECT_EQ(address, "0xa0010b0ea04ba4d76ca6e5e9900bacf19bc4402eaec7e36ea7ddd8eed48f60f3"); + break; + case TWCoinTypeAlgorand: + EXPECT_EQ(address, "52J2J5TPRULLQGN3TPVZ77GN7TOBIEXIP7XGUMSMFKM2DYHGOFEOGBP2T4"); + break; + case TWCoinTypeBandChain: + EXPECT_EQ(address, "band1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0q5lp5f"); + break; + case TWCoinTypeBinance: + EXPECT_EQ(address, "bnb1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0mlq0d0"); + break; + case TWCoinTypeTBinance: + EXPECT_EQ(address, "tbnb1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z042ftd7"); + break; + case TWCoinTypeBitcoin: + EXPECT_EQ(address, "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv"); + break; + case TWCoinTypeBitcoinCash: + EXPECT_EQ(address, "bitcoincash:qz7eyzytkl5z6cg6nw20hd62pyyp22mcfuardfd2vn"); + break; + case TWCoinTypeBitcoinDiamond: + EXPECT_EQ(address, "1JHMeqKunF2Up6zxnMQGhJu5667BXz98YQ"); + break; + case TWCoinTypeBitcoinGold: + EXPECT_EQ(address, "btg1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0eg8day"); + break; + case TWCoinTypeBluzelle: + EXPECT_EQ(address, "bluzelle1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0vrup2s"); + break; + case TWCoinTypeCardano: + EXPECT_EQ(address, "addr1qxzk4wqhh5qmzas4e26aghcvkz8feju6sa43nghfj5xxsly9d2up00gpk9mptj44630sevywnn9e4pmtrx3wn9gvdp7qjhvjl4"); + break; + case TWCoinTypeCosmos: + EXPECT_EQ(address, "cosmos1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0emlrvp"); + break; + case TWCoinTypeCryptoOrg: + EXPECT_EQ(address, "cro1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0pqh6ss"); + break; + case TWCoinTypeDash: + EXPECT_EQ(address, "XsyCV5yojxF4y3bYeEiVYqarvRgsWFELZL"); + break; + case TWCoinTypeDecred: + EXPECT_EQ(address, "Dsp4u8xxTHSZU2ELWTQLQP77xJhgeWrTsGK"); + break; + case TWCoinTypeDigiByte: + EXPECT_EQ(address, "dgb1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0c69ssz"); + break; + case TWCoinTypeDogecoin: + EXPECT_EQ(address, "DNRTC6GZ5evmM7BZWwPqF54fyDqUqULMyu"); + break; + case TWCoinTypeECash: + EXPECT_EQ(address, "ecash:qz7eyzytkl5z6cg6nw20hd62pyyp22mcfuywezks2y"); + break; + case TWCoinTypeEOS: + case TWCoinTypeWAX: + EXPECT_EQ(address, "EOS5TrYnZP1RkDSUMzBY4GanCy6AP68kCMdkAb5EACkAwkdgRLShz"); + break; + case TWCoinTypeMultiversX: + EXPECT_EQ(address, "erd1a6f6fan035ttsxdmn04ellxdlnwpgyhg0lhx5vjv92v6rc8xw9yq83344f"); + break; + case TWCoinTypeEverscale: + EXPECT_EQ(address, "0:ef64d51f95ef17973b737277cfecbd2a8d551141be2f58f5fb362575fc3eb5b0"); + break; + case TWCoinTypeTON: + EXPECT_EQ(address, "UQAoYT8nMLfeNh6h0uIoK_wLm9JkvxiGxJDr6GRXJGu2Zked"); + break; + case TWCoinTypeFIO: + EXPECT_EQ(address, "FIO5TrYnZP1RkDSUMzBY4GanCy6AP68kCMdkAb5EACkAwkdgRLShz"); + break; + case TWCoinTypeFilecoin: + EXPECT_EQ(address, "f1qsx7qwiojh5duxbxhbqgnlyx5hmpcf7mcz5oxsy"); + break; + case TWCoinTypeFiro: + EXPECT_EQ(address, "aHzpPjmY132KseS4nkiQTbDahTEXqesY89"); + break; + case TWCoinTypeGroestlcoin: + EXPECT_EQ(address, "grs1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0jsaf3d"); + break; + case TWCoinTypeHarmony: + EXPECT_EQ(address, "one1nk9x9ajk4rgkzhqjjn7hr6w0k0jg2kj0nmx3dt"); + break; + case TWCoinTypeICON: + EXPECT_EQ(address, "hx4728fc65c31728f0d3538b8783b5394b31a136b9"); + break; + case TWCoinTypeIOST: + EXPECT_EQ(address, "H4JcMPicKkHcxxDjkyyrLoQj7Kcibd9t815ak4UvTr9M"); + break; + case TWCoinTypeIoTeX: + EXPECT_EQ(address, "io1nk9x9ajk4rgkzhqjjn7hr6w0k0jg2kj0zgdt6h"); + break; + case TWCoinTypeKava: + EXPECT_EQ(address, "kava1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z09wt76x"); + break; + case TWCoinTypeKusama: + EXPECT_EQ(address, "Hy8mqcexg5FMwMYnQvzrUvD723qMxDjMRU9HdNCnTsMAypY"); + break; + case TWCoinTypeLitecoin: + EXPECT_EQ(address, "ltc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0tamvsu"); + break; + case TWCoinTypeMonacoin: + EXPECT_EQ(address, "MRBWtGEKHGCHhmyJ1L4CwaWQZJzM5DnVcs"); + break; + case TWCoinTypeNEAR: + EXPECT_EQ(address, "ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148"); + break; + case TWCoinTypeNULS: + EXPECT_EQ(address, "NULSd6HgfXT3m5JBGxeCZXHRQbb82FKgZGT8o"); + break; + case TWCoinTypeNano: + EXPECT_EQ(address, "nano_1qepdf4k95dhb5gsmhmq3iddqsxiafwkihunm7irn48jdiwdtnn6pe93k3f6"); + break; + case TWCoinTypeNativeEvmos: + EXPECT_EQ(address, "evmos1nk9x9ajk4rgkzhqjjn7hr6w0k0jg2kj07me7uu"); + break; + case TWCoinTypeNebulas: + EXPECT_EQ(address, "n1XTciu9ZRYt3ni7SxNBmivk9Y6XpP6VrhT"); + break; + case TWCoinTypeNimiq: + EXPECT_EQ(address, "NQ74 D40G N3M0 9EJD ET56 UPLR 02VC X6DU 8G1E"); + break; + case TWCoinTypeOasis: + EXPECT_EQ(address, "oasis1qzw4h3wmyjtrttduqqrs8udggyy2emwdzqmuzwg4"); + break; + case TWCoinTypeOsmosis: + EXPECT_EQ(address, "osmo1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z03qvn6n"); + break; + case TWCoinTypePivx: + EXPECT_EQ(address, "DNRTC6GZ5evmM7BZWwPqF54fyDqUqULMyu"); + break; + case TWCoinTypePolkadot: + EXPECT_EQ(address, "16PpFrXrC6Ko3pYcyMAx6gPMp3mFFaxgyYMt4G5brkgNcSz8"); + break; + case TWCoinTypeQtum: + EXPECT_EQ(address, "QdtLm8ccxhuJFF5zCgikpaghbM3thdaGsW"); + break; + case TWCoinTypeRavencoin: + EXPECT_EQ(address, "RSZYjMDCP4q3t7NAFXPPnqEGrMZn971pdB"); + break; + case TWCoinTypeRonin: + EXPECT_EQ(address, "ronin:9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); + break; + case TWCoinTypeSolana: + EXPECT_EQ(address, "H4JcMPicKkHcxxDjkyyrLoQj7Kcibd9t815ak4UvTr9M"); + break; + case TWCoinTypeSyscoin: + EXPECT_EQ(address, "sys1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z083sjh7"); + break; + case TWCoinTypeTHORChain: + EXPECT_EQ(address, "thor1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0luxce7"); + break; + case TWCoinTypeTezos: + EXPECT_EQ(address, "tz1gcEWswVU6dxfNQWbhTgaZrUrNUFwrsT4z"); + break; + case TWCoinTypeTron: + EXPECT_EQ(address, "TQLCsShbQNXMTVCjprY64qZmEA4rBarpQp"); + break; + case TWCoinTypeVerge: + EXPECT_EQ(address, "DNRTC6GZ5evmM7BZWwPqF54fyDqUqULMyu"); + break; + case TWCoinTypeViacoin: + EXPECT_EQ(address, "via1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z09y9mn2"); + break; + case TWCoinTypeWaves: + EXPECT_EQ(address, "3P2C786D6mBuvyf4WYr6K6Vch5uhi97nBHG"); + break; + case TWCoinTypeXRP: + EXPECT_EQ(address, "rJHMeqKu8Ep7Fazx8MQG6JunaafBXz93YQ"); + break; + case TWCoinTypeZen: + EXPECT_EQ(address, "zniNGeFxXRpY6RDGVdfdmbcvcFb1rrLdnFz"); + break; + case TWCoinTypeZilliqa: + EXPECT_EQ(address, "zil1j2cvtd7j9n7fnxfv2r3neucjw8tp4xz9sp07v4"); + break; + case TWCoinTypeStratis: + EXPECT_EQ(address, "strax1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z0rvt20n"); + break; + case TWCoinTypeNervos: + EXPECT_EQ(address, "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqtsqfsf77ae0wn5a7795hs2ydv83g6hl4qleywxw"); + break; + case TWCoinTypeAptos: + EXPECT_EQ(address, "0xce2fd04ac9efa74f17595e5785e847a2399d7e637f5e8179244f76191f653276"); + break; + case TWCoinTypeNebl: + EXPECT_EQ(address, "NdCKqb8BQoavA5PZ5b4APxKmSpmBA6yMSi"); + break; + case TWCoinTypeSui: + EXPECT_EQ(address, "0x870deb25d5c0a4d7250d52d5cd58dacca2d51eb2a120a979b13384cd52e21e1b"); + break; + case TWCoinTypeHedera: + EXPECT_EQ(address, "0.0.302a300506032b6570032100ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148"); + break; + case TWCoinTypeSecret: + EXPECT_EQ(address, "secret1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0m7t23a"); + break; + case TWCoinTypeNativeInjective: + EXPECT_EQ(address, "inj1nk9x9ajk4rgkzhqjjn7hr6w0k0jg2kj0knl55v"); + break; + case TWCoinTypeAgoric: + EXPECT_EQ(address, "agoric1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0txauuh"); + break; + case TWCoinTypeStargaze: + EXPECT_EQ(address, "stars1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0d8g78s"); + break; + case TWCoinTypeJuno: + EXPECT_EQ(address, "juno1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z00fucta"); + break; + case TWCoinTypeStride: + EXPECT_EQ(address, "stride1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z06sllcd"); + break; + case TWCoinTypeAxelar: + EXPECT_EQ(address, "axelar1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0a4ft8q"); + break; + case TWCoinTypeCrescent: + EXPECT_EQ(address, "cre1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0anvxev"); + break; + case TWCoinTypeKujira: + EXPECT_EQ(address, "kujira1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0gnampt"); + break; + case TWCoinTypeNativeCanto: + EXPECT_EQ(address, "canto1nk9x9ajk4rgkzhqjjn7hr6w0k0jg2kj0wvfqju"); + break; + case TWCoinTypeComdex: + EXPECT_EQ(address, "comdex1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z075ap4k"); + break; + case TWCoinTypeNeutron: + EXPECT_EQ(address, "neutron1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0aykpkx"); + break; + case TWCoinTypeSommelier: + EXPECT_EQ(address, "somm1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z048s0at"); + break; + case TWCoinTypeFetchAI: + EXPECT_EQ(address, "fetch1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z02xk8wk"); + break; + case TWCoinTypeMars: + EXPECT_EQ(address, "mars1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0yxx6e6"); + break; + case TWCoinTypeUmee: + EXPECT_EQ(address, "umee1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0tdzugn"); + break; + case TWCoinTypeCoreum: + EXPECT_EQ(address, "core1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0248ct6"); + break; + case TWCoinTypeQuasar: + EXPECT_EQ(address, "quasar1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0hc97py"); + break; + case TWCoinTypePersistence: + EXPECT_EQ(address, "persistence1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0hhesz9"); + break; + case TWCoinTypeAkash: + EXPECT_EQ(address, "akash1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z05qjy4m"); + break; + case TWCoinTypeNoble: + EXPECT_EQ(address, "noble1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z03c2t50"); + break; + case TWCoinTypeRootstock: + EXPECT_EQ(address, "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); + break; + case TWCoinTypeSei: + EXPECT_EQ(address, "sei1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z05hw42q"); + break; + case TWCoinTypeInternetComputer: + EXPECT_EQ(address, "cb3aa6a0471a417fc33d8e71f1d241750dfa29b4dc8f084265ce1301fb03b65b"); + break; + case TWCoinTypeTia: + EXPECT_EQ(address, "celestia1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0g3wnkv"); + break; + case TWCoinTypeNativeZetaChain: + EXPECT_EQ(address, "zeta1nk9x9ajk4rgkzhqjjn7hr6w0k0jg2kj027x9uy"); + break; + case TWCoinTypeDydx: + EXPECT_EQ(address, "dydx1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0sz38vk"); + break; + // end_of_coin_address_derivation_tests_marker_do_not_modify + // no default branch here, intentionally, to better notice any missing coins + } + } +} + +int countThreadReady = 0; +std::mutex countThreadReadyMutex; + +void useCoinFromThread() { + const int tryCount = 20; + for (int i = 0; i < tryCount; ++i) { + // perform some operations + TW::validateAddress(TWCoinTypeZilliqa, "zil1j8xae6lggm8y63m3y2r7aefu797ze7mhzulnqg"); + TW::validateAddress(TWCoinTypeEthereum, "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); + const auto coinTypes = TW::getCoinTypes(); + } + countThreadReadyMutex.lock(); + ++countThreadReady; + countThreadReadyMutex.unlock(); +} + +TEST(Coin, InitMultithread) { + const int numThread = 20; + countThreadReady = 0; + std::thread thread[numThread]; + // execute in threads + for (int i = 0; i < numThread; ++i) { + thread[i] = std::thread(useCoinFromThread); + } + // wait for completion + for (int i = 0; i < numThread; ++i) { + thread[i].join(); + } + // check that all completed OK + ASSERT_EQ(countThreadReady, numThread); +} + +} // namespace TW diff --git a/tests/CoinAddressValidationTests.cpp b/tests/common/CoinAddressValidationTests.cpp similarity index 91% rename from tests/CoinAddressValidationTests.cpp rename to tests/common/CoinAddressValidationTests.cpp index 607f9f120c5..b02fe64d635 100644 --- a/tests/CoinAddressValidationTests.cpp +++ b/tests/common/CoinAddressValidationTests.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Coin.h" #include "HexCoding.h" @@ -45,7 +43,10 @@ TEST(Coin, validateAddressBitcoin) { TEST(Coin, ValidateAddressBinance) { EXPECT_TRUE(validateAddress(TWCoinTypeBinance, "bnb12vtaxl9952zm6rwf7v8jerq74pvaf77fcmvzhw")); - EXPECT_FALSE(validateAddress(TWCoinTypeBinance, "tbnb12vtaxl9952zm6rwf7v8jerq74pvaf77fkw9xhl")); + EXPECT_TRUE(validateAddress(TWCoinTypeBinance, "tbnb1devga6q804tx9fqrnx0vtu5r36kxgp9t4ruzk2", "tbnb")); + + EXPECT_FALSE(validateAddress(TWCoinTypeBinance, "tbnb1devga6q804tx9fqrnx0vtu5r36kxgp9t4ruzk2")); + EXPECT_FALSE(validateAddress(TWCoinTypeBinance, "bad1devga6q804tx9fqrnx0vtu5r36kxgp9tqx8h9k")); } TEST(Coin, ValidateAddressLitecoin) { @@ -374,9 +375,9 @@ TEST(Coin, ValidateAddressVeChain) { EXPECT_EQ(normalizeAddress(TWCoinTypeVeChain, "0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f"), "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); } -TEST(Coin, ValidateAddressElrond) { - EXPECT_TRUE(validateAddress(TWCoinTypeElrond, "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz")); - EXPECT_FALSE(validateAddress(TWCoinTypeElrond, "xerd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz")); +TEST(Coin, ValidateAddressMultiversX) { + EXPECT_TRUE(validateAddress(TWCoinTypeMultiversX, "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz")); + EXPECT_FALSE(validateAddress(TWCoinTypeMultiversX, "xerd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz")); } TEST(Coin, ValidateAddressOasis) { @@ -402,4 +403,23 @@ TEST(Coin, ValidateAddressECash) { ASSERT_EQ(normalizeAddress(TWCoinTypeECash, "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2"), "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2"); } +TEST(Coin, ValidateAddressEverscale) { + EXPECT_TRUE(validateAddress(TWCoinTypeEverscale, "0:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a")); + EXPECT_FALSE(validateAddress(TWCoinTypeEverscale, "83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a")); + + ASSERT_EQ(normalizeAddress(TWCoinTypeEverscale, "0:83A0352908060FA87839195D8A763A8D9AB28F8FA41468832B398A719CC6469A"), "0:83a0352908060fa87839195d8a763a8d9ab28f8fa41468832b398a719cc6469a"); +} + +TEST(Coin, ValidateAddressNebl) { + EXPECT_TRUE(validateAddress(TWCoinTypeNebl, "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc")); + EXPECT_TRUE(validateAddress(TWCoinTypeNebl, "NidLccuLD8J4oK25PwPg5ipLj5L9VVrwi5")); +} + +TEST(Coin, ValidateAddressTheOpenNetwork) { + EXPECT_TRUE(validateAddress(TWCoinTypeTON, "0:8a8627861a5dd96c9db3ce0807b122da5ed473934ce7568a5b4b1c361cbb28ae")); + EXPECT_FALSE(validateAddress(TWCoinTypeTON, "8a8627861a5dd96c9db3ce0807b122da5ed473934ce7568a5b4b1c361cbb28ae")); + + ASSERT_EQ(normalizeAddress(TWCoinTypeTON, "0:8a8627861a5dd96c9db3ce0807b122da5ed473934ce7568a5b4b1c361cbb28ae"), "UQCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsorhqg"); +} + } // namespace TW diff --git a/tests/DataTests.cpp b/tests/common/DataTests.cpp similarity index 82% rename from tests/DataTests.cpp rename to tests/common/DataTests.cpp index bbdc6f3ffd8..9d0a573ecc7 100644 --- a/tests/DataTests.cpp +++ b/tests/common/DataTests.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Data.h" #include "HexCoding.h" @@ -14,34 +12,34 @@ using namespace TW; TEST(DataTests, fromVector) { const Data data = {1, 2, 3}; - EXPECT_EQ(data.size(), 3); + EXPECT_EQ(data.size(), 3ul); EXPECT_EQ(data[1], 2); EXPECT_EQ(hex(data), "010203"); } TEST(DataTests, fromHex) { const Data data = parse_hex("01020304"); - EXPECT_EQ(data.size(), 4); + EXPECT_EQ(data.size(), 4ul); EXPECT_EQ(hex(data), "01020304"); } TEST(DataTests, fromString) { const Data data = TW::data(std::string("ABC")); - EXPECT_EQ(data.size(), 3); + EXPECT_EQ(data.size(), 3ul); EXPECT_EQ(hex(data), "414243"); } TEST(DataTests, fromBytes) { const std::vector vec = {1, 2, 3}; const Data data = TW::data(vec.data(), vec.size()); - EXPECT_EQ(data.size(), 3); + EXPECT_EQ(data.size(), 3ul); EXPECT_EQ(hex(data), "010203"); } TEST(DataTests, padLeft) { Data data = parse_hex("01020304"); pad_left(data, 10); - EXPECT_EQ(data.size(), 10); + EXPECT_EQ(data.size(), 10ul); EXPECT_EQ(hex(data), "00000000000001020304"); } @@ -49,20 +47,20 @@ TEST(DataTests, append) { Data data1 = parse_hex("01020304"); const Data data2 = parse_hex("aeaf"); append(data1, data2); - EXPECT_EQ(data1.size(), 6); + EXPECT_EQ(data1.size(), 6ul); EXPECT_EQ(hex(data1), "01020304aeaf"); } TEST(DataTests, appendByte) { Data data1 = parse_hex("01020304"); append(data1, 5); - EXPECT_EQ(data1.size(), 5); + EXPECT_EQ(data1.size(), 5ul); EXPECT_EQ(hex(data1), "0102030405"); } TEST(DataTests, subData) { const Data data = parse_hex("0102030405060708090a"); - EXPECT_EQ(data.size(), 10); + EXPECT_EQ(data.size(), 10ul); EXPECT_EQ(hex(subData(data, 2, 3)), "030405"); EXPECT_EQ(hex(subData(data, 0, 10)), "0102030405060708090a"); diff --git a/tests/EncryptTests.cpp b/tests/common/EncryptTests.cpp similarity index 78% rename from tests/EncryptTests.cpp rename to tests/common/EncryptTests.cpp index 9870f4e2937..52c030913a2 100644 --- a/tests/EncryptTests.cpp +++ b/tests/common/EncryptTests.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Encrypt.h" #include "Data.h" @@ -12,41 +10,42 @@ #include -using namespace TW::Encrypt; using namespace TW; -const Data key = parse_hex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); +namespace TW::Encrypt::test { + +const Data gKey = parse_hex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); inline void assertHexEqual(const Data& data, const char* expected) { EXPECT_EQ(hex(data), expected); } TEST(Encrypt, paddingSize) { - EXPECT_EQ(paddingSize(0, 16, TWAESPaddingModeZero), 0); - EXPECT_EQ(paddingSize(1, 16, TWAESPaddingModeZero), 15); - EXPECT_EQ(paddingSize(8, 16, TWAESPaddingModeZero), 8); - EXPECT_EQ(paddingSize(15, 16, TWAESPaddingModeZero), 1); - EXPECT_EQ(paddingSize(16, 16, TWAESPaddingModeZero), 0); - EXPECT_EQ(paddingSize(17, 16, TWAESPaddingModeZero), 15); - EXPECT_EQ(paddingSize(24, 16, TWAESPaddingModeZero), 8); - EXPECT_EQ(paddingSize(31, 16, TWAESPaddingModeZero), 1); - EXPECT_EQ(paddingSize(32, 16, TWAESPaddingModeZero), 0); - EXPECT_EQ(paddingSize(0, 16, TWAESPaddingModePKCS7), 16); - EXPECT_EQ(paddingSize(1, 16, TWAESPaddingModePKCS7), 15); - EXPECT_EQ(paddingSize(8, 16, TWAESPaddingModePKCS7), 8); - EXPECT_EQ(paddingSize(15, 16, TWAESPaddingModePKCS7), 1); - EXPECT_EQ(paddingSize(16, 16, TWAESPaddingModePKCS7), 16); - EXPECT_EQ(paddingSize(17, 16, TWAESPaddingModePKCS7), 15); - EXPECT_EQ(paddingSize(24, 16, TWAESPaddingModePKCS7), 8); - EXPECT_EQ(paddingSize(31, 16, TWAESPaddingModePKCS7), 1); - EXPECT_EQ(paddingSize(32, 16, TWAESPaddingModePKCS7), 16); + EXPECT_EQ(paddingSize(0, 16, TWAESPaddingModeZero), 0ul); + EXPECT_EQ(paddingSize(1, 16, TWAESPaddingModeZero), 15ul); + EXPECT_EQ(paddingSize(8, 16, TWAESPaddingModeZero), 8ul); + EXPECT_EQ(paddingSize(15, 16, TWAESPaddingModeZero), 1ul); + EXPECT_EQ(paddingSize(16, 16, TWAESPaddingModeZero), 0ul); + EXPECT_EQ(paddingSize(17, 16, TWAESPaddingModeZero), 15ul); + EXPECT_EQ(paddingSize(24, 16, TWAESPaddingModeZero), 8ul); + EXPECT_EQ(paddingSize(31, 16, TWAESPaddingModeZero), 1ul); + EXPECT_EQ(paddingSize(32, 16, TWAESPaddingModeZero), 0ul); + EXPECT_EQ(paddingSize(0, 16, TWAESPaddingModePKCS7), 16ul); + EXPECT_EQ(paddingSize(1, 16, TWAESPaddingModePKCS7), 15ul); + EXPECT_EQ(paddingSize(8, 16, TWAESPaddingModePKCS7), 8ul); + EXPECT_EQ(paddingSize(15, 16, TWAESPaddingModePKCS7), 1ul); + EXPECT_EQ(paddingSize(16, 16, TWAESPaddingModePKCS7), 16ul); + EXPECT_EQ(paddingSize(17, 16, TWAESPaddingModePKCS7), 15ul); + EXPECT_EQ(paddingSize(24, 16, TWAESPaddingModePKCS7), 8ul); + EXPECT_EQ(paddingSize(31, 16, TWAESPaddingModePKCS7), 1ul); + EXPECT_EQ(paddingSize(32, 16, TWAESPaddingModePKCS7), 16ul); } TEST(Encrypt, AESCBCEncrypt) { auto iv = parse_hex("000102030405060708090A0B0C0D0E0F"); auto data = parse_hex("6bc1bee22e409f96e93d7e117393172a"); - auto encryptResult = AESCBCEncrypt(key, data, iv); + auto encryptResult = AESCBCEncrypt(gKey, data, iv); assertHexEqual(encryptResult, "f58c4c04d6e5f1ba779eabfb5f7bfbd6"); } @@ -70,7 +69,7 @@ TEST(Encrypt, AESCBCDecrypt) { auto iv = parse_hex("000102030405060708090A0B0C0D0E0F"); auto cipher = parse_hex("f58c4c04d6e5f1ba779eabfb5f7bfbd6"); - auto decryptResult = AESCBCDecrypt(key, cipher, iv); + auto decryptResult = AESCBCDecrypt(gKey, cipher, iv); assertHexEqual(decryptResult, "6bc1bee22e409f96e93d7e117393172a"); } @@ -98,7 +97,7 @@ TEST(Encrypt, AESCTREncrypt) { auto iv = parse_hex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); auto data = parse_hex("6bc1bee22e409f96e93d7e117393172a"); - auto encryptResult = AESCTREncrypt(key, data, iv); + auto encryptResult = AESCTREncrypt(gKey, data, iv); assertHexEqual(encryptResult, "601ec313775789a5b7a7f504bbf3d228"); } @@ -106,7 +105,7 @@ TEST(Encrypt, AESCTRDecrypt) { auto iv = parse_hex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); auto cipher = parse_hex("601ec313775789a5b7a7f504bbf3d228"); - auto decryptResult = AESCTRDecrypt(key, cipher, iv); + auto decryptResult = AESCTRDecrypt(gKey, cipher, iv); assertHexEqual(decryptResult, "6bc1bee22e409f96e93d7e117393172a"); } @@ -200,3 +199,5 @@ TEST(Encrypt, AESCTRDecryptInvalidKeySize) { } ADD_FAILURE() << "Missed expected exeption"; } + +} // namespace TW::Encrypt::tests diff --git a/tests/HDWallet/HDWalletInternalTests.cpp b/tests/common/HDWallet/HDWalletInternalTests.cpp similarity index 94% rename from tests/HDWallet/HDWalletInternalTests.cpp rename to tests/common/HDWallet/HDWalletInternalTests.cpp index 763b2c9561b..19e8c45868f 100644 --- a/tests/HDWallet/HDWalletInternalTests.cpp +++ b/tests/common/HDWallet/HDWalletInternalTests.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "HDWallet.h" #include "Data.h" @@ -11,13 +9,13 @@ #include "PublicKey.h" #include #include -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include #include #include -namespace TW { +namespace TW::HDWalletInternalTests { const auto mnemonic1 = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; @@ -61,7 +59,7 @@ TEST(HDWalletInternal, SquareDerivationRoutes) { // getMasterNode auto masterNode = HDNode(); - hdnode_from_seed(wallet.getSeed().data(), HDWallet::seedSize, SECP256K1_NAME, &masterNode); + hdnode_from_seed(wallet.getSeed().data(), HDWallet<>::mSeedSize, SECP256K1_NAME, &masterNode); auto node0 = masterNode; // getNode diff --git a/tests/common/HDWallet/HDWalletTests.cpp b/tests/common/HDWallet/HDWalletTests.cpp new file mode 100644 index 00000000000..355036071c6 --- /dev/null +++ b/tests/common/HDWallet/HDWalletTests.cpp @@ -0,0 +1,653 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Base58.h" +#include "Bitcoin/Address.h" +#include "Bitcoin/CashAddress.h" +#include "Bitcoin/SegwitAddress.h" +#include "IoTeX/Address.h" +#include "Cosmos/Address.h" +#include "Coin.h" +#include "Ethereum/Address.h" +#include "Ethereum/EIP2645.h" +#include "Ethereum/MessageSigner.h" +#include "HDWallet.h" +#include "Hash.h" +#include "Hedera/DER.h" +#include "HexCoding.h" +#include "ImmutableX/StarkKey.h" +#include "Mnemonic.h" +#include "NEAR/Address.h" +#include "PublicKey.h" +#include "StarkEx/MessageSigner.h" +#include "TestUtilities.h" + +#include + +extern std::string TESTS_ROOT; + +namespace TW::HDWalletTests { + +const auto mnemonic1 = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; +const auto gPassphrase = "passphrase"; + +TEST(HDWallet, generate) { + { + HDWallet wallet = HDWallet(128, gPassphrase); + EXPECT_TRUE(Mnemonic::isValid(wallet.getMnemonic())); + EXPECT_EQ(wallet.getPassphrase(), gPassphrase); + EXPECT_EQ(wallet.getEntropy().size(), 16ul); + } + { + HDWallet wallet = HDWallet(256, gPassphrase); + EXPECT_TRUE(Mnemonic::isValid(wallet.getMnemonic())); + EXPECT_EQ(wallet.getPassphrase(), gPassphrase); + EXPECT_EQ(wallet.getEntropy().size(), 32ul); + } +} + +TEST(HDWallet, generateInvalid) { + EXPECT_EXCEPTION(HDWallet(64, gPassphrase), "Invalid strength"); + EXPECT_EXCEPTION(HDWallet(129, gPassphrase), "Invalid strength"); + EXPECT_EXCEPTION(HDWallet(512, gPassphrase), "Invalid strength"); +} + +TEST(HDWallet, createFromMnemonic) { + { + HDWallet wallet = HDWallet(mnemonic1, gPassphrase); + EXPECT_EQ(wallet.getMnemonic(), mnemonic1); + EXPECT_EQ(wallet.getPassphrase(), gPassphrase); + EXPECT_EQ(hex(wallet.getEntropy()), "ba5821e8c356c05ba5f025d9532fe0f21f65d594"); + EXPECT_EQ(hex(wallet.getSeed()), "143cd5fc27ae46eb423efebc41610473f5e24a80f2ca2e2fa7bf167e537f58f4c68310ae487fce82e25bad29bab2530cf77fd724a5ebfc05a45872773d7ee2d6"); + } + { // empty passphrase + HDWallet wallet = HDWallet(mnemonic1, ""); + EXPECT_EQ(wallet.getMnemonic(), mnemonic1); + EXPECT_EQ(wallet.getPassphrase(), ""); + EXPECT_EQ(hex(wallet.getEntropy()), "ba5821e8c356c05ba5f025d9532fe0f21f65d594"); + EXPECT_EQ(hex(wallet.getSeed()), "354c22aedb9a37407adc61f657a6f00d10ed125efa360215f36c6919abd94d6dbc193a5f9c495e21ee74118661e327e84a5f5f11fa373ec33b80897d4697557d"); + } +} + +TEST(HDWallet, entropyLength_createFromMnemonic) { + { // 12 words + HDWallet wallet = HDWallet("oil oil oil oil oil oil oil oil oil oil oil oil", ""); + EXPECT_EQ(wallet.getEntropy().size(), 16ul); + EXPECT_EQ(hex(wallet.getEntropy()), "99d33a674ce99d33a674ce99d33a674c"); + } + { // 12 words, from https://github.com/trezor/python-mnemonic/blob/master/vectors.json + HDWallet wallet = HDWallet("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", ""); + EXPECT_EQ(wallet.getEntropy().size(), 16ul); + EXPECT_EQ(hex(wallet.getEntropy()), "00000000000000000000000000000000"); + } + { // 15 words + HDWallet wallet = HDWallet("history step cheap card humble screen raise seek robot slot coral roof spoil wreck caution", ""); + EXPECT_EQ(wallet.getEntropy().size(), 20ul); + EXPECT_EQ(hex(wallet.getEntropy()), "6c3aac9b9146ef832c4e18bb3980c0dddd25fc49"); + } + { // 18 words + HDWallet wallet = HDWallet("caught hockey split gun symbol code payment copy broccoli silly shed secret stove tell citizen staff photo high", ""); + EXPECT_EQ(wallet.getEntropy().size(), 24ul); + EXPECT_EQ(hex(wallet.getEntropy()), "246d8f48b3fdc65a2869801c791715614d6bbd8a56a0a3ad"); + } + { // 21 words + HDWallet wallet = HDWallet("diary shine country alpha bridge coast loan hungry hip media sell crucial swarm share gospel lake visa coin dizzy physical basket", ""); + EXPECT_EQ(wallet.getEntropy().size(), 28ul); + EXPECT_EQ(hex(wallet.getEntropy()), "3d58bcc40381bc59a0c37a6bf14f0d9a3db78a5933e5f4a5ad00d1f1"); + } + { // 24 words + HDWallet wallet = HDWallet("poet spider smile swift roof pilot subject save hand diet ice universe over brown inspire ugly wide economy symbol shove episode patient plug swamp", ""); + EXPECT_EQ(wallet.getEntropy().size(), 32ul); + EXPECT_EQ(hex(wallet.getEntropy()), "a73a3732edebbb49f5fdfe68c7b5c0f6e9de3a1d5760faa8c771e384bf4229b6"); + } + { // 24 words, from https://github.com/trezor/python-mnemonic/blob/master/vectors.json + HDWallet wallet = HDWallet("letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", ""); + EXPECT_EQ(wallet.getEntropy().size(), 32ul); + EXPECT_EQ(hex(wallet.getEntropy()), "8080808080808080808080808080808080808080808080808080808080808080"); + } +} + +TEST(HDWallet, createFromSpanishMnemonic) { + { + EXPECT_EXCEPTION(HDWallet("llanto radical atraer riesgo actuar masa fondo cielo dieta archivo sonrisa mamut", ""), "Invalid mnemonic"); + } + { + HDWallet wallet = HDWallet("llanto radical atraer riesgo actuar masa fondo cielo dieta archivo sonrisa mamut", "", false); + EXPECT_EQ(wallet.getMnemonic(), "llanto radical atraer riesgo actuar masa fondo cielo dieta archivo sonrisa mamut"); + EXPECT_EQ(wallet.getPassphrase(), ""); + EXPECT_EQ(hex(wallet.getEntropy()), ""); + EXPECT_EQ(hex(wallet.getSeed()), "ec8f8703432fc7d32e699ee056e9d84b1435e6a64a6a40ad63dbde11eab189a276ddcec20f3326d3c6ee39cbd018585b104fc3633b801c011063ae4c318fb9b6"); + } +} + +TEST(HDWallet, createFromMnemonicInvalid) { + EXPECT_EXCEPTION(HDWallet("THIS IS AN INVALID MNEMONIC", gPassphrase), "Invalid mnemonic"); + EXPECT_EXCEPTION(HDWallet("", gPassphrase), "Invalid mnemonic"); + + EXPECT_EXCEPTION(HDWallet("", gPassphrase, false), "Invalid mnemonic"); + HDWallet walletUnchecked = HDWallet("THIS IS AN INVALID MNEMONIC", gPassphrase, false); +} + +TEST(HDWallet, createFromEntropy) { + { + HDWallet wallet = HDWallet(parse_hex("ba5821e8c356c05ba5f025d9532fe0f21f65d594"), gPassphrase); + EXPECT_EQ(wallet.getMnemonic(), mnemonic1); + } +} + +TEST(HDWallet, createFromEntropyInvalid) { + EXPECT_EXCEPTION(HDWallet(parse_hex(""), gPassphrase), "Invalid mnemonic data"); + EXPECT_EXCEPTION(HDWallet(parse_hex("123456"), gPassphrase), "Invalid mnemonic data"); +} + +TEST(HDWallet, recreateFromEntropy) { + { + HDWallet wallet1 = HDWallet(mnemonic1, gPassphrase); + EXPECT_EQ(wallet1.getMnemonic(), mnemonic1); + EXPECT_EQ(hex(wallet1.getEntropy()), "ba5821e8c356c05ba5f025d9532fe0f21f65d594"); + HDWallet wallet2 = HDWallet(wallet1.getEntropy(), gPassphrase); + EXPECT_EQ(wallet2.getMnemonic(), wallet1.getMnemonic()); + EXPECT_EQ(wallet2.getEntropy(), wallet1.getEntropy()); + EXPECT_EQ(wallet2.getSeed(), wallet1.getSeed()); + } +} + +TEST(HDWallet, privateKeyFromXPRV) { + const std::string xprv = "xprv9yqEgpMG2KCjvotCxaiMkzmKJpDXz2xZi3yUe4XsURvo9DUbPySW1qRbdeDLiSxZt88hESHUhm2AAe2EqfWM9ucdQzH3xv1HoKoLDqHMK9n"; + auto privateKey = HDWallet<>::getPrivateKeyFromExtended(xprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 3)); + ASSERT_TRUE(privateKey); + auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); + auto address = Bitcoin::BitcoinCashAddress(publicKey); + + EXPECT_EQ(hex(publicKey.bytes), "025108168f7e5aad52f7381c18d8f880744dbee21dc02c15abe512da0b1cca7e2f"); + EXPECT_EQ(address.string(), "bitcoincash:qp3y0dyg6ya8nt4n3algazn073egswkytqs00z7rz4"); +} + +TEST(HDWallet, privateKeyFromXPRV_Invalid) { + const std::string xprv = "xprv9y0000"; + auto privateKey = HDWallet<>::getPrivateKeyFromExtended(xprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 3)); + ASSERT_FALSE(privateKey); +} + +TEST(HDWallet, privateKeyFromXPRV_InvalidVersion) { + { + // Version bytes (first 4) are invalid, 0x00000000 + const std::string xprv = "11117pE7xwz2GARukXY8Vj2ge4ozfX4HLgy5ztnJXjr5btzJE8EbtPhZwrcPWAodW2aFeYiXkXjSxJYm5QrnhSKFXDgACcFdMqGns9VLqESCq3"; + auto privateKey = HDWallet<>::getPrivateKeyFromExtended(xprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 3)); + ASSERT_FALSE(privateKey); + } + { + // Version bytes (first 4) are invalid, 0xdeadbeef + const std::string xprv = "pGoh3VZXR4mTkT4bfqj4paog12KmHkAWkdLY8HNsZagD1ihVccygLr1ioLBhVQsny47uEh5swP3KScFc4JJrazx1Y7xvzmH2y5AseLgVMwomBTg2"; + auto privateKey = HDWallet<>::getPrivateKeyFromExtended(xprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 3)); + ASSERT_FALSE(privateKey); + } +} + +TEST(HDWallet, privateKeyFromExtended_InvalidCurve) { + // invalid coin & curve, should fail + const std::string xprv = "xprv9yqEgpMG2KCjvotCxaiMkzmKJpDXz2xZi3yUe4XsURvo9DUbPySW1qRbdeDLiSxZt88hESHUhm2AAe2EqfWM9ucdQzH3xv1HoKoLDqHMK9n"; + auto privateKey = HDWallet<>::getPrivateKeyFromExtended(xprv, TWCoinType(123456), DerivationPath(TWPurposeBIP44, 123456, 0, 0, 0)); + ASSERT_FALSE(privateKey); +} + +TEST(HDWallet, privateKeyFromXPRV_Invalid45) { + // 45th byte is not 0 + const std::string xprv = "xprv9yqEgpMG2KCjvotCxaiMkzmKJpDXz2xZi3yUe4XsURvo9DUbPySW1qRbhw2dJ8QexahgVSfkjxU4FgmN4GLGN3Ui8oLqC6433CeyPUNVHHh"; + auto privateKey = HDWallet<>::getPrivateKeyFromExtended(xprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 3)); + ASSERT_FALSE(privateKey); +} + +TEST(HDWallet, privateKeyFromMptv) { + const std::string mptv = "Mtpv7SkyM349Svcf1WiRtB5hC91ZZkVsGuv3kz1V7tThGxBFBzBLFnw6LpaSvwpHHuy8dAfMBqpBvaSAHzbffvhj2TwfojQxM7Ppm3CzW67AFL5"; + auto privateKey = HDWallet<>::getPrivateKeyFromExtended(mptv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoinCash), 0, 0, 4)); + auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); + + auto witness = Data{0x00, 0x14}; + auto keyHash = Hash::sha256ripemd(publicKey.bytes.data(), 33); + witness.insert(witness.end(), keyHash.begin(), keyHash.end()); + + auto prefix = Data{TW::p2shPrefix(TWCoinTypeLitecoin)}; + auto redeemScript = Hash::sha256ripemd(witness.data(), witness.size()); + prefix.insert(prefix.end(), redeemScript.begin(), redeemScript.end()); + + auto address = Bitcoin::Address(prefix); + + EXPECT_EQ(hex(publicKey.bytes), "02c36f9c3051e9cfbb196ecc35311f3ad705ea6798ffbe6b039e70f6bd047e6f2c"); + EXPECT_EQ(address.string(), "MBzcCaoLk9626cLj2UVvcxs6nsVUi39zEy"); +} + +TEST(HDWallet, privateKeyFromZprv) { + const std::string zprv = "zprvAdzGEQ44z4WPLNCRpDaup2RumWxLGgR8PQ9UVsSmJigXsHVDaHK1b6qGM2u9PmxB2Gx264ctAz4yRoN3Xwf1HZmKcn6vmjqwsawF4WqQjfd"; + auto privateKey = HDWallet<>::getPrivateKeyFromExtended(zprv, TWCoinTypeBitcoinCash, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeBitcoin), 0, 0, 5)); + auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); + auto address = Bitcoin::SegwitAddress(publicKey, "bc"); + + EXPECT_EQ(hex(publicKey.bytes), "022dc3f5a3fcfd2d1cc76d0cb386eaad0e30247ba729da0d8847a2713e444fdafa"); + EXPECT_EQ(address.string(), "bc1q5yyq60jepll68hds7exa7kpj20gsvdu0aztw5x"); +} + +TEST(HDWallet, privateKeyFromDGRV) { + const std::string dgpv = "dgpv595jAJYGBLanByCJXRzrWBZFVXdNisfuPmKRDquCQcwBbwKbeR21AtkETf4EpjBsfsK3kDZgMqhcuky1B9PrT5nxiEcjghxpUVYviHXuCmc"; + auto privateKey = HDWallet<>::getPrivateKeyFromExtended(dgpv, TWCoinTypeDogecoin, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeDogecoin), 0, 0, 1)); + auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); + auto address = Bitcoin::Address(publicKey, TW::p2pkhPrefix(TWCoinTypeDogecoin)); + + EXPECT_EQ(hex(publicKey.bytes), "03eb6bf281990ee074a39c71ed8ce78c486066ac433bcf066dd5eb08f87d3a6c34"); + EXPECT_EQ(address.string(), "D5taDndQJ1fDF3AM1yWavmJY2BgSi17CUv"); +} + +TEST(HDWallet, privateKeyFromXPRVForDGB) { + const std::string xprvForDgb = "xprv9ynLofyuR3uCqCMJADwzBaPnXB53EVe5oLujvPfdvCxae3NzgEpYjZMgcUeS8EUeYfYVLG61ZgPXm9TZWiwBnLVCgd551vCwpXC19hX3mFJ"; + auto privateKey = HDWallet<>::getPrivateKeyFromExtended(xprvForDgb, TWCoinTypeDigiByte, DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeDigiByte), 0, 0, 1)); + auto publicKey = privateKey->getPublicKey(TWPublicKeyTypeSECP256k1); + auto address = Bitcoin::Address(publicKey, TW::p2pkhPrefix(TWCoinTypeDigiByte)); + + EXPECT_EQ(hex(publicKey.bytes), "03238a5c541c2cbbf769dbe0fb2a373c22db4da029370767fbe746d59da4de07f1"); + EXPECT_EQ(address.string(), "D9Gv7jWSVsS9Y5q98C79WyfEj6P2iM5Nzs"); +} + +TEST(HDWallet, DeriveWithLeadingZerosEth) { + // Derivation test case with leading zeroes, see https://blog.polychainlabs.com/bitcoin,/bip32,/bip39,/kdf/2021/05/17/inconsistent-bip32-derivations.html + const auto mnemonic = "name dash bleak force moral disease shine response menu rescue more will"; + const auto derivationPath = "m/44'/60'"; + const auto coin = TWCoinTypeEthereum; + auto wallet = HDWallet(mnemonic, ""); + const auto addr = Ethereum::Address(wallet.getKey(coin, DerivationPath(derivationPath)).getPublicKey(TW::publicKeyType(coin))); + EXPECT_EQ(addr.string(), "0x0ba17e928471c64AaEaf3ABfB3900EF4c27b380D"); +} + +static nlohmann::json getVectors() { + const std::string vectorsJsonPath = std::string(TESTS_ROOT) + "/common/HDWallet/bip39_vectors.json"; + auto vectorsJson = loadJson(vectorsJsonPath)["english"]; + return vectorsJson; +} + +TEST(HDWallet, Bip39Vectors) { + // BIP39 test vectors, from https://github.com/trezor/python-mnemonic/blob/master/vectors.json + const auto passphrase = "TREZOR"; + const auto vectors = getVectors(); + for (const auto& v : vectors) { + const std::string entropy = v[0]; + const std::string mnemonic = v[1]; + const std::string seed = v[2]; + const std::string xprv = v[3]; + { // from mnemonic + HDWallet wallet = HDWallet(mnemonic, passphrase); + EXPECT_EQ(wallet.getMnemonic(), mnemonic); + EXPECT_EQ(wallet.getPassphrase(), passphrase); + EXPECT_EQ(hex(wallet.getEntropy()), entropy); + EXPECT_EQ(hex(wallet.getSeed()), seed); + EXPECT_EQ(wallet.getRootKey(TWCoinTypeBitcoin, TWHDVersionXPRV), xprv); + } + { // from entropy + HDWallet wallet = HDWallet(parse_hex(entropy), passphrase); + EXPECT_EQ(wallet.getMnemonic(), mnemonic); + EXPECT_EQ(wallet.getPassphrase(), passphrase); + EXPECT_EQ(hex(wallet.getEntropy()), entropy); + EXPECT_EQ(hex(wallet.getSeed()), seed); + EXPECT_EQ(wallet.getRootKey(TWCoinTypeBitcoin, TWHDVersionXPRV), xprv); + } + } +} + +TEST(HDWallet, getExtendedPrivateKey) { + const HDWallet wallet = HDWallet(mnemonic1, ""); + const auto purpose = TWPurposeBIP44; + const auto coin = TWCoinTypeBitcoin; + const auto hdVersion = TWHDVersionZPRV; + + // default + const auto extPubKey1 = wallet.getExtendedPrivateKey(purpose, coin, hdVersion); + EXPECT_EQ(extPubKey1, "zprvAcwsTZNaY1f7rfwsy5GseSDStYBrxwtsBZDkb3iyuQUs4NF6n58BuH7Xj54RuaSCWtU5CiQzuYQgFgqr1HokgKcVAeGeXokhJUAJeP3VmvY"); + + // explicitly specify default account=0 + const auto extPubKey2 = wallet.getExtendedPrivateKeyAccount(purpose, coin, TWDerivationDefault, hdVersion, 0); + EXPECT_EQ(extPubKey2, "zprvAcwsTZNaY1f7rfwsy5GseSDStYBrxwtsBZDkb3iyuQUs4NF6n58BuH7Xj54RuaSCWtU5CiQzuYQgFgqr1HokgKcVAeGeXokhJUAJeP3VmvY"); + + // custom account=1 + const auto extPubKey3 = wallet.getExtendedPrivateKeyAccount(purpose, coin, TWDerivationDefault, hdVersion, 1); + EXPECT_EQ(extPubKey3, "zprvAcwsTZNaY1f7sifgNNgdNa4P9mPtyg3zRVgwkx2qF9Sn7F255MzP6Zyumn6bgV5xuoS8ZrDvjzE7APcFSacXdzFYpGvyybb1bnAoh5nHxpn"); +} + +TEST(HDWallet, getExtendedPublicKey) { + const HDWallet wallet = HDWallet(mnemonic1, ""); + const auto purpose = TWPurposeBIP44; + const auto coin = TWCoinTypeBitcoin; + const auto hdVersion = TWHDVersionZPUB; + const auto derivation = TWDerivationDefault; + + // default + const auto extPubKey1 = wallet.getExtendedPublicKey(purpose, coin, hdVersion); + EXPECT_EQ(extPubKey1, "zpub6qwDs4uUNPDR5A2M56ot1aABSa2MNQciYn9MPS8bTk1qwAaFKcSST5S1aLidvPp9twqpaumG7vikR2vHhBXjp5oGgHyMvWK3AtUkfeEgyns"); + + // explicitly specify default account=0 + const auto extPubKey2 = wallet.getExtendedPublicKeyAccount(purpose, coin, derivation, hdVersion, 0); + EXPECT_EQ(extPubKey2, "zpub6qwDs4uUNPDR5A2M56ot1aABSa2MNQciYn9MPS8bTk1qwAaFKcSST5S1aLidvPp9twqpaumG7vikR2vHhBXjp5oGgHyMvWK3AtUkfeEgyns"); + + // custom account=1 + const auto extPubKey3 = wallet.getExtendedPublicKeyAccount(purpose, coin, derivation, hdVersion, 1); + EXPECT_EQ(extPubKey3, "zpub6qwDs4uUNPDR6Ck9UQDdji17hoEPP8mqnicYZLSSoUykz3MDcuJdeNJPd3BozqEafeLZkegWqzAvkgA4JZZ5tTN2rDpGKfk54essyfx1eZP"); +} + +TEST(HDWallet, Derive_XpubPub_vs_PrivPub) { + // Test different routes for deriving address from mnemonic, result should be the same: + // - Direct: mnemonic -> seed -> privateKey -> publicKey -> address + // - Extended Public: mnemonic -> seed -> zpub -> publicKey -> address + // - Extended Private: mnemonic -> seed -> zpriv -> privateKey -> publicKey -> address + + const HDWallet wallet = HDWallet(mnemonic1, ""); + const auto coin = TWCoinTypeBitcoin; + const auto derivPath1 = DerivationPath("m/84'/0'/0'/0/0"); + const auto derivPath2 = DerivationPath("m/84'/0'/0'/0/2"); + const auto expectedPublicKey1 = "02df9ef2a7a5552765178b181e1e1afdefc7849985c7dfe9647706dd4fa40df6ac"; + const auto expectedPublicKey2 = "031e1f64d2f6768dccb6814545b2e2d58e26ad5f91b7cbaffe881ed572c65060db"; + const auto expectedAddress1 = "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"; + const auto expectedAddress2 = "bc1q7zddsunzaftf4zlsg9exhzlkvc5374a6v32jf6"; + + // -> privateKey -> publicKey + { + const auto privateKey1 = wallet.getKey(coin, derivPath1); + const auto publicKey1 = privateKey1.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(publicKey1.bytes), expectedPublicKey1); + const auto address1 = Bitcoin::SegwitAddress(publicKey1, "bc"); + EXPECT_EQ(address1.string(), expectedAddress1); + } + { + const auto privateKey2 = wallet.getKey(coin, derivPath2); + const auto publicKey2 = privateKey2.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(publicKey2.bytes), expectedPublicKey2); + const auto address2 = Bitcoin::SegwitAddress(publicKey2, "bc"); + EXPECT_EQ(address2.string(), expectedAddress2); + } + + // zpub -> publicKey + const auto zpub = wallet.getExtendedPublicKey(TWPurposeBIP84, coin, TWHDVersionZPUB); + EXPECT_EQ(zpub, "zpub6rNUNtxSa9Gxvm4Bdxf1MPMwrvkzwDx6vP96Hkzw3jiQKdg3fhXBStxjn12YixQB8h88B3RMSRscRstf9AEVaYr3MAqVBEWBDuEJU4PGaT9"); + + { + const auto publicKey1 = wallet.getPublicKeyFromExtended(zpub, coin, derivPath1); + EXPECT_TRUE(publicKey1.has_value()); + EXPECT_EQ(hex(publicKey1->bytes), expectedPublicKey1); + const auto address1 = Bitcoin::SegwitAddress(publicKey1.value(), "bc"); + EXPECT_EQ(address1.string(), expectedAddress1); + } + { + const auto publicKey2 = wallet.getPublicKeyFromExtended(zpub, coin, derivPath2); + EXPECT_TRUE(publicKey2.has_value()); + EXPECT_EQ(hex(publicKey2->bytes), expectedPublicKey2); + const auto address2 = Bitcoin::SegwitAddress(publicKey2.value(), "bc"); + EXPECT_EQ(address2.string(), expectedAddress2); + } + + // zpriv -> privateKey -> publicKey + const auto zpriv = wallet.getExtendedPrivateKey(TWPurposeBIP84, coin, TWHDVersionZPRV); + EXPECT_EQ(zpriv, "zprvAdP7yPRYjmifiGyiXw7zzFRDJtvWXmEFZADVVNbKVQBRSqLu8ACvu6eFvhrnnw4QwdTD8PUVa48MguwiPTiyfn85zWx9iA5MYy4Eufu5bas"); + + { + const auto privateKey1 = wallet.getPrivateKeyFromExtended(zpriv, coin, derivPath1); + EXPECT_TRUE(privateKey1.has_value()); + const auto publicKey1 = privateKey1->getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(publicKey1.bytes), expectedPublicKey1); + const auto address1 = Bitcoin::SegwitAddress(publicKey1, "bc"); + EXPECT_EQ(address1.string(), expectedAddress1); + } + { + const auto privateKey2 = wallet.getPrivateKeyFromExtended(zpriv, coin, derivPath2); + EXPECT_TRUE(privateKey2.has_value()); + const auto publicKey2 = privateKey2->getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(publicKey2.bytes), expectedPublicKey2); + const auto address2 = Bitcoin::SegwitAddress(publicKey2, "bc"); + EXPECT_EQ(address2.string(), expectedAddress2); + } +} + +TEST(HDWallet, getKeyByCurve) { + const auto derivPath = "m/44'/539'/0'/0/0"; + HDWallet wallet = HDWallet(mnemonic1, ""); + { + const auto privateKey = wallet.getKeyByCurve(TWCurveSECP256k1, DerivationPath(derivPath)); + EXPECT_EQ(hex(privateKey.bytes), "4fb8657d6464adcaa086d6758d7f0b6b6fc026c98dc1671fcc6460b5a74abc62"); + } + { + const auto privateKey = wallet.getKeyByCurve(TWCurveNIST256p1, DerivationPath(derivPath)); + EXPECT_EQ(hex(privateKey.bytes), "a13df52d5a5b438bbf921bbf86276e4347fe8e2f2ed74feaaee12b77d6d26f86"); + } +} + +TEST(HDWallet, getKey) { + const auto derivPath = "m/44'/539'/0'/0/0"; + HDWallet wallet = HDWallet(mnemonic1, ""); + { + const auto privateKey = wallet.getKey(TWCoinTypeBitcoin, DerivationPath(derivPath)); + EXPECT_EQ(hex(privateKey.bytes), "4fb8657d6464adcaa086d6758d7f0b6b6fc026c98dc1671fcc6460b5a74abc62"); + } + { + const auto privateKey = wallet.getKey(TWCoinTypeNEO, DerivationPath(derivPath)); + EXPECT_EQ(hex(privateKey.bytes), "a13df52d5a5b438bbf921bbf86276e4347fe8e2f2ed74feaaee12b77d6d26f86"); + } +} + +TEST(HDWallet, AptosKey) { + const auto derivPath = "m/44'/637'/0'/0'/0'"; + HDWallet wallet = HDWallet(mnemonic1, ""); + { + const auto privateKey = wallet.getKey(TWCoinTypeAptos, DerivationPath(derivPath)); + EXPECT_EQ(hex(privateKey.bytes), "7f2634c0e2414a621e96e39c41d09021700cee12ee43328ed094c5580cd0bd6f"); + EXPECT_EQ(hex(privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes), "633e5c7e355bdd484706436ce1f06fdf280bd7c2229a7f9b6489684412c6967c"); + } +} + +TEST(HDWallet, HederaKey) { + // https://github.com/hashgraph/hedera-sdk-js/blob/e0cd39c84ab189d59a6bcedcf16e4102d7bb8beb/packages/cryptography/test/unit/Mnemonic.js#L47 + { + const auto derivPath = "m/44'/3030'/0'/0'/0"; + HDWallet wallet = HDWallet("inmate flip alley wear offer often piece magnet surge toddler submit right radio absent pear floor belt raven price stove replace reduce plate home", ""); + { + const auto privateKey = wallet.getKey(TWCoinTypeHedera, DerivationPath(derivPath)); + EXPECT_EQ(Hedera::gHederaDerPrefixPrivate + hex(privateKey.bytes), "302e020100300506032b657004220420853f15aecd22706b105da1d709b4ac05b4906170c2b9c7495dff9af49e1391da"); + EXPECT_EQ(Hedera::gHederaDerPrefixPublic + hex(privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes), "302a300506032b6570032100b63b3815f453cf697b53b290b1d78e88c725d39bde52c34c79fb5b4c93894673"); + } + } + { + const auto derivPath = "m/44'/3030'/0'/0'/0"; + HDWallet wallet = HDWallet("walk gun glide frequent exhaust sugar siege prosper staff skill swarm label", ""); + { + const auto privateKey = wallet.getKey(TWCoinTypeHedera, DerivationPath(derivPath)); + EXPECT_EQ(Hedera::gHederaDerPrefixPrivate + hex(privateKey.bytes), "302e020100300506032b657004220420650c5120cbdc6244e3d10001eb27eea4dd3f80c331b3b6969fa434797d4edd50"); + EXPECT_EQ(Hedera::gHederaDerPrefixPublic + hex(privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes), "302a300506032b65700321007df3e1ab790b28de4706d36a7aa99a0e043cb3e2c3d6ec6686e4af7f638b0860"); + } + } +} + +TEST(HDWallet, FromSeedStark) { + auto seed = parse_hex("4c4a250231bcac7beb165aec4c9b049b4ba40ad8dd287dc79b92b1ffcf20cdcf"); + ASSERT_EQ(load(seed), uint256_t("34506778598894488719068064129252410649539581100963007245393949841529394744783")); + auto derivationPath = DerivationPath("m/2645'/579218131'/211006541'/1534045311'/1431804530'/1"); + auto key = HDWallet<32>::bip32DeriveRawSeed(TWCoinTypeEthereum, seed, derivationPath); + ASSERT_EQ(hex(key.bytes), "57384e99059bb1c0e51d70f0fca22d18d7191398dd39d6b9b4e0521174b2377a"); + auto addr = Ethereum::Address(key.getPublicKey(TWPublicKeyTypeSECP256k1Extended)).string(); + ASSERT_EQ(addr, "0x47bbe762944B089315ac50c9ca762F4B4884B965"); +} + +TEST(HDWallet, FromMnemonicStark) { + // https://github.com/starkware-libs/starkware-crypto-utils/blob/d3a1e655105afd66ebc07f88a179a3042407cc7b/test/js/key_derivation.spec.js#L20 + const auto mnemonic = "range mountain blast problem vibrant void vivid doctor cluster enough melody salt layer language laptop boat major space monkey unit glimpse pause change vibrant"; + const auto ethAddress = "0xA4864D977b944315389d1765Ffa7E66F74eE8cD7"; + HDWallet wallet = HDWallet(mnemonic, ""); + auto derivationPath = DerivationPath(Ethereum::accountPathFromAddress(ethAddress, "starkex", "starkdeployement", "0")); + ASSERT_EQ(derivationPath.string(), "m/2645'/579218131'/891216374'/1961790679'/2135936222'/0"); + + // ETH + { + auto ethPrivKey = wallet.getKey(TWCoinTypeEthereum, DerivationPath("m/44'/60'/0'/0/0")); + auto ethAddressFromPub = Ethereum::Address(ethPrivKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)).string(); + ASSERT_EQ(ethAddressFromPub, ethAddress); + } + + // Stark + { + auto starkPrivKey = wallet.getKeyByCurve(TWCurveStarkex, DerivationPath(derivationPath)); + auto starkPubKey = hex(starkPrivKey.getPublicKey(TWPublicKeyTypeStarkex).bytes); + ASSERT_EQ(hex(starkPrivKey.bytes), "06cf0a8bf113352eb863157a45c5e5567abb34f8d32cddafd2c22aa803f4892c"); + ASSERT_EQ(starkPubKey, "02d2bbdc1adaf887b0027cdde2113cfd81c60493aa6dc15d7887ddf1a82bc831"); + } +} + +TEST(HDWallet, FromMnemonicImmutableX) { + // Successfully register the user: https://api.sandbox.x.immutable.com/v1/users/0x1A817D0cC495C8157E4C734c48a1e840473CBCa1 + const auto mnemonic = "owner erupt swamp room swift final allow unaware hint identify figure cotton"; + const auto ethAddress = "0x1A817D0cC495C8157E4C734c48a1e840473CBCa1"; + HDWallet wallet = HDWallet(mnemonic, ""); + auto derivationPath = DerivationPath(Ethereum::accountPathFromAddress(ethAddress, "starkex", "immutablex", "1")); + ASSERT_EQ(derivationPath.string(), "m/2645'/579218131'/211006541'/1195162785'/289656960'/1"); + + // ETH + { + auto ethPrivKey = wallet.getKey(TWCoinTypeEthereum, DerivationPath("m/44'/60'/0'/0/0")); + ASSERT_EQ(hex(ethPrivKey.bytes), "a84f129929f6effe3fd541bcaa8a13d80714cd93c205682bea8b9e0cfc28a2ad"); + auto ethAddressFromPub = Ethereum::Address(ethPrivKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)).string(); + ASSERT_EQ(ethAddressFromPub, ethAddress); + } + + // Stark + { + auto starkPrivKey = wallet.getKeyByCurve(TWCurveStarkex, DerivationPath(derivationPath)); + auto starkPubKey = starkPrivKey.getPublicKey(TWPublicKeyTypeStarkex); + ASSERT_EQ(hex(starkPrivKey.bytes), "02d037bb9c1302295c2f9fa66bcc4ab8e353a3140600a390598777d69c1bc71a"); + ASSERT_EQ(hex(starkPubKey.bytes), "006c061ea4195769058e0e2e14cd747619a866954a412e15fa2241fdf49438cf"); + + auto starkMsg = "28419a504c5b1c83df4fdcbf7f5f36a7d5cfa8148aff2d33aed2f40a64e7ea0"; + auto starkSignature = StarkEx::MessageSigner::signMessage(starkPrivKey, starkMsg); + ASSERT_EQ(starkSignature, "077cae8f00327a2072d3ca8b31725263f61303dc0142a631561d33cb2b4cb221008d659541d59f1589b0e714ddc0a5bee77faddf093f96d529b6c55c0bffd45d"); + ASSERT_TRUE(StarkEx::MessageSigner::verifyMessage(starkPubKey, starkMsg, starkSignature)); + } +} + +TEST(HDWallet, FromMnemonicImmutableXMainnet) { + const auto mnemonic = "ocean seven canyon push fiscal banana music guess arrange edit glance school"; + const auto ethAddress = "0x39E652fE9458D391737058b0dd5eCC6ec910A7dd"; + HDWallet wallet = HDWallet(mnemonic, ""); + auto derivationPath = DerivationPath(Ethereum::accountPathFromAddress(ethAddress, "starkex", "immutablex", "1")); + ASSERT_EQ(derivationPath.string(), "m/2645'/579218131'/211006541'/1225828317'/985503965'/1"); + + // ETH + { + auto ethPrivKey = wallet.getKey(TWCoinTypeEthereum, DerivationPath("m/44'/60'/0'/0/0")); + ASSERT_EQ(hex(ethPrivKey.bytes), "3b0a61f46fdae924007146eacb6db6642de7a5603ad843ec58e10331d89d4b84"); + auto ethAddressFromPub = Ethereum::Address(ethPrivKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)).string(); + ASSERT_EQ(ethAddressFromPub, ethAddress); + + std::string tosign = "Only sign this request if you’ve initiated an action with Immutable X.\n\nFor internal use:\nbd717ba31dca6e0f3f136f7c4197babce5f09a9f25176044c0b3112b1b6017a3"; + auto hexEthSignature = Ethereum::MessageSigner::signMessage(ethPrivKey, tosign, Ethereum::MessageType::ImmutableX); + + ASSERT_EQ(hexEthSignature, "32cd5a58f3419fc5db672e3d57f76199b853eda0856d491b38f557b629b0a0814ace689412bf354a1af81126d2749207dffae8ae8845160f33948a6b787e17ee01"); + } + + // Stark + { + auto starkPrivKey = wallet.getKeyByCurve(TWCurveStarkex, DerivationPath(derivationPath)); + auto starkPubKey = starkPrivKey.getPublicKey(TWPublicKeyTypeStarkex); + ASSERT_EQ(hex(starkPrivKey.bytes), "070128376c2cfd21e7475708049d00c83d7ab65f15368e28730bf1684dee8370"); + ASSERT_EQ(hex(starkPubKey.bytes), "00453ca02b347f80e5ddfc4caf254852fc05b172b37bca8f7e28600631d12dfe"); + + auto starkMsg = "76b66c453cd1b812032ff206a28df59f6abe41e805b9f1c48a1c4afe780756c"; + auto starkSignature = StarkEx::MessageSigner::signMessage(starkPrivKey, starkMsg); + ASSERT_EQ(starkSignature, "070ad88f79650fbdc152affd738d4ec29888bed554ea74f9ad8ca7031ef300b50597f4a62752336db06e6d37dfc18047fdd40804f5fd19cebfda8cac91e4f178"); + ASSERT_TRUE(StarkEx::MessageSigner::verifyMessage(starkPubKey, starkMsg, starkSignature)); + } +} + +TEST(HDWallet, FromMnemonicImmutableXMainnetFromSignature) { + // Successfully register: https://api.x.immutable.com/v1/users/0xd0972E2312518Ca15A2304D56ff9cc0b7ea0Ea37 + const auto mnemonic = "obscure opera favorite shuffle mail tip age debate dirt pact cement loyal"; + const auto ethAddress = "0xd0972E2312518Ca15A2304D56ff9cc0b7ea0Ea37"; + HDWallet wallet = HDWallet(mnemonic, ""); + auto derivationPath = DerivationPath(Ethereum::accountPathFromAddress(ethAddress, "starkex", "immutablex", "1")); + ASSERT_EQ(derivationPath.string(), "m/2645'/579218131'/211006541'/2124474935'/1609799702'/1"); + + // ETH + stark + { + auto ethPrivKey = wallet.getKey(TWCoinTypeEthereum, DerivationPath("m/44'/60'/0'/0/0")); + ASSERT_EQ(hex(ethPrivKey.bytes), "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"); + auto ethAddressFromPub = Ethereum::Address(ethPrivKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)).string(); + ASSERT_EQ(ethAddressFromPub, ethAddress); + auto signature = Ethereum::MessageSigner::signMessage(ethPrivKey, "Only sign this request if you’ve initiated an action with Immutable X.", Ethereum::MessageType::ImmutableX); + ASSERT_EQ(signature, "18b1be8b78807d3326e28bc286d7ee3d068dcd90b1949ce1d25c1f99825f26e70992c5eb7f44f76b202aceded00d74f771ed751f2fe538eec01e338164914fe001"); + auto starkPrivKey = ImmutableX::getPrivateKeyFromRawSignature(parse_hex(signature), DerivationPath(derivationPath)); + auto starkPubKey = starkPrivKey.getPublicKey(TWPublicKeyTypeStarkex); + ASSERT_EQ(hex(starkPrivKey.bytes), "04be51a04e718c202e4dca60c2b72958252024cfc1070c090dd0f170298249de"); + ASSERT_EQ(hex(starkPubKey.bytes), "00e5b9b11f8372610ef35d647a1dcaba1a4010716588d591189b27bf3c2d5095"); + auto signatureToSend = Ethereum::MessageSigner::signMessage(ethPrivKey, "Only sign this key linking request from Immutable X", Ethereum::MessageType::ImmutableX); + ASSERT_EQ(signatureToSend, "646da4160f7fc9205e6f502fb7691a0bf63ecbb74bbb653465cd62388dd9f56325ab1e4a9aba99b1661e3e6251b42822855a71e60017b310b9f90e990a12e1dc01"); + + auto starkMsg = "463a2240432264a3aa71a5713f2a4e4c1b9e12bbb56083cd56af6d878217cf"; + auto starkSignature = StarkEx::MessageSigner::signMessage(starkPrivKey, starkMsg); + ASSERT_EQ(starkSignature, "04cf5f21333dd189ada3c0f2a51430d733501a9b1d5e07905273c1938cfb261e05b6013d74adde403e8953743a338c8d414bb96bf69d2ca1a91a85ed2700a528"); + ASSERT_TRUE(StarkEx::MessageSigner::verifyMessage(starkPubKey, starkMsg, starkSignature)); + } +} + +TEST(HDWallet, StargazeKey) { + const auto derivPath = "m/44'/118'/0'/0/0"; + HDWallet wallet = HDWallet("rude segment two fury you output manual volcano sugar draft elite fame", ""); + { + const auto privateKey = wallet.getKey(TWCoinTypeStargaze, DerivationPath(derivPath)); + EXPECT_EQ(hex(privateKey.bytes), "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433"); + const auto p = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(p.bytes), "02cbfdb5e472893322294e60cf0883d43df431e1089d29ecb447a9e6d55045aae5"); + EXPECT_EQ(Cosmos::Address(TWCoinTypeStargaze ,p).string(), "stars1mry47pkga5tdswtluy0m8teslpalkdq02a8nhy"); + } +} + +TEST(HDWallet, CoreumKey) { + const auto derivPath = "m/44'/990'/0'/0/0"; + HDWallet wallet = HDWallet("rude segment two fury you output manual volcano sugar draft elite fame", ""); + { + const auto privateKey = wallet.getKey(TWCoinTypeCoreum, DerivationPath(derivPath)); + EXPECT_EQ(hex(privateKey.bytes), "56e5e45bf33a779527ec670b5336f6bc78efbe0e3bf1f004e7250673a82a3431"); + const auto p = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(p.bytes), "0345d8d927b955c3cd468d12b5bc634c7919ee4777e578439af6314cf04b2ff114"); + EXPECT_EQ(Cosmos::Address(TWCoinTypeCoreum ,p).string(), "core1a5nvz6smgsph9gephguyhn30fmzrpaxrvvdjun"); + } +} + +TEST(HDWallet, NearKey) { + const auto derivPath = "m/44'/397'/0'"; + HDWallet wallet = HDWallet("owner erupt swamp room swift final allow unaware hint identify figure cotton", ""); + { + const auto privateKey = wallet.getKey(TWCoinTypeNEAR, DerivationPath(derivPath)); + EXPECT_EQ(hex(privateKey.bytes), "35e0d9631bd538d5569266abf6be7a9a403ebfda92ddd49b3268e35360a6c2dd"); + const auto p = privateKey.getPublicKey(TWPublicKeyTypeED25519); + EXPECT_EQ(hex(p.bytes), "b8d5df25047841365008f30fb6b30dd820e9a84d869f05623d114e96831f2fbf"); + EXPECT_EQ(NEAR::Address(p).string(), "b8d5df25047841365008f30fb6b30dd820e9a84d869f05623d114e96831f2fbf"); + } +} + +TEST(HDWallet, IoTexEvmKeys) { + const auto derivPath = "m/44'/304'/0'/0/0"; + HDWallet wallet = HDWallet("token major laundry actor dish lunch physical machine kingdom adapt gym true", ""); + { + const auto privateKey = wallet.getKey(TWCoinTypeEthereum, DerivationPath(derivPath)); + EXPECT_EQ(hex(privateKey.bytes), "3aa86eafa99cb9ae0f7c1c4f06391ffbef91578169715dfbdcdf76b532b73f24"); + const auto p = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ(hex(p.bytes), "042be00e86db75bbe3e8defe9bb09fbd5444eea10e2d53d55468f3d25bf3b0cb3ea8d992baba30c9353584b8ff061f8585cae1c792b8bb6f0607750dbf4fe8c760"); + EXPECT_EQ(Ethereum::Address(p).string(), "0x6b3FBEDcB9E106e84c3a47f63cf96Df8500bBc22"); + } +} + +TEST(HDWallet, IoTexKeys) { + const auto derivPath = "m/44'/304'/0'/0/0"; + HDWallet wallet = HDWallet("token major laundry actor dish lunch physical machine kingdom adapt gym true", ""); + { + const auto privateKey = wallet.getKey(TWCoinTypeIoTeX, DerivationPath(derivPath)); + EXPECT_EQ(hex(privateKey.bytes), "3aa86eafa99cb9ae0f7c1c4f06391ffbef91578169715dfbdcdf76b532b73f24"); + const auto p = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ(hex(p.bytes), "042be00e86db75bbe3e8defe9bb09fbd5444eea10e2d53d55468f3d25bf3b0cb3ea8d992baba30c9353584b8ff061f8585cae1c792b8bb6f0607750dbf4fe8c760"); + EXPECT_EQ(IoTeX::Address(p).string(), "io1dvlmah9euyrwsnp6glmre7tdlpgqh0pzz542zd"); + } + // io1qmkv62pvg56qkashkwauhhjv3gtjhcm889r8dc +} + +} // namespace TW::HDWalletTests diff --git a/tests/HDWallet/bip39_vectors.json b/tests/common/HDWallet/bip39_vectors.json similarity index 100% rename from tests/HDWallet/bip39_vectors.json rename to tests/common/HDWallet/bip39_vectors.json diff --git a/tests/HashTests.cpp b/tests/common/HashTests.cpp similarity index 94% rename from tests/HashTests.cpp rename to tests/common/HashTests.cpp index c60acec946c..f1c1229e377 100644 --- a/tests/HashTests.cpp +++ b/tests/common/HashTests.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Hash.h" #include "HexCoding.h" diff --git a/tests/common/HexCodingTests.cpp b/tests/common/HexCodingTests.cpp new file mode 100644 index 00000000000..db49b1b7376 --- /dev/null +++ b/tests/common/HexCodingTests.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "HexCoding.h" +#include "Data.h" +#include "uint256.h" +#include + +namespace TW { + +TEST(HexCoding, validation) { + const std::string valid = "0x7d8bf18c7ce84b3e175b339c4ca93aed1dd166f1"; + const std::string invalid = "0xMQqpqMQgCBuiPkoXfgZZsJvuzCeI1zc00z6vHJj4"; + const auto bytes = parse_hex(invalid); + const auto bytes2 = parse_hex(valid); + + ASSERT_TRUE(bytes.empty()); + ASSERT_EQ("0x" + hex(bytes2), valid); +} + +TEST(HexCoding, OddLength) { + const std::string oddHex = "0x28fa6ae00"; + const auto bytes = parse_hex(oddHex, true); + const auto number = load(bytes); + ASSERT_EQ(number, 11000000000); +} + +TEST(HexCoding, isHexEncoded) { + ASSERT_TRUE(is_hex_encoded("66fbe3c5c03bf5c82792f904c9f8bf28894a6aa3d213d41c20569b654aadedb3")); + ASSERT_TRUE(is_hex_encoded("0x66fbe3c5c03bf5c82792f904c9f8bf28894a6aa3d213d41c20569b654aadedb3")); + ASSERT_FALSE(is_hex_encoded("1x66fbe3c5c03bf5c82792f904c9f8bf28894a6aa3d213d41c20569b654aadedb3")); + ASSERT_FALSE(is_hex_encoded("0xyahoo")); +} + +} diff --git a/tests/Keystore/Data/empty-accounts.json b/tests/common/Keystore/Data/empty-accounts.json similarity index 100% rename from tests/Keystore/Data/empty-accounts.json rename to tests/common/Keystore/Data/empty-accounts.json diff --git a/tests/Keystore/Data/ethereum-wallet-address-no-0x.json b/tests/common/Keystore/Data/ethereum-wallet-address-no-0x.json similarity index 100% rename from tests/Keystore/Data/ethereum-wallet-address-no-0x.json rename to tests/common/Keystore/Data/ethereum-wallet-address-no-0x.json diff --git a/tests/Keystore/Data/key.json b/tests/common/Keystore/Data/key.json similarity index 100% rename from tests/Keystore/Data/key.json rename to tests/common/Keystore/Data/key.json diff --git a/tests/Keystore/Data/key_bitcoin.json b/tests/common/Keystore/Data/key_bitcoin.json similarity index 100% rename from tests/Keystore/Data/key_bitcoin.json rename to tests/common/Keystore/Data/key_bitcoin.json diff --git a/tests/Keystore/Data/legacy-mnemonic.json b/tests/common/Keystore/Data/legacy-mnemonic.json similarity index 100% rename from tests/Keystore/Data/legacy-mnemonic.json rename to tests/common/Keystore/Data/legacy-mnemonic.json diff --git a/tests/Keystore/Data/legacy-private-key.json b/tests/common/Keystore/Data/legacy-private-key.json similarity index 100% rename from tests/Keystore/Data/legacy-private-key.json rename to tests/common/Keystore/Data/legacy-private-key.json diff --git a/tests/Keystore/Data/livepeer.json b/tests/common/Keystore/Data/livepeer.json similarity index 100% rename from tests/Keystore/Data/livepeer.json rename to tests/common/Keystore/Data/livepeer.json diff --git a/tests/Keystore/Data/missing-address.json b/tests/common/Keystore/Data/missing-address.json similarity index 100% rename from tests/Keystore/Data/missing-address.json rename to tests/common/Keystore/Data/missing-address.json diff --git a/tests/Keystore/Data/myetherwallet.uu b/tests/common/Keystore/Data/myetherwallet.uu similarity index 100% rename from tests/Keystore/Data/myetherwallet.uu rename to tests/common/Keystore/Data/myetherwallet.uu diff --git a/tests/Keystore/Data/pbkdf2.json b/tests/common/Keystore/Data/pbkdf2.json similarity index 100% rename from tests/Keystore/Data/pbkdf2.json rename to tests/common/Keystore/Data/pbkdf2.json diff --git a/tests/Keystore/Data/wallet.json b/tests/common/Keystore/Data/wallet.json similarity index 100% rename from tests/Keystore/Data/wallet.json rename to tests/common/Keystore/Data/wallet.json diff --git a/tests/Keystore/Data/watch.json b/tests/common/Keystore/Data/watch.json similarity index 100% rename from tests/Keystore/Data/watch.json rename to tests/common/Keystore/Data/watch.json diff --git a/tests/Keystore/Data/web3j.json b/tests/common/Keystore/Data/web3j.json similarity index 100% rename from tests/Keystore/Data/web3j.json rename to tests/common/Keystore/Data/web3j.json diff --git a/tests/DerivationPathTests.cpp b/tests/common/Keystore/DerivationPathTests.cpp similarity index 83% rename from tests/DerivationPathTests.cpp rename to tests/common/Keystore/DerivationPathTests.cpp index 4ed6493387a..178c9072f54 100644 --- a/tests/DerivationPathTests.cpp +++ b/tests/common/Keystore/DerivationPathTests.cpp @@ -1,11 +1,9 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "DerivationPath.h" #include "Coin.h" +#include "DerivationPath.h" #include #include @@ -14,28 +12,28 @@ namespace TW { TEST(DerivationPath, InitWithIndices) { const auto path = DerivationPath(TWPurposeBIP44, TWCoinTypeSlip44Id(TWCoinTypeEthereum), 0, 0, 0); - ASSERT_EQ(path.indices[0], DerivationPathIndex(44, /* hardened: */true)); - ASSERT_EQ(path.indices[1], DerivationPathIndex(60, /* hardened: */true)); - ASSERT_EQ(path.indices[2], DerivationPathIndex(0, /* hardened: */true)); - ASSERT_EQ(path.indices[3], DerivationPathIndex(0, /* hardened: */false)); - ASSERT_EQ(path.indices[4], DerivationPathIndex(0, /* hardened: */false)); + ASSERT_EQ(path.indices[0], DerivationPathIndex(44, /* hardened: */ true)); + ASSERT_EQ(path.indices[1], DerivationPathIndex(60, /* hardened: */ true)); + ASSERT_EQ(path.indices[2], DerivationPathIndex(0, /* hardened: */ true)); + ASSERT_EQ(path.indices[3], DerivationPathIndex(0, /* hardened: */ false)); + ASSERT_EQ(path.indices[4], DerivationPathIndex(0, /* hardened: */ false)); } TEST(DerivationPath, InitWithString) { ASSERT_NO_THROW(DerivationPath("m/44'/60'/0'/0/0")); const auto path = DerivationPath("m/44'/60'/0'/0/0"); - ASSERT_EQ(path.indices[0], DerivationPathIndex(44, /* hardened: */true)); - ASSERT_EQ(path.indices[1], DerivationPathIndex(60, /* hardened: */true)); - ASSERT_EQ(path.indices[2], DerivationPathIndex(0, /* hardened: */true)); - ASSERT_EQ(path.indices[3], DerivationPathIndex(0, /* hardened: */false)); - ASSERT_EQ(path.indices[4], DerivationPathIndex(0, /* hardened: */false)); + ASSERT_EQ(path.indices[0], DerivationPathIndex(44, /* hardened: */ true)); + ASSERT_EQ(path.indices[1], DerivationPathIndex(60, /* hardened: */ true)); + ASSERT_EQ(path.indices[2], DerivationPathIndex(0, /* hardened: */ true)); + ASSERT_EQ(path.indices[3], DerivationPathIndex(0, /* hardened: */ false)); + ASSERT_EQ(path.indices[4], DerivationPathIndex(0, /* hardened: */ false)); ASSERT_EQ(path.purpose(), 44); - ASSERT_EQ(path.coin(), 60); - ASSERT_EQ(path.account(), 0); - ASSERT_EQ(path.change(), 0); - ASSERT_EQ(path.address(), 0); + ASSERT_EQ(path.coin(), 60ul); + ASSERT_EQ(path.account(), 0ul); + ASSERT_EQ(path.change(), 0ul); + ASSERT_EQ(path.address(), 0ul); } TEST(DerivationPath, InitInvalid) { @@ -46,13 +44,13 @@ TEST(DerivationPath, InitInvalid) { TEST(DerivationPath, IndexOutOfBounds) { DerivationPath path; - EXPECT_EQ(path.indices.size(), 0); + EXPECT_EQ(path.indices.size(), 0ul); EXPECT_EQ(path.purpose(), TWPurposeBIP44); EXPECT_EQ(path.coin(), TWCoinTypeBitcoin); - EXPECT_EQ(path.account(), 0); - EXPECT_EQ(path.change(), 0); - EXPECT_EQ(path.address(), 0); + EXPECT_EQ(path.account(), 0ul); + EXPECT_EQ(path.change(), 0ul); + EXPECT_EQ(path.address(), 0ul); ASSERT_NO_THROW(path.setPurpose(TWPurposeBIP44)); ASSERT_NO_THROW(path.setCoin(TWCoinTypeBitcoin)); @@ -94,4 +92,4 @@ TEST(Derivation, alternativeDerivation) { EXPECT_EQ(std::string(TW::derivationName(TWCoinTypeSolana, TWDerivationSolanaSolana)), "solana"); } -} // namespace +} // namespace TW diff --git a/tests/common/Keystore/StoredKeyTests.cpp b/tests/common/Keystore/StoredKeyTests.cpp new file mode 100644 index 00000000000..ba189cfe5a1 --- /dev/null +++ b/tests/common/Keystore/StoredKeyTests.cpp @@ -0,0 +1,709 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Keystore/StoredKey.h" + +#include "Bitcoin/Address.h" +#include "Coin.h" +#include "Data.h" +#include "HexCoding.h" +#include "Mnemonic.h" +#include "PrivateKey.h" + +#include +#include + +extern std::string TESTS_ROOT; + +namespace TW::Keystore::tests { + +using namespace std; + +const auto passwordString = "password"; +const auto gPassword = TW::data(string(passwordString)); +const auto gMnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; +const TWCoinType coinTypeBc = TWCoinTypeBitcoin; +const TWCoinType coinTypeBnb = TWCoinTypeBinance; +const TWCoinType coinTypeBsc = TWCoinTypeSmartChain; +const TWCoinType coinTypeEth = TWCoinTypeEthereum; +const TWCoinType coinTypeBscLegacy = TWCoinTypeSmartChainLegacy; + +const std::string testDataPath(const char* subpath) { + return TESTS_ROOT + "/common/Keystore/Data/" + subpath; +} + +TEST(StoredKey, CreateWithMnemonic) { + auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 0ul); + EXPECT_EQ(key.wallet(gPassword).getMnemonic(), string(gMnemonic)); + + const auto json = key.json(); + EXPECT_EQ(json["name"], "name"); + EXPECT_EQ(json["type"], "mnemonic"); + EXPECT_EQ(json["version"], 3); +} + +TEST(StoredKey, CreateWithMnemonicInvalid) { + try { + auto key = StoredKey::createWithMnemonic("name", gPassword, "_THIS_IS_NOT_A_VALID_MNEMONIC_", TWStoredKeyEncryptionLevelDefault); + } catch (std::invalid_argument&) { + // expedcted exception OK + return; + } + FAIL() << "Missing excpected excpetion"; +} + +TEST(StoredKey, CreateWithMnemonicRandom) { + const auto key = StoredKey::createWithMnemonicRandom("name", gPassword, TWStoredKeyEncryptionLevelDefault); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + // random mnemonic: check only length and validity + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_TRUE(mnemo2Data.size() >= 36); + EXPECT_TRUE(Mnemonic::isValid(string(mnemo2Data.begin(), mnemo2Data.end()))); + EXPECT_EQ(key.accounts.size(), 0ul); +} + +TEST(StoredKey, CreateWithMnemonicAddDefaultAddress) { + auto key = StoredKey::createWithMnemonicAddDefaultAddress("name", gPassword, gMnemonic, coinTypeBc); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + + const Data& mnemo2Data = key.payload.decrypt(gPassword); + + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 1ul); + EXPECT_EQ(key.accounts[0].coin, coinTypeBc); + EXPECT_EQ(key.accounts[0].address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + EXPECT_EQ(key.accounts[0].publicKey, "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"); + EXPECT_EQ(key.accounts[0].extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); + EXPECT_EQ(hex(key.privateKey(coinTypeBc, gPassword).bytes), "d2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f"); +} + +TEST(StoredKey, CreateWithMnemonicAddDefaultAddressAes256) { + auto key = StoredKey::createWithMnemonicAddDefaultAddress("name", gPassword, gMnemonic, coinTypeBc, TWStoredKeyEncryptionAes256Ctr); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + auto header = key.payload; + EXPECT_EQ(header.params.cipher(), "aes-256-ctr"); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 1ul); + EXPECT_EQ(key.accounts[0].coin, coinTypeBc); + EXPECT_EQ(key.accounts[0].address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + EXPECT_EQ(key.accounts[0].publicKey, "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"); + EXPECT_EQ(key.accounts[0].extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); + EXPECT_EQ(hex(key.privateKey(coinTypeBc, gPassword).bytes), "d2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f"); +} + +TEST(StoredKey, CreateWithPrivateKeyAddDefaultAddress) { + const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); + auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", gPassword, coinTypeBc, privateKey); + EXPECT_EQ(key.type, StoredKeyType::privateKey); + EXPECT_EQ(key.accounts.size(), 1ul); + EXPECT_EQ(key.accounts[0].coin, coinTypeBc); + EXPECT_EQ(key.accounts[0].address, "bc1q375sq4kl2nv0mlmup3vm8znn4eqwu7mt6hkwhr"); + EXPECT_EQ(hex(key.privateKey(coinTypeBc, gPassword).bytes), hex(privateKey)); + + const auto json = key.json(); + EXPECT_EQ(json["name"], "name"); + EXPECT_EQ(json["type"], "private-key"); + EXPECT_EQ(json["version"], 3); +} + +TEST(StoredKey, CreateWithPrivateKeyAddDefaultAddressAes256) { + const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); + auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", gPassword, coinTypeBc, privateKey, TWStoredKeyEncryptionAes256Ctr); + auto header = key.payload; + EXPECT_EQ(header.params.cipher(), "aes-256-ctr"); + EXPECT_EQ(key.type, StoredKeyType::privateKey); + EXPECT_EQ(key.accounts.size(), 1ul); + EXPECT_EQ(key.accounts[0].coin, coinTypeBc); + EXPECT_EQ(key.accounts[0].address, "bc1q375sq4kl2nv0mlmup3vm8znn4eqwu7mt6hkwhr"); + EXPECT_EQ(hex(key.privateKey(coinTypeBc, gPassword).bytes), hex(privateKey)); + + const auto json = key.json(); + EXPECT_EQ(json["name"], "name"); + EXPECT_EQ(json["type"], "private-key"); + EXPECT_EQ(json["version"], 3); +} + +TEST(StoredKey, CreateWithPrivateKeyAddDefaultAddressInvalid) { + try { + const auto privateKeyInvalid = parse_hex("0001020304"); + auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", gPassword, coinTypeBc, privateKeyInvalid); + } catch (std::invalid_argument&) { + // expected exception ok + return; + } + FAIL() << "Missing expected exception"; +} + +TEST(StoredKey, AccountGetCreate) { + auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault); + EXPECT_EQ(key.accounts.size(), 0ul); + + // not exists + EXPECT_FALSE(key.account(coinTypeBc).has_value()); + EXPECT_EQ(key.accounts.size(), 0ul); + + auto wallet = key.wallet(gPassword); + // not exists, wallet null, not create + EXPECT_FALSE(key.account(coinTypeBc, nullptr).has_value()); + EXPECT_EQ(key.accounts.size(), 0ul); + + // not exists, wallet nonnull, create + std::optional acc3 = key.account(coinTypeBc, &wallet); + EXPECT_TRUE(acc3.has_value()); + EXPECT_EQ(acc3->coin, coinTypeBc); + EXPECT_EQ(key.accounts.size(), 1ul); + + // exists + std::optional acc4 = key.account(coinTypeBc); + EXPECT_TRUE(acc4.has_value()); + EXPECT_EQ(acc4->coin, coinTypeBc); + EXPECT_EQ(key.accounts.size(), 1ul); + + // exists, wallet nonnull, not create + std::optional acc5 = key.account(coinTypeBc, &wallet); + EXPECT_TRUE(acc5.has_value()); + EXPECT_EQ(acc5->coin, coinTypeBc); + EXPECT_EQ(key.accounts.size(), 1ul); + + // exists, wallet null, not create + std::optional acc6 = key.account(coinTypeBc, nullptr); + EXPECT_TRUE(acc6.has_value()); + EXPECT_EQ(acc6->coin, coinTypeBc); + EXPECT_EQ(key.accounts.size(), 1ul); +} + +TEST(StoredKey, AccountGetDoesntChange) { + auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault); + auto wallet = key.wallet(gPassword); + EXPECT_EQ(key.accounts.size(), 0ul); + + vector coins = {coinTypeBc, coinTypeEth, coinTypeBnb}; + // retrieve multiple accounts, which will be created + vector accounts; + for (auto coin : coins) { + std::optional account = key.account(coin, &wallet); + accounts.push_back(*account); + + // check + ASSERT_TRUE(account.has_value()); + EXPECT_EQ(account->coin, coin); + } + + // Check again; make sure returned references don't change + for (auto i = 0ul; i < accounts.size(); ++i) { + // check + EXPECT_EQ(accounts[i].coin, coins[i]); + } +} + +TEST(StoredKey, AddRemoveAccount) { + auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault); + EXPECT_EQ(key.accounts.size(), 0ul); + + { + const auto derivationPath = DerivationPath("m/84'/0'/0'/0/0"); + key.addAccount("bc1qaucw06s3agez8tyyk4zj9kt0q2934e3mcewdpf", coinTypeBc, TWDerivationDefault, derivationPath, "", "zpub6rxtad3SPT1C5GUDjPiKQ5oJN5DBeMbdUR7LrdYt12VbU7TBSpGUkdLvfVYGuj1N5edkDoZ3bu1fdN1HprQYfCBdsSH5CaAAygHGsanwtTe"); + EXPECT_EQ(key.accounts.size(), 1ul); + } + { + const auto derivationPath = DerivationPath("m/714'/0'/0'/0/0"); + key.addAccount("bnb1utrnnjym7ustgw7pgyvtmnxay4qmt3ahh276nu", coinTypeBnb, TWDerivationDefault, derivationPath, "", ""); + key.addAccount("0x23b02dC8f67eD6cF8DCa47935791954286ffe7c9", coinTypeBsc, TWDerivationDefault, derivationPath, "", ""); + EXPECT_EQ(key.accounts.size(), 3ul); + } + { + const auto derivationPath = DerivationPath("m/60'/0'/0'/0/0"); + key.addAccount("0xC0d97f61A84A0708225F15d54978D628Fe2C5E62", coinTypeEth, TWDerivationDefault, derivationPath, "", ""); + key.addAccount("0xC0d97f61A84A0708225F15d54978D628Fe2C5E62", coinTypeBscLegacy, TWDerivationDefault, derivationPath, "", ""); + EXPECT_EQ(key.accounts.size(), 5ul); + } + + key.removeAccount(coinTypeBc); + key.removeAccount(coinTypeBnb); + key.removeAccount(coinTypeBsc); + key.removeAccount(coinTypeEth); + key.removeAccount(coinTypeBscLegacy); + EXPECT_EQ(key.accounts.size(), 0ul); +} + +TEST(StoredKey, AddRemoveAccountDerivation) { + auto key = StoredKey::createWithMnemonic("name", Data(), gMnemonic, TWStoredKeyEncryptionLevelDefault); + EXPECT_EQ(key.accounts.size(), 0ul); + + const auto derivationPath = DerivationPath("m/84'/0'/0'/0/0"); + { + key.addAccount("bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny", coinTypeBc, TWDerivationDefault, derivationPath, "", "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); + EXPECT_EQ(key.accounts.size(), 1ul); + } + { + key.addAccount("1NyRyFewhZcWMa9XCj3bBxSXPXyoSg8dKz", coinTypeBc, TWDerivationBitcoinLegacy, derivationPath, "", "xpub6CR52eaUuVb4kXAVyHC2i5ZuqJ37oWNPZFtjXaazFPXZD45DwWBYEBLdrF7fmCR9pgBuCA9Q57zZfyJjDUBDNtWkhWuGHNYKLgDHpqrHsxV"); + EXPECT_EQ(key.accounts.size(), 2ul); + } + + key.removeAccount(coinTypeBc, TWDerivationDefault); + EXPECT_EQ(key.accounts.size(), 1ul); + key.removeAccount(coinTypeBc, TWDerivationDefault); // try 2nd time + EXPECT_EQ(key.accounts.size(), 1ul); + key.removeAccount(coinTypeBc, TWDerivationBitcoinLegacy); + EXPECT_EQ(key.accounts.size(), 0ul); + key.removeAccount(coinTypeBc, TWDerivationBitcoinLegacy); // try 2nd time + EXPECT_EQ(key.accounts.size(), 0ul); +} + +TEST(StoredKey, AddRemoveAccountDerivationPath) { + auto key = StoredKey::createWithMnemonic("name", Data(), gMnemonic, TWStoredKeyEncryptionLevelDefault); + EXPECT_EQ(key.accounts.size(), 0ul); + + const auto derivationPath0 = DerivationPath("m/84'/0'/0'/0/0"); + { + key.addAccount("bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny", coinTypeBc, TWDerivationDefault, derivationPath0, "", "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); + EXPECT_EQ(key.accounts.size(), 1ul); + } + const auto derivationPath1 = DerivationPath("m/84'/0'/0'/1/0"); + { + key.addAccount("bc1qumuzptwdr6jlsqum8jnzz80rdg8nx6x29m2qpu", coinTypeBc, TWDerivationDefault, derivationPath1, "", "zpub6rxtad3SPT1C5GUDjPiKQ5oJN5DBeMbdUR7LrdYt12VbU7TBSpGUkdLvfVYGuj1N5edkDoZ3bu1fdN1HprQYfCBdsSH5CaAAygHGsanwtTe"); + EXPECT_EQ(key.accounts.size(), 2ul); + } + + key.removeAccount(coinTypeBc, derivationPath0); + EXPECT_EQ(key.accounts.size(), 1ul); + key.removeAccount(coinTypeBc, derivationPath0); // try 2nd time + EXPECT_EQ(key.accounts.size(), 1ul); + key.removeAccount(coinTypeBc, derivationPath1); + EXPECT_EQ(key.accounts.size(), 0ul); + key.removeAccount(coinTypeBc, derivationPath1); // try 2nd time + EXPECT_EQ(key.accounts.size(), 0ul); +} + +TEST(StoredKey, FixAddress) { + { + auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault); + key.fixAddresses(gPassword); + } + { + const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); + auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", gPassword, coinTypeBc, privateKey); + key.fixAddresses(gPassword); + } +} + +TEST(StoredKey, WalletInvalid) { + const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); + auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", gPassword, coinTypeBc, privateKey); + try { + auto wallet = key.wallet(gPassword); + } catch (std::invalid_argument&) { + // expected exception ok + return; + } + FAIL() << "Missing expected exception"; +} + +TEST(StoredKey, LoadNonexistent) { + ASSERT_THROW(StoredKey::load(testDataPath("nonexistent.json")), invalid_argument); +} + +TEST(StoredKey, LoadLegacyPrivateKey) { + const auto key = StoredKey::load(testDataPath("legacy-private-key.json")); + EXPECT_EQ(key.type, StoredKeyType::privateKey); + EXPECT_EQ(key.id, "3051ca7d-3d36-4a4a-acc2-09e9083732b0"); + EXPECT_EQ(key.accounts[0].coin, TWCoinTypeEthereum); + EXPECT_EQ(hex(key.payload.decrypt(TW::data("testpassword"))), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); +} + +TEST(StoredKey, LoadLivepeerKey) { + const auto key = StoredKey::load(testDataPath("livepeer.json")); + EXPECT_EQ(key.type, StoredKeyType::privateKey); + EXPECT_EQ(key.id, "70ea3601-ee21-4e94-a7e4-66255a987d22"); + EXPECT_EQ(key.accounts[0].coin, TWCoinTypeEthereum); + EXPECT_EQ(hex(key.payload.decrypt(TW::data("Radchenko"))), "09b4379d9a41a71d94ee36357bccb4d77b45e7fd9307e2c0f673dd54c0558c73"); +} + +TEST(StoredKey, LoadPBKDF2Key) { + const auto key = StoredKey::load(testDataPath("pbkdf2.json")); + EXPECT_EQ(key.type, StoredKeyType::privateKey); + EXPECT_EQ(key.id, "3198bc9c-6672-5ab3-d995-4942343ae5b6"); + + const auto& payload = key.payload; + ASSERT_TRUE(std::holds_alternative(payload.params.kdfParams)); + EXPECT_EQ(std::get(payload.params.kdfParams).desiredKeyLength, 32ul); + EXPECT_EQ(std::get(payload.params.kdfParams).iterations, 262144ul); + EXPECT_EQ(hex(std::get(payload.params.kdfParams).salt), "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"); + + EXPECT_EQ(hex(payload.decrypt(TW::data("testpassword"))), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); + + auto j = std::get(payload.params.kdfParams).json(); + auto expected = R"|({"c":262144,"dklen":32,"salt":"ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"})|"; + EXPECT_EQ(j.dump(), expected); +} + +TEST(StoredKey, RandomPBKDF2Param) { + auto p = PBKDF2Parameters(); + ASSERT_TRUE(p.salt.size() == 32ul); +} + +TEST(StoredKey, LoadLegacyMnemonic) { + const auto key = StoredKey::load(testDataPath("legacy-mnemonic.json")); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + EXPECT_EQ(key.id, "629aad29-0b22-488e-a0e7-b4219d4f311c"); + + const auto data = key.payload.decrypt(gPassword); + const auto mnemonic = string(reinterpret_cast(data.data())); + EXPECT_EQ(mnemonic, "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn back"); + + EXPECT_EQ(key.accounts[0].coin, TWCoinTypeEthereum); + EXPECT_EQ(key.accounts[0].derivationPath.string(), "m/44'/60'/0'/0/0"); + EXPECT_EQ(key.accounts[0].address, ""); + EXPECT_EQ(key.accounts[1].coin, coinTypeBc); + EXPECT_EQ(key.accounts[1].derivationPath.string(), "m/84'/0'/0'/0/0"); + EXPECT_EQ(key.accounts[1].address, ""); + EXPECT_EQ(key.accounts[1].extendedPublicKey, "zpub6r97AegwVxVbJeuDAWP5KQgX5y4Q6KyFUrsFQRn8yzSXrnmpwg1ZKHSWwECR1Kiqgr4h93WN5kdS48KC6hVFniuZHqVFXjULZZkCwurqyPn"); +} + +TEST(StoredKey, LoadFromWeb3j) { + const auto key = StoredKey::load(testDataPath("web3j.json")); + EXPECT_EQ(key.type, StoredKeyType::privateKey); + EXPECT_EQ(key.id, "86066d8c-8dba-4d81-afd4-934e2a2b72a2"); + const auto password = parse_hex("2d6eefbfbd4622efbfbdefbfbd516718efbfbdefbfbdefbfbdefbfbd59efbfbd30efbfbdefbfbd3a4348efbfbd2aefbfbdefbfbd49efbfbd27efbfbd0638efbfbdefbfbdefbfbd4cefbfbd6befbfbdefbfbd6defbfbdefbfbd63efbfbd5aefbfbd61262b70efbfbdefbfbdefbfbdefbfbdefbfbdc7aa373163417cefbfbdefbfbdefbfbd44efbfbdefbfbd1d10efbfbdefbfbdefbfbd61dc9e5b124befbfbd11efbfbdefbfbd2fefbfbdefbfbd3d7c574868efbfbdefbfbdefbfbd37043b7b5c1a436471592f02efbfbd18efbfbdefbfbd2befbfbdefbfbd7218efbfbd6a68efbfbdcb8e5f3328773ec48174efbfbd67efbfbdefbfbdefbfbdefbfbdefbfbd2a31efbfbd7f60efbfbdd884efbfbd57efbfbd25efbfbd590459efbfbd37efbfbd2bdca20fefbfbdefbfbdefbfbdefbfbd39450113efbfbdefbfbdefbfbd454671efbfbdefbfbdd49fefbfbd47efbfbdefbfbdefbfbdefbfbd00efbfbdefbfbdefbfbdefbfbd05203f4c17712defbfbd7bd1bbdc967902efbfbdc98a77efbfbd707a36efbfbd12efbfbdefbfbd57c78cefbfbdefbfbdefbfbd10efbfbdefbfbdefbfbde1a1bb08efbfbdefbfbd26efbfbdefbfbd58efbfbdefbfbdc4b1efbfbd295fefbfbd0eefbfbdefbfbdefbfbd0e6eefbfbd"); + const auto data = key.payload.decrypt(password); + EXPECT_EQ(hex(data), "043c5429c7872502531708ec0d821c711691402caf37ef7ba78a8c506f10653b"); +} + +TEST(StoredKey, ReadWallet) { + const auto key = StoredKey::load(testDataPath("key.json")); + + EXPECT_EQ(key.type, StoredKeyType::privateKey); + EXPECT_EQ(key.id, "e13b209c-3b2f-4327-bab0-3bef2e51630d"); + EXPECT_EQ(key.name, "Test Account"); + + const auto header = key.payload; + + EXPECT_EQ(header.params.cipher(), "aes-128-ctr"); + EXPECT_EQ(hex(header.encrypted), "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c"); + EXPECT_EQ(hex(header._mac), "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"); + EXPECT_EQ(hex(header.params.cipherParams.iv), "83dbcc02d8ccb40e466191a123791e0e"); + + ASSERT_TRUE(std::holds_alternative(header.params.kdfParams)); + EXPECT_EQ(std::get(header.params.kdfParams).desiredKeyLength, 32ul); + EXPECT_EQ(std::get(header.params.kdfParams).n, 262144ul); + EXPECT_EQ(std::get(header.params.kdfParams).p, 8ul); + EXPECT_EQ(std::get(header.params.kdfParams).r, 1ul); + EXPECT_EQ(hex(std::get(header.params.kdfParams).salt), "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"); +} + +TEST(StoredKey, ReadMyEtherWallet) { + ASSERT_NO_THROW(StoredKey::load(testDataPath("myetherwallet.uu"))); +} + +TEST(StoredKey, InvalidPassword) { + const auto key = StoredKey::load(testDataPath("key.json")); + + ASSERT_THROW(key.payload.decrypt(gPassword), DecryptionError); +} + +TEST(StoredKey, EmptyAccounts) { + const auto key = StoredKey::load(testDataPath("empty-accounts.json")); + + ASSERT_NO_THROW(key.payload.decrypt(TW::data("testpassword"))); +} + +TEST(StoredKey, Decrypt) { + const auto key = StoredKey::load(testDataPath("key.json")); + const auto privateKey = key.payload.decrypt(TW::data("testpassword")); + + EXPECT_EQ(hex(privateKey), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); +} + +TEST(StoredKey, CreateWallet) { + const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); + const auto key = StoredKey::createWithPrivateKey("name", gPassword, privateKey); + const auto decrypted = key.payload.decrypt(gPassword); + + EXPECT_EQ(hex(decrypted), hex(privateKey)); +} + +TEST(StoredKey, CreateAccounts) { + string mnemonicPhrase = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; + auto key = StoredKey::createWithMnemonic("name", gPassword, mnemonicPhrase, TWStoredKeyEncryptionLevelDefault); + const auto wallet = key.wallet(gPassword); + + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->address, "0x494f60cb6Ac2c8F5E1393aD9FdBdF4Ad589507F7"); + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->publicKey, "04cc32a479080d83fdcf69966713f0aad1bc1dc3ecf873b034894e84259841bc1c9b122717803e68905220ff54952d3f5ea2ab2698ca31f843addf94ae73fae9fd"); + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->extendedPublicKey, ""); + + EXPECT_EQ(key.account(coinTypeBc, &wallet)->address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + EXPECT_EQ(key.account(coinTypeBc, &wallet)->publicKey, "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"); + EXPECT_EQ(key.account(coinTypeBc, &wallet)->extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); +} + +TEST(StoredKey, CreateAccountsAes256) { + string mnemonicPhrase = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; + auto key = StoredKey::createWithMnemonic("name", gPassword, mnemonicPhrase, TWStoredKeyEncryptionLevelDefault, TWStoredKeyEncryptionAes256Ctr); + auto header = key.payload; + const auto wallet = key.wallet(gPassword); + + EXPECT_EQ(header.params.cipher(), "aes-256-ctr"); + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->address, "0x494f60cb6Ac2c8F5E1393aD9FdBdF4Ad589507F7"); + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->publicKey, "04cc32a479080d83fdcf69966713f0aad1bc1dc3ecf873b034894e84259841bc1c9b122717803e68905220ff54952d3f5ea2ab2698ca31f843addf94ae73fae9fd"); + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->extendedPublicKey, ""); + + EXPECT_EQ(key.account(coinTypeBc, &wallet)->address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + EXPECT_EQ(key.account(coinTypeBc, &wallet)->publicKey, "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"); + EXPECT_EQ(key.account(coinTypeBc, &wallet)->extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); +} + +TEST(StoredKey, DecodingEthereumAddress) { + const auto key = StoredKey::load(testDataPath("key.json")); + + EXPECT_EQ(key.accounts[0].address, "0x008AeEda4D805471dF9b2A5B0f38A0C3bCBA786b"); +} + +TEST(StoredKey, DecodingBitcoinAddress) { + const auto key = StoredKey::load(testDataPath("key_bitcoin.json")); + + EXPECT_EQ(key.accounts[0].address, "3PWazDi9n1Hfyq9gXFxDxzADNL8RNYyK2y"); +} + +TEST(StoredKey, RemoveAccount) { + auto key = StoredKey::load(testDataPath("legacy-mnemonic.json")); + EXPECT_EQ(key.accounts.size(), 2ul); + key.removeAccount(TWCoinTypeEthereum); + EXPECT_EQ(key.accounts.size(), 1ul); + EXPECT_EQ(key.accounts[0].coin, coinTypeBc); +} + +TEST(StoredKey, MissingAddressFix) { + auto key = StoredKey::load(testDataPath("missing-address.json")); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + + const auto wallet = key.wallet(gPassword); + EXPECT_EQ(wallet.getMnemonic(), "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"); + EXPECT_TRUE(Mnemonic::isValid(wallet.getMnemonic())); + + EXPECT_EQ(key.account(TWCoinTypeBitcoin)->address, ""); + EXPECT_EQ(key.account(TWCoinTypeEthereum)->address, ""); + + key.fixAddresses(gPassword); + + EXPECT_EQ(key.account(TWCoinTypeEthereum, nullptr)->address, "0xA3Dcd899C0f3832DFDFed9479a9d828c6A4EB2A7"); + EXPECT_EQ(key.account(TWCoinTypeEthereum, nullptr)->publicKey, "0448a9ffac8022f1c7eb5253746e24d11d9b6b2737c0aecd48335feabb95a179916b1f3a97bed6740a85a2d11c663d38566acfb08af48a47ce0c835c65c9b23d0d"); + EXPECT_EQ(key.account(coinTypeBc, nullptr)->address, "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"); + EXPECT_EQ(key.account(coinTypeBc, nullptr)->publicKey, "02df9ef2a7a5552765178b181e1e1afdefc7849985c7dfe9647706dd4fa40df6ac"); +} + +TEST(StoredKey, MissingAddressReadd) { + auto key = StoredKey::load(testDataPath("missing-address.json")); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + + const auto wallet = key.wallet(gPassword); + EXPECT_EQ(wallet.getMnemonic(), "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"); + EXPECT_TRUE(Mnemonic::isValid(wallet.getMnemonic())); + + EXPECT_EQ(key.account(TWCoinTypeBitcoin)->address, ""); + EXPECT_EQ(key.account(TWCoinTypeEthereum)->address, ""); + + // get accounts, this will also fill addresses as they are empty + const auto btcAccount = key.account(TWCoinTypeBitcoin, &wallet); + const auto ethAccount = key.account(TWCoinTypeEthereum, &wallet); + + EXPECT_TRUE(btcAccount.has_value()); + EXPECT_EQ(btcAccount->address, "bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd"); + EXPECT_TRUE(ethAccount.has_value()); + EXPECT_EQ(ethAccount->address, "0xA3Dcd899C0f3832DFDFed9479a9d828c6A4EB2A7"); +} + +TEST(StoredKey, EtherWalletAddressNo0x) { + auto key = StoredKey::load(testDataPath("ethereum-wallet-address-no-0x.json")); + key.fixAddresses(TW::data("15748c4e3dca6ae2110535576ab0c398cb79d985707c68ee6c9f9df9d421dd53")); + const auto account = key.account(TWCoinTypeEthereum, nullptr); + EXPECT_EQ(account->address, "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309"); + EXPECT_EQ(account->publicKey, "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91"); +} + +TEST(StoredKey, CreateMinimalEncryptionParameters) { + const auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelMinimal); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 0ul); + EXPECT_EQ(key.wallet(gPassword).getMnemonic(), string(gMnemonic)); + + const auto json = key.json(); + + EXPECT_EQ(json["crypto"]["kdf"], "scrypt"); + EXPECT_EQ(json["crypto"]["kdfparams"]["n"], 4096); + + // load it back + const auto key2 = StoredKey::createWithJson(json); + EXPECT_EQ(key2.wallet(gPassword).getMnemonic(), string(gMnemonic)); +} + +TEST(StoredKey, CreateWeakEncryptionParameters) { + const auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelWeak); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 0ul); + EXPECT_EQ(key.wallet(gPassword).getMnemonic(), string(gMnemonic)); + + const auto json = key.json(); + + EXPECT_EQ(json["crypto"]["kdf"], "scrypt"); + EXPECT_EQ(json["crypto"]["kdfparams"]["n"], 16384); + + // load it back + const auto key2 = StoredKey::createWithJson(json); + EXPECT_EQ(key2.wallet(gPassword).getMnemonic(), string(gMnemonic)); +} + +TEST(StoredKey, CreateStandardEncryptionParameters) { + const auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelStandard); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 0ul); + EXPECT_EQ(key.wallet(gPassword).getMnemonic(), string(gMnemonic)); + + const auto json = key.json(); + + EXPECT_EQ(json["crypto"]["kdf"], "scrypt"); + EXPECT_EQ(json["crypto"]["kdfparams"]["n"], 262144); + + // load it back + const auto key2 = StoredKey::createWithJson(json); + EXPECT_EQ(key2.wallet(gPassword).getMnemonic(), string(gMnemonic)); +} + +TEST(StoredKey, CreateMultiAccounts) { // Multiple accounts for the same coin + auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.wallet(gPassword).getMnemonic(), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 0ul); + + const auto expectedBtc1 = "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"; + const auto expectedBtc2 = "1NyRyFewhZcWMa9XCj3bBxSXPXyoSg8dKz"; + const auto expectedSol1 = "HiipoCKL8hX2RVmJTz3vaLy34hS2zLhWWMkUWtw85TmZ"; + const auto wallet = key.wallet(gPassword); + auto expectedAccounts = 0ul; + + { // Create default Bitcoin account + const auto coin = TWCoinTypeBitcoin; + + const auto btc1 = key.account(coin, &wallet); + + EXPECT_TRUE(btc1.has_value()); + EXPECT_EQ(btc1->address, expectedBtc1); + EXPECT_EQ(btc1->derivationPath.string(), "m/84'/0'/0'/0/0"); + EXPECT_EQ(btc1->extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); + EXPECT_EQ(key.accounts.size(), ++expectedAccounts); + EXPECT_EQ(key.accounts[expectedAccounts - 1].address, expectedBtc1); + EXPECT_EQ(key.account(coin)->address, expectedBtc1); + EXPECT_EQ(key.getAccounts(coin).size(), 1ul); + EXPECT_EQ(key.getAccounts(coin)[0].address, expectedBtc1); + } + { // Create default Solana account + const auto coin = TWCoinTypeSolana; + + const auto sol1 = key.account(coin, &wallet); + + EXPECT_TRUE(sol1.has_value()); + EXPECT_EQ(sol1->address, expectedSol1); + EXPECT_EQ(sol1->derivationPath.string(), "m/44'/501'/0'"); + EXPECT_EQ(key.accounts.size(), ++expectedAccounts); + EXPECT_EQ(key.accounts[expectedAccounts - 1].address, expectedSol1); + EXPECT_EQ(key.account(coin)->address, expectedSol1); + EXPECT_EQ(key.getAccounts(coin).size(), 1ul); + EXPECT_EQ(key.getAccounts(coin)[0].address, expectedSol1); + } + { // Create alternative P2PK Bitcoin account (different address format) + const auto coin = TWCoinTypeBitcoin; + + const auto btc2 = key.account(coin, TWDerivationBitcoinLegacy, wallet); + + EXPECT_EQ(btc2.address, expectedBtc2); + EXPECT_EQ(btc2.derivationPath.string(), "m/44'/0'/0'/0/0"); + EXPECT_EQ(btc2.extendedPublicKey, "xpub6CR52eaUuVb4kXAVyHC2i5ZuqJ37oWNPZFtjXaazFPXZD45DwWBYEBLdrF7fmCR9pgBuCA9Q57zZfyJjDUBDNtWkhWuGHNYKLgDHpqrHsxV"); + EXPECT_EQ(key.accounts.size(), ++expectedAccounts); + EXPECT_EQ(key.accounts[expectedAccounts - 1].address, expectedBtc2); + EXPECT_EQ(key.account(coin)->address, expectedBtc1); + EXPECT_EQ(key.account(coin, TWDerivationBitcoinLegacy, wallet).address, expectedBtc2); + EXPECT_EQ(key.getAccounts(coin).size(), 2ul); + EXPECT_EQ(key.getAccounts(coin)[0].address, expectedBtc1); + EXPECT_EQ(key.getAccounts(coin)[1].address, expectedBtc2); + } + { // Create alternative Solana account with non-default derivation path (different derivation path and address) + const auto coin = TWCoinTypeSolana; + + const auto sol2 = key.account(coin, TWDerivationSolanaSolana, wallet); + + const auto expectedSol2 = "CgWJeEWkiYqosy1ba7a3wn9HAQuHyK48xs3LM4SSDc1C"; + EXPECT_EQ(sol2.address, expectedSol2); + EXPECT_EQ(sol2.derivationPath.string(), "m/44'/501'/0'/0'"); + EXPECT_EQ(key.accounts.size(), ++expectedAccounts); + EXPECT_EQ(key.accounts[expectedAccounts - 1].address, expectedSol2); + // Now we have 2 Solana addresses, 1st is returned here + EXPECT_EQ(key.account(coin)->address, expectedSol1); + EXPECT_EQ(key.account(coin, TWDerivationSolanaSolana, wallet).address, expectedSol2); + EXPECT_EQ(key.getAccounts(coin).size(), 2ul); + EXPECT_EQ(key.getAccounts(coin)[0].address, expectedSol1); + EXPECT_EQ(key.getAccounts(coin)[1].address, expectedSol2); + } + { // Create CUSTOM account with alternative Bitcoin address. Note: this is not recommended. + const auto coin = TWCoinTypeBitcoin; + const auto customPath = DerivationPath("m/44'/2'/0'/0/0"); + const auto btcPrivateKey = wallet.getKey(coin, customPath); + EXPECT_NE(TW::deriveAddress(coin, btcPrivateKey), expectedBtc1); + const auto btcPublicKey = btcPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + const auto p2pkhBtcAddress = Bitcoin::Address(btcPublicKey, TWCoinTypeP2pkhPrefix(coin)).string(); + const auto expectedBtc3 = "1C43YUWSYTgaoBEsRffAkzF6HruJegEqP5"; + EXPECT_EQ(p2pkhBtcAddress, expectedBtc3); + const auto extendedPublicKey = wallet.getExtendedPublicKey(TW::purpose(coin), coin, TWHDVersionZPUB); + EXPECT_EQ(extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); + + key.addAccount(p2pkhBtcAddress, coin, TWDerivationCustom, customPath, hex(btcPublicKey.bytes), extendedPublicKey); + + EXPECT_EQ(key.accounts.size(), ++expectedAccounts); + EXPECT_EQ(key.accounts[expectedAccounts - 1].address, expectedBtc3); + // Now we have 2 Bitcoin addresses, 1st is returned here + EXPECT_EQ(key.account(coin)->address, expectedBtc1); + EXPECT_EQ(key.getAccounts(coin).size(), 3ul); + EXPECT_EQ(key.getAccounts(coin)[0].address, expectedBtc1); + EXPECT_EQ(key.getAccounts(coin)[1].address, expectedBtc2); + EXPECT_EQ(key.getAccounts(coin)[2].address, expectedBtc3); + EXPECT_EQ(key.getAccounts(coin)[2].derivationPath.string(), "m/44'/2'/0'/0/0"); + } +} + +TEST(StoredKey, CreateWithMnemonicAlternativeDerivation) { + const auto coin = TWCoinTypeSolana; + auto key = StoredKey::createWithMnemonicAddDefaultAddress("name", gPassword, gMnemonic, coin); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + + ASSERT_EQ(key.accounts.size(), 1ul); + EXPECT_EQ(key.accounts[0].coin, coin); + EXPECT_EQ(key.accounts[0].address, "HiipoCKL8hX2RVmJTz3vaLy34hS2zLhWWMkUWtw85TmZ"); + EXPECT_EQ(key.accounts[0].publicKey, "f86b18399096c8134dd185f1e72dd7e26528772a2a998abfd81c5f8c547223d0"); + EXPECT_EQ(hex(key.privateKey(coin, gPassword).bytes), "d81b5c525979e487736b69cb84ed8331559de17294f38491b304555c26687e83"); + EXPECT_EQ(hex(key.privateKey(coin, TWDerivationDefault, gPassword).bytes), "d81b5c525979e487736b69cb84ed8331559de17294f38491b304555c26687e83"); + ASSERT_EQ(key.accounts.size(), 1ul); + + // alternative derivation, different keys + EXPECT_EQ(hex(key.privateKey(coin, TWDerivationSolanaSolana, gPassword).bytes), "d49a5fa7f77593534c7afd2ba8dc8e9d8b007bc6ec65fe8df25ffe6fafc57151"); + + ASSERT_EQ(key.accounts.size(), 2ul); + EXPECT_EQ(key.accounts[1].coin, coin); + EXPECT_EQ(key.accounts[1].address, "CgWJeEWkiYqosy1ba7a3wn9HAQuHyK48xs3LM4SSDc1C"); + EXPECT_EQ(key.accounts[1].publicKey, "ad8f57924dce62f9040f93b4f6ce3c3d39afde7e29bcb4013dad59db7913c4c7"); + EXPECT_EQ(hex(key.privateKey(coin, TWDerivationSolanaSolana, gPassword).bytes), "d49a5fa7f77593534c7afd2ba8dc8e9d8b007bc6ec65fe8df25ffe6fafc57151"); +} + +} // namespace TW::Keystore diff --git a/tests/common/LiquidStaking/LiquidStakingTests.cpp b/tests/common/LiquidStaking/LiquidStakingTests.cpp new file mode 100644 index 00000000000..ac5bad4c7b2 --- /dev/null +++ b/tests/common/LiquidStaking/LiquidStakingTests.cpp @@ -0,0 +1,771 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "LiquidStaking/LiquidStaking.h" +#include "HexCoding.h" +#include "Base64.h" +#include "uint256.h" +#include +#include +#include +#include +#include "TestUtilities.h" + +namespace TW::LiquidStaking::tests { + TEST(LiquidStaking, Coverage) { + auto output = LiquidStaking::generateError(Proto::OK); + ASSERT_EQ(output.code(), Proto::OK); + } + + TEST(LiquidStaking, ErrorActionNotSet) { + Proto::Input input; + auto output = build(input); + ASSERT_EQ(output.status().code(), Proto::ERROR_ACTION_NOT_SET); + } + + TEST(LiquidStaking, ErrorCanDeserializeInput) { + const auto inputData_ = data("Invalid"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + const auto outputTWData_ = WRAPD(TWLiquidStakingBuildRequest(inputTWData_.get())); + Proto::Output outputProto; + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + ASSERT_EQ(outputProto.status().code(), Proto::ERROR_INPUT_PROTO_DESERIALIZATION); + } + + TEST(LiquidStaking, PolygonStride) { + // TODO: code logic + Proto::Input input; + input.set_blockchain(Proto::STRIDE); + input.set_protocol(Proto::Stride); + Proto::Stake stake; + Proto::Asset asset; + asset.set_staking_token(Proto::ATOM); + *stake.mutable_asset() = asset; + stake.set_amount("1000000"); + *input.mutable_stake() = stake; + + auto ls_output = build(input); + ASSERT_EQ(ls_output.status().code(), Proto::OK); + } + + TEST(LiquidStaking, PolygonStraderSmartContractAddressNotSet) { + Proto::Input input; + input.set_blockchain(Proto::POLYGON); + input.set_protocol(Proto::Strader); + Proto::Stake stake; + Proto::Asset asset; + asset.set_staking_token(Proto::POL); + *stake.mutable_asset() = asset; + stake.set_amount("1000000000000000000"); + *input.mutable_stake() = stake; + + auto ls_output = build(input); + ASSERT_EQ(ls_output.status().code(), Proto::ERROR_SMART_CONTRACT_ADDRESS_NOT_SET); + } + + TEST(LiquidStaking, PolygonStraderStakeInvalidBlockchain) { + Proto::Input input; + input.set_blockchain(Proto::Blockchain::STRIDE); + input.set_protocol(Proto::Strader); + input.set_smart_contract_address("0xfd225c9e6601c9d38d8f98d8731bf59efcf8c0e3"); + Proto::Stake stake; + Proto::Asset asset; + *stake.mutable_asset() = asset; + stake.set_amount("1000000"); + *input.mutable_stake() = stake; + + auto ls_output = build(input); + ASSERT_EQ(ls_output.status().code(), Proto::ERROR_TARGETED_BLOCKCHAIN_NOT_SUPPORTED_BY_PROTOCOL); + } + + TEST(LiquidStaking, PolygonStraderStakePol) { + Proto::Input input; + input.set_blockchain(Proto::POLYGON); + input.set_protocol(Proto::Strader); + input.set_smart_contract_address("0xfd225c9e6601c9d38d8f98d8731bf59efcf8c0e3"); + Proto::Stake stake; + Proto::Asset asset; + asset.set_staking_token(Proto::POL); + *stake.mutable_asset() = asset; + stake.set_amount("1000000000000000000"); + *input.mutable_stake() = stake; + + auto ls_output = build(input); + ASSERT_EQ(ls_output.status().code(), Proto::OK); + auto tx = *ls_output.mutable_ethereum(); + ASSERT_TRUE(tx.transaction().has_contract_generic()); + ASSERT_EQ(hex(tx.transaction().contract_generic().data(), true), "0xc78cf1a0"); + + auto fill_tx_functor = [](auto& tx){ + // Following fields must be set afterwards, before signing ... + const auto chainId = store(uint256_t(137)); + tx.set_chain_id(chainId.data(), chainId.size()); + auto nonce = store(uint256_t(1)); + tx.set_nonce(nonce.data(), nonce.size()); + auto maxInclusionFeePerGas = store(uint256_t(35941173184)); + auto maxFeePerGas = store(uint256_t(617347611864)); + tx.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); + tx.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); + auto gasLimit = store(uint256_t(116000)); + tx.set_gas_limit(gasLimit.data(), gasLimit.size()); + auto privKey = parse_hex("4a160b803c4392ea54865d0c5286846e7ad5e68fbd78880547697472b22ea7ab"); + tx.set_private_key(privKey.data(), privKey.size()); + // ... end + }; + + { + fill_tx_functor(tx); + Ethereum::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypePolygon); + EXPECT_EQ(hex(output.encoded()), "02f87a81890185085e42c7c0858fbcc8fcd88301c52094fd225c9e6601c9d38d8f98d8731bf59efcf8c0e3880de0b6b3a764000084c78cf1a0c001a04bcf92394d53d4908130cc6d4f7b2491967f9d6c59292b84c1f56adc49f6c458a073e09f45d64078c41a7946ffdb1dee8e604eb76f318088490f8f661bb7ddfc54"); + // Successfully broadcasted https://polygonscan.com/tx/0x0f6c4f7a893c3f08be30d2ea24479d7ed4bdba40875d07cfd607cf97980b7cf0 + } + + // TW interface + { + const auto inputData_ = input.SerializeAsString(); + EXPECT_EQ(hex(inputData_), "0a170a00121331303030303030303030303030303030303030222a3078666432323563396536363031633964333864386639386438373331626635396566636638633065333001"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + const auto outputTWData_ = WRAPD(TWLiquidStakingBuildRequest(inputTWData_.get())); + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_EQ(outputData.size(), 68ul); + Proto::Output outputProto; + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + ASSERT_TRUE(outputProto.has_ethereum()); + ASSERT_EQ(outputProto.status().code(), Proto::OK); + auto eth_tx = *ls_output.mutable_ethereum(); + ASSERT_TRUE(eth_tx.transaction().has_contract_generic()); + ASSERT_EQ(hex(eth_tx.transaction().contract_generic().data(), true), "0xc78cf1a0"); + fill_tx_functor(eth_tx); + Ethereum::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypePolygon); + EXPECT_EQ(hex(output.encoded()), "02f87a81890185085e42c7c0858fbcc8fcd88301c52094fd225c9e6601c9d38d8f98d8731bf59efcf8c0e3880de0b6b3a764000084c78cf1a0c001a04bcf92394d53d4908130cc6d4f7b2491967f9d6c59292b84c1f56adc49f6c458a073e09f45d64078c41a7946ffdb1dee8e604eb76f318088490f8f661bb7ddfc54"); + // Successfully broadcasted https://polygonscan.com/tx/0x0f6c4f7a893c3f08be30d2ea24479d7ed4bdba40875d07cfd607cf97980b7cf0 + } + } + + TEST(LiquidStaking, PolygonStraderUnStakePol) { + Proto::Input input; + input.set_blockchain(Proto::POLYGON); + input.set_protocol(Proto::Strader); + input.set_smart_contract_address("0xfd225c9e6601c9d38d8f98d8731bf59efcf8c0e3"); + Proto::Unstake unstake; + Proto::Asset asset; + *unstake.mutable_asset() = asset; + unstake.set_amount("1000000000000000000"); + *input.mutable_unstake() = unstake; + + auto ls_output = build(input); + ASSERT_EQ(ls_output.status().code(), Proto::OK); + auto tx = *ls_output.mutable_ethereum(); + ASSERT_TRUE(tx.transaction().has_contract_generic()); + ASSERT_EQ(hex(tx.transaction().contract_generic().data(), true), "0x48eaf6d60000000000000000000000000000000000000000000000000de0b6b3a7640000"); + + // Following fields must be set afterwards, before signing ... + const auto chainId = store(uint256_t(137)); + tx.set_chain_id(chainId.data(), chainId.size()); + auto nonce = store(uint256_t(4)); + tx.set_nonce(nonce.data(), nonce.size()); + auto maxInclusionFeePerGas = store(uint256_t(35941173184)); + auto maxFeePerGas = store(uint256_t(617347611864)); + tx.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); + tx.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); + auto gasLimit = store(uint256_t(200000)); + tx.set_gas_limit(gasLimit.data(), gasLimit.size()); + auto privKey = parse_hex("4a160b803c4392ea54865d0c5286846e7ad5e68fbd78880547697472b22ea7ab"); + tx.set_private_key(privKey.data(), privKey.size()); + // ... end + + Ethereum::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypePolygon); + EXPECT_EQ(hex(output.encoded()), "02f89281890485085e42c7c0858fbcc8fcd883030d4094fd225c9e6601c9d38d8f98d8731bf59efcf8c0e380a448eaf6d60000000000000000000000000000000000000000000000000de0b6b3a7640000c001a0a0dd3f23758fbcc6f25c8e4396881ab6a1fb444e5a9531b1028b121407d4b79ca0618908f0f1aa79ce3f9e25cfe24a86fd8870c85e78b3730115c033f4f6678531"); + // Successfully broadcasted https://polygonscan.com/tx/0xa66855e4af8e654e458915f59acd77e88706c01b59a3e4aed1363a665458368a + } + + TEST(LiquidStaking, PolygonStraderWithdrawPol) { + Proto::Input input; + input.set_blockchain(Proto::POLYGON); + input.set_protocol(Proto::Strader); + input.set_smart_contract_address("0xfd225c9e6601c9d38d8f98d8731bf59efcf8c0e3"); + Proto::Withdraw withdraw; + Proto::Asset asset; + *withdraw.mutable_asset() = asset; + withdraw.set_idx("0"); + *input.mutable_withdraw() = withdraw; + + auto ls_output = build(input); + ASSERT_EQ(ls_output.status().code(), Proto::OK); + auto tx = *ls_output.mutable_ethereum(); + ASSERT_TRUE(tx.transaction().has_contract_generic()); + ASSERT_EQ(hex(tx.transaction().contract_generic().data(), true), "0x77baf2090000000000000000000000000000000000000000000000000000000000000000"); + + // Following fields must be set afterwards, before signing ... + const auto chainId = store(uint256_t(137)); + tx.set_chain_id(chainId.data(), chainId.size()); + auto nonce = store(uint256_t(6)); + tx.set_nonce(nonce.data(), nonce.size()); + auto maxInclusionFeePerGas = store(uint256_t(35941173184)); + auto maxFeePerGas = store(uint256_t(678347611864)); + tx.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); + tx.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); + auto gasLimit = store(uint256_t(200000)); + tx.set_gas_limit(gasLimit.data(), gasLimit.size()); + auto privKey = parse_hex("4a160b803c4392ea54865d0c5286846e7ad5e68fbd78880547697472b22ea7ab"); + tx.set_private_key(privKey.data(), privKey.size()); + // ... end + + Ethereum::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypePolygon); + EXPECT_EQ(hex(output.encoded()), "02f89281890685085e42c7c0859df0ab1ed883030d4094fd225c9e6601c9d38d8f98d8731bf59efcf8c0e380a477baf2090000000000000000000000000000000000000000000000000000000000000000c080a02c2440ded34fc9930e4d07a7c16d5a6d865b17e37dba61f3a94a3b78cd269a35a055cb8ba79645063f99e999b6ca44cd0ac26d6fd2b3acc8453a1c1c61de767559"); + // Successfully broadcasted https://polygonscan.com/tx/0x61fa7b9051ddb9e2906130b0c5d94b8e3ecfc89b1fdfeff86042fbea851d8ba4 + } + + TEST(LiquidStaking, PolygonStraderStakeBnb) { + Proto::Input input; + input.set_blockchain(Proto::BNB_BSC); + input.set_protocol(Proto::Strader); + input.set_smart_contract_address("0x7276241a669489e4bbb76f63d2a43bfe63080f2f"); + Proto::Stake stake; + Proto::Asset asset; + asset.set_staking_token(Proto::BNB); + *stake.mutable_asset() = asset; + stake.set_amount("20000000000000000"); + *input.mutable_stake() = stake; + + auto ls_output = build(input); + ASSERT_EQ(ls_output.status().code(), Proto::OK); + auto tx = *ls_output.mutable_ethereum(); + ASSERT_TRUE(tx.transaction().has_contract_generic()); + ASSERT_EQ(hex(tx.transaction().contract_generic().data(), true), "0xd0e30db0"); + + // Following fields must be set afterwards, before signing ... + const auto chainId = store(uint256_t(56)); + tx.set_chain_id(chainId.data(), chainId.size()); + auto nonce = store(uint256_t(1)); + tx.set_nonce(nonce.data(), nonce.size()); + auto gasPrice = store(uint256_t(20000000000)); + tx.set_gas_price(gasPrice.data(), gasPrice.size()); + auto gasLimit = store(uint256_t(250000)); + tx.set_gas_limit(gasLimit.data(), gasLimit.size()); + auto privKey = parse_hex("4a160b803c4392ea54865d0c5286846e7ad5e68fbd78880547697472b22ea7ab"); + tx.set_private_key(privKey.data(), privKey.size()); + // ... end + + Ethereum::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeSmartChain); + EXPECT_EQ(hex(output.encoded()), "f871018504a817c8008303d090947276241a669489e4bbb76f63d2a43bfe63080f2f87470de4df82000084d0e30db08193a0ec9bcc1b203477b9e5af8d0f9338de2af2553bb34ba693e79183708d6025e5c9a01e1c1f5d724fa2aa55464451bc0eee45b8522091048e6ac377db0b181e412a15"); + // Successfully broadcasted https://bscscan.com/tx/0x17014f06b267f683d03d4d9cc2e5c1b182be05c14c3b9ffa0eaa3060bc859ba6 + } + + TEST(LiquidStaking, PolygonStraderUnStakeBnb) { + Proto::Input input; + input.set_blockchain(Proto::BNB_BSC); + input.set_protocol(Proto::Strader); + input.set_smart_contract_address("0x7276241a669489e4bbb76f63d2a43bfe63080f2f"); + Proto::Unstake unstake; + Proto::Asset asset; + asset.set_staking_token(Proto::BNB); + *unstake.mutable_asset() = asset; + unstake.set_amount("10000000000000000"); + *input.mutable_unstake() = unstake; + + auto ls_output = build(input); + ASSERT_EQ(ls_output.status().code(), Proto::OK); + auto tx = *ls_output.mutable_ethereum(); + ASSERT_TRUE(tx.transaction().has_contract_generic()); + ASSERT_EQ(hex(tx.transaction().contract_generic().data(), true), "0x745400c9000000000000000000000000000000000000000000000000002386f26fc10000"); + + // Following fields must be set afterwards, before signing ... + const auto chainId = store(uint256_t(56)); + tx.set_chain_id(chainId.data(), chainId.size()); + auto nonce = store(uint256_t(7)); + tx.set_nonce(nonce.data(), nonce.size()); + auto gasPrice = store(uint256_t(20000000000)); + tx.set_gas_price(gasPrice.data(), gasPrice.size()); + auto gasLimit = store(uint256_t(250000)); + tx.set_gas_limit(gasLimit.data(), gasLimit.size()); + auto privKey = parse_hex("4a160b803c4392ea54865d0c5286846e7ad5e68fbd78880547697472b22ea7ab"); + tx.set_private_key(privKey.data(), privKey.size()); + // ... end + + Ethereum::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeSmartChain); + EXPECT_EQ(hex(output.encoded()), "f889078504a817c8008303d090947276241a669489e4bbb76f63d2a43bfe63080f2f80a4745400c9000000000000000000000000000000000000000000000000002386f26fc100008193a0a1b72a5c368e0591c488094e5f9a431b1be915310fb47c1c9312c247044310279f5fffeaf2e1659c841f31927b0e60870b455fa35e041ae29006472c87550c9d"); + // Successfully broadcasted https://bscscan.com/tx/0x420b203b998d4de40e78ab7c6e80399d45a20620368c11c7d7d45820eeef3096 + } + + TEST(LiquidStaking, AptosTortugaStakeApt) { + // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x25dca849cb4ebacbff223139f7ad5d24c37c225d9506b8b12a925de70429e685/userTxnOverview?network=mainnet + Proto::Input input; + input.set_blockchain(Proto::APTOS); + input.set_protocol(Proto::Tortuga); + input.set_smart_contract_address("0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f"); + Proto::Stake stake; + Proto::Asset asset; + asset.set_staking_token(Proto::APT); + *stake.mutable_asset() = asset; + stake.set_amount("100000000"); + *input.mutable_stake() = stake; + + auto ls_output = build(input); + ASSERT_EQ(ls_output.status().code(), Proto::OK); + auto& tx = *ls_output.mutable_aptos(); + + auto fill_tx_functor = [](auto& tx){ + // Following fields must be set afterwards, before signing ... + tx.set_sender("0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc"); + tx.set_sequence_number(19); + tx.set_max_gas_amount(5554); + tx.set_gas_unit_price(100); + tx.set_expiration_timestamp_secs(1670240203); + tx.set_chain_id(1); + auto privateKey = parse_hex("786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05"); + tx.set_private_key(privateKey.data(), privateKey.size()); + }; + + auto verify_tx_functor = [](auto& tx) { + EXPECT_EQ(hex(tx.raw_txn()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1300000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000b2150000000000006400000000000000cbd78d630000000001"); + EXPECT_EQ(hex(tx.authenticator().signature()), "22d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d"); + EXPECT_EQ(hex(tx.encoded()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1300000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000b2150000000000006400000000000000cbd78d630000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc44022d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d"); + nlohmann::json expectedJson = R"( + { + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "19", + "max_gas_amount": "5554", + "gas_unit_price": "100", + "expiration_timestamp_secs": "1670240203", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::stake", + "type_arguments": [], + "arguments": [ + "100000000" + ], + "type": "entry_function_payload" + }, + "signature": { + "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", + "signature": "0x22d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d", + "type": "ed25519_signature" + } + } + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(tx.json()); + assertJSONEqual(expectedJson, parsedJson); + }; + + { + fill_tx_functor(tx); + Aptos::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeAptos); + verify_tx_functor(output); + } + + // TW interface + { + const auto inputData_ = input.SerializeAsString(); + EXPECT_EQ(hex(inputData_), "0a0f0a0208031209313030303030303030224230783866333936653432343662326261383762353163303733396566356561346632363531356139383337353330386333316163326563316534323134326135376628023004"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + const auto outputTWData_ = WRAPD(TWLiquidStakingBuildRequest(inputTWData_.get())); + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_EQ(outputData.size(), 79ul); + Proto::Output outputProto; + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + ASSERT_TRUE(outputProto.has_aptos()); + ASSERT_EQ(outputProto.status().code(), Proto::OK); + auto aptos_tx = *ls_output.mutable_aptos(); + fill_tx_functor(aptos_tx); + Aptos::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeAptos); + verify_tx_functor(output); + } + } + + TEST(LiquidStaking, AptosTortugaUnstakeApt) { + // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x92edb4f756fe86118e34a0e64746c70260ee02c2ae2cf402b3e39f6a282ce968?network=mainnet + Proto::Input input; + input.set_blockchain(Proto::APTOS); + input.set_protocol(Proto::Tortuga); + input.set_smart_contract_address("0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f"); + Proto::Unstake unstake; + Proto::Asset asset; + asset.set_staking_token(Proto::APT); + *unstake.mutable_asset() = asset; + unstake.set_amount("99178100"); + *input.mutable_unstake() = unstake; + + auto ls_output = build(input); + ASSERT_EQ(ls_output.status().code(), Proto::OK); + auto& tx = *ls_output.mutable_aptos(); + + auto fill_tx_functor = [](auto& tx){ + // Following fields must be set afterwards, before signing ... + tx.set_sender("0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc"); + tx.set_sequence_number(20); + tx.set_max_gas_amount(2371); + tx.set_gas_unit_price(120); + tx.set_expiration_timestamp_secs(1670304949); + tx.set_chain_id(1); + auto privateKey = parse_hex("786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05"); + tx.set_private_key(privateKey.data(), privateKey.size()); + }; + + auto verify_tx_functor = [](auto& tx) { + EXPECT_EQ(hex(tx.raw_txn()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1400000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e9050000000043090000000000007800000000000000b5d48e630000000001"); + EXPECT_EQ(hex(tx.authenticator().signature()), "6994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c"); + EXPECT_EQ(hex(tx.encoded()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1400000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e9050000000043090000000000007800000000000000b5d48e630000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4406994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c"); + nlohmann::json expectedJson = R"( + { + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "20", + "max_gas_amount": "2371", + "gas_unit_price": "120", + "expiration_timestamp_secs": "1670304949", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::unstake", + "type_arguments": [], + "arguments": [ + "99178100" + ], + "type": "entry_function_payload" + }, + "signature": { + "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", + "signature": "0x6994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c", + "type": "ed25519_signature" + } + } + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(tx.json()); + assertJSONEqual(expectedJson, parsedJson); + }; + + { + fill_tx_functor(tx); + Aptos::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeAptos); + verify_tx_functor(output); + } + + // TW interface + { + const auto inputData_ = input.SerializeAsString(); + EXPECT_EQ(hex(inputData_), "120e0a02080312083939313738313030224230783866333936653432343662326261383762353163303733396566356561346632363531356139383337353330386333316163326563316534323134326135376628023004"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + const auto outputTWData_ = WRAPD(TWLiquidStakingBuildRequest(inputTWData_.get())); + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_EQ(outputData.size(), 79ul); + Proto::Output outputProto; + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + ASSERT_TRUE(outputProto.has_aptos()); + ASSERT_EQ(outputProto.status().code(), Proto::OK); + auto aptos_tx = *ls_output.mutable_aptos(); + fill_tx_functor(aptos_tx); + Aptos::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeAptos); + verify_tx_functor(output); + } + } + + TEST(LiquidStaking, AptosTortugaWithdrawApt) { + // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x9fc874de7a7d3e813d9a1658d896023de270a0096a5e258c196005656ace7d54?network=mainnet + Proto::Input input; + input.set_blockchain(Proto::APTOS); + input.set_protocol(Proto::Tortuga); + input.set_smart_contract_address("0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f"); + Proto::Withdraw withdraw; + Proto::Asset asset; + asset.set_staking_token(Proto::APT); + *withdraw.mutable_asset() = asset; + withdraw.set_idx("0"); + *input.mutable_withdraw() = withdraw; + + auto ls_output = build(input); + ASSERT_EQ(ls_output.status().code(), Proto::OK); + auto& tx = *ls_output.mutable_aptos(); + + auto fill_tx_functor = [](auto& tx){ + // Following fields must be set afterwards, before signing ... + tx.set_sender("0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc"); + tx.set_sequence_number(28); + tx.set_max_gas_amount(10); + tx.set_gas_unit_price(148); + tx.set_expiration_timestamp_secs(1682066783); + tx.set_chain_id(1); + auto privateKey = parse_hex("786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05"); + tx.set_private_key(privateKey.data(), privateKey.size()); + }; + + auto verify_tx_functor = [](auto& tx) { + EXPECT_EQ(hex(tx.raw_txn()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657205636c61696d00010800000000000000000a0000000000000094000000000000005f4d42640000000001"); + EXPECT_EQ(hex(tx.authenticator().signature()), "c936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03"); + EXPECT_EQ(hex(tx.encoded()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657205636c61696d00010800000000000000000a0000000000000094000000000000005f4d42640000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc440c936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03"); + nlohmann::json expectedJson = R"( + { + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "28", + "max_gas_amount": "10", + "gas_unit_price": "148", + "expiration_timestamp_secs": "1682066783", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::claim", + "type_arguments": [], + "arguments": [ + "0" + ], + "type": "entry_function_payload" + }, + "signature": { + "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", + "signature": "0xc936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03", + "type": "ed25519_signature" + } + } + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(tx.json()); + assertJSONEqual(expectedJson, parsedJson); + }; + + { + fill_tx_functor(tx); + Aptos::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeAptos); + verify_tx_functor(output); + } + + // TW interface + { + const auto inputData_ = input.SerializeAsString(); + EXPECT_EQ(hex(inputData_), "1a070a0208031a0130224230783866333936653432343662326261383762353163303733396566356561346632363531356139383337353330386333316163326563316534323134326135376628023004"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + const auto outputTWData_ = WRAPD(TWLiquidStakingBuildRequest(inputTWData_.get())); + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_EQ(outputData.size(), 74ul); + Proto::Output outputProto; + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + ASSERT_TRUE(outputProto.has_aptos()); + ASSERT_EQ(outputProto.status().code(), Proto::OK); + auto aptos_tx = *ls_output.mutable_aptos(); + fill_tx_functor(aptos_tx); + Aptos::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeAptos); + verify_tx_functor(output); + } + } + + TEST(LiquidStaking, StrideStakeAtom) { + Proto::Input input; + input.set_blockchain(Proto::STRIDE); + input.set_protocol(Proto::Stride); + Proto::Stake stake; + Proto::Asset asset; + asset.set_staking_token(Proto::ATOM); + asset.set_denom("uatom"); + asset.set_from_address("stride1mry47pkga5tdswtluy0m8teslpalkdq0a2sjge"); + *stake.mutable_asset() = asset; + stake.set_amount("100000"); + *input.mutable_stake() = stake; + + auto ls_output = build(input); + ASSERT_EQ(ls_output.status().code(), Proto::OK); + ASSERT_TRUE(ls_output.has_cosmos()); + auto tx = *ls_output.mutable_cosmos(); + + auto fill_tx_functor = [](auto& tx){ + // Following fields must be set afterwards, before signing ...tx + auto privateKey = parse_hex("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433"); + tx.set_signing_mode(Cosmos::Proto::Protobuf); + tx.set_account_number(136412); + tx.set_chain_id("stride-1"); + tx.set_memo(""); + tx.set_sequence(0); + tx.set_private_key(privateKey.data(), privateKey.size()); + + auto& fee = *tx.mutable_fee(); + fee.set_gas(500000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("ustrd"); + amountOfFee->set_amount("0"); + }; + + auto verify_tx_functor = [](auto& tx) { + // Successfully broadcasted: https://www.mintscan.io/stride/txs/48E51A2571D99453C4581B30CECA2A1156C0D1EBACCD3619729B5A35AD67CC94?height=3485243 + auto expectedJson = R"( + { + "mode":"BROADCAST_MODE_BLOCK", + "tx_bytes":"CmMKYQofL3N0cmlkZS5zdGFrZWliYy5Nc2dMaXF1aWRTdGFrZRI+Ci1zdHJpZGUxbXJ5NDdwa2dhNXRkc3d0bHV5MG04dGVzbHBhbGtkcTBhMnNqZ2USBjEwMDAwMBoFdWF0b20SYgpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAsv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarlEgQKAggBEhAKCgoFdXN0cmQSATAQoMIeGkCDaZHV5/Z3CAQC5DXkaHmF6OKUiS5XKDsl3ZnBaaVuJjlSWV2vA7MPwGbC17P6jbVJt58ZLcxIWFt76UO3y1ix" + })"; + assertJSONEqual(tx.serialized(), expectedJson); + EXPECT_EQ(hex(tx.signature()), "836991d5e7f677080402e435e4687985e8e294892e57283b25dd99c169a56e263952595daf03b30fc066c2d7b3fa8db549b79f192dcc48585b7be943b7cb58b1"); + EXPECT_EQ(tx.json(), ""); + EXPECT_EQ(tx.error_message(), ""); + }; + + { + fill_tx_functor(tx); + Cosmos::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeStride); + verify_tx_functor(output); + } + + { + const auto inputData_ = input.SerializeAsString(); + EXPECT_EQ(hex(inputData_), "0a420a3808011a057561746f6d222d737472696465316d72793437706b67613574647377746c7579306d387465736c70616c6b6471306132736a6765120631303030303028013002"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + const auto outputTWData_ = WRAPD(TWLiquidStakingBuildRequest(inputTWData_.get())); + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_EQ(outputData.size(), 69ul); + Proto::Output outputProto; + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + ASSERT_TRUE(outputProto.has_cosmos()); + ASSERT_EQ(outputProto.status().code(), Proto::OK); + auto aptos_tx = *ls_output.mutable_cosmos(); + fill_tx_functor(aptos_tx); + Cosmos::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeStride); + verify_tx_functor(output); + } + } + + TEST(LiquidStaking, StrideUnstakeAtom) { + Proto::Input input; + input.set_blockchain(Proto::STRIDE); + input.set_protocol(Proto::Stride); + Proto::Unstake unstake; + Proto::Asset asset; + asset.set_staking_token(Proto::ATOM); + asset.set_from_address("stride1mry47pkga5tdswtluy0m8teslpalkdq0a2sjge"); + *unstake.mutable_asset() = asset; + unstake.set_amount("40000"); + unstake.set_receiver_chain_id("cosmoshub-4"); + unstake.set_receiver_address("cosmos1mry47pkga5tdswtluy0m8teslpalkdq07pswu4"); + *input.mutable_unstake() = unstake; + + auto ls_output = build(input); + ASSERT_EQ(ls_output.status().code(), Proto::OK); + ASSERT_TRUE(ls_output.has_cosmos()); + auto tx = *ls_output.mutable_cosmos(); + + auto fill_tx_functor = [](auto& tx){ + // Following fields must be set afterwards, before signing ...tx + auto privateKey = parse_hex("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433"); + tx.set_signing_mode(Cosmos::Proto::Protobuf); + tx.set_account_number(136412); + tx.set_chain_id("stride-1"); + tx.set_memo(""); + tx.set_sequence(1); + tx.set_private_key(privateKey.data(), privateKey.size()); + + auto& fee = *tx.mutable_fee(); + fee.set_gas(1000000); + auto amountOfFee = fee.add_amounts(); + amountOfFee->set_denom("ustrd"); + amountOfFee->set_amount("0"); + }; + + auto verify_tx_functor = [](auto& tx) { + // Successfully broadcasted: https://www.mintscan.io/stride/txs/B3D3A92A2FFB92A480A4B547A4303E6932204972A965D687DB4FB6B4E16B2C42?height=3485343 + auto expectedJson = R"( + { + "mode":"BROADCAST_MODE_BLOCK", + "tx_bytes":"CpgBCpUBCh8vc3RyaWRlLnN0YWtlaWJjLk1zZ1JlZGVlbVN0YWtlEnIKLXN0cmlkZTFtcnk0N3BrZ2E1dGRzd3RsdXkwbTh0ZXNscGFsa2RxMGEyc2pnZRIFNDAwMDAaC2Nvc21vc2h1Yi00Ii1jb3Ntb3MxbXJ5NDdwa2dhNXRkc3d0bHV5MG04dGVzbHBhbGtkcTA3cHN3dTQSZApQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAsv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarlEgQKAggBGAESEAoKCgV1c3RyZBIBMBDAhD0aQKf84TYoPqwnXw22r0dok2fYplUFu003TlIfpoT+wqTZF1lHPC+RTAoJob6x50CnfvGlgJFBEQYPD+Ccv659VVA=" + })"; + assertJSONEqual(tx.serialized(), expectedJson); + EXPECT_EQ(TW::Base64::encode(data(tx.signature())), "p/zhNig+rCdfDbavR2iTZ9imVQW7TTdOUh+mhP7CpNkXWUc8L5FMCgmhvrHnQKd+8aWAkUERBg8P4Jy/rn1VUA=="); + EXPECT_EQ(tx.json(), ""); + EXPECT_EQ(tx.error_message(), ""); + }; + + { + fill_tx_functor(tx); + Cosmos::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeStride); + verify_tx_functor(output); + } + + { + const auto inputData_ = input.SerializeAsString(); + EXPECT_EQ(hex(inputData_), "12760a310801222d737472696465316d72793437706b67613574647377746c7579306d387465736c70616c6b6471306132736a6765120534303030301a2d636f736d6f73316d72793437706b67613574647377746c7579306d387465736c70616c6b647130377073777534220b636f736d6f736875622d3428013002"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + const auto outputTWData_ = WRAPD(TWLiquidStakingBuildRequest(inputTWData_.get())); + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_EQ(outputData.size(), 121ul); + Proto::Output outputProto; + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + ASSERT_TRUE(outputProto.has_cosmos()); + ASSERT_EQ(outputProto.status().code(), Proto::OK); + auto aptos_tx = *ls_output.mutable_cosmos(); + fill_tx_functor(aptos_tx); + Cosmos::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeStride); + verify_tx_functor(output); + } + } + + TEST(LiquidStaking, EthereumLidoStakeEth) { + Proto::Input input; + input.set_blockchain(Proto::ETHEREUM); + input.set_protocol(Proto::Lido); + input.set_smart_contract_address("0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"); + Proto::Stake stake; + Proto::Asset asset; + asset.set_staking_token(Proto::ETH); + *stake.mutable_asset() = asset; + stake.set_amount("1000000000000000"); + *input.mutable_stake() = stake; + + auto ls_output = build(input); + ASSERT_EQ(ls_output.status().code(), Proto::OK); + auto tx = *ls_output.mutable_ethereum(); + ASSERT_TRUE(tx.transaction().has_contract_generic()); + ASSERT_EQ(hex(tx.transaction().contract_generic().data(), true), "0xa1903eab0000000000000000000000000000000000000000000000000000000000000000"); + + auto fill_tx_functor = [](auto& tx){ + // Following fields must be set afterwards, before signing ... + const auto chainId = store(uint256_t(1)); + tx.set_chain_id(chainId.data(), chainId.size()); + auto nonce = store(uint256_t(0)); + tx.set_nonce(nonce.data(), nonce.size()); + auto maxInclusionFeePerGas = store(uint256_t(1500000000)); + auto maxFeePerGas = store(uint256_t(109039719380)); + tx.set_max_inclusion_fee_per_gas(maxInclusionFeePerGas.data(), maxInclusionFeePerGas.size()); + tx.set_max_fee_per_gas(maxFeePerGas.data(), maxFeePerGas.size()); + auto gasLimit = store(uint256_t(117093)); + tx.set_gas_limit(gasLimit.data(), gasLimit.size()); + auto privKey = parse_hex("d68f3ae9c5b3974d27e606eb9fd8e03cf172380307dd0fe1273394b40c8b33aa"); + tx.set_private_key(privKey.data(), privKey.size()); + // ... end + }; + + { + fill_tx_functor(tx); + Ethereum::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeEthereum); + EXPECT_EQ(hex(output.encoded()), "02f89701808459682f008519634613d48301c96594ae7ab96520de3a18e5e111b5eaab095312d7fe8487038d7ea4c68000a4a1903eab0000000000000000000000000000000000000000000000000000000000000000c001a0daace8c05277cf7eaff3f03a4e1ba7edadab20407674d1b659f5c13f89a04087a0528b5d4499b67a50989b6397e530be23515532732b54d60c4a4d726e499e046a"); + // Successfully broadcasted https://etherscan.io/tx/0x4d509fd50f474a568419ade4df13b43943b5c8233e980d2217784c512941b3bd + } + + // TW interface + { + const auto inputData_ = input.SerializeAsString(); + EXPECT_EQ(hex(inputData_), "0a160a020804121031303030303030303030303030303030222a3078616537616239363532304445334131384535653131314235456141623039353331324437664538342803"); + const auto inputTWData_ = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData_.data(), inputData_.size())); + const auto outputTWData_ = WRAPD(TWLiquidStakingBuildRequest(inputTWData_.get())); + const auto outputData = data(TWDataBytes(outputTWData_.get()), TWDataSize(outputTWData_.get())); + EXPECT_EQ(outputData.size(), 99ul); + Proto::Output outputProto; + EXPECT_TRUE(outputProto.ParseFromArray(outputData.data(), static_cast(outputData.size()))); + ASSERT_TRUE(outputProto.has_ethereum()); + ASSERT_EQ(outputProto.status().code(), Proto::OK); + auto eth_tx = *ls_output.mutable_ethereum(); + ASSERT_TRUE(eth_tx.transaction().has_contract_generic()); + ASSERT_EQ(hex(eth_tx.transaction().contract_generic().data(), true), "0xa1903eab0000000000000000000000000000000000000000000000000000000000000000"); + fill_tx_functor(eth_tx); + Ethereum::Proto::SigningOutput output; + ANY_SIGN(tx, TWCoinTypeEthereum); + EXPECT_EQ(hex(output.encoded()), "02f89701808459682f008519634613d48301c96594ae7ab96520de3a18e5e111b5eaab095312d7fe8487038d7ea4c68000a4a1903eab0000000000000000000000000000000000000000000000000000000000000000c001a0daace8c05277cf7eaff3f03a4e1ba7edadab20407674d1b659f5c13f89a04087a0528b5d4499b67a50989b6397e530be23515532732b54d60c4a4d726e499e046a"); + // Successfully broadcasted https://etherscan.io/tx/0x4d509fd50f474a568419ade4df13b43943b5c8233e980d2217784c512941b3bd + } + } +} diff --git a/tests/MnemonicTests.cpp b/tests/common/MnemonicTests.cpp similarity index 94% rename from tests/MnemonicTests.cpp rename to tests/common/MnemonicTests.cpp index c82b08a82f5..bb0b86a55ac 100644 --- a/tests/MnemonicTests.cpp +++ b/tests/common/MnemonicTests.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Mnemonic.h" diff --git a/tests/common/NumericLiteralTests.cpp b/tests/common/NumericLiteralTests.cpp new file mode 100644 index 00000000000..ef1dcee68dd --- /dev/null +++ b/tests/common/NumericLiteralTests.cpp @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "NumericLiteral.h" + +#include + +TEST(UzLitteralOperator, SizetEquality) { + [[maybe_unused]] auto size = 42_uz; + static_assert(std::is_same_v); +} \ No newline at end of file diff --git a/tests/common/PrivateKeyTests.cpp b/tests/common/PrivateKeyTests.cpp new file mode 100644 index 00000000000..fa9ec36aea3 --- /dev/null +++ b/tests/common/PrivateKeyTests.cpp @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Hash.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include +#include "PublicKey.h" +#include "PublicKeyLegacy.h" + +#include + +using namespace TW; +using namespace std; + +namespace TW::tests { + +TEST(PrivateKey, CreateValid) { + Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + EXPECT_TRUE(PrivateKey::isValid(privKeyData, TWCurveSECP256k1)); + auto privateKey = PrivateKey(privKeyData); + EXPECT_EQ(hex(privKeyData), hex(privateKey.bytes)); +} + +string TestInvalid(const Data& privKeyData) { + try { + auto privateKey = PrivateKey(privKeyData); + return hex(privateKey.bytes); + } catch (invalid_argument& ex) { + // expected exception + return string("EXCEPTION: ") + string(ex.what()); + } +} + +TEST(PrivateKey, InvalidShort) { + string res = TestInvalid(parse_hex("deadbeef")); + EXPECT_EQ("EXCEPTION: Invalid private key data", res); +} + +TEST(PrivateKey, InvalidAllZeros) { + string res = TestInvalid(Data(32)); + EXPECT_EQ("EXCEPTION: Invalid private key data", res); +} + +TEST(PrivateKey, InvalidSECP256k1) { + { + auto privKeyData = parse_hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); + auto valid = PrivateKey::isValid(privKeyData, TWCurveSECP256k1); + EXPECT_EQ(valid, false); + } + { + auto privKeyData = parse_hex("0000000000000000000000000000000000000000000000000000000000000000"); + auto valid = PrivateKey::isValid(privKeyData, TWCurveSECP256k1); + EXPECT_EQ(valid, false); + } +} + +string TestInvalidExtended(const Data& data, const Data& ext, const Data& chainCode, const Data& data2, const Data& ext2, const Data& chainCode2) { + try { + auto privateKey = PrivateKey(data, ext, chainCode, data2, ext2, chainCode2); + return hex(privateKey.bytes); + } catch (invalid_argument& ex) { + // expected exception + return string("EXCEPTION: ") + string(ex.what()); + } +} + +TEST(PrivateKey, CreateExtendedInvalid) { + { + string res = TestInvalidExtended( + parse_hex("deadbeed"), + parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), + parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111")); + EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); + } + { + string res = TestInvalidExtended( + parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), + parse_hex("deadbeed"), + parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111")); + EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); + } + { + string res = TestInvalidExtended( + parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), + parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), + parse_hex("deadbeed"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111")); + EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); + } + { + string res = TestInvalidExtended( + parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), + parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), + parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), + parse_hex("deadbeed"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111"), + parse_hex("1111111111111111111111111111111111111111111111111111111111111111")); + EXPECT_EQ("EXCEPTION: Invalid private key or extended key data", res); + } +} + +TEST(PrivateKey, Valid) { + Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + EXPECT_TRUE(PrivateKey::isValid(privKeyData, TWCurveSECP256k1)); + EXPECT_TRUE(PrivateKey::isValid(privKeyData, TWCurveED25519)); +} + +TEST(PrivateKey, PublicKey) { + Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + auto privateKey = PrivateKey(privKeyData); + { + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + EXPECT_EQ( + "4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867", + hex(publicKey.bytes)); + } + { + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ( + "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1", + hex(publicKey.bytes)); + } + { + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ( + "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91", + hex(publicKey.bytes)); + } + { + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1Extended); + EXPECT_EQ( + "046d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab918b4fe46ccbf56701fb210d67d91c5779468f6b3fdc7a63692b9b62543f47ae", + hex(publicKey.bytes)); + } +} + +TEST(PrivateKey, Cleanup) { + Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + auto privateKey = new PrivateKey(privKeyData); + auto ptr = privateKey->bytes.data(); + ASSERT_EQ(hex(privKeyData), hex(data(ptr, 32))); + + privateKey->cleanup(); + + // Memory cleaned (filled with 0s). They may be overwritten by something else; we check that it is not equal to original, most of it has changed. + ASSERT_EQ(hex(data(ptr, 32)), "0000000000000000000000000000000000000000000000000000000000000000"); + + delete privateKey; + + // Note: it would be good to check the memory area after deletion of the object, but this is not possible +} + +TEST(PrivateKey, GetType) { + EXPECT_EQ(PrivateKey::getType(TWCurveSECP256k1), TWPrivateKeyTypeDefault); + EXPECT_EQ(PrivateKey::getType(TWCurveNIST256p1), TWPrivateKeyTypeDefault); + EXPECT_EQ(PrivateKey::getType(TWCurveED25519), TWPrivateKeyTypeDefault); + EXPECT_EQ(PrivateKey::getType(TWCurveCurve25519), TWPrivateKeyTypeDefault); + + EXPECT_EQ(PrivateKey::getType(TWCurveED25519ExtendedCardano), TWPrivateKeyTypeCardano); +} + +TEST(PrivateKey, PrivateKeyExtended) { + // Non-extended: both keys are 32 bytes. + auto privateKeyNonext = PrivateKey(parse_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + EXPECT_EQ("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", hex(privateKeyNonext.bytes)); + auto publicKeyNonext = privateKeyNonext.getPublicKey(TWPublicKeyTypeED25519); + EXPECT_EQ(32ul, publicKeyNonext.bytes.size()); + + const auto fullkey = + "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744" + "309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff" + "bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" + "639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05" + "d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7b" + "ed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a"; + // Extended keys: private key is 2x3x32 bytes, public key is 2x64 bytes + auto privateKeyExt = PrivateKey(parse_hex(fullkey)); + EXPECT_EQ(fullkey, hex(privateKeyExt.bytes)); + EXPECT_EQ("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744", hex(privateKeyExt.key())); + EXPECT_EQ("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff", hex(privateKeyExt.extension())); + EXPECT_EQ("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4", hex(privateKeyExt.chainCode())); + EXPECT_EQ("639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05", hex(privateKeyExt.secondKey())); + EXPECT_EQ("d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7b", hex(privateKeyExt.secondExtension())); + EXPECT_EQ("ed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a", hex(privateKeyExt.secondChainCode())); + + auto publicKeyExt = privateKeyExt.getPublicKey(TWPublicKeyTypeED25519Cardano); + EXPECT_EQ(2 * 64ul, publicKeyExt.bytes.size()); + + // Try other constructor for extended key + auto privateKeyExtOne = PrivateKey( + parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), + parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), + parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), + parse_hex("639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05"), + parse_hex("d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7b"), + parse_hex("ed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a")); + EXPECT_EQ(fullkey, hex(privateKeyExt.bytes)); +} + +TEST(PrivateKey, PrivateKeyExtendedError) { + // TWPublicKeyTypeED25519Cardano pubkey with non-extended private: error + auto privateKeyNonext = PrivateKey(parse_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + try { + auto publicKeyError = privateKeyNonext.getPublicKey(TWPublicKeyTypeED25519Cardano); + } catch (invalid_argument& ex) { + // expected exception + return; + } + FAIL() << "Should throw Invalid empty key extension"; +} + +TEST(PrivateKey, SignSECP256k1) { + Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + auto privateKey = PrivateKey(privKeyData); + Data messageData = TW::data("hello"); + Data hash = Hash::keccak256(messageData); + Data actual = privateKey.sign(hash, TWCurveSECP256k1); + + EXPECT_EQ( + "8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901", + hex(actual)); +} + +TEST(PrivateKey, SignExtended) { + const auto privateKeyExt = PrivateKey(parse_hex( + "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" + "639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a")); + Data messageData = TW::data("hello"); + Data hash = Hash::keccak256(messageData); + Data actual = privateKeyExt.sign(hash, TWCurveED25519ExtendedCardano); + + EXPECT_EQ( + "375df53b6a4931dcf41e062b1c64288ed4ff3307f862d5c1b1c71964ce3b14c99422d0fdfeb2807e9900a26d491d5e8a874c24f98eec141ed694d7a433a90f08", + hex(actual)); +} + +TEST(PrivateKey, SignSchnorr) { + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const Data messageData = TW::data("hello schnorr"); + const Data digest = Hash::sha256(messageData); + const auto signature = privateKey.signZilliqa(digest); + EXPECT_EQ(hex(signature), + "b8118ccb99563fe014279c957b0a9d563c1666e00367e9896fe541765246964f64a53052513da4e6dc20fdaf69ef0d95b4ca51c87ad3478986cf053c2dd0b853"); +} + +TEST(PrivateKey, SignNIST256p1) { + Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + auto privateKey = PrivateKey(privKeyData); + Data messageData = TW::data("hello"); + Data hash = Hash::keccak256(messageData); + Data actual = privateKey.sign(hash, TWCurveNIST256p1); + + EXPECT_EQ( + "8859e63a0c0cc2fc7f788d7e78406157b288faa6f76f76d37c4cd1534e8d83c468f9fd6ca7dde378df594625dcde98559389569e039282275e3d87c26e36447401", + hex(actual)); +} + +TEST(PrivateKey, SignNIST256p1VerifyLegacy) { + for (auto i = 0; i < 1000; ++i) { + Data secret(32); + random_buffer(secret.data(), 32); + + Data msg(32); + random_buffer(msg.data(), 32); + + PrivateKey privateKey(secret); + auto signature = privateKey.sign(msg, TWCurveNIST256p1); + + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); + EXPECT_TRUE(TrezorCrypto::verifyNist256p1Signature(publicKey.bytes, signature, msg)) + << "Error verifying nist256p1 signature" << std::endl + << "Private key: " << hex(secret) << std::endl + << "Message: " << hex(msg); + } +} + +int isCanonical([[maybe_unused]] uint8_t by, [[maybe_unused]] uint8_t sig[64]) { + return 1; +} + +TEST(PrivateKey, SignCanonicalSECP256k1) { + Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + auto privateKey = PrivateKey(privKeyData); + Data messageData = TW::data("hello"); + Data hash = Hash::keccak256(messageData); + Data actual = privateKey.sign(hash, TWCurveSECP256k1, isCanonical); + + EXPECT_EQ( + "208720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de9", + hex(actual)); +} + +TEST(PrivateKey, SignShortDigest) { + Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + auto privateKey = PrivateKey(privKeyData); + Data shortDigest = TW::data("12345"); + { + Data actual = privateKey.sign(shortDigest, TWCurveSECP256k1); + EXPECT_EQ(actual.size(), 0ul); + } + { + Data actual = privateKey.sign(shortDigest, TWCurveNIST256p1); + EXPECT_EQ(actual.size(), 0ul); + } + { + Data actual = privateKey.sign(shortDigest, TWCurveSECP256k1, isCanonical); + EXPECT_EQ(actual.size(), 0ul); + } +} + +} // namespace TW::tests diff --git a/tests/common/PublicKeyLegacy.h b/tests/common/PublicKeyLegacy.h new file mode 100644 index 00000000000..33007667c76 --- /dev/null +++ b/tests/common/PublicKeyLegacy.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include +#include "Data.h" + +namespace TW::TrezorCrypto { + +/// Verifies a signature for the provided message by using `trezor-crypto` library (legacy implementation). +inline bool verifyNist256p1Signature(const Data& publicKey, const Data& signature, const Data& message) { + return ecdsa_verify_digest(&nist256p1, publicKey.data(), signature.data(), message.data()) == 0; +} + +} // namespace TW::TrezorCrypto diff --git a/tests/common/PublicKeyTests.cpp b/tests/common/PublicKeyTests.cpp new file mode 100644 index 00000000000..d73c1c1ac8a --- /dev/null +++ b/tests/common/PublicKeyTests.cpp @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "PublicKey.h" + +#include "Hash.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "TestUtilities.h" + +#include + +using namespace TW; + +TEST(PublicKeyTests, CreateFromPrivateSecp256k1) { + const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + auto privateKey = PrivateKey(key); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(publicKey.bytes.size(), 33ul); + EXPECT_EQ(hex(publicKey.bytes), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); + EXPECT_EQ(publicKey.isCompressed(), true); + EXPECT_TRUE(PublicKey::isValid(publicKey.bytes, TWPublicKeyTypeSECP256k1)); +} + +TEST(PublicKeyTests, CreateFromDataSecp256k1) { + const Data key = parse_hex("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); + PublicKey publicKey(key, TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(publicKey.bytes), hex(key)); +} + +TEST(PublicKeyTests, CreateInvalid) { + const Data keyInvalid = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32af"); // too short + try { + PublicKey publicKey(keyInvalid, TWPublicKeyTypeSECP256k1); + } catch (const std::invalid_argument&) { + return; // OK + } + FAIL() << "Missing expected exception"; +} + +TEST(PublicKeyTests, CreateBlake) { + const auto privateKeyHex = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; + const auto publicKeyKeyHex = "b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"; + { + auto publicKey = PrivateKey(parse_hex(privateKeyHex)).getPublicKey(TWPublicKeyTypeED25519Blake2b); + EXPECT_EQ(hex(publicKey.bytes), publicKeyKeyHex); + EXPECT_EQ(publicKey.bytes.size(), 32ul); + } + { + const auto publicKey = PublicKey(parse_hex(publicKeyKeyHex), TWPublicKeyTypeED25519Blake2b); + EXPECT_EQ(hex(publicKey.bytes), publicKeyKeyHex); + } +} + +TEST(PublicKeyTests, CompressedExtended) { + const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + auto privateKey = PrivateKey(key); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(publicKey.type, TWPublicKeyTypeSECP256k1); + EXPECT_EQ(publicKey.bytes.size(), 33ul); + EXPECT_EQ(publicKey.isCompressed(), true); + EXPECT_TRUE(PublicKey::isValid(publicKey.bytes, TWPublicKeyTypeSECP256k1)); + EXPECT_EQ(hex(publicKey.bytes), std::string("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1")); + + auto extended = publicKey.extended(); + EXPECT_EQ(extended.type, TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ(extended.bytes.size(), 65ul); + EXPECT_EQ(extended.isCompressed(), false); + EXPECT_TRUE(PublicKey::isValid(extended.bytes, TWPublicKeyTypeSECP256k1Extended)); + EXPECT_EQ(hex(extended.bytes), std::string("0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91")); + + auto compressed = extended.compressed(); + EXPECT_EQ(compressed.type, TWPublicKeyTypeSECP256k1); + EXPECT_TRUE(compressed == publicKey); + EXPECT_EQ(compressed.bytes.size(), 33ul); + EXPECT_EQ(compressed.isCompressed(), true); + EXPECT_TRUE(PublicKey::isValid(compressed.bytes, TWPublicKeyTypeSECP256k1)); + EXPECT_EQ(hex(compressed.bytes), std::string("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1")); + + auto extended2 = extended.extended(); + EXPECT_EQ(extended2.type, TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ(extended2.bytes.size(), 65ul); + EXPECT_EQ(extended2.isCompressed(), false); + + auto compressed2 = compressed.compressed(); + EXPECT_EQ(compressed2.type, TWPublicKeyTypeSECP256k1); + EXPECT_TRUE(compressed2 == publicKey); + EXPECT_EQ(compressed2.bytes.size(), 33ul); + EXPECT_EQ(compressed2.isCompressed(), true); +} + +TEST(PublicKeyTests, CompressedExtendedNist) { + const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + auto privateKey = PrivateKey(key); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); + EXPECT_EQ(publicKey.type, TWPublicKeyTypeNIST256p1); + EXPECT_EQ(publicKey.bytes.size(), 33ul); + EXPECT_EQ(publicKey.isCompressed(), true); + EXPECT_TRUE(PublicKey::isValid(publicKey.bytes, TWPublicKeyTypeNIST256p1)); + EXPECT_EQ(hex(publicKey.bytes), std::string("026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab")); + + auto extended = publicKey.extended(); + EXPECT_EQ(extended.type, TWPublicKeyTypeNIST256p1Extended); + EXPECT_EQ(extended.bytes.size(), 65ul); + EXPECT_EQ(extended.isCompressed(), false); + EXPECT_TRUE(PublicKey::isValid(extended.bytes, TWPublicKeyTypeNIST256p1Extended)); + EXPECT_EQ(hex(extended.bytes), std::string("046d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab918b4fe46ccbf56701fb210d67d91c5779468f6b3fdc7a63692b9b62543f47ae")); + + auto compressed = extended.compressed(); + EXPECT_EQ(compressed.type, TWPublicKeyTypeNIST256p1); + EXPECT_TRUE(compressed == publicKey); + EXPECT_EQ(compressed.bytes.size(), 33ul); + EXPECT_EQ(compressed.isCompressed(), true); + EXPECT_TRUE(PublicKey::isValid(compressed.bytes, TWPublicKeyTypeNIST256p1)); + EXPECT_EQ(hex(compressed.bytes), std::string("026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab")); + + auto extended2 = extended.extended(); + EXPECT_EQ(extended2.type, TWPublicKeyTypeNIST256p1Extended); + EXPECT_EQ(extended2.bytes.size(), 65ul); + EXPECT_EQ(extended2.isCompressed(), false); + + auto compressed2 = compressed.compressed(); + EXPECT_EQ(compressed2.type, TWPublicKeyTypeNIST256p1); + EXPECT_TRUE(compressed2 == publicKey); + EXPECT_EQ(compressed2.bytes.size(), 33ul); + EXPECT_EQ(compressed2.isCompressed(), true); +} + +TEST(PublicKeyTests, CompressedExtendedED25519) { + const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); + auto privateKey = PrivateKey(key); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + EXPECT_EQ(publicKey.type, TWPublicKeyTypeED25519); + EXPECT_EQ(publicKey.bytes.size(), 32ul); + EXPECT_EQ(publicKey.isCompressed(), true); + EXPECT_TRUE(PublicKey::isValid(publicKey.bytes, TWPublicKeyTypeED25519)); + EXPECT_EQ(hex(publicKey.bytes), std::string("4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867")); + + auto extended = publicKey.extended(); + EXPECT_EQ(extended.type, TWPublicKeyTypeED25519); + EXPECT_TRUE(extended == publicKey); + EXPECT_EQ(extended.bytes.size(), 32ul); + EXPECT_EQ(extended.isCompressed(), true); + + auto compressed = publicKey.compressed(); + EXPECT_EQ(compressed.type, TWPublicKeyTypeED25519); + EXPECT_TRUE(compressed == publicKey); + EXPECT_EQ(compressed.bytes.size(), 32ul); + EXPECT_EQ(compressed.isCompressed(), true); +} + +TEST(PublicKeyTests, IsValidWrongType) { + EXPECT_FALSE(PublicKey::isValid(parse_hex("deadbeef"), (enum TWPublicKeyType)99)); +} + +TEST(PublicKeyTests, Verify) { + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + + const char* message = "Hello"; + const Data messageData = TW::data(message); + const Data digest = Hash::sha256(messageData); + + { + const auto signature = privateKey.sign(digest, TWCurveSECP256k1); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_TRUE(publicKey.verify(signature, digest)); + EXPECT_EQ(hex(signature), "0f5d5a9e5fc4b82a625312f3be5d3e8ad017d882de86c72c92fcefa924e894c12071772a14201a3a0debf381b5e8dea39fadb9bcabdc02ee71ab018f55bf717f01"); + } + { + const auto signature = privateKey.sign(digest, TWCurveED25519); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + EXPECT_TRUE(publicKey.verify(signature, digest)); + EXPECT_EQ(hex(signature), "42848abf2641a731e18b8a1fb80eff341a5acebdc56faeccdcbadb960aef775192842fccec344679446daa4d02d264259c8f9aa364164ebe0ebea218581e2e03"); + } + { + const auto signature = privateKey.sign(digest, TWCurveED25519Blake2bNano); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); + EXPECT_TRUE(publicKey.verify(signature, digest)); + EXPECT_EQ(hex(signature), "5c1473944cd0234ebc5a91b2966b9e707a33b936dadd149417a2e53b6b3fc97bef17b767b1690708c74d7b4c8fe48703fd44a6ef59d4cc5b9f88ba992db0a003"); + } + { + const auto signature = privateKey.sign(digest, TWCurveNIST256p1); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1Extended); + EXPECT_TRUE(publicKey.verify(signature, digest)); + EXPECT_EQ(hex(signature), "2e4655831f0c60729583595c103bf0d862af6313e4326f03f512682106c792822f5a9cd21e7d4a3316c2d337e5eee649b09c34f7b4407344f0d32e8d33167d8901"); + } +} + +TEST(PublicKeyTests, ED25519_malleability) { + const auto publicKey = PublicKey(parse_hex("a96e02312b03116ff88a9f3e7cea40f424af43a5c6ca6c8ed4f98969faf46ade"), TWPublicKeyTypeED25519); + + const Data messageData = TW::data("Hello, world!"); + + const Data origSign = parse_hex("ea85a47dcc18b512dfea7c209162abaea4808d77c1ec903dc7ba6e2afa3f9f07e7ed7a20a4e2fa1009db3d1443e937e6abb16ff3c3eaecb798faed7fbb40b008"); + const Data modifiedSign = parse_hex("ea85a47dcc18b512dfea7c209162abaea4808d77c1ec903dc7ba6e2afa3f9f07d4c1707dbe450d69df7735b721e316fbabb16ff3c3eaecb798faed7fbb40b018"); + + EXPECT_TRUE(publicKey.verify(origSign, messageData)); + EXPECT_FALSE(publicKey.verify(modifiedSign, messageData)); +} + +TEST(PublicKeyTests, VerifyAsDER) { + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + + const char* message = "Hello"; + const Data messageData = TW::data(message); + const Data digest = Hash::sha256(messageData); + + const auto signature = privateKey.signAsDER(digest); + EXPECT_EQ(signature.size(), 70ul); + EXPECT_EQ(hex(signature), "304402200f5d5a9e5fc4b82a625312f3be5d3e8ad017d882de86c72c92fcefa924e894c102202071772a14201a3a0debf381b5e8dea39fadb9bcabdc02ee71ab018f55bf717f"); + + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_EQ(hex(publicKey.bytes), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); + + EXPECT_TRUE(publicKey.verifyAsDER(signature, digest)); + + EXPECT_FALSE(publicKey.verify(signature, digest)); + + { // Negative: wrong key type + const auto publicKeyWrong = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1Extended); + EXPECT_FALSE(publicKeyWrong.verifyAsDER(signature, digest)); + } +} + +TEST(PublicKeyTests, VerifyEd25519Extended) { + const auto privateKey = PrivateKey(parse_hex("e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); + + const auto message = TW::data("Hello"); + const auto digest = Hash::sha256(message); + const auto signature = privateKey.sign(digest, TWCurveED25519ExtendedCardano); + const auto valid = publicKey.verify(signature, digest); + + EXPECT_TRUE(valid); +} + +TEST(PublicKeyTests, VerifySchnorr) { + const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto privateKey = PrivateKey(key); + + const Data messageData = TW::data("hello schnorr"); + const Data digest = Hash::sha256(messageData); + + const auto signature = privateKey.signZilliqa(digest); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + EXPECT_TRUE(publicKey.verifyZilliqa(signature, digest)); + EXPECT_EQ(hex(signature), "b8118ccb99563fe014279c957b0a9d563c1666e00367e9896fe541765246964f64a53052513da4e6dc20fdaf69ef0d95b4ca51c87ad3478986cf053c2dd0b853"); +} + +TEST(PublicKeyTests, VerifySchnorrWrongType) { + const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto privateKey = PrivateKey(key); + + const Data messageData = TW::data("hello schnorr"); + const Data digest = Hash::sha256(messageData); + + const auto signature = privateKey.signZilliqa(digest); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); + EXPECT_FALSE(publicKey.verifyZilliqa(signature, digest)); +} + +TEST(PublicKeyTests, RecoverRaw) { + { + const auto message = parse_hex("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb11432"); + const auto signature = parse_hex("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); + { + const auto publicKey = PublicKey::recoverRaw(signature, 1ul, message); + EXPECT_EQ(publicKey.type, TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ(hex(publicKey.bytes), "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed8090329274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0"); + } + { // same data but different recId -> different result + const auto publicKey = PublicKey::recoverRaw(signature, 0ul, message); + EXPECT_EQ(publicKey.type, TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ(hex(publicKey.bytes), "043fc5bf5fec35b6ffe6fd246226d312742a8c296bfa57dd22da509a2e348529b7ddb9faf8afe1ecda3c05e7b2bda47ee1f5a87e952742b22afca560b29d972fcf"); + } + } + { + const auto message = parse_hex("6468eb103d51c9a683b51818fdb73390151c9973831d2cfb4e9587ad54273155"); + const auto signature = parse_hex("92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c646487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e58"); + const auto recovered = PublicKey::recoverRaw(signature, 0ul, message); + EXPECT_EQ(hex(recovered.bytes), "0463ade8ebc212b85e7e4278dc3dcb4f9cc18aab912ef5d302b5d1940e772e9e1a9213522efddad487bbd5dd7907e8e776f918e9a5e4cb51893724e9fe76792a4f"); + } +} + +TEST(PublicKeyTests, SignAndRecoverRaw) { + const auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ(hex(publicKey.bytes), "0463ade8ebc212b85e7e4278dc3dcb4f9cc18aab912ef5d302b5d1940e772e9e1a9213522efddad487bbd5dd7907e8e776f918e9a5e4cb51893724e9fe76792a4f"); + const auto message = parse_hex("6468eb103d51c9a683b51818fdb73390151c9973831d2cfb4e9587ad54273155"); + + // sign + const auto signature = privateKey.sign(message, TWCurveSECP256k1); + EXPECT_EQ(hex(signature), "92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c646487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e5800"); + + // revocer + const auto pubkeyRecovered = PublicKey::recoverRaw(signature, signature[64], message); + EXPECT_EQ(hex(pubkeyRecovered.bytes), hex(publicKey.bytes)); + EXPECT_EQ(hex(pubkeyRecovered.bytes), "0463ade8ebc212b85e7e4278dc3dcb4f9cc18aab912ef5d302b5d1940e772e9e1a9213522efddad487bbd5dd7907e8e776f918e9a5e4cb51893724e9fe76792a4f"); +} + +TEST(PublicKeyTests, RecoverRawNegative) { + const auto message = parse_hex("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb11432"); + const auto signature = parse_hex("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); + // recid >= 4 + EXPECT_EXCEPTION(PublicKey::recoverRaw(signature, 4ul, message), "Invalid recId"); + // signature too short + EXPECT_EXCEPTION(PublicKey::recoverRaw(parse_hex("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcd"), 1ul, message), + "signature too short"); + // Digest too short + EXPECT_EXCEPTION(PublicKey::recoverRaw(signature, 1ul, parse_hex("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb114")), + "digest too short"); +} + +TEST(PublicKeyTests, Recover) { + { + const auto message = parse_hex("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb11432"); + const auto signature = parse_hex("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef80"); + const auto publicKey = PublicKey::recover(signature, message); + EXPECT_EQ(publicKey.type, TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ(hex(publicKey.bytes), + "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed8090329274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0"); + } + + const auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + EXPECT_EQ(hex(publicKey.bytes), "0463ade8ebc212b85e7e4278dc3dcb4f9cc18aab912ef5d302b5d1940e772e9e1a9213522efddad487bbd5dd7907e8e776f918e9a5e4cb51893724e9fe76792a4f"); + { + const auto message = parse_hex("6468eb103d51c9a683b51818fdb73390151c9973831d2cfb4e9587ad54273155"); + const auto signature = parse_hex("92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c646487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e5800"); + const auto recovered = PublicKey::recover(signature, message); + EXPECT_EQ(hex(recovered.bytes), hex(publicKey.bytes)); + } + { // same with v=27 + const auto message = parse_hex("6468eb103d51c9a683b51818fdb73390151c9973831d2cfb4e9587ad54273155"); + const auto signature = parse_hex("92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c646487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e581b"); + const auto recovered = PublicKey::recover(signature, message); + EXPECT_EQ(hex(recovered.bytes), hex(publicKey.bytes)); + } + { // same with v=35+2 + const auto message = parse_hex("6468eb103d51c9a683b51818fdb73390151c9973831d2cfb4e9587ad54273155"); + const auto signature = parse_hex("92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c646487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e5825"); + const auto recovered = PublicKey::recover(signature, message); + EXPECT_EQ(hex(recovered.bytes), hex(publicKey.bytes)); + } +} + +TEST(PublicKeyTests, isValidED25519) { + EXPECT_TRUE(PublicKey::isValid(parse_hex("beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519)); + EXPECT_TRUE(PublicKey(parse_hex("beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519).isValidED25519()); + EXPECT_TRUE(PublicKey::isValid(parse_hex("fc8c425a8a94a55ce42f2c24b2fb2ef5ab4a69142d2d97f6c11e0106c84136d5"), TWPublicKeyTypeED25519)); + EXPECT_TRUE(PublicKey(parse_hex("fc8c425a8a94a55ce42f2c24b2fb2ef5ab4a69142d2d97f6c11e0106c84136d5"), TWPublicKeyTypeED25519).isValidED25519()); + EXPECT_TRUE(PublicKey::isValid(parse_hex("01beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519)); + EXPECT_TRUE(PublicKey(parse_hex("01beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519).isValidED25519()); + // Following 32 bytes are not valid public keys (not on the curve) + EXPECT_TRUE(PublicKey::isValid(parse_hex("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"), TWPublicKeyTypeED25519)); + EXPECT_FALSE(PublicKey(parse_hex("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"), TWPublicKeyTypeED25519).isValidED25519()); + EXPECT_TRUE(PublicKey::isValid(parse_hex("51fdd5feae59d7dcbf5ebea99c05593ebee302577a5486ceac706ed568aa1e0e"), TWPublicKeyTypeED25519)); + EXPECT_FALSE(PublicKey(parse_hex("51fdd5feae59d7dcbf5ebea99c05593ebee302577a5486ceac706ed568aa1e0e"), TWPublicKeyTypeED25519).isValidED25519()); + // invalid input size/format + EXPECT_FALSE(PublicKey::isValid(parse_hex("1234"), TWPublicKeyTypeED25519)); + EXPECT_FALSE(PublicKey::isValid(parse_hex("beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa5279"), TWPublicKeyTypeED25519)); + EXPECT_FALSE(PublicKey::isValid(parse_hex("02beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519)); + EXPECT_FALSE(PublicKey::isValid(parse_hex("0101beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519)); + EXPECT_FALSE(PublicKey(parse_hex("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"), TWPublicKeyTypeSECP256k1).isValidED25519()); +} diff --git a/tests/common/TestUtilities.cpp b/tests/common/TestUtilities.cpp new file mode 100644 index 00000000000..924b8df3cd8 --- /dev/null +++ b/tests/common/TestUtilities.cpp @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include +#include + +using namespace std; + +/// Return a writable temp dir which can be used to create files during testing +string getTestTempDir(void) { + // In general, tests should not use hardcoded "/tmp", but TEST_TMPDIR env var. + const char* fromEnvironment = getenv("TEST_TMPDIR"); + if (fromEnvironment == NULL || fromEnvironment[0] == '\0') { return "/tmp"; } + return string(fromEnvironment); +} + +nlohmann::json loadJson(std::string path) { + std::ifstream stream(path); + nlohmann::json json; + stream >> json; + return json; +} diff --git a/tests/common/TestUtilities.h b/tests/common/TestUtilities.h new file mode 100644 index 00000000000..28ccc30584d --- /dev/null +++ b/tests/common/TestUtilities.h @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define WRAP(type, x) std::shared_ptr(x, type##Delete) +#define WRAPD(x) std::shared_ptr(x, TWDataDelete) +#define WRAPS(x) std::shared_ptr(x, TWStringDelete) +#define STRING(x) std::shared_ptr(TWStringCreateWithUTF8Bytes(x), TWStringDelete) +#define DATA(x) std::shared_ptr(TWDataCreateWithHexString(STRING(x).get()), TWDataDelete) + +inline void assertStringsEqual(const std::shared_ptr& string, const char* expected) { + ASSERT_STREQ(TWStringUTF8Bytes(string.get()), expected); +} + +inline void assertHexEqual(const std::shared_ptr& data, const char* expected) { + auto hex = WRAPS(TWStringCreateWithHexData(data.get())); + assertStringsEqual(hex, expected); +} + + +inline void assertJSONEqual(const nlohmann::json& lhs, const nlohmann::json& rhs) { + ASSERT_EQ(lhs, rhs); +} + +inline void assertJSONEqual(const std::string& lhs, const char* expected) { + auto lhsJson = nlohmann::json::parse(lhs); + auto rhsJson = nlohmann::json::parse(std::string(expected)); + return assertJSONEqual(lhsJson, rhsJson); +} + +inline std::vector* dataFromTWData(TWData* data) { + return const_cast*>(reinterpret_cast*>(data)); +} + +/// Return a writable temp dir which can be used to create files during testing +std::string getTestTempDir(void); + +#define ANY_SIGN(input, coin) \ + {\ + auto inputData = input.SerializeAsString();\ + auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size()));\ + auto outputTWData = WRAPD(TWAnySignerSign(inputTWData.get(), coin));\ + output.ParseFromArray(TWDataBytes(outputTWData.get()), static_cast(TWDataSize(outputTWData.get())));\ + } +#define ANY_PLAN(input, output, coin) \ + {\ + auto inputData = input.SerializeAsString();\ + auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size()));\ + auto outputTWData = WRAPD(TWAnySignerPlan(inputTWData.get(), coin));\ + output.ParseFromArray(TWDataBytes(outputTWData.get()), static_cast(TWDataSize(outputTWData.get())));\ + } +#define DUMP_PROTO(input) \ + { \ + std::string json; \ + google::protobuf::util::MessageToJsonString(input, &json); \ + std::cout<<"dump proto: "<(data.data()), data.size()); diff --git a/tests/WalletConsoleTests.cpp b/tests/common/WalletConsoleTests.cpp similarity index 93% rename from tests/WalletConsoleTests.cpp rename to tests/common/WalletConsoleTests.cpp index ec592aaf2dc..3ad84cb5f2c 100644 --- a/tests/WalletConsoleTests.cpp +++ b/tests/common/WalletConsoleTests.cpp @@ -1,34 +1,32 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "../walletconsole/lib/CommandExecutor.h" #include "../walletconsole/lib/WalletConsole.h" -#include "../walletconsole/lib/Util.h" #include #include #include -using namespace TW; -using namespace TW::WalletConsole; -using namespace std; +namespace TW::WalletConsole::tests { -// Test some command execution +using namespace std; static stringstream outputss; static CommandExecutor cmd(outputss); -static int staticInit() { cmd.init(); return 0; } +static int staticInit() { + cmd.init(); + return 0; +} static int dummyStatic = staticInit(); static const string mnemonic1 = "edge defense waste choose enrich upon flee junk siren film clown finish luggage leader kid quick brick print evidence swap drill paddle truly occur"; int countLines(const string& text) { int lines = 0; - for(int i = 0; i < text.length(); ++i) - { - if (text[i] == '\n') ++lines; + for (auto i = 0ul; i < text.length(); ++i) { + if (text[i] == '\n') + ++lines; } return lines; } @@ -41,7 +39,9 @@ TEST(WalletConsole, loopExit) { stringstream inss; stringstream outss; - inss << "coin eth" << endl << "newKey" << endl << "exit" << endl; + inss << "coin eth" << endl + << "newKey" << endl + << "exit" << endl; TW::WalletConsole::WalletConsole console(inss, outss); console.loop(); string res = outss.str(); @@ -84,27 +84,27 @@ TEST(WalletConsole, coins) { TEST(WalletConsole, coin) { { auto pos = outputss.str().length(); - cmd.executeLine("coin btc"); + cmd.executeLine("coin atom"); string res = outputss.str().substr(pos); - EXPECT_TRUE(res.find("Set active coin to: bitcoin") != string::npos); + EXPECT_TRUE(res.find("Set active coin to: cosmos") != string::npos); } { auto pos = outputss.str().length(); cmd.executeLine("coin ethereum"); string res = outputss.str().substr(pos); - EXPECT_TRUE(res.find("Set active coin to: ethereum") != string::npos); + EXPECT_TRUE(res.find("Set active coin to: ethereum") != string::npos) << res; } { auto pos = outputss.str().length(); cmd.executeLine("coin bitcoin"); string res = outputss.str().substr(pos); - EXPECT_TRUE(res.find("Set active coin to: bitcoin") != string::npos); + EXPECT_TRUE(res.find("Set active coin to: bitcoin") != string::npos) << res; } { auto pos = outputss.str().length(); cmd.executeLine("coin no_such_coin_exists"); string res = outputss.str().substr(pos); - EXPECT_TRUE(res.find("Error: No such coin") != string::npos); + EXPECT_TRUE(res.find("Error: No such coin") != string::npos) << res; } } @@ -127,7 +127,7 @@ TEST(WalletConsole, newkey1) { } TEST(WalletConsole, pubPri1) { - cmd.executeLine("coin btc"); + cmd.executeLine("coin bitcoin"); auto pos1 = outputss.str().length(); cmd.executeLine("pubPri 7d40d6a74e98543f545852989d54712834f9c86eddee89303a2083219749e38c"); string res1 = outputss.str().substr(pos1); @@ -135,7 +135,7 @@ TEST(WalletConsole, pubPri1) { } TEST(WalletConsole, pubPriInvalid) { - cmd.executeLine("coin btc"); + cmd.executeLine("coin bitcoin"); auto pos1 = outputss.str().length(); cmd.executeLine("pubPri Hello!_This_is_an_invalid_private_key"); string res1 = outputss.str().substr(pos1); @@ -143,7 +143,7 @@ TEST(WalletConsole, pubPriInvalid) { } TEST(WalletConsole, priPub) { - cmd.executeLine("coin btc"); + cmd.executeLine("coin bitcoin"); auto pos1 = outputss.str().length(); cmd.executeLine("priPub 0200266ab7dc3efec040cc8b9714ff49cc8339d2f30d9bab8a4b11043e1bdfee37"); string res1 = outputss.str().substr(pos1); @@ -151,7 +151,7 @@ TEST(WalletConsole, priPub) { } TEST(WalletConsole, addrPubBtc1) { - cmd.executeLine("coin btc"); + cmd.executeLine("coin bitcoin"); auto pos0 = outputss.str().length(); cmd.executeLine("addrPub 0200266ab7dc3efec040cc8b9714ff49cc8339d2f30d9bab8a4b11043e1bdfee37"); string res = outputss.str().substr(pos0); @@ -160,7 +160,7 @@ TEST(WalletConsole, addrPubBtc1) { } TEST(WalletConsole, addrPubInvalid) { - cmd.executeLine("coin btc"); + cmd.executeLine("coin bitcoin"); auto pos0 = outputss.str().length(); cmd.executeLine("addrPub Hello!"); string res = outputss.str().substr(pos0); @@ -168,7 +168,7 @@ TEST(WalletConsole, addrPubInvalid) { } TEST(WalletConsole, addrPri1) { - cmd.executeLine("coin btc"); + cmd.executeLine("coin bitcoin"); auto pos1 = outputss.str().length(); cmd.executeLine("addrPri 7d40d6a74e98543f545852989d54712834f9c86eddee89303a2083219749e38c"); string res1 = outputss.str().substr(pos1); @@ -177,7 +177,7 @@ TEST(WalletConsole, addrPri1) { } TEST(WalletConsole, addrPriInvalid) { - cmd.executeLine("coin btc"); + cmd.executeLine("coin bitcoin"); auto pos1 = outputss.str().length(); cmd.executeLine("addrPri Hello!"); string res1 = outputss.str().substr(pos1); @@ -185,7 +185,7 @@ TEST(WalletConsole, addrPriInvalid) { } TEST(WalletConsole, addrInvalid) { - cmd.executeLine("coin btc"); + cmd.executeLine("coin bitcoin"); auto pos1 = outputss.str().length(); cmd.executeLine("addr Hello_This_is_an_Invalid_BTC_Address!_"); string res1 = outputss.str().substr(pos1); @@ -194,7 +194,7 @@ TEST(WalletConsole, addrInvalid) { TEST(WalletConsole, addrDP1) { cmd.executeLine("setMnemonic " + mnemonic1); - cmd.executeLine("coin btc"); + cmd.executeLine("coin bitcoin"); // default DP auto pos1 = outputss.str().length(); @@ -263,7 +263,7 @@ TEST(WalletConsole, newMnemonic) { TEST(WalletConsole, dumpdp) { { - cmd.executeLine("coin btc"); + cmd.executeLine("coin bitcoin"); auto pos1 = outputss.str().length(); cmd.executeLine("dumpDP"); string res1 = outputss.str().substr(pos1); @@ -285,9 +285,8 @@ TEST(WalletConsole, dumpdp) { } } - TEST(WalletConsole, dumpXpub) { - cmd.executeLine("coin btc"); + cmd.executeLine("coin bitcoin"); auto pos1 = outputss.str().length(); cmd.executeLine("setMnemonic " + mnemonic1); string res1 = outputss.str().substr(pos1); @@ -302,7 +301,7 @@ TEST(WalletConsole, dumpXpub) { TEST(WalletConsole, derive) { // Step-by-step derivation, mnemo -> pri -> pub -> addr cmd.executeLine("setMnemonic " + mnemonic1); - cmd.executeLine("coin btc"); + cmd.executeLine("coin bitcoin"); { auto pos1 = outputss.str().length(); cmd.executeLine("priDP m/84'/0'/0'/0/1"); @@ -335,7 +334,7 @@ TEST(WalletConsole, derive) { TEST(WalletConsole, addrDefault) { { cmd.executeLine("setMnemonic " + mnemonic1); - cmd.executeLine("coin btc"); + cmd.executeLine("coin bitcoin"); auto pos1 = outputss.str().length(); cmd.executeLine("addrDefault"); string res1 = outputss.str().substr(pos1); @@ -358,7 +357,7 @@ TEST(WalletConsole, addrDefault) { } TEST(WalletConsole, addrXpub) { - cmd.executeLine("coin btc"); + cmd.executeLine("coin bitcoin"); // no need to set mnemonic here auto pos1 = outputss.str().length(); cmd.executeLine("addrXpub zpub6qvN3x2m4Q96SJJ8Q3ZRbCTm4mGdTny6u2hY8tTiyWznnjwc3rRYpHDb1gN9AAypB5m2x1WR954CLNqpLcAxkxt9x7LX9hKDGp9sGtZca7o 0"); @@ -444,7 +443,6 @@ TEST(WalletConsole, fileWriteRead) { string res1 = outputss.str().substr(pos1); EXPECT_TRUE(res1.find("Written to ") != string::npos); - auto pos2 = outputss.str().length(); cmd.executeLine("fileR " + fileName); string res2 = outputss.str().substr(pos2); @@ -458,14 +456,11 @@ TEST(WalletConsole, fileWriteRead) { EXPECT_TRUE(res3.find("already exists, not overwriting") != string::npos); // clean up created file - try - { + try { std::remove(fileName.c_str()); + } catch (...) { } - catch(...) - { - } - + auto pos4 = outputss.str().length(); cmd.executeLine("fileR __NO_SUCH_FILE__"); string res4 = outputss.str().substr(pos4); @@ -485,3 +480,5 @@ TEST(WalletConsole, harmonyAddressDerivation) { EXPECT_TRUE(res1.find("Result") != string::npos); EXPECT_TRUE(res1.find("rror") == string::npos); } + +} // namespace TW::WalletConsole::tests diff --git a/tests/common/WebAuthnTests.cpp b/tests/common/WebAuthnTests.cpp new file mode 100644 index 00000000000..9faaaf39d02 --- /dev/null +++ b/tests/common/WebAuthnTests.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include "HexCoding.h" +#include +#include + +namespace TW::WebAuthn::tests { + +TEST(WebAuthn, GetPublicKey) { + // C++ + { + const Data& attestationObject = parse_hex("0xa363666d74646e6f6e656761747453746d74a068617574684461746158a4f95bc73828ee210f9fd3bbe72d97908013b0a3759e9aea3d0ae318766cd2e1ad4500000000adce000235bcc60a648b0b25f1f055030020c720eb493e167ce93183dd91f5661e1004ed8cc1be23d3340d92381da5c0c80ca5010203262001215820a620a8cfc88fd062b11eab31663e56cad95278bef612959be214d98779f645b82258204e7b905b42917570148b0432f99ba21f2e7eebe018cbf837247e38150a89f771"); + const auto publicKey = getPublicKey(attestationObject); + ASSERT_EQ(hex(publicKey.value().bytes), "04a620a8cfc88fd062b11eab31663e56cad95278bef612959be214d98779f645b84e7b905b42917570148b0432f99ba21f2e7eebe018cbf837247e38150a89f771"); + } + + // C + { + const auto attestationObject = DATA("0xa363666d74646e6f6e656761747453746d74a068617574684461746158a4f95bc73828ee210f9fd3bbe72d97908013b0a3759e9aea3d0ae318766cd2e1ad4500000000adce000235bcc60a648b0b25f1f055030020c720eb493e167ce93183dd91f5661e1004ed8cc1be23d3340d92381da5c0c80ca5010203262001215820a620a8cfc88fd062b11eab31663e56cad95278bef612959be214d98779f645b82258204e7b905b42917570148b0432f99ba21f2e7eebe018cbf837247e38150a89f771"); + const auto& publicKey = WRAP(TWPublicKey, TWWebAuthnGetPublicKey(attestationObject.get())); + EXPECT_EQ(hex(publicKey->impl.bytes), "04a620a8cfc88fd062b11eab31663e56cad95278bef612959be214d98779f645b84e7b905b42917570148b0432f99ba21f2e7eebe018cbf837247e38150a89f771"); + } +} + +TEST(WebAuthn, GetRSValues) { + + // C + { + const auto signature = DATA("0x30440220766589b461a838748708cdf88444b21b1fa52b57d70671b4f9bf60ad14b372ec022020cc439c9c20661bfa39bbea24a900ec1484b2395eb065ead8ef4e273144a57d"); + const auto& rsValuesData = TWWebAuthnGetRSValues(signature.get()); + const auto& rsValues = hexEncoded(*reinterpret_cast(WRAPD(rsValuesData).get())); + EXPECT_EQ(rsValues, "0x766589b461a838748708cdf88444b21b1fa52b57d70671b4f9bf60ad14b372ec20cc439c9c20661bfa39bbea24a900ec1484b2395eb065ead8ef4e273144a57d"); + } +} + +TEST(WebAuthn, ReconstructOriginalMessage) { + + // C + { + const auto authenticatorData = DATA("0x1a70842af8c1feb7133b81e6a160a6a2be45ee057f0eb6d3f7f5126daa202e071d00000000"); + const auto clientDataJSON = DATA("0x7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a224e5549794f5545774d6b45744e554535517930304d6b5a424c546847516a4174517a52474f4441794d3045304f546b30222c226f726967696e223a2268747470733a2f2f747275737477616c6c65742e636f6d227d"); + + const auto& messageData = TWWebAuthnReconstructOriginalMessage(authenticatorData.get(), clientDataJSON.get()); + const auto& message = hexEncoded(*reinterpret_cast(WRAPD(messageData).get())); + EXPECT_EQ(message, "0x3254cdbd677e6e31e75d2135bad0cf56440d7c6b108c141a3509d76ce45c6731"); + } +} + +} \ No newline at end of file diff --git a/tests/common/algorithm/erase_tests.cpp b/tests/common/algorithm/erase_tests.cpp new file mode 100644 index 00000000000..97b6f06897d --- /dev/null +++ b/tests/common/algorithm/erase_tests.cpp @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "algorithm/erase.h" + +#include "gtest/gtest.h" +#include // std::iota + +TEST(Algorithm, Erase) { + std::vector cnt(10); + std::iota(cnt.begin(), cnt.end(), '0'); + cnt.back() = '3'; + std::size_t nbElementsErased = TW::erase(cnt, '3'); + ASSERT_EQ(cnt.size(), 8ul); + ASSERT_EQ(nbElementsErased, 2ul); +} + +TEST(Algorithm, EraseIf) { + std::vector cnt(10); + std::iota(cnt.begin(), cnt.end(), '0'); + auto erased = TW::erase_if(cnt, [](char x) { return (x - '0') % 2 == 0; }); + ASSERT_EQ(cnt.size(), 5ul); + ASSERT_EQ(erased, 5ul); +} diff --git a/tests/common/algorithm/sort_copy_tests.cpp b/tests/common/algorithm/sort_copy_tests.cpp new file mode 100644 index 00000000000..3bbe1a1c567 --- /dev/null +++ b/tests/common/algorithm/sort_copy_tests.cpp @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "algorithm/sort_copy.h" + +#include "gtest/gtest.h" + +using namespace TW; + +struct Amount { + int value; +}; + +TEST(SortCopy, IsSorted) { + std::vector data{9, 1, 2, 4, 5}; + const auto sorted = sortCopy(data); + std::vector anotherData{Amount{.value = 9}, Amount{.value = 1}, Amount{.value = 0}}; + auto sortFunctor = [](auto&& lhs, auto&& rhs) { return lhs.value < rhs.value; }; + const auto anotherSorted = sortCopy(anotherData, sortFunctor); + ASSERT_TRUE(std::is_sorted(cbegin(sorted), cend(sorted))); + ASSERT_TRUE(std::is_sorted(cbegin(anotherSorted), cend(anotherSorted), sortFunctor)); +} \ No newline at end of file diff --git a/tests/common/algorithm/string.cpp b/tests/common/algorithm/string.cpp new file mode 100644 index 00000000000..1371ddcf8b2 --- /dev/null +++ b/tests/common/algorithm/string.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "algorithm/string.hpp" + +#include "gtest/gtest.h" + +namespace TW::tests { + TEST(Algorithm, StringSplit) { + auto splitted = TW::ssplit("0.0.1", '.'); + ASSERT_EQ(splitted.size(), 3uL); + ASSERT_EQ(splitted[0], "0"); + ASSERT_EQ(splitted[1], "0"); + ASSERT_EQ(splitted[2], "1"); + } + + TEST(Algorithm, StringTrim) { + std::string str = " \t \n Hello, \n World \t \n"; + trim(str); + ASSERT_EQ(str, "Hello, \n World"); + } + + TEST(Algorithm, StringTrimSpecificSymbols) { + std::string str = ".\n Hello. World ..."; + trim(str, "."); + ASSERT_EQ(str, "\n Hello. World "); + } +} diff --git a/tests/common/algorithm/to_array_tests.cpp b/tests/common/algorithm/to_array_tests.cpp new file mode 100644 index 00000000000..033e5faa8e0 --- /dev/null +++ b/tests/common/algorithm/to_array_tests.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "algorithm/to_array.h" + +#include "gtest/gtest.h" + +using namespace TW; + +TEST(Algorithms, ToArray) { + std::string str{"foo"}; + auto value = TW::to_array(str); + auto expected = std::array{"foo"}; + ASSERT_EQ(value, expected); + + std::vector ints{0, 1, 2}; + auto another_value = TW::to_array(ints); + auto expected_ints = std::array{0, 1, 2}; + ASSERT_EQ(another_value, expected_ints); +} diff --git a/tests/common/memory/memzero_tests.cpp b/tests/common/memory/memzero_tests.cpp new file mode 100644 index 00000000000..5edee36dbf7 --- /dev/null +++ b/tests/common/memory/memzero_tests.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "memory/memzero_wrapper.h" + +#include "gtest/gtest.h" + +struct int_wrapper { + int value; +}; + +TEST(Memory, Memzero) { + int_wrapper obj{.value = 42}; + TW::memzero(&obj); + ASSERT_EQ(obj.value, 0); + obj.value = 42; + ASSERT_EQ(obj.value, 42); + TW::memzero(&obj, sizeof(int_wrapper)); + ASSERT_EQ(obj.value, 0); +} diff --git a/tests/common/operators/equality_comparable_tests.cpp b/tests/common/operators/equality_comparable_tests.cpp new file mode 100644 index 00000000000..ce54cd16e57 --- /dev/null +++ b/tests/common/operators/equality_comparable_tests.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "operators/equality_comparable.h" + +#include "gtest/gtest.h" + +namespace TW::operators::tests { + +struct Amount : equality_comparable { + int value; + friend bool operator==(const Amount& lhs, const Amount& rhs) { return lhs.value == rhs.value; } +}; + +TEST(Operators, EqualityComparable) { + ASSERT_TRUE(Amount{.value = 1} != Amount{.value = 2}); + ASSERT_TRUE(Amount{.value = 1} == Amount{.value = 1}); +} + +} // namespace TW::operators::tests diff --git a/tests/interface/TWAESTests.cpp b/tests/interface/TWAESTests.cpp index 8f1229ae5ca..52675f2aaab 100644 --- a/tests/interface/TWAESTests.cpp +++ b/tests/interface/TWAESTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include diff --git a/tests/interface/TWAccountTests.cpp b/tests/interface/TWAccountTests.cpp index cfcf75d3ce9..afc84a58498 100644 --- a/tests/interface/TWAccountTests.cpp +++ b/tests/interface/TWAccountTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include #include diff --git a/tests/interface/TWAnyAddressTests.cpp b/tests/interface/TWAnyAddressTests.cpp index bf6e4e98c42..1068efe40fb 100644 --- a/tests/interface/TWAnyAddressTests.cpp +++ b/tests/interface/TWAnyAddressTests.cpp @@ -1,20 +1,19 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include "HexCoding.h" #include #include +#include #include using namespace TW; -TEST(AnyAddress, InvalidString) { +TEST(TWAnyAddress, InvalidString) { auto string = STRING("0x4E5B2e1dc63F6b91cb6Cd759936495434C7e972F"); auto btcAddress = TWAnyAddressCreateWithString(string.get(), TWCoinTypeBitcoin); auto ethAaddress = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeEthereum)); @@ -24,7 +23,7 @@ TEST(AnyAddress, InvalidString) { ASSERT_EQ(TWAnyAddressCoin(ethAaddress.get()), TWCoinTypeEthereum); } -TEST(AnyAddress, Data) { +TEST(TWAnyAddress, Data) { // ethereum { auto string = STRING("0x4E5B2e1dc63F6b91cb6Cd759936495434C7e972F"); @@ -140,10 +139,10 @@ TEST(AnyAddress, Data) { auto keyHash = WRAPD(TWAnyAddressData(addr.get())); assertHexEqual(keyHash, "172bdf43265c0adfe105a1a8c45b3f406a38362f24"); } - // elrond + // multiversx { auto string = STRING("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"); - auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeElrond)); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypeMultiversX)); auto pubkey = WRAPD(TWAnyAddressData(addr.get())); assertHexEqual(pubkey, "fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293"); } @@ -169,3 +168,54 @@ TEST(AnyAddress, Data) { assertHexEqual(keyHash, "18f9d8d877393bbbe8d697a8a2e52879cc7e84f467656d1cce6bab5a8d2637ec"); } } + +TEST(TWAnyAddress, createFromPubKey) { + constexpr auto pubkey = "02753f5c275e1847ba4d2fd3df36ad00af2e165650b35fe3991e9c9c46f68b12bc"; + const auto pubkey_twstring = STRING(pubkey); + const auto pubkey_data = WRAPD(TWDataCreateWithHexString(pubkey_twstring.get())); + const auto pubkey_obj = WRAP(TWPublicKey, TWPublicKeyCreateWithData(pubkey_data.get(), TWPublicKeyTypeSECP256k1)); + + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(pubkey_obj.get(), TWCoinTypeBitcoin)); + + assertStringsEqual(WRAPS(TWAnyAddressDescription(addr.get())), "bc1qcj2vfjec3c3luf9fx9vddnglhh9gawmncmgxhz"); +} + +TEST(TWAnyAddress, createFromPubKeyDerivation) { + constexpr auto pubkey = "02753f5c275e1847ba4d2fd3df36ad00af2e165650b35fe3991e9c9c46f68b12bc"; + const auto pubkey_twstring = STRING(pubkey); + const auto pubkey_data = WRAPD(TWDataCreateWithHexString(pubkey_twstring.get())); + const auto pubkey_obj = WRAP(TWPublicKey, TWPublicKeyCreateWithData(pubkey_data.get(), TWPublicKeyTypeSECP256k1)); + + { + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(pubkey_obj.get(), TWCoinTypeBitcoin, TWDerivationDefault)); + assertStringsEqual(WRAPS(TWAnyAddressDescription(addr.get())), "bc1qcj2vfjec3c3luf9fx9vddnglhh9gawmncmgxhz"); + } + { + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(pubkey_obj.get(), TWCoinTypeBitcoin, TWDerivationBitcoinLegacy)); + assertStringsEqual(WRAPS(TWAnyAddressDescription(addr.get())), "1JvRfEQFv5q5qy9uTSAezH7kVQf4hqnHXx"); + } + { + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(pubkey_obj.get(), TWCoinTypeBitcoin, TWDerivationBitcoinTestnet)); + assertStringsEqual(WRAPS(TWAnyAddressDescription(addr.get())), "tb1qcj2vfjec3c3luf9fx9vddnglhh9gawmnjan4v3"); + } +} + +TEST(TWAnyAddress, createFromPubKeyFilecoinAddressType) { + constexpr auto pubkey = "0419bf99082cf2fcdaa812d6eba1eba9036ff3a3d84c1817c84954d4e8ae283fec5313e427a0f5f68dec3169b2eda876b1d9f97b1ede7f958baee6a2ce78f6e94a"; + const auto pubkey_twstring = STRING(pubkey); + const auto pubkey_data = WRAPD(TWDataCreateWithHexString(pubkey_twstring.get())); + const auto pubkey_obj = WRAP(TWPublicKey, TWPublicKeyCreateWithData(pubkey_data.get(), TWPublicKeyTypeSECP256k1Extended)); + + { + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyFilecoinAddressType(pubkey_obj.get(), TWFilecoinAddressTypeDefault)); + const auto actual = WRAPS(TWAnyAddressDescription(addr.get())); + assertStringsEqual(actual, "f1syn25x7infncgfvodhriq2dudvmudabtavm3wyy"); + ASSERT_TRUE(TWAnyAddressIsValid(actual.get(), TWCoinTypeFilecoin)); + } + { + const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyFilecoinAddressType(pubkey_obj.get(), TWFilecoinAddressTypeDelegated)); + const auto actual = WRAPS(TWAnyAddressDescription(addr.get())); + assertStringsEqual(actual, "f410fvak24cyg3saddajborn6idt7rrtfj2ptauk5pbq"); + ASSERT_TRUE(TWAnyAddressIsValid(actual.get(), TWCoinTypeFilecoin)); + } +} diff --git a/tests/interface/TWAsnParserTests.cpp b/tests/interface/TWAsnParserTests.cpp new file mode 100644 index 00000000000..ce0ccf0266e --- /dev/null +++ b/tests/interface/TWAsnParserTests.cpp @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include + +#include + +TEST(TWAsnParser, EcdsaSignatureFromDer) { + auto encoded = DATA("3046022100db421231f23d0320dbb8f1284b600cd34b8e9218628139539ff4f1f6c05495da022100ff715aab70d5317dbf8ee224eb18bec3120cfb9db1000dbb31eadaf96c71c1b1"); + auto decodedResult = WRAPD(TWAsnParserEcdsaSignatureFromDer(encoded.get())); + assertHexEqual(decodedResult, "db421231f23d0320dbb8f1284b600cd34b8e9218628139539ff4f1f6c05495daff715aab70d5317dbf8ee224eb18bec3120cfb9db1000dbb31eadaf96c71c1b1"); + + auto invalid = DATA(""); + ASSERT_EQ(TWAsnParserEcdsaSignatureFromDer(invalid.get()), nullptr); +} diff --git a/tests/interface/TWBase32Tests.cpp b/tests/interface/TWBase32Tests.cpp new file mode 100644 index 00000000000..da8fd615984 --- /dev/null +++ b/tests/interface/TWBase32Tests.cpp @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include "Data.h" +#include + +#include + +TEST(TWBase32, InvalidDecode) { + const auto encodedInput = STRING("JBSWY3DPK5XXE3DE======="); + auto result = WRAPD(TWBase32Decode(encodedInput.get())); + ASSERT_EQ(result, nullptr); +} + +TEST(TWBase32, Decode) { + const auto encodedInput = STRING("JBSWY3DPK5XXE3DE"); + auto result = WRAPD(TWBase32Decode(encodedInput.get())); + + ASSERT_NE(result, nullptr); + ASSERT_EQ(TWDataSize(result.get()), 10ul); + + auto data = *reinterpret_cast(result.get()); + std::string str(data.begin(), data.end()); + + ASSERT_EQ(str, "HelloWorld"); +} + +TEST(TWBase32, DecodeWithAlphabet) { + const auto encodedInput = STRING("g52w64jworydimrxov5hmn3gpj2gwyttnzxdmndjo5xxiztsojuxg5dxobzhs6i"); + const auto filecoinAlphabet = STRING("abcdefghijklmnopqrstuvwxyz234567"); + auto result = WRAPD(TWBase32DecodeWithAlphabet(encodedInput.get(), filecoinAlphabet.get())); + + ASSERT_NE(result, nullptr); + ASSERT_EQ(TWDataSize(result.get()), 39ul); + + auto data = *reinterpret_cast(result.get()); + std::string str(data.begin(), data.end()); + + ASSERT_EQ(str, "7uoq6tp427uzv7fztkbsnn64iwotfrristwpryy"); +} + +TEST(TWBase32, Encode) { + TW::Data data{'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'}; + auto encodedStr = TWBase32Encode(&data); + std::string result = TWStringUTF8Bytes(encodedStr); + + ASSERT_EQ(result, "JBSWY3DPK5XXE3DE"); + + TWStringDelete(encodedStr); +} + +TEST(TWBase32, EncodeWithAlphabet) { + const auto filecoinAlphabet = STRING("abcdefghijklmnopqrstuvwxyz234567"); + TW::Data data{'7', 'u', 'o', 'q', '6', 't', 'p', '4', '2', '7', 'u', 'z', 'v', '7', 'f', + 'z', 't', 'k', 'b', 's', 'n', 'n', '6', '4', 'i', 'w', 'o', 't', 'f', 'r', 'r', 'i', 's', 't', 'w', 'p', 'r', 'y', 'y'}; + auto encodedStr = TWBase32EncodeWithAlphabet(&data, filecoinAlphabet.get()); + std::string result = TWStringUTF8Bytes(encodedStr); + + ASSERT_EQ(result, "g52w64jworydimrxov5hmn3gpj2gwyttnzxdmndjo5xxiztsojuxg5dxobzhs6i"); + + TWStringDelete(encodedStr); +} diff --git a/tests/interface/TWBase58Tests.cpp b/tests/interface/TWBase58Tests.cpp index 79542447036..b0c4e17b3e6 100644 --- a/tests/interface/TWBase58Tests.cpp +++ b/tests/interface/TWBase58Tests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include diff --git a/tests/interface/TWBase64Tests.cpp b/tests/interface/TWBase64Tests.cpp new file mode 100644 index 00000000000..bf313b3be60 --- /dev/null +++ b/tests/interface/TWBase64Tests.cpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" + +#include "Data.h" +#include + +#include + +TEST(TWBase64, Decode) { + const auto encodedInput = STRING("Kyc/YWI="); + auto result = WRAPD(TWBase64Decode(encodedInput.get())); + + ASSERT_EQ(TWDataSize(result.get()), 5ul); + + auto data = *reinterpret_cast(result.get()); + std::string str(data.begin(), data.end()); + + ASSERT_EQ(str, "+\'?ab"); +} + +TEST(TWBase64, Encode) { + TW::Data data{'+', '\'', '?', 'a', 'b'}; + auto encodedStr = TWBase64Encode(&data); + std::string result = TWStringUTF8Bytes(encodedStr); + + ASSERT_EQ(result, "Kyc/YWI="); + + TWStringDelete(encodedStr); +} + +TEST(TWBase64, DecodeUrl) { + const auto encodedInput = STRING("Kyc_YWI="); + auto result = WRAPD(TWBase64DecodeUrl(encodedInput.get())); + + ASSERT_EQ(TWDataSize(result.get()), 5ul); + + auto data = *reinterpret_cast(result.get()); + std::string str(data.begin(), data.end()); + + ASSERT_EQ(str, "+\'?ab"); +} + +TEST(TWBase64, EncodeUrl) { + TW::Data data{'+', '\'', '?', 'a', 'b'}; + auto encodedStr = TWBase64EncodeUrl(&data); + std::string result = TWStringUTF8Bytes(encodedStr); + + ASSERT_EQ(result, "Kyc_YWI="); + + TWStringDelete(encodedStr); +} diff --git a/tests/interface/TWCoinTypeTests.cpp b/tests/interface/TWCoinTypeTests.cpp index 4cc2fc3877b..b7547ae34a9 100644 --- a/tests/interface/TWCoinTypeTests.cpp +++ b/tests/interface/TWCoinTypeTests.cpp @@ -1,12 +1,11 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include +#include #include @@ -28,7 +27,7 @@ TEST(TWCoinType, TWPurpose) { ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeBandChain)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeBluzelle)); ASSERT_EQ(TWPurposeBIP1852, TWCoinTypePurpose(TWCoinTypeCardano)); - ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeElrond)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeMultiversX)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeOasis)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTHORChain)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeCryptoOrg)); @@ -55,8 +54,8 @@ TEST(TWCoinType, TWPurpose) { ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeStellar)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTezos)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTheta)); - ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeThunderToken)); - ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTomoChain)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeThunderCore)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeViction)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeTron)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeVeChain)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeWanchain)); @@ -66,6 +65,7 @@ TEST(TWCoinType, TWPurpose) { ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeRavencoin)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeWaves)); ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeNEO)); + ASSERT_EQ(TWPurposeBIP44, TWCoinTypePurpose(TWCoinTypeNebl)); } TEST(TWCoinType, TWHDVersion) { @@ -99,8 +99,8 @@ TEST(TWCoinType, TWPublicKeyType) { ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeKava)); ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeBandChain)); ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeBluzelle)); - ASSERT_EQ(TWPublicKeyTypeED25519Extended, TWCoinTypePublicKeyType(TWCoinTypeCardano)); - ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeElrond)); + ASSERT_EQ(TWPublicKeyTypeED25519Cardano, TWCoinTypePublicKeyType(TWCoinTypeCardano)); + ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeMultiversX)); ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeOasis)); ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeTHORChain)); ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeCryptoOrg)); @@ -127,8 +127,8 @@ TEST(TWCoinType, TWPublicKeyType) { ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeStellar)); ASSERT_EQ(TWPublicKeyTypeED25519, TWCoinTypePublicKeyType(TWCoinTypeTezos)); ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeTheta)); - ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeThunderToken)); - ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeTomoChain)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeThunderCore)); + ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeViction)); ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeTron)); ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeVeChain)); ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeWanchain)); @@ -138,4 +138,39 @@ TEST(TWCoinType, TWPublicKeyType) { ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeRavencoin)); ASSERT_EQ(TWPublicKeyTypeCURVE25519, TWCoinTypePublicKeyType(TWCoinTypeWaves)); ASSERT_EQ(TWPublicKeyTypeNIST256p1, TWCoinTypePublicKeyType(TWCoinTypeNEO)); -} \ No newline at end of file + ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeNebl)); +} + +TEST(TWCoinType, ValidateAddress) { + ASSERT_TRUE(TWCoinTypeValidate(TWCoinTypeBitcoin, STRING("12dNaXQtN5Asn2YFwT1cvciCrJa525fAe4").get())); + ASSERT_TRUE(TWCoinTypeValidate(TWCoinTypeBitcoin, STRING("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4").get())); +} + +TEST(TWCoinType, DeriveAddress) { + auto pkData = DATA("0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"); + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(pkData.get(), TWPublicKeyTypeSECP256k1)); + + auto address = WRAPS(TWCoinTypeDeriveAddressFromPublicKeyAndDerivation(TWCoinTypeBitcoin, publicKey.get(), TWDerivationBitcoinSegwit)); + assertStringsEqual(address, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"); +} + +TEST(TWCoinType, TWCoinTypeDerivationPath) { + auto res = TWCoinTypeDerivationPath(TWCoinTypeBitcoin); + auto result = *reinterpret_cast(res); + ASSERT_EQ(result, "m/84'/0'/0'/0/0"); + TWStringDelete(res); +} + +TEST(TWCoinType, TWCoinTypeDerivationPathWithDerivation) { + auto res = TWCoinTypeDerivationPathWithDerivation(TWCoinTypeBitcoin, TWDerivationBitcoinLegacy); + auto result = *reinterpret_cast(res); + ASSERT_EQ(result, "m/44'/0'/0'/0/0"); + TWStringDelete(res); +} + +TEST(TWCoinType, TWCoinTypeDerivationPathWithDerivationSolana) { + auto res = TWCoinTypeDerivationPathWithDerivation(TWCoinTypeSolana, TWDerivationSolanaSolana); + auto result = *reinterpret_cast(res); + ASSERT_EQ(result, "m/44'/501'/0'/0'"); + TWStringDelete(res); +} diff --git a/tests/interface/TWCryptoBoxTests.cpp b/tests/interface/TWCryptoBoxTests.cpp new file mode 100644 index 00000000000..cb9a3335c54 --- /dev/null +++ b/tests/interface/TWCryptoBoxTests.cpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include "TrustWalletCore/TWCryptoBox.h" +#include "TrustWalletCore/TWCryptoBoxPublicKey.h" +#include "TrustWalletCore/TWCryptoBoxSecretKey.h" + +#include + +TEST(TWCryptoBox, EncryptDecryptEasy) { + const auto mySecret = WRAP(TWCryptoBoxSecretKey, TWCryptoBoxSecretKeyCreate()); + const auto myPubkey = WRAP(TWCryptoBoxPublicKey, TWCryptoBoxSecretKeyGetPublicKey(mySecret.get())); + + const auto otherSecret = WRAP(TWCryptoBoxSecretKey, TWCryptoBoxSecretKeyCreate()); + const auto otherPubkey = WRAP(TWCryptoBoxPublicKey, TWCryptoBoxSecretKeyGetPublicKey(otherSecret.get())); + + const auto message = "Well done is better than well said. -Benjamin Franklin"; + const auto messageData = WRAPD(TWDataCreateWithBytes(reinterpret_cast(message), strlen(message))); + + const auto encrypted = WRAPD(TWCryptoBoxEncryptEasy(mySecret.get(), otherPubkey.get(), messageData.get())); + + // Step 2. Make sure the Box can be decrypted by the other side. + const auto decrypted = WRAPD(TWCryptoBoxDecryptEasy(otherSecret.get(), myPubkey.get(), encrypted.get())); + const auto decryptedData = dataFromTWData(decrypted.get()); + std::string decryptedMessage(decryptedData->begin(), decryptedData->end()); + + EXPECT_EQ(decryptedMessage, message); +} + +TEST(TWCryptoBox, PublicKeyWithData) { + auto pubkeyBytesHex = "afccabc5b28a8a1fd1cd880516f9c854ae2498d0d1b978b53a59f38e4ae55747"; + auto pubkeyBytes = DATA(pubkeyBytesHex); + + ASSERT_TRUE(TWCryptoBoxPublicKeyIsValid(pubkeyBytes.get())); + const auto publicKey = WRAP(TWCryptoBoxPublicKey, TWCryptoBoxPublicKeyCreateWithData(pubkeyBytes.get())); + const auto actualBytes = WRAPD(TWCryptoBoxPublicKeyData(publicKey.get())); + assertHexEqual(actualBytes, pubkeyBytesHex); +} + +TEST(TWCryptoBox, SecretKeyWithData) { + auto secretBytesHex = "dd87000d4805d6fbd89ae1352f5e4445648b79d5e901c92aebcb610e9be468e4"; + auto secretBytes = DATA(secretBytesHex); + + ASSERT_TRUE(TWCryptoBoxSecretKeyIsValid(secretBytes.get())); + const auto publicKey = WRAP(TWCryptoBoxSecretKey, TWCryptoBoxSecretKeyCreateWithData(secretBytes.get())); + const auto actualBytes = WRAPD(TWCryptoBoxSecretKeyData(publicKey.get())); + assertHexEqual(actualBytes, secretBytesHex); +} + +TEST(TWCryptoBox, DecryptEasyError) { + auto otherPubkeyBytes = DATA("afccabc5b28a8a1fd1cd880516f9c854ae2498d0d1b978b53a59f38e4ae55747"); + + const auto mySecret = WRAP(TWCryptoBoxSecretKey, TWCryptoBoxSecretKeyCreate()); + const auto otherPubkey = WRAP(TWCryptoBoxPublicKey, TWCryptoBoxPublicKeyCreateWithData(otherPubkeyBytes.get())); + + // The given encrypted box cannot be decrypted by using `mySecret` and `otherPubkey`. + const auto invalidEncrypted = DATA("7a7b9c8fee6e3c597512848c7d513e7131193cdfd410ff6611522fdeea99d7160873182019d7a18502f22c5e3644d26a2b669365"); + + const auto* decrypted = TWCryptoBoxDecryptEasy(mySecret.get(), otherPubkey.get(), invalidEncrypted.get()); + ASSERT_EQ(decrypted, nullptr); +} diff --git a/tests/interface/TWDataTests.cpp b/tests/interface/TWDataTests.cpp index 6e648769c57..9c1a69c3b07 100644 --- a/tests/interface/TWDataTests.cpp +++ b/tests/interface/TWDataTests.cpp @@ -1,18 +1,16 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include TEST(TWData, CreateWithHexString) { { const auto data = WRAPD(TWDataCreateWithHexString(STRING("deadbeef").get())); - ASSERT_EQ(TWDataSize(data.get()), 4); + ASSERT_EQ(TWDataSize(data.get()), 4ul); EXPECT_EQ(TWDataBytes(data.get())[0], 0xde); EXPECT_EQ(TWDataBytes(data.get())[1], 0xad); EXPECT_EQ(TWDataBytes(data.get())[2], 0xbe); @@ -22,7 +20,7 @@ TEST(TWData, CreateWithHexString) { { const auto data = WRAPD(TWDataCreateWithHexString(STRING("00").get())); - ASSERT_EQ(TWDataSize(data.get()), 1); + ASSERT_EQ(TWDataSize(data.get()), 1ul); EXPECT_EQ(TWDataBytes(data.get())[0], 0); assertHexEqual(data, "00"); } @@ -60,10 +58,10 @@ TEST(TWData, CreateWithBytes) { } TEST(TWData, CreateWithSize) { - int n = 12; + std::size_t n = 12; const auto data = WRAPD(TWDataCreateWithSize(n)); ASSERT_EQ(TWDataSize(data.get()), n); - for (int i = 0; i < n; ++i) { + for (auto i = 0ul; i < n; ++i) { EXPECT_EQ(TWDataBytes(data.get())[i], 0); } } diff --git a/tests/interface/TWDataVectorTests.cpp b/tests/interface/TWDataVectorTests.cpp index 4f25c22acdf..670d50edeba 100644 --- a/tests/interface/TWDataVectorTests.cpp +++ b/tests/interface/TWDataVectorTests.cpp @@ -1,12 +1,10 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include #include "HexCoding.h" -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include @@ -17,7 +15,7 @@ TEST(TWDataVector, CreateDelete) { auto vec = TWDataVectorCreate(); ASSERT_TRUE(vec != nullptr); - EXPECT_EQ(TWDataVectorSize(vec), 0); + EXPECT_EQ(TWDataVectorSize(vec), 0ul); TWDataVectorDelete(vec); } @@ -26,7 +24,7 @@ TEST(TWDataVector, CreateWrapAutoDelete) { auto vec = WRAP(TWDataVector, TWDataVectorCreate()); ASSERT_TRUE(vec.get() != nullptr); - EXPECT_EQ(TWDataVectorSize(vec.get()), 0); + EXPECT_EQ(TWDataVectorSize(vec.get()), 0ul); } TEST(TWDataVector, CreateWithData) { @@ -35,7 +33,7 @@ TEST(TWDataVector, CreateWithData) { const auto vec = WRAP(TWDataVector, TWDataVectorCreateWithData(elem1.get())); ASSERT_TRUE(vec.get() != nullptr); - ASSERT_EQ(TWDataVectorSize(vec.get()), 1); + ASSERT_EQ(TWDataVectorSize(vec.get()), 1ul); const auto readElem1 = WRAPD(TWDataVectorGet(vec.get(), 0)); EXPECT_EQ(hex(*static_cast(readElem1.get())), "deadbeef"); @@ -45,13 +43,13 @@ TEST(TWDataVector, Add) { const auto vec = WRAP(TWDataVector, TWDataVectorCreate()); ASSERT_TRUE(vec.get() != nullptr); - EXPECT_EQ(TWDataVectorSize(vec.get()), 0); + EXPECT_EQ(TWDataVectorSize(vec.get()), 0ul); const auto elem1d = parse_hex("deadbeef"); const auto elem1 = WRAPD(TWDataCreateWithBytes(elem1d.data(), elem1d.size())); TWDataVectorAdd(vec.get(), elem1.get()); - ASSERT_EQ(TWDataVectorSize(vec.get()), 1); + ASSERT_EQ(TWDataVectorSize(vec.get()), 1ul); const auto readElem1 = WRAPD(TWDataVectorGet(vec.get(), 0)); EXPECT_EQ(hex(*static_cast(readElem1.get())), "deadbeef"); @@ -59,7 +57,7 @@ TEST(TWDataVector, Add) { const auto elem2 = WRAPD(TWDataCreateWithBytes(elem2d.data(), elem2d.size())); TWDataVectorAdd(vec.get(), elem2.get()); - ASSERT_EQ(TWDataVectorSize(vec.get()), 2); + ASSERT_EQ(TWDataVectorSize(vec.get()), 2ul); const auto readElem2 = WRAPD(TWDataVectorGet(vec.get(), 1)); EXPECT_EQ(hex(*static_cast(readElem2.get())), "0202"); } @@ -70,7 +68,7 @@ TEST(TWDataVector, Get) { const auto vec = WRAP(TWDataVector, TWDataVectorCreateWithData(elem1.get())); ASSERT_TRUE(vec.get() != nullptr); - ASSERT_EQ(TWDataVectorSize(vec.get()), 1); + ASSERT_EQ(TWDataVectorSize(vec.get()), 1ul); { // Get element const auto readElem1 = WRAPD(TWDataVectorGet(vec.get(), 0)); diff --git a/tests/interface/TWDerivationPathTests.cpp b/tests/interface/TWDerivationPathTests.cpp new file mode 100644 index 00000000000..24284ec8ef4 --- /dev/null +++ b/tests/interface/TWDerivationPathTests.cpp @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include "TrustWalletCore/TWDerivation.h" +#include "TrustWalletCore/TWPurpose.h" +#include +#include + +#include + +TEST(TWDerivationPath, CreateWithString) { + const auto derivationPath = STRING("m/84'/1'/2'/3/4"); + + const auto path = WRAP(TWDerivationPath, TWDerivationPathCreateWithString(derivationPath.get())); + + EXPECT_EQ(5u, TWDerivationPathIndicesCount(path.get())); + EXPECT_EQ(TWPurposeBIP84, TWDerivationPathPurpose(path.get())); + EXPECT_EQ(1u, TWDerivationPathCoin(path.get())); + EXPECT_EQ(2u, TWDerivationPathAccount(path.get())); + EXPECT_EQ(3u, TWDerivationPathChange(path.get())); + EXPECT_EQ(4u, TWDerivationPathAddress(path.get())); + assertStringsEqual(WRAPS(TWDerivationPathDescription(path.get())), "m/84'/1'/2'/3/4"); + + const auto index0 = WRAP(TWDerivationPathIndex, TWDerivationPathIndexAt(path.get(), 0)); + const auto index3 = WRAP(TWDerivationPathIndex, TWDerivationPathIndexAt(path.get(), 3)); + + EXPECT_EQ(NULL, TWDerivationPathIndexAt(path.get(), 10)); + EXPECT_EQ(TWPurposeBIP84, TWDerivationPathIndexValue(index0.get())); + EXPECT_TRUE(TWDerivationPathIndexHardened(index0.get())); + + EXPECT_EQ(3u, TWDerivationPathIndexValue(index3.get())); + EXPECT_FALSE(TWDerivationPathIndexHardened(index3.get())); + assertStringsEqual(WRAPS(TWDerivationPathIndexDescription(index0.get())), "84'"); + + const auto path2 = WRAP(TWDerivationPath, TWDerivationPathCreateWithString(STRING("m/44'/501'").get())); + + EXPECT_EQ(2u, TWDerivationPathIndicesCount(path2.get())); + EXPECT_EQ(TWPurposeBIP44, TWDerivationPathPurpose(path2.get())); + EXPECT_EQ(501u, TWDerivationPathCoin(path2.get())); + EXPECT_EQ(0u, TWDerivationPathAccount(path2.get())); + EXPECT_EQ(0u, TWDerivationPathChange(path2.get())); + EXPECT_EQ(0u, TWDerivationPathAddress(path2.get())); +} + +TEST(TWDerivationPath, CreateWithCoin) { + + const auto path = WRAP(TWDerivationPath, TWDerivationPathCreate(TWPurposeBIP44, 60, 0, 0, 0)); + + EXPECT_EQ(5u, TWDerivationPathIndicesCount(path.get())); + EXPECT_EQ(TWPurposeBIP44, TWDerivationPathPurpose(path.get())); + EXPECT_EQ(60u, TWDerivationPathCoin(path.get())); + EXPECT_EQ(0u, TWDerivationPathAccount(path.get())); + EXPECT_EQ(0u, TWDerivationPathChange(path.get())); + EXPECT_EQ(0u, TWDerivationPathAddress(path.get())); + assertStringsEqual(WRAPS(TWDerivationPathDescription(path.get())), "m/44'/60'/0'/0/0"); + + const auto index = WRAP(TWDerivationPathIndex, TWDerivationPathIndexCreate(44, true)); + const auto description = WRAPS(TWDerivationPathIndexDescription(index.get())); + assertStringsEqual(description, "44'"); +} diff --git a/tests/interface/TWHDWalletTests.cpp b/tests/interface/TWHDWalletTests.cpp index acf63a99a26..91d183be300 100644 --- a/tests/interface/TWHDWalletTests.cpp +++ b/tests/interface/TWHDWalletTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include "Coin.h" @@ -17,6 +15,10 @@ #include #include #include +#include +#include +#include +#include #include #include "HexCoding.h" @@ -27,8 +29,8 @@ using namespace TW; const auto wordsStr = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; -const auto words = STRING(wordsStr); -const auto passphrase = STRING("TREZOR"); +const auto gWords = STRING(wordsStr); +const auto gPassphrase = STRING("TREZOR"); const auto seedHex = "7ae6f661157bda6492f6162701e570097fc726b6235011ea5ad09bf04986731ed4d92bc43cbdee047b60ea0dd1b1fa4274377c9bf5bd14ab1982c272d8076f29"; const auto entropyHex = "ba5821e8c356c05ba5f025d9532fe0f21f65d594"; @@ -49,31 +51,36 @@ inline void assertEntropyEq(const std::shared_ptr& wallet, const cha } TEST(HDWallet, CreateFromMnemonic) { - const auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + const auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); assertMnemonicEq(wallet, wordsStr); assertEntropyEq(wallet, entropyHex); assertSeedEq(wallet, seedHex); } TEST(HDWallet, CreateFromEntropy) { - const auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithEntropy(DATA(entropyHex).get(), passphrase.get())); + const auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithEntropy(DATA(entropyHex).get(), gPassphrase.get())); assertMnemonicEq(wallet, wordsStr); assertSeedEq(wallet, seedHex); assertEntropyEq(wallet, entropyHex); } TEST(HDWallet, Generate) { - const auto wallet = WRAP(TWHDWallet, TWHDWalletCreate(128, passphrase.get())); + const auto wallet = WRAP(TWHDWallet, TWHDWalletCreate(128, gPassphrase.get())); EXPECT_TRUE(TWMnemonicIsValid(WRAPS(TWHDWalletMnemonic(wallet.get())).get())); } TEST(HDWallet, SeedWithExtraSpaces) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); assertSeedEq(wallet, "7ae6f661157bda6492f6162701e570097fc726b6235011ea5ad09bf04986731ed4d92bc43cbdee047b60ea0dd1b1fa4274377c9bf5bd14ab1982c272d8076f29"); } TEST(HDWallet, CreateFromMnemonicNoPassword) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), STRING("").get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), STRING("").get())); + assertSeedEq(wallet, "354c22aedb9a37407adc61f657a6f00d10ed125efa360215f36c6919abd94d6dbc193a5f9c495e21ee74118661e327e84a5f5f11fa373ec33b80897d4697557d"); +} + +TEST(HDWallet, CreateFromMnemonicCheck) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonicCheck(gWords.get(), STRING("").get(), false)); assertSeedEq(wallet, "354c22aedb9a37407adc61f657a6f00d10ed125efa360215f36c6919abd94d6dbc193a5f9c495e21ee74118661e327e84a5f5f11fa373ec33b80897d4697557d"); } @@ -88,7 +95,7 @@ TEST(HDWallet, CreateFromMnemonicInvalid) { } TEST(HDWallet, MasterPrivateKey) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), STRING("").get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), STRING("").get())); auto key1 = WRAP(TWPrivateKey, TWHDWalletGetMasterKey(wallet.get(), TWCurveSECP256k1)); auto hexKey1 = WRAPD(TWPrivateKeyData(key1.get())); @@ -102,7 +109,7 @@ TEST(HDWallet, MasterPrivateKey) { TEST(HDWallet, Derive) { const auto derivationPath = TW::derivationPath(TWCoinTypeEthereum); - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key0 = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeEthereum)); auto publicKey0 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(key0.get(), false)); @@ -112,7 +119,7 @@ TEST(HDWallet, Derive) { } TEST(HDWallet, DeriveBitcoinNonextended) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeBitcoin)); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(key.get(), false)); auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); @@ -122,7 +129,7 @@ TEST(HDWallet, DeriveBitcoinNonextended) { } TEST(HDWallet, DeriveBitcoinExtended) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeBitcoin)); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(key.get(), true)); auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); @@ -133,14 +140,44 @@ TEST(HDWallet, DeriveBitcoinExtended) { assertStringsEqual(address, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85"); } +TEST(HDWallet, GetKeyDerivation) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); + { + auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyDerivation(wallet.get(), TWCoinTypeBitcoin, TWDerivationBitcoinSegwit)); + assertHexEqual(WRAPD(TWPrivateKeyData(key.get())), "1901b5994f075af71397f65bd68a9fff8d3025d65f5a2c731cf90f5e259d6aac"); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(key.get(), true)); + auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); + assertHexEqual(publicKeyData, "037ea5dff03f677502c4a1d73c5ac897200e56b155e876774c8fba0cc22f80b941"); + } + { + auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyDerivation(wallet.get(), TWCoinTypeBitcoin, TWDerivationBitcoinLegacy)); + assertHexEqual(WRAPD(TWPrivateKeyData(key.get())), "28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4"); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(key.get(), true)); + auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); + assertHexEqual(publicKeyData, "0240ebf906b948281289405317a5eb9a98045af8a8ab5311b2e3060cfb66c507a1"); + } +} + TEST(HDWallet, DeriveAddressBitcoin) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto address = WRAP(TWString, TWHDWalletGetAddressForCoin(wallet.get(), TWCoinTypeBitcoin)); assertStringsEqual(address, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85"); } +TEST(HDWallet, DeriveAddressBitcoinDerivation) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); + { + auto address = WRAP(TWString, TWHDWalletGetAddressDerivation(wallet.get(), TWCoinTypeBitcoin, TWDerivationBitcoinSegwit)); + assertStringsEqual(address, "bc1qumwjg8danv2vm29lp5swdux4r60ezptzz7ce85"); + } + { + auto address = WRAP(TWString, TWHDWalletGetAddressDerivation(wallet.get(), TWCoinTypeBitcoin, TWDerivationBitcoinLegacy)); + assertStringsEqual(address, "1PeUvjuxyf31aJKX6kCXuaqxhmG78ZUdL1"); + } +} + TEST(HDWallet, DeriveEthereum) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeEthereum)); auto key2 = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeSmartChain)); @@ -157,7 +194,7 @@ TEST(HDWallet, DeriveEthereum) { } TEST(HDWallet, DeriveAddressEthereum) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto address = WRAP(TWString, TWHDWalletGetAddressForCoin(wallet.get(), TWCoinTypeEthereum)); assertStringsEqual(address, "0x27Ef5cDBe01777D62438AfFeb695e33fC2335979"); } @@ -179,7 +216,7 @@ TEST(HDWallet, DeriveCosmos) { } TEST(HDWallet, DeriveNimiq) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeNimiq)); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(key.get())); auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); @@ -188,7 +225,7 @@ TEST(HDWallet, DeriveNimiq) { } TEST(HDWallet, DeriveTezos) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeTezos)); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(key.get())); auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); @@ -197,7 +234,7 @@ TEST(HDWallet, DeriveTezos) { } TEST(HDWallet, DeriveDoge) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeDogecoin)); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(key.get(), true)); auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); @@ -212,7 +249,7 @@ TEST(HDWallet, DeriveDoge) { } TEST(HDWallet, DeriveZilliqa) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeZilliqa)); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(key.get(), true)); auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); @@ -244,7 +281,7 @@ TEST(HDWallet, DeriveFIO) { } TEST(HDWallet, DeriveAlgorand) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeAlgorand)); auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); auto address = WRAPS(TWCoinTypeDeriveAddress(TWCoinTypeAlgorand, privateKey.get())); @@ -252,18 +289,18 @@ TEST(HDWallet, DeriveAlgorand) { assertHexEqual(privateKeyData, "ce0b7ac644e2b7d9d14d3928b11643f43e48c33d3e328d059fef8add7f070e82"); } -TEST(HDWallet, DeriveElrond) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); - auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeElrond)); +TEST(HDWallet, DeriveMultiversX) { + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); + auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeMultiversX)); auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); - auto address = WRAPS(TWCoinTypeDeriveAddress(TWCoinTypeElrond, privateKey.get())); + auto address = WRAPS(TWCoinTypeDeriveAddress(TWCoinTypeMultiversX, privateKey.get())); assertHexEqual(privateKeyData, "0eb593141de897d60a0883320793eb49e63d556ccdf783a87ec014f150d50453"); assertStringsEqual(address, "erd1a6l7q9cfvrgr80xuzm37tapdr4zm3mwrtl6vt8f45df45x7eadfs8ds5vv"); } TEST(HDWallet, DeriveBinance) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeBinance)); auto key2 = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeSmartChain)); auto keyData = WRAPD(TWPrivateKeyData(key.get())); @@ -277,7 +314,7 @@ TEST(HDWallet, DeriveBinance) { } TEST(HDWallet, DeriveAvalancheCChain) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeAvalancheCChain)); auto keyData = WRAPD(TWPrivateKeyData(key.get())); @@ -287,10 +324,10 @@ TEST(HDWallet, DeriveAvalancheCChain) { } TEST(HDWallet, DeriveCardano) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeCardano)); auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); - EXPECT_EQ(TWDataSize(privateKeyData.get()), 192); + EXPECT_EQ(TWDataSize(privateKeyData.get()), 192ul); auto address = WRAPS(TWCoinTypeDeriveAddress(TWCoinTypeCardano, privateKey.get())); assertHexEqual(privateKeyData, "f8a3b8ad30e62c369b939336c2035aba26d1ffad135e6f346f2a370517a14952e73d20aeadf906bc8b531900fb6c3ed4a05b16973c10ae24650b68b26fae4ee5d97418ba7f3b2707fae963041ff5f174195d1578da09478ad2d17a1ecc00cad478a8ca3be214870accd41f008d70e3b4b59b5981ca933d6d3f389ad317a14952166d8fd329ae3fab4712da739efc2ded9b3eef2b1a8e225dd3dddeb4f065a729b297d9fa76b8852eef235c25aac8f0ff6209ab7251f2a84c83b3b5f1161f7c59"); @@ -327,14 +364,14 @@ TEST(HDWallet, ExtendedKeysCustomAccount) { auto words = STRING("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"); auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), STRING("").get())); - auto zprv0 = WRAPS(TWHDWalletGetExtendedPrivateKeyAccount(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoin, TWHDVersionZPRV, 0)); + auto zprv0 = WRAPS(TWHDWalletGetExtendedPrivateKeyAccount(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoin, TWDerivationBitcoinSegwit, TWHDVersionZPRV, 0)); assertStringsEqual(zprv0, "zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE"); - auto zprv1 = WRAPS(TWHDWalletGetExtendedPrivateKeyAccount(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoin, TWHDVersionZPRV, 1)); + auto zprv1 = WRAPS(TWHDWalletGetExtendedPrivateKeyAccount(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoin, TWDerivationBitcoinSegwit, TWHDVersionZPRV, 1)); assertStringsEqual(zprv1, "zprvAdG4iTXWBoAS2cCGuaGevCvH54GCunrvLJb2hoWCSuE3D9LS42XVg3c6sPm64w6VMq3w18vJf8nF3cBA2kUMkyWHsq6enWVXivzw42UrVHG"); - auto zpub0 = WRAPS(TWHDWalletGetExtendedPublicKeyAccount(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoin, TWHDVersionZPUB, 0)); + auto zpub0 = WRAPS(TWHDWalletGetExtendedPublicKeyAccount(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoin, TWDerivationBitcoinSegwit, TWHDVersionZPUB, 0)); assertStringsEqual(zpub0, "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs"); - auto zpub1 = WRAPS(TWHDWalletGetExtendedPublicKeyAccount(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoin, TWHDVersionZPUB, 1)); + auto zpub1 = WRAPS(TWHDWalletGetExtendedPublicKeyAccount(wallet.get(), TWPurposeBIP84, TWCoinTypeBitcoin, TWDerivationBitcoinSegwit, TWHDVersionZPUB, 1)); assertStringsEqual(zpub1, "zpub6rFR7y4Q2AijF6Gk1bofHLs1d66hKFamhXWdWBup1Em25wfabZqkDqvaieV63fDQFaYmaatCG7jVNUpUiM2hAMo6SAVHcrUpSnHDpNzucB7"); } @@ -449,18 +486,72 @@ TEST(HDWallet, MultipleThreads) { } TEST(HDWallet, GetDerivedKey) { - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), passphrase.get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); const auto privateKey = WRAP(TWPrivateKey, TWHDWalletGetDerivedKey(wallet.get(), TWCoinTypeBitcoin, 0, 0, 0)); const auto privateKeyData = WRAPD(TWPrivateKeyData(privateKey.get())); assertHexEqual(privateKeyData, "1901b5994f075af71397f65bd68a9fff8d3025d65f5a2c731cf90f5e259d6aac"); } +TEST(HDWallet, GetKeyByCurve) { + const auto derivPath = STRING("m/44'/539'/0'/0/0"); + + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), STRING("").get())); + const auto privateKey1 = WRAP(TWPrivateKey, TWHDWalletGetKeyByCurve(wallet.get(), TWCurveSECP256k1, derivPath.get())); + const auto privateKeyData1 = WRAPD(TWPrivateKeyData(privateKey1.get())); + assertHexEqual(privateKeyData1, "4fb8657d6464adcaa086d6758d7f0b6b6fc026c98dc1671fcc6460b5a74abc62"); + + const auto privateKey2 = WRAP(TWPrivateKey, TWHDWalletGetKeyByCurve(wallet.get(), TWCurveNIST256p1, derivPath.get())); + const auto privateKeyData2 = WRAPD(TWPrivateKeyData(privateKey2.get())); + assertHexEqual(privateKeyData2, "a13df52d5a5b438bbf921bbf86276e4347fe8e2f2ed74feaaee12b77d6d26f86"); +} + +TEST(TWHDWallet, FromMnemonicImmutableXMainnetFromSignature) { + // Successfully register: https://api.x.immutable.com/v1/users/0xd0972E2312518Ca15A2304D56ff9cc0b7ea0Ea37 + const auto mnemonic = STRING("obscure opera favorite shuffle mail tip age debate dirt pact cement loyal"); + const auto ethAddress = STRING("0xd0972E2312518Ca15A2304D56ff9cc0b7ea0Ea37"); + const auto layer = STRING("starkex"); + const auto application = STRING("immutablex"); + const auto index = STRING("1"); + const auto ethDerivationPath = STRING("m/44'/60'/0'/0/0"); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(mnemonic.get(), STRING("").get())); + auto derivationPath = WRAPS(TWEthereumEip2645GetPath(ethAddress.get(), layer.get(), application.get(), index.get())); + assertStringsEqual(derivationPath, "m/2645'/579218131'/211006541'/2124474935'/1609799702'/1"); + + // Retrieve eth private key + auto ethPrivateKey = WRAP(TWPrivateKey, TWHDWalletGetKey(wallet.get(), TWCoinTypeEthereum, ethDerivationPath.get())); + const auto ethPrivateKeyData = WRAPD(TWPrivateKeyData(ethPrivateKey.get())); + assertHexEqual(ethPrivateKeyData, "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"); + + // StarkKey Derivation Path + const auto starkDerivationPath = WRAP(TWDerivationPath, TWDerivationPathCreateWithString(derivationPath.get())); + + // Retrieve Stark Private key part + const auto ethMsg = STRING("Only sign this request if you’ve initiated an action with Immutable X."); + const auto ethSignature = WRAPS(TWEthereumMessageSignerSignMessageImmutableX(ethPrivateKey.get(), ethMsg.get())); + assertStringsEqual(ethSignature, "18b1be8b78807d3326e28bc286d7ee3d068dcd90b1949ce1d25c1f99825f26e70992c5eb7f44f76b202aceded00d74f771ed751f2fe538eec01e338164914fe001"); + const auto starkPrivateKey = WRAP(TWPrivateKey, TWStarkWareGetStarkKeyFromSignature(starkDerivationPath.get(), ethSignature.get())); + const auto starkPrivateKeyData = WRAPD(TWPrivateKeyData(starkPrivateKey.get())); + const auto starkPubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyByType(starkPrivateKey.get(), TWPublicKeyTypeStarkex)); + const auto starkPublicKeyData = WRAPD(TWPublicKeyData(starkPubKey.get())); + assertHexEqual(starkPrivateKeyData, "04be51a04e718c202e4dca60c2b72958252024cfc1070c090dd0f170298249de"); + assertHexEqual(starkPublicKeyData, "00e5b9b11f8372610ef35d647a1dcaba1a4010716588d591189b27bf3c2d5095"); + + // Account register + const auto ethMsgToRegister = STRING("Only sign this key linking request from Immutable X"); + const auto ethSignatureToRegister = WRAPS(TWEthereumMessageSignerSignMessageImmutableX(ethPrivateKey.get(), ethMsgToRegister.get())); + assertStringsEqual(ethSignatureToRegister, "646da4160f7fc9205e6f502fb7691a0bf63ecbb74bbb653465cd62388dd9f56325ab1e4a9aba99b1661e3e6251b42822855a71e60017b310b9f90e990a12e1dc01"); + const auto starkMsg = STRING("463a2240432264a3aa71a5713f2a4e4c1b9e12bbb56083cd56af6d878217cf"); + const auto starkSignature = WRAPS(TWStarkExMessageSignerSignMessage(starkPrivateKey.get(), starkMsg.get())); + assertStringsEqual(starkSignature, "04cf5f21333dd189ada3c0f2a51430d733501a9b1d5e07905273c1938cfb261e05b6013d74adde403e8953743a338c8d414bb96bf69d2ca1a91a85ed2700a528"); + ASSERT_TRUE(TWStarkExMessageSignerVerifyMessage(starkPubKey.get(), starkMsg.get(), starkSignature.get())); +} + TEST(TWHDWallet, Derive_XpubPub_vs_PrivPub) { // Test different routes for deriving address from mnemonic, result should be the same: // - Direct: mnemonic -> seed -> privateKey -> publicKey -> address // - Extended Public: mnemonic -> seed -> zpub -> publicKey -> address - auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(words.get(), STRING("").get())); + auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), STRING("").get())); const auto coin = TWCoinTypeBitcoin; const auto derivPath1 = STRING("m/84'/0'/0'/0/0"); const auto derivPath2 = STRING("m/84'/0'/0'/0/2"); diff --git a/tests/interface/TWHRPTests.cpp b/tests/interface/TWHRPTests.cpp index a4a205cb75a..14ddb39b657 100644 --- a/tests/interface/TWHRPTests.cpp +++ b/tests/interface/TWHRPTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -30,11 +28,12 @@ TEST(TWHRP, StringForHRP) { ASSERT_STREQ(stringForHRP(TWHRPBandChain), "band"); ASSERT_STREQ(stringForHRP(TWHRPBluzelle), "bluzelle"); ASSERT_STREQ(stringForHRP(TWHRPCardano), "addr"); - ASSERT_STREQ(stringForHRP(TWHRPElrond), "erd"); + ASSERT_STREQ(stringForHRP(TWHRPMultiversX), "erd"); ASSERT_STREQ(stringForHRP(TWHRPOasis), "oasis"); ASSERT_STREQ(stringForHRP(TWHRPTHORChain), "thor"); ASSERT_STREQ(stringForHRP(TWHRPCryptoOrg), "cro"); ASSERT_STREQ(stringForHRP(TWHRPOsmosis), "osmo"); + ASSERT_STREQ(stringForHRP(TWHRPSecret), "secret"); } TEST(TWHRP, HRPForString) { @@ -55,13 +54,14 @@ TEST(TWHRP, HRPForString) { ASSERT_EQ(hrpForString("kava"), TWHRPKava); ASSERT_EQ(hrpForString("band"), TWHRPBandChain); ASSERT_EQ(hrpForString("addr"), TWHRPCardano); - ASSERT_EQ(hrpForString("erd"), TWHRPElrond); + ASSERT_EQ(hrpForString("erd"), TWHRPMultiversX); ASSERT_EQ(hrpForString("oasis"), TWHRPOasis); ASSERT_EQ(hrpForString("thor"), TWHRPTHORChain); ASSERT_EQ(hrpForString("bluzelle"), TWHRPBluzelle); ASSERT_EQ(hrpForString("cro"), TWHRPCryptoOrg); ASSERT_EQ(hrpForString("osmo"), TWHRPOsmosis); ASSERT_EQ(hrpForString("ecash"), TWHRPECash); + ASSERT_EQ(hrpForString("secret"), TWHRPSecret); } TEST(TWHPR, HPRByCoinType) { @@ -82,12 +82,13 @@ TEST(TWHPR, HPRByCoinType) { ASSERT_EQ(TWHRPBandChain, TWCoinTypeHRP(TWCoinTypeBandChain)); ASSERT_EQ(TWHRPBluzelle, TWCoinTypeHRP(TWCoinTypeBluzelle)); ASSERT_EQ(TWHRPCardano, TWCoinTypeHRP(TWCoinTypeCardano)); - ASSERT_EQ(TWHRPElrond, TWCoinTypeHRP(TWCoinTypeElrond)); + ASSERT_EQ(TWHRPMultiversX, TWCoinTypeHRP(TWCoinTypeMultiversX)); ASSERT_EQ(TWHRPOasis, TWCoinTypeHRP(TWCoinTypeOasis)); ASSERT_EQ(TWHRPTHORChain, TWCoinTypeHRP(TWCoinTypeTHORChain)); ASSERT_EQ(TWHRPCryptoOrg, TWCoinTypeHRP(TWCoinTypeCryptoOrg)); ASSERT_EQ(TWHRPOsmosis, TWCoinTypeHRP(TWCoinTypeOsmosis)); ASSERT_EQ(TWHRPECash, TWCoinTypeHRP(TWCoinTypeECash)); + ASSERT_EQ(TWHRPSecret, TWCoinTypeHRP(TWCoinTypeSecret)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeAion)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeCallisto)); @@ -109,8 +110,8 @@ TEST(TWHPR, HPRByCoinType) { ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeStellar)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeTezos)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeTheta)); - ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeThunderToken)); - ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeTomoChain)); + ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeThunderCore)); + ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeViction)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeTron)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeVeChain)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeWanchain)); @@ -120,4 +121,5 @@ TEST(TWHPR, HPRByCoinType) { ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeRavencoin)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeWaves)); ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeNEO)); + ASSERT_EQ(TWHRPUnknown, TWCoinTypeHRP(TWCoinTypeNebl)); } diff --git a/tests/interface/TWHashTests.cpp b/tests/interface/TWHashTests.cpp index 09b4498c690..800be2f28e9 100644 --- a/tests/interface/TWHashTests.cpp +++ b/tests/interface/TWHashTests.cpp @@ -1,15 +1,13 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Hash.h" #include "HexCoding.h" #include -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include using namespace std; diff --git a/tests/interface/TWMnemonicTests.cpp b/tests/interface/TWMnemonicTests.cpp index 6ed7d3b1a9c..2fd55e74b85 100644 --- a/tests/interface/TWMnemonicTests.cpp +++ b/tests/interface/TWMnemonicTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include diff --git a/tests/interface/TWPBKDF2Tests.cpp b/tests/interface/TWPBKDF2Tests.cpp index 646126fb3de..dbd22ca6fa4 100644 --- a/tests/interface/TWPBKDF2Tests.cpp +++ b/tests/interface/TWPBKDF2Tests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include diff --git a/tests/interface/TWPrivateKeyTests.cpp b/tests/interface/TWPrivateKeyTests.cpp index d6fe4ea8f36..3c4882247ec 100644 --- a/tests/interface/TWPrivateKeyTests.cpp +++ b/tests/interface/TWPrivateKeyTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include "PrivateKey.h" #include "PublicKey.h" @@ -83,68 +81,23 @@ TEST(TWPrivateKeyTests, PublicKey) { const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyCurve25519(privateKey.get())); ASSERT_EQ(TW::hex(publicKey.get()->impl.bytes), "686cfce9108566dd43fc6aa75e31f9a9f319c9e9c04d6ad0a52505b86bc17c3a"); } -} - -TEST(TWPrivateKeyTests, GetSharedKey) { - const auto privateKeyHex = "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0"; - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privateKeyHex).get())); - ASSERT_TRUE(privateKey.get() != nullptr); - - const auto publicKeyHex = "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992"; - const auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyHex).get(), TWPublicKeyTypeSECP256k1)); - EXPECT_TRUE(publicKey != nullptr); - - const auto derivedData = WRAPD(TWPrivateKeyGetSharedKey(privateKey.get(), publicKey.get(), TWCurveSECP256k1)); - ASSERT_EQ(TW::hex(*((TW::Data*)derivedData.get())), "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a"); -} - -/** - * Valid test vector from Wycherproof project - * Source: https://github.com/google/wycheproof/blob/master/testvectors/ecdh_secp256k1_test.json#L31 - */ -TEST(TWPrivateKeyTests, GetSharedKeyWycherproof) { - // Stripped left-padded zeroes from: `00f4b7ff7cccc98813a69fae3df222bfe3f4e28f764bf91b4a10d8096ce446b254` - const auto privateKeyHex = "f4b7ff7cccc98813a69fae3df222bfe3f4e28f764bf91b4a10d8096ce446b254"; - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privateKeyHex).get())); - ASSERT_TRUE(privateKey.get() != nullptr); - - // Decoded from ASN.1 & uncompressed `3056301006072a8648ce3d020106052b8104000a03420004d8096af8a11e0b80037e1ee68246b5dcbb0aeb1cf1244fd767db80f3fa27da2b396812ea1686e7472e9692eaf3e958e50e9500d3b4c77243db1f2acd67ba9cc4` - const auto publicKeyHex = "02d8096af8a11e0b80037e1ee68246b5dcbb0aeb1cf1244fd767db80f3fa27da2b"; - const auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyHex).get(), TWPublicKeyTypeSECP256k1)); - EXPECT_TRUE(publicKey != nullptr); - - // SHA-256 of encoded x-coordinate `02544dfae22af6af939042b1d85b71a1e49e9a5614123c4d6ad0c8af65baf87d65` - const auto derivedData = WRAPD(TWPrivateKeyGetSharedKey(privateKey.get(), publicKey.get(), TWCurveSECP256k1)); - ASSERT_EQ(TW::hex(*((TW::Data*)derivedData.get())), "81165066322732362ca5d3f0991d7f1f7d0aad7ea533276496785d369e35159a"); -} - -TEST(TWPrivateKeyTests, GetSharedKeyBidirectional) { - const auto privateKeyHex1 = "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0"; - const auto privateKey1 = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privateKeyHex1).get())); - ASSERT_TRUE(privateKey1.get() != nullptr); - auto publicKey1 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey1.get(), true)); - - const auto privateKeyHex2 = "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a"; - const auto privateKey2 = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privateKeyHex2).get())); - ASSERT_TRUE(privateKey2.get() != nullptr); - auto publicKey2 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey2.get(), true)); - - const auto derivedData1 = WRAPD(TWPrivateKeyGetSharedKey(privateKey1.get(), publicKey2.get(), TWCurveSECP256k1)); - const auto derivedData2 = WRAPD(TWPrivateKeyGetSharedKey(privateKey2.get(), publicKey1.get(), TWCurveSECP256k1)); - ASSERT_EQ(TW::hex(*((TW::Data*)derivedData1.get())), TW::hex(*((TW::Data*)derivedData2.get()))); -} - -TEST(TWPrivateKeyTests, GetSharedKeyError) { - const auto privateKeyHex = "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0"; - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privateKeyHex).get())); - ASSERT_TRUE(privateKey.get() != nullptr); + { + const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeEthereum)); + ASSERT_EQ(TW::hex(publicKey.get()->impl.bytes), "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91"); - const auto publicKeyHex = "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992"; - const auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA(publicKeyHex).get(), TWPublicKeyTypeSECP256k1)); - EXPECT_TRUE(publicKey != nullptr); + auto pubkeyType = TWCoinTypePublicKeyType(TWCoinTypeEthereum); + const auto publicKeyByType = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyByType(privateKey.get(), pubkeyType)); - const auto derivedData = WRAPD(TWPrivateKeyGetSharedKey(privateKey.get(), publicKey.get(), TWCurveED25519)); - EXPECT_TRUE(derivedData == nullptr); + ASSERT_EQ(TW::hex(publicKey.get()->impl.bytes), TW::hex(publicKeyByType.get()->impl.bytes)); + } + { + const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeNEO)); + ASSERT_EQ(TW::hex(publicKey.get()->impl.bytes), "026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab"); + } + { + const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeWaves)); + ASSERT_EQ(TW::hex(publicKey.get()->impl.bytes), "686cfce9108566dd43fc6aa75e31f9a9f319c9e9c04d6ad0a52505b86bc17c3a"); + } } TEST(TWPrivateKeyTests, Sign) { @@ -167,7 +120,7 @@ TEST(TWPrivateKeyTests, SignAsDER) { const auto data = WRAPD(TWDataCreateWithBytes((uint8_t *)message, strlen(message))); const auto hash = WRAPD(TWHashKeccak256(data.get())); - auto actual = WRAPD(TWPrivateKeySignAsDER(privateKey.get(), hash.get(), TWCurveSECP256k1)); + auto actual = WRAPD(TWPrivateKeySignAsDER(privateKey.get(), hash.get())); ASSERT_EQ(TW::hex(*((TW::Data*)actual.get())), "30450221008720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba6202204d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de9"); diff --git a/tests/interface/TWPublicKeyTests.cpp b/tests/interface/TWPublicKeyTests.cpp index d20fa431dcb..16b707e0a95 100644 --- a/tests/interface/TWPublicKeyTests.cpp +++ b/tests/interface/TWPublicKeyTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include "PublicKey.h" #include "PrivateKey.h" @@ -49,20 +47,20 @@ TEST(TWPublicKeyTests, CompressedExtended) { const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); EXPECT_EQ(TWPublicKeyKeyType(publicKey.get()), TWPublicKeyTypeSECP256k1); - EXPECT_EQ(publicKey.get()->impl.bytes.size(), 33); + EXPECT_EQ(publicKey.get()->impl.bytes.size(), 33ul); EXPECT_EQ(TWPublicKeyIsCompressed(publicKey.get()), true); EXPECT_TRUE(TWPublicKeyIsValid(publicKey.get(), TWPublicKeyTypeSECP256k1)); auto extended = WRAP(TWPublicKey, TWPublicKeyUncompressed(publicKey.get())); EXPECT_EQ(TWPublicKeyKeyType(extended.get()), TWPublicKeyTypeSECP256k1Extended); - EXPECT_EQ(extended.get()->impl.bytes.size(), 65); + EXPECT_EQ(extended.get()->impl.bytes.size(), 65ul); EXPECT_EQ(TWPublicKeyIsCompressed(extended.get()), false); EXPECT_TRUE(TWPublicKeyIsValid(extended.get(), TWPublicKeyTypeSECP256k1Extended)); auto compressed = WRAP(TWPublicKey, TWPublicKeyCompressed(extended.get())); //EXPECT_TRUE(compressed == publicKey.get()); EXPECT_EQ(TWPublicKeyKeyType(compressed.get()), TWPublicKeyTypeSECP256k1); - EXPECT_EQ(compressed.get()->impl.bytes.size(), 33); + EXPECT_EQ(compressed.get()->impl.bytes.size(), 33ul); EXPECT_EQ(TWPublicKeyIsCompressed(compressed.get()), true); EXPECT_TRUE(TWPublicKeyIsValid(compressed.get(), TWPublicKeyTypeSECP256k1)); } @@ -89,7 +87,7 @@ TEST(TWPublicKeyTests, VerifyAsDER) { auto messageData = WRAPD(TWDataCreateWithBytes((const uint8_t*)message, strlen(message))); auto digest = WRAPD(TWHashKeccak256(messageData.get())); - auto signature = WRAPD(TWPrivateKeySignAsDER(privateKey.get(), digest.get(), TWCurveSECP256k1)); + auto signature = WRAPD(TWPrivateKeySignAsDER(privateKey.get(), digest.get())); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false)); diff --git a/tests/interface/TWStoredKeyTests.cpp b/tests/interface/TWStoredKeyTests.cpp index ff1518b3ed4..ac9bd0cc366 100644 --- a/tests/interface/TWStoredKeyTests.cpp +++ b/tests/interface/TWStoredKeyTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include #include @@ -25,23 +23,23 @@ using namespace std; /// Return a StoredKey instance that can be used for further tests. Needs to be deleted at the end. -struct std::shared_ptr createAStoredKey(TWCoinType coin, TWData* password) { +struct std::shared_ptr createAStoredKey(TWCoinType coin, TWData* password, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr) { const auto mnemonic = WRAPS(TWStringCreateWithUTF8Bytes("team engine square letter hero song dizzy scrub tornado fabric divert saddle")); const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); - const auto key = WRAP(TWStoredKey, TWStoredKeyImportHDWallet(mnemonic.get(), name.get(), password, coin)); + const auto key = WRAP(TWStoredKey, TWStoredKeyImportHDWalletWithEncryption(mnemonic.get(), name.get(), password, coin, encryption)); return key; } /// Return a StoredKey instance that can be used for further tests. Needs to be deleted at the end. -struct std::shared_ptr createDefaultStoredKey() { +struct std::shared_ptr createDefaultStoredKey(TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr) { const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); - return createAStoredKey(TWCoinTypeBitcoin, password.get()); + return createAStoredKey(TWCoinTypeBitcoin, password.get(), encryption); } TEST(TWStoredKey, loadPBKDF2Key) { - const auto filename = WRAPS(TWStringCreateWithUTF8Bytes((TESTS_ROOT + "/Keystore/Data/pbkdf2.json").c_str())); + const auto filename = WRAPS(TWStringCreateWithUTF8Bytes((TESTS_ROOT + "/common/Keystore/Data/pbkdf2.json").c_str())); const auto key = WRAP(TWStoredKey, TWStoredKeyLoad(filename.get())); const auto keyId = WRAPS(TWStoredKeyIdentifier(key.get())); EXPECT_EQ(string(TWStringUTF8Bytes(keyId.get())), "3198bc9c-6672-5ab3-d995-4942343ae5b6"); @@ -57,7 +55,7 @@ TEST(TWStoredKey, createWallet) { const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); - const auto key = WRAP(TWStoredKey, TWStoredKeyCreateLevel(name.get(), password.get(), TWStoredKeyEncryptionLevelDefault)); + const auto key = WRAP(TWStoredKey, TWStoredKeyCreateLevelAndEncryption(name.get(), password.get(), TWStoredKeyEncryptionLevelDefault, TWStoredKeyEncryptionAes128Ctr)); const auto name2 = WRAPS(TWStoredKeyName(key.get())); EXPECT_EQ(string(TWStringUTF8Bytes(name2.get())), "name"); const auto mnemonic = WRAPS(TWStoredKeyDecryptMnemonic(key.get(), password.get())); @@ -82,6 +80,23 @@ TEST(TWStoredKey, importPrivateKey) { TWPrivateKeyDelete(privateKey3); } +TEST(TWStoredKey, importPrivateKeyAes256) { + const auto privateKeyHex = "3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"; + const auto privateKey = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes(privateKeyHex)).get())); + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + const auto coin = TWCoinTypeBitcoin; + const auto key = WRAP(TWStoredKey, TWStoredKeyImportPrivateKeyWithEncryption(privateKey.get(), name.get(), password.get(), coin, TWStoredKeyEncryptionAes256Ctr)); + const auto privateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(key.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(privateKey2.get()), TWDataSize(privateKey2.get()))), privateKeyHex); + + const auto privateKey3 = TWStoredKeyPrivateKey(key.get(), coin, password.get()); + const auto pkData3 = WRAPD(TWPrivateKeyData(privateKey3)); + EXPECT_EQ(hex(data(TWDataBytes(pkData3.get()), TWDataSize(pkData3.get()))), privateKeyHex); + TWPrivateKeyDelete(privateKey3); +} + TEST(TWStoredKey, importHDWallet) { const auto mnemonic = WRAPS(TWStringCreateWithUTF8Bytes("team engine square letter hero song dizzy scrub tornado fabric divert saddle")); const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); @@ -97,6 +112,21 @@ TEST(TWStoredKey, importHDWallet) { EXPECT_EQ(nokey.get(), nullptr); } +TEST(TWStoredKey, importHDWalletAES256) { + const auto mnemonic = WRAPS(TWStringCreateWithUTF8Bytes("team engine square letter hero song dizzy scrub tornado fabric divert saddle")); + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + const auto coin = TWCoinTypeBitcoin; + const auto key = WRAP(TWStoredKey, TWStoredKeyImportHDWalletWithEncryption(mnemonic.get(), name.get(), password.get(), coin, TWStoredKeyEncryptionAes256Ctr)); + EXPECT_TRUE(TWStoredKeyIsMnemonic(key.get())); + + // invalid mnemonic + const auto mnemonicInvalid = WRAPS(TWStringCreateWithUTF8Bytes("_THIS_IS_AN_INVALID_MNEMONIC_")); + const auto nokey = WRAP(TWStoredKey, TWStoredKeyImportHDWalletWithEncryption(mnemonicInvalid.get(), name.get(), password.get(), coin, TWStoredKeyEncryptionAes256Ctr)); + EXPECT_EQ(nokey.get(), nullptr); +} + TEST(TWStoredKey, addressAddRemove) { const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); @@ -109,11 +139,11 @@ TEST(TWStoredKey, addressAddRemove) { const auto accountAddress = WRAPS(TWAccountAddress(accountCoin.get())); EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress.get())), "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); - EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 1); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 1ul); const auto accountIdx = WRAP(TWAccount, TWStoredKeyAccount(key.get(), 0)); TWStoredKeyRemoveAccountForCoin(key.get(), coin); - EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 0); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 0ul); const auto addressAdd = "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"; const auto derivationPath = "m/84'/0'/0'/0/0"; @@ -126,7 +156,7 @@ TEST(TWStoredKey, addressAddRemove) { WRAPS(TWStringCreateWithUTF8Bytes(derivationPath)).get(), WRAPS(TWStringCreateWithUTF8Bytes(pubKey)).get(), WRAPS(TWStringCreateWithUTF8Bytes(extPubKeyAdd)).get()); - EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 1); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 1ul); // invalid account index EXPECT_EQ(TWStoredKeyAccount(key.get(), 1001), nullptr); @@ -144,17 +174,17 @@ TEST(TWStoredKey, addressAddRemoveDerivationPath) { const auto accountAddress = WRAPS(TWAccountAddress(accountCoin.get())); EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress.get())), "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); - EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 1); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 1ul); const auto accountIdx = WRAP(TWAccount, TWStoredKeyAccount(key.get(), 0)); const auto derivationPath0 = "m/84'/0'/0'/0/0"; const auto derivationPath1 = "m/84'/0'/0'/1/0"; TWStoredKeyRemoveAccountForCoinDerivationPath(key.get(), coin, WRAPS(TWStringCreateWithUTF8Bytes(derivationPath1)).get()); - EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 1); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 1ul); TWStoredKeyRemoveAccountForCoinDerivationPath(key.get(), coin, WRAPS(TWStringCreateWithUTF8Bytes(derivationPath0)).get()); - EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 0); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 0ul); } TEST(TWStoredKey, addressAddDerivation) { @@ -173,7 +203,7 @@ TEST(TWStoredKey, addressAddDerivation) { const auto accountAddress2 = WRAPS(TWAccountAddress(accountCoin2.get())); EXPECT_EQ(string(TWStringUTF8Bytes(accountAddress2.get())), "1NyRyFewhZcWMa9XCj3bBxSXPXyoSg8dKz"); - EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 2); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 2ul); } TEST(TWStoredKey, exportJSON) { @@ -184,6 +214,30 @@ TEST(TWStoredKey, exportJSON) { EXPECT_EQ(TWDataGet(json.get(), 0), '{'); } +TEST(TWStoredKey, storeAndImportJSONAES256) { + const auto key = createDefaultStoredKey(TWStoredKeyEncryptionAes256Ctr); + const auto outFileName = string(getTestTempDir() + "/TWStoredKey_store.json"); + const auto outFileNameStr = WRAPS(TWStringCreateWithUTF8Bytes(outFileName.c_str())); + EXPECT_TRUE(TWStoredKeyStore(key.get(), outFileNameStr.get())); + + // read contents of file + ifstream ifs(outFileName); + // get length of file: + ifs.seekg (0, ifs.end); + auto length = ifs.tellg(); + ifs.seekg (0, ifs.beg); + EXPECT_TRUE(length > 20); + + Data json(length); + size_t idx = 0; + // read the slow way, ifs.read gave some false warnings with codacy + while (!ifs.eof() && idx < static_cast(length)) { char c = ifs.get(); json[idx++] = (uint8_t)c; } + + const auto key2 = WRAP(TWStoredKey, TWStoredKeyImportJSON(WRAPD(TWDataCreateWithData(&json)).get())); + const auto name2 = WRAPS(TWStoredKeyName(key2.get())); + EXPECT_EQ(string(TWStringUTF8Bytes(name2.get())), "name"); +} + TEST(TWStoredKey, storeAndImportJSON) { const auto key = createDefaultStoredKey(); const auto outFileName = string(getTestTempDir() + "/TWStoredKey_store.json"); @@ -202,7 +256,7 @@ TEST(TWStoredKey, storeAndImportJSON) { Data json(length); size_t idx = 0; // read the slow way, ifs.read gave some false warnings with codacy - while (!ifs.eof() && idx < length) { char c = ifs.get(); json[idx++] = (uint8_t)c; } + while (!ifs.eof() && idx < static_cast(length)) { char c = ifs.get(); json[idx++] = (uint8_t)c; } const auto key2 = WRAP(TWStoredKey, TWStoredKeyImportJSON(WRAPD(TWDataCreateWithData(&json)).get())); const auto name2 = WRAPS(TWStoredKeyName(key2.get())); @@ -224,6 +278,80 @@ TEST(TWStoredKey, fixAddresses) { EXPECT_TRUE(TWStoredKeyFixAddresses(key.get(), password.get())); } +// In this test, we add a TON account with an outdated bounceable (`EQ`) address to the key storage, +// and then check if `TWStoredKeyUpdateAddress` re-derives non-bounceable `UQ` instead. +TEST(TWStoredKey, UpdateAddressWithMnemonic) { + const auto keyName = STRING("key"); + const string passwordString = "password"; + const auto password = WRAPD(TWDataCreateWithBytes((const uint8_t*)passwordString.c_str(), passwordString.size())); + + // Create stored key with a dummy Bitcoin account. + auto key = createAStoredKey(TWCoinTypeBitcoin, password.get()); + + const auto oldAddress = "EQDSRYDMMez8BdcOuPEiaR6aJZpO6EjlIwmOBFn14mMbnUtk"; + const auto newAddress = "UQDSRYDMMez8BdcOuPEiaR6aJZpO6EjlIwmOBFn14mMbnRah"; + const auto derivationPath = "m/44'/607'/0'"; + const auto extPubKey = ""; + const auto pubKey = "b191d35f81aa8b144aa91c90a6b887e0b165ad9c2933b1c5266eb5c4e8bea241"; + + // Add a TON account with an outdated address (bounceable). + TWStoredKeyAddAccount(key.get(), + STRING(oldAddress).get(), + TWCoinTypeTON, + STRING(derivationPath).get(), + STRING(pubKey).get(), + STRING(extPubKey).get()); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 2ul); + + // Last step - update TON account address. + // Expect to have a non-bounceable address in the end. + ASSERT_TRUE(TWStoredKeyUpdateAddress(key.get(), TWCoinTypeTON)); + const auto tonAccount = WRAP(TWAccount, TWStoredKeyAccountForCoin(key.get(), TWCoinTypeTON, nullptr)); + assertStringsEqual(WRAPS(TWAccountAddress(tonAccount.get())), newAddress); +} + +// In this test, we add an Ethereum account with an outdated lowercase address to the key storage, +// and then check if `TWStoredKeyUpdateAddress` re-derives checksummed address instead. +TEST(TWStoredKey, UpdateAddressWithPrivateKey) { + const auto keyName = STRING("key"); + const auto privateKey = DATA("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); + const string passwordString = "password"; + const auto password = WRAPD(TWDataCreateWithBytes((const uint8_t*)passwordString.c_str(), passwordString.size())); + + // Create stored key with a dummy Bitcoin account. + auto key = WRAP(TWStoredKey, TWStoredKeyImportPrivateKey(privateKey.get(), keyName.get(), password.get(), TWCoinTypeBitcoin)); + + const auto oldAddress = "0xc2d7cf95645d33006175b78989035c7c9061d3f9"; + const auto newAddress = "0xC2D7CF95645D33006175B78989035C7c9061d3F9"; + const auto derivationPath = "m/44'/60'/0'"; + const auto extPubKey = ""; + const auto pubKey = "04efb99d9860f4dec4cb548a5722c27e9ef58e37fbab9719c5b33d55c216db49311221a01f638ce5f255875b194e0acaa58b19a89d2e56a864427298f826a7f887"; + + // Add an Ethereum account with an outdated address (lowercase). + TWStoredKeyAddAccount(key.get(), + STRING(oldAddress).get(), + TWCoinTypeEthereum, + STRING(derivationPath).get(), + STRING(pubKey).get(), + STRING(extPubKey).get()); + EXPECT_EQ(TWStoredKeyAccountCount(key.get()), 2ul); + + // Last step - update Ethereum account address. + // Expect to have a checksummed address in the end. + ASSERT_TRUE(TWStoredKeyUpdateAddress(key.get(), TWCoinTypeEthereum)); + const auto ethAccount = WRAP(TWAccount, TWStoredKeyAccountForCoin(key.get(), TWCoinTypeEthereum, nullptr)); + assertStringsEqual(WRAPS(TWAccountAddress(ethAccount.get())), newAddress); +} + +TEST(TWStoredKey, updateAddressNoAssociatedAccounts) { + const auto keyName = STRING("key"); + const string passwordString = "password"; + const auto password = WRAPD(TWDataCreateWithBytes((const uint8_t*)passwordString.c_str(), passwordString.size())); + + auto key = createAStoredKey(TWCoinTypeBitcoin, password.get()); + ASSERT_FALSE(TWStoredKeyUpdateAddress(key.get(), TWCoinTypeEthereum)); +} + TEST(TWStoredKey, importInvalidKey) { auto bytes = TW::parse_hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); auto data = WRAPD(TWDataCreateWithBytes(bytes.data(), bytes.size())); @@ -250,11 +378,11 @@ TEST(TWStoredKey, removeAccountForCoin) { ASSERT_NE(WRAP(TWAccount, TWStoredKeyAccountForCoin(key.get(), TWCoinTypeEthereum, wallet.get())).get(), nullptr); ASSERT_NE(WRAP(TWAccount, TWStoredKeyAccountForCoin(key.get(), TWCoinTypeBitcoin, wallet.get())).get(), nullptr); - ASSERT_EQ(TWStoredKeyAccountCount(key.get()), 2); + ASSERT_EQ(TWStoredKeyAccountCount(key.get()), 2ul); TWStoredKeyRemoveAccountForCoin(key.get(), TWCoinTypeBitcoin); - ASSERT_EQ(TWStoredKeyAccountCount(key.get()), 1); + ASSERT_EQ(TWStoredKeyAccountCount(key.get()), 1ul); ASSERT_NE(WRAP(TWAccount, TWStoredKeyAccountForCoin(key.get(), TWCoinTypeEthereum, nullptr)).get(), nullptr); ASSERT_EQ(WRAP(TWAccount, TWStoredKeyAccountForCoin(key.get(), TWCoinTypeBitcoin, nullptr)).get(), nullptr); @@ -281,7 +409,7 @@ TEST(TWStoredKey, encryptionParameters) { // compare some specific parameters EXPECT_EQ(jsonParams["kdfparams"]["n"], 16384); - EXPECT_EQ(std::string(jsonParams["cipherparams"]["iv"]).length(), 32); + EXPECT_EQ(std::string(jsonParams["cipherparams"]["iv"]).length(), 32ul); // compare all keys, except dynamic ones (like cipherparams/iv) jsonParams["cipherparams"] = {}; diff --git a/tests/interface/TWStringTests.cpp b/tests/interface/TWStringTests.cpp index 17d800b6782..15177373914 100644 --- a/tests/interface/TWStringTests.cpp +++ b/tests/interface/TWStringTests.cpp @@ -1,10 +1,8 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. -#include "TWTestUtilities.h" +#include "TestUtilities.h" #include @@ -21,3 +19,19 @@ TEST(StringTests, HexNumber) { auto string = WRAPS(TWStringCreateWithHexData(data.get())); ASSERT_STREQ(TWStringUTF8Bytes(string.get()), "deadbeef"); } + +TEST(StringTests, GetChar) { + uint8_t bytes[] = { 0xde, 0xad, 0xbe, 0xef }; + auto data = WRAPD(TWDataCreateWithBytes(bytes, 4)); + auto string = WRAPS(TWStringCreateWithHexData(data.get())); + ASSERT_STREQ(TWStringUTF8Bytes(string.get()), "deadbeef"); + + ASSERT_EQ(TWStringGet(string.get(), 0), 'd'); + ASSERT_EQ(TWStringGet(string.get(), 1), 'e'); + ASSERT_EQ(TWStringGet(string.get(), 2), 'a'); + ASSERT_EQ(TWStringGet(string.get(), 3), 'd'); + ASSERT_EQ(TWStringGet(string.get(), 4), 'b'); + ASSERT_EQ(TWStringGet(string.get(), 5), 'e'); + ASSERT_EQ(TWStringGet(string.get(), 6), 'e'); + ASSERT_EQ(TWStringGet(string.get(), 7), 'f'); +} \ No newline at end of file diff --git a/tests/interface/TWTestUtilities.cpp b/tests/interface/TWTestUtilities.cpp deleted file mode 100644 index 7a7fae7b15a..00000000000 --- a/tests/interface/TWTestUtilities.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "TWTestUtilities.h" - -#include -#include - -using namespace std; - -/// Return a writable temp dir which can be used to create files during testing -string getTestTempDir(void) { - // In general, tests should not use hardcoded "/tmp", but TEST_TMPDIR env var. - const char* fromEnvironment = getenv("TEST_TMPDIR"); - if (fromEnvironment == NULL || fromEnvironment[0] == '\0') { return "/tmp"; } - return string(fromEnvironment); -} - -nlohmann::json loadJson(std::string path) { - std::ifstream stream(path); - nlohmann::json json; - stream >> json; - return json; -} diff --git a/tests/interface/TWTestUtilities.h b/tests/interface/TWTestUtilities.h deleted file mode 100644 index 1adc36b9ac0..00000000000 --- a/tests/interface/TWTestUtilities.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright © 2017-2020 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include -#include - -#include -#include -#include - -#include - -#define WRAP(type, x) std::shared_ptr(x, type##Delete) -#define WRAPD(x) std::shared_ptr(x, TWDataDelete) -#define WRAPS(x) std::shared_ptr(x, TWStringDelete) -#define STRING(x) std::shared_ptr(TWStringCreateWithUTF8Bytes(x), TWStringDelete) -#define DATA(x) std::shared_ptr(TWDataCreateWithHexString(STRING(x).get()), TWDataDelete) - -inline void assertStringsEqual(const std::shared_ptr& string, const char* expected) { - ASSERT_STREQ(TWStringUTF8Bytes(string.get()), expected); -} - -inline void assertHexEqual(const std::shared_ptr& data, const char* expected) { - auto hex = WRAPS(TWStringCreateWithHexData(data.get())); - assertStringsEqual(hex, expected); -} - -inline void assertJSONEqual(const std::string& lhs, const char* expected) { - auto lhsJson = nlohmann::json::parse(lhs); - auto rhsJson = nlohmann::json::parse(std::string(expected)); - ASSERT_EQ(lhsJson.dump(), rhsJson.dump()); -} - -inline std::vector* dataFromTWData(TWData* data) { - return const_cast*>(reinterpret_cast*>(data)); -} - -/// Return a writable temp dir which can be used to create files during testing -std::string getTestTempDir(void); - -#define ANY_SIGN(input, coin) \ - {\ - auto inputData = input.SerializeAsString();\ - auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size()));\ - auto outputTWData = WRAPD(TWAnySignerSign(inputTWData.get(), coin));\ - output.ParseFromArray(TWDataBytes(outputTWData.get()), static_cast(TWDataSize(outputTWData.get())));\ - } -#define ANY_PLAN(input, output, coin) \ - {\ - auto inputData = input.SerializeAsString();\ - auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size()));\ - auto outputTWData = WRAPD(TWAnySignerPlan(inputTWData.get(), coin));\ - output.ParseFromArray(TWDataBytes(outputTWData.get()), static_cast(TWDataSize(outputTWData.get())));\ - } -#define DUMP_PROTO(input) \ - { \ - std::string json; \ - google::protobuf::util::MessageToJsonString(input, &json); \ - std::cout<<"dump proto: "< -#include -#include #include "proto/Binance.pb.h" #include "proto/Bitcoin.pb.h" #include "proto/Ethereum.pb.h" +#include "proto/NULS.pb.h" +#include "proto/Ripple.pb.h" +#include "proto/Solana.pb.h" #include "proto/TransactionCompiler.pb.h" +#include +#include +#include -#include #include "Bitcoin/Script.h" #include "Bitcoin/SegwitAddress.h" +#include "NULS/Address.h" +#include #include "HexCoding.h" #include "PrivateKey.h" @@ -22,80 +24,103 @@ #include "uint256.h" #include -#include "../interface/TWTestUtilities.h" +#include "TestUtilities.h" #include -#include #include #include +#include using namespace TW; TEST(TWTransactionCompiler, ExternalSignatureSignBinance) { /// Step 1: Prepare transaction input (protobuf) const auto coin = TWCoinTypeBinance; - const auto txInputData = WRAPD(TWTransactionCompilerBuildInput( - coin, - STRING("bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2").get(), // from - STRING("bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5").get(), // to - STRING("1").get(), // amount - STRING("BNB").get(), // asset - STRING("").get(), // memo - STRING("Binance-Chain-Nile").get() // testnet chainId - )); + // bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2 + const auto fromAddressData = parse_hex("40c2979694bbc961023d1d27be6fc4d21a9febe6"); + // bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5 + const auto toAddressData = parse_hex("bffe47abfaede50419c577f1074fee6dd1535cd1"); + + Binance::Proto::SigningInput txInput; + + txInput.set_chain_id("Binance-Chain-Nile"); + auto& sendOrder = *txInput.mutable_send_order(); + + auto& input1 = *sendOrder.add_inputs(); + input1.set_address(fromAddressData.data(), fromAddressData.size()); + auto& input1Coin = *input1.add_coins(); + input1Coin.set_amount(1); + input1Coin.set_denom("BNB"); + + auto& output1 = *sendOrder.add_outputs(); + output1.set_address(toAddressData.data(), toAddressData.size()); + auto& output1Coin = *output1.add_coins(); + output1Coin.set_amount(1); + output1Coin.set_denom("BNB"); + + auto txInputData = data(txInput.SerializeAsString()); { // Check, by parsing - EXPECT_EQ((int)TWDataSize(txInputData.get()), 88); + EXPECT_EQ((int)txInputData.size(), 88); Binance::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), (int)TWDataSize(txInputData.get()))); + ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); EXPECT_EQ(input.chain_id(), "Binance-Chain-Nile"); EXPECT_TRUE(input.has_send_order()); ASSERT_EQ(input.send_order().inputs_size(), 1); - EXPECT_EQ(hex(data(input.send_order().inputs(0).address())), "40c2979694bbc961023d1d27be6fc4d21a9febe6"); + EXPECT_EQ(hex(data(input.send_order().inputs(0).address())), + "40c2979694bbc961023d1d27be6fc4d21a9febe6"); } /// Step 2: Obtain preimage hash - const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); + auto txInputDataPtr = WRAPD(TWDataCreateWithBytes(txInputData.data(), txInputData.size())); + const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputDataPtr.get())); auto preImageHash = data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); - TxCompiler::Proto::PreSigningOutput output; - ASSERT_TRUE(output.ParseFromArray(preImageHash.data(), int(preImageHash.size()))); - ASSERT_EQ(output.error(), 0); - const auto preImageHashData = data(output.data_hash()); + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHash.data(), int(preImageHash.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + const auto preImageHashData = data(preSigningOutput.data_hash()); EXPECT_EQ(hex(preImageHashData), "3f3fece9059e714d303a9a1496ddade8f2c38fa78fc4cc2e505c5dbb0ea678d1"); // Simulate signature, normally obtained from signature server - const auto publicKeyData = parse_hex("026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502"); + const auto publicKeyData = + parse_hex("026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502"); const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); - const auto signature = parse_hex("1b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679"); + const auto signature = + parse_hex("1b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5" + "366bb8b041781a6771248550d04094c3d7a504f9e8310679"); // Verify signature (pubkey & hash & signature) - { - EXPECT_TRUE(publicKey.verify(signature, preImageHashData)); - } + { EXPECT_TRUE(publicKey.verify(signature, preImageHashData)); } /// Step 3: Compile transaction info const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( - coin, - txInputData.get(), + coin, txInputDataPtr.get(), WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), - WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get()) - ); - - const auto ExpectedTx = "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679"; + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get())); + + const auto ExpectedTx = + "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001" + "121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35" + "920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c" + "253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a5" + "04f9e8310679"; { - EXPECT_EQ(TWDataSize(outputData.get()), 189); Binance::Proto::SigningOutput output; - ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), (int)TWDataSize(outputData.get()))); + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); EXPECT_EQ(hex(output.encoded()), ExpectedTx); } - { // Double check: check if simple signature process gives the same result. Note that private keys were not used anywhere up to this point. + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. Binance::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), (int)TWDataSize(txInputData.get()))); + ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputDataPtr.get()), + (int)TWDataSize(txInputDataPtr.get()))); auto key = parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"); input.set_private_key(key.data(), key.size()); @@ -109,36 +134,25 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBinance) { TEST(TWTransactionCompiler, ExternalSignatureSignEthereum) { /// Step 1: Prepare transaction input (protobuf) const auto coin = TWCoinTypeEthereum; - const auto txInputData0 = WRAPD(TWTransactionCompilerBuildInput( - coin, - STRING("0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F").get(), // from - STRING("0x3535353535353535353535353535353535353535").get(), // to - STRING("1000000000000000000").get(), // amount - STRING("ETH").get(), // asset - STRING("").get(), // memo - STRING("").get() // chainId - )); - - // Check, by parsing - EXPECT_EQ((int)TWDataSize(txInputData0.get()), 61); - Ethereum::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData0.get()), (int)TWDataSize(txInputData0.get()))); - EXPECT_EQ(hex(input.chain_id()), "01"); - EXPECT_EQ(input.to_address(), "0x3535353535353535353535353535353535353535"); - ASSERT_TRUE(input.transaction().has_transfer()); - EXPECT_EQ(hex(input.transaction().transfer().amount()), "0de0b6b3a7640000"); - - // Set a few other values + Ethereum::Proto::SigningInput signingInput; + const auto nonce = store(uint256_t(11)); + const auto chainId = store(uint256_t(1)); const auto gasPrice = store(uint256_t(20000000000)); const auto gasLimit = store(uint256_t(21000)); - input.set_nonce(nonce.data(), nonce.size()); - input.set_gas_price(gasPrice.data(), gasPrice.size()); - input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_tx_mode(Ethereum::Proto::Legacy); + const auto amount = store(uint256_t(1'000'000'000'000'000'000)); + + signingInput.set_nonce(nonce.data(), nonce.size()); + signingInput.set_chain_id(chainId.data(), chainId.size()); + signingInput.set_gas_price(gasPrice.data(), gasPrice.size()); + signingInput.set_gas_limit(gasLimit.data(), gasLimit.size()); + signingInput.set_tx_mode(Ethereum::Proto::Legacy); + signingInput.set_to_address("0x3535353535353535353535353535353535353535"); + + signingInput.mutable_transaction()->mutable_transfer()->set_amount(amount.data(), amount.size()); // Serialize back, this shows how to serialize SigningInput protobuf to byte array - const auto txInputDataData = data(input.SerializeAsString()); + const auto txInputDataData = data(signingInput.SerializeAsString()); const auto txInputData = WRAPD(TWDataCreateWithBytes(txInputDataData.data(), txInputDataData.size())); EXPECT_EQ((int)TWDataSize(txInputData.get()), 75); @@ -146,43 +160,50 @@ TEST(TWTransactionCompiler, ExternalSignatureSignEthereum) { const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); auto preImageHash = data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); - TxCompiler::Proto::PreSigningOutput output; - ASSERT_TRUE(output.ParseFromArray(preImageHash.data(), int(preImageHash.size()))); - ASSERT_EQ(output.error(), 0); - const auto preImageHashData = data(output.data_hash()); + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHash.data(), int(preImageHash.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + const auto preImageHashData = data(preSigningOutput.data_hash()); EXPECT_EQ(hex(preImageHashData), "15e180a6274b2f6a572b9b51823fce25ef39576d10188ecdcd7de44526c47217"); // Simulate signature, normally obtained from signature server - const auto publicKeyData = parse_hex("044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad7119ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a"); + const auto publicKeyData = + parse_hex("044bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad711" + "9ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a"); const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1Extended); - const auto signature = parse_hex("360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c9796677900"); + const auto signature = + parse_hex("360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a19" + "1d844db458893b928f3efbfee90c9febf51ab84c9796677900"); // Verify signature (pubkey & hash & signature) - { - EXPECT_TRUE(publicKey.verify(signature, preImageHashData)); - } + { EXPECT_TRUE(publicKey.verify(signature, preImageHashData)); } /// Step 3: Compile transaction info const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( - coin, - txInputData.get(), + coin, txInputData.get(), WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), - WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get()) - ); + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get())); - const auto ExpectedTx = "f86c0b8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07ba053bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c97966779"; + const auto ExpectedTx = + "f86c0b8504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a0" + "360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07ba053bd9d1376e15a191d844db4" + "58893b928f3efbfee90c9febf51ab84c97966779"; { - EXPECT_EQ(TWDataSize(outputData.get()), 183); + EXPECT_EQ(TWDataSize(outputData.get()), 217ul); Ethereum::Proto::SigningOutput output; - ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), (int)TWDataSize(outputData.get()))); + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); - EXPECT_EQ(output.encoded().size(), 110); + EXPECT_EQ(output.encoded().size(), 110ul); EXPECT_EQ(hex(output.encoded()), ExpectedTx); } - { // Double check: check if simple signature process gives the same result. Note that private keys were not used anywhere up to this point. + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. Ethereum::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), (int)TWDataSize(txInputData.get()))); + ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), + (int)TWDataSize(txInputData.get()))); auto key = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); input.set_private_key(key.data(), key.size()); @@ -199,14 +220,19 @@ Data dataFromTWData(std::shared_ptr data) { } TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { - // Test external signining with a Bircoin transaction with 3 input UTXOs, all used, but only using 2 public keys. + // Test external signining with a Bitcoin transaction with 3 input UTXOs, all used, but only using 2 public keys. // Three signatures are neeeded. This illustrates that order of UTXOs/hashes is not always the same. - const auto revUtxoHash0 = parse_hex("07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8"); - const auto revUtxoHash1 = parse_hex("d6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e"); - const auto revUtxoHash2 = parse_hex("6021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d"); - const auto inPubKey0 = parse_hex("024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382"); - const auto inPubKey1 = parse_hex("0217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc"); + const auto revUtxoHash0 = + parse_hex("07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa8"); + const auto revUtxoHash1 = + parse_hex("d6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e"); + const auto revUtxoHash2 = + parse_hex("6021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d"); + const auto inPubKey0 = + parse_hex("024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382"); + const auto inPubKey1 = + parse_hex("0217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc"); const auto inPubKeyHash0 = parse_hex("bd92088bb7e82d611a9b94fbb74a0908152b784f"); const auto inPubKeyHash1 = parse_hex("6641abedacf9483b793afe1718689cc9420bbb1c"); @@ -219,11 +245,11 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { }; std::vector utxoInfos = { // first - UtxoInfo {revUtxoHash0, inPubKey0, 600'000, 0}, + UtxoInfo{revUtxoHash0, inPubKey0, 600'000, 0}, // second UTXO, with same pubkey - UtxoInfo {revUtxoHash1, inPubKey0, 500'000, 1}, + UtxoInfo{revUtxoHash1, inPubKey0, 500'000, 1}, // third UTXO, with different pubkey - UtxoInfo {revUtxoHash2, inPubKey1, 400'000, 0}, + UtxoInfo{revUtxoHash2, inPubKey1, 400'000, 0}, }; // Signature infos, indexed by pubkeyhash+hash @@ -232,83 +258,93 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { Data publicKey; }; std::map signatureInfos = { - { - hex(inPubKeyHash0) + "+" + "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7", - { - parse_hex("304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a40"), - inPubKey0, - } - }, - { - hex(inPubKeyHash1) + "+" + "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6", - { - parse_hex("3044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e4"), - inPubKey1, - } - }, - { - hex(inPubKeyHash0) + "+" + "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101", - { - parse_hex("30440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc"), - inPubKey0, - } - }, + {hex(inPubKeyHash0) + "+" + + "a296bead4172007be69b21971a790e076388666c162a9505698415f1b003ebd7", + { + parse_hex("304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b349022" + "00a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a40"), + inPubKey0, + }}, + {hex(inPubKeyHash1) + "+" + + "505f527f00e15fcc5a2d2416c9970beb57dfdfaca99e572a01f143b24dd8fab6", + { + parse_hex("3044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe11582022" + "0646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e4"), + inPubKey1, + }}, + {hex(inPubKeyHash0) + "+" + + "60ed6e9371e5ddc72fd88e46a12cb2f68516ebd307c0fd31b1b55cf767272101", + { + parse_hex("30440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b1022" + "07e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc"), + inPubKey0, + }}, }; const auto coin = TWCoinTypeBitcoin; const auto ownAddress = "bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv"; // Setup input for Plan - Bitcoin::Proto::SigningInput input; - input.set_coin_type(coin); - input.set_hash_type(TWBitcoinSigHashTypeAll); - input.set_amount(1'200'000); - input.set_use_max_amount(false); - input.set_byte_fee(1); - input.set_to_address("bc1q2dsdlq3343vk29runkgv4yc292hmq53jedfjmp"); - input.set_change_address(ownAddress); + Bitcoin::Proto::SigningInput signingInput; + signingInput.set_coin_type(coin); + signingInput.set_hash_type(TWBitcoinSigHashTypeAll); + signingInput.set_amount(1'200'000); + signingInput.set_use_max_amount(false); + signingInput.set_byte_fee(1); + signingInput.set_to_address("bc1q2dsdlq3343vk29runkgv4yc292hmq53jedfjmp"); + signingInput.set_change_address(ownAddress); // process UTXOs int count = 0; - for (auto& u: utxoInfos) { + for (auto& u : utxoInfos) { const auto publicKey = PublicKey(u.publicKey, TWPublicKeyTypeSECP256k1); const auto address = Bitcoin::SegwitAddress(publicKey, "bc"); - if (count == 0) EXPECT_EQ(address.string(), ownAddress); - if (count == 1) EXPECT_EQ(address.string(), ownAddress); - if (count == 2) EXPECT_EQ(address.string(), "bc1qveq6hmdvl9yrk7f6lct3s6yue9pqhwcuxedggg"); + if (count == 0) + EXPECT_EQ(address.string(), ownAddress); + if (count == 1) + EXPECT_EQ(address.string(), ownAddress); + if (count == 2) + EXPECT_EQ(address.string(), "bc1qveq6hmdvl9yrk7f6lct3s6yue9pqhwcuxedggg"); const auto utxoScript = Bitcoin::Script::lockScriptForAddress(address.string(), coin); - if (count == 0) EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); - if (count == 1) EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); - if (count == 2) EXPECT_EQ(hex(utxoScript.bytes), "00146641abedacf9483b793afe1718689cc9420bbb1c"); + if (count == 0) + EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); + if (count == 1) + EXPECT_EQ(hex(utxoScript.bytes), "0014bd92088bb7e82d611a9b94fbb74a0908152b784f"); + if (count == 2) + EXPECT_EQ(hex(utxoScript.bytes), "00146641abedacf9483b793afe1718689cc9420bbb1c"); Data keyHash; EXPECT_TRUE(utxoScript.matchPayToWitnessPublicKeyHash(keyHash)); - if (count == 0) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); - if (count == 1) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); - if (count == 2) EXPECT_EQ(hex(keyHash), hex(inPubKeyHash1)); + if (count == 0) + EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); + if (count == 1) + EXPECT_EQ(hex(keyHash), hex(inPubKeyHash0)); + if (count == 2) + EXPECT_EQ(hex(keyHash), hex(inPubKeyHash1)); const auto redeemScript = Bitcoin::Script::buildPayToPublicKeyHash(keyHash); if (count == 0) EXPECT_EQ(hex(redeemScript.bytes), "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac"); if (count == 1) EXPECT_EQ(hex(redeemScript.bytes), "76a914bd92088bb7e82d611a9b94fbb74a0908152b784f88ac"); if (count == 2) EXPECT_EQ(hex(redeemScript.bytes), "76a9146641abedacf9483b793afe1718689cc9420bbb1c88ac"); - (*input.mutable_scripts())[hex(keyHash)] = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); + (*signingInput.mutable_scripts())[hex(keyHash)] = std::string(redeemScript.bytes.begin(), redeemScript.bytes.end()); - auto utxo = input.add_utxo(); + auto utxo = signingInput.add_utxo(); utxo->set_script(utxoScript.bytes.data(), utxoScript.bytes.size()); utxo->set_amount(u.amount); - utxo->mutable_out_point()->set_hash(std::string(u.revUtxoHash.begin(), u.revUtxoHash.end())); + utxo->mutable_out_point()->set_hash( + std::string(u.revUtxoHash.begin(), u.revUtxoHash.end())); utxo->mutable_out_point()->set_index(u.index); utxo->mutable_out_point()->set_sequence(UINT32_MAX); ++count; } EXPECT_EQ(count, 3); - EXPECT_EQ(input.utxo_size(), 3); + EXPECT_EQ(signingInput.utxo_size(), 3); // Plan Bitcoin::Proto::TransactionPlan plan; - ANY_PLAN(input, plan, coin); + ANY_PLAN(signingInput, plan, coin); // Plan is checked, assume it is accepted EXPECT_EQ(plan.amount(), 1'200'000); @@ -321,12 +357,11 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { EXPECT_EQ(hex(plan.utxos(2).out_point().hash()), hex(revUtxoHash0)); // Extend input with accepted plan - *input.mutable_plan() = plan; + *signingInput.mutable_plan() = plan; - // Serialize input - const auto txInputDataData = data(input.SerializeAsString()); + // Serialize signingInput + const auto txInputDataData = data(signingInput.SerializeAsString()); const auto txInputData = WRAPD(TWDataCreateWithBytes(txInputDataData.data(), txInputDataData.size())); - EXPECT_EQ((int)TWDataSize(txInputData.get()), 692); /// Step 2: Obtain preimage hashes const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); @@ -346,10 +381,10 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { auto signatureVec = WRAP(TWDataVector, TWDataVectorCreate()); auto pubkeyVec = WRAP(TWDataVector, TWDataVectorCreate()); for (const auto& h: hashes) { - const auto& preImageHash = TW::data(h.data_hash()); + const auto& preImageHash_ = TW::data(h.data_hash()); const auto& pubkeyhash = h.public_key_hash(); - const std::string key = hex(pubkeyhash) + "+" + hex(preImageHash); + const std::string key = hex(pubkeyhash) + "+" + hex(preImageHash_); const auto sigInfoFind = signatureInfos.find(key); ASSERT_TRUE(sigInfoFind != signatureInfos.end()); const auto& sigInfo = std::get<1>(*sigInfoFind); @@ -360,20 +395,32 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { TWDataVectorAdd(signatureVec.get(), WRAPD(TWDataCreateWithBytes(signature.data(), signature.size())).get()); TWDataVectorAdd(pubkeyVec.get(), WRAPD(TWDataCreateWithBytes(publicKeyData.data(), publicKeyData.size())).get()); // Verify signature (pubkey & hash & signature) - EXPECT_TRUE(publicKey.verifyAsDER(signature, preImageHash)); + EXPECT_TRUE(publicKey.verifyAsDER(signature, preImageHash_)); } /// Step 3: Compile transaction info const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( - coin, txInputData.get(), signatureVec.get(), pubkeyVec.get() - )); - - const auto ExpectedTx = "010000000001036021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d0000000000ffffffffd6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e0100000000ffffffff07c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa80000000000ffffffff02804f1200000000001600145360df8231ac5965147c9d90ca930a2aafb05232cb92040000000000160014bd92088bb7e82d611a9b94fbb74a0908152b784f02473044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e37966414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e401210217142f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc0247304402201857bc6e6e48b46046a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efdbc9c6784aa2571ff4f6f2a400121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382024730440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc0121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb49338200000000"; + coin, txInputData.get(), signatureVec.get(), pubkeyVec.get())); + + const auto ExpectedTx = + "010000000001036021efcf7555f90627364339fc921139dd40a06ccb2cb2a2a4f8f4ea7a2dc74d0000000000ff" + "ffffffd6892a5aa54e3b8fe430efd23f49a8950733aaa9d7c915d9989179f48dd1905e0100000000ffffffff07" + "c42b969286be06fae38528c85f0a1ce508d4df837eb5ac4cf5f2a7a9d65fa80000000000ffffffff02804f1200" + "000000001600145360df8231ac5965147c9d90ca930a2aafb05232cb92040000000000160014bd92088bb7e82d" + "611a9b94fbb74a0908152b784f02473044022041294880caa09bb1b653775310fcdd1458da6b8e7d7fae34e379" + "66414fe115820220646397c9d2513edc5974ecc336e9b287de0cdf071c366f3b3dc3ff309213e4e40121021714" + "2f69535e4dad0dc7060df645c55a174cc1bfa5b9eb2e59aad2ae96072dfc0247304402201857bc6e6e48b46046" + "a4bd204136fc77e24c240943fb5a1f0e86387aae59b34902200a7f31478784e51c49f46ef072745a4f263d7efd" + "bc9c6784aa2571ff4f6f2a400121024bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb49" + "3382024730440220764e3d5b3971c4b3e70b23fb700a7462a6fe519d9830e863a1f8388c402ad0b102207e777f" + "7972c636961f92375a2774af3b7a2a04190251bbcb31d19c70927952dc0121024bc2a31265153f07e70e0bab08" + "724e6b85e217f8cd628ceb62974247bb49338200000000"; { - EXPECT_EQ(TWDataSize(outputData.get()), 786); + EXPECT_EQ(TWDataSize(outputData.get()), 786ul); Bitcoin::Proto::SigningOutput output; - ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), (int)TWDataSize(outputData.get()))); + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); - EXPECT_EQ(output.encoded().size(), 518); + EXPECT_EQ(output.encoded().size(), 518ul); EXPECT_EQ(hex(output.encoded()), ExpectedTx); } @@ -384,8 +431,10 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { // 2 private keys are needed (despite >2 UTXOs) auto key0 = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); auto key1 = parse_hex("7878787878787878787878787878787878787878787878787878787878787878"); - EXPECT_EQ(hex(PrivateKey(key0).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey0)); - EXPECT_EQ(hex(PrivateKey(key1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey1)); + EXPECT_EQ(hex(PrivateKey(key0).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + hex(inPubKey0)); + EXPECT_EQ(hex(PrivateKey(key1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + hex(inPubKey1)); *input.add_private_key() = std::string(key0.begin(), key0.end()); *input.add_private_key() = std::string(key1.begin(), key1.end()); @@ -395,3 +444,350 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { ASSERT_EQ(hex(output.encoded()), ExpectedTx); } } + +TEST(TWTransactionCompiler, ExternalSignatureSignSolana) { + const auto coin = TWCoinTypeSolana; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::Solana::Proto::SigningInput(); + auto& message = *input.mutable_transfer_transaction(); + auto recipient = std::string("3UVYmECPPMZSCqWKfENfuoTv51fTDTWicX9xmBD2euKe"); + auto sender = std::string("sp6VUqq1nDEuU83bU2hstmEYrJNipJYpwS7gZ7Jv7ZH"); + input.set_sender(sender); + input.set_recent_blockhash(std::string("TPJFTN4CjBn12HiBfAbGUhpD9zGvRSm2RcheFRA4Fyv")); + message.set_recipient(recipient); + message.set_value((uint64_t)1000); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + const auto txInputData = WRAPD(TWDataCreateWithBytes(inputStrData.data(), inputStrData.size())); + + /// Step 2: Obtain preimage hash + const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); + auto preImageHashData = data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); + + auto preSigningOutput = TW::Solana::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + ASSERT_EQ(preSigningOutput.signers_size(), 1); + auto signer = preSigningOutput.signers(0); + EXPECT_EQ(signer, sender); + auto preImageHash = preSigningOutput.data(); + EXPECT_EQ(hex(preImageHash), + "010001030d044a62d0a4dfe5a037a15b59fa4d4d0d3ab81103a2c10a6da08a4d058611c024c255a8bc3e" + "8496217a2cd2a1894b9b9dcace04fcd9c0d599acdaaea40a1b6100000000000000000000000000000000" + "0000000000000000000000000000000006c25012cc11a599a45b3b2f7f8a7c65b0547fa0bb67170d7a0c" + "d1eda4e2c9e501020200010c02000000e803000000000000"); + + // Simulate signature, normally obtained from signature server + const auto publicKeyData = + parse_hex("0d044a62d0a4dfe5a037a15b59fa4d4d0d3ab81103a2c10a6da08a4d058611c0"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeED25519); + const auto signature = + parse_hex("a8c610697087eaf8a34b3facbe06f8e9bb9603bb03270dad021ffcd2fc37b6e9efcdcb78b227401f" + "000eb9231c67685240890962e44a17fd27fc2ff7b971df03"); + + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + coin, txInputData.get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get())); + + const auto ExpectedTx = + "5bWxVCP5fuzkKSGby9hnsLranszQJR2evJGTfBrpDQ4rJceW1WxKNrWqVPBsN2QCAGmE6W7VaYkyWjv39HhGrr1Ne2" + "QSUuHZdyyn7hK4pxzLPMgPG8fY1XvXdppWMaKMLmhriLkckzGKJMaE3pWBRFBKzigXY28714uUNndb7S9hVakxa59h" + "rLph39CMgAkcj6b8KYvJEkb1YdYytHSZNGi4kVVTNqiicNgPdf1gmG6qz9zVtnqj9JtaD2efdS8qxsKnvNWSgb8Xxb" + "T6dwyp7msUUi7d27cYaPTpK"; + { + Solana::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); + EXPECT_EQ(output.encoded(), ExpectedTx); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + Solana::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(TWDataBytes(txInputData.get()), + (int)TWDataSize(txInputData.get()))); + auto key = parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1"); + signingInput.set_private_key(key.data(), key.size()); + + Solana::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } +} + +TEST(TWTransactionCompiler, ExternalSignatureSignNULS) { + const auto coin = TWCoinTypeNULS; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::NULS::Proto::SigningInput(); + auto from = std::string("NULSd6HgWabfcG6H7NDK2TJvtoU3wxY1YLKwJ"); + auto to = std::string("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + uint32_t chainId = 1; + uint32_t idassetsId = 1; + auto amount = TW::store((TW::uint256_t)10000000); + auto amountStr = std::string(amount.begin(), amount.end()); + auto balance = TW::store((TW::uint256_t)100000000); + auto balanceStr = std::string(balance.begin(), balance.end()); + auto nonce = std::string("0000000000000000"); + input.set_from(from); + input.set_to(to); + input.set_amount(amountStr); + input.set_chain_id(chainId); + input.set_idassets_id(idassetsId); + input.set_nonce(nonce); + input.set_balance(balanceStr); + input.set_timestamp((uint32_t)1569228280); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + const auto txInputData = WRAPD(TWDataCreateWithBytes(inputStrData.data(), inputStrData.size())); + + /// Step 2: Obtain preimage hash + const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); + auto preImageHashData = data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = preSigningOutput.data(); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ(hex(preImage), + "0200f885885d00008c01170100012c177a01a7afbe98e094007b99476534fb7926b701000100201d9a00" + "000000000000000000000000000000000000000000000000000000000800000000000000000001170100" + "01f05e7878971f3374515eabb6f16d75219d887312010001008096980000000000000000000000000000" + "0000000000000000000000000000000000000000000000"); + EXPECT_EQ(hex(preImageHash), + "8746b37cb4b443424d3093e8107c5dfd6c5318010bbffcc8e8ba7c1da60877fd"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = + parse_hex("033c87a3d9b812556b3034b6471cad5131a01e210c1d7ca06dd53b7d0aff0ee045"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = + parse_hex("a5234269eab6fe8a1510dd0cb36070a03464b48856e1ef2681dbb79a5ec656f92961ac01d401a6f8" + "49acc958c6c9653f49282f5a0916df036ea8766918bac19500"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + coin, txInputData.get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get())); + + const auto ExpectedEncoded = parse_hex( + "0200f885885d00008c01170100012c177a01a7afbe98e094007b99476534fb7926b701000100201d9a00000000" + "00000000000000000000000000000000000000000000000000080000000000000000000117010001f05e787897" + "1f3374515eabb6f16d75219d887312010001008096980000000000000000000000000000000000000000000000" + "00000000000000000000000000006a21033c87a3d9b812556b3034b6471cad5131a01e210c1d7ca06dd53b7d0a" + "ff0ee045473045022100a5234269eab6fe8a1510dd0cb36070a03464b48856e1ef2681dbb79a5ec656f9022029" + "61ac01d401a6f849acc958c6c9653f49282f5a0916df036ea8766918bac195"); + const auto ExpectedTx = std::string(ExpectedEncoded.begin(), ExpectedEncoded.end()); + { + EXPECT_EQ(TWDataSize(outputData.get()), 259ul); + NULS::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); + EXPECT_EQ(output.encoded(), ExpectedTx); + EXPECT_EQ(output.encoded().size(), 256ul); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + NULS::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(TWDataBytes(txInputData.get()), + (int)TWDataSize(txInputData.get()))); + auto key = parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1"); + signingInput.set_private_key(key.data(), key.size()); + + NULS::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } +} + +// TEST(TWTransactionCompiler, ExternalSignatureSignRipple) { +// const auto coin = TWCoinTypeXRP; +// /// Step 1: Prepare transaction input (protobuf) +// auto key = parse_hex("ba005cd605d8a02e3d5dfd04234cef3a3ee4f76bfbad2722d1fb5af8e12e6764"); +// auto input = TW::Ripple::Proto::SigningInput(); +// auto privateKey = TW::PrivateKey(key); +// auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); +// input.set_amount(29000000); +// input.set_fee(200000); +// input.set_sequence(1); +// input.set_account("rDpysuumkweqeC7XdNgYNtzL5GxbdsmrtF"); +// input.set_destination("rU893viamSnsfP3zjzM2KPxjqZjXSXK6VF"); +// input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); +// auto inputString = input.SerializeAsString(); +// auto inputStrData = TW::Data(inputString.begin(), inputString.end()); +// const auto txInputData = WRAPD(TWDataCreateWithBytes(inputStrData.data(), inputStrData.size())); + +// /// Step 2: Obtain preimage hash +// const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); +// auto preImageHashData = data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); + +// auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); +// ASSERT_TRUE( +// preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); +// ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); +// // preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size()); +// auto preImage = preSigningOutput.data(); +// EXPECT_EQ(hex(preImage), +// "5354580012000022800000002400000001614000000001ba8140684000000000030d407321026cc34b92" +// "cefb3a4537b3edb0b6044c04af27c01583c577823ecc69a9a21119b681148400b6b6d08d5d495653d73e" +// "da6804c249a5148883148132e4e20aecf29090ac428a9c43f230a829220d"); +// auto preImageHash = preSigningOutput.data_hash(); +// EXPECT_EQ(hex(preImageHash), +// "8624dbbd5da9ccc8f7a50faf8af8709837db72f51a50cac15a6cd28ce6107b3d"); +// // Simulate signature, normally obtained from signature server +// const auto signature = privateKey.sign(parse_hex("8624dbbd5da9ccc8f7a50faf8af8709837db72f51a50cac15a6cd28ce6107b3d"), TWCurveSECP256k1); +// // Verify signature (pubkey & hash & signature) +// EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); +// /// Step 3: Compile transaction info +// const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( +// coin, txInputData.get(), +// WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), +// WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKey.bytes)).get())); + +// const auto ExpectedTx = std::string( +// "12000022800000002400000001614000000001ba8140684000000000030d407321026cc34b92cefb3a45" +// "37b3edb0b6044c04af27c01583c577823ecc69a9a21119b6744630440220067f20b3eebfc7107dd0bcc7" +// "2337a236ac3be042c0469f2341d76694a17d4bb9022048393d7ee7dcb729783b33f5038939ddce1bb833" +// "7e66d752974626854556bbb681148400b6b6d08d5d495653d73eda6804c249a5148883148132e4e20aec" +// "f29090ac428a9c43f230a829220d"); +// EXPECT_EQ(TWDataSize(outputData.get()), 185ul); + +// { +// TW::Ripple::Proto::SigningOutput output; +// ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), +// (int)TWDataSize(outputData.get()))); + +// EXPECT_EQ(hex(output.encoded()), ExpectedTx); +// EXPECT_EQ(output.encoded().size(), 182ul); +// ASSERT_EQ(output.error(), TW::Common::Proto::SigningError::OK); +// } + +// { // Double check: check if simple signature process gives the same result. Note that private +// // keys were not used anywhere up to this point. +// Ripple::Proto::SigningInput signingInput; +// ASSERT_TRUE(signingInput.ParseFromArray(TWDataBytes(txInputData.get()), +// (int)TWDataSize(txInputData.get()))); +// signingInput.set_private_key(key.data(), key.size()); + +// Ripple::Proto::SigningOutput output; +// ANY_SIGN(signingInput, coin); + +// ASSERT_EQ(hex(output.encoded()), ExpectedTx); +// } +// } + +TEST(TWTransactionCompiler, ExternalSignatureSignNULSToken) { + const auto coin = TWCoinTypeNULS; + /// Step 1: Prepare transaction input (protobuf) + auto input = TW::NULS::Proto::SigningInput(); + auto from = std::string("NULSd6HgWabfcG6H7NDK2TJvtoU3wxY1YLKwJ"); + auto to = std::string("NULSd6Hgied7ym6qMEfVzZanMaa9qeqA6TZSe"); + uint32_t chainId = 9; + uint32_t idassetsId = 1; + auto amount = TW::store((TW::uint256_t)10000000); + auto amountStr = std::string(amount.begin(), amount.end()); + auto balance = TW::store((TW::uint256_t)100000000); + auto balanceStr = std::string(balance.begin(), balance.end()); + auto feePayerBalance = TW::store((TW::uint256_t)100000000); + auto feePayerBalanceStr = std::string(feePayerBalance.begin(), feePayerBalance.end()); + auto nonce = std::string("0000000000000000"); + auto asset_nonce = std::string("0000000000000000"); + input.set_from(from); + input.set_to(to); + input.set_amount(amountStr); + input.set_chain_id(chainId); + input.set_idassets_id(idassetsId); + input.set_nonce(asset_nonce.data(), asset_nonce.size()); + input.set_balance(balanceStr); + input.set_fee_payer(from); + input.set_fee_payer_nonce(nonce.data(), nonce.size()); + input.set_fee_payer_balance(feePayerBalanceStr); + input.set_timestamp((uint32_t)1569228280); + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + const auto txInputData = WRAPD(TWDataCreateWithBytes(inputStrData.data(), inputStrData.size())); + + /// Step 2: Obtain preimage hash + const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); + auto preImageHashData = + data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); + + auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput(); + ASSERT_TRUE( + preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size())); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + auto preImage = preSigningOutput.data(); + auto preImageHash = preSigningOutput.data_hash(); + EXPECT_EQ( + hex(preImage), + "0200f885885d0000d202170100012c177a01a7afbe98e094007b99476534fb7926b70900010080969800000000" + "0000000000000000000000000000000000000000000000000008000000000000000000170100012c177a01a7af" + "be98e094007b99476534fb7926b701000100a08601000000000000000000000000000000000000000000000000" + "0000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120900010080" + "969800000000000000000000000000000000000000000000000000000000000000000000000000"); + EXPECT_EQ(hex(preImageHash), + "9040642ce845b320453b2ccd6f80efc38fdf61ec8f0c12e0c16f6244ec2e0496"); + // Simulate signature, normally obtained from signature server + const Data publicKeyData = + parse_hex("033c87a3d9b812556b3034b6471cad5131a01e210c1d7ca06dd53b7d0aff0ee045"); + const PublicKey publicKey = PublicKey(publicKeyData, TWPublicKeyTypeSECP256k1); + const auto signature = + parse_hex("5ddea604c6cdfcf6cbe32f5873937641676ee5f9aee3c40aa9857c59aefedff25b77429cf62307d4" + "3a6a79b4c106123e6232e3981032573770fe2726bf9fc07c00"); + // Verify signature (pubkey & hash & signature) + EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); + + /// Step 3: Compile transaction info + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + coin, txInputData.get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get())); + + const auto ExpectedEncoded = parse_hex( + "0200f885885d0000d202170100012c177a01a7afbe98e094007b99476534fb7926b70900010080969800000000" + "0000000000000000000000000000000000000000000000000008000000000000000000170100012c177a01a7af" + "be98e094007b99476534fb7926b701000100a08601000000000000000000000000000000000000000000000000" + "0000000000080000000000000000000117010001f05e7878971f3374515eabb6f16d75219d8873120900010080" + "9698000000000000000000000000000000000000000000000000000000000000000000000000006921033c87a3" + "d9b812556b3034b6471cad5131a01e210c1d7ca06dd53b7d0aff0ee04546304402205ddea604c6cdfcf6cbe32f" + "5873937641676ee5f9aee3c40aa9857c59aefedff202205b77429cf62307d43a6a79b4c106123e6232e3981032" + "573770fe2726bf9fc07c"); + const auto ExpectedTx = std::string(ExpectedEncoded.begin(), ExpectedEncoded.end()); + { + EXPECT_EQ(TWDataSize(outputData.get()), 328ul); + NULS::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); + EXPECT_EQ(output.encoded(), ExpectedTx); + EXPECT_EQ(output.encoded().size(), 325ul); + } + + { // Double check: check if simple signature process gives the same result. Note that private + // keys were not used anywhere up to this point. + NULS::Proto::SigningInput signingInput; + ASSERT_TRUE(signingInput.ParseFromArray(TWDataBytes(txInputData.get()), + (int)TWDataSize(txInputData.get()))); + auto key = parse_hex("044014463e2ee3cc9c67a6f191dbac82288eb1d5c1111d21245bdc6a855082a1"); + signingInput.set_private_key(key.data(), key.size()); + + NULS::Proto::SigningOutput output; + ANY_SIGN(signingInput, coin); + + ASSERT_EQ(output.encoded(), ExpectedTx); + } +} diff --git a/tests/interface/TWTransactionUtilTests.cpp b/tests/interface/TWTransactionUtilTests.cpp new file mode 100644 index 00000000000..c6b59420e6b --- /dev/null +++ b/tests/interface/TWTransactionUtilTests.cpp @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include + +#include "Bitcoin/Script.h" +#include "uint256.h" + +#include "TestUtilities.h" +#include + +#include + +using namespace TW; + +TEST(TWTransactionUtil, CalcTxHashBitcoin) { + constexpr auto coin = TWCoinTypeBitcoin; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("02000000000101089098890d2653567b9e8df2d1fbe5c3c8bf1910ca7184e301db0ad3b495c88e0100000000ffffffff02581b000000000000225120e8b706a97732e705e22ae7710703e7f589ed13c636324461afa443016134cc051040000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d02483045022100a44aa28446a9a886b378a4a65e32ad9a3108870bd725dc6105160bed4f317097022069e9de36422e4ce2e42b39884aa5f626f8f94194d1013007d5a1ea9220a06dce0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1"); +} + +TEST(TWTransactionUtil, CalcTxHashEthereum) { + constexpr auto coin = TWCoinTypeEthereum; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("f8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec8000025a0724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98cea0032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "0x199a7829fc5149e49b452c2cab76d8fa5a9682fee6e4891b8acb697ac142513e"); +} + +TEST(TWTransactionUtil, CalcTxHashSolana) { + constexpr auto coin = TWCoinTypeSolana; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("AnQTYwZpkm3fs4SdLxnV6gQj3hSLsyacpxDdLMALYWObm722f79IfYFTbZeFK9xHtMumiDOWAM2hHQP4r/GtbARpncaXgOVFv7OgbRLMbuCEJHO1qwcdCbtH72VzyzU8yw9sqqHIAaCUE8xaQTgT6Z5IyZfeyMe2QGJIfOjz65UPAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8Aqe6sdLXiXSDILEtzckCjkjchiSf6zVGpMYiAE5BE2IqHAQUEAgQDAQoMoA8AAAAAAAAG")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "3KbvREZUat76wgWMtnJfWbJL74Vzh4U2eabVJa3Z3bb2fPtW8AREP5pbmRwUrxZCESbTomWpL41PeKDcPGbojsej"); +} + +TEST(TWTransactionUtil, CalcTxHashCosmos) { + constexpr auto coin = TWCoinTypeCosmos; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "85392373F54577562067030BF0D61596C91188AA5E6CA8FFE731BD0349296411"); +} + +TEST(TWTransactionUtil, CalcTxHashTon) { + constexpr auto coin = TWCoinTypeTON; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("te6cckECGwEAA2sAAkWIACm9HPyVOpjCNOG6nbf+EwCONRHHpeMQsIlCoWNKhUaaHgECAgE0AwQBoXNpZ25///8R/////wAAAACACOfqY7L3l3aKc58eNxuJTaeH/fgBw2aG0coM+hjDpjWhJKbYKmsAD8v054HYSuO6vN3bQnV5U19BhsGfe1MDoAUBFP8A9KQT9LzyyAsGAFGAAAAAP///iKrcG+d35KaMMtuxik4jqNofFL51Mu1f8Qf19onqsDlyIAIKDsPIbQMWBwIBIAgJAWJiAC90HaFSSy94Zxb85WYu97uTKIxAlLvolycpcYkWc+8giFAAAAAAAAAAAAAAAAABFgIBSAoLAQLyDALc0CDXScEgkVuPYyDXCx8gghBleHRuvSGCEHNpbnS9sJJfA+CCEGV4dG66jrSAINchAdB01yH6QDD6RPgo+kQwWL2RW+DtRNCBAUHXIfQFgwf0Dm+hMZEw4YBA1yFwf9s84DEg10mBAoC5kTDgcOIXDQIBIA4PAR4g1wsfghBzaWduuvLgin8NAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYFwIBIBARABm+Xw9qJoQICg65D6AsAgFuEhMCAUgUFQAZrc52omhAIOuQ64X/wAAZrx32omhAEOuQ64WPwAAXsyX7UTQcdch1wsfgABGyYvtRNDXCgCAAAAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKGBkaAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNB8Ui06")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "437dae441a95a6bccdcdcea2560c313de24f13dd85c76d5d7ecaab1e70a1e52b"); +} + +TEST(TWTransactionUtil, CalcTxHashAptos) { + constexpr auto coin = TWCoinTypeAptos; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467"); +} + +TEST(TWTransactionUtil, CalcTxHashSui) { + constexpr auto coin = TWCoinTypeSui; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("AAACAAgQJwAAAAAAAAAgJZ/4B0q0Jcu0ifI24Y4I8D8aeFa998eih3vWT3OLUBUCAgABAQAAAQEDAAAAAAEBANV1rX8Y6UhGKlz2mPVk7zlKdSpx/sYkk6+KBVwBLA1QAQbywsjB2JZN8QGdZhbpcFcZvrq9kx2idVy5SM635olk7AIAAAAAAAAgYEVuxmf1zRBGdoDr+VDtMpIFF12s2Ua7I2ru1XyGF8/Vda1/GOlIRipc9pj1ZO85SnUqcf7GJJOvigVcASwNUAEAAAAAAAAA0AcAAAAAAAAA")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "HkPo6rYPyDY53x1MBszvSZVZyixVN7CHvCJGX381czAh"); +} diff --git a/tests/main.cpp b/tests/main.cpp index d75eab4a6bc..93248dc0b6c 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,26 +1,24 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. +#include #include +#include std::string TESTS_ROOT; -int main(int argc, char **argv) { - if (argc < 2) { - std::cerr << "Please specify the tests root folder." << std::endl; - exit(1); - } - - TESTS_ROOT = argv[1]; - struct stat s; - if (stat(TESTS_ROOT.c_str(), &s) != 0 || (s.st_mode & S_IFDIR) == 0) { - std::cerr << "Please specify the tests root folder. '" << TESTS_ROOT << "' is not a valid directory." << std::endl; - exit(1); - } - +int main(int argc, char** argv) { + // current path + auto path = std::filesystem::current_path(); + // executable path + path.append(argv[0]); + // normalize + path = std::filesystem::canonical(path); + // root path + path = path.parent_path().parent_path().parent_path(); + TESTS_ROOT = path.append("tests").string(); + std::cout<<"TESTS_ROOT: "< -#include - - -TEST(TWxDaiCoinType, TWCoinType) { - auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(TWCoinTypeXDai)); - auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x936798a1ef607c9e856d7861b15999c770c06f0887c4fc1f6acbf3bef09899c1")); - auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(TWCoinTypeXDai, txId.get())); - auto accId = WRAPS(TWStringCreateWithUTF8Bytes("0x12d61a95CF55e18D267C2F1AA67d8e42ae1368f8")); - auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(TWCoinTypeXDai, accId.get())); - auto id = WRAPS(TWCoinTypeConfigurationGetID(TWCoinTypeXDai)); - auto name = WRAPS(TWCoinTypeConfigurationGetName(TWCoinTypeXDai)); - - ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(TWCoinTypeXDai), 18); - ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypeXDai)); - ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeXDai)); - ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeXDai)); - assertStringsEqual(symbol, "xDAI"); - assertStringsEqual(txUrl, "https://blockscout.com/xdai/mainnet/tx/0x936798a1ef607c9e856d7861b15999c770c06f0887c4fc1f6acbf3bef09899c1"); - assertStringsEqual(accUrl, "https://blockscout.com/xdai/mainnet/address/0x12d61a95CF55e18D267C2F1AA67d8e42ae1368f8"); - assertStringsEqual(id, "xdai"); - assertStringsEqual(name, "Gnosis Chain"); -} diff --git a/tools/android-build b/tools/android-build index f63af0c09af..8c4b1344af8 100755 --- a/tools/android-build +++ b/tools/android-build @@ -5,11 +5,20 @@ set -e source $(dirname $0)/library -version=$(wc_read_version) +version=$(wc_read_version $1) + +if [[ `uname` == "Darwin" ]]; then + export BOOST_ROOT=$(brew --prefix boost) +fi pushd android ./gradlew assembleRelease -cp trustwalletcore/build/outputs/aar/trustwalletcore-release.aar ../build/trustwalletcore.aar +cp wallet-core/build/outputs/aar/wallet-core-release.aar ../build/wallet-core.aar popd -echo "Now upload build/trustwalletcore.aar to https://github.com/TrustWallet/trust-wallet-core/releases/tag/$version" +echo "Now upload build/wallet-core.aar to https://github.com/TrustWallet/trust-wallet-core/releases/tag/$version" + +echo "Building docs..." +tools/kotlin-doc + +echo "Now upload Kotlin docs from build/kdoc.zip to whatever place it needs to be" diff --git a/tools/android-release b/tools/android-release index 60199fb10e5..2b4bca2da1d 100755 --- a/tools/android-release +++ b/tools/android-release @@ -1,11 +1,16 @@ #!/bin/bash # # This script uploads android build to repositories defined in maven-push.gradle +# It also builds and uploads Kotlin docs set -e source $(dirname $0)/library -version=$(wc_read_version) +version=$(wc_read_version $1) + +if [[ `uname` == "Darwin" ]]; then + export BOOST_ROOT=$(brew --prefix boost) +fi echo "Building $version" @@ -14,3 +19,18 @@ pushd android ./gradlew clean build assembleRelease publish -Pversion="$version" echo "Android build uploaded" +popd # android + +echo "Building docs..." +tools/kotlin-doc + +pushd build/dokka +release_url=$(wc_release_url ${version}) +echo "release_url url for docs is $release_url" + +filename=kdoc.zip +download_url=$(wc_upload_asset ${release_url} ${filename}) +echo "download_url is $download_url" + +popd # build/dokka + diff --git a/tools/android-sdk b/tools/android-sdk new file mode 100644 index 00000000000..2ee8025fdb4 --- /dev/null +++ b/tools/android-sdk @@ -0,0 +1,50 @@ +#!/bin/bash + +export NDK_API_LEVEL=21 + +find_android_ndk() { + if [[ "$ANDROID_NDK_HOME" != "" ]]; then + >&2 echo "Use ANDROID_NDK_HOME" + elif [[ "$ANDROID_HOME" != "" ]]; then + >&2 echo "ANDROID_NDK_HOME is not set. Use ANDROID_HOME value instead" + ANDROID_NDK_HOME="$ANDROID_HOME/ndk" + else + >&2 echo "WARNING: ANDROID_HOME is not set. Use a default path" + ANDROID_NDK_HOME="$HOME/Library/Android/sdk/ndk" + fi + + TEST_CLANG="aarch64-linux-android$NDK_API_LEVEL-clang" + PATH_TO_CLANG=$(find "$ANDROID_NDK_HOME" -iname $TEST_CLANG -print -quit) + + if [[ "$PATH_TO_CLANG" == "" ]]; then + >&2 echo "ERROR: cannot find NDK tools within '$ANDROID_NDK_HOME'" + exit 22 + fi + + echo $(dirname "$PATH_TO_CLANG") +} + +find_android_cmdline_tools() { + # Default version of cmdline tools is 11. + # https://github.com/android-actions/setup-android/blob/9584f05408b63719e3464df8ac85bdbe58f88a64/src/main.ts#L9 + CMDLINE_TOOLS_VERSION="11.0" + + if [[ "$ANDROID_HOME" == "" ]]; then + >&2 echo "ANDROID_HOME is not set. Use a default path" + ANDROID_HOME="$HOME/Library/Android/sdk" + fi + + # cmdline-tools could have a 'latest' version, but if it was installed 2 years ago it may not be 'latest' as of today + if [ -x "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" ]; then + echo "$ANDROID_HOME/cmdline-tools/latest/bin" + return 0 + fi + + if [ -x "$ANDROID_HOME/cmdline-tools/$CMDLINE_TOOLS_VERSION/bin/sdkmanager" ]; then + echo "$ANDROID_HOME/cmdline-tools/$CMDLINE_TOOLS_VERSION/bin" + return 0 + fi + + >&2 echo "ERROR: cannot find SDK cmdline tools within '$ANDROID_HOME'" + exit 22 +} diff --git a/tools/android-test b/tools/android-test index 576cb4dd659..7185d860711 100755 --- a/tools/android-test +++ b/tools/android-test @@ -4,9 +4,17 @@ set -e +source "$(dirname $0)/android-sdk" + +ANDROID_CMDTOOLS=$(find_android_cmdline_tools) + AVD_NAME="integration-tests" PORT=5556 +if [[ `uname` == "Darwin" ]]; then + export BOOST_ROOT=$(brew --prefix boost) +fi + # Make sure it builds before starting an emulator pushd android ./gradlew assembleAndroidTest @@ -17,9 +25,9 @@ SERIAL=emulator-${PORT} # We have to echo "no" because it will ask us if we want to use a custom hardware profile, and we don't. echo -e "\nCreating Android emulator...\n" -echo "no" | "$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager" create avd \ +echo "no" | "$ANDROID_CMDTOOLS/avdmanager" create avd \ -n "${AVD_NAME}" \ - -k "system-images;android-26;google_apis;x86" \ + -k "system-images;android-33;google_apis;arm64-v8a" \ -f echo -e "\nAVD ${AVD_NAME} created." diff --git a/tools/build-and-test b/tools/build-and-test new file mode 100755 index 00000000000..bd4ba92c6cf --- /dev/null +++ b/tools/build-and-test @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# +# Perform full build and runs the tests. +# Prerequisite: workspace with dependencies installed, see bootstrap.sh + +# Fail if any commands fails +set -e + +echo "#### Generating files... ####" +tools/generate-files native + +echo "#### Building... ####" +cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ +make -Cbuild -j12 tests TrezorCryptoTests + +if [ -x "$(command -v clang-tidy)" ]; then + echo "#### Linting... ####" + tools/lint +fi + +echo "#### Running trezor-crypto tests... ####" +export CK_TIMEOUT_MULTIPLIER=4 +build/trezor-crypto/crypto/tests/TrezorCryptoTests + +echo "#### Running unit tests... ####" +FILTER="*" +if [ -n "$1" ]; then + FILTER="*$1*" +fi +build/tests/tests --gtest_filter="$FILTER" diff --git a/tools/check-coverage b/tools/check-coverage new file mode 100755 index 00000000000..f37723408d6 --- /dev/null +++ b/tools/check-coverage @@ -0,0 +1,30 @@ +#!/bin/bash + +# This script processes captured test code coverage information +# and compares current value to previously saved value (stored in coverage.stats). +# +# - Usage (CPP part): +# ./tools/check-coverage coverage.stats coverage.info +# +# - Usage (Rust part): +# ./tools/check-coverage rust/coverage.stats rust/coverage.info + +# Fail if any commands fails +set -e + +coverage_stats=$1 +coverage_info=$2 + +lcov --summary $coverage_info + +current=$(<$coverage_stats) +new=$(lcov --summary $coverage_info | awk '/lines.*:/{sub("%", "", $2);print $2}') + +if (( $(echo "$current > $new" | bc -l) )); then + echo "Code coverage drops to ${new}% (current is ${current}%)" + exit -1 +else + echo "Update code coverage: ${current}% -> ${new}%" + echo -n $new > $coverage_stats + # commit to master +fi diff --git a/tools/coverage b/tools/coverage index 8a33e5f215d..88b246fecd7 100755 --- a/tools/coverage +++ b/tools/coverage @@ -23,7 +23,7 @@ set -e function install_llvm_gcov() { cat << EOF > /tmp/llvm-gcov.sh #!/bin/bash -exec /usr/bin/llvm-cov-11 gcov "\$@" +exec /usr/bin/llvm-cov-14 gcov "\$@" EOF sudo chmod 755 /tmp/llvm-gcov.sh } @@ -38,6 +38,7 @@ else fi lcov --remove coverage.info '/usr/*' --output-file coverage.info +lcov --remove coverage.info '/opt/*' --output-file coverage.info lcov --remove coverage.info '/Applications/*' --output-file coverage.info lcov --remove coverage.info '*/build/*' --output-file coverage.info lcov --remove coverage.info '*.pb.cc' --output-file coverage.info @@ -54,17 +55,4 @@ if [[ "$1" == "html" ]]; then coverage.info fi -lcov --summary coverage.info - -echo -current=$( $new" | bc -l) )); then - echo "Code coverage drops to ${new}% (current is ${current}%)" - exit -1 -else - echo "Update code coverage: ${current}% -> ${new}%" - echo -n $new > coverage.stats - # commit to master -fi +tools/check-coverage coverage.stats coverage.info diff --git a/tools/doxygen_convert_comments b/tools/doxygen_convert_comments new file mode 100755 index 00000000000..3a81e3baeae --- /dev/null +++ b/tools/doxygen_convert_comments @@ -0,0 +1,56 @@ +#!/bin/bash + +SWIFT_PARAMETER_PATTERN='s/\\param\s+([^\s]+)/\- Parameter $1:/g' +SWIFT_RETURN_PATTERN='s/\\return/\- Returns:/g' +SWIFT_NOTE_PATTERN='s/\\note/\- Note:/g' +SWIFT_SEE_PATTERN='s/\\see/\- SeeAlso:/g' +SWIFT_FOLDER_PATH="swift/Sources/Generated/*.swift" +SWIFT_FOLDER_PATH_BAK="swift/Sources/Generated/*.bak" + +KOTLIN_PARAMETER_PATTERN='s/\\param/\@param/g' +KOTLIN_RETURN_PATTERN='s/\\return/\@return/g' +KOTLIN_NOTE_PATTERN='s/\\note/\@note/g' +KOTLIN_SEE_PATTERN='s/\\see/\@see/g' +KOTLIN_FOLDER_PATH="jni/java/wallet/core/jni/*.java" +KOTLIN_FOLDER_PATH_BAK="jni/java/wallet/core/jni/*.bak" + +function process_swift_comments() { + perl -pi.bak -e "$SWIFT_PARAMETER_PATTERN" "$1" + perl -pi.bak -e "$SWIFT_RETURN_PATTERN" "$1" + perl -pi.bak -e "$SWIFT_NOTE_PATTERN" "$1" + perl -pi.bak -e "$SWIFT_SEE_PATTERN" "$1" +} + +function process_kotlin_comments() { + # Process multiline /// comments into javadoc /** ... */ format + perl -0777 -pi.bak -e 's/\/\/\/([^\n]*\n)((?:\ *\/\/\/[^\n]*\n)*)/\/**\n *$1$2*\/\n/g' $1 + perl -pi.bak -e 's/\/\/\//\ \*/g' $1 + + perl -pi.bak -e "$KOTLIN_PARAMETER_PATTERN" "$1" + perl -pi.bak -e "$KOTLIN_RETURN_PATTERN" "$1" + perl -pi.bak -e "$KOTLIN_NOTE_PATTERN" "$1" + perl -pi.bak -e "$KOTLIN_SEE_PATTERN" "$1" +} + +function swift_convert() { + echo "Processing swift conversion" + + for d in $SWIFT_FOLDER_PATH ; do + process_swift_comments $d + done + + rm -rf $SWIFT_FOLDER_PATH_BAK +} + +function kotlin_convert() { + echo "Processing kotlin conversion" + + for d in $KOTLIN_FOLDER_PATH ; do + process_kotlin_comments $d + done + + rm -rf $KOTLIN_FOLDER_PATH_BAK +} + +swift_convert +kotlin_convert diff --git a/tools/generate-files b/tools/generate-files index 08f968dd5af..bd0fb1638c0 100755 --- a/tools/generate-files +++ b/tools/generate-files @@ -39,39 +39,53 @@ $PROTOC --version # Clean rm -rf swift/Sources/Generated rm -rf jni/java/wallet/core/jni -rm -rf jni/cpp/generated +rm -rf jni/android/generated mkdir -p swift/Sources/Generated/Protobuf swift/Sources/Generated/Enums # Generate coins info from registry.json codegen/bin/coins -# Generate interface code +# Generate interface code, Swift bindings excluded. codegen/bin/codegen +# Convert doxygen comments to appropriate format +tools/doxygen_convert_comments + +# Generate rust bindgen +tools/rust-bindgen $1 + # Generate Java, C++ and Swift Protobuf files -if [ -x "$(command -v protoc-gen-swift)" ]; then - "$PROTOC" -I=$PREFIX/include -I=src/proto --cpp_out=src/proto --java_out=jni/java --swift_out=swift/Sources/Generated/Protobuf --swift_opt=Visibility=Public src/proto/*.proto +IOS="false" +if [[ "$1" == "ios" ]] || [[ "$1" == "" ]]; then + IOS="true" +fi + +if [ -x "$(command -v protoc-gen-swift)" ] && [[ "$IOS" == "true" ]]; then + "$PROTOC" -I=$PREFIX/include -I=src/proto --cpp_out=src/proto --java_out=lite:jni/proto --swift_out=swift/Sources/Generated/Protobuf --swift_opt=Visibility=Public src/proto/*.proto else - "$PROTOC" -I=$PREFIX/include -I=src/proto --cpp_out=src/proto --java_out=jni/java src/proto/*.proto + "$PROTOC" -I=$PREFIX/include -I=src/proto --cpp_out=src/proto --java_out=lite:jni/proto src/proto/*.proto fi # Generate internal message protocol Protobuf files "$PROTOC" -I=$PREFIX/include -I=src/Tron/Protobuf --cpp_out=src/Tron/Protobuf src/Tron/Protobuf/*.proto "$PROTOC" -I=$PREFIX/include -I=src/Zilliqa/Protobuf --cpp_out=src/Zilliqa/Protobuf src/Zilliqa/Protobuf/*.proto -"$PROTOC" -I=$PREFIX/include -I=src/Cosmos/Protobuf --cpp_out=src/Cosmos/Protobuf src/Cosmos/Protobuf/*.proto -"$PROTOC" -I=$PREFIX/include -I=tests/Cosmos/Protobuf --cpp_out=tests/Cosmos/Protobuf tests/Cosmos/Protobuf/*.proto +"$PROTOC" -I=$PREFIX/include -I=src/Hedera/Protobuf --cpp_out=src/Hedera/Protobuf src/Hedera/Protobuf/*.proto +"$PROTOC" -I=$PREFIX/include -I=tests/chains/Cosmos/Protobuf --cpp_out=tests/chains/Cosmos/Protobuf tests/chains/Cosmos/Protobuf/*.proto # Generate Proto interface file "$PROTOC" -I=$PREFIX/include -I=src/proto --plugin=$PREFIX/bin/protoc-gen-c-typedef --c-typedef_out include/TrustWalletCore src/proto/*.proto "$PROTOC" -I=$PREFIX/include -I=src/proto --plugin=$PREFIX/bin/protoc-gen-swift-typealias --swift-typealias_out swift/Sources/Generated/Protobuf src/proto/*.proto # Generate Xcode project -if [ -x "$(command -v xcodegen)" ]; then +if [ -x "$(command -v xcodegen)" ] && [[ "$IOS" == "true" ]]; then pushd swift xcodegen pod install popd +elif [ "$1" == "android" ]; then + echo -e "\nWARNING: Android detected, skipping xcodegen generation" else echo -e "\nWARNING: Skipped generating Xcode project because the xcodegen tool is not installed." fi + diff --git a/tools/install-android-dependencies b/tools/install-android-dependencies index 7f3d5c64b88..667e8c4342d 100755 --- a/tools/install-android-dependencies +++ b/tools/install-android-dependencies @@ -2,7 +2,40 @@ set -e -$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --verbose "cmake;3.18.1" "ndk;23.1.7779620" -$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "system-images;android-26;google_apis;x86" +source "$(dirname $0)/android-sdk" -echo -e "y\ny\ny\ny\ny\n" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses +ANDROID_CMDTOOLS=$(find_android_cmdline_tools) + +# Dokka stuff +ROOT="$PWD" +DOKKA_CLI_JAR=https://repo1.maven.org/maven2/org/jetbrains/dokka/dokka-cli/1.7.20/dokka-cli-1.7.20.jar + +declare -a DOKKA_DEPS=( +https://repo1.maven.org/maven2/org/jetbrains/dokka/dokka-base/1.7.20/dokka-base-1.7.20.jar +https://repo1.maven.org/maven2/org/jetbrains/dokka/dokka-analysis/1.7.20/dokka-analysis-1.7.20.jar +https://repo1.maven.org/maven2/org/jetbrains/dokka/kotlin-analysis-intellij/1.7.20/kotlin-analysis-intellij-1.7.20.jar +https://repo1.maven.org/maven2/org/jetbrains/dokka/kotlin-analysis-compiler/1.7.20/kotlin-analysis-compiler-1.7.20.jar +https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-coroutines-core/1.6.4/kotlinx-coroutines-core-1.6.4.jar +https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-html-jvm/0.8.0/kotlinx-html-jvm-0.8.0.jar +https://repo1.maven.org/maven2/org/freemarker/freemarker/2.3.31/freemarker-2.3.31.jar +) + +$ANDROID_CMDTOOLS/sdkmanager --verbose "cmake;3.18.1" "ndk;23.1.7779620" +$ANDROID_CMDTOOLS/sdkmanager "system-images;android-30;google_apis;x86" + +echo -e "y\ny\ny\ny\ny\n" | $ANDROID_CMDTOOLS/sdkmanager --licenses + +echo "Downloading Dokka..." +DOKKA_DIR="$ROOT/build/dokka" +mkdir -p "$DOKKA_DIR" +cd "$DOKKA_DIR" + +if [ ! -f dokka-cli-1.7.20.jar ]; then + curl -fSsOL $DOKKA_CLI_JAR +fi + +for dep in "${DOKKA_DEPS[@]}" ; do + if [ ! -f ${dep##*/} ]; then + curl -fSsOL $dep + fi +done diff --git a/tools/install-dependencies b/tools/install-dependencies index 3a04f8434bd..3ad3d77a54d 100755 --- a/tools/install-dependencies +++ b/tools/install-dependencies @@ -10,7 +10,10 @@ CMAKE=cmake MAKE=make # Load dependencies version -BASE_DIR=$(cd `dirname $0`; pwd) +BASE_DIR=$( + cd $(dirname $0) + pwd +) source ${BASE_DIR}/dependencies-version # Setup up folders @@ -50,28 +53,28 @@ function build_protobuf() { PROTOBUF_DIR="$ROOT/build/local/src/protobuf" cd ${PROTOBUF_DIR}/protobuf-$PROTOBUF_VERSION - ./configure --prefix="$PREFIX" - $MAKE -j4 - $MAKE install + $CMAKE -Scmake -B . -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_MODULE_COMPATIBLE=ON + $CMAKE --build . -j + $CMAKE --install . --prefix $PREFIX # after install, cleanup to save space (docker) - make clean + $CMAKE --build . --target clean "$PREFIX/bin/protoc" --version # Protobuf plugins cd "$ROOT/protobuf-plugin" - $CMAKE -H. -Bbuild -DCMAKE_INSTALL_PREFIX=$PREFIX - $MAKE -Cbuild -j12 - $MAKE -Cbuild install - rm -rf build + $CMAKE . -Bbuild -DProtobuf_DIR=$PREFIX/lib/cmake/protobuf + $CMAKE --build build -j + $CMAKE --install build --prefix $PREFIX + $CMAKE --build build --target clean - if [[ -x "$(command -v swift)" && `uname` == "Darwin" ]]; then + if [[ -x "$(command -v swift)" && $(uname) == "Darwin" ]]; then build_swift_plugin fi } function build_swift_plugin() { - # Download Swift Protobuf sources + # Download Swift Protobuf sources SWIFT_PROTOBUF_DIR="$ROOT/build/local/src/swift-protobuf" mkdir -p "$SWIFT_PROTOBUF_DIR" cd "$SWIFT_PROTOBUF_DIR" diff --git a/tools/install-kotlin-dependencies b/tools/install-kotlin-dependencies new file mode 100755 index 00000000000..42c24dca325 --- /dev/null +++ b/tools/install-kotlin-dependencies @@ -0,0 +1,10 @@ +#!/bin/bash + +set -e + +source "$(dirname $0)/android-sdk" + +ANDROID_CMDTOOLS=$(find_android_cmdline_tools) + +$ANDROID_CMDTOOLS/sdkmanager --verbose "cmake;3.22.1" "ndk;25.2.9519653" +yes | $ANDROID_CMDTOOLS/sdkmanager --licenses diff --git a/tools/install-rust-dependencies b/tools/install-rust-dependencies new file mode 100755 index 00000000000..1b12239c010 --- /dev/null +++ b/tools/install-rust-dependencies @@ -0,0 +1,31 @@ +#!/bin/bash + +set -e + +NIGHTLY="nightly-2024-06-13" + +rustup toolchain install $NIGHTLY +rustup default $NIGHTLY +rustup toolchain install $NIGHTLY-x86_64-apple-darwin --force-non-host +rustup toolchain install $NIGHTLY-aarch64-apple-darwin --force-non-host +rustup component add rust-src --toolchain $NIGHTLY-aarch64-apple-darwin +rustup component add rust-src --toolchain $NIGHTLY-x86_64-apple-darwin +if [[ `uname` == "Linux" ]]; then + rustup component add rust-src --toolchain $NIGHTLY-x86_64-unknown-linux-gnu +fi + +# Android +rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android +# iOS +rustup target add aarch64-apple-darwin x86_64-apple-darwin +# macOS +rustup target add x86_64-apple-ios aarch64-apple-ios-sim aarch64-apple-ios +# Wasm +rustup target add wasm32-unknown-emscripten + +cargo install cbindgen --locked + +if [[ "$1" == "dev" ]]; then + rustup component add llvm-tools-preview clippy rustfmt + cargo install cargo-llvm-cov --locked +fi diff --git a/tools/install-sys-dependencies-linux b/tools/install-sys-dependencies-linux new file mode 100755 index 00000000000..2f1e7db8e44 --- /dev/null +++ b/tools/install-sys-dependencies-linux @@ -0,0 +1,24 @@ +#!/bin/bash + +set -e + +# Downgrade libc to temporarily fix https://github.com/actions/runner-images/issues/8659 +if [[ "$1" == "ci" ]]; then + LIBSTD_PACKAGE_VERSION="12.3.0-1ubuntu1~22.04" + # Bump this version if the CI has been broken due to the packages update. + LIBC_PACKAGE_VERSION="2.35-0ubuntu3.8" + + echo "Remove GCC 13 from runner image - runner-images/8659 workaround" + echo "NOTE: Bump $LIBC_PACKAGE_VERSION version if the CI has been broken due to the packages update" + + sudo rm -f /etc/apt/sources.list.d/ubuntu-toolchain-r-ubuntu-test-jammy.list + sudo apt-get update + sudo apt-get install -y --allow-downgrades \ + libc6=$LIBC_PACKAGE_VERSION \ + libc6-dev=$LIBC_PACKAGE_VERSION \ + libstdc++6=$LIBSTD_PACKAGE_VERSION \ + libgcc-s1=$LIBSTD_PACKAGE_VERSION +fi + +# build-essential clang-14 libc++-dev libc++abi-dev ruby-full cmake +sudo apt-get update && sudo apt-get install ninja-build lcov llvm-14 clang-tidy-14 libboost-all-dev rustc --fix-missing diff --git a/tools/install-sys-dependencies-mac b/tools/install-sys-dependencies-mac new file mode 100755 index 00000000000..c01bd2cde28 --- /dev/null +++ b/tools/install-sys-dependencies-mac @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +# A workaround for "The `brew link` step did not complete successfully" error. +brew install python@3 || brew link --overwrite python@3 +brew install boost ninja xcodegen xcbeautify + +if command -v rustup &> /dev/null +then + echo "Rustup is already installed." +else + echo "Rustup is not installed. Installing it now." + brew install rustup + rustup-init -y --default-toolchain none --no-update-default-toolchain +fi diff --git a/tools/install-wasm-dependencies b/tools/install-wasm-dependencies index d70cb692d70..31fc852b850 100755 --- a/tools/install-wasm-dependencies +++ b/tools/install-wasm-dependencies @@ -2,11 +2,14 @@ set -e +emsdk_version=3.1.30 + git clone https://github.com/emscripten-core/emsdk.git cd emsdk -./emsdk install latest && ./emsdk activate latest + +./emsdk install $emsdk_version && ./emsdk activate $emsdk_version curl -fSsOL https://github.com/emscripten-ports/boost/releases/download/boost-1.75.0/boost-headers-1.75.0.zip unzip boost-headers-1.75.0.zip -d upstream/emscripten/system/include diff --git a/tools/ios-build b/tools/ios-build index bf5fa47251a..6a7b73eb96e 100755 --- a/tools/ios-build +++ b/tools/ios-build @@ -23,7 +23,7 @@ init() { # build destination archivePath build() { echo "Building scheme: $1, destination: $2, path: $3" - xcodebuild archive -project swift/${FRAMEWORK}.xcodeproj -scheme "$1" -destination "$2" -archivePath "$BUILD_FOLDER/$3".xcarchive SKIP_INSTALL=NO | xcpretty + xcodebuild archive -project swift/${FRAMEWORK}.xcodeproj -scheme "$1" -destination "$2" -archivePath "$BUILD_FOLDER/$3".xcarchive SKIP_INSTALL=NO | xcbeautify } build_ios_arm64() { @@ -43,17 +43,19 @@ build_mac_x64_arm64() { } create_xc_framework() { - echo "Createing xcframework..." + echo "Creating xcframework..." xcodebuild -create-xcframework -output $BUILD_FOLDER/$FRAMEWORK.xcframework \ -framework $BUILD_FOLDER/ios-arm64.xcarchive/Products/Library/Frameworks/$FRAMEWORK.framework \ -framework $BUILD_FOLDER/ios-arm64_x86_64-simulator.xcarchive/Products/Library/Frameworks/$FRAMEWORK.framework \ + -framework $BUILD_FOLDER/ios-x86_64_arm64-maccatalyst.xcarchive/Products/Library/Frameworks/$FRAMEWORK.framework \ -framework $BUILD_FOLDER/macos-arm64_x86_64.xcarchive/Products/Library/Frameworks/$FRAMEWORK.framework } main() { init - build_ios_arm64 && build_ios_simulator build_mac_x64_arm64 + build_ios_mac_catalyst + build_ios_arm64 && build_ios_simulator create_xc_framework } diff --git a/tools/ios-doc b/tools/ios-doc new file mode 100755 index 00000000000..60c5a41b1cd --- /dev/null +++ b/tools/ios-doc @@ -0,0 +1,22 @@ +#!/bin/bash + +# https://developer.apple.com/documentation/xcode/distributing-documentation-to-external-developers + +set -e +set -o pipefail + +pushd swift +mkdir -p build && rm -rf build/*.doccarchive + +export DOCC_JSON_PRETTYPRINT="YES" +xcodebuild -workspace TrustWalletCore.xcworkspace -derivedDataPath build/docsData -scheme WalletCore -destination 'platform=iOS Simulator,name=iPhone 14' -parallelizeTargets docbuild | xcbeautify + +pushd build + +mv `find docsData/Build/Products -type d -name "*.doccarchive"` . + +echo "Zipping docc archive..." +zip -rq WalletCore.doccarchive.zip WalletCore.doccarchive + +popd # build +popd # swift diff --git a/tools/ios-release b/tools/ios-release index 1499240cbff..85a70879c09 100755 --- a/tools/ios-release +++ b/tools/ios-release @@ -37,8 +37,9 @@ popd # upload to release download_url=$(wc_upload_asset ${release_url} ${filename}) +echo "download_url is $download_url" download_url=$(tr -d '"' <<< $download_url) -echo "download_url is ${download_url}" +echo "trimmed download_url is ${download_url}" # create podspec podspec_name=TrustWalletCore diff --git a/tools/ios-test b/tools/ios-test index 62d09d61417..639c605a8d6 100755 --- a/tools/ios-test +++ b/tools/ios-test @@ -11,7 +11,7 @@ xcodegen && pod install xcodebuild -workspace TrustWalletCore.xcworkspace \ -scheme WalletCore \ -sdk iphonesimulator \ - -destination "platform=iOS Simulator,name=iPhone 13" \ + -destination "platform=iOS Simulator,name=iPhone 15,OS=17.2" \ test | xcbeautify popd diff --git a/tools/ios-xcframework b/tools/ios-xcframework index 6b5f21808aa..d5d667f4b14 100755 --- a/tools/ios-xcframework +++ b/tools/ios-xcframework @@ -5,6 +5,10 @@ set -e +echo "Building Docc..." +tools/ios-doc + +echo "Building xcframework..." pushd swift fastlane ios xcframework popd diff --git a/tools/ios-xcframework-release b/tools/ios-xcframework-release index 168f0c4c74c..0ce859a8cc1 100755 --- a/tools/ios-xcframework-release +++ b/tools/ios-xcframework-release @@ -1,5 +1,6 @@ #!/bin/bash +set -o pipefail set -e # load release library code @@ -81,4 +82,7 @@ echo "protobuf dsyms url is: ${protobuf_dsyms_url}" core_dsyms_url=$(wc_upload_asset ${release_url} ${core_dsyms_filename}) echo "core dsyms url is: ${core_dsyms_url}" +docc_url=$(wc_upload_asset ${release_url} WalletCore.doccarchive.zip) +echo "docc url is: ${docc_url}" + popd diff --git a/tools/kotlin-build b/tools/kotlin-build new file mode 100755 index 00000000000..4681910d30a --- /dev/null +++ b/tools/kotlin-build @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +if [[ `uname` == "Darwin" ]]; then + export BOOST_ROOT=$(brew --prefix boost) +fi + +pushd kotlin +./gradlew :wallet-core-kotlin:generateProtos +./gradlew :wallet-core-kotlin:assemble +popd diff --git a/tools/kotlin-doc b/tools/kotlin-doc new file mode 100755 index 00000000000..d1ea9e5de3b --- /dev/null +++ b/tools/kotlin-doc @@ -0,0 +1,28 @@ +#!/bin/bash + +pushd jni + +DOKKA_CLI_JAR=../build/dokka/dokka-cli-1.7.20.jar + +declare -a DOKKA_DEPS=( +dokka-base-1.7.20.jar +dokka-analysis-1.7.20.jar +kotlin-analysis-intellij-1.7.20.jar +kotlin-analysis-compiler-1.7.20.jar +kotlinx-coroutines-core-1.6.4.jar +kotlinx-html-jvm-0.8.0.jar +freemarker-2.3.31.jar +) + +pluginClassPath="" +for dep in "${DOKKA_DEPS[@]}" ; do + pluginClassPath="$pluginClassPath;../build/dokka/$dep" +done + +mkdir -p dokka-out + +java -jar ${DOKKA_CLI_JAR} -moduleName WalletCore -outputDir dokka-out -pluginsClasspath $pluginClassPath -sourceSet "-sourceSetName TrustWallet -src java/wallet/core/jni" + +zip -rq kdoc.zip dokka-out && mv kdoc.zip ../build/dokka + +popd #jni diff --git a/tools/kotlin-release b/tools/kotlin-release new file mode 100755 index 00000000000..c6e5f40afa8 --- /dev/null +++ b/tools/kotlin-release @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +source $(dirname $0)/library +version=$(wc_read_version $1) + +echo "Building $version" + +export ANDROID_HOME="$HOME/Library/Android/sdk" +export BOOST_ROOT=$(brew --prefix boost) + +pushd kotlin +./gradlew :wallet-core-kotlin:generateProtos +./gradlew :wallet-core-kotlin:assemble :wallet-core-kotlin:publish -Pversion="$version" +popd + +echo "Kotlin build uploaded" diff --git a/tools/kotlin-test b/tools/kotlin-test new file mode 100755 index 00000000000..2c97cef3d00 --- /dev/null +++ b/tools/kotlin-test @@ -0,0 +1,11 @@ +#!/bin/bash + +set -e + +if [[ `uname` == "Darwin" ]]; then + export BOOST_ROOT=$(brew --prefix boost) +fi + +pushd kotlin +./gradlew :wallet-core-kotlin:jvmTest +popd diff --git a/tools/library b/tools/library index fe02dc8a57f..46891c22dc9 100755 --- a/tools/library +++ b/tools/library @@ -11,10 +11,10 @@ wc_read_version() { wc_release_url() { tag="$1" - id=$(curl "https://api.github.com/repos/trustwallet/wallet-core/releases/tags/${tag}" | jq ".id") + id=$(curl -u ${GITHUB_USER}:${GITHUB_TOKEN} "https://api.github.com/repos/trustwallet/wallet-core/releases/tags/${tag}" | jq ".id") if [[ $id == "null" ]]; then echo "Failed to get release id for tag ${tag}" - exit -1 + exit 22 fi release_url="https://uploads.github.com/repos/trustwallet/wallet-core/releases/${id}" echo ${release_url} @@ -25,6 +25,6 @@ wc_upload_asset() { filename="$2" upload_url="${release_url}/assets?name=${filename}" - download_url=$(curl -u ${GITHUB_USER}:${GITHUB_TOKEN} -X POST -H "Content-Type: application/octet-stream" --data-binary @${filename} ${upload_url} | jq ".browser_download_url") + download_url=$(curl --progress-bar --retry 3 -u ${GITHUB_USER}:${GITHUB_TOKEN} -X POST -H "Content-Type: application/octet-stream" --data-binary @${filename} ${upload_url} | jq ".browser_download_url") echo ${download_url} } diff --git a/tools/lint-all b/tools/lint-all index e3e1eb781c8..c2afc4bc026 100755 --- a/tools/lint-all +++ b/tools/lint-all @@ -4,4 +4,5 @@ set -e -find src \( -name "*.cpp" -o -name "*.h" \) -not -path "./src/proto/*" -not -path "./src/Tron/Protobuf/*" -exec clang-tidy-11 -quiet -p=build '{}' \; +clang-tidy --version +find src \( -name "*.cpp" -o -name "*.h" \) -not -path "./src/proto/*" -not -path "./src/Tron/Protobuf/*" -exec clang-tidy -quiet -p=build '{}' \; diff --git a/tools/new-blockchain b/tools/new-blockchain new file mode 100755 index 00000000000..8ccdd0e740b --- /dev/null +++ b/tools/new-blockchain @@ -0,0 +1,11 @@ +#!/bin/bash +# +# This script generates new Blockchain skeleton files. + +pushd codegen-v2 +cargo run -- new-blockchain $1 +popd # codegen-v2 + +pushd codegen +codegen/bin/newcoin-mobile-tests $1 +popd # codegen diff --git a/tools/new-evmchain b/tools/new-evmchain new file mode 100755 index 00000000000..3eb2c89410d --- /dev/null +++ b/tools/new-evmchain @@ -0,0 +1,7 @@ +#!/bin/bash +# +# This script generates new EVM chain skeleton files. + +pushd codegen-v2 +cargo run -- new-evmchain $1 +popd # codegen-v2 diff --git a/tools/pvs-studio-analyze b/tools/pvs-studio-analyze new file mode 100755 index 00000000000..c219fbefc52 --- /dev/null +++ b/tools/pvs-studio-analyze @@ -0,0 +1,25 @@ +#!/bin/bash +# +# This script generate a build folder with pvs studio enabled and run an analyze +# You need to have ninja, pvs-studio-analyzer and llvm installed +# If you are on macOS set CC/CXX to clang/clang++ from brew + +base_dir=$( + cd $(dirname $0) + pwd +) +src_dir=${base_dir}/.. + +mkdir -p "${src_dir}"/build_pvs_studio +cd "${src_dir}"/build_pvs_studio + +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + cmake -GNinja -DTW_ENABLE_PVS_STUDIO=ON -DTW_IDE_VSCODE=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ../ +elif [[ "$OSTYPE" == "darwin"* ]]; then + llvm_bin="$(brew --prefix)/opt/llvm/bin" + cmake -GNinja -DTW_ENABLE_PVS_STUDIO=ON -DTW_IDE_VSCODE=ON -DCMAKE_C_COMPILER=$llvm_bin/clang -DCMAKE_CXX_COMPILER=$llvm_bin/clang++ ../ +fi + +ninja TrustWalletCore.analyze + +cd - diff --git a/tools/pvs-studio/config.cfg b/tools/pvs-studio/config.cfg new file mode 100644 index 00000000000..4823855e789 --- /dev/null +++ b/tools/pvs-studio/config.cfg @@ -0,0 +1,11 @@ +exclude-path=*/boost/* +exclude-path=*/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform* +exclude-path=*/src/proto* +exclude-path=*/src/Cosmos/Protobuf* +exclude-path=*/build/local/include* +exclude-path=*/build/local/* +exclude-path=*/build/local/src/google/protobuf/* +exclude-path=*/opt/homebrew/opt/llvm* +exclude-path=*/src/Zilliqa/Protobuf* +exclude-path=*/src/Tron/Protobuf* +exclude-path=*/opt/homebrew/Cellar/llvm* diff --git a/tools/registry b/tools/registry new file mode 100755 index 00000000000..2f322890910 --- /dev/null +++ b/tools/registry @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +import argparse +import json +import requests +import subprocess +import sys + +PATH_TO_REGISTRY_JSON = "registry.json" + + +def ping_url(url): + response = requests.get(url) + if response.status_code != 200: + raise Exception(str(response.status_code)) + + +def ping_explorers(_args): + """Pings blockchain explorers inside 'registry.json'""" + registry_file = open(PATH_TO_REGISTRY_JSON, 'r') + registry_json = json.load(registry_file) + + for chain_json in registry_json: + explorer_json = chain_json["explorer"] + sample_url = explorer_json["url"] + + # Not all chains have `sampleTx` parameter. + if "sampleTx" in explorer_json: + sample_url += explorer_json["txPath"] + explorer_json["sampleTx"] + + try: + ping_url(sample_url) + except Exception as exception: + print(chain_json["name"], ":", sample_url, " NOT WORKING") + print("\tError: ", exception) + + +def registry_stats(_args): + """Print registry stats""" + subprocess.run([ + "cargo", + "run", + "--manifest-path", + "rust/Cargo.toml", + "-p", + "wallet_core_bin", + "--", + "registry-stats" + ], + shell=False) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="Operations over registry.json") + subparsers = parser.add_subparsers() + + ping_explorers_parser = subparsers.add_parser('ping-explorers', + help="Ping blockchain explorers inside 'registry.json'") + ping_explorers_parser.set_defaults(func=ping_explorers) + + registry_stats_parser = subparsers.add_parser('registry-stats', + help="Print registry statistic (e.g Rust transition progress)") + registry_stats_parser.set_defaults(func=registry_stats) + + args = parser.parse_args() + args.func(args) diff --git a/tools/release-size b/tools/release-size new file mode 100755 index 00000000000..c80c71ee873 --- /dev/null +++ b/tools/release-size @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 + +import argparse +import json +import os + +RUST_TARGETS = [ + "aarch64-apple-ios", + "aarch64-apple-ios-sim", + "aarch64-linux-android", + "armv7-linux-androideabi", + "wasm32-unknown-emscripten", +] +LIB_NAME = "libwallet_core_rs.a" + + +def display_size(size_kb: int) -> str: + if size_kb >= 10000: + size_mb = float(size_kb) / 1024 + return f'{size_mb:.2f} MB' + else: + return f'{size_kb} KB' + + +def display_diff(diff_kb: int) -> str: + if abs(diff_kb) >= 10000: + diff_mb = float(diff_kb) / 1024 + return f'{diff_mb:+.2f} MB' + else: + return f'{diff_kb:+} KB' + + +def measure_rust(_args): + result = {} + + for target in RUST_TARGETS: + path = f'rust/target/{target}/release/{LIB_NAME}' + file_stats = os.stat(path) + file_size_kb = file_stats.st_size / 1024 + result[target] = int(file_size_kb) + + print(json.dumps(result)) + + +def compare_sizes(args): + def display_target(target: str, before_kb: int, current_kb: int): + diff_kb = current_kb - before_kb + if before_kb == current_kb: + print(f'➡️ **{target}**: {display_size(before_kb)}') + else: + print(f'➡️ **{target}**:') + print("```diff") + print(f'- {display_size(before_kb)}') + print(f'+ {display_size(current_kb)} \t {display_diff(diff_kb)}') + print("```") + + current_json = json.load(open(args.current, 'r')) + before_json = {} + if os.path.isfile(args.before): + before_json = json.load(open(args.before, 'r')) + + print("## Binary size comparison") + print() + for target, current_kb in current_json.items(): + before_kb = before_json.get(target, current_kb) + display_target(target, before_kb, current_kb) + print() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="GitHub CI helper functions") + subparsers = parser.add_subparsers() + + measure_parser = subparsers.add_parser('measure-rust', help="Measures Rust release binaries'") + measure_parser.set_defaults(func=measure_rust) + + compare_parser = subparsers.add_parser('compare', + help="Compares binary sizes. Takes 'before' and 'current' file names") + compare_parser.add_argument('--before', type=str) + compare_parser.add_argument('--current', type=str) + compare_parser.set_defaults(func=compare_sizes) + + args = parser.parse_args() + args.func(args) diff --git a/tools/rust-bindgen b/tools/rust-bindgen new file mode 100755 index 00000000000..0ca640b7766 --- /dev/null +++ b/tools/rust-bindgen @@ -0,0 +1,165 @@ +#!/bin/bash + +set -e + +source "$(dirname $0)/android-sdk" + +TARGET_NAME="libwallet_core_rs.a" +TARGET_XCFRAMEWORK_NAME=../swift/WalletCoreRs.xcframework +BUILD_FOLDER=../rust/target +CRATE="wallet-core-rs" +HEADER_NAME="WalletCoreRSBindgen.h" + +create_xc_framework() { + rm -rf $TARGET_XCFRAMEWORK_NAME + xcodebuild -create-xcframework -library $BUILD_FOLDER/$TARGET_NAME -library $BUILD_FOLDER/darwin_universal/$TARGET_NAME -library $BUILD_FOLDER/aarch64-apple-ios/release/$TARGET_NAME -output $TARGET_XCFRAMEWORK_NAME + mkdir -p $TARGET_XCFRAMEWORK_NAME/ios-arm64_x86_64-maccatalyst + cp $BUILD_FOLDER/catalyst/$TARGET_NAME $TARGET_XCFRAMEWORK_NAME/ios-arm64_x86_64-maccatalyst +} + +cd rust + +NATIVE="false" +ANDROID="false" +IOS="false" +WASM="false" + +# Whether to generate bindings for native platform. +if [[ "$1" == "native" ]] || [[ "$1" == "" ]]; then + NATIVE="true" +fi + +# Whether to generate bindings for Android. + if [[ "$1" == "android" ]] || [[ "$1" == "" ]]; then + ANDROID="true" + fi + +# Generate bindings for ios platforms on MacOS only. +if [[ `uname` == "Darwin" ]]; then +# Whether to generate bindings for iOS. + if [[ "$1" == "ios" ]] || [[ "$1" == "" ]]; then + IOS="true" + fi +fi + +# Whether to generate bindings for WASM. +if [[ "$1" == "wasm" ]] || [[ "$1" == "" ]]; then + WASM="true" +fi + +if [[ "$NATIVE" == "true" ]]; then + echo "Generating Native target" + cargo build --release --lib +fi + +export RUSTFLAGS="-Zlocation-detail=none" + +if [[ "$WASM" == "true" ]]; then + echo "Generating WASM target" + cargo build -Z build-std=std,panic_abort --target wasm32-unknown-emscripten --release --lib +fi + +if [[ "$ANDROID" == "true" ]]; then + NDK_BIN_PATH=$(find_android_ndk) + + export AR="$NDK_BIN_PATH/llvm-ar" + export CC_aarch64_linux_android="$NDK_BIN_PATH/aarch64-linux-android$NDK_API_LEVEL-clang" + export CC_x86_64_linux_android="$NDK_BIN_PATH/x86_64-linux-android$NDK_API_LEVEL-clang" + export CC_i686_linux_android="$NDK_BIN_PATH/i686-linux-android$NDK_API_LEVEL-clang" + export CC_armv7_linux_androideabi="$NDK_BIN_PATH/armv7a-linux-androideabi$NDK_API_LEVEL-clang" + + echo "Generating Android targets" + cargo build -Z build-std=std,panic_abort --target aarch64-linux-android --target armv7-linux-androideabi --target x86_64-linux-android --target i686-linux-android --release --lib +fi + +if [[ "$IOS" == "true" ]]; then + echo "Generating iOS targets" + cargo build -Z build-std=std,panic_abort --target aarch64-apple-ios --target aarch64-apple-ios-sim --target x86_64-apple-ios --target aarch64-apple-darwin --target x86_64-apple-darwin --target aarch64-apple-ios-macabi --target x86_64-apple-ios-macabi --release --lib & + wait + lipo $BUILD_FOLDER/x86_64-apple-ios/release/$TARGET_NAME $BUILD_FOLDER/aarch64-apple-ios-sim/release/$TARGET_NAME -create -output $BUILD_FOLDER/$TARGET_NAME + mkdir -p $BUILD_FOLDER/darwin_universal + lipo $BUILD_FOLDER/x86_64-apple-darwin/release/$TARGET_NAME $BUILD_FOLDER/aarch64-apple-darwin/release/$TARGET_NAME -create -output $BUILD_FOLDER/darwin_universal/$TARGET_NAME + mkdir -p $BUILD_FOLDER/catalyst + lipo $BUILD_FOLDER/aarch64-apple-ios-macabi/release/$TARGET_NAME $BUILD_FOLDER/x86_64-apple-ios-macabi/release/$TARGET_NAME -create -output $BUILD_FOLDER/catalyst/$TARGET_NAME + + create_xc_framework +fi + +cbindgen --crate $CRATE --output ../src/rust/bindgen/$HEADER_NAME +cd - +[[ -e rust/target/release/${TARGET_NAME} ]] && cp rust/target/release/${TARGET_NAME} build/local/lib/ + +if [[ "$IOS" == "true" ]]; then +cd rust +cat > $TARGET_XCFRAMEWORK_NAME/Info.plist << EOF + + + + + AvailableLibraries + + + LibraryIdentifier + macos-arm64_x86_64 + LibraryPath + libwallet_core_rs.a + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + macos + + + LibraryIdentifier + ios-arm64_x86_64-maccatalyst + LibraryPath + libwallet_core_rs.a + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + maccatalyst + + + LibraryIdentifier + ios-arm64 + LibraryPath + libwallet_core_rs.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + LibraryIdentifier + ios-arm64_x86_64-simulator + LibraryPath + libwallet_core_rs.a + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + +EOF +cd - +fi diff --git a/tools/rust-coverage b/tools/rust-coverage new file mode 100755 index 00000000000..835b13997b3 --- /dev/null +++ b/tools/rust-coverage @@ -0,0 +1,29 @@ +#!/bin/bash + +# +# This script processes captured test code coverage information of the Rust part, +# generates HTML report (if html argument is given), +# and compares current value to previously saved value (stored in coverage.stats). +# +# To generate coverage info: +# - install cargo-llvm-cov binary +# cargo install cargo-llvm-cov +# - run unit tests ang generate coverage info (slow). Optionally generate report in HTML format: +# ./tools/rust-coverage html + +# Fail if any commands fails +set -e + +pushd rust + +# Generate HTML report if requested +if [[ "$1" == "html" ]]; then + cargo llvm-cov test --workspace --exclude wallet_core_bin --html + cargo llvm-cov report --lcov --output-path coverage.info +else + cargo llvm-cov test --workspace --exclude wallet_core_bin --lcov --output-path coverage.info +fi + +popd + +tools/check-coverage rust/coverage.stats rust/coverage.info diff --git a/tools/rust-fuzz b/tools/rust-fuzz new file mode 100755 index 00000000000..5aabfcf96cd --- /dev/null +++ b/tools/rust-fuzz @@ -0,0 +1,39 @@ +#!/bin/bash +# +# This script ensures that all `cargo-fuzz` targets are compilable if the `dry` argument is given. Otherwise it does nothing. +# Prerequisite: install `cargo-fuzz` by running `cargo install cargo-fuzz`. + +set -e + +run_fuzz_target_dry() { + crate=$1 + target=$2 + + pushd rust/$crate + + echo + echo "Running '$crate::$target' fuzz test (dry)" + echo + cargo fuzz run $target -- -runs=0 + echo + + popd # rust/$crate +} + +run_fuzz_crate_dry() { + crate=$1 + + pushd rust/$crate + targets=$(cargo fuzz list) + popd # rust/$crate + + while read -r target; do + run_fuzz_target_dry $crate $target + done < <(printf '%s\n' "$targets") +} + +if [[ $1 == "dry" ]]; then + run_fuzz_crate_dry "tw_encoding" + run_fuzz_crate_dry "tw_hash" + run_fuzz_crate_dry "tw_keypair" +fi diff --git a/tools/rust-lint b/tools/rust-lint new file mode 100755 index 00000000000..6b47a463c51 --- /dev/null +++ b/tools/rust-lint @@ -0,0 +1,15 @@ +#!/bin/bash +# +# Run Rust lints: fmt, clippy and udeps. + +set -e + +pushd rust + +echo "Check code formatting" +cargo fmt --check + +echo "Check Clippy warnings" +cargo clippy -- -D warnings + +popd diff --git a/tools/rust-test b/tools/rust-test new file mode 100755 index 00000000000..ad72918e65b --- /dev/null +++ b/tools/rust-test @@ -0,0 +1,26 @@ +#!/bin/bash + +# To run Rust tests (home architecture): +# - run unit tests without additional flags: +# ./tools/rust-test +# +# To run Rust tests in WASM: +# - install wasm dependencies +# ./tools/install-wasm-dependencies +# - run unit tests with `wasm` flag: +# ./tools/rust-test wasm + +set -e + +pushd rust + +if [[ "$1" == "wasm" ]]; then + source ../emsdk/emsdk_env.sh + export CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER=node + + cargo test --target wasm32-unknown-emscripten --profile wasm-test --workspace --exclude wallet_core_bin +else + cargo test --all +fi + +popd # rust diff --git a/tools/sonar-scanner.properties b/tools/sonar-scanner.properties new file mode 100644 index 00000000000..08eb8f97091 --- /dev/null +++ b/tools/sonar-scanner.properties @@ -0,0 +1,9 @@ +#Configure here general information about the environment, such as SonarQube server connection details for example +#No information about specific project should appear here + +#----- Default SonarQube server +sonar.host.url=https://sonarcloud.io/ + +#----- Default source code encoding +#sonar.sourceEncoding=UTF-8 + diff --git a/tools/sonarcloud-analysis b/tools/sonarcloud-analysis new file mode 100755 index 00000000000..a8daa2f79ad --- /dev/null +++ b/tools/sonarcloud-analysis @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +TARGET=sonar-scanner-cli-5.0.1.3006-linux.zip +TARGET_DIR=sonar-scanner-5.0.1.3006-linux +curl https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/${TARGET} --output ${TARGET} +unzip ${TARGET} +cp tools/sonar-scanner.properties ${TARGET_DIR}/conf +chmod +x ${TARGET_DIR}/bin/sonar-scanner +./${TARGET_DIR}/bin/sonar-scanner diff --git a/tools/test b/tools/test new file mode 100755 index 00000000000..97f79e9d566 --- /dev/null +++ b/tools/test @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# +# Perform full build and runs the tests. +# Prerequisite: workspace with dependencies installed, see bootstrap.sh + +# Fail if any commands fails +set -e + +make -Cbuild -j12 tests + +echo "#### Running unit tests... ####" +FILTER="*" +if [ -n "$1" ]; then + FILTER="*$1*" +fi +build/tests/tests --gtest_filter="$FILTER" diff --git a/tools/wasm-build b/tools/wasm-build index dd291ffab54..2fa483726a2 100755 --- a/tools/wasm-build +++ b/tools/wasm-build @@ -13,12 +13,10 @@ build_folder=${src_dir}/wasm-build boost_dir=${EMSDK}/upstream/emscripten/system/include pushd ${src_dir} -cp cmake/Wasm.cmake CMakeLists.txt - # cmake -emcmake cmake -Bwasm-build -DBoost_INCLUDE_DIR=${boost_dir} +cmake -Bwasm-build -DBoost_INCLUDE_DIR=${boost_dir} -DTW_COMPILE_WASM=ON -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake # make -emmake make -Cwasm-build +make -j8 -Cwasm-build popd diff --git a/trezor-crypto/CMakeLists.txt b/trezor-crypto/CMakeLists.txt index 25360b7fec9..a3ef46a38f2 100644 --- a/trezor-crypto/CMakeLists.txt +++ b/trezor-crypto/CMakeLists.txt @@ -1,8 +1,6 @@ -# Copyright © 2017-2022 Trust Wallet. +# SPDX-License-Identifier: Apache-2.0 # -# This file is part of Trust. The full Trust copyright notice, including -# terms governing use, modification, and redistribution, is contained in the -# file LICENSE at the root of the source code distribution tree. +# Copyright © 2017 Trust Wallet. set(TW_WARNING_FLAGS -W @@ -25,8 +23,10 @@ set(TW_WARNING_FLAGS -Wno-missing-braces ) +set(CMAKE_C_STANDARD 11) + add_library(TrezorCrypto - crypto/bignum.c crypto/ecdsa.c crypto/curves.c crypto/secp256k1.c crypto/rand.c crypto/hmac.c crypto/bip32.c crypto/bip39.c crypto/pbkdf2.c crypto/base58.c crypto/base32.c + crypto/bignum.c crypto/ecdsa.c crypto/curves.c crypto/secp256k1.c crypto/rand.c crypto/hmac.c crypto/bip32.c crypto/bip39.c crypto/slip39.c crypto/pbkdf2.c crypto/base58.c crypto/base32.c crypto/address.c crypto/script.c crypto/ripemd160.c @@ -56,20 +56,21 @@ add_library(TrezorCrypto crypto/groestl.c crypto/hmac_drbg.c crypto/rfc6979.c - crypto/schnorr.c crypto/shamir.c + crypto/zilliqa.c + crypto/cardano.c ) if (EMSCRIPTEN) message(STATUS "Skip building trezor-crypto/tests") set(TW_WARNING_FLAGS ${TW_WARNING_FLAGS} -Wno-bitwise-instead-of-logical) else () - if(NOT ANDROID AND NOT IOS_PLATFORM) + if(NOT ANDROID AND NOT IOS_PLATFORM AND NOT TW_COMPILE_JAVA) add_subdirectory(crypto/tests) endif() endif() -target_compile_options(TrezorCrypto PRIVATE ${TW_WARNING_FLAGS} -Werror) +target_compile_options(TrezorCrypto PRIVATE ${TW_WARNING_FLAGS} -Werror PUBLIC -Wno-deprecated-volatile) target_include_directories(TrezorCrypto PUBLIC diff --git a/trezor-crypto/crypto/README.md b/trezor-crypto/crypto/README.md index a71139335a2..856dbb44ac5 100644 --- a/trezor-crypto/crypto/README.md +++ b/trezor-crypto/crypto/README.md @@ -27,9 +27,9 @@ These include: - unit tests (using Check - check.sf.net; in test_check.c) - tests against OpenSSL (in test_openssl.c) - integrated Wycheproof tests -- public key convertion between Curve25519 to Ed25519 and vice versa +- public key conversion between Curve25519 to Ed25519 and vice versa -Distibuted under MIT License. +Distributed under MIT License. ## Some parts of the library come from external sources: diff --git a/trezor-crypto/crypto/aes/aescrypt.c b/trezor-crypto/crypto/aes/aescrypt.c index aac3b571b6c..8a168d04efe 100644 --- a/trezor-crypto/crypto/aes/aescrypt.c +++ b/trezor-crypto/crypto/aes/aescrypt.c @@ -215,7 +215,7 @@ AES_RETURN aes_xi(encrypt)(const unsigned char *in, unsigned char *out, const ae #endif /* This code can work with the decryption key schedule in the */ -/* order that is used for encrytpion (where the 1st decryption */ +/* order that is used for encryption (where the 1st decryption */ /* round key is at the high end ot the schedule) or with a key */ /* schedule that has been reversed to put the 1st decryption */ /* round key at the low end of the schedule in memory (when */ diff --git a/trezor-crypto/crypto/bignum.c b/trezor-crypto/crypto/bignum.c index 73bf79bb9c9..5791448d100 100644 --- a/trezor-crypto/crypto/bignum.c +++ b/trezor-crypto/crypto/bignum.c @@ -46,7 +46,7 @@ A limb of a bignum256 is *normalized* iff it's less than 2**29. A bignum256 is *normalized* iff every its limb is normalized. A number is *fully reduced modulo p* iff it is less than p. - A number is *partly reduced modulo p* iff is is less than 2*p. + A number is *partly reduced modulo p* iff it is less than 2*p. The number p is usually a prime number such that 2^256 - 2^224 <= p <= 2^256. All functions except bn_fast_mod expect that all their bignum256 inputs are @@ -271,7 +271,7 @@ int bn_is_equal(const bignum256 *x, const bignum256 *y) { // &truecase == &falsecase or &res == &truecase == &falsecase void bn_cmov(bignum256 *res, volatile uint32_t cond, const bignum256 *truecase, const bignum256 *falsecase) { - assert((cond == 1) | (cond == 0)); + assert((int)(cond == 1) | (cond == 0)); uint32_t tmask = -cond; // tmask = 0xFFFFFFFF if cond else 0x00000000 uint32_t fmask = ~tmask; // fmask = 0x00000000 if cond else 0xFFFFFFFF @@ -290,7 +290,7 @@ void bn_cmov(bignum256 *res, volatile uint32_t cond, const bignum256 *truecase, // Assumes prime is normalized and // 0 < prime < 2**260 == 2**(BITS_PER_LIMB * LIMBS - 1) void bn_cnegate(volatile uint32_t cond, bignum256 *x, const bignum256 *prime) { - assert((cond == 1) | (cond == 0)); + assert((int)(cond == 1) | (cond == 0)); uint32_t tmask = -cond; // tmask = 0xFFFFFFFF if cond else 0x00000000 uint32_t fmask = ~tmask; // fmask = 0x00000000 if cond else 0xFFFFFFFF diff --git a/trezor-crypto/crypto/bip32.c b/trezor-crypto/crypto/bip32.c index 9c0da8217ae..4d821a8cf69 100644 --- a/trezor-crypto/crypto/bip32.c +++ b/trezor-crypto/crypto/bip32.c @@ -48,23 +48,12 @@ #include "nem.h" #endif #if USE_CARDANO -#include +#include #endif #include -#define CARDANO_MAX_NODE_DEPTH 1048576 - const curve_info ed25519_info = { - .bip32_name = "ed25519 seed", - .params = NULL, - .hasher_base58 = HASHER_SHA2D, - .hasher_sign = HASHER_SHA2D, - .hasher_pubkey = HASHER_SHA2_RIPEMD, - .hasher_script = HASHER_SHA2, -}; - -const curve_info ed25519_cardano_info = { - .bip32_name = "ed25519 cardano seed", + .bip32_name = ED25519_SEED_NAME, .params = NULL, .hasher_base58 = HASHER_SHA2D, .hasher_sign = HASHER_SHA2D, @@ -215,11 +204,17 @@ uint32_t hdnode_fingerprint(HDNode *node) { return fingerprint; } -int hdnode_private_ckd(HDNode *inout, uint32_t i) { +int hdnode_private_ckd_bip32(HDNode *inout, uint32_t i) { CONFIDENTIAL uint8_t data[1 + 32 + 4]; CONFIDENTIAL uint8_t I[32 + 32]; CONFIDENTIAL bignum256 a, b; +#if USE_CARDANO + if (inout->curve == &ed25519_cardano_info) { + return 0; + } +#endif + if (i & 0x80000000) { // private derivation data[0] = 0; memcpy(data + 1, inout->private_key, 32); @@ -227,7 +222,9 @@ int hdnode_private_ckd(HDNode *inout, uint32_t i) { if (!inout->curve->params) { return 0; } - hdnode_fill_public_key(inout); + if (hdnode_fill_public_key(inout) != 0) { + return 0; + } memcpy(data, inout->public_key, 33); } write_be(data + 33, i); @@ -281,156 +278,17 @@ int hdnode_private_ckd(HDNode *inout, uint32_t i) { return 1; } +int hdnode_private_ckd(HDNode *inout, uint32_t i) { #if USE_CARDANO -static void scalar_multiply8(const uint8_t *src, int bytes, uint8_t *dst) { - uint8_t prev_acc = 0; - for (int i = 0; i < bytes; i++) { - dst[i] = (src[i] << 3) + (prev_acc & 0x7); - prev_acc = src[i] >> 5; - } - dst[bytes] = src[bytes - 1] >> 5; -} - -static void scalar_add_256bits(const uint8_t *src1, const uint8_t *src2, - uint8_t *dst) { - uint16_t r = 0; - for (int i = 0; i < 32; i++) { - r = r + (uint16_t)src1[i] + (uint16_t)src2[i]; - dst[i] = r & 0xff; - r >>= 8; - } -} - -int hdnode_private_ckd_cardano(HDNode *inout, uint32_t index) { - if (inout->depth >= CARDANO_MAX_NODE_DEPTH) { - return 0; - } - - // checks for hardened/non-hardened derivation, keysize 32 means we are - // dealing with public key and thus non-h, keysize 64 is for private key - int keysize = 32; - if (index & 0x80000000) { - keysize = 64; - } - - CONFIDENTIAL uint8_t data[1 + 64 + 4]; - CONFIDENTIAL uint8_t z[32 + 32]; - CONFIDENTIAL uint8_t priv_key[64]; - CONFIDENTIAL uint8_t res_key[64]; - - write_le(data + keysize + 1, index); - - memcpy(priv_key, inout->private_key, 32); - memcpy(priv_key + 32, inout->private_key_extension, 32); - - if (keysize == 64) { // private derivation - data[0] = 0; - memcpy(data + 1, inout->private_key, 32); - memcpy(data + 1 + 32, inout->private_key_extension, 32); - } else { // public derivation - hdnode_fill_public_key(inout); - data[0] = 2; - memcpy(data + 1, inout->public_key + 1, 32); - } - - CONFIDENTIAL HMAC_SHA512_CTX ctx; - hmac_sha512_Init(&ctx, inout->chain_code, 32); - hmac_sha512_Update(&ctx, data, 1 + keysize + 4); - hmac_sha512_Final(&ctx, z); - - CONFIDENTIAL uint8_t zl8[32]; - memzero(zl8, 32); - - /* get 8 * Zl */ - scalar_multiply8(z, 28, zl8); - /* Kl = 8*Zl + parent(K)l */ - scalar_add_256bits(zl8, priv_key, res_key); - - /* Kr = Zr + parent(K)r */ - scalar_add_256bits(z + 32, priv_key + 32, res_key + 32); - - memcpy(inout->private_key, res_key, 32); - memcpy(inout->private_key_extension, res_key + 32, 32); - - if (keysize == 64) { - data[0] = 1; - } else { - data[0] = 3; + if (inout->curve == &ed25519_cardano_info) { + return hdnode_private_ckd_cardano(inout, i); + } else +#endif + { + return hdnode_private_ckd_bip32(inout, i); } - hmac_sha512_Init(&ctx, inout->chain_code, 32); - hmac_sha512_Update(&ctx, data, 1 + keysize + 4); - hmac_sha512_Final(&ctx, z); - - memcpy(inout->chain_code, z + 32, 32); - inout->depth++; - inout->child_num = index; - memzero(inout->public_key, sizeof(inout->public_key)); - - // making sure to wipe our memory - memzero(z, sizeof(z)); - memzero(data, sizeof(data)); - memzero(priv_key, sizeof(priv_key)); - memzero(res_key, sizeof(res_key)); - return 1; -} - -static int hdnode_from_secret_cardano(const uint8_t *k, - const uint8_t *chain_code, HDNode *out) { - memzero(out, sizeof(HDNode)); - out->depth = 0; - out->child_num = 0; - out->curve = &ed25519_cardano_info; - memcpy(out->private_key, k, 32); - memcpy(out->private_key_extension, k + 32, 32); - memcpy(out->chain_code, chain_code, 32); - - out->private_key[0] &= 0xf8; - out->private_key[31] &= 0x1f; - out->private_key[31] |= 0x40; - - out->public_key[0] = 0; - hdnode_fill_public_key(out); - - return 1; -} - -// Derives the root Cardano HDNode from a master secret, aka seed, as defined in -// SLIP-0023. -int hdnode_from_seed_cardano(const uint8_t *seed, int seed_len, HDNode *out) { - CONFIDENTIAL uint8_t I[SHA512_DIGEST_LENGTH]; - CONFIDENTIAL uint8_t k[SHA512_DIGEST_LENGTH]; - CONFIDENTIAL HMAC_SHA512_CTX ctx; - - hmac_sha512_Init(&ctx, (const uint8_t *)ED25519_CARDANO_NAME, - strlen(ED25519_CARDANO_NAME)); - hmac_sha512_Update(&ctx, seed, seed_len); - hmac_sha512_Final(&ctx, I); - - sha512_Raw(I, 32, k); - - int ret = hdnode_from_secret_cardano(k, I + 32, out); - - memzero(I, sizeof(I)); - memzero(k, sizeof(k)); - memzero(&ctx, sizeof(ctx)); - return ret; } -// Derives the root Cardano HDNode from a passphrase and the entropy encoded in -// a BIP-0039 mnemonic using the Icarus derivation scheme, aka V2 derivation -// scheme. -int hdnode_from_entropy_cardano_icarus(const uint8_t *pass, int pass_len, - const uint8_t *entropy, int entropy_len, - HDNode *out) { - CONFIDENTIAL uint8_t secret[96]; - pbkdf2_hmac_sha512(pass, pass_len, entropy, entropy_len, 4096, secret, 96); - - int ret = hdnode_from_secret_cardano(secret, secret + 64, out); - memzero(secret, sizeof(secret)); - return ret; -} -#endif - int hdnode_public_ckd_cp(const ecdsa_curve *curve, const curve_point *parent, const uint8_t *parent_chain_code, uint32_t i, curve_point *child, uint8_t *child_chain_code) { @@ -530,6 +388,13 @@ CONFIDENTIAL struct { HDNode node; } private_ckd_cache[BIP32_CACHE_SIZE]; +void bip32_cache_clear(void) { + private_ckd_cache_root_set = false; + private_ckd_cache_index = 0; + memzero(&private_ckd_cache_root, sizeof(private_ckd_cache_root)); + memzero(private_ckd_cache, sizeof(private_ckd_cache)); +} + int hdnode_private_ckd_cached(HDNode *inout, const uint32_t *i, size_t i_count, uint32_t *fingerprint) { if (i_count == 0) { @@ -597,26 +462,34 @@ int hdnode_private_ckd_cached(HDNode *inout, const uint32_t *i, size_t i_count, } #endif -void hdnode_get_address_raw(HDNode *node, uint32_t version, uint8_t *addr_raw) { - hdnode_fill_public_key(node); +int hdnode_get_address_raw(HDNode *node, uint32_t version, uint8_t *addr_raw) { + if (hdnode_fill_public_key(node) != 0) { + return 1; + } ecdsa_get_address_raw(node->public_key, version, node->curve->hasher_pubkey, addr_raw); + return 0; } -void hdnode_get_address(HDNode *node, uint32_t version, char *addr, - int addrsize) { - hdnode_fill_public_key(node); +int hdnode_get_address(HDNode *node, uint32_t version, char *addr, + int addrsize) { + if (hdnode_fill_public_key(node) != 0) { + return 1; + } ecdsa_get_address(node->public_key, version, node->curve->hasher_pubkey, node->curve->hasher_base58, addr, addrsize); + return 0; } -void hdnode_fill_public_key(HDNode *node) { - if (node->public_key[0] != 0) return; +int hdnode_fill_public_key(HDNode *node) { + if (node->public_key[0] != 0) return 0; #if USE_BIP32_25519_CURVES if (node->curve->params) { - ecdsa_get_public_key33(node->curve->params, node->private_key, - node->public_key); + if (ecdsa_get_public_key33(node->curve->params, node->private_key, + node->public_key) != 0) { + return 1; + } } else { node->public_key[0] = 1; if (node->curve == &ed25519_info) { @@ -631,16 +504,18 @@ void hdnode_fill_public_key(HDNode *node) { curve25519_scalarmult_basepoint(node->public_key + 1, node->private_key); #if USE_CARDANO } else if (node->curve == &ed25519_cardano_info) { - ed25519_publickey_ext(node->private_key, node->private_key_extension, - node->public_key + 1); + ed25519_publickey_ext(node->private_key, node->public_key + 1); #endif } } #else - ecdsa_get_public_key33(node->curve->params, node->private_key, - node->public_key); + if (ecdsa_get_public_key33(node->curve->params, node->private_key, + node->public_key) != 0) { + return 1; + } #endif + return 0; } #if USE_ETHEREUM @@ -649,7 +524,10 @@ int hdnode_get_ethereum_pubkeyhash(const HDNode *node, uint8_t *pubkeyhash) { SHA3_CTX ctx = {0}; /* get uncompressed public key */ - ecdsa_get_public_key65(node->curve->params, node->private_key, buf); + if (ecdsa_get_public_key65(node->curve->params, node->private_key, buf) != + 0) { + return 0; + } /* compute sha3 of x and y coordinate without 04 prefix */ sha3_256_Init(&ctx); @@ -669,7 +547,10 @@ int hdnode_get_nem_address(HDNode *node, uint8_t version, char *address) { return 0; } - hdnode_fill_public_key(node); + if (hdnode_fill_public_key(node) != 0) { + return 0; + } + return nem_get_address(&node->public_key[1], version, address); } @@ -778,17 +659,12 @@ int hdnode_sign(HDNode *node, const uint8_t *msg, uint32_t msg_len, return 1; // signatures are not supported } else { if (node->curve == &ed25519_info) { - hdnode_fill_public_key(node); - ed25519_sign(msg, msg_len, node->private_key, node->public_key + 1, sig); + ed25519_sign(msg, msg_len, node->private_key, sig); } else if (node->curve == &ed25519_sha3_info) { - hdnode_fill_public_key(node); - ed25519_sign_sha3(msg, msg_len, node->private_key, node->public_key + 1, - sig); + ed25519_sign_sha3(msg, msg_len, node->private_key, sig); #if USE_KECCAK } else if (node->curve == &ed25519_keccak_info) { - hdnode_fill_public_key(node); - ed25519_sign_keccak(msg, msg_len, node->private_key, node->public_key + 1, - sig); + ed25519_sign_keccak(msg, msg_len, node->private_key, sig); #endif } else { return 1; // unknown or unsupported curve diff --git a/trezor-crypto/crypto/bip39.c b/trezor-crypto/crypto/bip39.c index b64a75207c9..fc61539c938 100644 --- a/trezor-crypto/crypto/bip39.c +++ b/trezor-crypto/crypto/bip39.c @@ -44,6 +44,11 @@ CONFIDENTIAL struct { uint8_t seed[512 / 8]; } bip39_cache[BIP39_CACHE_SIZE]; +void bip39_cache_clear(void) { + memzero(bip39_cache, sizeof(bip39_cache)); + bip39_cache_index = 0; +} + #endif // [wallet-core] Added output buffer @@ -249,7 +254,7 @@ void mnemonic_to_seed(const char *mnemonic, const char *passphrase, // binary search for finding the word in the wordlist int mnemonic_find_word(const char *word) { - int lo = 0, hi = BIP39_WORDS - 1; + int lo = 0, hi = BIP39_WORD_COUNT - 1; while (lo <= hi) { int mid = lo + (hi - lo) / 2; int cmp = strcmp(word, wordlist[mid]); @@ -277,7 +282,7 @@ const char *mnemonic_complete_word(const char *prefix, int len) { } const char *mnemonic_get_word(int index) { - if (index >= 0 && index < BIP39_WORDS) { + if (index >= 0 && index < BIP39_WORD_COUNT) { return wordlist[index]; } else { return NULL; diff --git a/trezor-crypto/crypto/blake256.c b/trezor-crypto/crypto/blake256.c index 0da6918102a..a4e9b489c37 100644 --- a/trezor-crypto/crypto/blake256.c +++ b/trezor-crypto/crypto/blake256.c @@ -169,9 +169,8 @@ void blake256_Update( BLAKE256_CTX *S, const uint8_t *in, size_t inlen ) { memcpy( ( void * ) ( S->buf + left ), \ ( void * ) in, ( size_t ) inlen ); - S->buflen = left + ( int )inlen; } - else S->buflen = 0; + S->buflen = left + inlen; } diff --git a/trezor-crypto/crypto/cardano.c b/trezor-crypto/crypto/cardano.c new file mode 100644 index 00000000000..6b07f776c15 --- /dev/null +++ b/trezor-crypto/crypto/cardano.c @@ -0,0 +1,307 @@ +/** + * Copyright (c) 2013-2021 SatoshiLabs + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if USE_CARDANO + +#define CARDANO_MAX_NODE_DEPTH 1048576 + +const curve_info ed25519_cardano_info = { + .bip32_name = ED25519_CARDANO_NAME, + .params = NULL, + .hasher_base58 = HASHER_SHA2D, + .hasher_sign = HASHER_SHA2D, + .hasher_pubkey = HASHER_SHA2_RIPEMD, + .hasher_script = HASHER_SHA2, +}; + +static void scalar_multiply8(const uint8_t *src, int bytes, uint8_t *dst) { + uint8_t prev_acc = 0; + for (int i = 0; i < bytes; i++) { + dst[i] = (src[i] << 3) + (prev_acc & 0x7); + prev_acc = src[i] >> 5; + } + dst[bytes] = src[bytes - 1] >> 5; +} + +static void scalar_add_256bits(const uint8_t *src1, const uint8_t *src2, + uint8_t *dst) { + uint16_t r = 0; + for (int i = 0; i < 32; i++) { + r = r + (uint16_t)src1[i] + (uint16_t)src2[i]; + dst[i] = r & 0xff; + r >>= 8; + } +} + +static void cardano_ed25519_tweak_bits(uint8_t private_key[32]) { + private_key[0] &= 0xf8; + private_key[31] &= 0x1f; + private_key[31] |= 0x40; +} + +int hdnode_private_ckd_cardano(HDNode *inout, uint32_t index) { + if (inout->curve != &ed25519_cardano_info) { + return 0; + } + + if (inout->depth >= CARDANO_MAX_NODE_DEPTH) { + return 0; + } + + // checks for hardened/non-hardened derivation, keysize 32 means we are + // dealing with public key and thus non-h, keysize 64 is for private key + int keysize = 32; + if (index & 0x80000000) { + keysize = 64; + } + + CONFIDENTIAL uint8_t data[1 + 64 + 4]; + CONFIDENTIAL uint8_t z[32 + 32]; + CONFIDENTIAL uint8_t priv_key[64]; + CONFIDENTIAL uint8_t res_key[64]; + + write_le(data + keysize + 1, index); + + memcpy(priv_key, inout->private_key, 32); + memcpy(priv_key + 32, inout->private_key_extension, 32); + + if (keysize == 64) { // private derivation + data[0] = 0; + memcpy(data + 1, inout->private_key, 32); + memcpy(data + 1 + 32, inout->private_key_extension, 32); + } else { // public derivation + if (hdnode_fill_public_key(inout) != 0) { + return 0; + } + data[0] = 2; + memcpy(data + 1, inout->public_key + 1, 32); + } + + CONFIDENTIAL HMAC_SHA512_CTX ctx; + hmac_sha512_Init(&ctx, inout->chain_code, 32); + hmac_sha512_Update(&ctx, data, 1 + keysize + 4); + hmac_sha512_Final(&ctx, z); + + CONFIDENTIAL uint8_t zl8[32]; + memzero(zl8, 32); + + /* get 8 * Zl */ + scalar_multiply8(z, 28, zl8); + /* Kl = 8*Zl + parent(K)l */ + scalar_add_256bits(zl8, priv_key, res_key); + + /* Kr = Zr + parent(K)r */ + scalar_add_256bits(z + 32, priv_key + 32, res_key + 32); + + memcpy(inout->private_key, res_key, 32); + memcpy(inout->private_key_extension, res_key + 32, 32); + + if (keysize == 64) { + data[0] = 1; + } else { + data[0] = 3; + } + hmac_sha512_Init(&ctx, inout->chain_code, 32); + hmac_sha512_Update(&ctx, data, 1 + keysize + 4); + hmac_sha512_Final(&ctx, z); + + memcpy(inout->chain_code, z + 32, 32); + inout->depth++; + inout->child_num = index; + memzero(inout->public_key, sizeof(inout->public_key)); + + // making sure to wipe our memory + memzero(z, sizeof(z)); + memzero(data, sizeof(data)); + memzero(priv_key, sizeof(priv_key)); + memzero(res_key, sizeof(res_key)); + return 1; +} + +int hdnode_from_secret_cardano(const uint8_t secret[CARDANO_SECRET_LENGTH], + HDNode *out) { + memzero(out, sizeof(HDNode)); + out->depth = 0; + out->child_num = 0; + out->curve = &ed25519_cardano_info; + memcpy(out->private_key, secret, 32); + memcpy(out->private_key_extension, secret + 32, 32); + memcpy(out->chain_code, secret + 64, 32); + + cardano_ed25519_tweak_bits(out->private_key); + + out->public_key[0] = 0; + if (hdnode_fill_public_key(out) != 0) { + return 0; + } + + return 1; +} + +// Derives the root Cardano secret from a master secret, aka seed, as defined in +// SLIP-0023. +int secret_from_seed_cardano_slip23(const uint8_t *seed, int seed_len, + uint8_t secret_out[CARDANO_SECRET_LENGTH]) { + CONFIDENTIAL uint8_t I[SHA512_DIGEST_LENGTH]; + CONFIDENTIAL HMAC_SHA512_CTX ctx; + + hmac_sha512_Init(&ctx, (const uint8_t *)ED25519_CARDANO_NAME, + strlen(ED25519_CARDANO_NAME)); + hmac_sha512_Update(&ctx, seed, seed_len); + hmac_sha512_Final(&ctx, I); + + sha512_Raw(I, 32, secret_out); + + memcpy(secret_out + SHA512_DIGEST_LENGTH, I + 32, 32); + cardano_ed25519_tweak_bits(secret_out); + + memzero(I, sizeof(I)); + memzero(&ctx, sizeof(ctx)); + return 1; +} + +// Derives the root Cardano secret from a BIP-32 master secret via the Ledger +// derivation: +// https://github.com/cardano-foundation/CIPs/blob/09d7d8ee1bd64f7e6b20b5a6cae088039dce00cb/CIP-0003/Ledger.md +int secret_from_seed_cardano_ledger(const uint8_t *seed, int seed_len, + uint8_t secret_out[CARDANO_SECRET_LENGTH]) { + CONFIDENTIAL uint8_t chain_code[SHA256_DIGEST_LENGTH]; + CONFIDENTIAL uint8_t root_key[SHA512_DIGEST_LENGTH]; + CONFIDENTIAL HMAC_SHA256_CTX ctx; + CONFIDENTIAL HMAC_SHA512_CTX sctx; + + const uint8_t *intermediate_result = seed; + int intermediate_result_len = seed_len; + do { + // STEP 1: derive a master secret like in BIP-32/SLIP-10 + hmac_sha512_Init(&sctx, (const uint8_t *)ED25519_SEED_NAME, + strlen(ED25519_SEED_NAME)); + hmac_sha512_Update(&sctx, intermediate_result, intermediate_result_len); + hmac_sha512_Final(&sctx, root_key); + + // STEP 2: check that the resulting key does not have a particular bit set, + // otherwise iterate like in SLIP-10 + intermediate_result = root_key; + intermediate_result_len = sizeof(root_key); + } while (root_key[31] & 0x20); + + // STEP 3: calculate the chain code as a HMAC-SHA256 of "\x01" + seed, + // key is "ed25519 seed" + hmac_sha256_Init(&ctx, (const unsigned char *)ED25519_SEED_NAME, + strlen(ED25519_SEED_NAME)); + hmac_sha256_Update(&ctx, (const unsigned char *)"\x01", 1); + hmac_sha256_Update(&ctx, seed, seed_len); + hmac_sha256_Final(&ctx, chain_code); + + // STEP 4: extract information into output + _Static_assert( + SHA512_DIGEST_LENGTH + SHA256_DIGEST_LENGTH == CARDANO_SECRET_LENGTH, + "Invalid configuration of Cardano secret size"); + memcpy(secret_out, root_key, SHA512_DIGEST_LENGTH); + memcpy(secret_out + SHA512_DIGEST_LENGTH, chain_code, SHA256_DIGEST_LENGTH); + + // STEP 5: tweak bits of the private key + cardano_ed25519_tweak_bits(secret_out); + + memzero(&ctx, sizeof(ctx)); + memzero(&sctx, sizeof(sctx)); + memzero(root_key, sizeof(root_key)); + memzero(chain_code, sizeof(chain_code)); + return 1; +} + +#define CARDANO_ICARUS_STEPS 32 +_Static_assert( + CARDANO_ICARUS_PBKDF2_ROUNDS % CARDANO_ICARUS_STEPS == 0, + "CARDANO_ICARUS_STEPS does not divide CARDANO_ICARUS_PBKDF2_ROUNDS"); +#define CARDANO_ICARUS_ROUNDS_PER_STEP \ + (CARDANO_ICARUS_PBKDF2_ROUNDS / CARDANO_ICARUS_STEPS) + +// Derives the root Cardano HDNode from a passphrase and the entropy encoded in +// a BIP-0039 mnemonic using the Icarus derivation scheme, aka V2 derivation +// scheme: +// https://github.com/cardano-foundation/CIPs/blob/09d7d8ee1bd64f7e6b20b5a6cae088039dce00cb/CIP-0003/Icarus.md +int secret_from_entropy_cardano_icarus( + const uint8_t *pass, int pass_len, const uint8_t *entropy, int entropy_len, + uint8_t secret_out[CARDANO_SECRET_LENGTH], + void (*progress_callback)(uint32_t, uint32_t)) { + CONFIDENTIAL PBKDF2_HMAC_SHA512_CTX pctx; + CONFIDENTIAL uint8_t digest[SHA512_DIGEST_LENGTH]; + uint32_t progress = 0; + + // PASS 1: first 64 bytes + pbkdf2_hmac_sha512_Init(&pctx, pass, pass_len, entropy, entropy_len, 1); + if (progress_callback) { + progress_callback(progress, CARDANO_ICARUS_PBKDF2_ROUNDS * 2); + } + for (int i = 0; i < CARDANO_ICARUS_STEPS; i++) { + pbkdf2_hmac_sha512_Update(&pctx, CARDANO_ICARUS_ROUNDS_PER_STEP); + if (progress_callback) { + progress += CARDANO_ICARUS_ROUNDS_PER_STEP; + progress_callback(progress, CARDANO_ICARUS_PBKDF2_ROUNDS * 2); + } + } + pbkdf2_hmac_sha512_Final(&pctx, digest); + + memcpy(secret_out, digest, SHA512_DIGEST_LENGTH); + + // PASS 2: remaining 32 bytes + pbkdf2_hmac_sha512_Init(&pctx, pass, pass_len, entropy, entropy_len, 2); + if (progress_callback) { + progress_callback(progress, CARDANO_ICARUS_PBKDF2_ROUNDS * 2); + } + for (int i = 0; i < CARDANO_ICARUS_STEPS; i++) { + pbkdf2_hmac_sha512_Update(&pctx, CARDANO_ICARUS_ROUNDS_PER_STEP); + if (progress_callback) { + progress += CARDANO_ICARUS_ROUNDS_PER_STEP; + progress_callback(progress, CARDANO_ICARUS_PBKDF2_ROUNDS * 2); + } + } + pbkdf2_hmac_sha512_Final(&pctx, digest); + + memcpy(secret_out + SHA512_DIGEST_LENGTH, digest, + CARDANO_SECRET_LENGTH - SHA512_DIGEST_LENGTH); + + cardano_ed25519_tweak_bits(secret_out); + + memzero(&pctx, sizeof(pctx)); + memzero(digest, sizeof(digest)); + return 1; +} + +#endif // USE_CARDANO diff --git a/trezor-crypto/crypto/chacha20poly1305/LICENSE b/trezor-crypto/crypto/chacha20poly1305/LICENSE new file mode 100644 index 00000000000..95404966f07 --- /dev/null +++ b/trezor-crypto/crypto/chacha20poly1305/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (C) 2016 Will Glozer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/trezor-crypto/crypto/chacha20poly1305/chacha_merged.c b/trezor-crypto/crypto/chacha20poly1305/chacha_merged.c index 8d4ee90c755..3819b865a59 100644 --- a/trezor-crypto/crypto/chacha20poly1305/chacha_merged.c +++ b/trezor-crypto/crypto/chacha20poly1305/chacha_merged.c @@ -23,6 +23,7 @@ void ECRYPT_init(void) return; } +// [wallet-core][non static] rename to avoid duplicate symbol in blake256.c const char chacha_sigma[16] = "expand 32-byte k"; const char tau[16] = "expand 16-byte k"; @@ -59,6 +60,12 @@ void ECRYPT_ivsetup(ECRYPT_ctx *x,const u8 *iv) x->input[15] = U8TO32_LITTLE(iv + 4); } +void ECRYPT_ctrsetup(ECRYPT_ctx *x,const u8 *ctr) +{ + x->input[12] = U8TO32_LITTLE(ctr + 0); + x->input[13] = U8TO32_LITTLE(ctr + 4); +} + void ECRYPT_encrypt_bytes(ECRYPT_ctx *x,const u8 *m,u8 *c,u32 bytes) { u32 x0 = 0, x1 = 0, x2 = 0, x3 = 0, x4 = 0, x5 = 0, x6 = 0, x7 = 0, x8 = 0, x9 = 0, x10 = 0, x11 = 0, x12 = 0, x13 = 0, x14 = 0, x15 = 0; diff --git a/trezor-crypto/crypto/chacha_drbg.c b/trezor-crypto/crypto/chacha_drbg.c index c1bd5d08e6f..e8027ffe939 100644 --- a/trezor-crypto/crypto/chacha_drbg.c +++ b/trezor-crypto/crypto/chacha_drbg.c @@ -19,44 +19,108 @@ #include +#include #include +#include + +#include +#include +#include + +#define CHACHA_DRBG_KEY_LENGTH 32 +#define CHACHA_DRBG_COUNTER_LENGTH 8 +#define CHACHA_DRBG_IV_LENGTH 8 +#define CHACHA_DRBG_SEED_LENGTH \ + (CHACHA_DRBG_KEY_LENGTH + CHACHA_DRBG_COUNTER_LENGTH + CHACHA_DRBG_IV_LENGTH) #define MAX(a, b) (a) > (b) ? (a) : (b) -void chacha_drbg_init(CHACHA_DRBG_CTX *ctx, - const uint8_t entropy[CHACHA_DRBG_SEED_LENGTH]) { +static void derivation_function(const uint8_t *input1, size_t input1_length, + const uint8_t *input2, size_t input2_length, + uint8_t *output, size_t output_length) { + // Implementation of Hash_df from NIST SP 800-90A + uint32_t block_count = (output_length - 1) / SHA256_DIGEST_LENGTH + 1; + size_t partial_block_length = output_length % SHA256_DIGEST_LENGTH; + assert(block_count <= 255); + + uint32_t output_length_bits = output_length * 8; +#if BYTE_ORDER == LITTLE_ENDIAN + REVERSE32(output_length_bits, output_length_bits); +#endif + + SHA256_CTX ctx = {0}; + + for (uint8_t counter = 1; counter <= block_count; counter++) { + sha256_Init(&ctx); + sha256_Update(&ctx, &counter, sizeof(counter)); + sha256_Update(&ctx, (uint8_t *)&output_length_bits, + sizeof(output_length_bits)); + sha256_Update(&ctx, input1, input1_length); + sha256_Update(&ctx, input2, input2_length); + + if (counter != block_count || partial_block_length == 0) { + sha256_Final(&ctx, output); + output += SHA256_DIGEST_LENGTH; + } else { // last block is partial + uint8_t digest[SHA256_DIGEST_LENGTH] = {0}; + sha256_Final(&ctx, digest); + memcpy(output, digest, partial_block_length); + memzero(digest, sizeof(digest)); + } + } + + memzero(&ctx, sizeof(ctx)); +} + +void chacha_drbg_init(CHACHA_DRBG_CTX *ctx, const uint8_t *entropy, + size_t entropy_length, const uint8_t *nonce, + size_t nonce_length) { uint8_t buffer[MAX(CHACHA_DRBG_KEY_LENGTH, CHACHA_DRBG_IV_LENGTH)] = {0}; ECRYPT_keysetup(&ctx->chacha_ctx, buffer, CHACHA_DRBG_KEY_LENGTH * 8, CHACHA_DRBG_IV_LENGTH * 8); ECRYPT_ivsetup(&ctx->chacha_ctx, buffer); - chacha_drbg_reseed(ctx, entropy); + chacha_drbg_reseed(ctx, entropy, entropy_length, nonce, nonce_length); } static void chacha_drbg_update(CHACHA_DRBG_CTX *ctx, const uint8_t data[CHACHA_DRBG_SEED_LENGTH]) { - uint8_t buffer[CHACHA_DRBG_SEED_LENGTH] = {0}; + uint8_t seed[CHACHA_DRBG_SEED_LENGTH] = {0}; if (data) - ECRYPT_encrypt_bytes(&ctx->chacha_ctx, data, buffer, - CHACHA_DRBG_SEED_LENGTH); + ECRYPT_encrypt_bytes(&ctx->chacha_ctx, data, seed, CHACHA_DRBG_SEED_LENGTH); else - ECRYPT_keystream_bytes(&ctx->chacha_ctx, buffer, CHACHA_DRBG_SEED_LENGTH); + ECRYPT_keystream_bytes(&ctx->chacha_ctx, seed, CHACHA_DRBG_SEED_LENGTH); - ECRYPT_keysetup(&ctx->chacha_ctx, buffer, CHACHA_DRBG_KEY_LENGTH * 8, + ECRYPT_keysetup(&ctx->chacha_ctx, seed, CHACHA_DRBG_KEY_LENGTH * 8, CHACHA_DRBG_IV_LENGTH * 8); - ECRYPT_ivsetup(&ctx->chacha_ctx, buffer + CHACHA_DRBG_KEY_LENGTH); + + ECRYPT_ivsetup(&ctx->chacha_ctx, + seed + CHACHA_DRBG_KEY_LENGTH + CHACHA_DRBG_COUNTER_LENGTH); + + ECRYPT_ctrsetup(&ctx->chacha_ctx, seed + CHACHA_DRBG_KEY_LENGTH); + + memzero(seed, sizeof(seed)); } void chacha_drbg_generate(CHACHA_DRBG_CTX *ctx, uint8_t *output, - uint8_t output_length) { + size_t output_length) { + assert(output_length < 65536); + assert(ctx->reseed_counter + 1 != 0); + ECRYPT_keystream_bytes(&ctx->chacha_ctx, output, output_length); chacha_drbg_update(ctx, NULL); ctx->reseed_counter++; } -void chacha_drbg_reseed(CHACHA_DRBG_CTX *ctx, - const uint8_t entropy[CHACHA_DRBG_SEED_LENGTH]) { - chacha_drbg_update(ctx, entropy); +void chacha_drbg_reseed(CHACHA_DRBG_CTX *ctx, const uint8_t *entropy, + size_t entropy_length, const uint8_t *additional_input, + size_t additional_input_length) { + uint8_t seed[CHACHA_DRBG_SEED_LENGTH] = {0}; + derivation_function(entropy, entropy_length, additional_input, + additional_input_length, seed, sizeof(seed)); + chacha_drbg_update(ctx, seed); + memzero(seed, sizeof(seed)); + ctx->reseed_counter = 1; } diff --git a/trezor-crypto/crypto/curves.c b/trezor-crypto/crypto/curves.c index c95ac8b7512..a6221a9943f 100644 --- a/trezor-crypto/crypto/curves.c +++ b/trezor-crypto/crypto/curves.c @@ -21,6 +21,7 @@ */ #include +#include const char SECP256K1_NAME[] = "secp256k1"; const char SECP256K1_DECRED_NAME[] = "secp256k1-decred"; @@ -28,10 +29,14 @@ const char SECP256K1_GROESTL_NAME[] = "secp256k1-groestl"; const char SECP256K1_SMART_NAME[] = "secp256k1-smart"; const char NIST256P1_NAME[] = "nist256p1"; const char ED25519_NAME[] = "ed25519"; +const char ED25519_SEED_NAME[] = "ed25519 seed"; +#if USE_CARDANO const char ED25519_CARDANO_NAME[] = "ed25519 cardano seed"; -const char ED25519_BLAKE2B_NANO_NAME[] = "ed25519-blake2b-nano"; // [wallet-core] +#endif const char ED25519_SHA3_NAME[] = "ed25519-sha3"; #if USE_KECCAK const char ED25519_KECCAK_NAME[] = "ed25519-keccak"; #endif const char CURVE25519_NAME[] = "curve25519"; + +const char ED25519_BLAKE2B_NANO_NAME[] = "ed25519-blake2b-nano"; // [wallet-core] diff --git a/trezor-crypto/crypto/ecdsa.c b/trezor-crypto/crypto/ecdsa.c index 0f2198fbbc0..445f29aa549 100644 --- a/trezor-crypto/crypto/ecdsa.c +++ b/trezor-crypto/crypto/ecdsa.c @@ -36,7 +36,6 @@ #include #include #include -#include // Set cp2 = cp1 void point_copy(const curve_point *cp1, curve_point *cp2) { *cp2 = *cp1; } @@ -404,13 +403,16 @@ void point_jacobian_double(jacobian_curve_point *p, const ecdsa_curve *curve) { } // res = k * p -void point_multiply(const ecdsa_curve *curve, const bignum256 *k, - const curve_point *p, curve_point *res) { +// returns 0 on success +int point_multiply(const ecdsa_curve *curve, const bignum256 *k, + const curve_point *p, curve_point *res) { // this algorithm is loosely based on // Katsuyuki Okeya and Tsuyoshi Takagi, The Width-w NAF Method Provides // Small Memory and Fast Elliptic Scalar Multiplications Secure against // Side Channel Attacks. - assert(bn_is_less(k, &curve->order)); + if (!bn_is_less(k, &curve->order)) { + return 1; + } int i = 0, j = 0; CONFIDENTIAL bignum256 a; @@ -442,7 +444,7 @@ void point_multiply(const ecdsa_curve *curve, const bignum256 *k, // special case 0*p: just return zero. We don't care about constant time. if (!is_non_zero) { point_set_infinity(res); - return; + return 1; } // Now a = k + 2^256 (mod curve->order) and a is odd. @@ -523,15 +525,20 @@ void point_multiply(const ecdsa_curve *curve, const bignum256 *k, jacobian_to_curve(&jres, res, prime); memzero(&a, sizeof(a)); memzero(&jres, sizeof(jres)); + + return 0; } #if USE_PRECOMPUTED_CP // res = k * G // k must be a normalized number with 0 <= k < curve->order -void scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, - curve_point *res) { - assert(bn_is_less(k, &curve->order)); +// returns 0 on success +int scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, + curve_point *res) { + if (!bn_is_less(k, &curve->order)) { + return 1; + } int i = {0}, j = {0}; CONFIDENTIAL bignum256 a; @@ -559,7 +566,7 @@ void scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, // special case 0*G: just return zero. We don't care about constant time. if (!is_non_zero) { point_set_infinity(res); - return; + return 0; } // Now a = k + 2^256 (mod curve->order) and a is odd. @@ -612,13 +619,15 @@ void scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, jacobian_to_curve(&jres, res, prime); memzero(&a, sizeof(a)); memzero(&jres, sizeof(jres)); + + return 0; } #else -void scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, - curve_point *res) { - point_multiply(curve, k, &curve->G, res); +int scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, + curve_point *res) { + return point_multiply(curve, k, &curve->G, res); } #endif @@ -632,6 +641,11 @@ int ecdh_multiply(const ecdsa_curve *curve, const uint8_t *priv_key, bignum256 k = {0}; bn_read_be(priv_key, &k); + if (bn_is_zero(&k) || !bn_is_less(&k, &curve->order)) { + // Invalid private key. + return 2; + } + point_multiply(curve, &k, &point, &point); memzero(&k, sizeof(k)); @@ -673,10 +687,17 @@ int ecdsa_sign_digest(const ecdsa_curve *curve, const uint8_t *priv_key, #if USE_RFC6979 rfc6979_state rng = {0}; - init_rfc6979(priv_key, digest, &rng); + init_rfc6979(priv_key, digest, curve, &rng); #endif bn_read_be(digest, &z); + if (bn_is_zero(&z)) { + // The probability of the digest being all-zero by chance is infinitesimal, + // so this is most likely an indication of a bug. Furthermore, the signature + // has no value, because in this case it can be easily forged for any public + // key, see ecdsa_verify_digest(). + return 1; + } for (i = 0; i < 10000; i++) { #if USE_RFC6979 @@ -704,11 +725,16 @@ int ecdsa_sign_digest(const ecdsa_curve *curve, const uint8_t *priv_key, continue; } + bn_read_be(priv_key, s); + if (bn_is_zero(s) || !bn_is_less(s, &curve->order)) { + // Invalid private key. + return 2; + } + // randomize operations to counter side-channel attacks generate_k_random(&randk, &curve->order); bn_multiply(&randk, &k, &curve->order); // k*rand bn_inverse(&k, &curve->order); // (k*rand)^-1 - bn_read_be(priv_key, s); // priv bn_multiply(&R.x, s, &curve->order); // R.x*priv bn_add(s, &z); // R.x*priv + z bn_multiply(&k, s, &curve->order); // (k*rand)^-1 (R.x*priv + z) @@ -755,33 +781,55 @@ int ecdsa_sign_digest(const ecdsa_curve *curve, const uint8_t *priv_key, return -1; } -void ecdsa_get_public_key33(const ecdsa_curve *curve, const uint8_t *priv_key, - uint8_t *pub_key) { +// returns 0 on success +int ecdsa_get_public_key33(const ecdsa_curve *curve, const uint8_t *priv_key, + uint8_t *pub_key) { curve_point R = {0}; bignum256 k = {0}; bn_read_be(priv_key, &k); + if (bn_is_zero(&k) || !bn_is_less(&k, &curve->order)) { + // Invalid private key. + memzero(pub_key, 33); + return -1; + } + // compute k*G - scalar_multiply(curve, &k, &R); + if (scalar_multiply(curve, &k, &R) != 0) { + memzero(&k, sizeof(k)); + return 1; + } pub_key[0] = 0x02 | (R.y.val[0] & 0x01); bn_write_be(&R.x, pub_key + 1); memzero(&R, sizeof(R)); memzero(&k, sizeof(k)); + return 0; } -void ecdsa_get_public_key65(const ecdsa_curve *curve, const uint8_t *priv_key, - uint8_t *pub_key) { +// returns 0 on success +int ecdsa_get_public_key65(const ecdsa_curve *curve, const uint8_t *priv_key, + uint8_t *pub_key) { curve_point R = {0}; bignum256 k = {0}; bn_read_be(priv_key, &k); + if (bn_is_zero(&k) || !bn_is_less(&k, &curve->order)) { + // Invalid private key. + memzero(pub_key, 65); + return -1; + } + // compute k*G - scalar_multiply(curve, &k, &R); + if (scalar_multiply(curve, &k, &R) != 0) { + memzero(&k, sizeof(k)); + return 1; + } pub_key[0] = 0x04; bn_write_be(&R.x, pub_key + 1); bn_write_be(&R.y, pub_key + 33); memzero(&R, sizeof(R)); memzero(&k, sizeof(k)); + return 0; } int ecdsa_uncompress_pubkey(const ecdsa_curve *curve, const uint8_t *pub_key, @@ -1017,6 +1065,10 @@ int ecdsa_recover_pub_from_sig(const ecdsa_curve *curve, uint8_t *pub_key, scalar_multiply(curve, &e, &cp2); // cp = (s * r^-1 * k - digest * r^-1) * G = Pub point_add(curve, &cp2, &cp); + // The point at infinity is not considered to be a valid public key. + if (point_is_infinity(&cp)) { + return 1; + } pub_key[0] = 0x04; bn_write_be(&cp.x, pub_key + 1); bn_write_be(&cp.y, pub_key + 33); @@ -1107,7 +1159,7 @@ int ecdsa_sig_to_der(const uint8_t *sig, uint8_t *der) { // process R i = 0; - while (sig[i] == 0 && i < 32) { + while (i < 31 && sig[i] == 0) { i++; } // skip leading zeroes if (sig[i] >= 0x80) { // put zero in output if MSB set @@ -1130,7 +1182,7 @@ int ecdsa_sig_to_der(const uint8_t *sig, uint8_t *der) { // process S i = 32; - while (sig[i] == 0 && i < 64) { + while (i < 63 && sig[i] == 0) { i++; } // skip leading zeroes if (sig[i] >= 0x80) { // put zero in output if MSB set @@ -1197,56 +1249,3 @@ int ecdsa_sig_from_der(const uint8_t *der, size_t der_len, uint8_t sig[64]) { return 0; } - -// [wallet-core] -int zil_schnorr_sign(const ecdsa_curve *curve, const uint8_t *priv_key, const uint8_t *msg, const uint32_t msg_len, uint8_t *sig) -{ - int i; - bignum256 k; - - uint8_t hash[32]; - sha256_Raw(msg, msg_len, hash); - - rfc6979_state rng; - init_rfc6979(priv_key, hash, &rng); - - for (i = 0; i < 10000; i++) { - // generate K deterministically - generate_k_rfc6979(&k, &rng); - // if k is too big or too small, we don't like it - if (bn_is_zero(&k) || !bn_is_less(&k, &curve->order)) { - continue; - } - - schnorr_sign_pair sign; - if (schnorr_sign(curve, priv_key, &k, msg, msg_len, &sign) != 0) { - continue; - } - - // we're done - memcpy(sig, sign.r, 32); - memcpy(sig + 32, sign.s, 32); - - memzero(&k, sizeof(k)); - memzero(&rng, sizeof(rng)); - memzero(&sign, sizeof(sign)); - return 0; - } - - // Too many retries without a valid signature - // -> fail with an error - memzero(&k, sizeof(k)); - memzero(&rng, sizeof(rng)); - return -1; -} - -// [wallet-core] -int zil_schnorr_verify(const ecdsa_curve *curve, const uint8_t *pub_key, const uint8_t *sig, const uint8_t *msg, const uint32_t msg_len) -{ - schnorr_sign_pair sign; - - memcpy(sign.r, sig, 32); - memcpy(sign.s, sig + 32, 32); - - return schnorr_verify(curve, pub_key, msg, msg_len, &sign); -} diff --git a/trezor-crypto/crypto/ed25519-donna/README.md b/trezor-crypto/crypto/ed25519-donna/README.md index 73eadf8db7c..71b643485de 100644 --- a/trezor-crypto/crypto/ed25519-donna/README.md +++ b/trezor-crypto/crypto/ed25519-donna/README.md @@ -1,5 +1,5 @@ [ed25519](https://ed25519.cr.yp.to) is an -[Elliptic Curve Digital Signature Algortithm](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm), +[Elliptic Curve Digital Signature Algorithm](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm), developed by [Dan Bernstein](https://cr.yp.to/djb.html), [Niels Duif](https://www.nielsduif.nl), [Tanja Lange](https://hyperelliptic.org/tanja), @@ -56,7 +56,7 @@ No configuration is needed **if you are compiling against OpenSSL**. ##### Hash Options -If you are not compiling aginst OpenSSL, you will need a hash function. +If you are not compiling against OpenSSL, you will need a hash function. To use a simple/**slow** implementation of SHA-512, use `-DED25519_REFHASH` when compiling `ed25519.c`. This should never be used except to verify the code works when OpenSSL is not available. @@ -73,7 +73,7 @@ custom hash implementation in ed25519-hash-custom.h. The hash must have a 512bit ##### Random Options -If you are not compiling aginst OpenSSL, you will need a random function for batch verification. +If you are not compiling against OpenSSL, you will need a random function for batch verification. To use a custom random function, use `-DED25519_CUSTOMRANDOM` when compiling `ed25519.c` and put your custom hash implementation in ed25519-randombytes-custom.h. The random function must implement: @@ -170,7 +170,7 @@ signing due to both using the same code for the scalar multiply. #### Testing -Fuzzing against reference implemenations is now available. See [fuzz/README](fuzz/README.md). +Fuzzing against reference implementations is now available. See [fuzz/README](fuzz/README.md). Building `ed25519.c` with `-DED25519_TEST` and linking with `test.c` will run basic sanity tests and benchmark each function. `test-batch.c` has been incorporated in to `test.c`. diff --git a/trezor-crypto/crypto/ed25519-donna/ed25519.c b/trezor-crypto/crypto/ed25519-donna/ed25519.c index 8c3b837fa4d..6e01c0c1e98 100644 --- a/trezor-crypto/crypto/ed25519-donna/ed25519.c +++ b/trezor-crypto/crypto/ed25519-donna/ed25519.c @@ -18,6 +18,7 @@ #include #include +#include /* Generates a (extsk[0..31]) and aExt (extsk[32..63]) @@ -31,10 +32,10 @@ ed25519_extsk(hash_512bits extsk, const ed25519_secret_key sk) { } static void -ed25519_hram(hash_512bits hram, const ed25519_signature RS, const ed25519_public_key pk, const unsigned char *m, size_t mlen) { +ed25519_hram(hash_512bits hram, const ed25519_public_key R, const ed25519_public_key pk, const unsigned char *m, size_t mlen) { ed25519_hash_context ctx; ed25519_hash_init(&ctx); - ed25519_hash_update(&ctx, RS, 32); + ed25519_hash_update(&ctx, R, 32); ed25519_hash_update(&ctx, pk, 32); ed25519_hash_update(&ctx, m, mlen); ed25519_hash_final(&ctx, hram); @@ -42,34 +43,11 @@ ed25519_hram(hash_512bits hram, const ed25519_signature RS, const ed25519_public void ED25519_FN(ed25519_publickey) (const ed25519_secret_key sk, ed25519_public_key pk) { - bignum256modm a = {0}; - ge25519 ALIGN(16) A; hash_512bits extsk = {0}; - - /* A = aB */ ed25519_extsk(extsk, sk); - - expand256_modm(a, extsk, 32); - ge25519_scalarmult_base_niels(&A, ge25519_niels_base_multiples, a); - ge25519_pack(pk, &A); -} - -#if USE_CARDANO -void -ED25519_FN(ed25519_publickey_ext) (const ed25519_secret_key sk, const ed25519_secret_key skext, ed25519_public_key pk) { - bignum256modm a = {0}; - ge25519 ALIGN(16) A; - hash_512bits extsk = {0}; - - /* we don't stretch the key through hashing first since its already 64 bytes */ - - memcpy(extsk, sk, 32); - memcpy(extsk+32, skext, 32); - expand256_modm(a, extsk, 32); - ge25519_scalarmult_base_niels(&A, ge25519_niels_base_multiples, a); - ge25519_pack(pk, &A); + ed25519_publickey_ext(extsk, pk); + memzero(&extsk, sizeof(extsk)); } -#endif void ED25519_FN(ed25519_cosi_sign) (const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_secret_key nonce, const ed25519_public_key R, const ed25519_public_key pk, ed25519_cosi_signature sig) { @@ -81,6 +59,7 @@ ED25519_FN(ed25519_cosi_sign) (const unsigned char *m, size_t mlen, const ed2551 /* r = nonce */ expand256_modm(r, extnonce, 32); + memzero(&extnonce, sizeof(extnonce)); /* S = H(R,A,m).. */ ed25519_hram(hram, R, pk, m, mlen); @@ -88,57 +67,25 @@ ED25519_FN(ed25519_cosi_sign) (const unsigned char *m, size_t mlen, const ed2551 /* S = H(R,A,m)a */ expand256_modm(a, extsk, 32); + memzero(&extsk, sizeof(extsk)); mul256_modm(S, S, a); + memzero(&a, sizeof(a)); /* S = (r + H(R,A,m)a) */ add256_modm(S, S, r); + memzero(&r, sizeof(r)); /* S = (r + H(R,A,m)a) mod L */ contract256_modm(sig, S); } void -ED25519_FN(ed25519_sign) (const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_public_key pk, ed25519_signature RS) { - ed25519_hash_context ctx; - bignum256modm r = {0}, S = {0}, a = {0}; - ge25519 ALIGN(16) R = {0}; - hash_512bits extsk = {0}, hashr = {0}, hram = {0}; - - ed25519_extsk(extsk, sk); - - - /* r = H(aExt[32..64], m) */ - ed25519_hash_init(&ctx); - ed25519_hash_update(&ctx, extsk + 32, 32); - ed25519_hash_update(&ctx, m, mlen); - ed25519_hash_final(&ctx, hashr); - expand256_modm(r, hashr, 64); - - /* R = rB */ - ge25519_scalarmult_base_niels(&R, ge25519_niels_base_multiples, r); - ge25519_pack(RS, &R); - - /* S = H(R,A,m).. */ - ed25519_hram(hram, RS, pk, m, mlen); - expand256_modm(S, hram, 64); - - /* S = H(R,A,m)a */ - expand256_modm(a, extsk, 32); - mul256_modm(S, S, a); - - /* S = (r + H(R,A,m)a) */ - add256_modm(S, S, r); - - /* S = (r + H(R,A,m)a) mod L */ - contract256_modm(RS + 32, S); -} - -#if USE_CARDANO -void -ED25519_FN(ed25519_sign_ext) (const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_secret_key skext, const ed25519_public_key pk, ed25519_signature RS) { +ED25519_FN(ed25519_sign_ext) (const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_secret_key skext, ed25519_signature RS) { ed25519_hash_context ctx; bignum256modm r = {0}, S = {0}, a = {0}; ge25519 ALIGN(16) R = {0}; + ge25519 ALIGN(16) A = {0}; + ed25519_public_key pk = {0}; hash_512bits extsk = {0}, hashr = {0}, hram = {0}; /* we don't stretch the key through hashing first since its already 64 bytes */ @@ -153,30 +100,47 @@ ED25519_FN(ed25519_sign_ext) (const unsigned char *m, size_t mlen, const ed25519 ed25519_hash_update(&ctx, m, mlen); ed25519_hash_final(&ctx, hashr); expand256_modm(r, hashr, 64); + memzero(&hashr, sizeof(hashr)); /* R = rB */ ge25519_scalarmult_base_niels(&R, ge25519_niels_base_multiples, r); ge25519_pack(RS, &R); + /* a = aExt[0..31] */ + expand256_modm(a, extsk, 32); + memzero(&extsk, sizeof(extsk)); + + /* A = aB */ + ge25519_scalarmult_base_niels(&A, ge25519_niels_base_multiples, a); + ge25519_pack(pk, &A); + /* S = H(R,A,m).. */ ed25519_hram(hram, RS, pk, m, mlen); expand256_modm(S, hram, 64); /* S = H(R,A,m)a */ - expand256_modm(a, extsk, 32); mul256_modm(S, S, a); + memzero(&a, sizeof(a)); /* S = (r + H(R,A,m)a) */ add256_modm(S, S, r); + memzero(&r, sizeof(r)); /* S = (r + H(R,A,m)a) mod L */ contract256_modm(RS + 32, S); } -#endif + +void +ED25519_FN(ed25519_sign) (const unsigned char *m, size_t mlen, const ed25519_secret_key sk, ed25519_signature RS) { + hash_512bits extsk = {0}; + ed25519_extsk(extsk, sk); + ED25519_FN(ed25519_sign_ext)(m, mlen, extsk, extsk + 32, RS); + memzero(&extsk, sizeof(extsk)); +} int ED25519_FN(ed25519_sign_open) (const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS) { - ge25519 ALIGN(16) R, A; + ge25519 ALIGN(16) R = {0}, A = {0}; hash_512bits hash = {0}; bignum256modm hram = {0}, S = {0}; unsigned char checkR[32] = {0}; @@ -204,17 +168,19 @@ ED25519_FN(ed25519_sign_open) (const unsigned char *m, size_t mlen, const ed2551 int ED25519_FN(ed25519_scalarmult) (ed25519_public_key res, const ed25519_secret_key sk, const ed25519_public_key pk) { bignum256modm a = {0}; - ge25519 ALIGN(16) A, P; + ge25519 ALIGN(16) A = {0}, P = {0}; hash_512bits extsk = {0}; ed25519_extsk(extsk, sk); expand256_modm(a, extsk, 32); + memzero(&extsk, sizeof(extsk)); if (!ge25519_unpack_negative_vartime(&P, pk)) { return -1; } ge25519_scalarmult(&A, &P, a); + memzero(&a, sizeof(a)); curve25519_neg(A.x, A.x); ge25519_pack(res, &A); return 0; @@ -225,6 +191,19 @@ ED25519_FN(ed25519_scalarmult) (ed25519_public_key res, const ed25519_secret_key #include +void +ed25519_publickey_ext(const ed25519_secret_key extsk, ed25519_public_key pk) { + bignum256modm a = {0}; + ge25519 ALIGN(16) A = {0}; + + expand256_modm(a, extsk, 32); + + /* A = aB */ + ge25519_scalarmult_base_niels(&A, ge25519_niels_base_multiples, a); + memzero(&a, sizeof(a)); + ge25519_pack(pk, &A); +} + int ed25519_cosi_combine_publickeys(ed25519_public_key res, CONST ed25519_public_key *pks, size_t n) { size_t i = 0; @@ -277,8 +256,8 @@ void curve25519_scalarmult_basepoint(curve25519_key pk, const curve25519_key e) { curve25519_key ec = {0}; bignum256modm s = {0}; - bignum25519 ALIGN(16) yplusz, zminusy; - ge25519 ALIGN(16) p; + bignum25519 ALIGN(16) yplusz = {0}, zminusy = {0}; + ge25519 ALIGN(16) p = {0}; size_t i = 0; /* clamp */ @@ -288,9 +267,11 @@ curve25519_scalarmult_basepoint(curve25519_key pk, const curve25519_key e) { ec[31] |= 64; expand_raw256_modm(s, ec); + memzero(&ec, sizeof(ec)); /* scalar * basepoint */ ge25519_scalarmult_base_niels(&p, ge25519_niels_base_multiples, s); + memzero(&s, sizeof(s)); /* u = (y + z) / (z - y) */ curve25519_add(yplusz, p.y, p.z); @@ -310,6 +291,7 @@ curve25519_scalarmult(curve25519_key mypublic, const curve25519_key secret, cons e[31] &= 0x7f; e[31] |= 0x40; curve25519_scalarmult_donna(mypublic, e, basepoint); + memzero(&e, sizeof(e)); } #endif // ED25519_SUFFIX diff --git a/trezor-crypto/crypto/nem.c b/trezor-crypto/crypto/nem.c index fd844156871..66de5cfa9e6 100644 --- a/trezor-crypto/crypto/nem.c +++ b/trezor-crypto/crypto/nem.c @@ -205,8 +205,7 @@ size_t nem_transaction_end(nem_transaction_ctx *ctx, const ed25519_secret_key private_key, ed25519_signature signature) { if (private_key != NULL && signature != NULL) { - ed25519_sign_keccak(ctx->buffer, ctx->offset, private_key, ctx->public_key, - signature); + ed25519_sign_keccak(ctx->buffer, ctx->offset, private_key, signature); } return ctx->offset; diff --git a/trezor-crypto/crypto/rfc6979.c b/trezor-crypto/crypto/rfc6979.c index 98491594bf5..c781e47b926 100644 --- a/trezor-crypto/crypto/rfc6979.c +++ b/trezor-crypto/crypto/rfc6979.c @@ -21,14 +21,30 @@ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ +#include -#include #include #include +#include void init_rfc6979(const uint8_t *priv_key, const uint8_t *hash, - rfc6979_state *state) { - hmac_drbg_init(state, priv_key, 32, hash, 32); + const ecdsa_curve *curve, rfc6979_state *state) { + if (curve) { + bignum256 hash_bn = {0}; + bn_read_be(hash, &hash_bn); + + // Make sure hash is partly reduced modulo order + assert(bn_bitcount(&curve->order) >= 256); + bn_mod(&hash_bn, &curve->order); + + uint8_t hash_reduced[32] = {0}; + bn_write_be(&hash_bn, hash_reduced); + memzero(&hash_bn, sizeof(hash_bn)); + hmac_drbg_init(state, priv_key, 32, hash_reduced, 32); + memzero(hash_reduced, sizeof(hash_reduced)); + } else { + hmac_drbg_init(state, priv_key, 32, hash, 32); + } } // generate next number from deterministic random number generator diff --git a/trezor-crypto/crypto/schnorr.c b/trezor-crypto/crypto/schnorr.c deleted file mode 100644 index c37e48f75ba..00000000000 --- a/trezor-crypto/crypto/schnorr.c +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright (c) 2019 Anatolii Kurotych - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include - -// r = H(Q, kpub, m) -static void calc_r(const curve_point *Q, const uint8_t pub_key[33], - const uint8_t *msg, const uint32_t msg_len, bignum256 *r) { - uint8_t Q_compress[33]; - compress_coords(Q, Q_compress); - - SHA256_CTX ctx; - uint8_t digest[SHA256_DIGEST_LENGTH]; - sha256_Init(&ctx); - sha256_Update(&ctx, Q_compress, 33); - sha256_Update(&ctx, pub_key, 33); - sha256_Update(&ctx, msg, msg_len); - sha256_Final(&ctx, digest); - - // Convert the raw bigendian 256 bit value to a normalized, partly reduced bignum - bn_read_be(digest, r); -} - -// Returns 0 if signing succeeded -int schnorr_sign(const ecdsa_curve *curve, const uint8_t *priv_key, - const bignum256 *k, const uint8_t *msg, const uint32_t msg_len, - schnorr_sign_pair *result) { - uint8_t pub_key[33]; - curve_point Q; - bignum256 private_key_scalar; - bignum256 r_temp; - bignum256 s_temp; - bignum256 r_kpriv_result; - - bn_read_be(priv_key, &private_key_scalar); - ecdsa_get_public_key33(curve, priv_key, pub_key); - - // Compute commitment Q = kG - point_multiply(curve, k, &curve->G, &Q); - - // Compute challenge r = H(Q, kpub, m) - calc_r(&Q, pub_key, msg, msg_len, &r_temp); - - // Fully reduce the bignum - bn_mod(&r_temp, &curve->order); - - // Convert the normalized, fully reduced bignum to a raw bigendian 256 bit value - bn_write_be(&r_temp, result->r); - - // Compute s = k - r*kpriv - bn_copy(&r_temp, &r_kpriv_result); - - // r*kpriv result is partly reduced - bn_multiply(&private_key_scalar, &r_kpriv_result, &curve->order); - - // k - r*kpriv result is normalized but not reduced - bn_subtractmod(k, &r_kpriv_result, &s_temp, &curve->order); - - // Partly reduce the result - bn_fast_mod(&s_temp, &curve->order); - - // Fully reduce the result - bn_mod(&s_temp, &curve->order); - - // Convert the normalized, fully reduced bignum to a raw bigendian 256 bit value - bn_write_be(&s_temp, result->s); - - if (bn_is_zero(&r_temp) || bn_is_zero(&s_temp)) return 1; - - return 0; -} - -// Returns 0 if verification succeeded -int schnorr_verify(const ecdsa_curve *curve, const uint8_t *pub_key, - const uint8_t *msg, const uint32_t msg_len, - const schnorr_sign_pair *sign) { - curve_point pub_key_point; - curve_point sG, Q; - bignum256 r_temp; - bignum256 s_temp; - bignum256 r_computed; - - if (msg_len == 0) return 1; - - // Convert the raw bigendian 256 bit values to normalized, partly reduced bignums - bn_read_be(sign->r, &r_temp); - bn_read_be(sign->s, &s_temp); - - // Check if r,s are in [1, ..., order-1] - if (bn_is_zero(&r_temp)) return 2; - if (bn_is_zero(&s_temp)) return 3; - if (bn_is_less(&curve->order, &r_temp)) return 4; - if (bn_is_less(&curve->order, &s_temp)) return 5; - if (bn_is_equal(&curve->order, &r_temp)) return 6; - if (bn_is_equal(&curve->order, &s_temp)) return 7; - - if (!ecdsa_read_pubkey(curve, pub_key, &pub_key_point)) { - return 8; - } - - // Compute Q = sG + r*kpub - point_multiply(curve, &s_temp, &curve->G, &sG); - point_multiply(curve, &r_temp, &pub_key_point, &Q); - point_add(curve, &sG, &Q); - - // Compute r' = H(Q, kpub, m) - calc_r(&Q, pub_key, msg, msg_len, &r_computed); - - // Fully reduce the bignum - bn_mod(&r_computed, &curve->order); - - // Check r == r' - if (bn_is_equal(&r_temp, &r_computed)) return 0; // success - - return 10; -} diff --git a/trezor-crypto/crypto/scrypt.c b/trezor-crypto/crypto/scrypt.c index c1856dcbda5..4e94beab0e8 100644 --- a/trezor-crypto/crypto/scrypt.c +++ b/trezor-crypto/crypto/scrypt.c @@ -42,7 +42,7 @@ #include #include -static void blkcpy(void *, void *, size_t); +static void blkcpy(uint32_t *, const uint32_t *, size_t); static void blkxor(void *, void *, size_t); static void salsa20_8(uint32_t[16]); static void blockmix_salsa8(uint32_t *, uint32_t *, uint32_t *, size_t); @@ -50,15 +50,12 @@ static uint64_t integerify(void *, size_t); static void smix(uint8_t *, size_t, uint64_t, uint32_t *, uint32_t *); static void -blkcpy(void * dest, void * src, size_t len) +blkcpy(uint32_t * dest, const uint32_t * src, size_t len) { - size_t * D = dest; - size_t * S = src; - size_t L = len / sizeof(size_t); - size_t i; + size_t L = len / sizeof(uint32_t); - for (i = 0; i < L; i++) - D[i] = S[i]; + for (size_t i = 0; i < L; i++) + dest[i] = src[i]; } static void diff --git a/trezor-crypto/crypto/sha2.c b/trezor-crypto/crypto/sha2.c index 0f14e970874..47d7eebbcdb 100644 --- a/trezor-crypto/crypto/sha2.c +++ b/trezor-crypto/crypto/sha2.c @@ -589,7 +589,7 @@ void sha1_Update(SHA1_CTX* context, const sha2_byte *data, size_t len) { usedspace = freespace = 0; } -void sha1_Final(SHA1_CTX* context, sha2_byte digest[]) { +void sha1_Final(SHA1_CTX* context, sha2_byte digest[SHA1_DIGEST_LENGTH]) { unsigned int usedspace = 0; /* If no digest buffer is passed, we don't bother doing this: */ @@ -643,7 +643,7 @@ void sha1_Final(SHA1_CTX* context, sha2_byte digest[]) { usedspace = 0; } -char *sha1_End(SHA1_CTX* context, char buffer[]) { +char *sha1_End(SHA1_CTX* context, char buffer[SHA1_DIGEST_STRING_LENGTH]) { sha2_byte digest[SHA1_DIGEST_LENGTH] = {0}, *d = digest; int i = 0; @@ -896,7 +896,7 @@ void sha256_Update(SHA256_CTX* context, const sha2_byte *data, size_t len) { usedspace = freespace = 0; } -void sha256_Final(SHA256_CTX* context, sha2_byte digest[]) { +void sha256_Final(SHA256_CTX* context, sha2_byte digest[SHA256_DIGEST_LENGTH]) { unsigned int usedspace = 0; /* If no digest buffer is passed, we don't bother doing this: */ @@ -950,7 +950,7 @@ void sha256_Final(SHA256_CTX* context, sha2_byte digest[]) { usedspace = 0; } -char *sha256_End(SHA256_CTX* context, char buffer[]) { +char *sha256_End(SHA256_CTX* context, char buffer[SHA256_DIGEST_STRING_LENGTH]) { sha2_byte digest[SHA256_DIGEST_LENGTH] = {0}, *d = digest; int i = 0; @@ -1250,7 +1250,7 @@ static void sha512_Last(SHA512_CTX* context) { sha512_Transform(context->state, context->buffer, context->state); } -void sha512_Final(SHA512_CTX* context, sha2_byte digest[]) { +void sha512_Final(SHA512_CTX* context, sha2_byte digest[SHA512_DIGEST_LENGTH]) { /* If no digest buffer is passed, we don't bother doing this: */ if (digest != (sha2_byte*)0) { sha512_Last(context); @@ -1269,7 +1269,7 @@ void sha512_Final(SHA512_CTX* context, sha2_byte digest[]) { memzero(context, sizeof(SHA512_CTX)); } -char *sha512_End(SHA512_CTX* context, char buffer[]) { +char *sha512_End(SHA512_CTX* context, char buffer[SHA512_DIGEST_STRING_LENGTH]) { sha2_byte digest[SHA512_DIGEST_LENGTH] = {0}, *d = digest; int i = 0; diff --git a/trezor-crypto/crypto/slip39.c b/trezor-crypto/crypto/slip39.c new file mode 100644 index 00000000000..ec1adf20169 --- /dev/null +++ b/trezor-crypto/crypto/slip39.c @@ -0,0 +1,151 @@ +/** + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +/** + * Returns word at position `index`. + */ +const char* get_word(uint16_t index) { + if (index >= WORDS_COUNT) { + return NULL; + } + + return slip39_wordlist[index]; +} + +/** + * Finds the index of a given word. + * Returns true on success and stores result in `index`. + */ +bool word_index(uint16_t* index, const char* word, uint8_t word_length) { + uint16_t lo = 0; + uint16_t hi = WORDS_COUNT; + uint16_t mid = 0; + + while ((hi - lo) > 1) { + mid = (hi + lo) / 2; + if (strncmp(slip39_wordlist[mid], word, word_length) > 0) { + hi = mid; + } else { + lo = mid; + } + } + if (strncmp(slip39_wordlist[lo], word, word_length) != 0) { + return false; + } + *index = lo; + return true; +} + +/** + * Returns the index of the first sequence in words_button_seq[] which is not + * less than the given sequence. Returns WORDS_COUNT if there is no such + * sequence. + */ +static uint16_t find_sequence(uint16_t sequence) { + if (sequence <= words_button_seq[0].sequence) { + return 0; + } + + uint16_t lo = 0; + uint16_t hi = WORDS_COUNT; + + while (hi - lo > 1) { + uint16_t mid = (hi + lo) / 2; + if (words_button_seq[mid].sequence >= sequence) { + hi = mid; + } else { + lo = mid; + } + } + + return hi; +} + +/** + * Returns a word matching the button sequence prefix or NULL if no match is + * found. + */ +const char* button_sequence_to_word(uint16_t sequence) { + if (sequence == 0) { + return slip39_wordlist[words_button_seq[0].index]; + } + + uint16_t multiplier = 1; + while (sequence < 1000) { + sequence *= 10; + multiplier *= 10; + } + + uint16_t i = find_sequence(sequence); + if (i >= WORDS_COUNT || + words_button_seq[i].sequence - sequence >= multiplier) { + return NULL; + } + + return slip39_wordlist[words_button_seq[i].index]; +} + +/** + * Calculates which buttons on the T9 keyboard can still be pressed after the + * prefix was entered. Returns a 9-bit bitmask, where each bit specifies which + * buttons can be pressed (there are still words in this combination). The least + * significant bit corresponds to the first button. + * + * Example: 110000110 - second, third, eighth and ninth button still can be + * pressed. + */ +uint16_t slip39_word_completion_mask(uint16_t prefix) { + if (prefix >= 1000) { + // Four char prefix -> the mask is zero. + return 0; + } + + // Determine the range of sequences [min, max), which have the given prefix. + uint16_t min = prefix; + uint16_t max = prefix + 1; + uint16_t divider = 1; + while (max <= 1000) { + min *= 10; + max *= 10; + divider *= 10; + } + divider /= 10; + + // Determine the range we will be searching in words_button_seq[]. + min = find_sequence(min); + max = find_sequence(max); + + uint16_t bitmap = 0; + for (uint16_t i = min; i < max; ++i) { + uint8_t digit = (words_button_seq[i].sequence / divider) % 10; + bitmap |= 1 << (digit - 1); + } + + return bitmap; +} diff --git a/trezor-crypto/crypto/tests/CMakeLists.txt b/trezor-crypto/crypto/tests/CMakeLists.txt index 16acd1d0f0e..0765cae1f44 100644 --- a/trezor-crypto/crypto/tests/CMakeLists.txt +++ b/trezor-crypto/crypto/tests/CMakeLists.txt @@ -1,8 +1,6 @@ -# Copyright © 2017-2022 Trust Wallet. +# SPDX-License-Identifier: Apache-2.0 # -# This file is part of Trust. The full Trust copyright notice, including -# terms governing use, modification, and redistribution, is contained in the -# file LICENSE at the root of the source code distribution tree. +# Copyright © 2017 Trust Wallet. enable_testing() @@ -11,6 +9,7 @@ find_library(check PATH ${CMAKE_SOURCE_DIR}/build/local/lib/pkgconfig NO_DEFAULT # Test executable add_executable(TrezorCryptoTests test_check.c) target_link_libraries(TrezorCryptoTests TrezorCrypto check) -target_include_directories(TrezorCryptoTests PRIVATE ${CMAKE_SOURCE_DIR}/src) +target_link_directories(TrezorCryptoTests PRIVATE ${PREFIX}/lib) +target_include_directories(TrezorCryptoTests PRIVATE ${CMAKE_SOURCE_DIR}/src ${PREFIX}/include) add_test(NAME test_check COMMAND TrezorCryptoTests) diff --git a/trezor-crypto/crypto/tests/test_check.c b/trezor-crypto/crypto/tests/test_check.c index 44239eca224..0227f0146a8 100644 --- a/trezor-crypto/crypto/tests/test_check.c +++ b/trezor-crypto/crypto/tests/test_check.c @@ -21,6 +21,7 @@ * OTHER DEALINGS IN THE SOFTWARE. */ +#include #include #include #include @@ -31,7 +32,7 @@ #include -#if VALGRIND +#ifdef VALGRIND #include #include #endif @@ -48,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -70,11 +72,10 @@ #include #include #include -#include // [wallet-core] -//#include // [wallet-core] -//#include +#include +#include -#if VALGRIND +#ifdef VALGRIND /* * This is a clever trick to make Valgrind's Memcheck verify code * is constant-time with respect to secret data. @@ -139,7 +140,7 @@ START_TEST(test_bignum_read_be) { 0x14087f0a, 0x15498fe5, 0x10b161bb, 0xc55ece}}; for (int i = 0; i < 9; i++) { - ck_assert_int_eq(a.val[i], b.val[i]); + ck_assert_uint_eq(a.val[i], b.val[i]); } } END_TEST @@ -348,21 +349,21 @@ START_TEST(test_bignum_write_uint32) { fromhex( "000000000000000000000000000000000000000000000000000000001fffffff"), &a); - ck_assert_int_eq(bn_write_uint32(&a), 0x1fffffff); + ck_assert_uint_eq(bn_write_uint32(&a), 0x1fffffff); // lowest 30 bits set bn_read_be( fromhex( "000000000000000000000000000000000000000000000000000000003fffffff"), &a); - ck_assert_int_eq(bn_write_uint32(&a), 0x3fffffff); + ck_assert_uint_eq(bn_write_uint32(&a), 0x3fffffff); // bit 31 set bn_read_be( fromhex( "0000000000000000000000000000000000000000000000000000000040000000"), &a); - ck_assert_int_eq(bn_write_uint32(&a), 0x40000000); + ck_assert_uint_eq(bn_write_uint32(&a), 0x40000000); } END_TEST @@ -374,35 +375,35 @@ START_TEST(test_bignum_write_uint64) { fromhex( "000000000000000000000000000000000000000000000000000000003fffffff"), &a); - ck_assert_int_eq(bn_write_uint64(&a), 0x3fffffff); + ck_assert_uint_eq(bn_write_uint64(&a), 0x3fffffff); // bit 31 set bn_read_be( fromhex( "0000000000000000000000000000000000000000000000000000000040000000"), &a); - ck_assert_int_eq(bn_write_uint64(&a), 0x40000000); + ck_assert_uint_eq(bn_write_uint64(&a), 0x40000000); // bit 33 set bn_read_be( fromhex( "0000000000000000000000000000000000000000000000000000000100000000"), &a); - ck_assert_int_eq(bn_write_uint64(&a), 0x100000000LL); + ck_assert_uint_eq(bn_write_uint64(&a), 0x100000000LL); // bit 61 set bn_read_be( fromhex( "0000000000000000000000000000000000000000000000002000000000000000"), &a); - ck_assert_int_eq(bn_write_uint64(&a), 0x2000000000000000LL); + ck_assert_uint_eq(bn_write_uint64(&a), 0x2000000000000000LL); // all 64 bits set bn_read_be( fromhex( "000000000000000000000000000000000000000000000000ffffffffffffffff"), &a); - ck_assert_int_eq(bn_write_uint64(&a), 0xffffffffffffffffLL); + ck_assert_uint_eq(bn_write_uint64(&a), 0xffffffffffffffffLL); } END_TEST @@ -556,19 +557,19 @@ END_TEST START_TEST(test_bignum_format_uint64) { char buf[128], str[128]; - int r; + size_t r; // test for (10^i) and (10^i) - 1 uint64_t m = 1; for (int i = 0; i <= 19; i++, m *= 10) { sprintf(str, "%" PRIu64, m); r = bn_format_uint64(m, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, strlen(str)); + ck_assert_uint_eq(r, strlen(str)); ck_assert_str_eq(buf, str); uint64_t n = m - 1; sprintf(str, "%" PRIu64, n); r = bn_format_uint64(n, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, strlen(str)); + ck_assert_uint_eq(r, strlen(str)); ck_assert_str_eq(buf, str); } } @@ -577,14 +578,14 @@ END_TEST START_TEST(test_bignum_format) { bignum256 a; char buf[128]; - int r; + size_t r; bn_read_be( fromhex( "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "0"); bn_read_be( @@ -592,7 +593,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, NULL, NULL, 20, 0, true, buf, sizeof(buf)); - ck_assert_int_eq(r, 22); + ck_assert_uint_eq(r, 22); ck_assert_str_eq(buf, "0.00000000000000000000"); bn_read_be( @@ -600,7 +601,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, NULL, NULL, 0, 5, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "0"); bn_read_be( @@ -608,7 +609,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, NULL, NULL, 0, -5, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "0"); bn_read_be( @@ -616,7 +617,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, "", "", 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "0"); bn_read_be( @@ -624,7 +625,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, NULL, "SFFX", 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1 + 4); + ck_assert_uint_eq(r, 1 + 4); ck_assert_str_eq(buf, "0SFFX"); bn_read_be( @@ -632,7 +633,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, "PRFX", NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 4 + 1); + ck_assert_uint_eq(r, 4 + 1); ck_assert_str_eq(buf, "PRFX0"); bn_read_be( @@ -640,7 +641,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, "PRFX", "SFFX", 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 4 + 1 + 4); + ck_assert_uint_eq(r, 4 + 1 + 4); ck_assert_str_eq(buf, "PRFX0SFFX"); bn_read_be( @@ -648,7 +649,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000000"), &a); r = bn_format(&a, NULL, NULL, 18, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "0"); bn_read_be( @@ -656,7 +657,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000001"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "1"); bn_read_be( @@ -664,7 +665,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000001"), &a); r = bn_format(&a, NULL, NULL, 6, 6, true, buf, sizeof(buf)); - ck_assert_int_eq(r, 8); + ck_assert_uint_eq(r, 8); ck_assert_str_eq(buf, "1.000000"); bn_read_be( @@ -672,7 +673,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000002"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "2"); bn_read_be( @@ -680,7 +681,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000005"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "5"); bn_read_be( @@ -688,7 +689,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000009"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "9"); bn_read_be( @@ -696,7 +697,7 @@ START_TEST(test_bignum_format) { "000000000000000000000000000000000000000000000000000000000000000a"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 2); + ck_assert_uint_eq(r, 2); ck_assert_str_eq(buf, "10"); bn_read_be( @@ -704,7 +705,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000014"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 2); + ck_assert_uint_eq(r, 2); ck_assert_str_eq(buf, "20"); bn_read_be( @@ -712,7 +713,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000032"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 2); + ck_assert_uint_eq(r, 2); ck_assert_str_eq(buf, "50"); bn_read_be( @@ -720,7 +721,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000063"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 2); + ck_assert_uint_eq(r, 2); ck_assert_str_eq(buf, "99"); bn_read_be( @@ -728,7 +729,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000000064"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 3); + ck_assert_uint_eq(r, 3); ck_assert_str_eq(buf, "100"); bn_read_be( @@ -736,7 +737,7 @@ START_TEST(test_bignum_format) { "00000000000000000000000000000000000000000000000000000000000000c8"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 3); + ck_assert_uint_eq(r, 3); ck_assert_str_eq(buf, "200"); bn_read_be( @@ -744,7 +745,7 @@ START_TEST(test_bignum_format) { "00000000000000000000000000000000000000000000000000000000000001f4"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 3); + ck_assert_uint_eq(r, 3); ck_assert_str_eq(buf, "500"); bn_read_be( @@ -752,7 +753,7 @@ START_TEST(test_bignum_format) { "00000000000000000000000000000000000000000000000000000000000003e7"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 3); + ck_assert_uint_eq(r, 3); ck_assert_str_eq(buf, "999"); bn_read_be( @@ -760,7 +761,7 @@ START_TEST(test_bignum_format) { "00000000000000000000000000000000000000000000000000000000000003e8"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 4); + ck_assert_uint_eq(r, 4); ck_assert_str_eq(buf, "1000"); bn_read_be( @@ -768,7 +769,7 @@ START_TEST(test_bignum_format) { "0000000000000000000000000000000000000000000000000000000000989680"), &a); r = bn_format(&a, NULL, NULL, 7, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 1); + ck_assert_uint_eq(r, 1); ck_assert_str_eq(buf, "1"); bn_read_be( @@ -776,7 +777,7 @@ START_TEST(test_bignum_format) { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), &a); r = bn_format(&a, NULL, NULL, 0, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 78); + ck_assert_uint_eq(r, 78); ck_assert_str_eq(buf, "11579208923731619542357098500868790785326998466564056403945" "7584007913129639935"); @@ -786,7 +787,7 @@ START_TEST(test_bignum_format) { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), &a); r = bn_format(&a, NULL, NULL, 1, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 79); + ck_assert_uint_eq(r, 79); ck_assert_str_eq(buf, "11579208923731619542357098500868790785326998466564056403945" "758400791312963993.5"); @@ -796,7 +797,7 @@ START_TEST(test_bignum_format) { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), &a); r = bn_format(&a, NULL, NULL, 2, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 79); + ck_assert_uint_eq(r, 79); ck_assert_str_eq(buf, "11579208923731619542357098500868790785326998466564056403945" "75840079131296399.35"); @@ -806,7 +807,7 @@ START_TEST(test_bignum_format) { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), &a); r = bn_format(&a, NULL, NULL, 8, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 79); + ck_assert_uint_eq(r, 79); ck_assert_str_eq(buf, "11579208923731619542357098500868790785326998466564056403945" "75840079131.29639935"); @@ -816,7 +817,7 @@ START_TEST(test_bignum_format) { "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffe3bbb00"), &a); r = bn_format(&a, NULL, NULL, 8, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 70); + ck_assert_uint_eq(r, 70); ck_assert_str_eq(buf, "11579208923731619542357098500868790785326998466564056403945" "75840079131"); @@ -826,7 +827,7 @@ START_TEST(test_bignum_format) { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), &a); r = bn_format(&a, NULL, NULL, 18, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 79); + ck_assert_uint_eq(r, 79); ck_assert_str_eq(buf, "11579208923731619542357098500868790785326998466564056403945" "7.584007913129639935"); @@ -836,7 +837,7 @@ START_TEST(test_bignum_format) { "fffffffffffffffffffffffffffffffffffffffffffffffff7e52fe5afe40000"), &a); r = bn_format(&a, NULL, NULL, 18, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 60); + ck_assert_uint_eq(r, 60); ck_assert_str_eq( buf, "115792089237316195423570985008687907853269984665640564039457"); @@ -845,7 +846,7 @@ START_TEST(test_bignum_format) { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), &a); r = bn_format(&a, NULL, NULL, 78, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 80); + ck_assert_uint_eq(r, 80); ck_assert_str_eq(buf, "0." "11579208923731619542357098500868790785326998466564056403945" @@ -856,7 +857,7 @@ START_TEST(test_bignum_format) { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), &a); r = bn_format(&a, NULL, NULL, 0, 10, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 88); + ck_assert_uint_eq(r, 88); ck_assert_str_eq(buf, "11579208923731619542357098500868790785326998466564056403945" "75840079131296399350000000000"); @@ -867,7 +868,7 @@ START_TEST(test_bignum_format) { &a); r = bn_format(&a, "quite a long prefix", "even longer suffix", 60, 0, false, buf, sizeof(buf)); - ck_assert_int_eq(r, 116); + ck_assert_uint_eq(r, 116); ck_assert_str_eq(buf, "quite a long " "prefix115792089237316195." @@ -881,11 +882,11 @@ START_TEST(test_bignum_format) { memset(buf, 'a', sizeof(buf)); r = bn_format(&a, "prefix", "suffix", 10, 0, false, buf, 31); ck_assert_str_eq(buf, "prefix8198552.9216486895suffix"); - ck_assert_int_eq(r, 30); + ck_assert_uint_eq(r, 30); memset(buf, 'a', sizeof(buf)); r = bn_format(&a, "prefix", "suffix", 10, 0, false, buf, 30); - ck_assert_int_eq(r, 0); + ck_assert_uint_eq(r, 0); ck_assert_str_eq(buf, ""); } END_TEST @@ -954,7 +955,7 @@ END_TEST // https://tools.ietf.org/html/rfc4648#section-10 START_TEST(test_base32_rfc4648) { - const struct { + static const struct { const char *decoded; const char *encoded; const char *encoded_lowercase; @@ -978,8 +979,8 @@ START_TEST(test_base32_rfc4648) { size_t inlen = strlen(in); size_t outlen = strlen(out); - ck_assert_int_eq(outlen, base32_encoded_length(inlen)); - ck_assert_int_eq(inlen, base32_decoded_length(outlen)); + ck_assert_uint_eq(outlen, base32_encoded_length(inlen)); + ck_assert_uint_eq(inlen, base32_decoded_length(outlen)); ck_assert(base32_encode((uint8_t *)in, inlen, buffer, sizeof(buffer), BASE32_ALPHABET_RFC4648) != NULL); @@ -1003,7 +1004,7 @@ END_TEST // from // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/base58_keys_valid.json START_TEST(test_base58) { - const char *base58_vector[] = { + static const char *base58_vector[] = { "0065a16059864a2fdbc7c99a4723a8395bc6f188eb", "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", "0574f209f6ea907e2ea48f74fae05782ae8a665257", @@ -1134,7 +1135,7 @@ END_TEST // Graphene Base85CheckEncoding START_TEST(test_base58gph) { - const char *base58_vector[] = { + static const char *base58_vector[] = { "02e649f63f8e8121345fd7f47d0d185a3ccaa843115cd2e9392dcd9b82263bc680", "6dumtt9swxCqwdPZBGXh9YmHoEjFFnNfwHaTqRbQTghGAY2gRz", "021c7359cd885c0e319924d97e3980206ad64387aff54908241125b3a88b55ca16", @@ -1188,7 +1189,7 @@ START_TEST(test_bignum_divmod) { i = 0; while (!bn_is_zero(&a) && i < 44) { bn_divmod58(&a, &r); - ck_assert_int_eq(r, ar[i]); + ck_assert_uint_eq(r, ar[i]); i++; } ck_assert_int_eq(i, 44); @@ -1205,7 +1206,7 @@ START_TEST(test_bignum_divmod) { i = 0; while (!bn_is_zero(&b) && i < 26) { bn_divmod1000(&b, &r); - ck_assert_int_eq(r, br[i]); + ck_assert_uint_eq(r, br[i]); i++; } ck_assert_int_eq(i, 26); @@ -1226,7 +1227,7 @@ START_TEST(test_bip32_vector_1) { // [Chain m] fingerprint = 0; - ck_assert_int_eq(fingerprint, 0x00000000); + ck_assert_uint_eq(fingerprint, 0x00000000); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1237,7 +1238,7 @@ START_TEST(test_bip32_vector_1) { fromhex( "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1251,8 +1252,7 @@ START_TEST(test_bip32_vector_1) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1268,7 +1268,7 @@ START_TEST(test_bip32_vector_1) { // [Chain m/0'] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd_prime(&node, 0); - ck_assert_int_eq(fingerprint, 0x3442193e); + ck_assert_uint_eq(fingerprint, 0x3442193e); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1279,7 +1279,7 @@ START_TEST(test_bip32_vector_1) { fromhex( "edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1293,7 +1293,7 @@ START_TEST(test_bip32_vector_1) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1309,7 +1309,7 @@ START_TEST(test_bip32_vector_1) { // [Chain m/0'/1] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 1); - ck_assert_int_eq(fingerprint, 0x5c1bd648); + ck_assert_uint_eq(fingerprint, 0x5c1bd648); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1320,7 +1320,7 @@ START_TEST(test_bip32_vector_1) { fromhex( "3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1334,7 +1334,7 @@ START_TEST(test_bip32_vector_1) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1350,7 +1350,7 @@ START_TEST(test_bip32_vector_1) { // [Chain m/0'/1/2'] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd_prime(&node, 2); - ck_assert_int_eq(fingerprint, 0xbef5a2f9); + ck_assert_uint_eq(fingerprint, 0xbef5a2f9); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1361,7 +1361,7 @@ START_TEST(test_bip32_vector_1) { fromhex( "cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1375,7 +1375,7 @@ START_TEST(test_bip32_vector_1) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1391,7 +1391,7 @@ START_TEST(test_bip32_vector_1) { // [Chain m/0'/1/2'/2] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 2); - ck_assert_int_eq(fingerprint, 0xee7ab90c); + ck_assert_uint_eq(fingerprint, 0xee7ab90c); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1402,7 +1402,7 @@ START_TEST(test_bip32_vector_1) { fromhex( "0f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1416,7 +1416,7 @@ START_TEST(test_bip32_vector_1) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1432,7 +1432,7 @@ START_TEST(test_bip32_vector_1) { // [Chain m/0'/1/2'/2/1000000000] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 1000000000); - ck_assert_int_eq(fingerprint, 0xd880d7d8); + ck_assert_uint_eq(fingerprint, 0xd880d7d8); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1443,7 +1443,7 @@ START_TEST(test_bip32_vector_1) { fromhex( "471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1457,7 +1457,7 @@ START_TEST(test_bip32_vector_1) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1489,7 +1489,7 @@ START_TEST(test_bip32_vector_2) { // [Chain m] fingerprint = 0; - ck_assert_int_eq(fingerprint, 0x00000000); + ck_assert_uint_eq(fingerprint, 0x00000000); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1500,7 +1500,7 @@ START_TEST(test_bip32_vector_2) { fromhex( "4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1514,7 +1514,7 @@ START_TEST(test_bip32_vector_2) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1531,7 +1531,7 @@ START_TEST(test_bip32_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 0); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0xbd16bee5); + ck_assert_uint_eq(fingerprint, 0xbd16bee5); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1542,7 +1542,7 @@ START_TEST(test_bip32_vector_2) { fromhex( "abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1556,7 +1556,7 @@ START_TEST(test_bip32_vector_2) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1573,7 +1573,7 @@ START_TEST(test_bip32_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 2147483647); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x5a61ff8e); + ck_assert_uint_eq(fingerprint, 0x5a61ff8e); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1584,7 +1584,7 @@ START_TEST(test_bip32_vector_2) { fromhex( "877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1598,7 +1598,7 @@ START_TEST(test_bip32_vector_2) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1615,7 +1615,7 @@ START_TEST(test_bip32_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 1); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0xd8ab4937); + ck_assert_uint_eq(fingerprint, 0xd8ab4937); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1626,7 +1626,7 @@ START_TEST(test_bip32_vector_2) { fromhex( "704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1640,7 +1640,7 @@ START_TEST(test_bip32_vector_2) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1657,7 +1657,7 @@ START_TEST(test_bip32_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 2147483646); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x78412e3a); + ck_assert_uint_eq(fingerprint, 0x78412e3a); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1668,7 +1668,7 @@ START_TEST(test_bip32_vector_2) { fromhex( "f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1682,7 +1682,7 @@ START_TEST(test_bip32_vector_2) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1699,7 +1699,7 @@ START_TEST(test_bip32_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 2); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x31a507b8); + ck_assert_uint_eq(fingerprint, 0x31a507b8); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1710,7 +1710,7 @@ START_TEST(test_bip32_vector_2) { fromhex( "bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1724,7 +1724,7 @@ START_TEST(test_bip32_vector_2) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1749,7 +1749,7 @@ START_TEST(test_bip32_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_public_ckd(&node, 0); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0xbd16bee5); + ck_assert_uint_eq(fingerprint, 0xbd16bee5); ck_assert_mem_eq( node.chain_code, fromhex( @@ -1760,7 +1760,7 @@ START_TEST(test_bip32_vector_2) { fromhex( "0000000000000000000000000000000000000000000000000000000000000000"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -1786,8 +1786,8 @@ START_TEST(test_bip32_vector_3) { // [Chain m] fingerprint = 0; - ck_assert_int_eq(fingerprint, 0x00000000); - hdnode_fill_public_key(&node); + ck_assert_uint_eq(fingerprint, 0x00000000); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, sizeof(str)); ck_assert_str_eq(str, @@ -1796,7 +1796,7 @@ START_TEST(test_bip32_vector_3) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1813,7 +1813,7 @@ START_TEST(test_bip32_vector_3) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 0); ck_assert_int_eq(r, 1); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, sizeof(str)); ck_assert_str_eq(str, @@ -1822,7 +1822,7 @@ START_TEST(test_bip32_vector_3) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1854,7 +1854,7 @@ START_TEST(test_bip32_vector_4) { // [Chain m] fingerprint = 0; ck_assert_int_eq(fingerprint, 0x00000000); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, sizeof(str)); ck_assert_str_eq(str, @@ -1863,7 +1863,7 @@ START_TEST(test_bip32_vector_4) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1880,7 +1880,7 @@ START_TEST(test_bip32_vector_4) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 0); ck_assert_int_eq(r, 1); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, sizeof(str)); ck_assert_str_eq(str, @@ -1889,7 +1889,7 @@ START_TEST(test_bip32_vector_4) { r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); ck_assert_str_eq(str, @@ -1901,6 +1901,32 @@ START_TEST(test_bip32_vector_4) { memcpy(&node3, &node, sizeof(HDNode)); memzero(&node3.private_key, 32); ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); + + // [Chain m/0'/1'] + fingerprint = hdnode_fingerprint(&node); + r = hdnode_private_ckd_prime(&node, 1); + ck_assert_int_eq(r, 1); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); + hdnode_serialize_private(&node, fingerprint, VERSION_PRIVATE, str, + sizeof(str)); + ck_assert_str_eq(str, + "xprv9xJocDuwtYCMNAo3Zw76WENQeAS6WGXQ55RCy7tDJ8oALr4FWkuVoHJ" + "eHVAcAqiZLE7Je3vZJHxspZdFHfnBEjHqU5hG1Jaj32dVoS6XLT1"); + r = hdnode_deserialize_private(str, VERSION_PRIVATE, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); + ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); + hdnode_serialize_public(&node, fingerprint, VERSION_PUBLIC, str, sizeof(str)); + ck_assert_str_eq(str, + "xpub6BJA1jSqiukeaesWfxe6sNK9CCGaujFFSJLomWHprUL9DePQ4JDkM5d" + "88n49sMGJxrhpjazuXYWdMf17C9T5XnxkopaeS7jGk1GyyVziaMt"); + r = hdnode_deserialize_public(str, VERSION_PUBLIC, SECP256K1_NAME, &node2, + NULL); + ck_assert_int_eq(r, 0); + memcpy(&node3, &node, sizeof(HDNode)); + memzero(&node3.private_key, 32); + ck_assert_mem_eq(&node2, &node3, sizeof(HDNode)); } END_TEST @@ -1917,20 +1943,20 @@ START_TEST(test_bip32_compare) { "301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788" "f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), 64, SECP256K1_NAME, &node2); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); for (i = 0; i < 100; i++) { memcpy(&node3, &node1, sizeof(HDNode)); - hdnode_fill_public_key(&node3); + ck_assert_int_eq(hdnode_fill_public_key(&node3), 0); r = hdnode_private_ckd(&node1, i); ck_assert_int_eq(r, 1); r = hdnode_public_ckd(&node2, i); ck_assert_int_eq(r, 1); r = hdnode_public_ckd(&node3, i); ck_assert_int_eq(r, 1); - ck_assert_int_eq(node1.depth, node2.depth); - ck_assert_int_eq(node1.depth, node3.depth); - ck_assert_int_eq(node1.child_num, node2.child_num); - ck_assert_int_eq(node1.child_num, node3.child_num); + ck_assert_uint_eq(node1.depth, node2.depth); + ck_assert_uint_eq(node1.depth, node3.depth); + ck_assert_uint_eq(node1.child_num, node2.child_num); + ck_assert_uint_eq(node1.child_num, node3.child_num); ck_assert_mem_eq(node1.chain_code, node2.chain_code, 32); ck_assert_mem_eq(node1.chain_code, node3.chain_code, 32); ck_assert_mem_eq( @@ -1943,7 +1969,7 @@ START_TEST(test_bip32_compare) { fromhex( "0000000000000000000000000000000000000000000000000000000000000000"), 32); - hdnode_fill_public_key(&node1); + ck_assert_int_eq(hdnode_fill_public_key(&node1), 0); ck_assert_mem_eq(node1.public_key, node2.public_key, 33); ck_assert_mem_eq(node1.public_key, node3.public_key, 33); } @@ -1953,7 +1979,7 @@ END_TEST START_TEST(test_bip32_optimized) { HDNode root; hdnode_from_seed((uint8_t *)"NothingToSeeHere", 16, SECP256K1_NAME, &root); - hdnode_fill_public_key(&root); + ck_assert_int_eq(hdnode_fill_public_key(&root), 0); curve_point pub; ecdsa_read_pubkey(&secp256k1, root.public_key, &pub); @@ -1965,7 +1991,7 @@ START_TEST(test_bip32_optimized) { // unoptimized memcpy(&node, &root, sizeof(HDNode)); hdnode_public_ckd(&node, i); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ecdsa_get_address(node.public_key, 0, HASHER_SHA2_RIPEMD, HASHER_SHA2D, addr1, sizeof(addr1)); // optimized @@ -1978,7 +2004,8 @@ START_TEST(test_bip32_optimized) { } END_TEST -#if USE_BIP32_CACHE // [wallet-core] +#if USE_BIP32_CACHE + START_TEST(test_bip32_cache_1) { HDNode node1, node2; int i, r; @@ -2112,7 +2139,7 @@ START_TEST(test_bip32_nist_seed) { fromhex( "7762f9729fed06121fd13f326884c82f59aa95c57ac492ce8c9654e60efd130c"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2136,7 +2163,7 @@ START_TEST(test_bip32_nist_seed) { fromhex( "0e49dc46ce1d8c29d9b80a05e40f5d0cd68cbf02ae98572186f5343be18084bf"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2155,7 +2182,7 @@ START_TEST(test_bip32_nist_vector_1) { // [Chain m] fingerprint = 0; - ck_assert_int_eq(fingerprint, 0x00000000); + ck_assert_uint_eq(fingerprint, 0x00000000); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2166,7 +2193,7 @@ START_TEST(test_bip32_nist_vector_1) { fromhex( "612091aaa12e22dd2abef664f8a01a82cae99ad7441b7ef8110424915c268bc2"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2176,7 +2203,7 @@ START_TEST(test_bip32_nist_vector_1) { // [Chain m/0'] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd_prime(&node, 0); - ck_assert_int_eq(fingerprint, 0xbe6105b5); + ck_assert_uint_eq(fingerprint, 0xbe6105b5); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2187,7 +2214,7 @@ START_TEST(test_bip32_nist_vector_1) { fromhex( "6939694369114c67917a182c59ddb8cafc3004e63ca5d3b84403ba8613debc0c"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2197,7 +2224,7 @@ START_TEST(test_bip32_nist_vector_1) { // [Chain m/0'/1] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 1); - ck_assert_int_eq(fingerprint, 0x9b02312f); + ck_assert_uint_eq(fingerprint, 0x9b02312f); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2208,7 +2235,7 @@ START_TEST(test_bip32_nist_vector_1) { fromhex( "284e9d38d07d21e4e281b645089a94f4cf5a5a81369acf151a1c3a57f18b2129"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2218,7 +2245,7 @@ START_TEST(test_bip32_nist_vector_1) { // [Chain m/0'/1/2'] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd_prime(&node, 2); - ck_assert_int_eq(fingerprint, 0xb98005c1); + ck_assert_uint_eq(fingerprint, 0xb98005c1); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2229,7 +2256,7 @@ START_TEST(test_bip32_nist_vector_1) { fromhex( "694596e8a54f252c960eb771a3c41e7e32496d03b954aeb90f61635b8e092aa7"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2239,7 +2266,7 @@ START_TEST(test_bip32_nist_vector_1) { // [Chain m/0'/1/2'/2] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 2); - ck_assert_int_eq(fingerprint, 0x0e9f3274); + ck_assert_uint_eq(fingerprint, 0x0e9f3274); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2250,7 +2277,7 @@ START_TEST(test_bip32_nist_vector_1) { fromhex( "5996c37fd3dd2679039b23ed6f70b506c6b56b3cb5e424681fb0fa64caf82aaa"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2260,7 +2287,7 @@ START_TEST(test_bip32_nist_vector_1) { // [Chain m/0'/1/2'/2/1000000000] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 1000000000); - ck_assert_int_eq(fingerprint, 0x8b2b5c4b); + ck_assert_uint_eq(fingerprint, 0x8b2b5c4b); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2271,7 +2298,7 @@ START_TEST(test_bip32_nist_vector_1) { fromhex( "21c4f269ef0a5fd1badf47eeacebeeaa3de22eb8e5b0adcd0f27dd99d34d0119"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2294,7 +2321,7 @@ START_TEST(test_bip32_nist_vector_2) { // [Chain m] fingerprint = 0; - ck_assert_int_eq(fingerprint, 0x00000000); + ck_assert_uint_eq(fingerprint, 0x00000000); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2305,7 +2332,7 @@ START_TEST(test_bip32_nist_vector_2) { fromhex( "eaa31c2e46ca2962227cf21d73a7ef0ce8b31c756897521eb6c7b39796633357"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2316,7 +2343,7 @@ START_TEST(test_bip32_nist_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 0); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x607f628f); + ck_assert_uint_eq(fingerprint, 0x607f628f); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2327,7 +2354,7 @@ START_TEST(test_bip32_nist_vector_2) { fromhex( "d7d065f63a62624888500cdb4f88b6d59c2927fee9e6d0cdff9cad555884df6e"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2338,7 +2365,7 @@ START_TEST(test_bip32_nist_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 2147483647); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x946d2a54); + ck_assert_uint_eq(fingerprint, 0x946d2a54); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2349,7 +2376,7 @@ START_TEST(test_bip32_nist_vector_2) { fromhex( "96d2ec9316746a75e7793684ed01e3d51194d81a42a3276858a5b7376d4b94b9"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2360,7 +2387,7 @@ START_TEST(test_bip32_nist_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 1); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x218182d8); + ck_assert_uint_eq(fingerprint, 0x218182d8); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2371,7 +2398,7 @@ START_TEST(test_bip32_nist_vector_2) { fromhex( "974f9096ea6873a915910e82b29d7c338542ccde39d2064d1cc228f371542bbc"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2382,7 +2409,7 @@ START_TEST(test_bip32_nist_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 2147483646); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x931223e4); + ck_assert_uint_eq(fingerprint, 0x931223e4); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2393,7 +2420,7 @@ START_TEST(test_bip32_nist_vector_2) { fromhex( "da29649bbfaff095cd43819eda9a7be74236539a29094cd8336b07ed8d4eff63"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2404,7 +2431,7 @@ START_TEST(test_bip32_nist_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 2); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x956c4629); + ck_assert_uint_eq(fingerprint, 0x956c4629); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2415,7 +2442,7 @@ START_TEST(test_bip32_nist_vector_2) { fromhex( "bb0a77ba01cc31d77205d51d08bd313b979a71ef4de9b062f8958297e746bd67"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2434,7 +2461,7 @@ START_TEST(test_bip32_nist_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_public_ckd(&node, 0); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x607f628f); + ck_assert_uint_eq(fingerprint, 0x607f628f); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2466,20 +2493,20 @@ START_TEST(test_bip32_nist_compare) { "301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788" "f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), 64, NIST256P1_NAME, &node2); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); for (i = 0; i < 100; i++) { memcpy(&node3, &node1, sizeof(HDNode)); - hdnode_fill_public_key(&node3); + ck_assert_int_eq(hdnode_fill_public_key(&node3), 0); r = hdnode_private_ckd(&node1, i); ck_assert_int_eq(r, 1); r = hdnode_public_ckd(&node2, i); ck_assert_int_eq(r, 1); r = hdnode_public_ckd(&node3, i); ck_assert_int_eq(r, 1); - ck_assert_int_eq(node1.depth, node2.depth); - ck_assert_int_eq(node1.depth, node3.depth); - ck_assert_int_eq(node1.child_num, node2.child_num); - ck_assert_int_eq(node1.child_num, node3.child_num); + ck_assert_uint_eq(node1.depth, node2.depth); + ck_assert_uint_eq(node1.depth, node3.depth); + ck_assert_uint_eq(node1.child_num, node2.child_num); + ck_assert_uint_eq(node1.child_num, node3.child_num); ck_assert_mem_eq(node1.chain_code, node2.chain_code, 32); ck_assert_mem_eq(node1.chain_code, node3.chain_code, 32); ck_assert_mem_eq( @@ -2492,7 +2519,7 @@ START_TEST(test_bip32_nist_compare) { fromhex( "0000000000000000000000000000000000000000000000000000000000000000"), 32); - hdnode_fill_public_key(&node1); + ck_assert_int_eq(hdnode_fill_public_key(&node1), 0); ck_assert_mem_eq(node1.public_key, node2.public_key, 33); ck_assert_mem_eq(node1.public_key, node3.public_key, 33); } @@ -2512,7 +2539,7 @@ START_TEST(test_bip32_nist_repeat) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 28578); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0xbe6105b5); + ck_assert_uint_eq(fingerprint, 0xbe6105b5); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2523,7 +2550,7 @@ START_TEST(test_bip32_nist_repeat) { fromhex( "06f0db126f023755d0b8d86d4591718a5210dd8d024e3e14b6159d63f53aa669"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2534,7 +2561,7 @@ START_TEST(test_bip32_nist_repeat) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node2, 33941); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x3e2b7bc6); + ck_assert_uint_eq(fingerprint, 0x3e2b7bc6); ck_assert_mem_eq( node2.chain_code, fromhex( @@ -2545,7 +2572,7 @@ START_TEST(test_bip32_nist_repeat) { fromhex( "092154eed4af83e078ff9b84322015aefe5769e31270f62c3f66c33888335f3a"), 32); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq( node2.public_key, fromhex( @@ -2556,13 +2583,13 @@ START_TEST(test_bip32_nist_repeat) { memzero(&node2.private_key, 32); r = hdnode_public_ckd(&node2, 33941); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x3e2b7bc6); + ck_assert_uint_eq(fingerprint, 0x3e2b7bc6); ck_assert_mem_eq( node2.chain_code, fromhex( "9e87fe95031f14736774cd82f25fd885065cb7c358c1edf813c72af535e83071"), 32); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq( node2.public_key, fromhex( @@ -2590,7 +2617,7 @@ START_TEST(test_bip32_ed25519_vector_1) { fromhex( "2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2609,7 +2636,7 @@ START_TEST(test_bip32_ed25519_vector_1) { fromhex( "68e0fe46dfb67e368c75379acec591dad19df3cde26e63b93a8e704f1dade7a3"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2628,7 +2655,7 @@ START_TEST(test_bip32_ed25519_vector_1) { fromhex( "b1d0bad404bf35da785a64ca1ac54b2617211d2777696fbffaf208f746ae84f2"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2647,7 +2674,7 @@ START_TEST(test_bip32_ed25519_vector_1) { fromhex( "92a5b23c0b8a99e37d07df3fb9966917f5d06e02ddbd909c7e184371463e9fc9"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2666,7 +2693,7 @@ START_TEST(test_bip32_ed25519_vector_1) { fromhex( "30d1dc7e5fc04c31219ab25a27ae00b50f6fd66622f6e9c913253d6511d1e662"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2685,7 +2712,7 @@ START_TEST(test_bip32_ed25519_vector_1) { fromhex( "8f94d394a8e8fd6b1bc2f3f49f5c47e385281d5c17e65324b0f62483e37e8793"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2717,7 +2744,7 @@ START_TEST(test_bip32_ed25519_vector_2) { fromhex( "171cb88b1b3c1db25add599712e36245d75bc65a1a5c9e18d76f9f2b1eab4012"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2737,7 +2764,7 @@ START_TEST(test_bip32_ed25519_vector_2) { fromhex( "1559eb2bbec5790b0c65d8693e4d0875b1747f4970ae8b650486ed7470845635"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2757,7 +2784,7 @@ START_TEST(test_bip32_ed25519_vector_2) { fromhex( "ea4f5bfe8694d8bb74b7b59404632fd5968b774ed545e810de9c32a4fb4192f4"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2777,7 +2804,7 @@ START_TEST(test_bip32_ed25519_vector_2) { fromhex( "3757c7577170179c7868353ada796c839135b3d30554bbb74a4b1e4a5a58505c"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2797,7 +2824,7 @@ START_TEST(test_bip32_ed25519_vector_2) { fromhex( "5837736c89570de861ebc173b1086da4f505d4adb387c6a1b1342d5e4ac9ec72"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2817,7 +2844,7 @@ START_TEST(test_bip32_ed25519_vector_2) { fromhex( "551d333177df541ad876a60ea71f00447931c0a9da16f227c11ea080d7391b8d"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2844,7 +2871,7 @@ START_TEST(test_bip32_decred_vector_1) { // [Chain m] fingerprint = 0; - ck_assert_int_eq(fingerprint, 0x00000000); + ck_assert_uint_eq(fingerprint, 0x00000000); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2855,7 +2882,7 @@ START_TEST(test_bip32_decred_vector_1) { fromhex( "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2870,7 +2897,7 @@ START_TEST(test_bip32_decred_vector_1) { SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -2887,7 +2914,7 @@ START_TEST(test_bip32_decred_vector_1) { // [Chain m/0'] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd_prime(&node, 0); - ck_assert_int_eq(fingerprint, 0xbc495588); + ck_assert_uint_eq(fingerprint, 0xbc495588); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2898,7 +2925,7 @@ START_TEST(test_bip32_decred_vector_1) { fromhex( "edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2912,7 +2939,7 @@ START_TEST(test_bip32_decred_vector_1) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -2929,7 +2956,7 @@ START_TEST(test_bip32_decred_vector_1) { // [Chain m/0'/1] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 1); - ck_assert_int_eq(fingerprint, 0xc67bc2ef); + ck_assert_uint_eq(fingerprint, 0xc67bc2ef); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2940,7 +2967,7 @@ START_TEST(test_bip32_decred_vector_1) { fromhex( "3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2954,7 +2981,7 @@ START_TEST(test_bip32_decred_vector_1) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -2971,7 +2998,7 @@ START_TEST(test_bip32_decred_vector_1) { // [Chain m/0'/1/2'] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd_prime(&node, 2); - ck_assert_int_eq(fingerprint, 0xe7072187); + ck_assert_uint_eq(fingerprint, 0xe7072187); ck_assert_mem_eq( node.chain_code, fromhex( @@ -2982,7 +3009,7 @@ START_TEST(test_bip32_decred_vector_1) { fromhex( "cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -2996,7 +3023,7 @@ START_TEST(test_bip32_decred_vector_1) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3013,7 +3040,7 @@ START_TEST(test_bip32_decred_vector_1) { // [Chain m/0'/1/2'/2] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 2); - ck_assert_int_eq(fingerprint, 0xbcbbc1c4); + ck_assert_uint_eq(fingerprint, 0xbcbbc1c4); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3024,7 +3051,7 @@ START_TEST(test_bip32_decred_vector_1) { fromhex( "0f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -3038,7 +3065,7 @@ START_TEST(test_bip32_decred_vector_1) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3055,7 +3082,7 @@ START_TEST(test_bip32_decred_vector_1) { // [Chain m/0'/1/2'/2/1000000000] fingerprint = hdnode_fingerprint(&node); hdnode_private_ckd(&node, 1000000000); - ck_assert_int_eq(fingerprint, 0xe58b52e4); + ck_assert_uint_eq(fingerprint, 0xe58b52e4); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3066,7 +3093,7 @@ START_TEST(test_bip32_decred_vector_1) { fromhex( "471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -3080,7 +3107,7 @@ START_TEST(test_bip32_decred_vector_1) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3117,7 +3144,7 @@ START_TEST(test_bip32_decred_vector_2) { // [Chain m] fingerprint = 0; - ck_assert_int_eq(fingerprint, 0x00000000); + ck_assert_uint_eq(fingerprint, 0x00000000); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3128,7 +3155,7 @@ START_TEST(test_bip32_decred_vector_2) { fromhex( "4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -3142,7 +3169,7 @@ START_TEST(test_bip32_decred_vector_2) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3160,7 +3187,7 @@ START_TEST(test_bip32_decred_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 0); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x2524c9d3); + ck_assert_uint_eq(fingerprint, 0x2524c9d3); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3171,7 +3198,7 @@ START_TEST(test_bip32_decred_vector_2) { fromhex( "abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -3185,7 +3212,7 @@ START_TEST(test_bip32_decred_vector_2) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3203,7 +3230,7 @@ START_TEST(test_bip32_decred_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 2147483647); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x6035c6ad); + ck_assert_uint_eq(fingerprint, 0x6035c6ad); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3214,7 +3241,7 @@ START_TEST(test_bip32_decred_vector_2) { fromhex( "877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -3228,7 +3255,7 @@ START_TEST(test_bip32_decred_vector_2) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3246,7 +3273,7 @@ START_TEST(test_bip32_decred_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 1); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x36fc7080); + ck_assert_uint_eq(fingerprint, 0x36fc7080); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3257,7 +3284,7 @@ START_TEST(test_bip32_decred_vector_2) { fromhex( "704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -3271,7 +3298,7 @@ START_TEST(test_bip32_decred_vector_2) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3289,7 +3316,7 @@ START_TEST(test_bip32_decred_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd_prime(&node, 2147483646); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x45309b4c); + ck_assert_uint_eq(fingerprint, 0x45309b4c); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3300,7 +3327,7 @@ START_TEST(test_bip32_decred_vector_2) { fromhex( "f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -3314,7 +3341,7 @@ START_TEST(test_bip32_decred_vector_2) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3332,7 +3359,7 @@ START_TEST(test_bip32_decred_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_private_ckd(&node, 2); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x3491a5e6); + ck_assert_uint_eq(fingerprint, 0x3491a5e6); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3343,7 +3370,7 @@ START_TEST(test_bip32_decred_vector_2) { fromhex( "bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key, fromhex( @@ -3357,7 +3384,7 @@ START_TEST(test_bip32_decred_vector_2) { r = hdnode_deserialize_private(str, DECRED_VERSION_PRIVATE, SECP256K1_DECRED_NAME, &node2, NULL); ck_assert_int_eq(r, 0); - hdnode_fill_public_key(&node2); + ck_assert_int_eq(hdnode_fill_public_key(&node2), 0); ck_assert_mem_eq(&node, &node2, sizeof(HDNode)); hdnode_serialize_public(&node, fingerprint, DECRED_VERSION_PUBLIC, str, sizeof(str)); @@ -3382,7 +3409,7 @@ START_TEST(test_bip32_decred_vector_2) { fingerprint = hdnode_fingerprint(&node); r = hdnode_public_ckd(&node, 0); ck_assert_int_eq(r, 1); - ck_assert_int_eq(fingerprint, 0x6a19cfb3); + ck_assert_uint_eq(fingerprint, 0x6a19cfb3); ck_assert_mem_eq( node.chain_code, fromhex( @@ -3405,33 +3432,79 @@ START_TEST(test_bip32_decred_vector_2) { } END_TEST -START_TEST(test_ecdsa_signature) { - int res; - uint8_t digest[32]; - uint8_t pubkey[65]; - uint8_t sig[64]; +static void test_ecdsa_get_public_key33_helper(int (*ecdsa_get_public_key33_fn)( + const ecdsa_curve *, const uint8_t *, uint8_t *)) { + uint8_t privkey[32] = {0}; + uint8_t pubkey[65] = {0}; const ecdsa_curve *curve = &secp256k1; + int res = 0; - // Signature verification for a digest which is equal to the group order. - // https://github.com/trezor/trezor-firmware/pull/1374 memcpy( + privkey, + fromhex( + "c46f5b217f04ff28886a89d3c762ed84e5fa318d1c9a635d541131e69f1f49f5"), + 32); + res = ecdsa_get_public_key33_fn(curve, privkey, pubkey); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq( pubkey, fromhex( - "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179848" - "3ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"), - sizeof(pubkey)); + "0232b062e9153f573c220b1be0299d6447e81577274bf11a7c08dff71384c6b6ec"), + 33); + memcpy( - digest, + privkey, fromhex( - "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"), - sizeof(digest)); - memcpy(sig, - fromhex( - "a0b37f8fba683cc68f6574cd43b39f0343a50008bf6ccea9d13231d9e7e2e1e41" - "1edc8d307254296264aebfc3dc76cd8b668373a072fd64665b50000e9fcce52"), - sizeof(sig)); - res = ecdsa_verify_digest(curve, pubkey, sig, digest); + "3b90a4de80fb00d77795762c389d1279d4b4ab5992ae3cde6bc12ca63116f74c"), + 32); + res = ecdsa_get_public_key33_fn(curve, privkey, pubkey); ck_assert_int_eq(res, 0); + ck_assert_mem_eq( + pubkey, + fromhex( + "0332b062e9153f573c220b1be0299d6447e81577274bf11a7c08dff71384c6b6ec"), + 33); +} + +START_TEST(test_ecdsa_get_public_key33) { + test_ecdsa_get_public_key33_helper(ecdsa_get_public_key33); +} +END_TEST + +static void test_ecdsa_get_public_key65_helper(int (*ecdsa_get_public_key65_fn)( + const ecdsa_curve *, const uint8_t *, uint8_t *)) { + uint8_t privkey[32] = {0}; + uint8_t pubkey[65] = {0}; + const ecdsa_curve *curve = &secp256k1; + int res = 0; + + memcpy( + privkey, + fromhex( + "c46f5b217f04ff28886a89d3c762ed84e5fa318d1c9a635d541131e69f1f49f5"), + 32); + res = ecdsa_get_public_key65_fn(curve, privkey, pubkey); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq( + pubkey, + fromhex( + "0432b062e9153f573c220b1be0299d6447e81577274bf11a7c08dff71384c6b6ec" + "179ca56b637a57e0fcd28cefa10c9433dc30532682647f4daa053d43d5cc960a"), + 65); +} + +START_TEST(test_ecdsa_get_public_key65) { + test_ecdsa_get_public_key65_helper(ecdsa_get_public_key65); +} +END_TEST + +static void test_ecdsa_recover_pub_from_sig_helper(int ( + *ecdsa_recover_pub_from_sig_fn)(const ecdsa_curve *, uint8_t *, + const uint8_t *, const uint8_t *, int)) { + int res; + uint8_t digest[32]; + uint8_t pubkey[65]; + const ecdsa_curve *curve = &secp256k1; // sha2(sha2("\x18Bitcoin Signed Message:\n\x0cHello World!")) memcpy( @@ -3440,7 +3513,7 @@ START_TEST(test_ecdsa_signature) { "de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb11432"), 32); // r = 2: Four points should exist - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000020123" @@ -3453,7 +3526,7 @@ START_TEST(test_ecdsa_signature) { "043fc5bf5fec35b6ffe6fd246226d312742a8c296bfa57dd22da509a2e348529b7dd" "b9faf8afe1ecda3c05e7b2bda47ee1f5a87e952742b22afca560b29d972fcf"), 65); - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000020123" @@ -3466,7 +3539,7 @@ START_TEST(test_ecdsa_signature) { "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed809032" "9274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0"), 65); - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000020123" @@ -3479,7 +3552,7 @@ START_TEST(test_ecdsa_signature) { "04cee0e740f41aab39156844afef0182dea2a8026885b10454a2d539df6f6df9023a" "bfcb0f01c50bef3c0fa8e59a998d07441e18b1c60583ef75cc8b912fb21a15"), 65); - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000020123" @@ -3492,6 +3565,14 @@ START_TEST(test_ecdsa_signature) { "0490d2bd2e9a564d6e1d8324fc6ad00aa4ae597684ecf4abea58bdfe7287ea4fa729" "68c2e5b0b40999ede3d7898d94e82c3f8dc4536a567a4bd45998c826a4c4b2"), 65); + // The point at infinity is not considered to be a valid public key. + res = ecdsa_recover_pub_from_sig_fn( + curve, pubkey, + fromhex( + "220cf4c7b6d568f2256a8c30cc1784a625a28c3627dac404aa9a9ecd08314ec81a88" + "828f20d69d102bab5de5f6ee7ef040cb0ff7b8e1ba3f29d79efb5250f47d"), + digest, 0); + ck_assert_int_eq(res, 1); memcpy( digest, @@ -3499,7 +3580,7 @@ START_TEST(test_ecdsa_signature) { "0000000000000000000000000000000000000000000000000000000000000000"), 32); // r = 7: No point P with P.x = 7, but P.x = (order + 7) exists - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000070123" @@ -3512,7 +3593,7 @@ START_TEST(test_ecdsa_signature) { "044d81bb47a31ffc6cf1f780ecb1e201ec47214b651650867c07f13ad06e12a1b040" "de78f8dbda700f4d3cd7ee21b3651a74c7661809699d2be7ea0992b0d39797"), 65); - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000070123" @@ -3525,7 +3606,7 @@ START_TEST(test_ecdsa_signature) { "044d81bb47a31ffc6cf1f780ecb1e201ec47214b651650867c07f13ad06e12a1b0bf" "21870724258ff0b2c32811de4c9ae58b3899e7f69662d41815f66c4f2c6498"), 65); - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000070123" @@ -3539,7 +3620,7 @@ START_TEST(test_ecdsa_signature) { "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), 32); // r = 1: Two points P with P.x = 1, but P.x = (order + 7) doesn't exist - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000010123" @@ -3552,7 +3633,7 @@ START_TEST(test_ecdsa_signature) { "045d330b2f89dbfca149828277bae852dd4aebfe136982cb531a88e9e7a89463fe71" "519f34ea8feb9490c707f14bc38c9ece51762bfd034ea014719b7c85d2871b"), 65); - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000010123" @@ -3567,14 +3648,14 @@ START_TEST(test_ecdsa_signature) { 65); // r = 0 is always invalid - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000010123" "456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), digest, 2); ck_assert_int_eq(res, 1); - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000000123" @@ -3582,7 +3663,7 @@ START_TEST(test_ecdsa_signature) { digest, 0); ck_assert_int_eq(res, 1); // r >= order is always invalid - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd03641410123" @@ -3590,7 +3671,7 @@ START_TEST(test_ecdsa_signature) { digest, 0); ck_assert_int_eq(res, 1); // check that overflow of r is handled - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "000000000000000000000000000000014551231950B75FC4402DA1722FC9BAEE0123" @@ -3598,7 +3679,7 @@ START_TEST(test_ecdsa_signature) { digest, 2); ck_assert_int_eq(res, 1); // s = 0 is always invalid - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "00000000000000000000000000000000000000000000000000000000000000020000" @@ -3606,7 +3687,7 @@ START_TEST(test_ecdsa_signature) { digest, 0); ck_assert_int_eq(res, 1); // s >= order is always invalid - res = ecdsa_recover_pub_from_sig( + res = ecdsa_recover_pub_from_sig_fn( curve, pubkey, fromhex( "0000000000000000000000000000000000000000000000000000000000000002ffff" @@ -3614,12 +3695,51 @@ START_TEST(test_ecdsa_signature) { digest, 0); ck_assert_int_eq(res, 1); } + +START_TEST(test_ecdsa_recover_pub_from_sig) { + test_ecdsa_recover_pub_from_sig_helper(ecdsa_recover_pub_from_sig); +} +END_TEST + +static void test_ecdsa_verify_digest_helper(int (*ecdsa_verify_digest_fn)( + const ecdsa_curve *, const uint8_t *, const uint8_t *, const uint8_t *)) { + int res; + uint8_t digest[32]; + uint8_t pubkey[65]; + uint8_t sig[64]; + const ecdsa_curve *curve = &secp256k1; + + // Signature verification for a digest which is equal to the group order. + // https://github.com/trezor/trezor-firmware/pull/1374 + memcpy( + pubkey, + fromhex( + "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179848" + "3ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"), + sizeof(pubkey)); + memcpy( + digest, + fromhex( + "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"), + sizeof(digest)); + memcpy(sig, + fromhex( + "a0b37f8fba683cc68f6574cd43b39f0343a50008bf6ccea9d13231d9e7e2e1e41" + "1edc8d307254296264aebfc3dc76cd8b668373a072fd64665b50000e9fcce52"), + sizeof(sig)); + res = ecdsa_verify_digest_fn(curve, pubkey, sig, digest); + ck_assert_int_eq(res, 0); +} + +START_TEST(test_ecdsa_verify_digest) { + test_ecdsa_verify_digest_helper(ecdsa_verify_digest); +} END_TEST #define test_deterministic(KEY, MSG, K) \ do { \ sha256_Raw((uint8_t *)MSG, strlen(MSG), buf); \ - init_rfc6979(fromhex(KEY), buf, &rng); \ + init_rfc6979(fromhex(KEY), buf, NULL, &rng); \ generate_k_rfc6979(&k, &rng); \ bn_write_be(&k, buf); \ ck_assert_mem_eq(buf, fromhex(K), 32); \ @@ -3664,6 +3784,49 @@ START_TEST(test_rfc6979) { } END_TEST +static void test_ecdsa_sign_digest_deterministic_helper( + int (*ecdsa_sign_digest_fn)(const ecdsa_curve *, const uint8_t *, + const uint8_t *, uint8_t *, uint8_t *, + int (*)(uint8_t by, uint8_t sig[64]))) { + static struct { + const char *priv_key; + const char *digest; + const char *sig; + } tests[] = { + {"312155017c70a204106e034520e0cdf17b3e54516e2ece38e38e38e38e38e38e", + "ffffffffffffffffffffffffffffffff20202020202020202020202020202020", + "e3d70248ea2fc771fc8d5e62d76b9cfd5402c96990333549eaadce1ae9f737eb" + "5cfbdc7d1e0ec18cc9b57bbb18f0a57dc929ec3c4dfac9073c581705015f6a8a"}, + {"312155017c70a204106e034520e0cdf17b3e54516e2ece38e38e38e38e38e38e", + "2020202020202020202020202020202020202020202020202020202020202020", + "40666188895430715552a7e4c6b53851f37a93030fb94e043850921242db78e8" + "75aa2ac9fd7e5a19402973e60e64382cdc29a09ebf6cb37e92f23be5b9251aee"}, + }; + + const ecdsa_curve *curve = &secp256k1; + uint8_t priv_key[32] = {0}; + uint8_t digest[32] = {0}; + uint8_t expected_sig[64] = {0}; + uint8_t computed_sig[64] = {0}; + int res = 0; + + for (size_t i = 0; i < sizeof(tests) / sizeof(*tests); i++) { + memcpy(priv_key, fromhex(tests[i].priv_key), 32); + memcpy(digest, fromhex(tests[i].digest), 32); + memcpy(expected_sig, fromhex(tests[i].sig), 64); + + res = + ecdsa_sign_digest_fn(curve, priv_key, digest, computed_sig, NULL, NULL); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq(expected_sig, computed_sig, 64); + } +} + +START_TEST(test_ecdsa_sign_digest_deterministic) { + test_ecdsa_sign_digest_deterministic_helper(ecdsa_sign_digest); +} +END_TEST + // test vectors from // http://www.inconteam.com/software-development/41-encryption/55-aes-test-vectors START_TEST(test_aes) { @@ -3673,7 +3836,7 @@ START_TEST(test_aes) { const char **ivp, **plainp, **cipherp; // ECB - const char *ecb_vector[] = { + static const char *ecb_vector[] = { // plain cipher "6bc1bee22e409f96e93d7e117393172a", "f3eed1bdb5d2a03c064b5a7e3db181f8", @@ -3710,7 +3873,7 @@ START_TEST(test_aes) { } // CBC - const char *cbc_vector[] = { + static const char *cbc_vector[] = { // iv plain cipher "000102030405060708090A0B0C0D0E0F", "6bc1bee22e409f96e93d7e117393172a", @@ -3756,7 +3919,7 @@ START_TEST(test_aes) { } // CFB - const char *cfb_vector[] = { + static const char *cfb_vector[] = { "000102030405060708090A0B0C0D0E0F", "6bc1bee22e409f96e93d7e117393172a", "DC7E84BFDA79164B7ECD8486985D3860", @@ -3801,7 +3964,7 @@ START_TEST(test_aes) { } // OFB - const char *ofb_vector[] = { + static const char *ofb_vector[] = { "000102030405060708090A0B0C0D0E0F", "6bc1bee22e409f96e93d7e117393172a", "dc7e84bfda79164b7ecd8486985d3860", @@ -3846,7 +4009,7 @@ START_TEST(test_aes) { } // CTR - const char *ctr_vector[] = { + static const char *ctr_vector[] = { // plain cipher "6bc1bee22e409f96e93d7e117393172a", "601ec313775789a5b7a7f504bbf3d228", @@ -4171,84 +4334,101 @@ END_TEST // test vectors from http://www.di-mgt.com.au/sha_testvectors.html START_TEST(test_sha3_256) { - uint8_t digest[SHA3_256_DIGEST_LENGTH]; - - sha3_256((uint8_t *)"", 0, digest); - ck_assert_mem_eq( - digest, - fromhex( - "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a"), - SHA3_256_DIGEST_LENGTH); - - sha3_256((uint8_t *)"abc", 3, digest); - ck_assert_mem_eq( - digest, - fromhex( - "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"), - SHA3_256_DIGEST_LENGTH); - - sha3_256( - (uint8_t *)"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56, - digest); - ck_assert_mem_eq( - digest, - fromhex( - "41c0dba2a9d6240849100376a8235e2c82e1b9998a999e21db32dd97496d3376"), - SHA3_256_DIGEST_LENGTH); + static const struct { + const char *data; + const char *hash; + } tests[] = { + { + "", + "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a", + }, + { + "abc", + "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532", + }, + { + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "41c0dba2a9d6240849100376a8235e2c82e1b9998a999e21db32dd97496d3376", + }, + { + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijkl" + "mnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", + "916f6061fe879741ca6469b43971dfdb28b1a32dc36cb3254e812be27aad1d18", + }, + }; - sha3_256((uint8_t *)"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", 112, digest); - ck_assert_mem_eq( - digest, - fromhex( - "916f6061fe879741ca6469b43971dfdb28b1a32dc36cb3254e812be27aad1d18"), - SHA3_256_DIGEST_LENGTH); + uint8_t digest[SHA3_256_DIGEST_LENGTH]; + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t len = strlen(tests[i].data); + sha3_256((uint8_t *)tests[i].data, len, digest); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), SHA3_256_DIGEST_LENGTH); + + // Test progressive hashing. + size_t part_len = len; + SHA3_CTX ctx; + sha3_256_Init(&ctx); + sha3_Update(&ctx, (uint8_t *)tests[i].data, part_len); + sha3_Update(&ctx, NULL, 0); + sha3_Update(&ctx, (uint8_t *)tests[i].data + part_len, len - part_len); + sha3_Final(&ctx, digest); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), SHA3_256_DIGEST_LENGTH); + } } END_TEST // test vectors from http://www.di-mgt.com.au/sha_testvectors.html START_TEST(test_sha3_512) { - uint8_t digest[SHA3_512_DIGEST_LENGTH]; - - sha3_512((uint8_t *)"", 0, digest); - ck_assert_mem_eq( - digest, - fromhex( + static const struct { + const char *data; + const char *hash; + } tests[] = { + { + "", "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2" - "123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26"), - SHA3_512_DIGEST_LENGTH); - - sha3_512((uint8_t *)"abc", 3, digest); - ck_assert_mem_eq( - digest, - fromhex( + "123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26", + }, + { + "abc", "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e1" - "16e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0"), - SHA3_512_DIGEST_LENGTH); - - sha3_512( - (uint8_t *)"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56, - digest); - ck_assert_mem_eq( - digest, - fromhex( + "16e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0", + }, + { + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "04a371e84ecfb5b8b77cb48610fca8182dd457ce6f326a0fd3d7ec2f1e91636dee69" - "1fbe0c985302ba1b0d8dc78c086346b533b49c030d99a27daf1139d6e75e"), - SHA3_512_DIGEST_LENGTH); - - sha3_512((uint8_t *)"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", 112, digest); - ck_assert_mem_eq( - digest, - fromhex( + "1fbe0c985302ba1b0d8dc78c086346b533b49c030d99a27daf1139d6e75e", + }, + { + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijkl" + "mnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", "afebb2ef542e6579c50cad06d2e578f9f8dd6881d7dc824d26360feebf18a4fa73e3" - "261122948efcfd492e74e82e2189ed0fb440d187f382270cb455f21dd185"), - SHA3_512_DIGEST_LENGTH); + "261122948efcfd492e74e82e2189ed0fb440d187f382270cb455f21dd185", + }, + }; + + uint8_t digest[SHA3_512_DIGEST_LENGTH]; + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t len = strlen(tests[i].data); + sha3_512((uint8_t *)tests[i].data, len, digest); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), SHA3_512_DIGEST_LENGTH); + + // Test progressive hashing. + size_t part_len = len; + SHA3_CTX ctx; + sha3_512_Init(&ctx); + sha3_Update(&ctx, (const uint8_t *)tests[i].data, part_len); + sha3_Update(&ctx, NULL, 0); + sha3_Update(&ctx, (const uint8_t *)tests[i].data + part_len, + len - part_len); + sha3_Final(&ctx, digest); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), SHA3_512_DIGEST_LENGTH); + } } END_TEST // test vectors from // https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/0.test-sha3-256.dat START_TEST(test_keccak_256) { - const struct { + static const struct { const char *hash; size_t length; const char *data; @@ -4490,6 +4670,17 @@ START_TEST(test_keccak_256) { for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { keccak_256(fromhex(tests[i].data), tests[i].length, hash); ck_assert_mem_eq(hash, fromhex(tests[i].hash), SHA3_256_DIGEST_LENGTH); + + // Test progressive hashing. + size_t part_len = tests[i].length / 2; + SHA3_CTX ctx = {0}; + keccak_256_Init(&ctx); + keccak_Update(&ctx, fromhex(tests[i].data), part_len); + keccak_Update(&ctx, fromhex(tests[i].data), 0); + keccak_Update(&ctx, fromhex(tests[i].data) + part_len, + tests[i].length - part_len); + keccak_Final(&ctx, hash); + ck_assert_mem_eq(hash, fromhex(tests[i].hash), SHA3_256_DIGEST_LENGTH); } } END_TEST @@ -4497,7 +4688,7 @@ END_TEST // test vectors from // https://raw.githubusercontent.com/monero-project/monero/master/tests/hash/tests-extra-blake.txt START_TEST(test_blake256) { - struct { + static const struct { const char *hash; const char *data; } tests[] = { @@ -4586,7 +4777,18 @@ START_TEST(test_blake256) { uint8_t hash[BLAKE256_DIGEST_LENGTH]; for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { - blake256(fromhex(tests[i].data), i, hash); + size_t len = strlen(tests[i].data) / 2; + blake256(fromhex(tests[i].data), len, hash); + ck_assert_mem_eq(hash, fromhex(tests[i].hash), BLAKE256_DIGEST_LENGTH); + + // Test progressive hashing. + size_t part_len = len / 2; + BLAKE256_CTX ctx; + blake256_Init(&ctx); + blake256_Update(&ctx, fromhex(tests[i].data), part_len); + blake256_Update(&ctx, NULL, 0); + blake256_Update(&ctx, fromhex(tests[i].data) + part_len, len - part_len); + blake256_Final(&ctx, hash); ck_assert_mem_eq(hash, fromhex(tests[i].hash), BLAKE256_DIGEST_LENGTH); } } @@ -4595,6 +4797,36 @@ END_TEST // test vectors from // https://raw.githubusercontent.com/BLAKE2/BLAKE2/master/testvectors/blake2b-kat.txt START_TEST(test_blake2b) { + static const struct { + const char *msg; + const char *hash; + } tests[] = { + { + "", + "10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e9" + "96e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568", + }, + { + "000102", + "33d0825dddf7ada99b0e7e307104ad07ca9cfd9692214f1561356315e784f3e5a17e" + "364ae9dbb14cb2036df932b77f4b292761365fb328de7afdc6d8998f5fc1", + }, + { + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021" + "22232425262728292a2b2c2d2e2f3031323334353637", + "f8f3726ac5a26cc80132493a6fedcb0e60760c09cfc84cad178175986819665e7684" + "2d7b9fedf76dddebf5d3f56faaad4477587af21606d396ae570d8e719af2", + }, + { + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021" + "22232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40414243" + "4445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465" + "666768696a6b6c6d6e6f", + "227e3aed8d2cb10b918fcb04f9de3e6d0a57e08476d93759cd7b2ed54a1cbf0239c5" + "28fb04bbf288253e601d3bc38b21794afef90b17094a182cac557745e75f", + }, + }; + uint8_t key[BLAKE2B_KEY_LENGTH]; memcpy(key, fromhex( @@ -4603,54 +4835,111 @@ START_TEST(test_blake2b) { BLAKE2B_KEY_LENGTH); uint8_t digest[BLAKE2B_DIGEST_LENGTH]; + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t msg_len = strlen(tests[i].msg) / 2; + blake2b_Key(fromhex(tests[i].msg), msg_len, key, sizeof(key), digest, + sizeof(digest)); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), sizeof(digest)); + + // Test progressive hashing. + size_t part_len = msg_len / 2; + BLAKE2B_CTX ctx; + ck_assert_int_eq(blake2b_InitKey(&ctx, sizeof(digest), key, sizeof(key)), + 0); + ck_assert_int_eq(blake2b_Update(&ctx, fromhex(tests[i].msg), part_len), 0); + ck_assert_int_eq(blake2b_Update(&ctx, NULL, 0), 0); + ck_assert_int_eq(blake2b_Update(&ctx, fromhex(tests[i].msg) + part_len, + msg_len - part_len), + 0); + ck_assert_int_eq(blake2b_Final(&ctx, digest, sizeof(digest)), 0); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), BLAKE2B_DIGEST_LENGTH); + } +} +END_TEST - blake2b_Key((uint8_t *)"", 0, key, BLAKE2B_KEY_LENGTH, digest, - BLAKE2B_DIGEST_LENGTH); - ck_assert_mem_eq( - digest, - fromhex( - "10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e9" - "96e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568"), - BLAKE2B_DIGEST_LENGTH); - - blake2b_Key(fromhex("000102"), 3, key, BLAKE2B_KEY_LENGTH, digest, - BLAKE2B_DIGEST_LENGTH); - ck_assert_mem_eq( - digest, - fromhex( - "33d0825dddf7ada99b0e7e307104ad07ca9cfd9692214f1561356315e784f3e5a17e" - "364ae9dbb14cb2036df932b77f4b292761365fb328de7afdc6d8998f5fc1"), - BLAKE2B_DIGEST_LENGTH); +// Blake2b-256 personalized, a la ZCash +// Test vectors from https://zips.z.cash/zip-0243 +START_TEST(test_blake2bp) { + static const struct { + const char *msg; + const char *personal; + const char *hash; + } tests[] = { + { + "", + "ZcashPrevoutHash", + "d53a633bbecf82fe9e9484d8a0e727c73bb9e68c96e72dec30144f6a84afa136", + }, + { + "", + "ZcashSequencHash", + "a5f25f01959361ee6eb56a7401210ee268226f6ce764a4f10b7f29e54db37272", - blake2b_Key( - fromhex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" - "202122232425262728292a2b2c2d2e2f3031323334353637"), - 56, key, BLAKE2B_KEY_LENGTH, digest, BLAKE2B_DIGEST_LENGTH); - ck_assert_mem_eq( - digest, - fromhex( - "f8f3726ac5a26cc80132493a6fedcb0e60760c09cfc84cad178175986819665e7684" - "2d7b9fedf76dddebf5d3f56faaad4477587af21606d396ae570d8e719af2"), - BLAKE2B_DIGEST_LENGTH); + }, + { + "e7719811893e0000095200ac6551ac636565b2835a0805750200025151", + "ZcashOutputsHash", + "ab6f7f6c5ad6b56357b5f37e16981723db6c32411753e28c175e15589172194a", + }, + { + "0bbe32a598c22adfb48cef72ba5d4287c0cefbacfd8ce195b4963c34a94bba7a1" + "75dae4b090f47a068e227433f9e49d3aa09e356d8d66d0c0121e91a3c4aa3f27fa1b" + "63396e2b41d", + "ZcashPrevoutHash", + "cacf0f5210cce5fa65a59f314292b3111d299e7d9d582753cf61e1e408552ae4", + }}; - blake2b_Key( - fromhex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" - "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" - "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f" - "606162636465666768696a6b6c6d6e6f"), - 112, key, BLAKE2B_KEY_LENGTH, digest, BLAKE2B_DIGEST_LENGTH); - ck_assert_mem_eq( - digest, - fromhex( - "227e3aed8d2cb10b918fcb04f9de3e6d0a57e08476d93759cd7b2ed54a1cbf0239c5" - "28fb04bbf288253e601d3bc38b21794afef90b17094a182cac557745e75f"), - BLAKE2B_DIGEST_LENGTH); + uint8_t digest[32]; + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t msg_len = strlen(tests[i].msg) / 2; + + // Test progressive hashing. + size_t part_len = msg_len / 2; + BLAKE2B_CTX ctx; + ck_assert_int_eq( + blake2b_InitPersonal(&ctx, sizeof(digest), tests[i].personal, + strlen(tests[i].personal)), + 0); + ck_assert_int_eq(blake2b_Update(&ctx, fromhex(tests[i].msg), part_len), 0); + ck_assert_int_eq(blake2b_Update(&ctx, NULL, 0), 0); + ck_assert_int_eq(blake2b_Update(&ctx, fromhex(tests[i].msg) + part_len, + msg_len - part_len), + 0); + ck_assert_int_eq(blake2b_Final(&ctx, digest, sizeof(digest)), 0); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), sizeof(digest)); + } } END_TEST // test vectors from // https://raw.githubusercontent.com/BLAKE2/BLAKE2/master/testvectors/blake2s-kat.txt START_TEST(test_blake2s) { + static const struct { + const char *msg; + const char *hash; + } tests[] = { + { + "", + "48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49", + }, + { + "000102", + "1d220dbe2ee134661fdf6d9e74b41704710556f2f6e5a091b227697445dbea6b", + }, + { + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021" + "22232425262728292a2b2c2d2e2f3031323334353637", + "2966b3cfae1e44ea996dc5d686cf25fa053fb6f67201b9e46eade85d0ad6b806", + }, + { + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021" + "22232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40414243" + "4445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465" + "666768696a6b6c6d6e6f", + "90a83585717b75f0e9b725e055eeeeb9e7a028ea7e6cbc07b20917ec0363e38c", + }, + }; + uint8_t key[BLAKE2S_KEY_LENGTH]; memcpy( key, @@ -4659,62 +4948,64 @@ START_TEST(test_blake2s) { BLAKE2S_KEY_LENGTH); uint8_t digest[BLAKE2S_DIGEST_LENGTH]; - - blake2s_Key((uint8_t *)"", 0, key, BLAKE2S_KEY_LENGTH, digest, - BLAKE2S_DIGEST_LENGTH); - ck_assert_mem_eq( - digest, - fromhex( - "48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49"), - BLAKE2S_DIGEST_LENGTH); - - blake2s_Key(fromhex("000102"), 3, key, BLAKE2S_KEY_LENGTH, digest, - BLAKE2S_DIGEST_LENGTH); - ck_assert_mem_eq( - digest, - fromhex( - "1d220dbe2ee134661fdf6d9e74b41704710556f2f6e5a091b227697445dbea6b"), - BLAKE2S_DIGEST_LENGTH); - - blake2s_Key( - fromhex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" - "202122232425262728292a2b2c2d2e2f3031323334353637"), - 56, key, BLAKE2S_KEY_LENGTH, digest, BLAKE2S_DIGEST_LENGTH); - ck_assert_mem_eq( - digest, - fromhex( - "2966b3cfae1e44ea996dc5d686cf25fa053fb6f67201b9e46eade85d0ad6b806"), - BLAKE2S_DIGEST_LENGTH); - - blake2s_Key( - fromhex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" - "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" - "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f" - "606162636465666768696a6b6c6d6e6f"), - 112, key, BLAKE2S_KEY_LENGTH, digest, BLAKE2S_DIGEST_LENGTH); - ck_assert_mem_eq( - digest, - fromhex( - "90a83585717b75f0e9b725e055eeeeb9e7a028ea7e6cbc07b20917ec0363e38c"), - BLAKE2S_DIGEST_LENGTH); + for (size_t i = 0; i < (sizeof(tests) / sizeof(*tests)); i++) { + size_t msg_len = strlen(tests[i].msg) / 2; + blake2s_Key(fromhex(tests[i].msg), msg_len, key, sizeof(key), digest, + sizeof(digest)); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), sizeof(digest)); + + // Test progressive hashing. + size_t part_len = msg_len / 2; + BLAKE2S_CTX ctx; + ck_assert_int_eq(blake2s_InitKey(&ctx, sizeof(digest), key, sizeof(key)), + 0); + ck_assert_int_eq(blake2s_Update(&ctx, fromhex(tests[i].msg), part_len), 0); + ck_assert_int_eq(blake2s_Update(&ctx, NULL, 0), 0); + ck_assert_int_eq(blake2s_Update(&ctx, fromhex(tests[i].msg) + part_len, + msg_len - part_len), + 0); + ck_assert_int_eq(blake2s_Final(&ctx, digest, sizeof(digest)), 0); + ck_assert_mem_eq(digest, fromhex(tests[i].hash), BLAKE2S_DIGEST_LENGTH); + } } END_TEST +#include + START_TEST(test_chacha_drbg) { - char entropy[] = "8a09b482de30c12ee1d2eb69dd49753d4252b3d36128ee1e"; - char reseed[] = "9ec4b991f939dbb44355392d05cd793a2e281809d2ed7139"; + char entropy[] = + "06032cd5eed33f39265f49ecb142c511da9aff2af71203bffaf34a9ca5bd9c0d"; + char nonce[] = "0e66f71edc43e42a45ad3c6fc6cdc4df"; + char reseed[] = + "01920a4e669ed3a85ae8a33b35a74ad7fb2a6bb4cf395ce00334a9c9a5a5d552"; char expected[] = - "4caaeb7db073d34b37b5b26f8a3863849f298dab754966e0f75526823216057c2626e044" - "9f7ffda7c3dba8841c06af01029eebfd4d4cae951c19c9f6ff6812783e58438840883401" - "2a05cd24c38cd22d18296aceed6829299190ebb9455eb8fd8d1cac1d"; - uint8_t result[100]; + "e172c5d18f3e8c77e9f66f9e1c24560772117161a9a0a237ab490b0769ad5d910f5dfb36" + "22edc06c18be0495c52588b200893d90fd80ff2149ead0c45d062c90f5890149c0f9591c" + "41bf4110865129a0fe524f210cca1340bd16f71f57906946cbaaf1fa863897d70d203b5a" + "f9996f756eec08861ee5875f9d915adcddc38719"; + uint8_t result[128]; + uint8_t null_bytes[128] = {0}; + uint8_t nonce_bytes[16]; + memcpy(nonce_bytes, fromhex(nonce), sizeof(nonce_bytes)); CHACHA_DRBG_CTX ctx; - chacha_drbg_init(&ctx, fromhex(entropy)); - chacha_drbg_reseed(&ctx, fromhex(reseed)); + chacha_drbg_init(&ctx, fromhex(entropy), strlen(entropy) / 2, nonce_bytes, + strlen(nonce) / 2); + chacha_drbg_reseed(&ctx, fromhex(reseed), strlen(reseed) / 2, NULL, 0); chacha_drbg_generate(&ctx, result, sizeof(result)); chacha_drbg_generate(&ctx, result, sizeof(result)); ck_assert_mem_eq(result, fromhex(expected), sizeof(result)); + + for (size_t i = 0; i <= sizeof(result); ++i) { + chacha_drbg_init(&ctx, fromhex(entropy), strlen(entropy) / 2, nonce_bytes, + strlen(nonce) / 2); + chacha_drbg_reseed(&ctx, fromhex(reseed), strlen(reseed) / 2, NULL, 0); + chacha_drbg_generate(&ctx, result, sizeof(result) - 13); + memset(result, 0, sizeof(result)); + chacha_drbg_generate(&ctx, result, i); + ck_assert_mem_eq(result, fromhex(expected), i); + ck_assert_mem_eq(result + i, null_bytes, sizeof(result) - i); + } } END_TEST @@ -4853,7 +5144,7 @@ START_TEST(test_hmac_drbg) { END_TEST START_TEST(test_mnemonic) { - const char *vectors[] = { + static const char *vectors[] = { "00000000000000000000000000000000", "abandon abandon abandon abandon abandon abandon abandon abandon abandon " "abandon abandon about", @@ -4990,10 +5281,10 @@ START_TEST(test_mnemonic) { a = vectors; b = vectors + 1; c = vectors + 2; - const size_t bufSize = 300; // large enough to hold 24 long words - char buf[bufSize]; + int buf_size = 308; + char buf[buf_size]; while (*a && *b && *c) { - m = mnemonic_from_data(fromhex(*a), strlen(*a) / 2, buf, bufSize); + m = mnemonic_from_data(fromhex(*a), strlen(*a) / 2, buf, buf_size); ck_assert_str_eq(m, *b); mnemonic_to_seed(m, "TREZOR", seed, 0); ck_assert_mem_eq(seed, fromhex(*c), strlen(*c) / 2); @@ -5005,17 +5296,13 @@ START_TEST(test_mnemonic) { a += 3; b += 3; c += 3; + memzero(buf, buf_size); } - - // [wallet-core] negative test: provided buffer invalid (too small or null) - ck_assert_int_eq((int)(mnemonic_from_data(fromhex(vectors[0]), strlen(vectors[0]) / 2, buf, 200)), 0); - ck_assert_int_eq((int)(mnemonic_from_data(fromhex(vectors[0]), strlen(vectors[0]) / 2, buf, 0)), 0); - ck_assert_int_eq((int)(mnemonic_from_data(fromhex(vectors[0]), strlen(vectors[0]) / 2, NULL, 240)), 0); } END_TEST START_TEST(test_mnemonic_check) { - const char *vectors_ok[] = { + static const char *vectors_ok[] = { "abandon abandon abandon abandon abandon abandon abandon abandon abandon " "abandon abandon about", "legal winner thank year wave sausage worth useful legal winner thank " @@ -5071,7 +5358,7 @@ START_TEST(test_mnemonic_check) { "away coconut", 0, }; - const char *vectors_fail[] = { + static const char *vectors_fail[] = { "above abandon abandon abandon abandon abandon abandon abandon abandon " "abandon abandon about", "above winner thank year wave sausage worth useful legal winner thank " @@ -5194,7 +5481,7 @@ START_TEST(test_mnemonic_check) { END_TEST START_TEST(test_mnemonic_to_bits) { - const char *vectors[] = { + static const char *vectors[] = { "00000000000000000000000000000000", "abandon abandon abandon abandon abandon abandon abandon abandon abandon " "abandon abandon about", @@ -5285,7 +5572,7 @@ START_TEST(test_mnemonic_to_bits) { int mnemonic_bits_len = mnemonic_to_bits(*b, mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len % 33, 0); mnemonic_bits_len = mnemonic_bits_len * 4 / 33; - ck_assert_int_eq(mnemonic_bits_len, strlen(*a) / 2); + ck_assert_uint_eq((size_t)mnemonic_bits_len, strlen(*a) / 2); ck_assert_mem_eq(mnemonic_bits, fromhex(*a), mnemonic_bits_len); a += 2; b += 2; @@ -5296,7 +5583,7 @@ END_TEST START_TEST(test_mnemonic_find_word) { ck_assert_int_eq(-1, mnemonic_find_word("aaaa")); ck_assert_int_eq(-1, mnemonic_find_word("zzzz")); - for (int i = 0; i < BIP39_WORDS; i++) { + for (int i = 0; i < BIP39_WORD_COUNT; i++) { const char *word = mnemonic_get_word(i); int index = mnemonic_find_word(word); ck_assert_int_eq(i, index); @@ -5304,9 +5591,8 @@ START_TEST(test_mnemonic_find_word) { } END_TEST -/* // [wallet-core] START_TEST(test_slip39_get_word) { - const struct { + static const struct { const int index; const char *expected_word; } vectors[] = {{573, "member"}, @@ -5323,7 +5609,7 @@ END_TEST START_TEST(test_slip39_word_index) { uint16_t index; - const struct { + static const struct { const char *word; bool expected_result; uint16_t expected_index; @@ -5335,17 +5621,17 @@ START_TEST(test_slip39_word_index) { // 9999 value is never checked since the word is not in list {"fakeword", false, 9999}}; for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); i++) { - bool result = word_index(&index, vectors[i].word, sizeof(vectors[i].word)); + bool result = word_index(&index, vectors[i].word, strlen(vectors[i].word)); ck_assert_int_eq(result, vectors[i].expected_result); if (result) { - ck_assert_int_eq(index, vectors[i].expected_index); + ck_assert_uint_eq(index, vectors[i].expected_index); } } } END_TEST START_TEST(test_slip39_word_completion_mask) { - const struct { + static const struct { const uint16_t prefix; const uint16_t expected_mask; } vectors[] = { @@ -5364,13 +5650,13 @@ START_TEST(test_slip39_word_completion_mask) { }; for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); i++) { uint16_t mask = slip39_word_completion_mask(vectors[i].prefix); - ck_assert_int_eq(mask, vectors[i].expected_mask); + ck_assert_uint_eq(mask, vectors[i].expected_mask); } } END_TEST START_TEST(test_slip39_sequence_to_word) { - const struct { + static const struct { const uint16_t prefix; const char *expected_word; } vectors[] = { @@ -5405,11 +5691,10 @@ START_TEST(test_slip39_word_completion) { } } END_TEST -*/ START_TEST(test_shamir) { #define SHAMIR_MAX_COUNT 16 - const struct { + static const struct { const uint8_t result[SHAMIR_MAX_LEN]; uint8_t result_index; const uint8_t share_indices[SHAMIR_MAX_COUNT]; @@ -5937,7 +6222,7 @@ START_TEST(test_address_decode) { END_TEST START_TEST(test_ecdsa_der) { - const struct { + static const struct { const char *r; const char *s; const char *der; @@ -5997,6 +6282,11 @@ START_TEST(test_ecdsa_der) { "00000000000000000000000000000000000000000000000000000000000000ff", "3008020200ee020200ff", }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "3006020100020100", + }, }; uint8_t sig[64]; @@ -6146,11 +6436,11 @@ static void test_point_mult_curve(const ecdsa_curve *curve) { /* test distributivity: (a + b)P = aP + bP */ bn_mod(&a, &curve->order); bn_mod(&b, &curve->order); - point_multiply(curve, &a, &p, &p1); - point_multiply(curve, &b, &p, &p2); + ck_assert_int_eq(point_multiply(curve, &a, &p, &p1), 0); + ck_assert_int_eq(point_multiply(curve, &b, &p, &p2), 0); bn_addmod(&a, &b, &curve->order); bn_mod(&a, &curve->order); - point_multiply(curve, &a, &p, &p3); + ck_assert_int_eq(point_multiply(curve, &a, &p, &p3), 0); point_add(curve, &p1, &p2); ck_assert_mem_eq(&p2, &p3, sizeof(curve_point)); // new "random" numbers and a "random" point @@ -6177,17 +6467,17 @@ static void test_scalar_point_mult_curve(const ecdsa_curve *curve) { */ bn_mod(&a, &curve->order); bn_mod(&b, &curve->order); - scalar_multiply(curve, &a, &p1); - point_multiply(curve, &b, &p1, &p1); + ck_assert_int_eq(scalar_multiply(curve, &a, &p1), 0); + ck_assert_int_eq(point_multiply(curve, &b, &p1, &p1), 0); - scalar_multiply(curve, &b, &p2); - point_multiply(curve, &a, &p2, &p2); + ck_assert_int_eq(scalar_multiply(curve, &b, &p2), 0); + ck_assert_int_eq(point_multiply(curve, &a, &p2, &p2), 0); ck_assert_mem_eq(&p1, &p2, sizeof(curve_point)); bn_multiply(&a, &b, &curve->order); bn_mod(&b, &curve->order); - scalar_multiply(curve, &b, &p2); + ck_assert_int_eq(scalar_multiply(curve, &b, &p2), 0); ck_assert_mem_eq(&p1, &p2, sizeof(curve_point)); @@ -6209,7 +6499,7 @@ END_TEST START_TEST(test_ed25519) { // test vectors from // https://github.com/torproject/tor/blob/master/src/test/ed25519_vectors.inc - const char *vectors[] = { + static const char *vectors[] = { "26c76712d89d906e6672dafa614c42e5cb1caac8c6568e4d2493087db51f0d3" "6", // secret "c2247870536a192d142d056abefca68d6193158e7c1a59c1654c954eccaff89" @@ -6285,7 +6575,7 @@ START_TEST(test_ed25519) { UNMARK_SECRET_DATA(pk, sizeof(pk)); ck_assert_mem_eq(pk, fromhex(*spk), 32); - ed25519_sign(pk, 32, sk, pk, sig); + ed25519_sign(pk, 32, sk, sig); UNMARK_SECRET_DATA(sig, sizeof(sig)); ck_assert_mem_eq(sig, fromhex(*ssig), 64); @@ -6301,7 +6591,7 @@ END_TEST // test vectors from // https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/2.test-sign.dat START_TEST(test_ed25519_keccak) { - const struct { + static const struct { const char *private_key; const char *public_key; const char *signature; @@ -6503,7 +6793,7 @@ START_TEST(test_ed25519_keccak) { ck_assert_mem_eq(public_key, fromhex(tests[i].public_key), 32); ed25519_sign_keccak(fromhex(tests[i].data), tests[i].length, private_key, - public_key, signature); + signature); UNMARK_SECRET_DATA(signature, sizeof(signature)); ck_assert_mem_eq(signature, fromhex(tests[i].signature), 64); @@ -6528,7 +6818,7 @@ START_TEST(test_ed25519_cosi) { "26c76712d89d906e6672dafa614c42e5cb1caac8c6568e4d2493087db51f0d36"), fromhex( "26659c1cf7321c178c07437150639ff0c5b7679c7ea195253ed9abda2e081a37"), - &rng); + NULL, &rng); for (int N = 1; N < 11; N++) { ed25519_public_key pk; @@ -6735,7 +7025,8 @@ START_TEST(test_ed25519_modl_sub) { } END_TEST -#if USE_MONERO // [wallet-core] +#if USE_MONERO + START_TEST(test_ge25519_double_scalarmult_vartime2) { char tests[][5][65] = { {"c537208ed4985e66e9f7a35c9a69448a732ba93960bbbd2823604f7ae9e3ed08", @@ -6859,13 +7150,14 @@ START_TEST(test_ge25519_double_scalarmult_vartime2) { } } END_TEST + #endif static void test_bip32_ecdh_init_node(HDNode *node, const char *seed_str, const char *curve_name) { hdnode_from_seed((const uint8_t *)seed_str, strlen(seed_str), curve_name, node); - hdnode_fill_public_key(node); + ck_assert_int_eq(hdnode_fill_public_key(node), 0); if (node->public_key[0] == 1) { node->public_key[0] = 0x40; // Curve25519 public keys start with 0x40 byte } @@ -6933,7 +7225,7 @@ START_TEST(test_bip32_ecdh_errors) { END_TEST START_TEST(test_output_script) { - const char *vectors[] = { + static const char *vectors[] = { "76A914010966776006953D5567439E5E39F86A0D273BEE88AC", "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM", "A914010966776006953D5567439E5E39F86A0D273BEE87", @@ -6952,7 +7244,7 @@ START_TEST(test_output_script) { while (*scr && *adr) { int r = script_output_to_address(fromhex(*scr), strlen(*scr) / 2, address, 60); - ck_assert_int_eq(r, (int)(strlen(*adr) + 1)); + ck_assert_uint_eq((size_t)r, strlen(*adr) + 1); ck_assert_str_eq(address, *adr); scr += 2; adr += 2; @@ -6961,6 +7253,7 @@ START_TEST(test_output_script) { END_TEST #if USE_ETHEREUM + START_TEST(test_ethereum_pubkeyhash) { uint8_t pubkeyhash[20]; int res; @@ -7062,25 +7355,25 @@ START_TEST(test_ethereum_pubkeyhash) { END_TEST START_TEST(test_ethereum_address) { - const char *vectors[] = {"52908400098527886E0F7030069857D2E4169EE7", - "8617E340B3D01FA5F11F306F4090FD50E238070D", - "de709f2102306220921060314715629080e2fb77", - "27b1fdb04752bbc536007a920d24acb045561c26", - "5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", - "fB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", - "dbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", - "D1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", - "5A4EAB120fB44eb6684E5e32785702FF45ea344D", - "5be4BDC48CeF65dbCbCaD5218B1A7D37F58A0741", - "a7dD84573f5ffF821baf2205745f768F8edCDD58", - "027a49d11d118c0060746F1990273FcB8c2fC196", - "CD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + static const char *vectors[] = {"0x52908400098527886E0F7030069857D2E4169EE7", + "0x8617E340B3D01FA5F11F306F4090FD50E238070D", + "0xde709f2102306220921060314715629080e2fb77", + "0x27b1fdb04752bbc536007a920d24acb045561c26", + "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", + "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", + "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", + "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", + "0x5A4EAB120fB44eb6684E5e32785702FF45ea344D", + "0x5be4BDC48CeF65dbCbCaD5218B1A7D37F58A0741", + "0xa7dD84573f5ffF821baf2205745f768F8edCDD58", + "0x027a49d11d118c0060746F1990273FcB8c2fC196", + "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", 0}; uint8_t addr[20]; - char address[41]; + char address[43]; const char **vec = vectors; while (*vec) { - memcpy(addr, fromhex(*vec), 20); + memcpy(addr, fromhex(*vec + 2), 20); ethereum_address_checksum(addr, address, false, 0); ck_assert_str_eq(address, *vec); vec++; @@ -7092,42 +7385,43 @@ END_TEST // https://github.com/rsksmart/RSKIPs/blob/master/IPs/RSKIP60.md START_TEST(test_rsk_address) { uint8_t addr[20]; - char address[41]; + char address[43]; - const char *rskip60_chain30[] = { - "5aaEB6053f3e94c9b9a09f33669435E7ef1bEAeD", - "Fb6916095cA1Df60bb79ce92cE3EA74c37c5d359", - "DBF03B407c01E7CD3cBea99509D93F8Dddc8C6FB", - "D1220A0Cf47c7B9BE7a2e6ba89F429762E7B9adB", 0}; + static const char *rskip60_chain30[] = { + "0x5aaEB6053f3e94c9b9a09f33669435E7ef1bEAeD", + "0xFb6916095cA1Df60bb79ce92cE3EA74c37c5d359", + "0xDBF03B407c01E7CD3cBea99509D93F8Dddc8C6FB", + "0xD1220A0Cf47c7B9BE7a2e6ba89F429762E7B9adB", 0}; const char **vec = rskip60_chain30; while (*vec) { - memcpy(addr, fromhex(*vec), 20); + memcpy(addr, fromhex(*vec + 2), 20); ethereum_address_checksum(addr, address, true, 30); ck_assert_str_eq(address, *vec); vec++; } - const char *rskip60_chain31[] = { - "5aAeb6053F3e94c9b9A09F33669435E7EF1BEaEd", - "Fb6916095CA1dF60bb79CE92ce3Ea74C37c5D359", - "dbF03B407C01E7cd3cbEa99509D93f8dDDc8C6fB", - "d1220a0CF47c7B9Be7A2E6Ba89f429762E7b9adB", 0}; + static const char *rskip60_chain31[] = { + "0x5aAeb6053F3e94c9b9A09F33669435E7EF1BEaEd", + "0xFb6916095CA1dF60bb79CE92ce3Ea74C37c5D359", + "0xdbF03B407C01E7cd3cbEa99509D93f8dDDc8C6fB", + "0xd1220a0CF47c7B9Be7A2E6Ba89f429762E7b9adB", 0}; vec = rskip60_chain31; while (*vec) { - memcpy(addr, fromhex(*vec), 20); + memcpy(addr, fromhex(*vec + 2), 20); ethereum_address_checksum(addr, address, true, 31); ck_assert_str_eq(address, *vec); vec++; } } END_TEST + #endif #if USE_NEM // test vectors from // https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/1.test-keys.dat START_TEST(test_nem_address) { - const struct { + static const struct { const char *private_key; const char *public_key; const char *address; @@ -7256,7 +7550,7 @@ END_TEST // test vectors from // https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/3.test-derive.dat START_TEST(test_nem_derive) { - const struct { + static const struct { const char *salt; const char *private_key; const char *public_key; @@ -7430,7 +7724,7 @@ END_TEST // test vectors from // https://raw.githubusercontent.com/NemProject/nem-test-vectors/master/4.test-cipher.dat START_TEST(test_nem_cipher) { - const struct { + static const struct { const char *private_key; const char *public_key; const char *salt; @@ -7691,13 +7985,13 @@ START_TEST(test_nem_cipher) { memcpy(iv, fromhex(tests[i].iv), sizeof(iv)); ck_assert(hdnode_nem_encrypt(&node, public_key, iv, salt, input, input_size, buffer)); - ck_assert_int_eq(output_size, NEM_ENCRYPTED_SIZE(input_size)); + ck_assert_uint_eq(output_size, NEM_ENCRYPTED_SIZE(input_size)); ck_assert_mem_eq(buffer, output, output_size); memcpy(iv, fromhex(tests[i].iv), sizeof(iv)); ck_assert(hdnode_nem_decrypt(&node, public_key, iv, salt, output, output_size, buffer)); - ck_assert_int_eq(input_size, NEM_DECRYPTED_SIZE(buffer, output_size)); + ck_assert_uint_eq(input_size, NEM_DECRYPTED_SIZE(buffer, output_size)); ck_assert_mem_eq(buffer, input, input_size); } } @@ -8401,11 +8695,11 @@ END_TEST // https://tools.ietf.org/html/rfc6229#section-2 START_TEST(test_rc4_rfc6229) { - const size_t offsets[] = { + static const size_t offsets[] = { 0x0, 0xf0, 0x1f0, 0x2f0, 0x3f0, 0x5f0, 0x7f0, 0xbf0, 0xff0, }; - const struct { + static const struct { char key[65]; char vectors[sizeof(offsets) / sizeof(*offsets)][65]; } tests[] = { @@ -8725,7 +9019,7 @@ static void test_compress_coord(const char *k_raw) { } START_TEST(test_compress_coords) { - const char *k_raw[] = { + static const char *k_raw[] = { "dc05960ac673fd59554c98655e26722d007bb7ada0c8ff00883fdee70783d0be", "41e41e0a218c980411108a0a58cf88f528c828b4d6f0d2c86234bc2504bdc3cd", "1d963ddcb79f6028a32cadd2421ff7fff969bff5774f73063dab41519b3da175", @@ -8743,219 +9037,6 @@ START_TEST(test_compress_coords) { } END_TEST -// [wallet-core] -START_TEST(test_schnorr_sign_verify) { - static struct { - const char *message; - const char *priv_key; - const char *k_hex; - const char *s_hex; - const char *r_hex; - } test_cases[] = { - { - "123", - "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", - "669301F724C555D7BB1185C04909E9CACA3EC7A292B3A1C92DDCCD5A5A7DDDD3", - "FFD72C290B98C93A4BCEDC0EDCDF040C35579BE962FE83E6821D4F3CB4B795D2", - "74AAE9C3E069E2806E1B0D890970BE387AEBED8040F37991AACAD70B27895E39", - }, - { - "1234", - "51a2758eed776c40b367364909c8a9c98cc969104f69ff316f7a287495c37c9b", - "A0A1A9B3570AAE963535B8D4376C58A61646C18182C9FDDA5FB13703F88D4D1E", - "99A0CB942C81571B77C682F79CD3CB663CE9E1C55BB425BA24B9F11A0DE84FE2", - "C3C10363E38158BBA20556A36DE9358DFD81A31C180ABC9E7617C1CC1CAF03B3", - }, - { - "12345", - "2685adffdbb4b2c515054cffc25cfcbfe2e462df65bbe82fb50f71e1e68dd285", - "38DE7B3315F201433D271E91FBE62966576CA05CBFEC1770B77D7EC9D6A01D6D", - "28982FA6C2B620CBC550F7EF9EAB605F409C584FBE5A765678877B79AB517086", - "9A0788E5B0947DEDEDE386DF57A006CF3FE43919A74D9CA630F8A1A9D97B4650", - }, - { - "fun", - "7457dc574d927e5dae84b05264a5b637b5a68e34a85b3965084ed6fed5b7f12d", - "E005ABD242C7C602AB5EED080C5083C7C5F8DAEC6D046A54F384A8B8CDECF740", - "51070ABCA039DAC294F6BA3BFC8C36CFC66020EDF66D1ACF1A9B545B0BF09F52", - "330A924525EF722FA20E8E25CB6E8BD7DF4394886FA4414E4A0B6812AA25BBC0", - }, - { - "funny", - "52c395a6d304de1a959e73e4604e32c5ad3f2bf01c8f730af426b38d7d5dd908", - "0CF28B5C40A8830F3195BB99A9F0E2808F576105F41D16ABCF596AC5A8CFE88A", - "3D60FB4664C994AD956378B9402BC68F7B4799D74F4783A6199C0D74865EA2B6", - "5ED5EDEE0314DFFBEE39EE4E9C76DE8BC3EB8CB891AEC32B83957514284B205B", - }, - { - "What is great in man is that he is a bridge and not a goal", - "52c395a6d304de1a959e73e4604e32c5ad3f2bf01c8f730af426b38d7d5dd908", - "000000000000000000000000000000000000000000000000000000000000007B", - "546F70AA1FEE3718C95508240CDC073B9FEFED05959C5319DD8E2BF07A1DD028", - "B8667BE5E10B113608BFE5327C44E9F0462BE26F789177E10DCE53019AA33DAA", - }, - { - "123456789147258369qwertyuiopasdfghjklzxcvbnm,", - "2685adffdbb4b2c515054cffc25cfcbfe2e462df65bbe82fb50f71e1e68dd285", - "1D0CB70310C4D793A4561FE592B7C156771E3E26283B28AB588E968243B52DD0", - "54D7A435E5E3F2811AA542F8895C20CCB760F2713DBDDB7291DAB6DA4E4F927E", - "20A3BDABFFF2C1BF8E2AF709F6CDCAFE70DA9A1DBC22305B6332E36844092984", - }, - { - "11111111111111111111111111111111111111111111111111111111111111111" - "11111111111111111111111111111111111111111111111111111111111111111" - "111111111111111111", - "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", - "A669F372B3C2EEA351210082CAEC3B96767A7B222D19FF2EE3D814860F0D703A", - "4890F9AC3A8D102EE3A2A473930C01CAD29DCE3860ACB7A5DADAEF16FE808991", - "979F088E58F1814D5E462CB9F935D2924ABD8D32211D8F02DD7E0991726DF573", - }, - { - "qwertyuiop[]asdfghjkl;'zxcvbnm,./1234567890-=", - "7457dc574d927e5dae84b05264a5b637b5a68e34a85b3965084ed6fed5b7f12d", - "000000000000000000000000000000000000000000000000000000000000007C", - "0AA595A649E517133D3448CA657424DD07BBED289030F0C0AA6738D26AB9A910", - "83812632F1443A70B198D112D075D886BE7BBC6EC6275AE52661E52B7358BB8B", - }, - }; - - const ecdsa_curve *curve = &secp256k1; - bignum256 k; - uint8_t priv_key[32]; - uint8_t pub_key[33]; - uint8_t buf_raw[32]; - schnorr_sign_pair result; - schnorr_sign_pair expected; - int res; - - for (size_t i = 0; i < sizeof(test_cases) / sizeof(*test_cases); i++) { - memcpy(priv_key, fromhex(test_cases[i].priv_key), 32); - memcpy(&buf_raw, fromhex(test_cases[i].k_hex), 32); - bn_read_be(buf_raw, &k); - schnorr_sign(curve, priv_key, &k, (const uint8_t *)test_cases[i].message, - strlen(test_cases[i].message), &result); - - memcpy(&expected.s, fromhex(test_cases[i].s_hex), 32); - memcpy(&expected.r, fromhex(test_cases[i].r_hex), 32); - - ck_assert_mem_eq(&expected.r, &result.r, 32); - ck_assert_mem_eq(&expected.s, &result.s, 32); - - ecdsa_get_public_key33(curve, priv_key, pub_key); - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_cases[i].message, - strlen(test_cases[i].message), &result); - ck_assert_int_eq(res, 0); - } -} -END_TEST - -START_TEST(test_schnorr_fail_verify) { - static struct { - const char *message; - const char *priv_key; - const char *k_hex; - const char *s_hex; - const char *r_hex; - } test_case = { - "123", - "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", - "669301F724C555D7BB1185C04909E9CACA3EC7A292B3A1C92DDCCD5A5A7DDDD3", - "FFD72C290B98C93A4BCEDC0EDCDF040C35579BE962FE83E6821D4F3CB4B795D2", - "74AAE9C3E069E2806E1B0D890970BE387AEBED8040F37991AACAD70B27895E39", - }; - - const ecdsa_curve *curve = &secp256k1; - bignum256 k; - bignum256 bn_temp; - uint8_t priv_key[32]; - uint8_t pub_key[33]; - uint8_t buf_raw[32]; - schnorr_sign_pair result; - schnorr_sign_pair bad_result; - int res; - - memcpy(priv_key, fromhex(test_case.priv_key), 32); - memcpy(&buf_raw, fromhex(test_case.k_hex), 32); - bn_read_be(buf_raw, &k); - - schnorr_sign(curve, priv_key, &k, (const uint8_t *)test_case.message, - strlen(test_case.message), &result); - - ecdsa_get_public_key33(curve, priv_key, pub_key); - - // Test result = 0 (OK) - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, - strlen(test_case.message), &result); - ck_assert_int_eq(res, 0); - - // Test result = 1 (empty message) - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, 0, - &result); - ck_assert_int_eq(res, 1); - - // Test result = 2 (r = 0) - bn_zero(&bn_temp); - bn_write_be(&bn_temp, bad_result.r); - memcpy(bad_result.s, result.s, 32); - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, - strlen(test_case.message), &bad_result); - ck_assert_int_eq(res, 2); - - // Test result = 3 (s = 0) - memcpy(bad_result.r, result.r, 32); - bn_zero(&bn_temp); - bn_write_be(&bn_temp, bad_result.s); - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, - strlen(test_case.message), &bad_result); - ck_assert_int_eq(res, 3); - - // Test result = 4 (curve->order < r) - bn_copy(&curve->order, &bn_temp); - bn_addi(&bn_temp, 1); - bn_write_be(&bn_temp, bad_result.r); - memcpy(bad_result.s, result.s, 32); - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, - strlen(test_case.message), &bad_result); - ck_assert_int_eq(res, 4); - - // Test result = 5 (curve->order < s) - memcpy(bad_result.r, result.r, 32); - bn_copy(&curve->order, &bn_temp); - bn_addi(&bn_temp, 1); - bn_write_be(&bn_temp, bad_result.s); - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, - strlen(test_case.message), &bad_result); - ck_assert_int_eq(res, 5); - - // Test result = 6 (curve->order = r) - bn_copy(&curve->order, &bn_temp); - bn_write_be(&bn_temp, bad_result.r); - memcpy(bad_result.s, result.s, 32); - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, - strlen(test_case.message), &bad_result); - ck_assert_int_eq(res, 6); - - // Test result = 7 (curve->order = s) - memcpy(bad_result.r, result.r, 32); - bn_copy(&curve->order, &bn_temp); - bn_write_be(&bn_temp, bad_result.s); - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, - strlen(test_case.message), &bad_result); - ck_assert_int_eq(res, 7); - - // Test result = 8 (failed ecdsa_read_pubkey) - // TBD - - // Test result = 10 (r != r') - memcpy(bad_result.r, result.r, 32); - memcpy(bad_result.s, result.s, 32); - test_case.message = "12"; - res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, - strlen(test_case.message), &bad_result); - ck_assert_int_eq(res, 10); -} -END_TEST - static int my_strncasecmp(const char *s1, const char *s2, size_t n) { size_t i = 0; while (i < n) { @@ -8972,6 +9053,7 @@ static int my_strncasecmp(const char *s1, const char *s2, size_t n) { } #include "test_check_cashaddr.h" +#include "test_check_zilliqa.h" // [wallet-core] #if USE_SEGWIT #include "test_check_segwit.h" #endif @@ -9069,7 +9151,13 @@ Suite *test_suite(void) { suite_add_tcase(s, tc); tc = tcase_create("ecdsa"); - tcase_add_test(tc, test_ecdsa_signature); + tcase_add_test(tc, test_ecdsa_get_public_key33); + tcase_add_test(tc, test_ecdsa_get_public_key65); + tcase_add_test(tc, test_ecdsa_recover_pub_from_sig); + tcase_add_test(tc, test_ecdsa_verify_digest); +#if USE_RFC6979 + tcase_add_test(tc, test_ecdsa_sign_digest_deterministic); +#endif suite_add_tcase(s, tc); tc = tcase_create("rfc6979"); @@ -9124,6 +9212,7 @@ Suite *test_suite(void) { tc = tcase_create("blake2"); tcase_add_test(tc, test_blake2b); + tcase_add_test(tc, test_blake2bp); tcase_add_test(tc, test_blake2s); suite_add_tcase(s, tc); @@ -9147,7 +9236,6 @@ Suite *test_suite(void) { tcase_add_test(tc, test_mnemonic_find_word); suite_add_tcase(s, tc); -/* tc = tcase_create("slip39"); tcase_add_test(tc, test_slip39_get_word); tcase_add_test(tc, test_slip39_word_index); @@ -9155,7 +9243,6 @@ Suite *test_suite(void) { tcase_add_test(tc, test_slip39_sequence_to_word); tcase_add_test(tc, test_slip39_word_completion); suite_add_tcase(s, tc); -*/ tc = tcase_create("shamir"); tcase_add_test(tc, test_shamir); @@ -9283,6 +9370,10 @@ Suite *test_suite(void) { tcase_add_test(tc, test_bip32_cardano_hdnode_vector_8); tcase_add_test(tc, test_bip32_cardano_hdnode_vector_9); + tcase_add_test(tc, test_cardano_ledger_vector_1); + tcase_add_test(tc, test_cardano_ledger_vector_2); + tcase_add_test(tc, test_cardano_ledger_vector_3); + tcase_add_test(tc, test_ed25519_cardano_sign_vectors); suite_add_tcase(s, tc); #endif @@ -9325,16 +9416,9 @@ Suite *test_suite(void) { tcase_add_test(tc, test_xmr_get_subaddress_secret_key); tcase_add_test(tc, test_xmr_gen_c); tcase_add_test(tc, test_xmr_varint); - tcase_add_test(tc, test_xmr_gen_range_sig); suite_add_tcase(s, tc); #endif - // [wallet-core] - tc = tcase_create("schnorr"); - tcase_add_test(tc, test_schnorr_sign_verify); - tcase_add_test(tc, test_schnorr_fail_verify); - suite_add_tcase(s, tc); - return s; } diff --git a/trezor-crypto/crypto/tests/test_check_cardano.h b/trezor-crypto/crypto/tests/test_check_cardano.h index 4fc03cb3ee1..4f31a55309f 100644 --- a/trezor-crypto/crypto/tests/test_check_cardano.h +++ b/trezor-crypto/crypto/tests/test_check_cardano.h @@ -5,7 +5,7 @@ START_TEST(test_ed25519_cardano_sign_vectors) { ed25519_secret_key secret_key_extension; ed25519_signature signature; - const char *vectors[] = { + static const char *vectors[] = { "6065a956b1b34145c4416fdc3ba3276801850e91a77a31a7be782463288aea5" "3", // private key "60ba6e25b1a02157fb69c5d1d7b96c4619736e545447069a6a6f0ba90844bc8" @@ -89,14 +89,13 @@ START_TEST(test_ed25519_cardano_sign_vectors) { memcpy(secret_key_extension, fromhex(*(test_data + 1)), 32); MARK_SECRET_DATA(secret_key_extension, sizeof(secret_key_extension)); - ed25519_publickey_ext(secret_key, secret_key_extension, public_key); + ed25519_publickey_ext(secret_key, public_key); UNMARK_SECRET_DATA(public_key, sizeof(public_key)); ck_assert_mem_eq(public_key, fromhex(*(test_data + 2)), 32); const uint8_t *message = (const uint8_t *)"Hello World"; - ed25519_sign_ext(message, 11, secret_key, secret_key_extension, public_key, - signature); + ed25519_sign_ext(message, 11, secret_key, secret_key_extension, signature); UNMARK_SECRET_DATA(signature, sizeof(signature)); ck_assert_mem_eq(signature, fromhex(*(test_data + 3)), 64); @@ -113,14 +112,24 @@ START_TEST(test_bip32_cardano_hdnode_vector_1) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); + ck_assert_mem_eq( + cardano_secret, + fromhex( + "08a14df748e477a69d21c97c56db151fc19e2521f31dd0ac5360f269e5b6ea46" + "daeb991f2d2128e2525415c56a07f4366baa26c1e48572a5e073934b6de35fbc" + "affbc325d9027c0f2d9f925b1dcf6c12bf5c1dd08904474066a4f2c00db56173"), + 96); ck_assert_mem_eq( node.chain_code, fromhex( @@ -136,7 +145,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_1) { fromhex( "daeb991f2d2128e2525415c56a07f4366baa26c1e48572a5e073934b6de35fbc"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -149,15 +158,18 @@ START_TEST(test_bip32_cardano_hdnode_vector_2) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); - hdnode_private_ckd_cardano(&node, 0x80000000); + hdnode_private_ckd(&node, 0x80000000); ck_assert_mem_eq( node.chain_code, @@ -174,7 +186,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_2) { fromhex( "64aa9a16331f14c981b769efcf96addcc4c6db44047fe7a7feae0be23d33bf54"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -187,15 +199,18 @@ START_TEST(test_bip32_cardano_hdnode_vector_3) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); - hdnode_private_ckd_cardano(&node, 0x80000001); + hdnode_private_ckd(&node, 0x80000001); ck_assert_mem_eq( node.chain_code, @@ -212,7 +227,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_3) { fromhex( "b4fc241feffe840b8a54a26ab447f5a5caa31032db3a8091fca14f38b86ed539"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -225,16 +240,19 @@ START_TEST(test_bip32_cardano_hdnode_vector_4) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); - hdnode_private_ckd_cardano(&node, 0x80000000); - hdnode_private_ckd_cardano(&node, 0x80000001); + hdnode_private_ckd(&node, 0x80000000); + hdnode_private_ckd(&node, 0x80000001); ck_assert_mem_eq( node.chain_code, @@ -251,7 +269,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_4) { fromhex( "a3071959013af95aaecf78a7a2e1b9838bbbc4864d6a8a2295243782078345cd"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -264,17 +282,20 @@ START_TEST(test_bip32_cardano_hdnode_vector_5) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); - hdnode_private_ckd_cardano(&node, 0x80000000); - hdnode_private_ckd_cardano(&node, 0x80000001); - hdnode_private_ckd_cardano(&node, 0x80000002); + hdnode_private_ckd(&node, 0x80000000); + hdnode_private_ckd(&node, 0x80000001); + hdnode_private_ckd(&node, 0x80000002); ck_assert_mem_eq( node.chain_code, @@ -291,7 +312,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_5) { fromhex( "5bebf1eea68acd04932653d944b064b10baaf5886dd73c185cc285059bf93363"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -304,18 +325,21 @@ START_TEST(test_bip32_cardano_hdnode_vector_6) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); - hdnode_private_ckd_cardano(&node, 0x80000000); - hdnode_private_ckd_cardano(&node, 0x80000001); - hdnode_private_ckd_cardano(&node, 0x80000002); - hdnode_private_ckd_cardano(&node, 0x80000002); + hdnode_private_ckd(&node, 0x80000000); + hdnode_private_ckd(&node, 0x80000001); + hdnode_private_ckd(&node, 0x80000002); + hdnode_private_ckd(&node, 0x80000002); ck_assert_mem_eq( node.chain_code, @@ -332,7 +356,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_6) { fromhex( "466332cb097934b43008701e7e27044aa56c7859019e4eba18d91a3bea23dff7"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -345,19 +369,22 @@ START_TEST(test_bip32_cardano_hdnode_vector_7) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); - hdnode_private_ckd_cardano(&node, 0x80000000); - hdnode_private_ckd_cardano(&node, 0x80000001); - hdnode_private_ckd_cardano(&node, 0x80000002); - hdnode_private_ckd_cardano(&node, 0x80000002); - hdnode_private_ckd_cardano(&node, 0xBB9ACA00); + hdnode_private_ckd(&node, 0x80000000); + hdnode_private_ckd(&node, 0x80000001); + hdnode_private_ckd(&node, 0x80000002); + hdnode_private_ckd(&node, 0x80000002); + hdnode_private_ckd(&node, 0xBB9ACA00); ck_assert_mem_eq( node.chain_code, @@ -374,7 +401,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_7) { fromhex( "01eccef768a79859f824a1d3c3e35e131184e2940c3fca9a4c9b307741f65363"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -387,19 +414,22 @@ START_TEST(test_bip32_cardano_hdnode_vector_8) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "found differ bulb shadow wrist blue bind vessel deposit tip pelican " "action surprise weapon check fiction muscle this", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 198); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); - hdnode_private_ckd_cardano(&node, 0x80000000); - hdnode_private_ckd_cardano(&node, 0x80000001); - hdnode_private_ckd_cardano(&node, 0x80000002); - hdnode_private_ckd_cardano(&node, 0x80000002); - hdnode_private_ckd_cardano(&node, 0xBB9ACA00); + hdnode_private_ckd(&node, 0x80000000); + hdnode_private_ckd(&node, 0x80000001); + hdnode_private_ckd(&node, 0x80000002); + hdnode_private_ckd(&node, 0x80000002); + hdnode_private_ckd(&node, 0xBB9ACA00); ck_assert_mem_eq( node.chain_code, @@ -416,7 +446,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_8) { fromhex( "170e0d3b65ba8d71f27a6db60d0ac26dcb16e52e08cc259db72066f206b258d5"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -429,20 +459,23 @@ START_TEST(test_bip32_cardano_hdnode_vector_9) { HDNode node; uint8_t mnemonic_bits[66]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; int mnemonic_bits_len = mnemonic_to_bits( "balance exotic ranch knife glory slow tape favorite yard gym awake " "ill exist useless parent aim pig stay effort into square gasp credit " "butter", mnemonic_bits); ck_assert_int_eq(mnemonic_bits_len, 264); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, - mnemonic_bits_len / 8, &node); + secret_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, cardano_secret, + NULL); + hdnode_from_secret_cardano(cardano_secret, &node); - hdnode_private_ckd_cardano(&node, 0x80000000); - hdnode_private_ckd_cardano(&node, 0x80000001); - hdnode_private_ckd_cardano(&node, 0x80000002); - hdnode_private_ckd_cardano(&node, 0x80000002); - hdnode_private_ckd_cardano(&node, 0xBB9ACA00); + hdnode_private_ckd(&node, 0x80000000); + hdnode_private_ckd(&node, 0x80000001); + hdnode_private_ckd(&node, 0x80000002); + hdnode_private_ckd(&node, 0x80000002); + hdnode_private_ckd(&node, 0xBB9ACA00); ck_assert_mem_eq( node.chain_code, @@ -459,7 +492,7 @@ START_TEST(test_bip32_cardano_hdnode_vector_9) { fromhex( "80d2c677638e5dbd4395cdec279bf2a42077f2797c9e887949d37cdb317fce6a"), 32); - hdnode_fill_public_key(&node); + ck_assert_int_eq(hdnode_fill_public_key(&node), 0); ck_assert_mem_eq( node.public_key + 1, fromhex( @@ -467,3 +500,72 @@ START_TEST(test_bip32_cardano_hdnode_vector_9) { 32); } END_TEST + +START_TEST(test_cardano_ledger_vector_1) { + uint8_t seed[512 / 8]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; + + const char *mnemonic = + "recall grace sport punch exhibit mad harbor stand obey " + "short width stem awkward used stairs wool ugly " + "trap season stove worth toward congress jaguar"; + + mnemonic_to_seed(mnemonic, "", seed, NULL); + const int res = + secret_from_seed_cardano_ledger(seed, sizeof(seed), cardano_secret); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq( + cardano_secret, + fromhex( + "a08cf85b564ecf3b947d8d4321fb96d70ee7bb760877e371899b14e2ccf88658" + "104b884682b57efd97decbb318a45c05a527b9cc5c2f64f7352935a049ceea60" + "680d52308194ccef2a18e6812b452a5815fbd7f5babc083856919aaf668fe7e4"), + CARDANO_SECRET_LENGTH); +} +END_TEST + +START_TEST(test_cardano_ledger_vector_2) { + uint8_t seed[512 / 8]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; + + const char *mnemonic = + "correct cherry mammal bubble want mandate polar hazard " + "crater better craft exotic choice fun tourist census " + "gap lottery neglect address glow carry old business"; + + mnemonic_to_seed(mnemonic, "", seed, NULL); + const int res = + secret_from_seed_cardano_ledger(seed, sizeof(seed), cardano_secret); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq( + cardano_secret, + fromhex( + "587c6774357ecbf840d4db6404ff7af016dace0400769751ad2abfc77b9a3844" + "cc71702520ef1a4d1b68b91187787a9b8faab0a9bb6b160de541b6ee62469901" + "fc0beda0975fe4763beabd83b7051a5fd5cbce5b88e82c4bbaca265014e524bd"), + CARDANO_SECRET_LENGTH); +} +END_TEST + +START_TEST(test_cardano_ledger_vector_3) { + uint8_t seed[512 / 8]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; + + const char *mnemonic = + "abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon abandon abandon art"; + + mnemonic_to_seed(mnemonic, "foo", seed, NULL); + const int res = + secret_from_seed_cardano_ledger(seed, sizeof(seed), cardano_secret); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq( + cardano_secret, + fromhex( + "f053a1e752de5c26197b60f032a4809f08bb3e5d90484fe42024be31efcba757" + "8d914d3ff992e21652fee6a4d99f6091006938fac2c0c0f9d2de0ba64b754e92" + "a4f3723f23472077aa4cd4dd8a8a175dba07ea1852dad1cf268c61a2679c3890"), + CARDANO_SECRET_LENGTH); +} +END_TEST diff --git a/trezor-crypto/crypto/tests/test_check_zilliqa.h b/trezor-crypto/crypto/tests/test_check_zilliqa.h new file mode 100644 index 00000000000..b8b16824b51 --- /dev/null +++ b/trezor-crypto/crypto/tests/test_check_zilliqa.h @@ -0,0 +1,214 @@ +#include +#include + +START_TEST(test_zil_schnorr_sign_verify) { + static struct { + const char *message; + const char *priv_key; + const char *k_hex; + const char *s_hex; + const char *r_hex; + } test_cases[] = { + { + "123", + "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + "669301F724C555D7BB1185C04909E9CACA3EC7A292B3A1C92DDCCD5A5A7DDDD3", + "FFD72C290B98C93A4BCEDC0EDCDF040C35579BE962FE83E6821D4F3CB4B795D2", + "74AAE9C3E069E2806E1B0D890970BE387AEBED8040F37991AACAD70B27895E39", + }, + { + "1234", + "51a2758eed776c40b367364909c8a9c98cc969104f69ff316f7a287495c37c9b", + "A0A1A9B3570AAE963535B8D4376C58A61646C18182C9FDDA5FB13703F88D4D1E", + "99A0CB942C81571B77C682F79CD3CB663CE9E1C55BB425BA24B9F11A0DE84FE2", + "C3C10363E38158BBA20556A36DE9358DFD81A31C180ABC9E7617C1CC1CAF03B3", + }, + { + "12345", + "2685adffdbb4b2c515054cffc25cfcbfe2e462df65bbe82fb50f71e1e68dd285", + "38DE7B3315F201433D271E91FBE62966576CA05CBFEC1770B77D7EC9D6A01D6D", + "28982FA6C2B620CBC550F7EF9EAB605F409C584FBE5A765678877B79AB517086", + "9A0788E5B0947DEDEDE386DF57A006CF3FE43919A74D9CA630F8A1A9D97B4650", + }, + { + "fun", + "7457dc574d927e5dae84b05264a5b637b5a68e34a85b3965084ed6fed5b7f12d", + "E005ABD242C7C602AB5EED080C5083C7C5F8DAEC6D046A54F384A8B8CDECF740", + "51070ABCA039DAC294F6BA3BFC8C36CFC66020EDF66D1ACF1A9B545B0BF09F52", + "330A924525EF722FA20E8E25CB6E8BD7DF4394886FA4414E4A0B6812AA25BBC0", + }, + { + "funny", + "52c395a6d304de1a959e73e4604e32c5ad3f2bf01c8f730af426b38d7d5dd908", + "0CF28B5C40A8830F3195BB99A9F0E2808F576105F41D16ABCF596AC5A8CFE88A", + "3D60FB4664C994AD956378B9402BC68F7B4799D74F4783A6199C0D74865EA2B6", + "5ED5EDEE0314DFFBEE39EE4E9C76DE8BC3EB8CB891AEC32B83957514284B205B", + }, + { + "What is great in man is that he is a bridge and not a goal", + "52c395a6d304de1a959e73e4604e32c5ad3f2bf01c8f730af426b38d7d5dd908", + "000000000000000000000000000000000000000000000000000000000000007B", + "546F70AA1FEE3718C95508240CDC073B9FEFED05959C5319DD8E2BF07A1DD028", + "B8667BE5E10B113608BFE5327C44E9F0462BE26F789177E10DCE53019AA33DAA", + }, + { + "123456789147258369qwertyuiopasdfghjklzxcvbnm,", + "2685adffdbb4b2c515054cffc25cfcbfe2e462df65bbe82fb50f71e1e68dd285", + "1D0CB70310C4D793A4561FE592B7C156771E3E26283B28AB588E968243B52DD0", + "54D7A435E5E3F2811AA542F8895C20CCB760F2713DBDDB7291DAB6DA4E4F927E", + "20A3BDABFFF2C1BF8E2AF709F6CDCAFE70DA9A1DBC22305B6332E36844092984", + }, + { + "11111111111111111111111111111111111111111111111111111111111111111" + "11111111111111111111111111111111111111111111111111111111111111111" + "111111111111111111", + "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + "A669F372B3C2EEA351210082CAEC3B96767A7B222D19FF2EE3D814860F0D703A", + "4890F9AC3A8D102EE3A2A473930C01CAD29DCE3860ACB7A5DADAEF16FE808991", + "979F088E58F1814D5E462CB9F935D2924ABD8D32211D8F02DD7E0991726DF573", + }, + { + "qwertyuiop[]asdfghjkl;'zxcvbnm,./1234567890-=", + "7457dc574d927e5dae84b05264a5b637b5a68e34a85b3965084ed6fed5b7f12d", + "000000000000000000000000000000000000000000000000000000000000007C", + "0AA595A649E517133D3448CA657424DD07BBED289030F0C0AA6738D26AB9A910", + "83812632F1443A70B198D112D075D886BE7BBC6EC6275AE52661E52B7358BB8B", + }, + }; + + const ecdsa_curve *curve = &secp256k1; + bignum256 k; + uint8_t priv_key[32]; + uint8_t pub_key[33]; + uint8_t buf_raw[32]; + schnorr_sign_pair result; + schnorr_sign_pair expected; + int res; + + for (size_t i = 0; i < sizeof(test_cases) / sizeof(*test_cases); i++) { + memcpy(priv_key, fromhex(test_cases[i].priv_key), 32); + memcpy(&buf_raw, fromhex(test_cases[i].k_hex), 32); + bn_read_be(buf_raw, &k); + zil_schnorr_sign_k(curve, priv_key, &k, (const uint8_t *)test_cases[i].message, + strlen(test_cases[i].message), &result); + + memcpy(&expected.s, fromhex(test_cases[i].s_hex), 32); + memcpy(&expected.r, fromhex(test_cases[i].r_hex), 32); + + ck_assert_mem_eq(&expected.r, &result.r, 32); + ck_assert_mem_eq(&expected.s, &result.s, 32); + + ecdsa_get_public_key33(curve, priv_key, pub_key); + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_cases[i].message, + strlen(test_cases[i].message), &result); + ck_assert_int_eq(res, 0); + } +} +END_TEST + +START_TEST(test_zil_schnorr_fail_verify) { + static struct { + const char *message; + const char *priv_key; + const char *k_hex; + const char *s_hex; + const char *r_hex; + } test_case = { + "123", + "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + "669301F724C555D7BB1185C04909E9CACA3EC7A292B3A1C92DDCCD5A5A7DDDD3", + "FFD72C290B98C93A4BCEDC0EDCDF040C35579BE962FE83E6821D4F3CB4B795D2", + "74AAE9C3E069E2806E1B0D890970BE387AEBED8040F37991AACAD70B27895E39", + }; + + const ecdsa_curve *curve = &secp256k1; + bignum256 k; + bignum256 bn_temp; + uint8_t priv_key[32]; + uint8_t pub_key[33]; + uint8_t buf_raw[32]; + schnorr_sign_pair result; + schnorr_sign_pair bad_result; + int res; + + memcpy(priv_key, fromhex(test_case.priv_key), 32); + memcpy(&buf_raw, fromhex(test_case.k_hex), 32); + bn_read_be(buf_raw, &k); + + zil_schnorr_sign_k(curve, priv_key, &k, (const uint8_t *)test_case.message, + strlen(test_case.message), &result); + + ecdsa_get_public_key33(curve, priv_key, pub_key); + + // Test result = 0 (OK) + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &result); + ck_assert_int_eq(res, 0); + + // Test result = 1 (empty message) + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, 0, + &result); + ck_assert_int_eq(res, 1); + + // Test result = 2 (r = 0) + bn_zero(&bn_temp); + bn_write_be(&bn_temp, bad_result.r); + memcpy(bad_result.s, result.s, 32); + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 2); + + // Test result = 3 (s = 0) + memcpy(bad_result.r, result.r, 32); + bn_zero(&bn_temp); + bn_write_be(&bn_temp, bad_result.s); + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 3); + + // Test result = 4 (curve->order < r) + bn_copy(&curve->order, &bn_temp); + bn_addi(&bn_temp, 1); + bn_write_be(&bn_temp, bad_result.r); + memcpy(bad_result.s, result.s, 32); + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 4); + + // Test result = 5 (curve->order < s) + memcpy(bad_result.r, result.r, 32); + bn_copy(&curve->order, &bn_temp); + bn_addi(&bn_temp, 1); + bn_write_be(&bn_temp, bad_result.s); + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 5); + + // Test result = 6 (curve->order = r) + bn_copy(&curve->order, &bn_temp); + bn_write_be(&bn_temp, bad_result.r); + memcpy(bad_result.s, result.s, 32); + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 6); + + // Test result = 7 (curve->order = s) + memcpy(bad_result.r, result.r, 32); + bn_copy(&curve->order, &bn_temp); + bn_write_be(&bn_temp, bad_result.s); + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 7); + + // Test result = 8 (failed ecdsa_read_pubkey) + // TBD + + // Test result = 10 (r != r') + memcpy(bad_result.r, result.r, 32); + memcpy(bad_result.s, result.s, 32); + test_case.message = "12"; + res = zil_schnorr_verify_pair(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 10); +} +END_TEST diff --git a/trezor-crypto/crypto/tests/test_openssl.c b/trezor-crypto/crypto/tests/test_openssl.c index 4b38658f811..b9390846cb9 100644 --- a/trezor-crypto/crypto/tests/test_openssl.c +++ b/trezor-crypto/crypto/tests/test_openssl.c @@ -79,8 +79,15 @@ void openssl_check(unsigned int iterations, int nid, const ecdsa_curve *curve) { } // generate public key from private key - ecdsa_get_public_key33(curve, priv_key, pub_key33); - ecdsa_get_public_key65(curve, priv_key, pub_key65); + if (ecdsa_get_public_key33(curve, priv_key, pub_key33) != 0) { + printf("ecdsa_get_public_key33 failed\n"); + return; + } + + if (ecdsa_get_public_key65(curve, priv_key, pub_key65) != 0) { + printf("ecdsa_get_public_key65 failed\n"); + return; + } // use our ECDSA verifier to verify the message signature if (ecdsa_verify(curve, HASHER_SHA2, pub_key65, sig, msg, msg_len) != 0) { diff --git a/trezor-crypto/crypto/tests/test_speed.c b/trezor-crypto/crypto/tests/test_speed.c index 8a675eca576..a82f67f3e65 100644 --- a/trezor-crypto/crypto/tests/test_speed.c +++ b/trezor-crypto/crypto/tests/test_speed.c @@ -11,7 +11,7 @@ #include "nist256p1.h" #include -uint8_t msg[256]; +static uint8_t msg[256]; void prepare_msg(void) { for (size_t i = 0; i < sizeof(msg); i++) { @@ -50,18 +50,16 @@ void bench_sign_nist256p1(int iterations) { } void bench_sign_ed25519(int iterations) { - ed25519_public_key pk; ed25519_secret_key sk; ed25519_signature sig; - memcpy(pk, + memcpy(sk, "\xc5\x5e\xce\x85\x8b\x0d\xdd\x52\x63\xf9\x68\x10\xfe\x14\x43\x7c\xd3" "\xb5\xe1\xfb\xd7\xc6\xa2\xec\x1e\x03\x1f\x05\xe8\x6d\x8b\xd5", 32); - ed25519_publickey(sk, pk); for (int i = 0; i < iterations; i++) { - ed25519_sign(msg, sizeof(msg), sk, pk, sig); + ed25519_sign(msg, sizeof(msg), sk, sig); } } @@ -138,12 +136,12 @@ void bench_verify_ed25519(int iterations) { ed25519_secret_key sk; ed25519_signature sig; - memcpy(pk, + memcpy(sk, "\xc5\x5e\xce\x85\x8b\x0d\xdd\x52\x63\xf9\x68\x10\xfe\x14\x43\x7c\xd3" "\xb5\xe1\xfb\xd7\xc6\xa2\xec\x1e\x03\x1f\x05\xe8\x6d\x8b\xd5", 32); ed25519_publickey(sk, pk); - ed25519_sign(msg, sizeof(msg), sk, pk, sig); + ed25519_sign(msg, sizeof(msg), sk, sig); for (int i = 0; i < iterations; i++) { ed25519_sign_open(msg, sizeof(msg), pk, sig); @@ -169,7 +167,7 @@ void bench_multiply_curve25519(int iterations) { } } -HDNode root; +static HDNode root; void prepare_node(void) { hdnode_from_seed((uint8_t *)"NothingToSeeHere", 16, SECP256K1_NAME, &root); diff --git a/trezor-crypto/crypto/zilliqa.c b/trezor-crypto/crypto/zilliqa.c new file mode 100644 index 00000000000..5d30b56bf6c --- /dev/null +++ b/trezor-crypto/crypto/zilliqa.c @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2019 Anatolii Kurotych + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "string.h" + +#include +#include +#include +#include + +int zil_schnorr_sign(const ecdsa_curve *curve, const uint8_t *priv_key, const uint8_t *msg, const uint32_t msg_len, uint8_t *sig) +{ + int i; + bignum256 k; + + uint8_t hash[32]; + sha256_Raw(msg, msg_len, hash); + + rfc6979_state rng; + init_rfc6979(priv_key, hash, curve, &rng); + + for (i = 0; i < 10000; i++) { + // generate K deterministically + generate_k_rfc6979(&k, &rng); + // if k is too big or too small, we don't like it + if (bn_is_zero(&k) || !bn_is_less(&k, &curve->order)) { + continue; + } + + schnorr_sign_pair sign; + if (zil_schnorr_sign_k(curve, priv_key, &k, msg, msg_len, &sign) != 0) { + continue; + } + + // we're done + memcpy(sig, sign.r, 32); + memcpy(sig + 32, sign.s, 32); + + memzero(&k, sizeof(k)); + memzero(&rng, sizeof(rng)); + memzero(&sign, sizeof(sign)); + return 0; + } + + // Too many retries without a valid signature + // -> fail with an error + memzero(&k, sizeof(k)); + memzero(&rng, sizeof(rng)); + return -1; +} + +// r = H(Q, kpub, m) +static void calc_r(const curve_point *Q, const uint8_t pub_key[33], + const uint8_t *msg, const uint32_t msg_len, bignum256 *r) { + uint8_t Q_compress[33]; + compress_coords(Q, Q_compress); + + SHA256_CTX ctx; + uint8_t digest[SHA256_DIGEST_LENGTH]; + sha256_Init(&ctx); + sha256_Update(&ctx, Q_compress, 33); + sha256_Update(&ctx, pub_key, 33); + sha256_Update(&ctx, msg, msg_len); + sha256_Final(&ctx, digest); + + // Convert the raw bigendian 256 bit value to a normalized, partly reduced bignum + bn_read_be(digest, r); +} + +// Returns 0 if signing succeeded +int zil_schnorr_sign_k(const ecdsa_curve *curve, const uint8_t *priv_key, + const bignum256 *k, const uint8_t *msg, const uint32_t msg_len, + schnorr_sign_pair *result) { + uint8_t pub_key[33]; + curve_point Q; + bignum256 private_key_scalar; + bignum256 r_temp; + bignum256 s_temp; + bignum256 r_kpriv_result; + + bn_read_be(priv_key, &private_key_scalar); + ecdsa_get_public_key33(curve, priv_key, pub_key); + + // Compute commitment Q = kG + point_multiply(curve, k, &curve->G, &Q); + + // Compute challenge r = H(Q, kpub, m) + calc_r(&Q, pub_key, msg, msg_len, &r_temp); + + // Fully reduce the bignum + bn_mod(&r_temp, &curve->order); + + // Convert the normalized, fully reduced bignum to a raw bigendian 256 bit value + bn_write_be(&r_temp, result->r); + + // Compute s = k - r*kpriv + bn_copy(&r_temp, &r_kpriv_result); + + // r*kpriv result is partly reduced + bn_multiply(&private_key_scalar, &r_kpriv_result, &curve->order); + + // k - r*kpriv result is normalized but not reduced + bn_subtractmod(k, &r_kpriv_result, &s_temp, &curve->order); + + // Partly reduce the result + bn_fast_mod(&s_temp, &curve->order); + + // Fully reduce the result + bn_mod(&s_temp, &curve->order); + + // Convert the normalized, fully reduced bignum to a raw bigendian 256 bit value + bn_write_be(&s_temp, result->s); + + if (bn_is_zero(&r_temp) || bn_is_zero(&s_temp)) return 1; + + return 0; +} + +int zil_schnorr_verify(const ecdsa_curve *curve, const uint8_t *pub_key, const uint8_t *sig, const uint8_t *msg, const uint32_t msg_len) +{ + schnorr_sign_pair sign; + + memcpy(sign.r, sig, 32); + memcpy(sign.s, sig + 32, 32); + + return zil_schnorr_verify_pair(curve, pub_key, msg, msg_len, &sign); +} + +// Returns 0 if verification succeeded +int zil_schnorr_verify_pair(const ecdsa_curve *curve, const uint8_t *pub_key, + const uint8_t *msg, const uint32_t msg_len, + const schnorr_sign_pair *sign) { + curve_point pub_key_point; + curve_point sG, Q; + bignum256 r_temp; + bignum256 s_temp; + bignum256 r_computed; + + if (msg_len == 0) return 1; + + // Convert the raw bigendian 256 bit values to normalized, partly reduced bignums + bn_read_be(sign->r, &r_temp); + bn_read_be(sign->s, &s_temp); + + // Check if r,s are in [1, ..., order-1] + if (bn_is_zero(&r_temp)) return 2; + if (bn_is_zero(&s_temp)) return 3; + if (bn_is_less(&curve->order, &r_temp)) return 4; + if (bn_is_less(&curve->order, &s_temp)) return 5; + if (bn_is_equal(&curve->order, &r_temp)) return 6; + if (bn_is_equal(&curve->order, &s_temp)) return 7; + + if (!ecdsa_read_pubkey(curve, pub_key, &pub_key_point)) { + return 8; + } + + // Compute Q = sG + r*kpub + point_multiply(curve, &s_temp, &curve->G, &sG); + point_multiply(curve, &r_temp, &pub_key_point, &Q); + point_add(curve, &sG, &Q); + + // Compute r' = H(Q, kpub, m) + calc_r(&Q, pub_key, msg, msg_len, &r_computed); + + // Fully reduce the bignum + bn_mod(&r_computed, &curve->order); + + // Check r == r' + if (bn_is_equal(&r_temp, &r_computed)) return 0; // success + + return 10; +} diff --git a/trezor-crypto/include/TrezorCrypto/TrezorCrypto.h b/trezor-crypto/include/TrezorCrypto/TrezorCrypto.h index 8aa414c9a50..10910c17b2c 100644 --- a/trezor-crypto/include/TrezorCrypto/TrezorCrypto.h +++ b/trezor-crypto/include/TrezorCrypto/TrezorCrypto.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include diff --git a/trezor-crypto/include/TrezorCrypto/aes/aesopt.h b/trezor-crypto/include/TrezorCrypto/aes/aesopt.h index e8d9db4b4c0..5e8763fa545 100644 --- a/trezor-crypto/include/TrezorCrypto/aes/aesopt.h +++ b/trezor-crypto/include/TrezorCrypto/aes/aesopt.h @@ -363,10 +363,10 @@ Issue Date: 20/12/2007 /* 10. TABLE ALIGNMENT - On some sytsems speed will be improved by aligning the AES large lookup + On some systems speed will be improved by aligning the AES large lookup tables on particular boundaries. This define should be set to a power of two giving the desired alignment. It can be left undefined if alignment - is not needed. This option is specific to the Microsft VC++ compiler - + is not needed. This option is specific to the Microsoft VC++ compiler - it seems to sometimes cause trouble for the VC++ version 6 compiler. */ diff --git a/trezor-crypto/include/TrezorCrypto/bip32.h b/trezor-crypto/include/TrezorCrypto/bip32.h index 99b35762469..4b698bcc600 100644 --- a/trezor-crypto/include/TrezorCrypto/bip32.h +++ b/trezor-crypto/include/TrezorCrypto/bip32.h @@ -79,14 +79,6 @@ int hdnode_from_seed(const uint8_t *seed, int seed_len, const char *curve, int hdnode_private_ckd(HDNode *inout, uint32_t i); -#if USE_CARDANO -int hdnode_private_ckd_cardano(HDNode *inout, uint32_t i); -int hdnode_from_seed_cardano(const uint8_t *seed, int seed_len, HDNode *out); -int hdnode_from_entropy_cardano_icarus(const uint8_t *pass, int pass_len, - const uint8_t *seed, int seed_len, - HDNode *out); -#endif - int hdnode_public_ckd_cp(const ecdsa_curve *curve, const curve_point *parent, const uint8_t *parent_chain_code, uint32_t i, curve_point *child, uint8_t *child_chain_code); @@ -101,13 +93,14 @@ void hdnode_public_ckd_address_optimized(const curve_point *pub, int addrsize, int addrformat); #if USE_BIP32_CACHE +void bip32_cache_clear(void); int hdnode_private_ckd_cached(HDNode *inout, const uint32_t *i, size_t i_count, uint32_t *fingerprint); #endif uint32_t hdnode_fingerprint(HDNode *node); -void hdnode_fill_public_key(HDNode *node); +int hdnode_fill_public_key(HDNode *node); #if USE_ETHEREUM int hdnode_get_ethereum_pubkeyhash(const HDNode *node, uint8_t *pubkeyhash); @@ -151,9 +144,9 @@ int hdnode_deserialize_private(const char *str, uint32_t version, const char *curve, HDNode *node, uint32_t *fingerprint); -void hdnode_get_address_raw(HDNode *node, uint32_t version, uint8_t *addr_raw); -void hdnode_get_address(HDNode *node, uint32_t version, char *addr, - int addrsize); +int hdnode_get_address_raw(HDNode *node, uint32_t version, uint8_t *addr_raw); +int hdnode_get_address(HDNode *node, uint32_t version, char *addr, + int addrsize); const curve_info *get_curve_by_name(const char *curve_name); diff --git a/trezor-crypto/include/TrezorCrypto/bip39.h b/trezor-crypto/include/TrezorCrypto/bip39.h index cd073a2d62e..46ec5b0229c 100644 --- a/trezor-crypto/include/TrezorCrypto/bip39.h +++ b/trezor-crypto/include/TrezorCrypto/bip39.h @@ -24,25 +24,30 @@ #ifndef __BIP39_H__ #define __BIP39_H__ -#include -#include - #ifdef __cplusplus extern "C" { #endif -#define BIP39_WORDS 2048 +#include +#include + +#include + +#define BIP39_WORD_COUNT 2048 #define BIP39_PBKDF2_ROUNDS 2048 -#define BIP39_MAX_WORDS 24 // [wallet-core] -#define BIP39_MAX_WORD_LENGTH 9 // [wallet-core] +#if USE_BIP39_CACHE +void bip39_cache_clear(void); +#endif + +// [wallet-core] +#define BIP39_MAX_WORDS 24 +#define BIP39_MAX_WORD_LENGTH 9 // [wallet-core] Added output buffer -const char *mnemonic_generate(int strength, char *buf, int buflen); // strength in bits -// [wallet-core] Added output buffer +const char *mnemonic_generate(int strength, char *buf, int buflen); // strength in bits const char *mnemonic_from_data(const uint8_t *data, int datalen, char *buf, int buflen); -// [wallet-core] No longer used -//void mnemonic_clear(void); +void mnemonic_clear(void); int mnemonic_check(const char *mnemonic); @@ -54,13 +59,13 @@ void mnemonic_to_seed(const char *mnemonic, const char *passphrase, void (*progress_callback)(uint32_t current, uint32_t total)); -#ifdef __cplusplus -} /* extern "C" */ -#endif - int mnemonic_find_word(const char *word); const char *mnemonic_complete_word(const char *prefix, int len); const char *mnemonic_get_word(int index); uint32_t mnemonic_word_completion_mask(const char *prefix, int len); +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif diff --git a/trezor-crypto/include/TrezorCrypto/cardano.h b/trezor-crypto/include/TrezorCrypto/cardano.h new file mode 100644 index 00000000000..3e9986c52bd --- /dev/null +++ b/trezor-crypto/include/TrezorCrypto/cardano.h @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2013-2021 SatoshiLabs + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __CARDANO_H__ +#define __CARDANO_H__ + +#if defined(__cplusplus) +extern "C" +{ +#endif + +#include +#include +#include +#include + +#if USE_CARDANO + +#define CARDANO_SECRET_LENGTH 96 +#define CARDANO_ICARUS_PBKDF2_ROUNDS 4096 + +extern const curve_info ed25519_cardano_info; + +int hdnode_private_ckd_cardano(HDNode *inout, uint32_t i); + +int secret_from_entropy_cardano_icarus( + const uint8_t *pass, int pass_len, const uint8_t *entropy, int entropy_len, + uint8_t secret_out[CARDANO_SECRET_LENGTH], + void (*progress_callback)(uint32_t current, uint32_t total)); +int secret_from_seed_cardano_ledger(const uint8_t *seed, int seed_len, + uint8_t secret_out[CARDANO_SECRET_LENGTH]); +int secret_from_seed_cardano_slip23(const uint8_t *seed, int seed_len, + uint8_t secret_out[CARDANO_SECRET_LENGTH]); + +int hdnode_from_secret_cardano(const uint8_t secret[CARDANO_SECRET_LENGTH], + HDNode *out); + +#endif // USE_CARDANO + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif // __CARDANO_H__ diff --git a/trezor-crypto/include/TrezorCrypto/chacha20poly1305/ecrypt-sync.h b/trezor-crypto/include/TrezorCrypto/chacha20poly1305/ecrypt-sync.h index 39be5c9bc88..efce9dde273 100644 --- a/trezor-crypto/include/TrezorCrypto/chacha20poly1305/ecrypt-sync.h +++ b/trezor-crypto/include/TrezorCrypto/chacha20poly1305/ecrypt-sync.h @@ -90,12 +90,21 @@ void ECRYPT_keysetup( * IV setup. After having called ECRYPT_keysetup(), the user is * allowed to call ECRYPT_ivsetup() different times in order to * encrypt/decrypt different messages with the same key but different - * IV's. + * IV's. ECRYPT_ivsetup() also sets block counter to zero. */ void ECRYPT_ivsetup( ECRYPT_ctx* ctx, const u8* iv); +/* + * Block counter setup. It is used only for special purposes, + * since block counter is usually initialized with ECRYPT_ivsetup. + * ECRYPT_ctrsetup has to be called after ECRYPT_ivsetup. + */ +void ECRYPT_ctrsetup( + ECRYPT_ctx* ctx, + const u8* ctr); + /* * Encryption/decryption of arbitrary length messages. * diff --git a/trezor-crypto/include/TrezorCrypto/chacha_drbg.h b/trezor-crypto/include/TrezorCrypto/chacha_drbg.h index 416ace82414..0676d126cd3 100644 --- a/trezor-crypto/include/TrezorCrypto/chacha_drbg.h +++ b/trezor-crypto/include/TrezorCrypto/chacha_drbg.h @@ -21,23 +21,34 @@ #define __CHACHA_DRBG__ #include +#include -// Very fast deterministic random bit generator inspired by CTR_DRBG in NIST SP -// 800-90A +// A very fast deterministic random bit generator based on CTR_DRBG in NIST SP +// 800-90A. Chacha is used instead of a block cipher in the counter mode, SHA256 +// is used as a derivation function. The highest supported security strength is +// at least 256 bits. Reseeding is left up to caller. -#define CHACHA_DRBG_KEY_LENGTH 16 -#define CHACHA_DRBG_IV_LENGTH 8 -#define CHACHA_DRBG_SEED_LENGTH (CHACHA_DRBG_KEY_LENGTH + CHACHA_DRBG_IV_LENGTH) +// Length of inputs of chacha_drbg_init (entropy and nonce) or +// chacha_drbg_reseed (entropy and additional_input) that fill exactly +// block_count blocks of hash function in derivation_function. There is no need +// the input to have this length, it's just an optimalization. +#define CHACHA_DRBG_OPTIMAL_RESEED_LENGTH(block_count) \ + ((block_count)*SHA256_BLOCK_LENGTH - 1 - 4 - 9) +// 1 = sizeof(counter), 4 = sizeof(output_length) in +// derivation_function, 9 is length of SHA256 padding of message +// aligned to bytes typedef struct _CHACHA_DRBG_CTX { ECRYPT_ctx chacha_ctx; uint32_t reseed_counter; } CHACHA_DRBG_CTX; -void chacha_drbg_init(CHACHA_DRBG_CTX *ctx, - const uint8_t entropy[CHACHA_DRBG_SEED_LENGTH]); -void chacha_drbg_reseed(CHACHA_DRBG_CTX *ctx, - const uint8_t entropy[CHACHA_DRBG_SEED_LENGTH]); +void chacha_drbg_init(CHACHA_DRBG_CTX *ctx, const uint8_t *entropy, + size_t entropy_length, const uint8_t *nonce, + size_t nonce_length); void chacha_drbg_generate(CHACHA_DRBG_CTX *ctx, uint8_t *output, - uint8_t output_length); + size_t output_length); +void chacha_drbg_reseed(CHACHA_DRBG_CTX *ctx, const uint8_t *entropy, + size_t entropy_length, const uint8_t *additional_input, + size_t additional_input_length); #endif // __CHACHA_DRBG__ diff --git a/trezor-crypto/include/TrezorCrypto/curves.h b/trezor-crypto/include/TrezorCrypto/curves.h index d65d1fe953f..d8d423563c6 100644 --- a/trezor-crypto/include/TrezorCrypto/curves.h +++ b/trezor-crypto/include/TrezorCrypto/curves.h @@ -35,16 +35,16 @@ extern const char SECP256K1_GROESTL_NAME[]; extern const char SECP256K1_SMART_NAME[]; extern const char NIST256P1_NAME[]; extern const char ED25519_NAME[]; -// [wallet-core] +extern const char ED25519_SEED_NAME[]; extern const char ED25519_CARDANO_NAME[]; -// [wallet-core] -extern const char ED25519_BLAKE2B_NANO_NAME[]; extern const char ED25519_SHA3_NAME[]; #if USE_KECCAK extern const char ED25519_KECCAK_NAME[]; #endif extern const char CURVE25519_NAME[]; +extern const char ED25519_BLAKE2B_NANO_NAME[]; // [wallet-core] + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/trezor-crypto/include/TrezorCrypto/ecdsa.h b/trezor-crypto/include/TrezorCrypto/ecdsa.h index 4d9046d5c82..48033642325 100644 --- a/trezor-crypto/include/TrezorCrypto/ecdsa.h +++ b/trezor-crypto/include/TrezorCrypto/ecdsa.h @@ -70,14 +70,14 @@ void point_copy(const curve_point *cp1, curve_point *cp2); void point_add(const ecdsa_curve *curve, const curve_point *cp1, curve_point *cp2); void point_double(const ecdsa_curve *curve, curve_point *cp); -void point_multiply(const ecdsa_curve *curve, const bignum256 *k, - const curve_point *p, curve_point *res); +int point_multiply(const ecdsa_curve *curve, const bignum256 *k, + const curve_point *p, curve_point *res); void point_set_infinity(curve_point *p); int point_is_infinity(const curve_point *p); int point_is_equal(const curve_point *p, const curve_point *q); int point_is_negative_of(const curve_point *p, const curve_point *q); -void scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, - curve_point *res); +int scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, + curve_point *res); int ecdh_multiply(const ecdsa_curve *curve, const uint8_t *priv_key, const uint8_t *pub_key, uint8_t *session_key); void compress_coords(const curve_point *cp, uint8_t *compressed); @@ -93,10 +93,10 @@ int ecdsa_sign(const ecdsa_curve *curve, HasherType hasher_sign, int ecdsa_sign_digest(const ecdsa_curve *curve, const uint8_t *priv_key, const uint8_t *digest, uint8_t *sig, uint8_t *pby, int (*is_canonical)(uint8_t by, uint8_t sig[64])); -void ecdsa_get_public_key33(const ecdsa_curve *curve, const uint8_t *priv_key, - uint8_t *pub_key); -void ecdsa_get_public_key65(const ecdsa_curve *curve, const uint8_t *priv_key, - uint8_t *pub_key); +int ecdsa_get_public_key33(const ecdsa_curve *curve, const uint8_t *priv_key, + uint8_t *pub_key); +int ecdsa_get_public_key65(const ecdsa_curve *curve, const uint8_t *priv_key, + uint8_t *pub_key); void ecdsa_get_pubkeyhash(const uint8_t *pub_key, HasherType hasher_pubkey, uint8_t *pubkeyhash); void ecdsa_get_address_raw(const uint8_t *pub_key, uint32_t version, @@ -130,10 +130,6 @@ int ecdsa_recover_pub_from_sig(const ecdsa_curve *curve, uint8_t *pub_key, int ecdsa_sig_to_der(const uint8_t *sig, uint8_t *der); int ecdsa_sig_from_der(const uint8_t *der, size_t der_len, uint8_t sig[64]); -// [wallet-core] -int zil_schnorr_sign(const ecdsa_curve *curve, const uint8_t *priv_key, const uint8_t *msg, const uint32_t msg_len, uint8_t *sig); -int zil_schnorr_verify(const ecdsa_curve *curve, const uint8_t *pub_key, const uint8_t *sig, const uint8_t *msg, const uint32_t msg_len); - #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-blake2b.h b/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-blake2b.h index 9dddc5f7401..f9bd619e93c 100644 --- a/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-blake2b.h +++ b/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-blake2b.h @@ -10,7 +10,7 @@ extern "C" { void ed25519_publickey_blake2b(const ed25519_secret_key sk, ed25519_public_key pk); int ed25519_sign_open_blake2b(const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS); -void ed25519_sign_blake2b(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_public_key pk, ed25519_signature RS); +void ed25519_sign_blake2b(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, ed25519_signature RS); int ed25519_scalarmult_blake2b(ed25519_public_key res, const ed25519_secret_key sk, const ed25519_public_key pk); diff --git a/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-keccak.h b/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-keccak.h index fa63770e92b..58fd8355ae4 100644 --- a/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-keccak.h +++ b/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-keccak.h @@ -10,7 +10,7 @@ extern "C" { void ed25519_publickey_keccak(const ed25519_secret_key sk, ed25519_public_key pk); int ed25519_sign_open_keccak(const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS); -void ed25519_sign_keccak(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_public_key pk, ed25519_signature RS); +void ed25519_sign_keccak(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, ed25519_signature RS); int ed25519_scalarmult_keccak(ed25519_public_key res, const ed25519_secret_key sk, const ed25519_public_key pk); diff --git a/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-sha3.h b/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-sha3.h index e8a62d0dca3..3adcf84891f 100644 --- a/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-sha3.h +++ b/trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-sha3.h @@ -10,7 +10,7 @@ extern "C" { void ed25519_publickey_sha3(const ed25519_secret_key sk, ed25519_public_key pk); int ed25519_sign_open_sha3(const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS); -void ed25519_sign_sha3(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_public_key pk, ed25519_signature RS); +void ed25519_sign_sha3(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, ed25519_signature RS); int ed25519_scalarmult_sha3(ed25519_public_key res, const ed25519_secret_key sk, const ed25519_public_key pk); diff --git a/trezor-crypto/include/TrezorCrypto/ed25519.h b/trezor-crypto/include/TrezorCrypto/ed25519.h index 78a27a279ed..6b4c098b963 100644 --- a/trezor-crypto/include/TrezorCrypto/ed25519.h +++ b/trezor-crypto/include/TrezorCrypto/ed25519.h @@ -16,15 +16,11 @@ typedef unsigned char curve25519_key[32]; typedef unsigned char ed25519_cosi_signature[32]; void ed25519_publickey(const ed25519_secret_key sk, ed25519_public_key pk); -#if USE_CARDANO -void ed25519_publickey_ext(const ed25519_secret_key sk, const ed25519_secret_key skext, ed25519_public_key pk); -#endif +void ed25519_publickey_ext(const ed25519_secret_key extsk, ed25519_public_key pk); int ed25519_sign_open(const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS); -void ed25519_sign(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_public_key pk, ed25519_signature RS); -#if USE_CARDANO -void ed25519_sign_ext(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_secret_key skext, const ed25519_public_key pk, ed25519_signature RS); -#endif +void ed25519_sign(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, ed25519_signature RS); +void ed25519_sign_ext(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_secret_key skext, ed25519_signature RS); int ed25519_scalarmult(ed25519_public_key res, const ed25519_secret_key sk, const ed25519_public_key pk); diff --git a/trezor-crypto/include/TrezorCrypto/groestl.h b/trezor-crypto/include/TrezorCrypto/groestl.h index e18f96910bb..8335abf46a1 100644 --- a/trezor-crypto/include/TrezorCrypto/groestl.h +++ b/trezor-crypto/include/TrezorCrypto/groestl.h @@ -86,7 +86,7 @@ void groestl512_Update(void *cc, const void *data, size_t len); /** * Terminate the current Groestl-512 computation and output the result into * the provided buffer. The destination buffer must be wide enough to - * accomodate the result (64 bytes). The context is automatically + * accommodate the result (64 bytes). The context is automatically * reinitialized. * * @param cc the Groestl-512 context diff --git a/trezor-crypto/include/TrezorCrypto/rfc6979.h b/trezor-crypto/include/TrezorCrypto/rfc6979.h index 3e409535093..e4cb9ff049f 100644 --- a/trezor-crypto/include/TrezorCrypto/rfc6979.h +++ b/trezor-crypto/include/TrezorCrypto/rfc6979.h @@ -27,13 +27,14 @@ #include #include "bignum.h" +#include "ecdsa.h" #include "hmac_drbg.h" // rfc6979 pseudo random number generator state typedef HMAC_DRBG_CTX rfc6979_state; void init_rfc6979(const uint8_t *priv_key, const uint8_t *hash, - rfc6979_state *rng); + const ecdsa_curve *curve, rfc6979_state *rng); void generate_rfc6979(uint8_t rnd[32], rfc6979_state *rng); void generate_k_rfc6979(bignum256 *k, rfc6979_state *rng); diff --git a/trezor-crypto/include/TrezorCrypto/schnorr.h b/trezor-crypto/include/TrezorCrypto/schnorr.h deleted file mode 100644 index 4091c807446..00000000000 --- a/trezor-crypto/include/TrezorCrypto/schnorr.h +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2019 Anatolii Kurotych - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifndef __SCHNORR_H__ -#define __SCHNORR_H__ - -#include - -#if defined(__cplusplus) -extern "C" -{ -#endif - -// result of sign operation -typedef struct { - uint8_t r[32]; - uint8_t s[32]; -} schnorr_sign_pair; - -// sign/verify returns 0 if operation succeeded - -// k is a random from [1, ..., order-1] -int schnorr_sign(const ecdsa_curve *curve, const uint8_t *priv_key, - const bignum256 *k, const uint8_t *msg, const uint32_t msg_len, - schnorr_sign_pair *result); -int schnorr_verify(const ecdsa_curve *curve, const uint8_t *pub_key, - const uint8_t *msg, const uint32_t msg_len, - const schnorr_sign_pair *sign); -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/trezor-crypto/include/TrezorCrypto/slip39.h b/trezor-crypto/include/TrezorCrypto/slip39.h new file mode 100644 index 00000000000..08883edf2b5 --- /dev/null +++ b/trezor-crypto/include/TrezorCrypto/slip39.h @@ -0,0 +1,47 @@ +/** + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __SLIP39_H__ +#define __SLIP39_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +const char* get_word(uint16_t index); + +bool word_index(uint16_t* index, const char* word, uint8_t word_length); + +uint16_t slip39_word_completion_mask(uint16_t prefix); + +const char* button_sequence_to_word(uint16_t prefix); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/trezor-crypto/include/TrezorCrypto/slip39_wordlist.h b/trezor-crypto/include/TrezorCrypto/slip39_wordlist.h new file mode 100644 index 00000000000..3464aae9412 --- /dev/null +++ b/trezor-crypto/include/TrezorCrypto/slip39_wordlist.h @@ -0,0 +1,1246 @@ +/** + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __SLIP39_WORDLIST_H__ +#define __SLIP39_WORDLIST_H__ + +#include + +#define WORDS_COUNT 1024 + +static const char* const slip39_wordlist[WORDS_COUNT] = { + "academic", "acid", "acne", "acquire", "acrobat", "activity", + "actress", "adapt", "adequate", "adjust", "admit", "adorn", + "adult", "advance", "advocate", "afraid", "again", "agency", + "agree", "aide", "aircraft", "airline", "airport", "ajar", + "alarm", "album", "alcohol", "alien", "alive", "alpha", + "already", "alto", "aluminum", "always", "amazing", "ambition", + "amount", "amuse", "analysis", "anatomy", "ancestor", "ancient", + "angel", "angry", "animal", "answer", "antenna", "anxiety", + "apart", "aquatic", "arcade", "arena", "argue", "armed", + "artist", "artwork", "aspect", "auction", "august", "aunt", + "average", "aviation", "avoid", "award", "away", "axis", + "axle", "beam", "beard", "beaver", "become", "bedroom", + "behavior", "being", "believe", "belong", "benefit", "best", + "beyond", "bike", "biology", "birthday", "bishop", "black", + "blanket", "blessing", "blimp", "blind", "blue", "body", + "bolt", "boring", "born", "both", "boundary", "bracelet", + "branch", "brave", "breathe", "briefing", "broken", "brother", + "browser", "bucket", "budget", "building", "bulb", "bulge", + "bumpy", "bundle", "burden", "burning", "busy", "buyer", + "cage", "calcium", "camera", "campus", "canyon", "capacity", + "capital", "capture", "carbon", "cards", "careful", "cargo", + "carpet", "carve", "category", "cause", "ceiling", "center", + "ceramic", "champion", "change", "charity", "check", "chemical", + "chest", "chew", "chubby", "cinema", "civil", "class", + "clay", "cleanup", "client", "climate", "clinic", "clock", + "clogs", "closet", "clothes", "club", "cluster", "coal", + "coastal", "coding", "column", "company", "corner", "costume", + "counter", "course", "cover", "cowboy", "cradle", "craft", + "crazy", "credit", "cricket", "criminal", "crisis", "critical", + "crowd", "crucial", "crunch", "crush", "crystal", "cubic", + "cultural", "curious", "curly", "custody", "cylinder", "daisy", + "damage", "dance", "darkness", "database", "daughter", "deadline", + "deal", "debris", "debut", "decent", "decision", "declare", + "decorate", "decrease", "deliver", "demand", "density", "deny", + "depart", "depend", "depict", "deploy", "describe", "desert", + "desire", "desktop", "destroy", "detailed", "detect", "device", + "devote", "diagnose", "dictate", "diet", "dilemma", "diminish", + "dining", "diploma", "disaster", "discuss", "disease", "dish", + "dismiss", "display", "distance", "dive", "divorce", "document", + "domain", "domestic", "dominant", "dough", "downtown", "dragon", + "dramatic", "dream", "dress", "drift", "drink", "drove", + "drug", "dryer", "duckling", "duke", "duration", "dwarf", + "dynamic", "early", "earth", "easel", "easy", "echo", + "eclipse", "ecology", "edge", "editor", "educate", "either", + "elbow", "elder", "election", "elegant", "element", "elephant", + "elevator", "elite", "else", "email", "emerald", "emission", + "emperor", "emphasis", "employer", "empty", "ending", "endless", + "endorse", "enemy", "energy", "enforce", "engage", "enjoy", + "enlarge", "entrance", "envelope", "envy", "epidemic", "episode", + "equation", "equip", "eraser", "erode", "escape", "estate", + "estimate", "evaluate", "evening", "evidence", "evil", "evoke", + "exact", "example", "exceed", "exchange", "exclude", "excuse", + "execute", "exercise", "exhaust", "exotic", "expand", "expect", + "explain", "express", "extend", "extra", "eyebrow", "facility", + "fact", "failure", "faint", "fake", "false", "family", + "famous", "fancy", "fangs", "fantasy", "fatal", "fatigue", + "favorite", "fawn", "fiber", "fiction", "filter", "finance", + "findings", "finger", "firefly", "firm", "fiscal", "fishing", + "fitness", "flame", "flash", "flavor", "flea", "flexible", + "flip", "float", "floral", "fluff", "focus", "forbid", + "force", "forecast", "forget", "formal", "fortune", "forward", + "founder", "fraction", "fragment", "frequent", "freshman", "friar", + "fridge", "friendly", "frost", "froth", "frozen", "fumes", + "funding", "furl", "fused", "galaxy", "game", "garbage", + "garden", "garlic", "gasoline", "gather", "general", "genius", + "genre", "genuine", "geology", "gesture", "glad", "glance", + "glasses", "glen", "glimpse", "goat", "golden", "graduate", + "grant", "grasp", "gravity", "gray", "greatest", "grief", + "grill", "grin", "grocery", "gross", "group", "grownup", + "grumpy", "guard", "guest", "guilt", "guitar", "gums", + "hairy", "hamster", "hand", "hanger", "harvest", "have", + "havoc", "hawk", "hazard", "headset", "health", "hearing", + "heat", "helpful", "herald", "herd", "hesitate", "hobo", + "holiday", "holy", "home", "hormone", "hospital", "hour", + "huge", "human", "humidity", "hunting", "husband", "hush", + "husky", "hybrid", "idea", "identify", "idle", "image", + "impact", "imply", "improve", "impulse", "include", "income", + "increase", "index", "indicate", "industry", "infant", "inform", + "inherit", "injury", "inmate", "insect", "inside", "install", + "intend", "intimate", "invasion", "involve", "iris", "island", + "isolate", "item", "ivory", "jacket", "jerky", "jewelry", + "join", "judicial", "juice", "jump", "junction", "junior", + "junk", "jury", "justice", "kernel", "keyboard", "kidney", + "kind", "kitchen", "knife", "knit", "laden", "ladle", + "ladybug", "lair", "lamp", "language", "large", "laser", + "laundry", "lawsuit", "leader", "leaf", "learn", "leaves", + "lecture", "legal", "legend", "legs", "lend", "length", + "level", "liberty", "library", "license", "lift", "likely", + "lilac", "lily", "lips", "liquid", "listen", "literary", + "living", "lizard", "loan", "lobe", "location", "losing", + "loud", "loyalty", "luck", "lunar", "lunch", "lungs", + "luxury", "lying", "lyrics", "machine", "magazine", "maiden", + "mailman", "main", "makeup", "making", "mama", "manager", + "mandate", "mansion", "manual", "marathon", "march", "market", + "marvel", "mason", "material", "math", "maximum", "mayor", + "meaning", "medal", "medical", "member", "memory", "mental", + "merchant", "merit", "method", "metric", "midst", "mild", + "military", "mineral", "minister", "miracle", "mixed", "mixture", + "mobile", "modern", "modify", "moisture", "moment", "morning", + "mortgage", "mother", "mountain", "mouse", "move", "much", + "mule", "multiple", "muscle", "museum", "music", "mustang", + "nail", "national", "necklace", "negative", "nervous", "network", + "news", "nuclear", "numb", "numerous", "nylon", "oasis", + "obesity", "object", "observe", "obtain", "ocean", "often", + "olympic", "omit", "oral", "orange", "orbit", "order", + "ordinary", "organize", "ounce", "oven", "overall", "owner", + "paces", "pacific", "package", "paid", "painting", "pajamas", + "pancake", "pants", "papa", "paper", "parcel", "parking", + "party", "patent", "patrol", "payment", "payroll", "peaceful", + "peanut", "peasant", "pecan", "penalty", "pencil", "percent", + "perfect", "permit", "petition", "phantom", "pharmacy", "photo", + "phrase", "physics", "pickup", "picture", "piece", "pile", + "pink", "pipeline", "pistol", "pitch", "plains", "plan", + "plastic", "platform", "playoff", "pleasure", "plot", "plunge", + "practice", "prayer", "preach", "predator", "pregnant", "premium", + "prepare", "presence", "prevent", "priest", "primary", "priority", + "prisoner", "privacy", "prize", "problem", "process", "profile", + "program", "promise", "prospect", "provide", "prune", "public", + "pulse", "pumps", "punish", "puny", "pupal", "purchase", + "purple", "python", "quantity", "quarter", "quick", "quiet", + "race", "racism", "radar", "railroad", "rainbow", "raisin", + "random", "ranked", "rapids", "raspy", "reaction", "realize", + "rebound", "rebuild", "recall", "receiver", "recover", "regret", + "regular", "reject", "relate", "remember", "remind", "remove", + "render", "repair", "repeat", "replace", "require", "rescue", + "research", "resident", "response", "result", "retailer", "retreat", + "reunion", "revenue", "review", "reward", "rhyme", "rhythm", + "rich", "rival", "river", "robin", "rocky", "romantic", + "romp", "roster", "round", "royal", "ruin", "ruler", + "rumor", "sack", "safari", "salary", "salon", "salt", + "satisfy", "satoshi", "saver", "says", "scandal", "scared", + "scatter", "scene", "scholar", "science", "scout", "scramble", + "screw", "script", "scroll", "seafood", "season", "secret", + "security", "segment", "senior", "shadow", "shaft", "shame", + "shaped", "sharp", "shelter", "sheriff", "short", "should", + "shrimp", "sidewalk", "silent", "silver", "similar", "simple", + "single", "sister", "skin", "skunk", "slap", "slavery", + "sled", "slice", "slim", "slow", "slush", "smart", + "smear", "smell", "smirk", "smith", "smoking", "smug", + "snake", "snapshot", "sniff", "society", "software", "soldier", + "solution", "soul", "source", "space", "spark", "speak", + "species", "spelling", "spend", "spew", "spider", "spill", + "spine", "spirit", "spit", "spray", "sprinkle", "square", + "squeeze", "stadium", "staff", "standard", "starting", "station", + "stay", "steady", "step", "stick", "stilt", "story", + "strategy", "strike", "style", "subject", "submit", "sugar", + "suitable", "sunlight", "superior", "surface", "surprise", "survive", + "sweater", "swimming", "swing", "switch", "symbolic", "sympathy", + "syndrome", "system", "tackle", "tactics", "tadpole", "talent", + "task", "taste", "taught", "taxi", "teacher", "teammate", + "teaspoon", "temple", "tenant", "tendency", "tension", "terminal", + "testify", "texture", "thank", "that", "theater", "theory", + "therapy", "thorn", "threaten", "thumb", "thunder", "ticket", + "tidy", "timber", "timely", "ting", "tofu", "together", + "tolerate", "total", "toxic", "tracks", "traffic", "training", + "transfer", "trash", "traveler", "treat", "trend", "trial", + "tricycle", "trip", "triumph", "trouble", "true", "trust", + "twice", "twin", "type", "typical", "ugly", "ultimate", + "umbrella", "uncover", "undergo", "unfair", "unfold", "unhappy", + "union", "universe", "unkind", "unknown", "unusual", "unwrap", + "upgrade", "upstairs", "username", "usher", "usual", "valid", + "valuable", "vampire", "vanish", "various", "vegan", "velvet", + "venture", "verdict", "verify", "very", "veteran", "vexed", + "victim", "video", "view", "vintage", "violence", "viral", + "visitor", "visual", "vitamins", "vocal", "voice", "volume", + "voter", "voting", "walnut", "warmth", "warn", "watch", + "wavy", "wealthy", "weapon", "webcam", "welcome", "welfare", + "western", "width", "wildlife", "window", "wine", "wireless", + "wisdom", "withdraw", "wits", "wolf", "woman", "work", + "worthy", "wrap", "wrist", "writing", "wrote", "year", + "yelp", "yield", "yoga", "zero", +}; + +/** + * This array contains number representations of SLIP-39 words. + * These numbers are determined how the words were entered on a + * T9 keyboard with the following layout: + * ab (1) cd (2) ef (3) + * ghij (4) klm (5) nopq (6) + * rs (7) tuv (8) wxyz (9) + * + * Each word is uniquely defined by four buttons. + */ +static const struct { + uint16_t sequence; + uint16_t index; +} words_button_seq[WORDS_COUNT] = { + {1212, 0}, // academic + {1216, 7}, // adapt + {1236, 8}, // adequate + {1242, 1}, // acid + {1248, 9}, // adjust + {1254, 10}, // admit + {1263, 2}, // acne + {1267, 11}, // adorn + {1268, 3}, // acquire + {1276, 4}, // acrobat + {1281, 13}, // advance + {1284, 5}, // activity + {1285, 12}, // adult + {1286, 14}, // advocate + {1287, 6}, // actress + {1315, 67}, // beam + {1317, 68}, // beard + {1318, 69}, // beaver + {1326, 70}, // become + {1327, 71}, // bedroom + {1341, 72}, // behavior + {1346, 73}, // being + {1354, 74}, // believe + {1356, 75}, // belong + {1363, 76}, // benefit + {1371, 15}, // afraid + {1378, 77}, // best + {1396, 78}, // beyond + {1414, 16}, // again + {1417, 23}, // ajar + {1423, 19}, // aide + {1436, 17}, // agency + {1453, 79}, // bike + {1465, 80}, // biology + {1472, 20}, // aircraft + {1473, 18}, // agree + {1474, 82}, // bishop + {1475, 21}, // airline + {1476, 22}, // airport + {1478, 81}, // birthday + {1512, 83}, // black + {1514, 35}, // ambition + {1516, 84}, // blanket + {1517, 24}, // alarm + {1518, 25}, // album + {1519, 34}, // amazing + {1526, 26}, // alcohol + {1537, 85}, // blessing + {1543, 27}, // alien + {1545, 86}, // blimp + {1546, 87}, // blind + {1548, 28}, // alive + {1564, 29}, // alpha + {1568, 36}, // amount + {1573, 30}, // already + {1583, 88}, // blue + {1585, 32}, // aluminum + {1586, 31}, // alto + {1587, 37}, // amuse + {1591, 33}, // always + {1615, 38}, // analysis + {1617, 48}, // apart + {1618, 39}, // anatomy + {1623, 40}, // ancestor + {1624, 41}, // ancient + {1629, 89}, // body + {1643, 42}, // angel + {1645, 44}, // animal + {1647, 43}, // angry + {1658, 90}, // bolt + {1674, 91}, // boring + {1676, 92}, // born + {1679, 45}, // answer + {1681, 49}, // aquatic + {1683, 46}, // antenna + {1684, 93}, // both + {1686, 94}, // boundary + {1694, 47}, // anxiety + {1712, 95}, // bracelet + {1716, 96}, // branch + {1718, 97}, // brave + {1721, 50}, // arcade + {1731, 98}, // breathe + {1736, 51}, // arena + {1743, 99}, // briefing + {1748, 52}, // argue + {1753, 53}, // armed + {1763, 56}, // aspect + {1765, 100}, // broken + {1768, 101}, // brother + {1769, 102}, // browser + {1784, 54}, // artist + {1789, 55}, // artwork + {1824, 104}, // budget + {1825, 103}, // bucket + {1828, 57}, // auction + {1837, 60}, // average + {1841, 61}, // aviation + {1845, 105}, // building + {1848, 58}, // august + {1851, 106}, // bulb + {1854, 107}, // bulge + {1856, 108}, // bumpy + {1862, 109}, // bundle + {1864, 62}, // avoid + {1868, 59}, // aunt + {1872, 110}, // burden + {1876, 111}, // burning + {1879, 112}, // busy + {1893, 113}, // buyer + {1917, 63}, // award + {1919, 64}, // away + {1947, 65}, // axis + {1953, 66}, // axle + {2143, 114}, // cage + {2147, 185}, // daisy + {2151, 186}, // damage + {2152, 115}, // calcium + {2153, 116}, // camera + {2156, 117}, // campus + {2161, 119}, // capacity + {2162, 187}, // dance + {2164, 120}, // capital + {2168, 121}, // capture + {2169, 118}, // canyon + {2171, 122}, // carbon + {2172, 123}, // cards + {2173, 124}, // careful + {2174, 125}, // cargo + {2175, 188}, // darkness + {2176, 126}, // carpet + {2178, 127}, // carve + {2181, 189}, // database + {2183, 128}, // category + {2184, 190}, // daughter + {2187, 129}, // cause + {2312, 191}, // deadline + {2315, 192}, // deal + {2317, 193}, // debris + {2318, 194}, // debut + {2323, 195}, // decent + {2324, 196}, // decision + {2325, 197}, // declare + {2326, 198}, // decorate + {2327, 199}, // decrease + {2345, 130}, // ceiling + {2351, 201}, // demand + {2354, 200}, // deliver + {2361, 204}, // depart + {2363, 205}, // depend + {2364, 206}, // depict + {2365, 207}, // deploy + {2367, 202}, // density + {2368, 131}, // center + {2369, 203}, // deny + {2371, 132}, // ceramic + {2372, 208}, // describe + {2373, 209}, // desert + {2374, 210}, // desire + {2375, 211}, // desktop + {2378, 212}, // destroy + {2381, 213}, // detailed + {2383, 214}, // detect + {2384, 215}, // device + {2386, 216}, // devote + {2414, 217}, // diagnose + {2415, 133}, // champion + {2416, 134}, // change + {2417, 135}, // charity + {2428, 218}, // dictate + {2432, 136}, // check + {2435, 137}, // chemical + {2437, 138}, // chest + {2438, 219}, // diet + {2439, 139}, // chew + {2453, 220}, // dilemma + {2454, 221}, // diminish + {2463, 141}, // cinema + {2464, 222}, // dining + {2465, 223}, // diploma + {2471, 224}, // disaster + {2472, 225}, // discuss + {2473, 226}, // disease + {2474, 227}, // dish + {2475, 228}, // dismiss + {2476, 229}, // display + {2478, 230}, // distance + {2481, 140}, // chubby + {2483, 231}, // dive + {2484, 142}, // civil + {2486, 232}, // divorce + {2517, 143}, // class + {2519, 144}, // clay + {2531, 145}, // cleanup + {2543, 146}, // client + {2545, 147}, // climate + {2546, 148}, // clinic + {2562, 149}, // clock + {2564, 150}, // clogs + {2567, 151}, // closet + {2568, 152}, // clothes + {2581, 153}, // club + {2587, 154}, // cluster + {2615, 155}, // coal + {2617, 156}, // coastal + {2624, 157}, // coding + {2628, 233}, // document + {2651, 234}, // domain + {2653, 235}, // domestic + {2654, 236}, // dominant + {2656, 159}, // company + {2658, 158}, // column + {2676, 160}, // corner + {2678, 161}, // costume + {2683, 164}, // cover + {2684, 237}, // dough + {2686, 162}, // counter + {2687, 163}, // course + {2691, 165}, // cowboy + {2696, 238}, // downtown + {2712, 166}, // cradle + {2713, 167}, // craft + {2714, 239}, // dragon + {2715, 240}, // dramatic + {2719, 168}, // crazy + {2731, 241}, // dream + {2732, 169}, // credit + {2737, 242}, // dress + {2742, 170}, // cricket + {2743, 243}, // drift + {2745, 171}, // criminal + {2746, 244}, // drink + {2747, 172}, // crisis + {2748, 173}, // critical + {2768, 245}, // drove + {2769, 174}, // crowd + {2782, 175}, // crucial + {2784, 246}, // drug + {2786, 176}, // crunch + {2787, 177}, // crush + {2793, 247}, // dryer + {2797, 178}, // crystal + {2814, 179}, // cubic + {2825, 248}, // duckling + {2853, 249}, // duke + {2858, 180}, // cultural + {2871, 250}, // duration + {2874, 181}, // curious + {2875, 182}, // curly + {2878, 183}, // custody + {2917, 251}, // dwarf + {2954, 184}, // cylinder + {2961, 252}, // dynamic + {3124, 323}, // facility + {3128, 324}, // fact + {3145, 325}, // failure + {3146, 326}, // faint + {3153, 327}, // fake + {3154, 329}, // family + {3156, 330}, // famous + {3157, 328}, // false + {3162, 331}, // fancy + {3164, 332}, // fangs + {3168, 333}, // fantasy + {3173, 255}, // easel + {3175, 253}, // early + {3178, 254}, // earth + {3179, 256}, // easy + {3181, 334}, // fatal + {3184, 335}, // fatigue + {3186, 336}, // favorite + {3196, 337}, // fawn + {3243, 260}, // edge + {3246, 257}, // echo + {3248, 261}, // editor + {3254, 258}, // eclipse + {3265, 259}, // ecology + {3282, 262}, // educate + {3413, 338}, // fiber + {3428, 339}, // fiction + {3458, 340}, // filter + {3461, 341}, // finance + {3462, 342}, // findings + {3464, 343}, // finger + {3472, 346}, // fiscal + {3473, 344}, // firefly + {3474, 347}, // fishing + {3475, 345}, // firm + {3484, 263}, // either + {3486, 348}, // fitness + {3514, 273}, // email + {3515, 349}, // flame + {3516, 264}, // elbow + {3517, 350}, // flash + {3518, 351}, // flavor + {3523, 265}, // elder + {3531, 352}, // flea + {3532, 266}, // election + {3534, 267}, // elegant + {3535, 268}, // element + {3536, 269}, // elephant + {3537, 274}, // emerald + {3538, 270}, // elevator + {3539, 353}, // flexible + {3546, 354}, // flip + {3547, 275}, // emission + {3548, 271}, // elite + {3561, 355}, // float + {3563, 276}, // emperor + {3564, 277}, // emphasis + {3565, 278}, // employer + {3567, 356}, // floral + {3568, 279}, // empty + {3573, 272}, // else + {3583, 357}, // fluff + {3624, 280}, // ending + {3625, 281}, // endless + {3626, 282}, // endorse + {3628, 358}, // focus + {3635, 283}, // enemy + {3636, 285}, // enforce + {3637, 284}, // energy + {3641, 286}, // engage + {3642, 292}, // epidemic + {3646, 287}, // enjoy + {3647, 293}, // episode + {3651, 288}, // enlarge + {3671, 359}, // forbid + {3672, 360}, // force + {3673, 361}, // forecast + {3674, 362}, // forget + {3675, 363}, // formal + {3678, 364}, // fortune + {3679, 365}, // forward + {3681, 294}, // equation + {3683, 290}, // envelope + {3684, 295}, // equip + {3686, 366}, // founder + {3687, 289}, // entrance + {3689, 291}, // envy + {3712, 367}, // fraction + {3714, 368}, // fragment + {3717, 296}, // eraser + {3721, 298}, // escape + {3736, 369}, // frequent + {3737, 370}, // freshman + {3741, 371}, // friar + {3742, 372}, // fridge + {3743, 373}, // friendly + {3762, 297}, // erode + {3767, 374}, // frost + {3768, 375}, // froth + {3769, 376}, // frozen + {3781, 299}, // estate + {3784, 300}, // estimate + {3815, 301}, // evaluate + {3836, 302}, // evening + {3842, 303}, // evidence + {3845, 304}, // evil + {3853, 377}, // fumes + {3862, 378}, // funding + {3865, 305}, // evoke + {3873, 380}, // fused + {3875, 379}, // furl + {3912, 306}, // exact + {3915, 307}, // example + {3923, 308}, // exceed + {3924, 309}, // exchange + {3925, 310}, // exclude + {3928, 311}, // excuse + {3931, 322}, // eyebrow + {3932, 312}, // execute + {3937, 313}, // exercise + {3941, 314}, // exhaust + {3961, 316}, // expand + {3963, 317}, // expect + {3965, 318}, // explain + {3967, 319}, // express + {3968, 315}, // exotic + {3983, 320}, // extend + {3987, 321}, // extra + {4125, 483}, // jacket + {4147, 420}, // hairy + {4151, 381}, // galaxy + {4153, 382}, // game + {4157, 421}, // hamster + {4162, 422}, // hand + {4164, 423}, // hanger + {4171, 383}, // garbage + {4172, 384}, // garden + {4175, 385}, // garlic + {4176, 386}, // gasoline + {4178, 424}, // harvest + {4183, 425}, // have + {4184, 387}, // gather + {4186, 426}, // havoc + {4191, 428}, // hazard + {4195, 427}, // hawk + {4231, 452}, // idea + {4236, 453}, // identify + {4253, 454}, // idle + {4312, 429}, // headset + {4315, 430}, // health + {4317, 431}, // hearing + {4318, 432}, // heat + {4356, 433}, // helpful + {4363, 388}, // general + {4364, 389}, // genius + {4365, 392}, // geology + {4367, 390}, // genre + {4368, 391}, // genuine + {4371, 434}, // herald + {4372, 435}, // herd + {4374, 436}, // hesitate + {4375, 484}, // jerky + {4378, 393}, // gesture + {4393, 485}, // jewelry + {4512, 394}, // glad + {4514, 455}, // image + {4516, 395}, // glance + {4517, 396}, // glasses + {4536, 397}, // glen + {4545, 398}, // glimpse + {4561, 456}, // impact + {4565, 457}, // imply + {4567, 458}, // improve + {4568, 459}, // impulse + {4616, 437}, // hobo + {4618, 399}, // goat + {4623, 463}, // index + {4624, 464}, // indicate + {4625, 460}, // include + {4626, 461}, // income + {4627, 462}, // increase + {4628, 465}, // industry + {4631, 466}, // infant + {4636, 467}, // inform + {4643, 468}, // inherit + {4646, 486}, // join + {4648, 469}, // injury + {4651, 470}, // inmate + {4652, 400}, // golden + {4653, 440}, // home + {4654, 438}, // holiday + {4659, 439}, // holy + {4673, 471}, // insect + {4674, 472}, // inside + {4675, 441}, // hormone + {4676, 442}, // hospital + {4678, 473}, // install + {4681, 476}, // invasion + {4683, 474}, // intend + {4684, 475}, // intimate + {4686, 477}, // involve + {4687, 443}, // hour + {4712, 401}, // graduate + {4716, 402}, // grant + {4717, 403}, // grasp + {4718, 404}, // gravity + {4719, 405}, // gray + {4731, 406}, // greatest + {4743, 407}, // grief + {4745, 408}, // grill + {4746, 409}, // grin + {4747, 478}, // iris + {4751, 479}, // island + {4762, 410}, // grocery + {4765, 480}, // isolate + {4767, 411}, // gross + {4768, 412}, // group + {4769, 413}, // grownup + {4785, 414}, // grumpy + {4817, 415}, // guard + {4824, 487}, // judicial + {4835, 481}, // item + {4837, 416}, // guest + {4842, 488}, // juice + {4843, 444}, // huge + {4845, 417}, // guilt + {4848, 418}, // guitar + {4851, 445}, // human + {4854, 446}, // humidity + {4856, 489}, // jump + {4857, 419}, // gums + {4862, 490}, // junction + {4864, 491}, // junior + {4865, 492}, // junk + {4867, 482}, // ivory + {4868, 447}, // hunting + {4871, 448}, // husband + {4874, 449}, // hush + {4875, 450}, // husky + {4878, 494}, // justice + {4879, 493}, // jury + {4917, 451}, // hybrid + {5123, 502}, // laden + {5124, 549}, // machine + {5125, 503}, // ladle + {5129, 504}, // ladybug + {5141, 550}, // magazine + {5142, 551}, // maiden + {5145, 552}, // mailman + {5146, 553}, // main + {5147, 505}, // lair + {5151, 556}, // mama + {5153, 554}, // makeup + {5154, 555}, // making + {5156, 506}, // lamp + {5161, 557}, // manager + {5162, 558}, // mandate + {5164, 507}, // language + {5167, 559}, // mansion + {5168, 560}, // manual + {5171, 561}, // marathon + {5172, 562}, // march + {5173, 509}, // laser + {5174, 508}, // large + {5175, 563}, // market + {5176, 565}, // mason + {5178, 564}, // marvel + {5183, 566}, // material + {5184, 567}, // math + {5186, 510}, // laundry + {5194, 568}, // maximum + {5196, 569}, // mayor + {5197, 511}, // lawsuit + {5312, 512}, // leader + {5313, 513}, // leaf + {5316, 570}, // meaning + {5317, 514}, // learn + {5318, 515}, // leaves + {5321, 571}, // medal + {5324, 572}, // medical + {5328, 516}, // lecture + {5341, 517}, // legal + {5343, 518}, // legend + {5347, 519}, // legs + {5351, 573}, // member + {5356, 574}, // memory + {5362, 520}, // lend + {5364, 521}, // length + {5368, 575}, // mental + {5372, 576}, // merchant + {5374, 577}, // merit + {5376, 495}, // kernel + {5383, 522}, // level + {5384, 578}, // method + {5387, 579}, // metric + {5391, 496}, // keyboard + {5413, 523}, // liberty + {5417, 524}, // library + {5423, 525}, // license + {5426, 497}, // kidney + {5427, 580}, // midst + {5438, 526}, // lift + {5451, 528}, // lilac + {5452, 581}, // mild + {5453, 527}, // likely + {5454, 582}, // military + {5459, 529}, // lily + {5462, 498}, // kind + {5463, 583}, // mineral + {5464, 584}, // minister + {5467, 530}, // lips + {5468, 531}, // liquid + {5471, 585}, // miracle + {5478, 532}, // listen + {5482, 499}, // kitchen + {5483, 533}, // literary + {5484, 534}, // living + {5491, 535}, // lizard + {5493, 586}, // mixed + {5498, 587}, // mixture + {5613, 537}, // lobe + {5614, 588}, // mobile + {5616, 536}, // loan + {5621, 538}, // location + {5623, 589}, // modern + {5624, 590}, // modify + {5643, 500}, // knife + {5647, 591}, // moisture + {5648, 501}, // knit + {5653, 592}, // moment + {5674, 539}, // losing + {5676, 593}, // morning + {5678, 594}, // mortgage + {5682, 540}, // loud + {5683, 598}, // move + {5684, 595}, // mother + {5686, 596}, // mountain + {5687, 597}, // mouse + {5691, 541}, // loyalty + {5824, 599}, // much + {5825, 542}, // luck + {5853, 600}, // mule + {5858, 601}, // multiple + {5861, 543}, // lunar + {5862, 544}, // lunch + {5864, 545}, // lungs + {5872, 602}, // muscle + {5873, 603}, // museum + {5874, 604}, // music + {5878, 605}, // mustang + {5898, 546}, // luxury + {5946, 547}, // lying + {5974, 548}, // lyrics + {6123, 636}, // paces + {6124, 637}, // pacific + {6125, 638}, // package + {6137, 618}, // obesity + {6141, 641}, // pajamas + {6142, 639}, // paid + {6143, 619}, // object + {6145, 606}, // nail + {6146, 640}, // painting + {6161, 644}, // papa + {6162, 642}, // pancake + {6163, 645}, // paper + {6168, 643}, // pants + {6172, 646}, // parcel + {6173, 620}, // observe + {6174, 617}, // oasis + {6175, 647}, // parking + {6178, 648}, // party + {6181, 621}, // obtain + {6183, 649}, // patent + {6184, 607}, // national + {6187, 650}, // patrol + {6195, 651}, // payment + {6197, 652}, // payroll + {6231, 622}, // ocean + {6312, 653}, // peaceful + {6316, 654}, // peanut + {6317, 655}, // peasant + {6321, 656}, // pecan + {6325, 608}, // necklace + {6341, 609}, // negative + {6361, 657}, // penalty + {6362, 658}, // pencil + {6372, 659}, // percent + {6373, 660}, // perfect + {6375, 661}, // permit + {6378, 610}, // nervous + {6383, 623}, // often + {6384, 662}, // petition + {6389, 611}, // network + {6397, 612}, // news + {6416, 663}, // phantom + {6417, 664}, // pharmacy + {6425, 668}, // pickup + {6428, 669}, // picture + {6432, 670}, // piece + {6453, 671}, // pile + {6463, 673}, // pipeline + {6465, 672}, // pink + {6468, 665}, // photo + {6471, 666}, // phrase + {6478, 674}, // pistol + {6482, 675}, // pitch + {6497, 667}, // physics + {6514, 676}, // plains + {6516, 677}, // plan + {6517, 678}, // plastic + {6518, 679}, // platform + {6519, 680}, // playoff + {6531, 681}, // pleasure + {6548, 625}, // omit + {6568, 682}, // plot + {6586, 683}, // plunge + {6595, 624}, // olympic + {6712, 684}, // practice + {6714, 628}, // orbit + {6715, 626}, // oral + {6716, 627}, // orange + {6719, 685}, // prayer + {6723, 629}, // order + {6724, 630}, // ordinary + {6731, 686}, // preach + {6732, 687}, // predator + {6734, 688}, // pregnant + {6735, 689}, // premium + {6736, 690}, // prepare + {6737, 691}, // presence + {6738, 692}, // prevent + {6741, 631}, // organize + {6743, 693}, // priest + {6745, 694}, // primary + {6746, 695}, // priority + {6747, 696}, // prisoner + {6748, 697}, // privacy + {6749, 698}, // prize + {6761, 699}, // problem + {6762, 700}, // process + {6763, 701}, // profile + {6764, 702}, // program + {6765, 703}, // promise + {6767, 704}, // prospect + {6768, 705}, // provide + {6786, 706}, // prune + {6815, 707}, // public + {6816, 716}, // quantity + {6817, 717}, // quarter + {6825, 613}, // nuclear + {6836, 633}, // oven + {6837, 634}, // overall + {6842, 718}, // quick + {6843, 719}, // quiet + {6851, 614}, // numb + {6853, 615}, // numerous + {6856, 709}, // pumps + {6857, 708}, // pulse + {6861, 712}, // pupal + {6862, 632}, // ounce + {6864, 710}, // punish + {6869, 711}, // puny + {6872, 713}, // purchase + {6876, 714}, // purple + {6956, 616}, // nylon + {6963, 635}, // owner + {6984, 715}, // python + {7121, 722}, // radar + {7123, 720}, // race + {7124, 721}, // racism + {7125, 775}, // sack + {7131, 776}, // safari + {7145, 723}, // railroad + {7146, 724}, // rainbow + {7147, 725}, // raisin + {7151, 777}, // salary + {7156, 778}, // salon + {7158, 779}, // salt + {7162, 726}, // random + {7164, 728}, // rapids + {7165, 727}, // ranked + {7176, 729}, // raspy + {7183, 782}, // saver + {7184, 780}, // satisfy + {7186, 781}, // satoshi + {7197, 783}, // says + {7216, 784}, // scandal + {7217, 785}, // scared + {7218, 786}, // scatter + {7236, 787}, // scene + {7243, 789}, // science + {7246, 788}, // scholar + {7268, 790}, // scout + {7271, 791}, // scramble + {7273, 792}, // screw + {7274, 793}, // script + {7276, 794}, // scroll + {7312, 730}, // reaction + {7313, 795}, // seafood + {7315, 731}, // realize + {7316, 732}, // rebound + {7317, 796}, // season + {7318, 733}, // rebuild + {7321, 734}, // recall + {7323, 735}, // receiver + {7326, 736}, // recover + {7327, 797}, // secret + {7328, 798}, // security + {7343, 739}, // reject + {7345, 799}, // segment + {7347, 737}, // regret + {7348, 738}, // regular + {7351, 740}, // relate + {7353, 741}, // remember + {7354, 742}, // remind + {7356, 743}, // remove + {7361, 745}, // repair + {7362, 744}, // render + {7363, 746}, // repeat + {7364, 800}, // senior + {7365, 747}, // replace + {7368, 748}, // require + {7372, 749}, // rescue + {7373, 750}, // research + {7374, 751}, // resident + {7376, 752}, // response + {7378, 753}, // result + {7381, 754}, // retailer + {7383, 757}, // revenue + {7384, 758}, // review + {7386, 756}, // reunion + {7387, 755}, // retreat + {7391, 759}, // reward + {7412, 801}, // shadow + {7413, 802}, // shaft + {7415, 803}, // shame + {7416, 804}, // shaped + {7417, 805}, // sharp + {7423, 811}, // sidewalk + {7424, 762}, // rich + {7435, 806}, // shelter + {7437, 807}, // sheriff + {7453, 812}, // silent + {7454, 814}, // similar + {7456, 815}, // simple + {7458, 813}, // silver + {7464, 816}, // single + {7467, 808}, // short + {7468, 809}, // should + {7474, 810}, // shrimp + {7478, 817}, // sister + {7481, 763}, // rival + {7483, 764}, // river + {7495, 760}, // rhyme + {7498, 761}, // rhythm + {7516, 820}, // slap + {7517, 827}, // smart + {7518, 821}, // slavery + {7531, 828}, // smear + {7532, 822}, // sled + {7535, 829}, // smell + {7542, 823}, // slice + {7545, 824}, // slim + {7546, 818}, // skin + {7547, 830}, // smirk + {7548, 831}, // smith + {7565, 832}, // smoking + {7569, 825}, // slow + {7584, 833}, // smug + {7586, 819}, // skunk + {7587, 826}, // slush + {7612, 843}, // space + {7614, 765}, // robin + {7615, 834}, // snake + {7616, 835}, // snapshot + {7617, 844}, // spark + {7624, 837}, // society + {7625, 766}, // rocky + {7631, 845}, // speak + {7632, 846}, // species + {7635, 847}, // spelling + {7636, 848}, // spend + {7638, 838}, // software + {7639, 849}, // spew + {7642, 850}, // spider + {7643, 836}, // sniff + {7645, 851}, // spill + {7646, 852}, // spine + {7647, 853}, // spirit + {7648, 854}, // spit + {7651, 767}, // romantic + {7652, 839}, // soldier + {7656, 768}, // romp + {7658, 840}, // solution + {7671, 855}, // spray + {7674, 856}, // sprinkle + {7678, 769}, // roster + {7681, 857}, // square + {7683, 858}, // squeeze + {7685, 841}, // soul + {7686, 770}, // round + {7687, 842}, // source + {7691, 771}, // royal + {7812, 859}, // stadium + {7813, 860}, // staff + {7814, 873}, // subject + {7815, 874}, // submit + {7816, 861}, // standard + {7817, 862}, // starting + {7818, 863}, // station + {7819, 864}, // stay + {7831, 865}, // steady + {7836, 866}, // step + {7841, 875}, // sugar + {7842, 867}, // stick + {7845, 868}, // stilt + {7846, 772}, // ruin + {7848, 876}, // suitable + {7853, 773}, // ruler + {7856, 774}, // rumor + {7863, 878}, // superior + {7865, 877}, // sunlight + {7867, 869}, // story + {7871, 870}, // strategy + {7873, 879}, // surface + {7874, 871}, // strike + {7876, 880}, // surprise + {7878, 881}, // survive + {7895, 872}, // style + {7931, 882}, // sweater + {7945, 883}, // swimming + {7946, 884}, // swing + {7948, 885}, // switch + {7951, 886}, // symbolic + {7956, 887}, // sympathy + {7962, 888}, // syndrome + {7978, 889}, // system + {8125, 890}, // tackle + {8126, 892}, // tadpole + {8128, 891}, // tactics + {8153, 893}, // talent + {8154, 965}, // valid + {8156, 967}, // vampire + {8158, 966}, // valuable + {8164, 968}, // vanish + {8174, 969}, // various + {8175, 894}, // task + {8178, 895}, // taste + {8184, 896}, // taught + {8194, 897}, // taxi + {8312, 898}, // teacher + {8315, 899}, // teammate + {8317, 900}, // teaspoon + {8341, 970}, // vegan + {8356, 901}, // temple + {8358, 971}, // velvet + {8361, 902}, // tenant + {8362, 903}, // tendency + {8367, 904}, // tension + {8368, 972}, // venture + {8372, 973}, // verdict + {8374, 974}, // verify + {8375, 905}, // terminal + {8378, 906}, // testify + {8379, 975}, // very + {8383, 976}, // veteran + {8393, 977}, // vexed + {8398, 907}, // texture + {8416, 908}, // thank + {8418, 909}, // that + {8423, 979}, // video + {8425, 917}, // ticket + {8428, 978}, // victim + {8429, 918}, // tidy + {8431, 910}, // theater + {8436, 911}, // theory + {8437, 912}, // therapy + {8439, 980}, // view + {8451, 919}, // timber + {8453, 920}, // timely + {8459, 946}, // ugly + {8464, 921}, // ting + {8465, 982}, // violence + {8467, 913}, // thorn + {8468, 981}, // vintage + {8471, 983}, // viral + {8473, 914}, // threaten + {8474, 984}, // visitor + {8478, 985}, // visual + {8481, 986}, // vitamins + {8485, 915}, // thumb + {8486, 916}, // thunder + {8517, 948}, // umbrella + {8584, 947}, // ultimate + {8621, 987}, // vocal + {8623, 950}, // undergo + {8626, 949}, // uncover + {8631, 951}, // unfair + {8636, 952}, // unfold + {8638, 922}, // tofu + {8641, 953}, // unhappy + {8642, 988}, // voice + {8643, 923}, // together + {8646, 954}, // union + {8647, 960}, // upgrade + {8648, 955}, // universe + {8653, 924}, // tolerate + {8654, 956}, // unkind + {8656, 957}, // unknown + {8658, 989}, // volume + {8678, 961}, // upstairs + {8681, 925}, // total + {8683, 990}, // voter + {8684, 991}, // voting + {8687, 958}, // unusual + {8694, 926}, // toxic + {8697, 959}, // unwrap + {8712, 927}, // tracks + {8713, 928}, // traffic + {8714, 929}, // training + {8716, 930}, // transfer + {8717, 931}, // trash + {8718, 932}, // traveler + {8731, 933}, // treat + {8736, 934}, // trend + {8737, 962}, // username + {8741, 935}, // trial + {8742, 936}, // tricycle + {8743, 963}, // usher + {8746, 937}, // trip + {8748, 938}, // triumph + {8768, 939}, // trouble + {8781, 964}, // usual + {8783, 940}, // true + {8787, 941}, // trust + {8942, 942}, // twice + {8946, 943}, // twin + {8963, 944}, // type + {8964, 945}, // typical + {9156, 992}, // walnut + {9175, 993}, // warmth + {9176, 994}, // warn + {9182, 995}, // watch + {9189, 996}, // wavy + {9312, 999}, // webcam + {9315, 997}, // wealthy + {9316, 998}, // weapon + {9317, 1019}, // year + {9352, 1000}, // welcome + {9353, 1001}, // welfare + {9356, 1020}, // yelp + {9376, 1023}, // zero + {9378, 1002}, // western + {9428, 1003}, // width + {9435, 1021}, // yield + {9452, 1004}, // wildlife + {9462, 1005}, // window + {9463, 1006}, // wine + {9472, 1008}, // wisdom + {9473, 1007}, // wireless + {9484, 1009}, // withdraw + {9487, 1010}, // wits + {9641, 1022}, // yoga + {9651, 1012}, // woman + {9653, 1011}, // wolf + {9675, 1013}, // work + {9678, 1014}, // worthy + {9716, 1015}, // wrap + {9747, 1016}, // wrist + {9748, 1017}, // writing + {9768, 1018}, // wrote +}; + +#endif diff --git a/trezor-crypto/include/TrezorCrypto/zilliqa.h b/trezor-crypto/include/TrezorCrypto/zilliqa.h new file mode 100644 index 00000000000..46ada660b06 --- /dev/null +++ b/trezor-crypto/include/TrezorCrypto/zilliqa.h @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2019 Anatolii Kurotych + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __ZILLIQA_H__ +#define __ZILLIQA_H__ + +#include + +#if defined(__cplusplus) +extern "C" +{ +#endif + +// result of sign operation +typedef struct { + uint8_t r[32]; + uint8_t s[32]; +} schnorr_sign_pair; + +// sign/verify returns 0 if operation succeeded + +int zil_schnorr_sign(const ecdsa_curve *curve, const uint8_t *priv_key, + const uint8_t *msg, const uint32_t msg_len, uint8_t *sig); +int zil_schnorr_verify(const ecdsa_curve *curve, const uint8_t *pub_key, + const uint8_t *sig, const uint8_t *msg, const uint32_t msg_len); + +// k is a random from [1, ..., order-1] +int zil_schnorr_sign_k(const ecdsa_curve *curve, const uint8_t *priv_key, + const bignum256 *k, const uint8_t *msg, const uint32_t msg_len, + schnorr_sign_pair *result); +int zil_schnorr_verify_pair(const ecdsa_curve *curve, const uint8_t *pub_key, + const uint8_t *msg, const uint32_t msg_len, + const schnorr_sign_pair *sign); +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/walletconsole/CMakeLists.txt b/walletconsole/CMakeLists.txt index 6aabb95e234..f5c8c08ff31 100644 --- a/walletconsole/CMakeLists.txt +++ b/walletconsole/CMakeLists.txt @@ -1,20 +1,11 @@ -# Copyright © 2017-2022 Trust Wallet. +# SPDX-License-Identifier: Apache-2.0 # -# This file is part of Trust. The full Trust copyright notice, including -# terms governing use, modification, and redistribution, is contained in the -# file LICENSE at the root of the source code distribution tree. +# Copyright © 2017 Trust Wallet. # walletconsole executable file(GLOB walletconsole_sources *.cpp) add_executable(walletconsole ${walletconsole_sources}) target_link_libraries(walletconsole walletconsolelib TrezorCrypto TrustWalletCore protobuf Boost::boost) target_include_directories(walletconsole PRIVATE ${CMAKE_SOURCE_DIR}/walletconsole/lib ${CMAKE_SOURCE_DIR}/src) -target_compile_options(walletconsole PRIVATE "-Wall") - -set_target_properties(walletconsole - PROPERTIES - CXX_STANDARD 17 - CXX_STANDARD_REQUIRED ON -) INSTALL(TARGETS walletconsole DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/walletconsole/lib/Address.cpp b/walletconsole/lib/Address.cpp index 48f6aa9e65c..166efc215cc 100644 --- a/walletconsole/lib/Address.cpp +++ b/walletconsole/lib/Address.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Address.h" @@ -29,7 +27,7 @@ bool Address::addrPub(const string& coinid, const string& pubkey_in, string& res pubDat = parse_hex(pubkey_in); } catch (exception& ex) { _out << "Error: could not parse public key data" << endl; - return false; + return false; } auto ctype = (TWCoinType)coin.c; PublicKey pubKey = PublicKey(pubDat, (TWPublicKeyType)coin.pubKeyType); @@ -45,7 +43,7 @@ bool Address::addrPri(const string& coinid, const string& prikey_in, string& res priDat = parse_hex(prikey_in); } catch (exception& ex) { _out << "Error: could not parse private key data" << endl; - return false; + return false; } auto ctype = (TWCoinType)coin.c; PrivateKey priKey = PrivateKey(priDat); @@ -53,7 +51,7 @@ bool Address::addrPri(const string& coinid, const string& prikey_in, string& res return true; } -bool Address::addr(const string& coinid, const string& addrStr, string& res) { +bool Address::addr(const string& coinid, const string& addrStr, [[maybe_unused]] string& res) { Coin coin; if (!_coins.findCoin(coinid, coin)) { return false; } auto ctype = (TWCoinType)coin.c; @@ -108,7 +106,7 @@ bool Address::deriveFromXpubIndex(const string& coinid, const string& xpub, cons dp.setChange(0); dp.setAddress(index); - const auto publicKey = HDWallet::getPublicKeyFromExtended(xpub, ctype, dp); + const auto publicKey = HDWallet<>::getPublicKeyFromExtended(xpub, ctype, dp); if (!publicKey) { return false; } res = TW::deriveAddress(ctype, publicKey.value()); return true; diff --git a/walletconsole/lib/Address.h b/walletconsole/lib/Address.h index 1d3306c190c..d9db3474502 100644 --- a/walletconsole/lib/Address.h +++ b/walletconsole/lib/Address.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/walletconsole/lib/Buffer.cpp b/walletconsole/lib/Buffer.cpp index 3c79030c7b2..5f75eba20fd 100644 --- a/walletconsole/lib/Buffer.cpp +++ b/walletconsole/lib/Buffer.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Buffer.h" #include "WalletConsole.h" @@ -43,7 +41,7 @@ bool Buffer::prepareInput(const string& in, string& in_out) { int n = std::stoi(in2.substr(1)); // of the form #n int idx = n - 1; - if (idx < 0 || idx >= _prev.size()) { + if (idx < 0 || idx >= static_cast(_prev.size())) { _out << "Requested " << in2 << ", but out of range of buffers (n=" << _prev.size() << ")." << endl; return false; } @@ -58,7 +56,7 @@ bool Buffer::prepareInput(const string& in, string& in_out) { void Buffer::buffer() const { _out << "Last value: " << _last.get() << endl; _out << _prev.size() << " previous values:" << endl; - for (int i = 0; i < _prev.size(); ++i) { + for (auto i = 0ul; i < _prev.size(); ++i) { _out << " #" << i + 1 << " " << _prev[i].get() << endl; } } diff --git a/walletconsole/lib/Buffer.h b/walletconsole/lib/Buffer.h index 05428f2c012..c0043f6d7a9 100644 --- a/walletconsole/lib/Buffer.h +++ b/walletconsole/lib/Buffer.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/walletconsole/lib/CMakeLists.txt b/walletconsole/lib/CMakeLists.txt index 4f42ec02667..dc3453b0112 100644 --- a/walletconsole/lib/CMakeLists.txt +++ b/walletconsole/lib/CMakeLists.txt @@ -1,8 +1,6 @@ -# Copyright © 2017-2022 Trust Wallet. +# SPDX-License-Identifier: Apache-2.0 # -# This file is part of Trust. The full Trust copyright notice, including -# terms governing use, modification, and redistribution, is contained in the -# file LICENSE at the root of the source code distribution tree. +# Copyright © 2017 Trust Wallet. # walletconsolelib library file(GLOB_RECURSE walletconsolelib_sources *.cpp) @@ -10,10 +8,3 @@ add_library(walletconsolelib ${walletconsolelib_sources}) #target_link_libraries(tests gtest_main TrezorCrypto TrustWalletCore protobuf Boost::boost) target_link_libraries(walletconsolelib TrezorCrypto TrustWalletCore protobuf Boost::boost) target_include_directories(walletconsolelib PRIVATE ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/src) -target_compile_options(walletconsolelib PRIVATE "-Wall") - -set_target_properties(walletconsolelib - PROPERTIES - CXX_STANDARD 17 - CXX_STANDARD_REQUIRED ON -) diff --git a/walletconsole/lib/Coins.cpp b/walletconsole/lib/Coins.cpp index 368c1b9853e..b73eb6a1eb9 100644 --- a/walletconsole/lib/Coins.cpp +++ b/walletconsole/lib/Coins.cpp @@ -1,9 +1,7 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Coins.h" diff --git a/walletconsole/lib/Coins.h b/walletconsole/lib/Coins.h index 4aacc299eca..6b9a5321fd8 100644 --- a/walletconsole/lib/Coins.h +++ b/walletconsole/lib/Coins.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/walletconsole/lib/CommandExecutor.cpp b/walletconsole/lib/CommandExecutor.cpp index e20be755518..4692abf9b98 100644 --- a/walletconsole/lib/CommandExecutor.cpp +++ b/walletconsole/lib/CommandExecutor.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "CommandExecutor.h" #include "WalletConsole.h" @@ -189,7 +187,7 @@ string CommandExecutor::parseLine(const string& line, vector& params) { return cmd; } -bool CommandExecutor::checkMinParams(const vector& params, int n) const { +bool CommandExecutor::checkMinParams(const vector& params, std::size_t n) const { if (params.size() - 1 >= n) { return true; } diff --git a/walletconsole/lib/CommandExecutor.h b/walletconsole/lib/CommandExecutor.h index 1177d987266..2098cb502be 100644 --- a/walletconsole/lib/CommandExecutor.h +++ b/walletconsole/lib/CommandExecutor.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once @@ -46,7 +44,7 @@ class CommandExecutor { static string parseLine(const string& line, vector& params); bool prepareInputs(const vector& p_in, vector& p_out); bool setCoin(const string& coin, bool force); - bool checkMinParams(const vector& params, int n) const; + bool checkMinParams(const vector& params, std::size_t n) const; void help() const; }; diff --git a/walletconsole/lib/Keys.cpp b/walletconsole/lib/Keys.cpp index 772f83a8a62..b30e90b98f8 100644 --- a/walletconsole/lib/Keys.cpp +++ b/walletconsole/lib/Keys.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2021 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Keys.h" @@ -65,7 +63,7 @@ bool Keys::pubPri(const string& coinid, const string& p, string& res) { } } -bool Keys::priPub(const string& p, string& res) { +bool Keys::priPub([[maybe_unused]] const string& p, [[maybe_unused]] string& res) { _out << "Not yet implemented! :)" << endl; return false; } @@ -77,7 +75,7 @@ void Keys::setMnemonic(const vector& param) { } // concatenate string mnem = ""; - for (int i = 1; i < param.size(); ++i) { + for (auto i = 1ul; i < param.size(); ++i) { if (i > 1) mnem += " "; mnem += param[i]; } diff --git a/walletconsole/lib/Keys.h b/walletconsole/lib/Keys.h index 674a50e86c9..d33ace97f44 100644 --- a/walletconsole/lib/Keys.h +++ b/walletconsole/lib/Keys.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/walletconsole/lib/Util.cpp b/walletconsole/lib/Util.cpp index eea581e34e5..6cc444822ec 100644 --- a/walletconsole/lib/Util.cpp +++ b/walletconsole/lib/Util.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "Util.h" @@ -38,17 +36,16 @@ bool Util::base64Encode(const string& p, string& res) { } bool Util::base64Decode(const string& p, string& res) { - try { - auto dec = Base64::decode(p); - res = TW::hex(dec); - return true; - } catch (exception& ex) { + auto dec = Base64::decode(p); + if (dec.empty()) { _out << "Error while Base64 decode" << endl; return false; } + res = TW::hex(dec); + return true; } -bool Util::fileW(const string& fileName, const string& data, string& res) { +bool Util::fileW(const string& fileName, const string& data, [[maybe_unused]] string& res) { if (fileExists(fileName)) { _out << "Warning: File '" << fileName << "' already exists, not overwriting to be safe." << endl; return false; diff --git a/walletconsole/lib/Util.h b/walletconsole/lib/Util.h index ab9f0f13c9b..d025feb064e 100644 --- a/walletconsole/lib/Util.h +++ b/walletconsole/lib/Util.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/walletconsole/lib/WalletConsole.cpp b/walletconsole/lib/WalletConsole.cpp index 6602428a982..47c4f73a6f9 100644 --- a/walletconsole/lib/WalletConsole.cpp +++ b/walletconsole/lib/WalletConsole.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "WalletConsole.h" #include "CommandExecutor.h" diff --git a/walletconsole/lib/WalletConsole.h b/walletconsole/lib/WalletConsole.h index fc740ae363a..e1482ffffd4 100644 --- a/walletconsole/lib/WalletConsole.h +++ b/walletconsole/lib/WalletConsole.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #pragma once diff --git a/walletconsole/main.cpp b/walletconsole/main.cpp index 27819c1e57f..e04d5c09b6a 100644 --- a/walletconsole/main.cpp +++ b/walletconsole/main.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2020 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. #include "WalletConsole.h" #include diff --git a/wasm/.gitignore b/wasm/.gitignore index 011b114988d..13d2d8098a4 100644 --- a/wasm/.gitignore +++ b/wasm/.gitignore @@ -1,5 +1,6 @@ dist/ generated/ +wallet-core.d.ts lib/wallet-core.js lib/wallet-core.d.ts lib/wallet-core.wasm diff --git a/wasm/.mocharc.json b/wasm/.mocharc.json new file mode 100644 index 00000000000..6a7296561e4 --- /dev/null +++ b/wasm/.mocharc.json @@ -0,0 +1,8 @@ +{ + "require": "ts-node/register", + "extensions": ["ts", "tsx"], + "spec":[ + "tests/*.test.ts", + "tests/**/*.test.*" + ] +} diff --git a/wasm/CMakeLists.txt b/wasm/CMakeLists.txt index 3aeeddb5010..1b4017a7ea4 100644 --- a/wasm/CMakeLists.txt +++ b/wasm/CMakeLists.txt @@ -1,8 +1,6 @@ -# Copyright © 2017-2022 Trust Wallet. +# SPDX-License-Identifier: Apache-2.0 # -# This file is part of Trust. The full Trust copyright notice, including -# terms governing use, modification, and redistribution, is contained in the -# file LICENSE at the root of the source code distribution tree. +# Copyright © 2017 Trust Wallet. file(GLOB wasm_sources src/*.cpp src/generated/*.cpp) file(GLOB wasm_headers src/*.h src/generated/*.h) @@ -16,11 +14,29 @@ target_compile_options(${TARGET_NAME} PRIVATE "-Wall") set_target_properties(${TARGET_NAME} PROPERTIES - CXX_STANDARD 17 + CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON ) + +# We use a set of COMPILE_FLAGS and LINK_FLAGS, see below links for more details. +# - https://emscripten.org/docs/optimizing/Optimizing-Code.html#how-to-optimize-code +# - https://github.com/emscripten-core/emscripten/blob/main/src/settings.js +# - https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html?highlight=lembind#embind + +# STRICT: Emscripten 'strict' build mode, disables AUTO_NATIVE_LIBRARIES and AUTO_JS_LIBRARIES etc. +# ASSERTIONS: Enable runtime assertions. + +# MODULARIZE=1: Emit generated JavaScript code wrapped in a function that returns a promise. +# ALLOW_MEMORY_GROWTH=1: Allowing allocating more memory from the system as necessary. +# DYNAMIC_EXECUTION=0: Do not emit eval() and new Function() in the generated JavaScript code. + +# -O2: good old code optimization level for release. +# --bind: Link Embind library. +# --no-entry: Skip main entry point because it's built as a library. +# --closure 1: Enable Emscripten closure compiler to minimize generated JavaScript code size. + set_target_properties(${TARGET_NAME} PROPERTIES - COMPILE_FLAGS "-O3 -s USE_BOOST_HEADERS=1" - LINK_FLAGS "--bind --no-entry --closure=1 -O3 -s ALLOW_MEMORY_GROWTH=1 -s ERROR_ON_UNDEFINED_SYMBOLS=1" + COMPILE_FLAGS "-O2 -sSTRICT -sUSE_BOOST_HEADERS=1" + LINK_FLAGS "--bind --no-entry --closure 1 -O2 -sSTRICT -sASSERTIONS -sMODULARIZE=1 -sALLOW_MEMORY_GROWTH=1 -sDYNAMIC_EXECUTION=0 -s EXPORTED_FUNCTIONS=['_setThrew']" ) diff --git a/wasm/README.md b/wasm/README.md new file mode 100644 index 00000000000..63385904724 --- /dev/null +++ b/wasm/README.md @@ -0,0 +1,7 @@ +Trust Wallet Core is an open source, cross platform and cross blockchain library, it adds beta support for WebAssembly recently, You can try it out now: + +```js +npm install @trustwallet/wallet-core +``` + +Documentation will be added to [developer.trustwallet.com](https://developer.trustwallet.com/wallet-core) later, please check out [tests](https://github.com/trustwallet/wallet-core/tree/master/wasm/tests) here for API usages. diff --git a/wasm/index.ts b/wasm/index.ts index cdf8b0ae229..a7f54ee4b95 100644 --- a/wasm/index.ts +++ b/wasm/index.ts @@ -1,9 +1,13 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. +import * as Loader from "./lib/wallet-core"; import { TW } from "./generated/core_proto"; -import * as WalletCore from "./lib/wallet-core"; -export { TW, WalletCore }; +import { WalletCore } from "./src/wallet-core"; +import * as KeyStore from "./src/keystore"; + +declare function load(): Promise; + +export const initWasm: typeof load = Loader; +export { TW, WalletCore, KeyStore }; diff --git a/wasm/package-lock.json b/wasm/package-lock.json index 55c9781bd2a..bd207c0436d 100644 --- a/wasm/package-lock.json +++ b/wasm/package-lock.json @@ -1,8 +1,1353 @@ { "name": "@trustwallet/wallet-core", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "@trustwallet/wallet-core", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "protobufjs": ">=6.11.3" + }, + "devDependencies": { + "@types/chai": "^4.3.0", + "@types/mocha": "^10.0.0", + "@types/node": "^18.7.18", + "@types/webextension-polyfill": "^0.9.0", + "buffer": "^6.0.3", + "chai": "^4.3.6", + "mocha": "^10.1.0", + "ts-node": "^10.7.0", + "typescript": "^4.6.3" + } + }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "node_modules/@types/chai": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", + "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", + "dev": true + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "node_modules/@types/mocha": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.0.tgz", + "integrity": "sha512-rADY+HtTOA52l9VZWtgQfn4p+UDVM2eDVkMZT1I6syp0YKxW2F9v+0pbRZLsvskhQv/vMb6ZfCay81GHbz5SHg==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.7.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", + "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==" + }, + "node_modules/@types/webextension-polyfill": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.9.0.tgz", + "integrity": "sha512-HG1y1o2hK8ag6Y7dfkrAbfKmMIP+B0E6SwAzUfmQ1dDxEIdLTtMyrStY26suHBPrAL7Xw/chlDW02ugc3uXWtQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", + "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/protobufjs": { + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", + "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.0", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/typescript": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", + "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==", + "dev": true + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, "dependencies": { "@cspotcode/source-map-consumer": { "version": "0.8.0", @@ -109,20 +1454,20 @@ "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" }, "@types/mocha": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", - "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.0.tgz", + "integrity": "sha512-rADY+HtTOA52l9VZWtgQfn4p+UDVM2eDVkMZT1I6syp0YKxW2F9v+0pbRZLsvskhQv/vMb6ZfCay81GHbz5SHg==", "dev": true }, "@types/node": { - "version": "17.0.38", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.38.tgz", - "integrity": "sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g==" + "version": "18.7.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", + "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==" }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "@types/webextension-polyfill": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.9.0.tgz", + "integrity": "sha512-HG1y1o2hK8ag6Y7dfkrAbfKmMIP+B0E6SwAzUfmQ1dDxEIdLTtMyrStY26suHBPrAL7Xw/chlDW02ugc3uXWtQ==", "dev": true }, "acorn": { @@ -215,12 +1560,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-stdout": { @@ -342,9 +1687,9 @@ "dev": true }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -398,9 +1743,9 @@ "dev": true }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -481,12 +1826,6 @@ "is-glob": "^4.0.1" } }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -569,12 +1908,6 @@ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -624,41 +1957,49 @@ "dev": true }, "minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + } } }, "mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", + "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==", "dev": true, "requires": { - "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", "chokidar": "3.5.3", - "debug": "4.3.3", + "debug": "4.3.4", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", "glob": "7.2.0", - "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", - "minimatch": "4.2.1", + "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.1", + "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", + "workerpool": "6.2.1", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" @@ -679,9 +2020,9 @@ "dev": true }, "nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true }, "normalize-path": { @@ -742,9 +2083,9 @@ "dev": true }, "protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", + "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -883,19 +2224,10 @@ "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==", "dev": true }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, "workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, "wrap-ansi": { diff --git a/wasm/package.json b/wasm/package.json index 58bede92848..996f10ac35e 100644 --- a/wasm/package.json +++ b/wasm/package.json @@ -5,13 +5,16 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { - "test": "mocha -r ts-node/register tests/*.test.ts tests/**/*.test.ts", + "test": "mocha --trace-warnings", "generate": "npm run codegen:js && npm run codegen:ts", "codegen:js": "pbjs -t static-module '../src/proto/*.proto' --no-delimited --force-long -o generated/core_proto.js", + "codegen:js-browser": "pbjs -t static-module '../src/proto/*.proto' -w closure --no-delimited --force-long -o ../samples/wasm/core_proto.js", "codegen:ts": "pbts -o generated/core_proto.d.ts generated/core_proto.js", "clean": "rm -rf dist generated && mkdir -p dist/generated generated", - "build": "npm run clean && npm run generate && cp -R generated lib dist && tsc --skipLibCheck", - "copy:wasm": "mkdir -p lib && cp ../wasm-build/wasm/wallet-core.* lib" + "build": "npm run copy:wasm && npm run clean && npm run generate && cp -R generated lib dist && tsc && cp src/wallet-core.d.ts dist/src", + "build-and-test": "npm run build && npm test", + "copy:wasm": "mkdir -p lib && cp ../wasm-build/wasm/wallet-core.* lib", + "copy:wasm-sample": "cp ../wasm-build/wasm/wallet-core.* ../samples/wasm/" }, "repository": { "type": "git", @@ -31,10 +34,12 @@ }, "devDependencies": { "@types/chai": "^4.3.0", - "@types/mocha": "^9.1.0", + "@types/mocha": "^10.0.0", + "@types/node": "^18.7.18", + "@types/webextension-polyfill": "^0.9.0", "buffer": "^6.0.3", "chai": "^4.3.6", - "mocha": "^9.2.2", + "mocha": "^10.1.0", "ts-node": "^10.7.0", "typescript": "^4.6.3" } diff --git a/wasm/src/AnySigner.cpp b/wasm/src/AnySigner.cpp index 557ce3494ea..9ebd732149c 100644 --- a/wasm/src/AnySigner.cpp +++ b/wasm/src/AnySigner.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // #include @@ -34,7 +32,7 @@ class AnySigner { } }; -EMSCRIPTEN_BINDINGS(Wasm_TWAnyAddress) { +EMSCRIPTEN_BINDINGS(Wasm_TWAnySigner) { class_("AnySigner") .class_function("sign", &AnySigner::sign) .class_function("plan", &AnySigner::plan) diff --git a/wasm/src/BitcoinSigHashTypeExt.cpp b/wasm/src/BitcoinSigHashTypeExt.cpp new file mode 100644 index 00000000000..5ec44c8385f --- /dev/null +++ b/wasm/src/BitcoinSigHashTypeExt.cpp @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include + +using namespace emscripten; + +#include "BitcoinSigHashTypeExt.h" + +namespace TW::Wasm { + + auto BitcoinSigHashTypeExt::isSingle(TWBitcoinSigHashType type) { + return TWBitcoinSigHashTypeIsSingle(type); + } + auto BitcoinSigHashTypeExt::isNone(TWBitcoinSigHashType type) { + return TWBitcoinSigHashTypeIsNone(type); + } + + EMSCRIPTEN_BINDINGS(Wasm_BitcoinSigHashTypeExt) { + class_("BitcoinSigHashTypeExt") + .class_function("isSingle", &BitcoinSigHashTypeExt::isSingle) + .class_function("isNone", &BitcoinSigHashTypeExt::isNone); + }; +} diff --git a/wasm/src/BitcoinSigHashTypeExt.h b/wasm/src/BitcoinSigHashTypeExt.h new file mode 100644 index 00000000000..315c62b6c91 --- /dev/null +++ b/wasm/src/BitcoinSigHashTypeExt.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include + +#include + +using namespace emscripten; + +namespace TW::Wasm { + + class BitcoinSigHashTypeExt { + public: + static auto isSingle(TWBitcoinSigHashType type); + static auto isNone(TWBitcoinSigHashType type); + }; +} diff --git a/wasm/src/CoinTypeExt.cpp b/wasm/src/CoinTypeExt.cpp new file mode 100644 index 00000000000..40bd47b5292 --- /dev/null +++ b/wasm/src/CoinTypeExt.cpp @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include "WasmString.h" + +#include "generated/PrivateKey.h" +#include "generated/PublicKey.h" + +#include + +using namespace emscripten; + +#include "CoinTypeExt.h" + +namespace TW::Wasm { + + auto CoinTypeExt::blockchain(TWCoinType coin) { + return TWCoinTypeBlockchain(coin); + } + auto CoinTypeExt::purpose(TWCoinType coin) { + return TWCoinTypePurpose(coin); + } + auto CoinTypeExt::curve(TWCoinType coin) { + return TWCoinTypeCurve(coin); + } + auto CoinTypeExt::xpubVersion(TWCoinType coin) { + return TWCoinTypeXpubVersion(coin); + } + auto CoinTypeExt::xprvVersion(TWCoinType coin) { + return TWCoinTypeXprvVersion(coin); + } + auto CoinTypeExt::hrp(TWCoinType coin) { + return TWCoinTypeHRP(coin); + } + auto CoinTypeExt::p2pkhPrefix(TWCoinType coin) { + return TWCoinTypeP2pkhPrefix(coin); + } + auto CoinTypeExt::p2shPrefix(TWCoinType coin) { + return TWCoinTypeP2shPrefix(coin); + } + auto CoinTypeExt::staticPrefix(TWCoinType coin) { + return TWCoinTypeStaticPrefix(coin); + } + auto CoinTypeExt::chainId(TWCoinType coin) { + return TWStringToStd(TWCoinTypeChainId(coin)); + } + auto CoinTypeExt::slip44Id(TWCoinType coin) { + return TWCoinTypeSlip44Id(coin); + } + auto CoinTypeExt::ss58Prefix(TWCoinType coin) { + return TWCoinTypeSS58Prefix(coin); + } + auto CoinTypeExt::publicKeyType(TWCoinType coin) { + return TWCoinTypePublicKeyType(coin); + } + auto CoinTypeExt::validate(TWCoinType coin, const std::string& address) { + return TWCoinTypeValidate(coin, &address); + } + auto CoinTypeExt::derivationPath(TWCoinType coin) { + return TWStringToStd(TWCoinTypeDerivationPath(coin)); + } + auto CoinTypeExt::derivationPathWithDerivation(TWCoinType coin, TWDerivation derivation) { + return TWStringToStd(TWCoinTypeDerivationPathWithDerivation(coin, derivation)); + } + auto CoinTypeExt::deriveAddress(TWCoinType coin, WasmPrivateKey* privateKey) { + return TWStringToStd(TWCoinTypeDeriveAddress(coin, privateKey->instance)); + } + auto CoinTypeExt::deriveAddressFromPublicKey(TWCoinType coin, WasmPublicKey* publicKey) { + return TWStringToStd(TWCoinTypeDeriveAddressFromPublicKey(coin, publicKey->instance)); + } + auto CoinTypeExt::deriveAddressFromPublicKeyAndDerivation(TWCoinType coin, WasmPublicKey* publicKey, TWDerivation derivation) { + return TWStringToStd(TWCoinTypeDeriveAddressFromPublicKeyAndDerivation(coin, publicKey->instance, derivation)); + } + + EMSCRIPTEN_BINDINGS(Wasm_CoinTypeExt) { + class_("CoinTypeExt") + .class_function("blockchain", &CoinTypeExt::blockchain) + .class_function("purpose", &CoinTypeExt::purpose) + .class_function("curve", &CoinTypeExt::curve) + .class_function("xpubVersion", &CoinTypeExt::xpubVersion) + .class_function("xprvVersion", &CoinTypeExt::xprvVersion) + .class_function("hrp", &CoinTypeExt::hrp) + .class_function("p2pkhPrefix", &CoinTypeExt::p2pkhPrefix) + .class_function("p2shPrefix", &CoinTypeExt::p2shPrefix) + .class_function("staticPrefix", &CoinTypeExt::staticPrefix) + .class_function("chainId", &CoinTypeExt::chainId) + .class_function("slip44Id", &CoinTypeExt::slip44Id) + .class_function("ss58Prefix", &CoinTypeExt::ss58Prefix) + .class_function("publicKeyType", &CoinTypeExt::publicKeyType) + .class_function("validate", &CoinTypeExt::validate) + .class_function("derivationPath", &CoinTypeExt::derivationPath) + .class_function("derivationPathWithDerivation", &CoinTypeExt::derivationPathWithDerivation) + .class_function("deriveAddress", &CoinTypeExt::deriveAddress, allow_raw_pointers()) + .class_function("deriveAddressFromPublicKey", &CoinTypeExt::deriveAddressFromPublicKey, allow_raw_pointers()) + .class_function("deriveAddressFromPublicKeyAndDerivation", &CoinTypeExt::deriveAddressFromPublicKeyAndDerivation, allow_raw_pointers()); + }; +} diff --git a/wasm/src/CoinTypeExt.h b/wasm/src/CoinTypeExt.h new file mode 100644 index 00000000000..3f7d4d35842 --- /dev/null +++ b/wasm/src/CoinTypeExt.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include +#include + +#include "generated/PrivateKey.h" +#include "generated/PublicKey.h" + +#include + +using namespace emscripten; + +#include + +#include "WasmString.h" +#include "WasmData.h" + +namespace TW::Wasm { + + class CoinTypeExt { + public: + static auto blockchain(TWCoinType coin); + static auto purpose(TWCoinType coin); + static auto curve(TWCoinType coin); + static auto xpubVersion(TWCoinType coin); + static auto xprvVersion(TWCoinType coin); + static auto hrp(TWCoinType coin); + static auto p2pkhPrefix(TWCoinType coin); + static auto p2shPrefix(TWCoinType coin); + static auto staticPrefix(TWCoinType coin); + static auto chainId(TWCoinType coin); + static auto slip44Id(TWCoinType coin); + static auto ss58Prefix(TWCoinType coin); + static auto publicKeyType(TWCoinType coin); + static auto validate(TWCoinType coin, const std::string& address); + static auto derivationPath(TWCoinType coin); + static auto derivationPathWithDerivation(TWCoinType coin, TWDerivation derivation); + static auto deriveAddress(TWCoinType coin, WasmPrivateKey* privateKey); + static auto deriveAddressFromPublicKey(TWCoinType coin, WasmPublicKey* publicKey); + static auto deriveAddressFromPublicKeyAndDerivation(TWCoinType coin, WasmPublicKey* publicKey, TWDerivation derivation); + }; +} diff --git a/wasm/src/HDVersionExt.cpp b/wasm/src/HDVersionExt.cpp new file mode 100644 index 00000000000..da7b736a6db --- /dev/null +++ b/wasm/src/HDVersionExt.cpp @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include + +#include + +using namespace emscripten; + +#include "HDVersionExt.h" + +namespace TW::Wasm { + + auto HDVersionExt::isPublic(TWHDVersion version) { + return TWHDVersionIsPublic(version); + } + auto HDVersionExt::isPrivate(TWHDVersion version) { + return TWHDVersionIsPrivate(version); + } + + EMSCRIPTEN_BINDINGS(Wasm_HDVersionExt) { + class_("HDVersionExt") + .class_function("isPublic", &HDVersionExt::isPublic) + .class_function("isPrivate", &HDVersionExt::isPrivate); + }; +} diff --git a/wasm/src/HDVersionExt.h b/wasm/src/HDVersionExt.h new file mode 100644 index 00000000000..2bc5ca14035 --- /dev/null +++ b/wasm/src/HDVersionExt.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include + +#include + +using namespace emscripten; + +namespace TW::Wasm { + + class HDVersionExt { + public: + static auto isPublic(TWHDVersion version); + static auto isPrivate(TWHDVersion version); + }; +} diff --git a/wasm/src/HexCoding.cpp b/wasm/src/HexCoding.cpp index 1a1c58f57c9..b91dd05fc7c 100644 --- a/wasm/src/HexCoding.cpp +++ b/wasm/src/HexCoding.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // #include diff --git a/wasm/src/Random.cpp b/wasm/src/Random.cpp index d0ea0b2557f..84e49a515f4 100644 --- a/wasm/src/Random.cpp +++ b/wasm/src/Random.cpp @@ -1,24 +1,86 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // -#include #include #include -#include +#include + +// clang-format off +static uint32_t +javascript_random(void) +{ + return EM_ASM_INT_V({ + return Module.getRandomValue(); + }); +} + +// https://github.com/jedisct1/libsodium/blob/master/src/libsodium/randombytes/randombytes.c#L53 +static void +javascript_stir(void) +{ + EM_ASM({ + if (Module.getRandomValue === undefined) { + try { + var window_ = 'object' === typeof window ? window : self; + var crypto_ = typeof window_.crypto !== 'undefined' ? window_.crypto : window_.msCrypto; + var randomValuesStandard = function() { + var buf = new Uint32Array(1); + crypto_.getRandomValues(buf); + return buf[0] >>> 0; + }; + randomValuesStandard(); + Module.getRandomValue = randomValuesStandard; + } catch (e) { + try { + var crypto = require('crypto'); + var randomValueNodeJS = function() { + var buf = crypto['randomBytes'](4); + return (buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]) >>> 0; + }; + randomValueNodeJS(); + Module.getRandomValue = randomValueNodeJS; + } catch (e) { + throw 'No secure random number generator found'; + } + } + } + }); +} + + +static void +randombytes_init_if_needed(void) +{ + static bool initialized = false; + if (!initialized) { + javascript_stir(); + initialized = true; + } +} + +static void +javascript_buf(void * const buf, const size_t size) +{ + unsigned char *p = (unsigned char *) buf; + size_t i; + + for (i = (size_t) 0U; i < size; i++) { + p[i] = (unsigned char) javascript_random(); + } +} +// clang-format on extern "C" { uint32_t random32(void) { - std::mt19937 rng(std::random_device{}()); - return rng(); + randombytes_init_if_needed(); + return javascript_random(); } void random_buffer(uint8_t* buf, size_t len) { - std::mt19937 rng(std::random_device{}()); - std::generate_n(buf, len, [&rng]() -> uint8_t { return rng() & 0x000000ff; }); + randombytes_init_if_needed(); + javascript_buf(buf, len); return; } diff --git a/wasm/src/WasmData.cpp b/wasm/src/WasmData.cpp index 7426d443675..7916c9e39a0 100644 --- a/wasm/src/WasmData.cpp +++ b/wasm/src/WasmData.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // #include "WasmData.h" diff --git a/wasm/src/WasmData.h b/wasm/src/WasmData.h index 133f9b69f73..8cd2a4853ad 100644 --- a/wasm/src/WasmData.h +++ b/wasm/src/WasmData.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // #pragma once diff --git a/wasm/src/WasmString.cpp b/wasm/src/WasmString.cpp index 2ea9f9cec79..f03f58d7b6e 100644 --- a/wasm/src/WasmString.cpp +++ b/wasm/src/WasmString.cpp @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // #include "WasmString.h" diff --git a/wasm/src/WasmString.h b/wasm/src/WasmString.h index ae58e22daa8..9d7c8999e18 100644 --- a/wasm/src/WasmString.h +++ b/wasm/src/WasmString.h @@ -1,8 +1,6 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. // #pragma once diff --git a/wasm/src/enum-ext.d.ts b/wasm/src/enum-ext.d.ts new file mode 100644 index 00000000000..06164ae114c --- /dev/null +++ b/wasm/src/enum-ext.d.ts @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +export class BitcoinSigHashTypeExt { + static isSingle(type: BitcoinSigHashType): boolean; + static isNone(type: BitcoinSigHashType): boolean; +} + +export class CoinTypeExt { + static blockchain(coin: CoinType): Blockchain; + static purpose(coin: CoinType): Purpose; + static curve(coin: CoinType): Curve; + static xpubVersion(coin: CoinType): HDVersion; + static xprvVersion(coin: CoinType): HDVersion; + static hrp(coin: CoinType): HRP; + static p2pkhPrefix(coin: CoinType): number; + static p2shPrefix(coin: CoinType): number; + static staticPrefix(coin: CoinType): number; + static chainId(coin: CoinType): string; + static slip44Id(coin: CoinType): number; + static ss58Prefix(coin: CoinType): number; + static publicKeyType(coin: CoinType): PublicKeyType; + static validate(coin: CoinType, address: string): boolean; + static derivationPath(coin: CoinType): string; + static derivationPathWithDerivation(coin: CoinType, derivation: Derivation): string; + static deriveAddress(coin: CoinType, privateKey: PrivateKey): string; + static deriveAddressFromPublicKey(coin: CoinType, publicKey: PublicKey): string; +} + +export class HDVersionExt { + static isPublic(version: HDVersion): boolean; + static isPrivate(version: HDVersion): boolean; +} diff --git a/wasm/src/keystore/default-impl.ts b/wasm/src/keystore/default-impl.ts new file mode 100644 index 00000000000..6da4ae6c57a --- /dev/null +++ b/wasm/src/keystore/default-impl.ts @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import {WalletCore, CoinType, PrivateKey, StoredKey, StoredKeyEncryption} from "../wallet-core"; +import * as Types from "./types"; + +export class Default implements Types.IKeyStore { + private readonly core: WalletCore; + private readonly storage: Types.IStorage; + + constructor(core: WalletCore, storage: Types.IStorage) { + this.core = core; + this.storage = storage; + } + + hasWallet(id: string): Promise { + return this.storage + .get(id) + .then((wallet) => true) + .catch((error) => false); + } + + load(id: string): Promise { + return this.storage.get(id); + } + + loadAll(): Promise { + return this.storage.loadAll(); + } + + delete(id: string, password: string): Promise { + return this.storage.delete(id, password); + } + + mapWallet(storedKey: StoredKey): Types.Wallet { + const json = storedKey.exportJSON(); + return JSON.parse(Buffer.from(json).toString()) as Types.Wallet; + } + + mapStoredKey(wallet: Types.Wallet): StoredKey { + const json = Buffer.from(JSON.stringify(wallet)); + return this.core.StoredKey.importJSON(json); + } + + importWallet(wallet: Types.Wallet): Promise { + return this.storage.set(wallet.id, wallet); + } + + import( + mnemonic: string, + name: string, + password: string, + coins: CoinType[], + encryption: StoredKeyEncryption + ): Promise { + return new Promise((resolve, reject) => { + const { Mnemonic, StoredKey, HDWallet, StoredKeyEncryption } = this.core; + + if (!Mnemonic.isValid(mnemonic)) { + throw Types.Error.InvalidMnemonic; + } + let pass = Buffer.from(password); + let storedKey = StoredKey.importHDWalletWithEncryption(mnemonic, name, pass, coins[0], encryption); + let hdWallet = HDWallet.createWithMnemonic(mnemonic, ""); + coins.forEach((coin) => { + storedKey.accountForCoin(coin, hdWallet); + }); + let wallet = this.mapWallet(storedKey); + + storedKey.delete(); + hdWallet.delete(); + + this.importWallet(wallet) + .then(() => resolve(wallet)) + .catch((error) => reject(error)); + }); + } + + importKey( + key: Uint8Array, + name: string, + password: string, + coin: CoinType, + encryption: StoredKeyEncryption + ): Promise { + return new Promise((resolve, reject) => { + const { StoredKey, PrivateKey, Curve, StoredKeyEncryption } = this.core; + + // FIXME: get curve from coin + if ( + !PrivateKey.isValid(key, Curve.secp256k1) || + !PrivateKey.isValid(key, Curve.ed25519) + ) { + throw Types.Error.InvalidKey; + } + let pass = Buffer.from(password); + let storedKey = StoredKey.importPrivateKeyWithEncryption(key, name, pass, coin, encryption); + let wallet = this.mapWallet(storedKey); + storedKey.delete(); + this.importWallet(wallet) + .then(() => resolve(wallet)) + .catch((error) => reject(error)); + }); + } + + addAccounts( + id: string, + password: string, + coins: CoinType[] + ): Promise { + return this.load(id).then((wallet) => { + let storedKey = this.mapStoredKey(wallet); + let hdWallet = storedKey.wallet(Buffer.from(password)); + coins.forEach((coin) => { + storedKey.accountForCoin(coin, hdWallet); + }); + let newWallet = this.mapWallet(storedKey); + storedKey.delete(); + hdWallet.delete(); + return this.importWallet(newWallet).then(() => newWallet); + }); + } + + getKey( + id: string, + password: string, + account: Types.ActiveAccount + ): Promise { + return this.load(id).then((wallet) => { + let storedKey = this.mapStoredKey(wallet); + let hdWallet = storedKey.wallet(Buffer.from(password)); + let coin = (this.core.CoinType as any).values["" + account.coin]; + let privateKey = hdWallet.getKey(coin, account.derivationPath); + storedKey.delete(); + hdWallet.delete(); + return privateKey; + }); + } + + export(id: string, password: string): Promise { + return this.load(id).then((wallet) => { + let storedKey = this.mapStoredKey(wallet); + let value: string | Uint8Array; + switch (wallet.type) { + case Types.WalletType.Mnemonic: + value = storedKey.decryptMnemonic(Buffer.from(password)); + break; + case Types.WalletType.PrivateKey: + value = storedKey.decryptPrivateKey(Buffer.from(password)); + break; + default: + throw Types.Error.InvalidJSON; + } + storedKey.delete(); + return value; + }); + } +} diff --git a/wasm/src/keystore/extension-storage.ts b/wasm/src/keystore/extension-storage.ts new file mode 100644 index 00000000000..b17e3398649 --- /dev/null +++ b/wasm/src/keystore/extension-storage.ts @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import { Storage } from "webextension-polyfill"; +import * as Types from "./types"; + +// Extension KeyStore +export class ExtensionStorage implements Types.IStorage { + readonly storage: Storage.StorageArea; + readonly walletIdsKey: string; + + constructor(walletIdsKey: string, storage: Storage.StorageArea) { + this.walletIdsKey = walletIdsKey; + this.storage = storage; + } + + get(id: string): Promise { + return this.storage.get(id).then((object) => { + let wallet = object[id]; + if (wallet === undefined) { + throw Types.Error.WalletNotFound; + } + return wallet as Types.Wallet; + }); + } + + set(id: string, wallet: Types.Wallet): Promise { + return this.getWalletIds().then((ids) => { + if (ids.indexOf(id) === -1) { + ids.push(id); + } + return this.storage.set({ + [id]: wallet, + [this.walletIdsKey]: ids, + }); + }); + } + + loadAll(): Promise { + return this.getWalletIds().then((ids) => { + if (ids.length === 0) { + return []; + } + return this.storage + .get(ids) + .then((wallets) => Object.keys(wallets).map((key) => wallets[key])); + }); + } + + delete(id: string, password: string): Promise { + return this.getWalletIds().then((ids) => { + let index = ids.indexOf(id); + if (index === -1) { + return; + } + ids.splice(index, 1); + return this.storage + .remove(id) + .then(() => this.storage.set({ [this.walletIdsKey]: ids })); + }); + } + + private getWalletIds(): Promise { + return this.storage.get(this.walletIdsKey).then((object) => { + let ids = object[this.walletIdsKey] as string[]; + return ids === undefined ? [] : ids; + }); + } +} diff --git a/wasm/src/keystore/fs-storage.ts b/wasm/src/keystore/fs-storage.ts new file mode 100644 index 00000000000..fd5eaf64631 --- /dev/null +++ b/wasm/src/keystore/fs-storage.ts @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import * as Types from "./types"; +import * as fs from "fs/promises"; + +// FileSystem Storage +export class FileSystemStorage implements Types.IStorage { + private readonly directory: string; + + constructor(directory: string) { + this.directory = directory.endsWith("/") ? directory : directory + "/"; + } + + getFilename(id): string { + return this.directory + id + ".json"; + } + + get(id: string): Promise { + return fs + .readFile(this.getFilename(id)) + .then((data) => JSON.parse(data.toString()) as Types.Wallet); + } + + set(id: string, wallet: Types.Wallet): Promise { + return fs.writeFile(this.getFilename(id), JSON.stringify(wallet)); + } + + loadAll(): Promise { + return fs.readdir(this.directory).then((files) => { + return Promise.all( + files + .filter((file) => file.endsWith(".json")) + .map((file) => this.get(file.replace(".json", ""))) + ); + }); + } + + delete(id: string, password: string): Promise { + return fs.unlink(this.getFilename(id)); + } +} diff --git a/wasm/src/keystore/index.ts b/wasm/src/keystore/index.ts new file mode 100644 index 00000000000..663f3affeb0 --- /dev/null +++ b/wasm/src/keystore/index.ts @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +export { Default } from "./default-impl"; +export * from "./types"; +export { FileSystemStorage } from "./fs-storage"; +export { ExtensionStorage } from "./extension-storage"; + diff --git a/wasm/src/keystore/types.ts b/wasm/src/keystore/types.ts new file mode 100644 index 00000000000..bccb05ef58a --- /dev/null +++ b/wasm/src/keystore/types.ts @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import { CoinType, PrivateKey, StoredKeyEncryption } from "../wallet-core"; + +export enum WalletType { + Mnemonic = "mnemonic", + PrivateKey = "privateKey", + WatchOnly = "watchOnly", + Hardware = "hardware", +} + +export enum Error { + WalletNotFound = "wallet not found", + AccountNotFound = "account not found", + InvalidPassword = "invalid password", + InvalidMnemonic = "invalid mnemonic", + InvalidJSON = "invalid JSON", + InvalidKey = "invalid key", +} + +export interface ActiveAccount { + address: string; + coin: number; + publicKey: string; + derivationPath: string; + extendedPublicKey?: string; +} + +export interface Wallet { + id: string; + + type: WalletType; + name: string; + version: number; + activeAccounts: ActiveAccount[]; +} + +export interface IKeyStore { + // Check if wallet id exists + hasWallet(id: string): Promise; + + // Load a wallet by wallet id + load(id: string): Promise; + + // Load all wallets + loadAll(): Promise; + + // Import a wallet by mnemonic, name, password and initial active accounts (from coinTypes) + import( + mnemonic: string, + name: string, + password: string, + coins: CoinType[], + encryption: StoredKeyEncryption + ): Promise; + + // Import a wallet by private key, name and password + importKey( + key: Uint8Array, + name: string, + password: string, + coin: CoinType, + encryption: StoredKeyEncryption + ): Promise; + + // Import a Wallet object directly + importWallet(wallet: Wallet): Promise; + + // Add active accounts to a wallet by wallet id, password, coin + addAccounts(id: string, password: string, coins: CoinType[]): Promise; + + // Get private key of an account by wallet id, password, coin and derivation path + getKey( + id: string, + password: string, + account: ActiveAccount + ): Promise; + + // Delete a wallet by wallet id and password.aq1aq + delete(id: string, password: string): Promise; + + // Export a wallet by wallet id and password, returns mnemonic or private key + export(id: string, password: string): Promise; +} + +export interface IStorage { + get(id: string): Promise; + set(id: string, wallet: Wallet): Promise; + loadAll(): Promise; + delete(id: string, password: string): Promise; +} diff --git a/wasm/tests/AES.test.ts b/wasm/tests/AES.test.ts index fd77e2a228c..a6a6b65a56a 100644 --- a/wasm/tests/AES.test.ts +++ b/wasm/tests/AES.test.ts @@ -1,17 +1,15 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import "mocha"; import { assert } from "chai"; -import { WalletCore } from "../dist"; import { Buffer } from "buffer"; describe("AES", () => { + it("test decrypting", () => { - const { AES, HexCoding, AESPaddingMode } = WalletCore; + const { AES, HexCoding, AESPaddingMode } = globalThis.core; const key = HexCoding.decode( "5caa3a74154cee16bd1b570a1330be46e086474ac2f4720530662ef1a469662c" @@ -29,7 +27,7 @@ describe("AES", () => { }); it("test encrypting", () => { - const { AES, HexCoding, AESPaddingMode } = WalletCore; + const { AES, HexCoding, AESPaddingMode } = globalThis.core; const key = HexCoding.decode( "bbc82a01ebdb14698faee4a9e5038de72c995a9f6bcdb21903d62408b0c5ca96" diff --git a/wasm/tests/AnyAddress.test.ts b/wasm/tests/AnyAddress.test.ts index 80c89098ebf..5701ea89c98 100644 --- a/wasm/tests/AnyAddress.test.ts +++ b/wasm/tests/AnyAddress.test.ts @@ -1,16 +1,13 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import "mocha"; import { assert } from "chai"; -import { WalletCore } from "../dist"; describe("AnyAddress", () => { it("test validating Solana address", () => { - const { AnyAddress, HexCoding, CoinType } = WalletCore; + const { AnyAddress, HexCoding, CoinType } = globalThis.core; var address = AnyAddress.createWithString( "7v91N7iZ9mNicL8WfG6cgSCKyRXydQjLh6UYBWwm6y1Q", @@ -27,5 +24,5 @@ describe("AnyAddress", () => { assert.equal(HexCoding.encode(data), "0x66c2f508c9c555cacc9fb26d88e88dd54e210bb5a8bce5687f60d7e75c4cd07f"); address.delete(); - }); + }).timeout(5000); }); diff --git a/wasm/tests/Base32.test.ts b/wasm/tests/Base32.test.ts new file mode 100644 index 00000000000..281a535c48d --- /dev/null +++ b/wasm/tests/Base32.test.ts @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import { assert } from "chai"; +import { Buffer } from "buffer"; + +describe("Base32", () => { + + it("test decrypting", () => { + const { Base32 } = globalThis.core; + + const decoded = Base32.decode("JBSWY3DPK5XXE3DE"); + + assert.equal(Buffer.from(decoded).toString(), "HelloWorld"); + }); + + it("test decrypting with alphabet", () => { + const { Base32 } = globalThis.core; + + const decoded = Base32.decodeWithAlphabet( + "g52w64jworydimrxov5hmn3gpj2gwyttnzxdmndjo5xxiztsojuxg5dxobzhs6i", + "abcdefghijklmnopqrstuvwxyz234567" + ); + + assert.equal( + Buffer.from(decoded).toString(), + "7uoq6tp427uzv7fztkbsnn64iwotfrristwpryy" + ); + }); + + it("test encrypting", () => { + const { Base32 } = globalThis.core; + + const encoded = Base32.encode(Buffer.from("HelloWorld")); + + assert.equal(encoded, "JBSWY3DPK5XXE3DE"); + }); + + it("test encrypting with alphabet", () => { + const { Base32 } = globalThis.core; + + const encoded = Base32.encodeWithAlphabet( + Buffer.from("7uoq6tp427uzv7fztkbsnn64iwotfrristwpryy"), + "abcdefghijklmnopqrstuvwxyz234567" + ); + + assert.equal( + encoded, + "g52w64jworydimrxov5hmn3gpj2gwyttnzxdmndjo5xxiztsojuxg5dxobzhs6i" + ); + }); +}); diff --git a/wasm/tests/Base64.test.ts b/wasm/tests/Base64.test.ts new file mode 100644 index 00000000000..b89c99820ec --- /dev/null +++ b/wasm/tests/Base64.test.ts @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import { assert } from "chai"; +import { Buffer } from "buffer"; + +describe("Base64", () => { + + it("test decoding", () => { + const { Base64 } = globalThis.core; + + const decoded = Base64.decode("SGVsbG9Xb3JsZA=="); + + assert.equal(Buffer.from(decoded).toString(), "HelloWorld"); + }); + + it("test encoding", () => { + const { Base64 } = globalThis.core; + + const encoded = Base64.encode(Buffer.from("HelloWorld")); + + assert.equal(encoded, "SGVsbG9Xb3JsZA=="); + }); + + it("test encoding (URL-safe)", () => { + const { Base64 } = globalThis.core; + + const encoded = Base64.encodeUrl(Buffer.from("==?=")); + + assert.equal(encoded, "PT0_PQ=="); + }); + + it("test decoding (URL-safe)", () => { + const { Base64 } = globalThis.core; + const decoded = Base64.decodeUrl("PT0_PQ=="); + + assert.equal(Buffer.from(decoded).toString(), "==?="); + }); +}); diff --git a/wasm/tests/BitcoinSigHashType.test.ts b/wasm/tests/BitcoinSigHashType.test.ts new file mode 100644 index 00000000000..4ff0fdc1a26 --- /dev/null +++ b/wasm/tests/BitcoinSigHashType.test.ts @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; + +describe("BitcoinSigHashType", () => { + it("test isSingle", () => { + const { BitcoinSigHashType, BitcoinSigHashTypeExt } = globalThis.core; + + assert.isFalse(BitcoinSigHashTypeExt.isSingle(BitcoinSigHashType.all)); + assert.isFalse(BitcoinSigHashTypeExt.isSingle(BitcoinSigHashType.none)); + assert.isTrue(BitcoinSigHashTypeExt.isSingle(BitcoinSigHashType.single)); + assert.isFalse(BitcoinSigHashTypeExt.isSingle(BitcoinSigHashType.fork)); + assert.isFalse(BitcoinSigHashTypeExt.isSingle(BitcoinSigHashType.forkBTG)); + }); + + it("test isNone", () => { + const { BitcoinSigHashType, BitcoinSigHashTypeExt } = globalThis.core; + + assert.isFalse(BitcoinSigHashTypeExt.isNone(BitcoinSigHashType.all)); + assert.isTrue(BitcoinSigHashTypeExt.isNone(BitcoinSigHashType.none)); + assert.isFalse(BitcoinSigHashTypeExt.isNone(BitcoinSigHashType.single)); + assert.isFalse(BitcoinSigHashTypeExt.isNone(BitcoinSigHashType.fork)); + assert.isFalse(BitcoinSigHashTypeExt.isNone(BitcoinSigHashType.forkBTG)); + }); +}); diff --git a/wasm/tests/Blockchain/Aptos.test.ts b/wasm/tests/Blockchain/Aptos.test.ts new file mode 100644 index 00000000000..98ccaf9ce27 --- /dev/null +++ b/wasm/tests/Blockchain/Aptos.test.ts @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; +import { Buffer } from "buffer"; +import { TW } from "../../dist"; +import Long = require("long"); + +describe("Aptos", () => { + it("test sign aptos", () => { + const { PrivateKey, HexCoding, AnySigner, AnyAddress, CoinType } = globalThis.core; + const txDataInput = TW.Aptos.Proto.SigningInput.create({ + chainId: 33, + sender: "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + transfer: TW.Aptos.Proto.TransferMessage.create({ + amount: new Long(1000), + to: "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + }), + sequenceNumber: new Long(99), + expirationTimestampSecs: new Long(3664390082), + gasUnitPrice: new Long(100), + maxGasAmount: new Long(3296766), + privateKey: PrivateKey.createWithData( + HexCoding.decode( + "0x5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", + ), + ).data(), + }); + const input = TW.Aptos.Proto.SigningInput.encode(txDataInput).finish(); + const outputData = AnySigner.sign(input, CoinType.aptos); + const output = TW.Aptos.Proto.SigningOutput.decode(outputData); + assert.equal(HexCoding.encode(output.encoded), "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01") + assert.equal(HexCoding.encode(output.authenticator!.signature), "0x5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01") + assert.equal(HexCoding.encode(output.rawTxn), "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021") + }); +}); diff --git a/wasm/tests/Blockchain/Bitcoin.test.ts b/wasm/tests/Blockchain/Bitcoin.test.ts index cf518fdb8fa..c55ebeb0db8 100644 --- a/wasm/tests/Blockchain/Bitcoin.test.ts +++ b/wasm/tests/Blockchain/Bitcoin.test.ts @@ -1,16 +1,337 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import "mocha"; import { assert } from "chai"; -import { TW, WalletCore } from "../../dist"; +import { TW } from "../../dist"; +import Long = require("long"); describe("Bitcoin", () => { it("test Bitcoin SigningInput / SigningOutput", () => { assert.isNotNull(TW.Bitcoin.Proto.SigningInput); assert.isNotNull(TW.Binance.Proto.SigningOutput); }); + + // Transfer from P2TR to P2WPKH address. + // Successfully broadcasted: https://mempool.space/tx/a9c63dfe54f6ff462155d966a54226c456b3e43b52a9abe55d7fa87d6564c6e4 + it("test Bitcoin sign P2TR", () => { + const { AnySigner, BitcoinSigHashType, PrivateKey, HexCoding, CoinType } = globalThis.core; + const Proto = TW.BitcoinV2.Proto; + + const privateKeyData = HexCoding.decode("7fa638b0df495b2968ae6dc7011c4db08c86df16c91aa71a77ee6a222954e5bb"); + const dustAmount = new Long(546); + const utxoTxId = HexCoding.decode("75ed78f0ae2bad924065d2357ef01184ceee2181c44e03337746512be9371a82").reverse(); + + const privateKey = PrivateKey.createWithData(privateKeyData); + const publicKey = privateKey.getPublicKeySecp256k1(true); + + const utxo0 = Proto.Input.create({ + outPoint: { + hash: utxoTxId, + vout: 1, + }, + value: new Long(8_802), + sighashType: BitcoinSigHashType.all.value, + scriptBuilder: { + // Spend a UTXO sent to a P2TR address associated with this account + // (bc1pgy48w0sthfw0k4rz6qjv6jljensms6y2nea850u9ql4m4rcqmevqp3w344). + p2trKeyPath: publicKey.data(), + }, + }); + + const out0 = Proto.Output.create({ + value: new Long(3_000), + toAddress: "bc1qtaquch7d90x37qre6f75z5a6l0luzh0c03epyz", + }); + + // Send the change amount back to the same P2TR address. + // The correct amount will be calculated for us. + const changeOut = Proto.Output.create({ + builder: { + p2trKeyPath: publicKey.data(), + }, + }); + + const signingInput = Proto.SigningInput.create({ + version: Proto.TransactionVersion.V2, + privateKeys: [privateKeyData], + inputs: [utxo0], + outputs: [out0], + inputSelector: Proto.InputSelector.SelectDescending, + feePerVb: new Long(8), + chainInfo: { + p2pkhPrefix: 0, + p2shPrefix: 5, + }, + changeOutput: changeOut, + // WARNING Do not use in production! + dangerousUseFixedSchnorrRng: true, + fixedDustThreshold: dustAmount, + }); + + const legacySigningInput = TW.Bitcoin.Proto.SigningInput.create({ + signingV2: signingInput, + }); + + const encoded = TW.Bitcoin.Proto.SigningInput.encode(legacySigningInput).finish(); + const outputData = AnySigner.sign(encoded, CoinType.bitcoin); + const output = TW.Bitcoin.Proto.SigningOutput.decode(outputData); + + assert.equal(output.error, TW.Common.Proto.SigningError.OK); + assert.equal(output.signingResultV2!.error, TW.Common.Proto.SigningError.OK); + assert.equal( + HexCoding.encode(output.signingResultV2!.encoded), + "0x02000000000101821a37e92b51467733034ec48121eece8411f07e35d2654092ad2baef078ed750100000000ffffffff02b80b0000000000001600145f41cc5fcd2bcd1f0079d27d4153bafbffc15df83212000000000000225120412a773e0bba5cfb5462d024cd4bf2cce1b8688a9e7a7a3f8507ebba8f00de580140cbe4d13bc9e067b042179e2c217e4e4b1d552119d12839aa4df11c21282f9159e2c4b58a4f22b291c200c0d0c5f277902282bdd78589dff0edbea89d3f00d77400000000" + ); + assert.equal( + HexCoding.encode(output.signingResultV2!.txid), + "0xa9c63dfe54f6ff462155d966a54226c456b3e43b52a9abe55d7fa87d6564c6e4" + ); + }); + + // Successfully broadcasted: https://www.blockchain.com/explorer/transactions/btc/3e3576eb02667fac284a5ecfcb25768969680cc4c597784602d0a33ba7c654b7 + it("test Bitcoin sign BRC20 Transfer", () => { + const { AnySigner, BitcoinSigHashType, PrivateKey, HexCoding, CoinType } = globalThis.core; + const Proto = TW.BitcoinV2.Proto; + + const privateKeyData = HexCoding.decode("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); + const dustSatoshis = new Long(546); + const txIdInscription = HexCoding.decode("7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca").reverse(); + const txIdForFees = HexCoding.decode("797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1").reverse(); + + const privateKey = PrivateKey.createWithData(privateKeyData); + const publicKey = privateKey.getPublicKeySecp256k1(true); + const bobAddress = "bc1qazgc2zhu2kmy42py0vs8d7yff67l3zgpwfzlpk"; + + // Now spend just created `7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca` reveal output. + const utxo0 = Proto.Input.create({ + outPoint: { + hash: txIdInscription, + vout: 0, + }, + value: dustSatoshis, + sighashType: BitcoinSigHashType.all.value, + scriptBuilder: { + p2wpkh: { + pubkey: publicKey.data(), + }, + }, + }); + + // UTXO to cover fee. + const utxo1 = Proto.Input.create({ + outPoint: { + hash: txIdForFees, + vout: 1, + }, + value: new Long(16_400), + sighashType: BitcoinSigHashType.all.value, + scriptBuilder: { + p2wpkh: { + pubkey: publicKey.data(), + }, + }, + }); + + const out0 = Proto.Output.create({ + value: dustSatoshis, + toAddress: bobAddress, + }); + + // Change/return transaction. Set it explicitly. + const changeOut = Proto.Output.create({ + value: new Long(13_400), + builder: { + p2wpkh: { + pubkey: publicKey.data() + }, + }, + }); + + const signingInput = Proto.SigningInput.create({ + version: Proto.TransactionVersion.V2, + privateKeys: [privateKeyData], + inputs: [utxo0, utxo1], + outputs: [out0, changeOut], + inputSelector: Proto.InputSelector.UseAll, + chainInfo: { + p2pkhPrefix: 0, + p2shPrefix: 5, + }, + fixedDustThreshold: dustSatoshis, + }); + + const legacySigningInput = TW.Bitcoin.Proto.SigningInput.create({ + signingV2: signingInput, + }); + + const encoded = TW.Bitcoin.Proto.SigningInput.encode(legacySigningInput).finish(); + const outputData = AnySigner.sign(encoded, CoinType.bitcoin); + const output = TW.Bitcoin.Proto.SigningOutput.decode(outputData); + + assert.equal(output.error, TW.Common.Proto.SigningError.OK); + assert.equal(output.signingResultV2!.error, TW.Common.Proto.SigningError.OK); + assert.equal( + HexCoding.encode(output.signingResultV2!.encoded), + "0x02000000000102ca3edda74a46877efa5364ab85947e148508713910ada23e147ea28926dc46700000000000ffffffffb11f1782607a1fe5f033ccf9dc17404db020a0dedff94183596ee67ad4177d790100000000ffffffff022202000000000000160014e891850afc55b64aa8247b2076f8894ebdf889015834000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d024830450221008798393eb0b7390217591a8c33abe18dd2f7ea7009766e0d833edeaec63f2ec302200cf876ff52e68dbaf108a3f6da250713a9b04949a8f1dcd1fb867b24052236950121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb0248304502210096bbb9d1f0596d69875646689e46f29485e8ceccacde9d0025db87fd96d3066902206d6de2dd69d965d28df3441b94c76e812384ab9297e69afe3480ee4031e1b2060121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000" + ); + assert.equal( + HexCoding.encode(output.signingResultV2!.txid), + "0x3e3576eb02667fac284a5ecfcb25768969680cc4c597784602d0a33ba7c654b7" + ); + }); + + // Successfully broadcasted: https://www.blockchain.com/explorer/transactions/btc/797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1 + it("test Bitcoin sign BRC20 Commit", () => { + const { AnySigner, BitcoinSigHashType, PrivateKey, HexCoding, CoinType } = globalThis.core; + const Proto = TW.BitcoinV2.Proto; + + const privateKeyData = HexCoding.decode("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); + const dustAmount = new Long(546); + const txId = HexCoding.decode("8ec895b4d30adb01e38471ca1019bfc8c3e5fbd1f28d9e7b5653260d89989008").reverse(); + + const privateKey = PrivateKey.createWithData(privateKeyData); + const publicKey = privateKey.getPublicKeySecp256k1(true); + + const utxo0 = Proto.Input.create({ + outPoint: { + hash: txId, + vout: 1, + }, + value: new Long(26_400), + sighashType: BitcoinSigHashType.all.value, + scriptBuilder: { + p2wpkh: { + pubkey: publicKey.data(), + }, + }, + }); + + const out0 = Proto.Output.create({ + value: new Long(7_000), + builder: { + brc20Inscribe: { + inscribeTo: publicKey.data(), + ticker: "oadf", + transferAmount: "20", + }, + }, + }); + + // Change/return transaction. Set it explicitly. + const changeOut = Proto.Output.create({ + value: new Long(16_400), + builder: { + p2wpkh: { + pubkey: publicKey.data(), + }, + }, + }); + + const signingInput = Proto.SigningInput.create({ + version: Proto.TransactionVersion.V2, + privateKeys: [privateKeyData], + inputs: [utxo0], + outputs: [out0, changeOut], + inputSelector: Proto.InputSelector.UseAll, + chainInfo: { + p2pkhPrefix: 0, + p2shPrefix: 5, + }, + fixedDustThreshold: dustAmount, + }); + + const legacySigningInput = TW.Bitcoin.Proto.SigningInput.create({ + signingV2: signingInput, + }); + + const encoded = TW.Bitcoin.Proto.SigningInput.encode(legacySigningInput).finish(); + const outputData = AnySigner.sign(encoded, CoinType.bitcoin); + const output = TW.Bitcoin.Proto.SigningOutput.decode(outputData); + + assert.equal(output.error, TW.Common.Proto.SigningError.OK); + assert.equal(output.signingResultV2!.error, TW.Common.Proto.SigningError.OK); + assert.equal( + HexCoding.encode(output.signingResultV2!.encoded), + "0x02000000000101089098890d2653567b9e8df2d1fbe5c3c8bf1910ca7184e301db0ad3b495c88e0100000000ffffffff02581b000000000000225120e8b706a97732e705e22ae7710703e7f589ed13c636324461afa443016134cc051040000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d02483045022100a44aa28446a9a886b378a4a65e32ad9a3108870bd725dc6105160bed4f317097022069e9de36422e4ce2e42b39884aa5f626f8f94194d1013007d5a1ea9220a06dce0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000" + ); + assert.equal( + HexCoding.encode(output.signingResultV2!.txid), + "0x797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1" + ); + }); + + // Successfully broadcasted: https://www.blockchain.com/explorer/transactions/btc/7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca + it("test Bitcoin sign BRC20 Reveal", () => { + const { AnySigner, BitcoinSigHashType, PrivateKey, HexCoding, CoinType } = globalThis.core; + const Proto = TW.BitcoinV2.Proto; + + const privateKeyData = HexCoding.decode("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); + const dustAmount = new Long(546); + const txIdCommit = HexCoding.decode("797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1").reverse(); + + const privateKey = PrivateKey.createWithData(privateKeyData); + const publicKey = privateKey.getPublicKeySecp256k1(true); + + // Now spend just created `797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1` commit output. + const utxo0 = Proto.Input.create({ + outPoint: { + hash: txIdCommit, + vout: 0, + }, + value: new Long(7_000), + sighashType: BitcoinSigHashType.all.value, + scriptBuilder: { + brc20Inscribe: { + inscribeTo: publicKey.data(), + ticker: "oadf", + transferAmount: "20", + }, + }, + }); + + const out0 = Proto.Output.create({ + value: dustAmount, + builder: { + p2wpkh: { + pubkey: publicKey.data(), + }, + }, + }); + + const signingInput = Proto.SigningInput.create({ + version: Proto.TransactionVersion.V2, + privateKeys: [privateKeyData], + inputs: [utxo0], + outputs: [out0], + inputSelector: Proto.InputSelector.UseAll, + chainInfo: { + p2pkhPrefix: 0, + p2shPrefix: 5, + }, + // WARNING Do not use in production! + dangerousUseFixedSchnorrRng: true, + fixedDustThreshold: dustAmount, + }); + + const legacySigningInput = TW.Bitcoin.Proto.SigningInput.create({ + signingV2: signingInput, + }); + + const encoded = TW.Bitcoin.Proto.SigningInput.encode(legacySigningInput).finish(); + const outputData = AnySigner.sign(encoded, CoinType.bitcoin); + const output = TW.Bitcoin.Proto.SigningOutput.decode(outputData); + + assert.equal(output.error, TW.Common.Proto.SigningError.OK); + assert.equal(output.signingResultV2!.error, TW.Common.Proto.SigningError.OK); + assert.equal( + HexCoding.encode(output.signingResultV2!.encoded), + "0x02000000000101b11f1782607a1fe5f033ccf9dc17404db020a0dedff94183596ee67ad4177d790000000000ffffffff012202000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d03406a35548b8fa4620028e021a944c1d3dc6e947243a7bfc901bf63fefae0d2460efa149a6440cab51966aa4f09faef2d1e5efcba23ab4ca6e669da598022dbcfe35b0063036f7264010118746578742f706c61696e3b636861727365743d7574662d3800377b2270223a226272632d3230222c226f70223a227472616e73666572222c227469636b223a226f616466222c22616d74223a223230227d6821c00f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000" + ); + assert.equal( + HexCoding.encode(output.signingResultV2!.txid), + "0x7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca" + ); + }); }); diff --git a/wasm/tests/Blockchain/Cardano.test.ts b/wasm/tests/Blockchain/Cardano.test.ts new file mode 100644 index 00000000000..8624da2e81c --- /dev/null +++ b/wasm/tests/Blockchain/Cardano.test.ts @@ -0,0 +1,84 @@ +import {TW} from "../../dist"; +import {assert} from "chai"; +import Long = require("long"); + +describe("Cardano", () => { + it("test outputMinAdaAmount", () => { + const { Cardano } = globalThis.core; + + const output = TW.Cardano.Proto.TxOutput.create() + const outputData = TW.Cardano.Proto.TxOutput.encode(output).finish() + const coinsPerUtxoByte = "4310" + const toAddress = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23" + + const actual = Cardano.outputMinAdaAmount(toAddress, outputData, coinsPerUtxoByte) + assert.equal(actual, "969750") + }); + + it("test signTransferNft", () => { + const { AnySigner, CoinType, HexCoding } = globalThis.core; + + const privateKeyData = HexCoding.decode("d09831a668db6b36ffb747600cb1cd3e3d34f36e1e6feefc11b5f988719b7557a7029ab80d3e6fe4180ad07a59ddf742ea9730f3c4145df6365fa4ae2ee49c3392e19444caf461567727b7fefec40a3763bdb6ce5e0e8c05f5e340355a8fef4528dfe7502cfbda49e38f5a0021962d52dc3dee82834a23abb6750981799b75577d1ed9af9853707f0ef74264274e71b2f12e86e3c91314b6efa75ef750d9711b84cedd742ab873ef2f9566ad20b3fc702232c6d2f5d83ff425019234037d1e58"); + const fromAddress = "addr1qy5eme9r6frr0m6q2qpncg282jtrhq5lg09uxy2j0545hj8rv7v2ntdxuv6p4s3eq4lqzg39lewgvt6fk5kmpa0zppesufzjud" + const toAddress = "addr1qy9wjfn6nd8kak6dd8z53u7t5wt9f4lx0umll40px5hnq05avwcsq5r3ytdp36wttzv4558jaq8lvhgqhe3y8nuf5xrquju7z4" + // 1.20249 ADA. Amount locked by the NFT. + const nftInputAmount = new Long(1202490); + + const tokenAmount = TW.Cardano.Proto.TokenAmount.create({ + policyId: "219820e6cb04316f41a337fea356480f412e7acc147d28f175f21b5e", + assetName: "coolcatssociety4567", + amount: HexCoding.decode("1"), + }); + const utxo1 = TW.Cardano.Proto.TxInput.create({ + outPoint: TW.Cardano.Proto.OutPoint.create({ + txHash: HexCoding.decode("aba499ec2f23529e70bb256ceaffcc6274a882cf02f29e5670c75ee980d7c2b8"), + outputIndex: new Long(0), + }), + address: fromAddress, + amount: nftInputAmount, + tokenAmount: [tokenAmount], + }); + + const utxo2 = TW.Cardano.Proto.TxInput.create({ + outPoint: TW.Cardano.Proto.OutPoint.create({ + txHash: HexCoding.decode("ee414d635b3bc67831907354d274a31174664777c57c21ae923b9459e5644840"), + outputIndex: new Long(0), + }), + address: fromAddress, + amount: new Long(1000000), + }); + + const utxo3 = TW.Cardano.Proto.TxInput.create({ + outPoint: TW.Cardano.Proto.OutPoint.create({ + txHash: HexCoding.decode("6a7221dcc28353ed69b733391ffeb984a34c1e72293af111d59f9ddfa8639167"), + outputIndex: new Long(0), + }), + address: fromAddress, + amount: new Long(2000000), + }); + + const transferMessage = TW.Cardano.Proto.Transfer.create({ + toAddress: toAddress, + changeAddress: fromAddress, + amount: nftInputAmount, + tokenAmount: TW.Cardano.Proto.TokenBundle.create({ + token: [tokenAmount], + }), + }); + + const input = TW.Cardano.Proto.SigningInput.create({ + utxos: [utxo1, utxo2, utxo3], + privateKey: [privateKeyData], + ttl: new Long(89130965), + transferMessage: transferMessage, + }) + + const encoded = TW.Cardano.Proto.SigningInput.encode(input).finish(); + const outputData = AnySigner.sign(encoded, CoinType.cardano); + const output = TW.Cardano.Proto.SigningOutput.decode(outputData); + assert.equal( + HexCoding.encode(output.encoded), + "0x83a400838258206a7221dcc28353ed69b733391ffeb984a34c1e72293af111d59f9ddfa863916700825820aba499ec2f23529e70bb256ceaffcc6274a882cf02f29e5670c75ee980d7c2b800825820ee414d635b3bc67831907354d274a31174664777c57c21ae923b9459e5644840000182825839010ae9267a9b4f6edb4d69c548f3cba39654d7e67f37ffd5e1352f303e9d63b100507122da18e9cb58995a50f2e80ff65d00be6243cf89a186821a0012593aa1581c219820e6cb04316f41a337fea356480f412e7acc147d28f175f21b5ea153636f6f6c63617473736f6369657479343536370182583901299de4a3d24637ef4050033c214754963b829f43cbc311527d2b4bc8e36798a9ada6e3341ac239057e012225fe5c862f49b52db0f5e208731a002b1525021a0002b19b031a055007d5a1008182582088bd26e8656fa7dead846c3373588f0192da5bfb90bf5d3fb877decfb3b3fd085840da8656aca0dacc57d4c2d957fc7dff03908f6dcf60c48f1e40b3006e2fd0cfacfa4c24fa02e35a310572526586d4ce0d30bf660ba274c8efd507848cbe177d09f6" + ); + }); +}); diff --git a/wasm/tests/Blockchain/Ethereum.test.ts b/wasm/tests/Blockchain/Ethereum.test.ts index 33be2e86b32..357e8d306f3 100644 --- a/wasm/tests/Blockchain/Ethereum.test.ts +++ b/wasm/tests/Blockchain/Ethereum.test.ts @@ -1,17 +1,16 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import "mocha"; import { assert } from "chai"; import { Buffer } from "buffer"; -import { TW, WalletCore } from "../../dist"; +import { TW } from "../../dist"; describe("Ethereum", () => { + it("test address", () => { - const { PrivateKey, HexCoding, AnyAddress, CoinType, Curve } = WalletCore; + const { PrivateKey, HexCoding, AnyAddress, CoinType, Curve } = globalThis.core; const data = HexCoding.decode("727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06"); @@ -35,7 +34,7 @@ describe("Ethereum", () => { }); it("test signing transfer tx", () => { - const { HexCoding, AnySigner, CoinType } = WalletCore; + const { HexCoding, AnySigner, CoinType } = globalThis.core;; const input = TW.Ethereum.Proto.SigningInput.create({ toAddress: "0x3535353535353535353535353535353535353535", chainId: Buffer.from("01", "hex"), @@ -67,7 +66,7 @@ describe("Ethereum", () => { }); it("test signing eip1559 erc20 transfer tx", () => { - const { HexCoding, AnySigner, CoinType } = WalletCore; + const { HexCoding, AnySigner, CoinType } = globalThis.core;; const input = TW.Ethereum.Proto.SigningInput.create({ toAddress: "0x6b175474e89094c44da98b954eedeac495271d0f", @@ -98,7 +97,7 @@ describe("Ethereum", () => { }); it("test signing personal message", () => { - const { EthereumAbi, HexCoding, Hash, PrivateKey, Curve } = WalletCore; + const { HexCoding, Hash, PrivateKey, Curve } = globalThis.core; const message = Buffer.from("Some data"); const prefix = Buffer.from("\x19Ethereum Signed Message:\n" + message.length); const hash = Hash.keccak256(Buffer.concat([prefix, message])); @@ -115,7 +114,7 @@ describe("Ethereum", () => { }); it("test signing EIP712 message", () => { - const { EthereumAbi, HexCoding, Hash, PrivateKey, Curve } = WalletCore; + const { EthereumAbi, HexCoding, Hash, PrivateKey, Curve } = globalThis.core;; const key = PrivateKey.createWithData(Hash.keccak256(Buffer.from("cow"))); const message = { diff --git a/wasm/tests/Blockchain/Greenfield.test.ts b/wasm/tests/Blockchain/Greenfield.test.ts new file mode 100644 index 00000000000..222d3482d96 --- /dev/null +++ b/wasm/tests/Blockchain/Greenfield.test.ts @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; +import { TW } from "../../dist"; +import Long = require("long"); + +describe("Greenfield", () => { + + // Successfully broadcasted: https://greenfieldscan.com/tx/ED8508F3C174C4430B8EE718A6D6F0B02A8C516357BE72B1336CF74356529D19 + it("test signing transfer tx", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core; + + const msgSend = TW.Greenfield.Proto.Message.create({ + sendCoinsMessage: TW.Greenfield.Proto.Message.Send.create({ + fromAddress: "0xA815ae0b06dC80318121745BE40e7F8c6654e9f3", + toAddress: "0x8dbD6c7Ede90646a61Bbc649831b7c298BFd37A0", + amounts: [TW.Greenfield.Proto.Amount.create({ + amount: "1234500000000000", + denom: "BNB", + })] + }), + }); + const input = TW.Greenfield.Proto.SigningInput.create({ + signingMode: TW.Greenfield.Proto.SigningMode.Eip712, + encodingMode: TW.Greenfield.Proto.EncodingMode.Protobuf, + accountNumber: new Long(15952), + cosmosChainId: "greenfield_5600-1", + ethChainId: "5600", + sequence: new Long(0), + mode: TW.Greenfield.Proto.BroadcastMode.SYNC, + memo: "Trust Wallet test memo", + messages: [msgSend], + fee: TW.Greenfield.Proto.Fee.create({ + amounts: [TW.Greenfield.Proto.Amount.create({ + amount: "6000000000000", + denom: "BNB", + })], + gas: new Long(1200), + }), + privateKey: HexCoding.decode( + "825d2bb32965764a98338139412c7591ed54c951dd65504cd8ddaeaa0fea7b2a" + ), + }); + + const encoded = TW.Greenfield.Proto.SigningInput.encode(input).finish(); + + const outputData = AnySigner.sign(encoded, CoinType.greenfield); + const output = TW.Greenfield.Proto.SigningOutput.decode(outputData); + assert.equal( + output.serialized, + "{\"mode\":\"BROADCAST_MODE_SYNC\",\"tx_bytes\":\"CqwBCpEBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnEKKjB4QTgxNWFlMGIwNmRDODAzMTgxMjE3NDVCRTQwZTdGOGM2NjU0ZTlmMxIqMHg4ZGJENmM3RWRlOTA2NDZhNjFCYmM2NDk4MzFiN2MyOThCRmQzN0EwGhcKA0JOQhIQMTIzNDUwMDAwMDAwMDAwMBIWVHJ1c3QgV2FsbGV0IHRlc3QgbWVtbxJzClYKTQomL2Nvc21vcy5jcnlwdG8uZXRoLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohAhm/mQgs8vzaqBLW66HrqQNv86PYTBgXyElU1OiuKD/sEgUKAwjIBRIZChQKA0JOQhINNjAwMDAwMDAwMDAwMBCwCRpBwbRb1qEAWwaqVfmp1Mn7iMi7wwV/oPi2J2eW9NBIdNoky+ZL+uegS/kY+funCOrqVZ+Kbol9/djAV+bQaNUB0xw=\"}" + ); + }); +}); diff --git a/wasm/tests/Blockchain/Hedera.test.ts b/wasm/tests/Blockchain/Hedera.test.ts new file mode 100644 index 00000000000..e9dbbe0c881 --- /dev/null +++ b/wasm/tests/Blockchain/Hedera.test.ts @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; +import { Buffer } from "buffer"; +import { TW } from "../../dist"; +import Long = require("long"); + +describe("Hedera", () => { + + it("test address", () => { + const { PrivateKey, HexCoding, AnyAddress, CoinType, Curve } = globalThis.core; + const address = AnyAddress.createWithString("0.0.48694347", CoinType.hedera); + assert.equal(address.description(), "0.0.48694347"); + assert.equal(AnyAddress.isValid("0.0.48694347", CoinType.hedera), true); + assert.equal(AnyAddress.isValid("0.0.a", CoinType.hedera), false); + address.delete(); + }); + + it("test sign simple transfer Hedera", () => { + const { PrivateKey, HexCoding, AnySigner, AnyAddress, CoinType } = globalThis.core; + const transferMsg = TW.Hedera.Proto.TransferMessage.create({ + from: "0.0.48694347", + to: "0.0.48462050", + amount: new Long(100000000) + }) + + const transactionID = TW.Hedera.Proto.TransactionID.create({ + accountID: "0.0.48694347", + transactionValidStart: TW.Hedera.Proto.Timestamp.create({ + seconds: new Long(1667222879), + nanos: 749068449 + }) + }) + + const transactionBody = TW.Hedera.Proto.TransactionBody.create({ + memo: "", + nodeAccountID: "0.0.9", + transactionFee: new Long(100000000), + transactionValidDuration: new Long(120), + transfer: transferMsg, + transactionID: transactionID + }) + + const txDataInput = TW.Hedera.Proto.SigningInput.create({ + privateKey: PrivateKey.createWithData( + HexCoding.decode( + "0xe87a5584c0173263e138db689fdb2a7389025aaae7cb1a18a1017d76012130e8", + ), + ).data(), + body: transactionBody + }); + const input = TW.Hedera.Proto.SigningInput.encode(txDataInput).finish(); + const outputData = AnySigner.sign(input, CoinType.hedera); + const output = TW.Hedera.Proto.SigningOutput.decode(outputData); + assert.equal(HexCoding.encode(output.encoded), "0x0a440a150a0c08df9aff9a0610a1c197e502120518cb889c17120218091880c2d72f22020878721e0a1c0a0c0a0518e2f18d17108084af5f0a0c0a0518cb889c1710ff83af5f12660a640a205d3a70d08b2beafb72c7a68986b3ff819a306078b8c359d739e4966e82e6d40e1a40612589c3b15f1e3ed6084b5a3a5b1b81751578cac8d6c922f31731b3982a5bac80a22558b2197276f5bae49b62503a4d39448ceddbc5ef3ba9bee4c0f302f70c") + }); +}); diff --git a/wasm/tests/Blockchain/InternetComputer.test.ts b/wasm/tests/Blockchain/InternetComputer.test.ts new file mode 100644 index 00000000000..210a06961e0 --- /dev/null +++ b/wasm/tests/Blockchain/InternetComputer.test.ts @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; +import { Buffer } from "buffer"; +import { TW } from "../../dist"; +import Long = require("long"); + +describe("InternetComputer", () => { + + it("test address", () => { + const { PrivateKey, HexCoding, AnyAddress, CoinType, Curve } = globalThis.core; + const privateKeyBytes = HexCoding.decode("ee42eaada903e20ef6e5069f0428d552475c1ea7ed940842da6448f6ef9d48e7"); + + assert.isTrue(PrivateKey.isValid(privateKeyBytes, Curve.secp256k1)); + + const privateKey = PrivateKey.createWithData(privateKeyBytes); + const publicKey = privateKey.getPublicKeySecp256k1(false); + + assert.equal( + HexCoding.encode(publicKey.data()), + "0x048542e6fb4b17d6dfcac3948fe412c00d626728815ee7cc70509603f1bc92128a6e7548f3432d6248bc49ff44a1e50f6389238468d17f7d7024de5be9b181dbc8" + ); + + const address = AnyAddress.createWithPublicKey(publicKey, CoinType.internetComputer); + + assert.equal(address.description(), "2f25874478d06cf68b9833524a6390d0ba69c566b02f46626979a3d6a4153211"); + + privateKey.delete(); + publicKey.delete(); + address.delete(); + }); + + it("test sign", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core; + const privateKeyBytes = HexCoding.decode("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be"); + + const input = TW.InternetComputer.Proto.SigningInput.create({ + transaction: TW.InternetComputer.Proto.Transaction.create({ + transfer: TW.InternetComputer.Proto.Transaction.Transfer.create({ + toAccountIdentifier: "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a", + amount: new Long(100000000), + memo: new Long(0), + currentTimestampNanos: Long.fromString("1691709940000000000") + }) + }), + privateKey: privateKeyBytes + }); + + const encoded = TW.InternetComputer.Proto.SigningInput.encode(input).finish(); + + const outputData = AnySigner.sign(encoded, CoinType.internetComputer); + const output = TW.InternetComputer.Proto.SigningOutput.decode(outputData); + const signedTransaction = HexCoding.encode(output.signedTransaction); + + assert.equal(signedTransaction, "0x81826b5452414e53414354494f4e81a266757064617465a367636f6e74656e74a66c726571756573745f747970656463616c6c6e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d026b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f706263617267583b0a0012070a050880c2d72f2a220a20943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a3a0a088090caa5a3a78abd176d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f736967984013186f18b9181c189818b318a8186518b2186118d418971618b1187d18eb185818e01826182f1873183b185018cb185d18ef18d81839186418b3183218da1824182f184e18a01880182718c0189018c918a018fd18c418d9189e189818b318ef1874183b185118e118a51864185918e718ed18c71889186c1822182318ca6a726561645f7374617465a367636f6e74656e74a46c726571756573745f747970656a726561645f73746174656e696e67726573735f6578706972791b177a297215cfe8006673656e646572581d971cd2ddeecd1cf1b28be914d7a5c43441f6296f1f9966a7c8aff68d0265706174687381824e726571756573745f7374617475735820e8fbc2d5b0bf837b3a369249143e50d4476faafb2dd620e4e982586a51ebcf1e6d73656e6465725f7075626b65799858183018561830100607182a1886184818ce183d02010605182b188104000a0318420004183d18ab183a182118a81838184d184c187e1852188a187e18dc18d8184418ea18cd18c5189518ac188518b518bc181d188515186318bc18e618ab18d2184318d3187c184f18cd18f018de189b18b5181918dd18ef1889187218e71518c40418d4189718881843187218c611182e18cc18e6186b182118630218356a73656e6465725f7369679840182d182718201888188618ce187f0c182a187a18d718e818df18fb18d318d41118a5186a184b18341842185318d718e618e8187a1828186c186a183618461418f3183318bd18a618a718bc18d918c818b7189d186e1865188418ff18fd18e418e9187f181b18d705184818b21872187818d6181c161833184318a2"); + }); + + it("test sign with invalid private key", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core; + const privateKeyBytes = HexCoding.decode("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7000000"); + + const input = TW.InternetComputer.Proto.SigningInput.create({ + transaction: TW.InternetComputer.Proto.Transaction.create({ + transfer: TW.InternetComputer.Proto.Transaction.Transfer.create({ + toAccountIdentifier: "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a", + amount: new Long(100000000), + memo: new Long(0), + currentTimestampNanos: Long.fromString("1691709940000000000") + }) + }), + privateKey: privateKeyBytes + }); + + const encoded = TW.InternetComputer.Proto.SigningInput.encode(input).finish(); + + const outputData = AnySigner.sign(encoded, CoinType.internetComputer); + const output = TW.InternetComputer.Proto.SigningOutput.decode(outputData); + const signedTransaction = HexCoding.encode(output.signedTransaction); + + // Invalid private key + assert.equal(output.error, 15); + }); + + it("test sign with invalid to account identifier", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core; + const privateKeyBytes = HexCoding.decode("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be"); + + const input = TW.InternetComputer.Proto.SigningInput.create({ + transaction: TW.InternetComputer.Proto.Transaction.create({ + transfer: TW.InternetComputer.Proto.Transaction.Transfer.create({ + toAccountIdentifier: "643d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826b", + amount: new Long(100000000), + memo: new Long(0), + currentTimestampNanos: Long.fromString("1691709940000000000") + }) + }), + privateKey: privateKeyBytes + }); + + const encoded = TW.InternetComputer.Proto.SigningInput.encode(input).finish(); + + const outputData = AnySigner.sign(encoded, CoinType.internetComputer); + const output = TW.InternetComputer.Proto.SigningOutput.decode(outputData); + const signedTransaction = HexCoding.encode(output.signedTransaction); + + // Invalid account identifier + assert.equal(output.error, 16); + }); + + it("test sign with invalid amount", () => { + const { HexCoding, AnySigner, CoinType } = globalThis.core; + const privateKeyBytes = HexCoding.decode("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be"); + + const input = TW.InternetComputer.Proto.SigningInput.create({ + transaction: TW.InternetComputer.Proto.Transaction.create({ + transfer: TW.InternetComputer.Proto.Transaction.Transfer.create({ + toAccountIdentifier: "943d12e762f43806782f524b8f90297298a6d79e4749b41b585ec427409c826a", + amount: new Long(0), + memo: new Long(0), + currentTimestampNanos: Long.fromString("1691709940000000000") + }) + }), + privateKey: privateKeyBytes + }); + + const encoded = TW.InternetComputer.Proto.SigningInput.encode(input).finish(); + + const outputData = AnySigner.sign(encoded, CoinType.internetComputer); + const output = TW.InternetComputer.Proto.SigningOutput.decode(outputData); + + assert.equal(output.error, 23); + assert.equal(output.errorMessage, 'Invalid input token amount'); + }); +}); \ No newline at end of file diff --git a/wasm/tests/Blockchain/Sui.test.ts b/wasm/tests/Blockchain/Sui.test.ts new file mode 100644 index 00000000000..fe4a3e15bcf --- /dev/null +++ b/wasm/tests/Blockchain/Sui.test.ts @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; +import { Buffer } from "buffer"; +import { TW } from "../../dist"; + +describe("Sui", () => { + it("test sign Sui", () => { + const { PrivateKey, HexCoding, AnySigner, AnyAddress, CoinType } = globalThis.core; + const txDataInput = TW.Sui.Proto.SigningInput.create({ + signDirectMessage: TW.Sui.Proto.SignDirect.create({ + unsignedTxMsg: "AAACAAgQJwAAAAAAAAAgJZ/4B0q0Jcu0ifI24Y4I8D8aeFa998eih3vWT3OLUBUCAgABAQAAAQEDAAAAAAEBANV1rX8Y6UhGKlz2mPVk7zlKdSpx/sYkk6+KBVwBLA1QAQbywsjB2JZN8QGdZhbpcFcZvrq9kx2idVy5SM635olk7AIAAAAAAAAgYEVuxmf1zRBGdoDr+VDtMpIFF12s2Ua7I2ru1XyGF8/Vda1/GOlIRipc9pj1ZO85SnUqcf7GJJOvigVcASwNUAEAAAAAAAAA0AcAAAAAAAAA" + }), + privateKey: HexCoding.decode( + "0x3823dce5288ab55dd1c00d97e91933c613417fdb282a0b8b01a7f5f5a533b266", + ) + }); + const input = TW.Sui.Proto.SigningInput.encode(txDataInput).finish(); + const outputData = AnySigner.sign(input, CoinType.sui); + const output = TW.Sui.Proto.SigningOutput.decode(outputData); + assert.equal(output.signature, "APxPduNVvHj2CcRcHOtiP2aBR9qP3vO2Cb0g12PI64QofDB6ks33oqe/i/iCTLcop2rBrkczwrayZuJOdi7gvwNqfN7sFqdcD/Z4e8I1YQlGkDMCK7EOgmydRDqfH8C9jg==") + }); +}); diff --git a/wasm/tests/Blockchain/TheOpenNetwork.test.ts b/wasm/tests/Blockchain/TheOpenNetwork.test.ts new file mode 100644 index 00000000000..b96cb551675 --- /dev/null +++ b/wasm/tests/Blockchain/TheOpenNetwork.test.ts @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; +import { Buffer } from "buffer"; +import { TW } from "../../dist"; +import Long = require("long"); + +describe("TheOpenNetwork", () => { + it("test address from private key TheOpenNetwork", () => { + const { PrivateKey, HexCoding, AnyAddress, CoinType, Curve } = globalThis.core; + let data = HexCoding.decode("63474e5fe9511f1526a50567ce142befc343e71a49b865ac3908f58667319cb8"); + let privateKey = PrivateKey.createWithData(data); + + assert.isTrue(PrivateKey.isValid(data, Curve.ed25519)); + + let publicKey = privateKey.getPublicKeyEd25519(); + let address = AnyAddress.createWithPublicKey(publicKey, CoinType.ton) + + assert.equal(publicKey.description(), "f42c77f931bea20ec5d0150731276bbb2e2860947661245b2319ef8133ee8d41"); + assert.equal(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV"); + }); + + it("test address from public key TheOpenNetwork", () => { + const { PublicKey, PublicKeyType, HexCoding, AnyAddress, CoinType } = globalThis.core; + let publicKey = PublicKey.createWithData(HexCoding.decode("f42c77f931bea20ec5d0150731276bbb2e2860947661245b2319ef8133ee8d41"), PublicKeyType.ed25519); + let address = AnyAddress.createWithPublicKey(publicKey, CoinType.ton); + assert.equal(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV"); + }); + + it("test address from raw string TheOpenNetwork", () => { + const { AnyAddress, CoinType } = globalThis.core; + let addressString = "0:66fbe3c5c03bf5c82792f904c9f8bf28894a6aa3d213d41c20569b654aadedb3"; + let address = AnyAddress.createWithString(addressString, CoinType.ton); + assert.equal(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV"); + }); + + it("test address invalid hex TheOpenNetwork", () => { + const { AnyAddress, CoinType } = globalThis.core; + let addressString = "0:yahoo3c5c03bf5c82792f904c9f8bf28894a6aa3d213d41c20569b654aadedb3"; + let valid = AnyAddress.isValid(addressString, CoinType.ton); + assert.isFalse(valid); + }); + + it("test address invalid workchain id TheOpenNetwork", () => { + const { AnyAddress, CoinType } = globalThis.core; + let addressString = "a:66fbe3c5c03bf5c82792f904c9f8bf28894a6aa3d213d41c20569b654aadedb3"; + let valid = AnyAddress.isValid(addressString, CoinType.ton); + assert.isFalse(valid); + }); + + it("test address from user friendly string TheOpenNetwork", () => { + const { AnyAddress, CoinType } = globalThis.core; + let addressString = "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q"; + let address = AnyAddress.createWithString(addressString, CoinType.ton); + assert.equal(address.description(), "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV"); + }); + + it("test address from user friendly invalid base64 decoding TheOpenNetwork", () => { + const { AnyAddress, CoinType } = globalThis.core; + let addressString = "MwCKhieGGl3ZbJ2zzggHsSLaXtRzk0znVopbSxw2HLsors=#"; + let valid = AnyAddress.isValid(addressString, CoinType.ton); + assert.isFalse(valid); + }); + + it("test sign TheOpenNetwork", () => { + const { PrivateKey, HexCoding, CoinType, AnySigner } = globalThis.core; + + let privateKeyData = HexCoding.decode("c38f49de2fb13223a9e7d37d5d0ffbdd89a5eb7c8b0ee4d1c299f2cefe7dc4a0"); + + let transfer = TW.TheOpenNetwork.Proto.Transfer.create({ + dest: "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q", + amount: new Long(10), + mode: (TW.TheOpenNetwork.Proto.SendMode.PAY_FEES_SEPARATELY | TW.TheOpenNetwork.Proto.SendMode.IGNORE_ACTION_PHASE_ERRORS), + bounceable: true, + }); + + let input = TW.TheOpenNetwork.Proto.SigningInput.create({ + messages: [transfer], + privateKey: PrivateKey.createWithData(privateKeyData).data(), + sequenceNumber: 6, + expireAt: 1671132440, + walletVersion: TW.TheOpenNetwork.Proto.WalletVersion.WALLET_V4_R2, + }); + + const encoded = TW.TheOpenNetwork.Proto.SigningInput.encode(input).finish(); + let outputData = AnySigner.sign(encoded, CoinType.ton); + let output = TW.TheOpenNetwork.Proto.SigningOutput.decode(outputData); + + // tx: https://tonscan.org/tx/3Z4tHpXNLyprecgu5aTQHWtY7dpHXEoo11MAX61Xyg0= + let expectedString = "te6cckEBBAEArQABRYgBsLf6vJOEq42xW0AoyWX0K+uBMUcXFDLFqmkDg6k1Io4MAQGcEUPkil2aZ4s8KKparSep/OKHMC8vuXafFbW2HGp/9AcTRv0J5T4dwyW1G0JpHw+g5Ov6QI3Xo0O9RFr3KidICimpoxdjm3UYAAAABgADAgFiYgAzffHi4B365BPJfIJk/F+URKU1UekJ6g4QK02ypVb22YhQAAAAAAAAAAAAAAAAAQMAAA08Nzs="; + + assert.equal(output.encoded, expectedString) + }); + + it("test jetton transfer TheOpenNetwork", () => { + const { PrivateKey, HexCoding, CoinType, AnySigner } = globalThis.core; + + let privateKeyData = HexCoding.decode("c054900a527538c1b4325688a421c0469b171c29f23a62da216e90b0df2412ee"); + + let jettonTransfer = TW.TheOpenNetwork.Proto.JettonTransfer.create({ + jettonAmount: new Long(500 * 1000 * 1000), + toOwner: "EQAFwMs5ha8OgZ9M4hQr80z9NkE7rGxUpE1hCFndiY6JnDx8", + responseAddress: "EQBaKIMq5Am2p_rfR1IFTwsNWHxBkOpLTmwUain5Fj4llTXk", + forwardAmount: new Long(1) + }); + + let transfer = TW.TheOpenNetwork.Proto.Transfer.create({ + dest: "EQBiaD8PO1NwfbxSkwbcNT9rXDjqhiIvXWymNO-edV0H5lja", + amount: new Long(100 * 1000 * 1000), + mode: (TW.TheOpenNetwork.Proto.SendMode.PAY_FEES_SEPARATELY | TW.TheOpenNetwork.Proto.SendMode.IGNORE_ACTION_PHASE_ERRORS), + comment: "test comment", + bounceable: true, + jettonTransfer: jettonTransfer, + }); + + let input = TW.TheOpenNetwork.Proto.SigningInput.create({ + messages: [transfer], + privateKey: PrivateKey.createWithData(privateKeyData).data(), + sequenceNumber: 1, + expireAt: 1787693046, + walletVersion: TW.TheOpenNetwork.Proto.WalletVersion.WALLET_V4_R2, + }); + + const encoded = TW.TheOpenNetwork.Proto.SigningInput.encode(input).finish(); + let outputData = AnySigner.sign(encoded, CoinType.ton); + let output = TW.TheOpenNetwork.Proto.SigningOutput.decode(outputData); + + // tx: https://testnet.tonscan.org/tx/Er_oT5R3QK7D-qVPBKUGkJAOOq6ayVls-mgEphpI9Ck= + let expectedString = "te6cckECBAEAARUAAUWIALRRBlXIE21P9b6OpAqeFhqw+IMh1Jac2CjUU/IsfEsqDAEBnGiFlaLItV573gJqBvctP5j3jVKlLuxmO+pnW0QGlXjXgzjw5YeTNwRG9upJHOl6GA3pFetKNojqGzfkxku+owUpqaMXao4H9gAAAAEAAwIBaGIAMTQfh52puD7eKUmDbhqfta4cdUMRF662Uxp3zzqug/MgL68IAAAAAAAAAAAAAAAAAAEDAMoPin6lAAAAAAAAAABB3NZQCAALgZZzC14dAz6ZxChX5pn6bIJ3WNipSJrCELO7Ex0TOQAWiiDKuQJtqf630dSBU8LDVh8QZDqS05sFGop+RY+JZUICAAAAAHRlc3QgY29tbWVudG/bd5c="; + + assert.equal(output.encoded, expectedString) + }); + + +}); + + diff --git a/wasm/tests/CoinType.test.ts b/wasm/tests/CoinType.test.ts index dd517715e8e..75a0bed0408 100644 --- a/wasm/tests/CoinType.test.ts +++ b/wasm/tests/CoinType.test.ts @@ -1,16 +1,13 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import "mocha"; import { assert } from "chai"; -import { WalletCore } from "../dist"; describe("CoinType", () => { it("test raw value", () => { - const { CoinType } = WalletCore; + const { CoinType } = globalThis.core; assert.equal(CoinType.bitcoin.value, 0); assert.equal(CoinType.litecoin.value, 2); @@ -19,6 +16,43 @@ describe("CoinType", () => { assert.equal(CoinType.binance.value, 714); assert.equal(CoinType.cosmos.value, 118); assert.equal(CoinType.solana.value, 501); + }); + + it("test CoinTypeExt methods", () => { + const { CoinType, CoinTypeExt, Blockchain, Purpose, Curve, Derivation } = globalThis.core; + + assert.equal(CoinTypeExt.blockchain(CoinType.solana), Blockchain.solana); + assert.equal(CoinTypeExt.purpose(CoinType.solana), Purpose.bip44); + assert.equal(CoinTypeExt.curve(CoinType.solana), Curve.ed25519); + assert.isTrue(CoinTypeExt.validate(CoinType.solana, "Bxp8yhH9zNwxyE4UqxP7a7hgJ5xTZfxNNft7YJJ2VRjT")) + assert.equal(CoinTypeExt.derivationPath(CoinType.solana), "m/44'/501'/0'"); + assert.equal(CoinTypeExt.derivationPathWithDerivation(CoinType.solana, Derivation.solanaSolana), "m/44'/501'/0'/0'"); + }); + + it("test deriveAddress", () => { + const { CoinType, CoinTypeExt, PrivateKey, HexCoding } = globalThis.core; + + const data = HexCoding.decode("8778cc93c6596387e751d2dc693bbd93e434bd233bc5b68a826c56131821cb63"); + const key = PrivateKey.createWithData(data); + const addr = CoinTypeExt.deriveAddress(CoinType.solana, key); + assert.equal(addr, "7v91N7iZ9mNicL8WfG6cgSCKyRXydQjLh6UYBWwm6y1Q") + }); + + it("test deriveAddressFromPublicKey", () => { + const { CoinType, CoinTypeExt, PublicKey, PublicKeyType, HexCoding } = globalThis.core; + + const data = HexCoding.decode("044516c4aa5352035e1bb5be132694e1389a4ac37d32e5e717d35ee4c4dfab513226a9d14ea37a55962ad3644a08e2ce551b4495beabb9b09e688c7b92eba18acc"); + const key = PublicKey.createWithData(data, PublicKeyType.secp256k1Extended); + const addr = CoinTypeExt.deriveAddressFromPublicKey(CoinType.ethereum, key); + assert.equal(addr, "0x996891c410FB76C19DBA72C6f6cEFF2d9DD069b1"); + }); + + it("test deriveAddressFromPublicKeyAndDerivation", () => { + const { CoinType, CoinTypeExt, Derivation, PublicKey, PublicKeyType, HexCoding } = globalThis.core; + const data = HexCoding.decode("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"); + const key = PublicKey.createWithData(data, PublicKeyType.secp256k1); + const addr = CoinTypeExt.deriveAddressFromPublicKeyAndDerivation(CoinType.bitcoin, key, Derivation.bitcoinSegwit); + assert.equal(addr, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"); }); }); diff --git a/wasm/tests/HDVersion.test.ts b/wasm/tests/HDVersion.test.ts new file mode 100644 index 00000000000..864f5e2cca5 --- /dev/null +++ b/wasm/tests/HDVersion.test.ts @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; + +describe("HDVersion", () => { + it("test isPublic", () => { + const { HDVersion, HDVersionExt } = globalThis.core; + + assert.isFalse(HDVersionExt.isPublic(HDVersion.none)); + assert.isTrue(HDVersionExt.isPublic(HDVersion.xpub)); + assert.isFalse(HDVersionExt.isPublic(HDVersion.xprv)); + assert.isTrue(HDVersionExt.isPublic(HDVersion.ypub)); + assert.isFalse(HDVersionExt.isPublic(HDVersion.yprv)); + assert.isTrue(HDVersionExt.isPublic(HDVersion.zpub)); + assert.isFalse(HDVersionExt.isPublic(HDVersion.zprv)); + assert.isTrue(HDVersionExt.isPublic(HDVersion.ltub)); + assert.isFalse(HDVersionExt.isPublic(HDVersion.ltpv)); + assert.isTrue(HDVersionExt.isPublic(HDVersion.mtub)); + assert.isFalse(HDVersionExt.isPublic(HDVersion.mtpv)); + assert.isTrue(HDVersionExt.isPublic(HDVersion.dpub)); + assert.isFalse(HDVersionExt.isPublic(HDVersion.dprv)); + assert.isTrue(HDVersionExt.isPublic(HDVersion.dgub)); + assert.isFalse(HDVersionExt.isPublic(HDVersion.dgpv)); + }); + + it("test isPrivate", () => { + const { HDVersion, HDVersionExt } = globalThis.core; + + assert.isFalse(HDVersionExt.isPrivate(HDVersion.none)); + assert.isFalse(HDVersionExt.isPrivate(HDVersion.xpub)); + assert.isTrue(HDVersionExt.isPrivate(HDVersion.xprv)); + assert.isFalse(HDVersionExt.isPrivate(HDVersion.ypub)); + assert.isTrue(HDVersionExt.isPrivate(HDVersion.yprv)); + assert.isFalse(HDVersionExt.isPrivate(HDVersion.zpub)); + assert.isTrue(HDVersionExt.isPrivate(HDVersion.zprv)); + assert.isFalse(HDVersionExt.isPrivate(HDVersion.ltub)); + assert.isTrue(HDVersionExt.isPrivate(HDVersion.ltpv)); + assert.isFalse(HDVersionExt.isPrivate(HDVersion.mtub)); + assert.isTrue(HDVersionExt.isPrivate(HDVersion.mtpv)); + assert.isFalse(HDVersionExt.isPrivate(HDVersion.dpub)); + assert.isTrue(HDVersionExt.isPrivate(HDVersion.dprv)); + assert.isFalse(HDVersionExt.isPrivate(HDVersion.dgub)); + assert.isTrue(HDVersionExt.isPrivate(HDVersion.dgpv)); + }); +}); diff --git a/wasm/tests/HDWallet.test.ts b/wasm/tests/HDWallet.test.ts index 56a612bd510..a45fc92c3dd 100644 --- a/wasm/tests/HDWallet.test.ts +++ b/wasm/tests/HDWallet.test.ts @@ -1,18 +1,14 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import "mocha"; import { assert } from "chai"; -import { WalletCore } from "../dist"; -import { Buffer } from "buffer"; describe("HDWallet", () => { it("test creating 24 words", () => { - const { HDWallet, Mnemonic } = WalletCore; + const { HDWallet, Mnemonic } = globalThis.core; var wallet = HDWallet.create(256, "password"); const mnemonic = wallet.mnemonic(); @@ -24,7 +20,7 @@ describe("HDWallet", () => { }); it("test deriving Ethereum address", () => { - const { HDWallet, CoinType } = WalletCore; + const { HDWallet, CoinType } = globalThis.core; var wallet = HDWallet.createWithMnemonic("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal", "TREZOR"); const address = wallet.getAddressForCoin(CoinType.ethereum); diff --git a/wasm/tests/HRP.test.ts b/wasm/tests/HRP.test.ts index 5b94f747062..e6e81505003 100644 --- a/wasm/tests/HRP.test.ts +++ b/wasm/tests/HRP.test.ts @@ -1,16 +1,13 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import "mocha"; import { assert } from "chai"; -import { WalletCore } from "../dist"; describe("HRP", () => { it("test string value", () => { - const { HRP, describeHRP } = WalletCore; + const { HRP, describeHRP } = globalThis.core; assert.equal(describeHRP(HRP.bitcoin), "bc"); assert.equal(describeHRP(HRP.binance), "bnb"); diff --git a/wasm/tests/Hash.test.ts b/wasm/tests/Hash.test.ts index 33b2c723bb0..1747995eb48 100644 --- a/wasm/tests/Hash.test.ts +++ b/wasm/tests/Hash.test.ts @@ -1,17 +1,14 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import "mocha"; import { assert } from "chai"; import { Buffer } from "buffer"; -import { WalletCore } from "../dist"; describe("Hash", () => { it("test keccak256", () => { - const { Hash, HexCoding } = WalletCore; + const { Hash, HexCoding } = globalThis.core; const sha3Hash = Hash.keccak256(Buffer.from("Test keccak-256")); @@ -22,7 +19,7 @@ describe("Hash", () => { }); it("test sha256", () => { - const { Hash, HexCoding } = WalletCore; + const { Hash, HexCoding } = globalThis.core; const sha256Hash = Hash.sha256(Buffer.from("Test hash")); assert.equal( @@ -32,7 +29,7 @@ describe("Hash", () => { }); it("test sha512_256", () => { - const { Hash, HexCoding } = WalletCore; + const { Hash, HexCoding } = globalThis.core; const hash = Hash.sha512_256(Buffer.from("hello")); assert.equal( diff --git a/wasm/tests/HexCoding.test.ts b/wasm/tests/HexCoding.test.ts index a3c8879e495..9879072a047 100644 --- a/wasm/tests/HexCoding.test.ts +++ b/wasm/tests/HexCoding.test.ts @@ -1,16 +1,13 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import "mocha"; import { assert } from "chai"; -import { WalletCore } from "../dist"; describe("HexCoding", () => { it("test encoding / decoding hex string", () => { - const { HexCoding } = WalletCore; + const { HexCoding } = globalThis.core; const expected = new Uint8Array([0x52, 0x8]); const decoded = HexCoding.decode("0x5208"); diff --git a/wasm/tests/KeyStore+extension.test.ts b/wasm/tests/KeyStore+extension.test.ts new file mode 100644 index 00000000000..9cc1dcf5c22 --- /dev/null +++ b/wasm/tests/KeyStore+extension.test.ts @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; +import { KeyStore } from "../dist"; +import { ChromeStorageMock } from "./mock"; + +describe("KeyStore", async () => { + it("test ExtensionStorage", async () => { + const { CoinType, HexCoding, StoredKeyEncryption } = globalThis.core; + const mnemonic = globalThis.mnemonic as string; + const password = globalThis.password as string; + + const walletIdsKey = "all-wallet-ids"; + const storage = new KeyStore.ExtensionStorage( + walletIdsKey, + new ChromeStorageMock() + ); + const keystore = new KeyStore.Default(globalThis.core, storage); + + var wallet = await keystore.import(mnemonic, "Coolw", password, [ + CoinType.bitcoin, + ], StoredKeyEncryption.aes128Ctr); + + assert.equal(wallet.name, "Coolw"); + assert.equal(wallet.type, "mnemonic"); + assert.equal(wallet.version, 3); + + const account = wallet.activeAccounts[0]; + const key = await keystore.getKey(wallet.id, password, account); + + assert.equal( + HexCoding.encode(key.data()), + "0xd2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f" + ); + assert.equal(account.address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + assert.equal( + account.extendedPublicKey, + "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn" + ); + assert.equal( + account.publicKey, + "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006" + ); + + wallet = await keystore.addAccounts(wallet.id, password, [ + CoinType.ethereum, + CoinType.binance, + ]); + + assert.equal(wallet.activeAccounts.length, 3); + assert.isTrue(await keystore.hasWallet(wallet.id)); + assert.isFalse(await keystore.hasWallet("invalid-id")); + + const exported = await keystore.export(wallet.id, password); + assert.equal(exported, mnemonic); + + const wallets = await keystore.loadAll(); + + await wallets.forEach((w) => { + keystore.delete(w.id, password); + }); + }).timeout(10000); + + it("test ExtensionStorage AES256", async () => { + const { CoinType, HexCoding, StoredKeyEncryption } = globalThis.core; + const mnemonic = globalThis.mnemonic as string; + const password = globalThis.password as string; + + const walletIdsKey = "all-wallet-ids"; + const storage = new KeyStore.ExtensionStorage( + walletIdsKey, + new ChromeStorageMock() + ); + const keystore = new KeyStore.Default(globalThis.core, storage); + + var wallet = await keystore.import(mnemonic, "Coolw", password, [ + CoinType.bitcoin, + ], StoredKeyEncryption.aes256Ctr); + + assert.equal(wallet.name, "Coolw"); + assert.equal(wallet.type, "mnemonic"); + assert.equal(wallet.version, 3); + + const account = wallet.activeAccounts[0]; + const key = await keystore.getKey(wallet.id, password, account); + + assert.equal( + HexCoding.encode(key.data()), + "0xd2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f" + ); + assert.equal(account.address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + assert.equal( + account.extendedPublicKey, + "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn" + ); + assert.equal( + account.publicKey, + "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006" + ); + + wallet = await keystore.addAccounts(wallet.id, password, [ + CoinType.ethereum, + CoinType.binance, + ]); + + assert.equal(wallet.activeAccounts.length, 3); + assert.isTrue(await keystore.hasWallet(wallet.id)); + assert.isFalse(await keystore.hasWallet("invalid-id")); + + const exported = await keystore.export(wallet.id, password); + assert.equal(exported, mnemonic); + + const wallets = await keystore.loadAll(); + + await wallets.forEach((w) => { + keystore.delete(w.id, password); + }); + }).timeout(10000); +}); diff --git a/wasm/tests/KeyStore+fs.test.ts b/wasm/tests/KeyStore+fs.test.ts new file mode 100644 index 00000000000..78359293dd6 --- /dev/null +++ b/wasm/tests/KeyStore+fs.test.ts @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; +import * as fs from "fs"; +import { KeyStore } from "../dist"; + +describe("KeyStore", async () => { + it("test FileSystemStorage", async () => { + const { CoinType, HexCoding, StoredKeyEncryption } = globalThis.core; + const mnemonic = globalThis.mnemonic as string; + const password = globalThis.password as string; + const testDir = "/tmp/wasm-test"; + + fs.mkdirSync(testDir, { recursive: true }); + + const storage = new KeyStore.FileSystemStorage(testDir); + const keystore = new KeyStore.Default(globalThis.core, storage); + + var wallet = await keystore.import(mnemonic, "Coolw", password, [ + CoinType.bitcoin, + ], StoredKeyEncryption.aes128Ctr); + const stats = fs.statSync(storage.getFilename(wallet.id)); + + assert.isTrue(stats.isFile()); + assert.isTrue(stats.size > 0); + assert.equal(wallet.name, "Coolw"); + assert.equal(wallet.type, "mnemonic"); + assert.equal(wallet.version, 3); + + const account = wallet.activeAccounts[0]; + const key = await keystore.getKey(wallet.id, password, account); + + assert.equal( + HexCoding.encode(key.data()), + "0xd2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f" + ); + assert.equal(account.address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + assert.equal( + account.extendedPublicKey, + "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn" + ); + assert.equal( + account.publicKey, + "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006" + ); + + wallet = await keystore.addAccounts(wallet.id, password, [ + CoinType.ethereum, + CoinType.binance, + ]); + + assert.equal(wallet.activeAccounts.length, 3); + assert.isTrue(await keystore.hasWallet(wallet.id)); + assert.isFalse(await keystore.hasWallet("invalid-id")); + + const exported = await keystore.export(wallet.id, password); + assert.equal(exported, mnemonic); + + const wallets = await keystore.loadAll(); + + await wallets.forEach((w) => { + keystore.delete(w.id, password); + }); + }).timeout(10000); + + it("test FileSystemStorage AES256", async () => { + const { CoinType, HexCoding, StoredKeyEncryption } = globalThis.core; + const mnemonic = globalThis.mnemonic as string; + const password = globalThis.password as string; + const testDir = "/tmp/wasm-test"; + + fs.mkdirSync(testDir, { recursive: true }); + + const storage = new KeyStore.FileSystemStorage(testDir); + const keystore = new KeyStore.Default(globalThis.core, storage); + + var wallet = await keystore.import(mnemonic, "Coolw", password, [ + CoinType.bitcoin, + ], StoredKeyEncryption.aes256Ctr); + const stats = fs.statSync(storage.getFilename(wallet.id)); + + assert.isTrue(stats.isFile()); + assert.isTrue(stats.size > 0); + assert.equal(wallet.name, "Coolw"); + assert.equal(wallet.type, "mnemonic"); + assert.equal(wallet.version, 3); + + const account = wallet.activeAccounts[0]; + const key = await keystore.getKey(wallet.id, password, account); + + assert.equal( + HexCoding.encode(key.data()), + "0xd2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f" + ); + assert.equal(account.address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + assert.equal( + account.extendedPublicKey, + "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn" + ); + assert.equal( + account.publicKey, + "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006" + ); + + wallet = await keystore.addAccounts(wallet.id, password, [ + CoinType.ethereum, + CoinType.binance, + ]); + + assert.equal(wallet.activeAccounts.length, 3); + assert.isTrue(await keystore.hasWallet(wallet.id)); + assert.isFalse(await keystore.hasWallet("invalid-id")); + + const exported = await keystore.export(wallet.id, password); + assert.equal(exported, mnemonic); + + const wallets = await keystore.loadAll(); + + await wallets.forEach((w) => { + keystore.delete(w.id, password); + }); + }).timeout(10000); +}); diff --git a/wasm/tests/Mnemonic.test.ts b/wasm/tests/Mnemonic.test.ts index c178e6ebf83..f392a659e27 100644 --- a/wasm/tests/Mnemonic.test.ts +++ b/wasm/tests/Mnemonic.test.ts @@ -1,16 +1,14 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import "mocha"; import { assert } from "chai"; -import { WalletCore } from "../dist"; describe("Mnemonic", () => { + it("test isValid", () => { - const { Mnemonic } = WalletCore; + const { Mnemonic } = globalThis.core; assert.isTrue( Mnemonic.isValid( @@ -25,7 +23,7 @@ describe("Mnemonic", () => { }); it("test isValidWord", () => { - const { Mnemonic } = WalletCore; + const { Mnemonic } = globalThis.core; assert.isTrue(Mnemonic.isValidWord("credit")); @@ -35,7 +33,7 @@ describe("Mnemonic", () => { }); it("test suggest", () => { - const { Mnemonic } = WalletCore; + const { Mnemonic } = globalThis.core; assert.equal(Mnemonic.suggest("air"), "air airport"); assert.equal(Mnemonic.suggest("rob"), "robot robust"); diff --git a/wasm/tests/PBKDF2.test.ts b/wasm/tests/PBKDF2.test.ts index ee2199416fa..8658a79d69e 100644 --- a/wasm/tests/PBKDF2.test.ts +++ b/wasm/tests/PBKDF2.test.ts @@ -1,17 +1,14 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import "mocha"; import { assert } from "chai"; import { Buffer } from "buffer"; -import { WalletCore } from "../dist"; describe("PBKDF2", () => { it("test sha256 hmac", () => { - const { PBKDF2, HexCoding } = WalletCore; + const { PBKDF2, HexCoding } = globalThis.core; const password = Buffer.from("password"); const salt = Buffer.from("salt"); diff --git a/wasm/tests/StoredKey.test.ts b/wasm/tests/StoredKey.test.ts index 2b0b6aa4950..7f3b04638c2 100644 --- a/wasm/tests/StoredKey.test.ts +++ b/wasm/tests/StoredKey.test.ts @@ -1,17 +1,14 @@ -// Copyright © 2017-2022 Trust Wallet. +// SPDX-License-Identifier: Apache-2.0 // -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. +// Copyright © 2017 Trust Wallet. import "mocha"; import { assert } from "chai"; -import { WalletCore } from "../dist"; import { Buffer } from "buffer"; describe("StoredKey", () => { it("test importing mnemonic", () => { - const { StoredKey, CoinType } = WalletCore; + const { StoredKey, CoinType } = globalThis.core; const mnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; const password = Buffer.from("password"); diff --git a/wasm/tests/initWasm.test.ts b/wasm/tests/initWasm.test.ts new file mode 100644 index 00000000000..e20b9cb00b6 --- /dev/null +++ b/wasm/tests/initWasm.test.ts @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; +import { initWasm } from "../dist"; + +describe("Module", () => { + it("load test", (done) => { + initWasm().then((WalletCore) => { + assert.isDefined(WalletCore); + assert.isNotNull(WalletCore); + done(); + }); + }).timeout(5000); +}); diff --git a/wasm/tests/mock.ts b/wasm/tests/mock.ts new file mode 100644 index 00000000000..7064f115489 --- /dev/null +++ b/wasm/tests/mock.ts @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import { Events, Storage } from "webextension-polyfill"; + +export class ChromeStorageMock implements Storage.StorageArea { + object = {}; + + get( + keys?: string | string[] | Record | null | undefined + ): Promise> { + var ids: string[] = []; + if (typeof keys === "string") { + ids.push(keys); + } else if (keys instanceof Array) { + ids = ids.concat(keys); + } + + var result: Record = {}; + ids.forEach((id) => { + result[id] = this.object[id]; + }); + return Promise.resolve(result); + } + + set(items: Record): Promise { + Object.keys(items).forEach((key) => { + this.object[key] = items[key]; + }); + return Promise.resolve(); + } + + remove(keys: string | string[]): Promise { + var ids: string[] = []; + if (typeof keys === "string") { + ids.push(keys); + } + ids = ids.concat(keys); + ids.forEach((id) => delete this.object[id]); + return Promise.resolve(); + } + + clear(): Promise { + throw new Error("Method not implemented."); + } + + onChanged: Events.Event< + (changes: Storage.StorageAreaOnChangedChangesType) => void + > = {} as any; +} diff --git a/wasm/tests/setup.test.ts b/wasm/tests/setup.test.ts new file mode 100644 index 00000000000..54d6664beee --- /dev/null +++ b/wasm/tests/setup.test.ts @@ -0,0 +1,9 @@ +import "mocha"; +import { initWasm } from "../dist"; + +before(async () => { + globalThis.mnemonic = + "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; + globalThis.password = "password"; + globalThis.core = await initWasm(); +}); diff --git a/wasm/tsconfig.json b/wasm/tsconfig.json index 808eaf5fc74..bcca79e9e1b 100644 --- a/wasm/tsconfig.json +++ b/wasm/tsconfig.json @@ -2,14 +2,17 @@ "compilerOptions": { "target": "es5", "module": "commonjs", + "moduleResolution": "node", "declaration": true, "outDir": "./dist", "strict": true, + "skipLibCheck": true, "typeRoots": [ "./node_modules/@types" ], "types": [ - "node" + "node", + "mocha" ], "noImplicitAny": false },